Search Unity

Nesting IComponentData structs

Discussion in 'Entity Component System' started by jasonm_unity3d, Apr 6, 2018.

?

Good form?

  1. Yes

    14 vote(s)
    38.9%
  2. No

    22 vote(s)
    61.1%
  1. jasonm_unity3d

    jasonm_unity3d

    Unity Technologies

    Joined:
    Mar 2, 2017
    Posts:
    41
    Is it *considered* ok/acceptable to nest two structs that both have the interface IComponentData?
    Or is it actually very bad form?

    for example:

    public struct Foo1 : IComponentData
    {
    public int a;
    }

    public struct Foo2 : IComponentData
    {
    public Foo1 innerFoo;
    int b;
    }
     
  2. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    The better question is:
    What is the usecase?
     
  3. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    2,129
    I think it is acceptable as after current Unity Components converted to IComponentData too, it will definitely have this usecase.
     
    Last edited: Apr 8, 2018
    Djayp likes this.
  4. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    356
    I guess that having Inheritance kind of break the ecs pattern, it was pretty much invented to not have that. You should have the two components on the same entity.
    But I might be wrong, it's what I have in mind when I think ecs.
     
    Spy-Shifty likes this.
  5. Cynicat

    Cynicat

    Joined:
    Jun 12, 2013
    Posts:
    290
    Only usecase that comes to mind would be to make larger high-level components out of smaller specific components, but for that you would probably need systems to still work on subcomponents. for example let's imagine an RPG with an ActorStats component, but then the player has a bunch of extra stats, so you have a PlayerStats component that contains an ActorStats field called actorStats. I would still expect a system that operates on ActorStats components to effect PlayerStats.actorStats. Could be a really slick way to make higher level component groups but i have no idea how that would be implemented =/.

    However! I think having a reliable RequireComponent tag would probably be a better solution to this problem, since then multiple high level components could share components they need (eg: ActorStats, Rigidbody, etc...).

    Those are just my thoughts.
     
  6. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    I'd rather have [RequireComponent(...)] too. including automatically transfer [RequireComponent] between IComponentData to their corresponding ComponentDataWrapper

    a better idea would be having a IGroupedComponentData interface that can contain N IComponentData fields (and corresponding wrapper), when added to an entity it actually adds the components (i.e. doesn't exist at runtime). IGroupedComponentData can be nested.

    nesting directly IComponentData would be fine as long there is a clear and intuitive semantics about the nested components (do they get attached to the entity (i.e. other systems see it)? if so, the data is duplicated or you would weave the assembly with some pointer trick? what if you have multiple components with the same nested component?)
     
  7. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    I like that grouped thing and RequireComponent as a design aspect for the Designteam...
    But I don't see a super really important usecase for nesting.

    In my mind, nesting will break the ecs paradigm.
    You create additional dependencies between different data structures and between data and systems.
    And there is no information growth or other benefits. You can do the same without nesting, at the moment.

    But maybe there is a usecase, I don't know :)
     
    Rennan24 likes this.
  8. jasonm_unity3d

    jasonm_unity3d

    Unity Technologies

    Joined:
    Mar 2, 2017
    Posts:
    41
    My take is that it should not be done: IComponentData is a way of controling you rmemory layout. It has a very specific semantic attache to it. And the ECS handles it very specifically.

    Nesting one into an other, works but the inner one is ignored by ECS (as far as I can tell), so when nested inside an other IComponentData, the nested item is no longer a IComponentData, it's just data that is part of a IComponentData struct.

    That's my take on it any way. But I'm wondering if there is a good reason to do it that I dont see, other than lazyness (reuse of a struct)?
    Or could this simply be labeled as bad form?
     
    Rennan24 likes this.
  9. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    @jasonm_unity3d
    but how do you interpret 2nd struct from component system view point: it's only Foo2 or it's Foo2 and Foo1 entity. I think you will pass reference so what about job security? Can you track nesting (first and/or more)? Not to mention memory layout as you mentioned before.

    There should be one purpose component so systems can be clear about with what they work and with not. Even if system can do nested components for me it's just bad practice for simplicity and code readability sake.
     
    recursive and Rennan24 like this.
  10. jasonm_unity3d

    jasonm_unity3d

    Unity Technologies

    Joined:
    Mar 2, 2017
    Posts:
    41
    My stance is that ECS should not allow nested IComponentData structs. if you want to do that, Add them directly to the Entity as two components. My impression is that nesting IComponentData struct brings nothing to the table except complexity and weird behavirous and questions. For example: can you schedule two job to run currently against Foo1 and Foo2? you can actually because the backing memory is distinct (two separate blocks) and so is safe. But should it? what is the intent? Just one more argument that leans on the; dont nest I ComponentData...


    Agreed.
     
  11. optimise

    optimise

    Joined:
    Jan 22, 2014
    Posts:
    2,129
    Now I change my mind that Nesting IComponentData structs is bad idea. I think if u make it nested it's kind of like go back to OOP approach and make it complex again. Not only that, when u access the data from IComponentData, it's also very ugly like Group.Component.AnotherComponent.JustAnotherComponent.someData.

    I guess you can make it throw compile error when people doing Nesting IComponentData structs.
     
    Prodigga and Spy-Shifty like this.
  12. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    I think the problem lies in expectations.
    you can just do group.transformMatrix.value.m1.x where only TransformMatrix is a IComponentData, it just contains other structs (value is a float4x4, that contains 4 float4 (m0, m1, m2, m3) that each contain 4 floats (x, y, z, w))

    but what if one of that nested structs happens to implement IComponentData? naturally it gets ignored, it's just nested data, it's not added directly to an entity, so it doesn't gets picked up by systems. but it does say IComponentData and that can generate confusion.
    specially picking those nested fields would open a can of worms, like defining exactly how they get picked and (de)duplicated, which can cause more confusion.

    but there is nothing preventing c# from having a struct with a field that's a struct implementing an interface, so it can't be a compiler error. maybe have a warning when adding it to an entity, with an "I know what i'm doing" attribute to suppress it (like [NativeDisableParallelForRestriction])
     
  13. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    982
    What about "has a" relationship? I have these little non-MonoBehaviour classes that I use a lot in my games. I was wondering how I could port them to the ECS way. Adding them as separate components is clunky. They are owned by other components in the same entity. How do you build this relationship?

    I've thought about using nesting then calling a static method from the system operating them:

    Code (CSharp):
    1. struct Utility {
    2.     ... // Whatever
    3. }
    4.  
    5. struct SomeComponent : IComponentData {
    6.     public Utility utility;
    7.     ... // Other attributes
    8. }
    9.  
    10.  
    11. class SomeSystem {
    12.     void Update {
    13.         foreach(Component component in components) {
    14.             component.utility = UtilitySystem.Update(component.utility);
    15.         }
    16.     }
    17. }
    18.  
     
  14. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    "Has-a" in the ECS framework may not be a direct ownership relationship that we're used to with OOP and regular imperative programming (and I think I may do a deeper write-up on this concept), rather, we use various methods to associate entities together (this can include entity keys themselves).

    For example (paraphrased from something I'm working on, definitely still figuring out the best structure)

    Actor
    * ActorId - Shared Value, Entity Id of Main Actor object
    * TransformMatrix
    * Speed
    * Direction
    * JumpState -- swap around with the other *States
    * GroundState -- swap around with the other *States
    * AirState -- swap around with the other *States

    Actor Animator Entity
    * ActorId - Shared Value
    * Visual "Tag"
    * FacingInfo
    * Animator (regular Component)

    Actor Current Status - add / remove status effect data as needed
    * ActorId
    * Health
    * Stamina
    * ActorId - Shared Value
    * Slowed
    * OnFire

    * IsDeleted - use for cleanup at end of frame, only applied when Health drops to 0 and we hit the end of the frame.

    From this line of thinking, let's look at it this way:

    Actor has-a Animator and has-a Current Status via the ActorId (which could be just anything that works for ISharedComponentData, but likely the Entity ID of the Actor those things belongs to in the direction my game's framework is moving), which defines the "has-a" relationship.

    The systems that operate on Actors and their related entities acts as rules that define and enforce the relationship between these otherwise loose sets of data.

    We're so used to the OOP forcing us into thinking about is-a and has-a in terms of direct ownership or direct inheritance it's difficult to dissociate those from their higher actual concepts. In the above example, an actor is-an Actor because it has-an ActorId that matches it's Entity Id. That's it. This actor also Has-a Animator and Has-A Current Status, without needing direct ownership. It THAT realization that makes this concept so powerful and is something I suspect causes a lot of programmers raised by OOP have issues with ECS framework concepts in general, since it's such a 180 from how we're taught to make stuff.

    The beauty of the above system is we can create rules that delete our associated sub-entities when the master entity dies, but the master entity doesn't necessarily need the sub-entities to do it's basic functions (for example, we could ditch the Curent Status for NPCs that don't need it, or simulate background actors without an Animator entity when they're offscreen), and they don't have to directly reference the master entity unless it's necessary. In fact, the only NECESSARY component is the ActorId, since it not only ties everything together, but denotes the life cycle (if the ID ceases to be valid, the actor is dead, and anything sharing that Id can be cleaned up implicity via our lifecycle rules).

    You can extend this line of thought to inheritance as well, having a string if Entities that represent class-like objects or stable data definitions with a "Derives From" that defines the relation ship to the "base class", then use these structures as prototype definitions to assemble complete objects via a factory. And change those prototypes at runtime. INHERITANCE DEFINED BY "Plain old data" AT RUNTIME, without an extra interpreter or hot-compiler. It could be super useful for stuff like game mods and UGC that work even on limited platforms like mobile devices or consoles. You may not be able to compile up entirely new code at runtime, but with a robust enough set of components and systems, you could build an entire limited scripting extension platform for your game entirely in ECS.

    Apologies for going on a rant there, I had some realizations on all of this over the last few days, it's been kinda slow at my day job.
     
    Last edited: Apr 11, 2018
  15. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    I think your code is irrelevant to this topic because you have a basic struct inside IComponentData struct which isn't a bad thing.
     
    jasonm_unity3d likes this.
  16. jasonm_unity3d

    jasonm_unity3d

    Unity Technologies

    Joined:
    Mar 2, 2017
    Posts:
    41
    Actually, once Roslyn starts being use, custom compilation errors and warnings will be added that are ECS specific. :)
     
  17. floboc

    floboc

    Joined:
    Oct 31, 2017
    Posts:
    91
    I just came accross a use case.

    I am currently working on a collision system :
    Entities that can collide have a Position component, a Collider component and a RigidBody component (these structs inherit IComponentData).
    When a collision between two objects occurs, I have to somehow signal this collision event so that it can be processed by other systems.
    To achieve that, I chose to create a new Entity with a CollisionEvent attached to it.
    This event should contain information about the objects at the time of the collision event.

    So I have to store the Position, Collider and RigidBody components values inside of the CollisionEvent for each of the two colliding objects. Do you see any other way to achieve that without nesting IComponentData structs ?
     
  18. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    system that inject all 4 IComponentData? CollisionEvent should have only nessesary information about other entity
     
  19. floboc

    floboc

    Joined:
    Oct 31, 2017
    Posts:
    91
    I need at least to store the Position data (see it as the Unity's transform) as it could be modified by another system before the collision event is processed. And what if I need access to the colliders that actually collided ? Should I then look if a Collider component exists on the Entity at the moment the event is processed ? I thought this was not very efficient.

    Or did you mean that I should not create a separate entity for the event, but instead add the component to the moving object with only the info about the second object ? What if more collisions occur at the same frame since it seems we cannot add more than one component of the same type ?
     
    Last edited: Apr 12, 2018
  20. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    Nope, I mean this: entities that collides gets IComponentData CollisionEvent with all necessary data in it to process this collision.
    While you should set system execution order for setting up a priority for position update (so it will be updated as you wish), CollisionEvent should have corresponding data of collided objects so each object can change only itself instead of other.
    It seems right to not be able to add more components of one type. How can you process two CollisionEvent components? That entity should be process twice by system that expects one component or once by system that expects two components? So sure, you shouldn't be able to do such ambiguous thing.
    To resolve that problem I think there need to be a list of collisions data in CollisionEvent component so one system can process all collisions at once for a entity.

    But still I think we need to wait until Unity Team introduce us to ECS integrated collisions. They really have the answers.
     
    recursive likes this.
  21. mike_acton

    mike_acton

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    110
    > To achieve that, I chose to create a new Entity with a CollisionEvent attached to it.

    Conceptually, this seems fine.

    If you need to refer to source and target entities (and you *always* have source an target entities) You can add those as in:
    Code (CSharp):
    1. struct CollisionEvent : IComponentData
    2. {
    3.   Entity Source;
    4.   Entity Target;
    5. }
    And resolve look up the components you need by Entity in the system that reads CollisionEvent.

    If source and/or target are optional, you might split those into separate components so that you have
    Code (CSharp):
    1. struct CollisionEvent: IComponentData
    2. {
    3. }
    4. struct CollisionSource : IComponentData
    5. {
    6.   Entity Value;
    7. }
    8. struct CollisionTarget : IComponentData
    9. {
    10.   Entity Value;
    11. }
    Plus other components for any other info you may or may not have with the collision event. It's hard to answer this question in the abstract though. Best practice will be driven by what you specifically need, concretely.
     
    Zoey_O and Cynicat like this.
  22. floboc

    floboc

    Joined:
    Oct 31, 2017
    Posts:
    91
    THank you both for your answers. I chose to go with the approach suggested by Miruku since it allows for instance to filter entities based on the fact that they are colliding or not, while the "independant entity" solution would need to iterate other all entities to see if they match the collision event entities.

    Seems like I will have to have a look at arrays inside components to handle multiple events.