How to save a Dictionary<int, List<string>> into related IComponentData variables?

How to save a Dictionary<int, List> into related IComponentData variables?

Overview:
I open up a database connection and I populate a Dictionary<int, List> someDictionaryList = new Dictionary<int, List>() with the related database fields.
(This part works fine)

Problem:
I need to take that Dictionary<int, List> someDictionaryList = new Dictionary<int, List>() created and set the related IComponentData variables. This way I can access them when I instantiate a Prefab, and need to apply that information to each Prefab, from with in a SystemBase.

Types included in the IComponentData.
NativeString64
int
float
float3

Any idea on how to best approach this issue?

I think there’s a class based IComponentData which is managed and associated with an entity, so my guess is you could dump your data there and rip what you need from it before jobs. I haven’t played with it much.

1 Like

Thanks for the heads up.

Try NativeMultiHashMap. I Don’t know if it would work in IComponentData but it’s Native so it should?

1 Like

Found this note.

You should implement IComponentData as struct rather than a class, which means that it is copied by value instead of by reference by default

https://docs.unity3d.com/Packages/com.unity.entities@0.10/manual/component_data.html

So that does not look like it will work for my project. Because each time that struct needs to pertain to each Prefab/Entity instantiated (they all have their own different values associated with them at runtime. e.g. size, color, related string).

You might be able to get away with BlobAssets if your int key is an index. Then it’s just a lookup into a table.

Otherwise You can implement IComponentData classes, and use the Add/Remove/Get/SetComponentObject entity manager variants.

1 Like

But from what I understood, the class version will not allow unique values for each prefab\entity. So they would all get the same values?

No, they can have unique values. You’re probably thinking of ISharedComponentData, which up until recently was the only way to give an entity a managed reference. Those are shared between entities, as entities are grouped by matching ISharedComponentData.

If you use the AddComponentObject* and friend methods, those values can be distinct, as it’s just a normal reference.

Otherwise, hybrid entities that add MonoBehaviour-based components during conversion wouldn’t work properly.

1 Like

Thanks. I’ll try this later today when I’m back to work. The way I read it. Class version all same. Struct all different.

Another point is I have a public class Database : MonoBehaviour that handles the dbconn.Open(); //Open connection to the database. and related code. So I currently create that Dictionary<int, List> someDictionaryList = new Dictionary<int, List>() in this Class. Adding in the database records.

So I’m not in a SystemBase at this point. That’s what’s messing with me. How can I got from

From
General purpose components | Entities | 0.10.0-preview.6?

managed IComponentData (that is, IComponentData declared using a class rather than struct)

  • I get this line.

Managed IComponentData must implement the IEquatable interface and override for Object.GetHashCode(). Additionally, for serialization purposes, managed components must be default constructible.

  • I don’t get how to implement this part. Any sample code someone knows of I can base my project’s implementation from?

Managed IComponentData must implement the IEquatable interface and override for Object.GetHashCode(). Additionally, for serialization purposes, managed components must be default constructible.

  • I don’t get how to implement this part. Any sample code someone knows of I can base my project’s implementation from?

All it’s saying is that your custom class needs to implement a specific C# interface and override the default implementation of .GetHashCode(), which is defined on the object type, which all reference types in C# inherit from

In visual studio you can just type:

public class MyClass : IEquatable

then vs will give you an error, and if you right click and select implement interface, it’ll pre-fill the methods you need to write.

This is just from memory, I’m on mobile.

look up GetHashCode and how to write your own, it’s required for C# to determine object equality when doing checks like objectA == objectB. Not sure if there’s any unity specific stuff.

May I ask what you’re actually doing though, your initial question is a bit weird; you’re pulling data from a database and storing it in a dict, what does the key (int) represent? what does the list of strings represent? do you want to end up with a component with a list of strings? or are you trying to set properties on the component data based on the database data?

If it’s the latter, reflection will be your friend, and there’s no need for classes

Thanks jonwah00. In short, we have a database with tens of thousands of entries. the Key is the indexID (in our database), the Value is a List<> (which contains things like size, color, position, etc.). So I need to ultimately store about six “attributes” if you will on each Entity. Think of it as 100,000 asteroids. Each has their own set of properties like this or attributes. I thought this would be easy. I did a class for this and member variables in OOP. And when I instantiated Prefabs that class was already there with what I needed. I just assigned values (from the database). I have to use DOTS because OOP can’t handle the number of objects. So down the rabbit hole I’ve gone. But with DOTS it makes it pretty hard (least for me) to get this basic need for the project done. I wish there was a tutorial or a clear code example in the ECS Samples Unity has. To me this is probably a pretty common need. People make suggestions and I appreciate it. But it’s never clear enough or test suggestions proving it works. But, again, I appreciate any and all help. And this is cutting edge stuff. So I take it in stride like everyone else using DOTS.

Ok. I’m going to run with the assumption that your variables are in the list in some kind of order, so that you know what each one is, and what type it should be? Example; if you have size (int64), color (string), position x (float), position y (float), position z (float), you might have a list like “202130”, “blue”, “123.23”, “123.35”, “022.34”, right?

Firstly you need to get outside data (your dictionary) accessible by a system. I don’t know a good way to do this yet (see here: DOTS Change Material within System apart from just using static variables. Let’s assume you have a static variable for your dictionary, which is populated before your system will attempt to run.

Your system will use an EntityCommandBuffer to instantiate entities and call AddComponent etc?

                List<string> variables = myStaticDictionary[myIndex];
                var newEntity = ecb.CreateEntity(yourArchetype);

                var translation = new Translation {
                    Value = new float3(float.Parse(variables[4]), float.Parse(variables[5]), float.Parse(variables[6]))
                };
                ecb.SetComponent(newEntity, translation);

Is that what you’re trying to do? It seems fairly simple. Or is the data dynamic, depends on the entity type being constructed, and you need to dynamically create different kinds of IComponentData structs and set different variables on them depending on the type?

1 Like

Sorry just re-read what you’re trying to do. I don’t know if you need a system for this, it depends when in the life-cycle of the game you’re planning on spawning all these entities.

If you’re trying to do it rarely, maybe on level/game load, you don’t really need a system, you can run the following code in a regular MonoBehaivour:

EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;

        var types = new ComponentType[]{
            typeof(Translation),
            typeof(RenderMesh),
            typeof(LocalToWorld),
            typeof(Rotation),
            typeof(RenderBounds),
            typeof(AsteroidComponent)
        };


        var renderMesh = new RenderMesh {
            mesh = mesh,
            material = material
        };

        var meshAABB = mesh.bounds.ToAABB();
        var renderBounds = new RenderBounds {
            Value = meshAABB
        };

        Dictionary<int, IEnumerable<string>> myDictionary = new Dictionary<int, IEnumerable<string>>();
        // Load from DB
        var entityCount = myDictionary.Count;

        var arc = entityManager.CreateArchetype(types);

        var entityArray = entityManager.CreateEntity(arc, entityCount, Allocator.TempJob);

        var translationArray = new NativeArray<Translation>(entityCount, Allocator.TempJob);
        var rotationArray = new NativeArray<Rotation>(entityCount, Allocator.TempJob);
        var asteroidArray = new NativeArray<AsteroidComponent>(entityCount, Allocator.TempJob);
        var renderBoundsArray = new NativeArray<RenderBounds>(entityCount, Allocator.TempJob);

        // Prepare your data - fill the arrays with your component data
        for (int i = 0; i < entityCount; i++) {

            var dictEntry = myDictionary.ElementAt(i);

            float xPos = float.Parse(dictEntry.Value[2]);
            float yPos = float.Parse(dictEntry.Value[3]);
            float zPos = float.Parse(dictEntry.Value[4]);
            float rot = float.Parse(dictEntry.Value[5]);

            var translation = new float3(xPos, yPos, zPos);
            var rotation = quaternion.Euler(math.radians(new float3(rot, 0f, 0f)));

            translationArray[i] = new Translation { Value = translation };
            rotationArray[i] = new Rotation { Value = rotation };

            asteroidArray[i] = new AsteroidComponent {
                IndexId = dictEntry.Key,
                SomeStringValue = dictEntry.Value[0],
                SomeIntValue = int.Parse(dictEntry.Value[1])
            };
            renderBoundsArray[i] = renderBounds;
        }

        // Set SharedComponentData (RenderMesh), separately
        for (int i = 0; i < entityCount; i++) {
            entityManager.SetSharedComponentData(entityArray[i], renderMesh);
        }

        // Create a query based off your types
        // Note; you want this query to return exactly the right entities, it's no good already having some entities which will match
        var entityQuery = entityManager.CreateEntityQuery(types);

        //Array length must match entity count return from the EQ exactly.
        //The data lands on each entity in order of chunks returned.

        //Each of these is threaded per chunk, and for a chunk there is only one MemCpy to initialize
        //multiple entities at once.
        entityQuery.CopyFromComponentDataArray(translationArray);
        entityQuery.CopyFromComponentDataArray(rotationArray);
        entityQuery.CopyFromComponentDataArray(asteroidArray);
        entityQuery.CopyFromComponentDataArray(renderBoundsArray);

        // Very important to dispose of native arrays
        entityArray.Dispose();
        translationArray.Dispose();
        rotationArray.Dispose();
        asteroidArray.Dispose();
        renderBoundsArray.Dispose();

That’s the fastest way I’ve found to instantiate a tonne of entities at once.

1 Like

That’s exactly what I’m doing a static variable and you’re right they are in the order that I already know what they’ll be in. So that’s the approach I’m taking right now. I can get the count of the dictionary from the area that I would be making the changes so that’s a plus.

I’ll take a look at that option you gave as well I’ve seen that I’ve not used that before, the buffer. Thx I’ll follow up this weekend when I’m in front of my system again and test this.

That’s it I’m using the static approach for now it’s only way I could think of them may work. You’re correct again I do know the order and I know and can see the data is getting into the dictionary properly. Thanks on the buffer suggestion I’ll try that when I’m in back in front of my system later this weekend.

Yeah you’re right I’m not doing this on a system base for example. I have it in my own namespace and then call it from a startup MonoBehaviour script if you will. But I’m going to need to do things like set the position based on scale to expand the objects apart from one another. What’s that type of code I have from the version one project based on OOP implementation. This was the I think the last key part was communicating between the dictionary collection and the set component based on a prefab to entity conversion.

I appreciate all your input and help so far.