#Wanna have Collection inside IBufferElementData

1 messages · Page 1 of 1 (latest)

white spruce
#

GameResourceMap is attached to any players, works as their "Resources" pocket.
In UnitProfileSO we have a SerializedDIctionary works as a cost map for that unit when being spawned.
Each UnitProfileSO will be converted into IBufferElementData and attached to the Entity that can spawn Units.
The Costs dictionary can not be put into IBufferElementData, that is what i currently concern right now.
Any suggestion? May be my Design is not good.
Thanks for reading.

waxen hawk
#

You should think twice before deciding to use managed components for your code won't be burstable.

#

If your map is reasonably small, contains just a few items (8 maybe?) you can just store them in a simple dynamic buffer and do a linear search for cost. Bursted linear search would be much faster.

#

There is another solution: store a giant NativeHashMap on a singleton entity somewhere and use it to look up your cost. Btw if the cost of each resource is different for each player then you should devise an ID strategy to differentiate each item in that giant hash map.

#

Which approach is better really depends on your specific usecase. It would be more helpful if you provide more information around the case.

#

For example, does each player have a different set of resource costs? How is this resource cost map initiated for each player? Is this map dynamic or static? means, do you know before hand every resource costs for a player?

white spruce
white spruce
white spruce
white spruce
waxen hawk
elfin osprey
waxen hawk
elfin osprey
#

so now units don't need dynamic buffers or hash maps on them

#

instead you just use their type/id to get the costs

waxen hawk
#

isn't that the giant native hash map I talked about 🤔

white spruce
# waxen hawk Put the native hash map directly in an ICD, you need no class here.

Tried, always got this exception:
ArgumentException: Blittable component type 'Components.GameResource.ResourceCostMapTest' on GameObject 'Player' contains a (potentially nested) pointer field. Serializing bare pointers will likely lead to runtime errors. Remove this field and consider serializing the data it points to another way such as by using a BlobAssetReference or a [Serializable] ISharedComponent. If for whatever reason the pointer field should in fact be serialized, add the [ChunkSerializable] attribute to your type to bypass this error.
Put NativeHashMap into a wrapper class and the exception is gone

white spruce
waxen hawk
#

note that, this native hash map must be created at runtime. You can't bake it into subscene, if this is what you're doing.

white spruce
waxen hawk
#

You can only use dynamic buffer or blob asset if you want to bake collections into subscene.

#

Native collections can't be baked.

waxen hawk
white spruce
elfin osprey
#

and it's not nested dictionary inside other dictionary

#

it's one big flat dictionary

#

nested dictionary won't work well with job system + it's simply much slower than flat solution

waxen hawk
#

That's why I told you to devise an ID strategy

#

you can make a complex ID out of these 2 components UnitType and CostType

elfin osprey
#

I usually avoid enums, but you can make it enum if you want

#

I prefer to use guids, which are created from assets (scriptable object for example)

white spruce
white spruce
waxen hawk
#

I would do this for example. UnitCostId would be the key for any kind of hash map. Essentially it's just an ushort value.

public enum UnitType : byte { } // enough for 255 values

public enum CostType : byte { } // enough for 255 values

[StructLayout(LayoutKind.Explicit)]
public readonly struct UnitCostId : IEquatable<UnitCostId>
{
    [FieldOffset(0)] private readonly ushort _raw;
    
    [FieldOffset(0)] public readonly UnitType Unit;
    [FieldOffset(1)] public readonly CostType Cost;

    public UnitCostId(UnitType unit, CostType cost) : this()
    {
        Unit = unit;
        Cost = cost;
    }

    public bool Equals(UnitCostId other)
        => _raw == other._raw;

    public override bool Equals(object obj)
        => obj is UnitCostId other && _raw == other._raw;

    public override int GetHashCode()
        => _raw.GetHashCode();

    public static bool operator ==(UnitCostId left, UnitCostId right)
        => left._raw == right._raw;

    public static bool operator !=(UnitCostId left, UnitCostId right)
        => left._raw != right._raw;
}
#

Another approach would be using an algorithm that makes 1D index out of an 2D index.

#

That would be less of a boilerplate than this

#

For me, in practice, I use source generator to make a complex ID out of multiple enums. Because I have many of these complex IDs to the point that manual coding is too tedious.

white spruce
white spruce
waxen hawk
#

In essence, this ID type contains only 1 value field and that is the _raw. Both Unit and Cost fields are just a section in bytes on the byte-stream of _raw. In the constructor, you set the value for _raw just by setting each byte-section.

#

That means you don't have to set the value for _raw explicitly.

#

FieldOffset attribute is a tool to let the program know on which byte-section you want to store a specific value.

#

ushort is a 2 bytes. The first byte to store a value of UnitType, the second to store a value of CostType.

#

so if you have a pair UnitType=1 and CostType=2, then the layout in memory would be
_raw = 0b__0000_0001__0000_0010 in binary
( 1 ) ( 2 )
which is 0x12 in hexadecimal
which is 18 in decimal.

#

The sample code for UnitCostId above is a complete and functional code. You can copy it into your project to use immediately. You need nothing else.

#

Source generator is just a tool that helps me write the code for a lot of these complex IDs with less code. It has nothing to do with your question. Let's just forget about it for now.

#

My apologies for adding confusing information.

white spruce
white spruce
brave sluice
#

Coming into this late, I think others have already made similar suggestions but without directly calling it out (maybe they did I didn't read the whole thread).

But the concept you have is very object oriented design, not data oriented design.

I find whenever I want to associated data with an object (like in your case the player) I need to ask myself, do I think of this like an object that owns that data and so I'm thinking about PlayerA's Resources versus PlayerB's Resources. If the answer is yes, I try and think of a way to manage the resources and access them without them knowing which Player they belong to.

Almost like Inversion of Control theory, so in this example, the Resource in question, shouldn't need to know about what Player it belongs to, all it's logic should be able to function without knowing which player it belongs to, or maybe even a scenario where there is no Player at all, or some other type of object that eventually might need Resources that you haven't planned for.

When you start to think of things this way, you stop organizing your data into Collections or Lists or Arrays that you can iterate over, and why would you, ECS already has Components that are organized into chunks, each chunk is an archetype that already acts like the container for your data anyway. By suggesting to put a collection inside a buffer, you're telling ecs you want to have a container of containers of containers that belong to the Player archetype.

While that's technically feasible, I wouldn't be surprised if you end up with poor chunk utilization or everything not fitting in chunks and just being pushed to the heap anyway causing you to completely lose out on the performance of dots.

You could have every Resource be an entity, every entry in your Costs dictionary be an entity, and their components can store references to the player entity they belong to.

Then you can make use of the SystemAPI.Query which is very fast to operate. Good luck.

waxen hawk
#

I don't think it's a good idea to turn a lookup table into a group of entities. A lookup table exists so that you can search for a piece of data as fast as you can. Also the data contained in a lookup table is generally immutable. Entities imply the ability to change, and a group of entities certainly can't ensure the fastest search speed.

waxen hawk