Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Tag Components vs ISharedComponentData

Discussion in 'Entity Component System' started by slim_trunks, Apr 19, 2018.

  1. slim_trunks

    slim_trunks

    Joined:
    Dec 31, 2017
    Posts:
    41
    So recently I've encountered the following problem;
    I'm writing a module where you can create an (in principle) unlimited amount of tag components that implement the same interface and are used in generic systems as the generic type parameter.
    The number of those generic systems always equals the number of tag components.

    Now the problem is that these components can be added and removed relatively rapidly at run time.
    This is done in a job, and as of now, I didn't find a way to tell an EntityCommandBuffer to add a component of a type that is not known at compile time (because the systems can add tag components that are not of the generic parameter type).

    I thought about using ISharedComponentData instead with an id based approach but this has memory layout implications and I think shared components are not meant for fast removal and adding nor as tag components and so introduce a whole slew of new complexities.

    So has anyone any idea what to do in this situation?
     
  2. slim_trunks

    slim_trunks

    Joined:
    Dec 31, 2017
    Posts:
    41
    Ok so maybe this is kind of a marginal problem but to anyone who is interested or encounters a similar problem:

    I'm now using a code generator to generate ids based on the components implementing the interface and functions that directly operate only on those specific components in your project by name.

    This may be a little unclean because the generated code uses a switch statement to switch on the ids and add components, meaning that the switch scales directly with the amount of components.
    I'm just hoping that this will not incur an unreasonable performance loss but currently this is the best working option I can see that doesn't push any assumptions onto the ECS.
     
  3. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    Have you considered using a persistently-allocated NativeHashmap<T>? I've done similar things with Dictionary<> in the past with MonoBehaviours? The code should be cleaner and easier to maintain. In addition, large switch statements will get converted to a dictionary or jump table under the hood after like 6 entries or so, at least it worked that way in older mono / .NET 3.5).
     
  4. slim_trunks

    slim_trunks

    Joined:
    Dec 31, 2017
    Posts:
    41
    I actually didn't consider it although I really should have done that...
    The conversion to a dictionary you talk about with the generated switch statement also is a bit worrisome.

    I've now tried to implement your solution but hit a few road blocks.
    First off, NativeHashmap only accepts structs as values, but the problem is that my components only have the interface implementation in common, they are all of different types.
    Next I've tried to use a regular Dictionary with the interface type as the generic value parameter. But EntityCommandBuffer.AddComponent needs a specific struct instance and there is no way for me to know the needed type at compile time currently, so I can cast the interface instance returned by the Dictionary to it (which might not be a good idea to begin with).

    Still, thank you for your suggestion and if you have any idea how to possibly solve those issues (or the overarching issue) please don't hesitate to answer.
     
  5. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    So I've dealt with this sort of situation before. You could have a dictionary that uses the interface or concrete type as a key, then have delegate factory functions as the values. From what it sounds like you need, you can probably get away with a standardized factory
    System.Func <>
    or
    System.Action<>
    (or even a custom delegate type if you need ref / out parameters) that when handed an entity manager and an Entity performs the appropriate change. If you need to buffer it make it accept a command buffer instead.

    If you need to modify or pass additional construction arguments, you may need to look into storing a System.Multicast delegate type as the value in the dictionary, and use generic functions with certain constraints as helpers to deal with the specifics of accessing casting to the correct concrete delegate type.
    If you want to get fancy with the above method and avoid some generics, you could either have a union struct you pass in (generated by your code generator, has all possible unique blittable arguments and a factory function knows what kind to pull) or the unsafe route of passing a void* (NOT recommended) made from a stack-local variable.

    The up and coming incremental compiler has ref locals and ref returns, combine this with generic type restrictions and the generic type helpers might be a better option.

    In any case your code generator should also genrate the function that registers the factories with the dictionary, amd possible any generic helpers you might need.

    I also recommend for any factory lambda or function, it acts as a static function and captured no external state.

    And always remember casts are significantly cheaper than boxing structs.
     
    FROS7 likes this.
  6. slim_trunks

    slim_trunks

    Joined:
    Dec 31, 2017
    Posts:
    41
    Thank you for your suggestions. I'll have a go at an implementation and will get back with the results.

    Still, I think a lot of the complexity here could be avoided by having EntityCommandBuffer.AddComponent take a typeId or ComponentType (like with ComponentType.FixedArray) as an overload.
    Maybe this isn't possible because of garbage creation or other concerns but if it is, it would be a very welcome addition.
     
  7. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    I could definitely see a ComponentType at the least.