#what is "state data"? Also maybe we
1 messages · Page 1 of 1 (latest)
no i meant this
I mean there are essentially two ways to do this
with a bunch of hybrid ways in between
One is basically polymorphism, and one is composition.
For polymorphism you basically have:
Dictionary<Vector2Int, TileData> tiles;
abstract class TileData {
string name;
// etc
}
class Chest : TileData {
Inventory contents;
}
class Crop : TileData {
int growthProgress;
int growthLimit;
}```
For composition you basically have:
```cs
Dictionary<Vector2Int, TileData> tiles;
class TileData {
List<Attribute> attributes;
}
struct Attribute {
// choose enums or strings here
string/AttributeType attributeName;
double doubleVal;
string stringVal;
// helper functions and properties to deal with the different type possibility.
}```
The latter seems more data efficient but also a bit harder to reason around
I guess my conception would be like a behavior layer that is stateless, and a data layer that has the data. Which may be an anti-pattern as it's more of a webapp framework.
like
struct helloStruct = { string message = "hello"}
helloStruct = UpdateDataBlock.DoUpdate(helloStruct, "world")
idk. this all makes my head spin
yeah that makes sense
the nice thing is that if you use the "composition" approach, the specific behavior that cares about a specific data block just knows what data it needs by convention
so the lack of type safety isn't a huge concern
e.g.
DoCropUpdate(Datablock block) {
int currentGrowth = block.GetInt("cropGrowth");
// etc.
}```
so I guess world update loop would be like
iterate through currently rendered chunks and blocks
if (Datablock.id == crop) updateCrop(Datablock)
or something like that
Essentially - though you could get a bit more sophisiticated about the behavior dispatch
like a polymorphic update method?
procedural world is a bit ambitious lol ugh
fun problem to solve, but hard
i wanna build scalable but also trying to build everything at once is overwhelming
either polymorphism or using delegates. E.g.
delegate void BlockUpdater(DataBlock data);
Dictionary<BlockType, BlockUpdater> dispatchDict;
void Setup() {
dispatchDict[BlockType.Crop] = CropUpdater;
dispatchDict[BlockType.Chest] = ChestUpdater;
// etc...
}
public BlockUpdater GetBehavior(BlockType bt) {
return dispatchDict[bt];
}```
reminds me of redux in js
it's annoying because I have a cs background and do software professionally. but web arch concepts don't map easily to pure OOP.
what are you building out of curiosity? is it a streamable world? how many concurrently simulated cells?
Think stardew + procedural generation
There's more to it than that
no doubt but it's a good mental image
The frustrating part is that it makes my head spin trying to reason about this all. like there are similarities to web arch in that
data/model layer - state/db
behavioral layer - API
view - UI / user input
but the separation is less clear in OOP
MVP/MVC and OOP are sort of orthogonal concepts
makes sense why my head is spinning lol
OOP is just.. the way C# operates. You can build your MVP/MVC framework on top of C#'s OOP system. It's really not much different from how you'd do it in JS.
Like my concept for the world is as follows
worldTileData ScriptObj
- represents fixed data and behavior
worldTilePureData primatives - data storage / persistence
terrainManager - event handler / orchestrator
The separation of functionality and data seems like a requirement for procedural world
Usually it would be the same class
absolutely. Not least of which because you will need to serialize this world presumably to save it between play sessions
having a well structured, holistic serializable data model of the current state of the world is a must
my brain goes composition
IWaterSource has these data fields
IInteractable has these functionalities
etc
if those fields are variable though, how do I store the composed thing.
like if i did attribute struct list, wouldn't the struct need a static type?
sorry if I'm wasting yalls time
That was the thing I had above:
class TileData {
List<Attribute> attributes;
}
struct Attribute {
// choose enums or strings here
string/AttributeType attributeName;
double doubleVal;
string stringVal;
// helper functions and properties to deal with the different type possibility.
}```
something like this
this is a simplified version
Hre's an example of something I used in a recent project using this kind of pattern https://paste.ofcode.org/V2KQry4YXepgvMx2D3T7cx
How would this work for like cropdata vs chest data. for simplicity lets say cropdata is
int totalGrowData
int currDays
and chest is
int[] itemIds
(opening link now)
for the chest I would probably just have one attribute and it would be like InventoryID - and this could just be a string or numerical ID pointing to separately stored data
I wouldn't store the actual inventory "inline" at this layer, if that makes sense
ahh
basically like storing keys to db entries
it would allow me to query the data
yeah- the inventory (chests, etc) is kind of a special case where the data is a bit too deep and structured to live directly at thhe "tiledata" layer
but for something like a crop where the whole state can be like one or two ints, you could store it directly with this structure
doesn't that risk the structure getting flooded?
can you elaborate on "flooded"?
crop has
int growDays
int totalDays
craftingMachine1 has
int secondsProcessed
int totalSeconds
for every new type in this example there are 2+ fields
with 10 types we have 20 fields
Well they're not really individual fields here
it's a List<Attribute>
which are, essentially, key/value pairs
ah
so any given item only has the attributes in its list that are actually required for that item
and the other fields would be null so you don't store much data for them
or are struct fields optional?
like could
{
int test = 1
} fit in a
{
int test;
string test2
} struct?
It's not clear to me if you're talkking about the Attribute struct here or the ItemData or whatever it is that contains the List<Attribute>
struct fields are not optional in C#
they will always exist - though if it's a string it may be null for example
I think ultimately I need to read up on this further but I definitely have more of a starting place from this conversation. I appreciate both of your patience and helpfulness
maybe a view from the bottom up?
You have the following needs:
persistence of your data across sessions
runtime behaviour
and you need an architecture that consolidates both well
You have a runtime entity(be it monobehaviour, or whatever your heart desires), and a serializable representation of that entity for data persistence. You then need an interface that allows rapid conversion.
By having a list of attributes the way @craggy tendon recommended, essentially this removes a large part of that burden. You can save your cells to disk as a list of attribute mapped to a position, and then pass it back to your runtime representations of your entities/cells when loading regions/chunks.
If, depending on your performance needs, you can maintain this list of attributes as the driving data in your actual runtime, this is fantastic. It means you just eliminated all needs for per-entity custom serialization logic
This makes a lot of sense
So would an Attribute in this case represent all data for a cell? like why would one cell require more than one attribute if an attribute has all the potential data fields?
it's ONE datapoint
one key/value pair
growthprogress: 5
something like that is one attribute
but wouldn't the Attribute create a struct with all the other fields?
Think of every field you would normally expose in your runtime. This stores them, individually, in a generalized form
attribute is:
struct Attribute {
string key;
float value;
}```
for example
And you just have a list of them
ok. but that would only be a float?
remember my example from above? It was a bit more generalized: https://paste.ofcode.org/V2KQry4YXepgvMx2D3T7cx
The actual fields here are:
[JsonProperty]
[SerializeField]
private AttributeType type;
[JsonProperty]
[SerializeField]
private double value;```
and througfh clever use of properties I'm able to encode a lot of different types in this double field including int float boolenums, etc
e.g. ```cs
public float FloatValue {
get => (float) value;
set => this.value = value;
}
public bool BoolValue {
get => value != 0;
set => this.value = value ? 1 : 0;
}```
oh clever
And even:
// We do something very cheeky here - store the Vector2 in the double.
public Vector2 Vector2Value {
get {
// Interpret the double as a 64-bit ulong
ulong combinedBits = DoubleToUInt64(value);
// Extract the high 32 bits and the low 32 bits
uint firstBits = (uint)(combinedBits >> 32); // High 32 bits
uint secondBits = (uint)(combinedBits & 0xFFFFFFFF); // Low 32 bits
// Convert the uint bits back to floats
float first = UIntToFloat(firstBits);
float second = UIntToFloat(secondBits);
return new(first, second);
}
set {
// Convert floats to uint using unsafe casting
uint firstBits = FloatToUInt(value.x);
uint secondBits = FloatToUInt(value.y);
// Combine both uint bits into one 64-bit ulong
ulong combinedBits = ((ulong)firstBits << 32) | secondBits;
// Interpret the combined bits as a double
this.value = UInt64ToDouble(combinedBits);
}
}```
was wondering why the double rather than a float
If you want even more flexibility you could add additional fields here but they will have significant memory costs, since every single attribute will have it, even if unused
but a string field is possible as well
i think double and string would be sufficient to cover most simple data types
anything more complicated like Inventory would have to be a lookup elsewhere
if your world isn't endless, you could even encode an index in the double that maps to a contiguous buffer of strings, so you can maintain the double as the primary data and derive the string
my world is ideally endless
but I do finally understand the attribute system
and I'm now impressed lol
I've basically been evolving this system over 8-10 years or so and using it on various projects to differing levels of success
yes - strings of a limited length at least
only like 8 cahracters or 4 characters
depending on the encoding
for ascii you could encode 8 characters
you could use the double to index a list of strings or contents though, solving your chest problem via attributes
so now you end up serializing all chests contents together in a collection, and the relevant cell has an attribute "chestContent" or w/e which maps to an index
in performance intensive code, best practice often f's right off. Wouldn't overthink it, engineer in a way that is easy to reason about and maintain/extend
yeah ultimately I have a heavy initial load, and then I can hit the registry for assets, SO, etc.
depending on the size though it may get unwieldy
heavy's relative. It's a function of how many regions/chunks you're loading simultaneously.
I probably will need to learn addressables at some point
you said earlier you're limiting to what's being rendered. If that's the case, then I wouldn't think twice, this is nothing. Esp if the size of cells is similar to stardew
well the registry stores like assets
I don't know much about your game, but if you have a runtime registry for your chest, then it should probably be limited to chests in active regions. Which means the worst, worst case is chestMaxContent*renderedCells
true. but yeah I also might just accept that my initial approach for anything may not ~~work ~~ scale but I'll learn from mistakes lol
ah, such is life. Iterate, refine, iterate, refine and hopefully ship
Best of luck, sounds like a cool project
This was my first pass. Art style is going to change though and I'm reworking a lot lol
That's ambitious! Nice. Good luck