Search Unity

Cannot call "EntityManager.AddComponentData" with an unknown IComponentData type

Discussion in 'Entity Component System' started by RShackleton, Apr 2, 2018.

  1. RShackleton

    RShackleton

    Joined:
    Mar 31, 2012
    Posts:
    15
    Hi all,

    I'm am trying to dynamically create an entity with IComponentData components based on data deserialised from JSON.

    This results in the following code:

    var componentData = compDef.CreateComponentData();
    EntityManager.AddComponentData(entity, componentData);


    The return type of compDef.CreateComponentData is IComponentData and this causes the following error:

    The type 'Unity.Entities.IComponentData' must be a non-nullable value type in order to use it as parameter 'T'


    Is there a way of working around this and adding components without knowing which type they are?
     
  2. DwinTeimlon

    DwinTeimlon

    Joined:
    Feb 25, 2016
    Posts:
    300
    Soren_cadpeople likes this.
  3. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    224
    System.String is not blittable what to use instead?
     
  4. RShackleton

    RShackleton

    Joined:
    Mar 31, 2012
    Posts:
    15
    The types returned from that method are all structs and are all using non-blittable types. The error is a compile-time error as it can't determine if the return type is non-nullable. I've tried adding a type parameter with the "where T : struct, IComponentData" but then the consumer needs to pass in the type which it doesn't know.

    Surely there is some way of making this work? It seems like a fairly major missing feature to not be able to populate an entity dynamically.

    @SubPixelPerfect I think you should make a separate thread for that.
     
  5. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    I think you need a specific class because its a generic method call!

    You call AddComponent<IComponentData>(...)
    IComponentData is nullable and can't be used directly here.

    Try to cast your componentData to the specific ComponentType like:
    Code (CSharp):
    1. MyComponent componentData = (MyComponent)compDef.CreateComponentData();
    Now you can call AddComponent<MyComponent>(...) and it will work!
     
  6. RShackleton

    RShackleton

    Joined:
    Mar 31, 2012
    Posts:
    15
    That's my point, I cannot know what the type is at compile time. I'm dynamically generating the components to be added to an entity.

    // This works, but means the consumer needs to know that the return type is Cell and not a different component type.
    var componentData = compDef.CreateComponentData<Cell>();
    EntityManager.SetComponentData(entity, componentData);


    I'm enumerating through a collection of "CompDef" instances each of which return a different IComponentData for example Cell or Buildable etc. This means I cannot possibly know what those types are going to be at compile time.
     
  7. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    Use a Tag to get the information of what type the specific component is and then cast to it!

    Otherwise it will not work!
     
  8. RShackleton

    RShackleton

    Joined:
    Mar 31, 2012
    Posts:
    15
    Do you have an example of what you mean by that? If I need to manually cast to a type then that's not going to result in particularly clean code considering there will be a large number of component types to handle.
     
  9. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    I don't know what jsonlib you're using.

    Anyway, may your JSON string looks like this:
    Code (CSharp):
    1. {
    2.     "Cell":
    3.     {
    4.         "x":"1901",
    5.         "y":"412"
    6.     }
    7.    "Buildable":
    8.    {
    9.       //What ever
    10.    }
    11. }
    You need to read the type of the current json object e.g. "Cell"
    If you have this information you can do the following:

    Code (CSharp):
    1.  
    2. string myClassString = // some call from your json lib to retreive the current class
    3.  
    4. switch(myClassString ){
    5.     case "Cell":
    6.         Cell cellData = compDef.CreateComponentData<Cell>();
    7.         EntityManager.SetComponentData(entity, cellData);
    8.     break;
    9.  
    10.     //other cases ...
    11.  
    12.     default:
    13.          throw new NotImplementedException();
    14. }
    15.  
    16.  
     
  10. RShackleton

    RShackleton

    Joined:
    Mar 31, 2012
    Posts:
    15
    That's what I thought you may have meant, having a mammoth switch case statement seems like an absolute last resort since there would end up being a huge number of cases. I was hoping for an alternative to hard-coding all of the different component data types.
     
  11. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546

    EDIT:
    I've another idee... Maybe this apporach is better...
    Take use of an Interface


    Code (CSharp):
    1. interface IJsonbase {
    2.      void AssignToEntity(EntityManager manager, Entity entity);
    3. }
    4.  
    5. struct Cell : IJsonbase, IComponentData {
    6.        public int X;
    7.        public int Y;
    8.  
    9.        public override void AssignToEntity(EntityManager manager, Entity entity) {
    10.              manager.SetComponentData(entity, this);
    11.        }
    12. }
    13.  
    14.  
    15.  
    16.  
    17. //Now you can do something like this
    18. IJsonbase componentData = compDef.CreateComponentData();
    19. componentData.AssignToEntity(EntityManager, entity);
    20.  
     
    Last edited: Apr 3, 2018
  12. RShackleton

    RShackleton

    Joined:
    Mar 31, 2012
    Posts:
    15
    You can't use inheritance with structs, this would have been my preferred option otherwise.
     
  13. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    you can use interface (it will box but you are already boxing). still requires each concrete type to implement the method.
    or you can use reflection to generate the 'SetComponentData<T>' method at runtime (it may be worse)

    I think EntityManager has a 'SetComponentBoxed(Entity, object)' or similar... I saw some post in this forum mentioning it
     
  14. RShackleton

    RShackleton

    Joined:
    Mar 31, 2012
    Posts:
    15
    @M_R looks like that SetComponentBoxed method is internal so can't rely on that. Any chance you could provide an example regarding your first point?
     
  15. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    224
    it was my tread mentioning SetComponentBoxed method
    i'm trying to do exactly opposite thing to what you looking for - i'm trying to serialize entity with all its components and values
    and there is a very similar problem with generics

    for now I ended up with generation generic GetComponentData<T> methods using reflection
    Code (CSharp):
    1. public void SerializeEntiy(Entity e){
    2.    
    3.       var types =  this.EntityManager.GetComponentTypes( e );
    4.       for( int j = 0; j != types.Length; j++ ){
    5.         ComponentType ct = types[j];
    6.         Type t = ct.GetManagedType();
    7.        
    8.         if( t.GetInterfaces().Contains( typeof( IComponentData ) ) ){
    9.           MethodInfo methodInfo = typeof(EntityManager).GetMethod("GetComponentData");
    10.           MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(t);
    11.           var parameters = new object[]{e};
    12.           object componentData = genericMethodInfo.Invoke( EntityManager, parameters );
    13.           Debug.Log(t +" : "+ JsonUtility.ToJson( componentData ) );
    14.         }
    15.  
    16.       }
    17.       types.Dispose();
    18.     }
    very ugly solution but works, can't find a better way so far
     
    Last edited: Apr 3, 2018
  16. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    the interface approach is what @Spy-Shifty did in their post. (it was edited)
    the reflection approach is almost exactly as @SubPixelPerfect has
    (1. I thought GetMethod("name") didn't work with generic methods, I usually had to do GetMethods().First(m => m.MethodName == "name"). maybe it's something that got fixed with the new runtime.
    2. I'd cache heavily though, reflection is heavy)
     
  17. RShackleton

    RShackleton

    Joined:
    Mar 31, 2012
    Posts:
    15
    Thanks all!

    I've ended up using the reflection approach to reduce the amount of code duplication, seems to be working and hasn't added too much overhead.

    Would be useful to have this scenario supported directly by the ECS API but we'll see if that changes between now and release.