Search Unity

  1. If you have experience with import & exporting custom (.unitypackage) packages, please help complete a survey (open until May 15, 2024).
    Dismiss Notice
  2. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice

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

Discussion in 'Entity Component System' started by GDevTeam, May 7, 2020.

  1. GDevTeam

    GDevTeam

    Joined:
    Feb 14, 2014
    Posts:
    90
    How to save a Dictionary<int, List<string>> into related IComponentData variables?

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

    Problem:
    I need to take that Dictionary<int, List<string>> someDictionaryList = new Dictionary<int, List<string>>() 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?
     
    Last edited: May 7, 2020
  2. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    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.
     
    GDevTeam likes this.
  3. GDevTeam

    GDevTeam

    Joined:
    Feb 14, 2014
    Posts:
    90
    Thanks for the heads up.
     
  4. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    Try NativeMultiHashMap. I Don't know if it would work in IComponentData but it's Native so it should?
     
    GDevTeam likes this.
  5. GDevTeam

    GDevTeam

    Joined:
    Feb 14, 2014
    Posts:
    90
    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).
     
    Last edited: May 8, 2020
  6. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    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.
     
    GDevTeam likes this.
  7. GDevTeam

    GDevTeam

    Joined:
    Feb 14, 2014
    Posts:
    90
    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?
     
  8. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    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.
     
    GDevTeam likes this.
  9. GDevTeam

    GDevTeam

    Joined:
    Feb 14, 2014
    Posts:
    90
    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.
     
  10. GDevTeam

    GDevTeam

    Joined:
    Feb 14, 2014
    Posts:
    90
    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<string>> someDictionaryList = new Dictionary<int, List<string>>() 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
     
  11. GDevTeam

    GDevTeam

    Joined:
    Feb 14, 2014
    Posts:
    90
    From
    https://docs.unity3d.com/Packages/com.unity.entities@0.10/manual/component_data.html?


    managed IComponentData (that is, IComponentData declared using a class rather than struct)
    - I get this line.

    Managed IComponentData must implement the IEquatable<T> 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?
     
  12. jonwah00

    jonwah00

    Joined:
    Jul 21, 2019
    Posts:
    36
    Managed IComponentData must implement the IEquatable<T> 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<My class>

    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
     
  13. GDevTeam

    GDevTeam

    Joined:
    Feb 14, 2014
    Posts:
    90
    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.
     
  14. jonwah00

    jonwah00

    Joined:
    Jul 21, 2019
    Posts:
    36
    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: https://forum.unity.com/threads/dots-change-material-within-system.885361/ 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?

    Code (CSharp):
    1.  
    2.                 List<string> variables = myStaticDictionary[myIndex];
    3.                 var newEntity = ecb.CreateEntity(yourArchetype);
    4.  
    5.                 var translation = new Translation {
    6.                     Value = new float3(float.Parse(variables[4]), float.Parse(variables[5]), float.Parse(variables[6]))
    7.                 };
    8.                 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?
     
    GDevTeam likes this.
  15. jonwah00

    jonwah00

    Joined:
    Jul 21, 2019
    Posts:
    36
    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:

    Code (CSharp):
    1. EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
    2.  
    3.         var types = new ComponentType[]{
    4.             typeof(Translation),
    5.             typeof(RenderMesh),
    6.             typeof(LocalToWorld),
    7.             typeof(Rotation),
    8.             typeof(RenderBounds),
    9.             typeof(AsteroidComponent)
    10.         };
    11.  
    12.  
    13.         var renderMesh = new RenderMesh {
    14.             mesh = mesh,
    15.             material = material
    16.         };
    17.  
    18.         var meshAABB = mesh.bounds.ToAABB();
    19.         var renderBounds = new RenderBounds {
    20.             Value = meshAABB
    21.         };
    22.  
    23.         Dictionary<int, IEnumerable<string>> myDictionary = new Dictionary<int, IEnumerable<string>>();
    24.         // Load from DB
    25.         var entityCount = myDictionary.Count;
    26.  
    27.         var arc = entityManager.CreateArchetype(types);
    28.  
    29.         var entityArray = entityManager.CreateEntity(arc, entityCount, Allocator.TempJob);
    30.  
    31.         var translationArray = new NativeArray<Translation>(entityCount, Allocator.TempJob);
    32.         var rotationArray = new NativeArray<Rotation>(entityCount, Allocator.TempJob);
    33.         var asteroidArray = new NativeArray<AsteroidComponent>(entityCount, Allocator.TempJob);
    34.         var renderBoundsArray = new NativeArray<RenderBounds>(entityCount, Allocator.TempJob);
    35.  
    36.         // Prepare your data - fill the arrays with your component data
    37.         for (int i = 0; i < entityCount; i++) {
    38.  
    39.             var dictEntry = myDictionary.ElementAt(i);
    40.  
    41.             float xPos = float.Parse(dictEntry.Value[2]);
    42.             float yPos = float.Parse(dictEntry.Value[3]);
    43.             float zPos = float.Parse(dictEntry.Value[4]);
    44.             float rot = float.Parse(dictEntry.Value[5]);
    45.  
    46.             var translation = new float3(xPos, yPos, zPos);
    47.             var rotation = quaternion.Euler(math.radians(new float3(rot, 0f, 0f)));
    48.  
    49.             translationArray[i] = new Translation { Value = translation };
    50.             rotationArray[i] = new Rotation { Value = rotation };
    51.  
    52.             asteroidArray[i] = new AsteroidComponent {
    53.                 IndexId = dictEntry.Key,
    54.                 SomeStringValue = dictEntry.Value[0],
    55.                 SomeIntValue = int.Parse(dictEntry.Value[1])
    56.             };
    57.             renderBoundsArray[i] = renderBounds;
    58.         }
    59.  
    60.         // Set SharedComponentData (RenderMesh), separately
    61.         for (int i = 0; i < entityCount; i++) {
    62.             entityManager.SetSharedComponentData(entityArray[i], renderMesh);
    63.         }
    64.  
    65.         // Create a query based off your types
    66.         // Note; you want this query to return exactly the right entities, it's no good already having some entities which will match
    67.         var entityQuery = entityManager.CreateEntityQuery(types);
    68.  
    69.         //Array length must match entity count return from the EQ exactly.
    70.         //The data lands on each entity in order of chunks returned.
    71.  
    72.         //Each of these is threaded per chunk, and for a chunk there is only one MemCpy to initialize
    73.         //multiple entities at once.
    74.         entityQuery.CopyFromComponentDataArray(translationArray);
    75.         entityQuery.CopyFromComponentDataArray(rotationArray);
    76.         entityQuery.CopyFromComponentDataArray(asteroidArray);
    77.         entityQuery.CopyFromComponentDataArray(renderBoundsArray);
    78.  
    79.         // Very important to dispose of native arrays
    80.         entityArray.Dispose();
    81.         translationArray.Dispose();
    82.         rotationArray.Dispose();
    83.         asteroidArray.Dispose();
    84.         renderBoundsArray.Dispose();
    That's the fastest way I've found to instantiate a tonne of entities at once.
     
    GDevTeam likes this.
  16. GDevTeam

    GDevTeam

    Joined:
    Feb 14, 2014
    Posts:
    90
    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.
     
  17. GDevTeam

    GDevTeam

    Joined:
    Feb 14, 2014
    Posts:
    90
    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.