Search Unity

How to handle Utility methods which always need the same data

Discussion in 'Entity Component System' started by PublicEnumE, Aug 19, 2019.

  1. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Here's a pattern I've been running into with my Utility methods:

    Let's say I'm making a game that's about managing flocks of Sparrows. Several of my game's Systems may need to spawn new Sparrows, and multiple programmers on my team may be writing these systems.

    So to simplify and standardize this work, I create a utility method:

    Code (CSharp):
    1. public static class SparrowCreationUtility
    2. {
    3.     public static void CreateSparrow(EntityCommandBuffer entityCommandBuffer, EntityArchetype sparrowArchetype, SparrowSetupData sparrowSetupData)
    4.     {
    5.         Entity sparrowEntity = entityCommandBuffer.CreateEntity(sparrowArchetype);
    6.  
    7.         // use SparrowSetupData to configure this sparrow entity.
    8.     }
    9. }
    But if a programmer on my team wants to call this method from a job, they must pass in both the EntityCommandBuffer and the archetype used to create the sparrow. That means they also must add them as members of their job:

    Code (CSharp):
    1. public struct SomeJob
    2. {
    3.     public EntityCommandBuffer sparrowCreationBuffer;
    4.     public EntityArchetype sparrowArchetype;
    5.  
    6.     public void Execute()
    7.     {
    8.         SparrowSetupData sparrowSetupData = new SparrowSetupData
    9.         {
    10.             // setup sparrow config data
    11.         };
    12.  
    13.         BirdCreationUtility.CreateSparrow(sparrowCreationBuffer, sparrowArchetype, sparrowSetupData);
    14.     }
    15. }
    In this case,
    sparrowCreationBuffer
    and
    sparrowArchetype
    should always be the same value.

    As is, this setup leaves too much room for human error. Passing the wrong ECB or archetype to the Utility method would cause bugs that would need to be tracked down - not very scalable for a team.

    - - -

    Is there a better way to do this in DOTS? Is there a way to:

    1. Prevent the user from having to configure all of that data in the job, just to pass it into a Utility method?
    2. Guarantee that the Utility method is always using the same (correct) ECBs and archetypes?

    So far, I've been investigating using:
    • ISharedComponentData
    • job-safe readonly statics,
    • The singleton API
    • blob assets

    It seems like a solution could kind-of be put together using most of these. But most of my thinking so far feels like I would be using the features in a very unintended way. And I'm unsure what the perf implications would be.

    What would you do?

    Thank you for any advice.

    - - -

    PS. I expect someone might (wisely) comment that I shouldn't have multiple systems actually creating Sparrow entities - That instead I should make a single "SparrowCreationSystem" which is the only system that does this, and therefore only that system would need to know which ECB and archetype to use.

    That's wise advise, but it inadvertently creates the same problem: In order for that SparrowCreationSystem to know to make a new Sparrow, earlier Systems would need to spawn "SparrowCreation" 'event' entities.

    So now, we have the same problem, but with the ECB/archetype for the event entities, rather than the Sparrow itself.
     
    Last edited: Aug 19, 2019
  2. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I have an idea:

    1. have a single "SparrowCreationSystem" which does all of the Sparrow creation.
    2. Store a persistent NativeArray<SparrowSetupData> on that System.
    3. Store a reference to that System in other systems which need to spawn sparrows.
    4. When one of those systems needs to spawn a sparrow, add it's SparrowSetupData to that native array to queue it up for the later system to do the work.

    - - -

    That might work, but it doesn't cover all cases. For example:

    1. It doesn't immediately hand back an Entity handle, so now we're back to the days before ECB handed back temporary entities from CreatEntity().

    2. #1 means you can't create new Sparrows, and then link them together in some way (like adding them to some struct that represents a flock).
     
  3. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,780
    PublicEnumE likes this.
  4. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Thank you! I appreciate your reply.

    I'm confused, though - this doesn't seem like it would improve the situation. Now, rather than passing an ECB and EntityArchetype into the utility method, you would need to pass an ECB and an Entity (the prefab to be instanced) into the method.

    That seems to have the same problems of:
    1. Verbosity of code just to use the utility method.
    2. Potential for human error, if the team member misunderstands which prefab or ECB to use.

    How do I enforce that the same ECB and prefab/archetype are used to create Sparrows across all of my jobs and systems?
     
    Last edited: Aug 19, 2019
  5. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,780
    When you work with multiple people, it important to write clear documentation /instruction, so missuse, or misinterpretation down to happen.

    using entity prefab, gives you advantage, that you don't need setup all initial properties for new entity. Only the ones which are mandatory.

    For example prefab stores information about size, color, position direction. Now after spawn, system need to worry about position and rotation.

    Regarding spawning, there are multiple ways to handle things. You can for example create request entity, which gets information about sparrows to spawn. After request is done, spawning system handles process of spawning sparrows. You can have multiple jobs in one system if needed. New sparrows can be either marked with relevant component tag, so co-worker's sytem, can access any created sparrows by that tag.
    Or you can store all new sparrow entities in buffer, of request entity. Then relevant co-worker handles in own system, as needed.

    There are also other options.
     
  6. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I'm excited by the potential of storing entity prefabs in a Singleton component, and then referencing that in the jobs that need to spawn things.

    One question about entity prefabs:

    In the HelloCube examples, the prefab is always initially assigned using the entity conversion process. It's always done through a MonoBehavior.

    Is there a good way to load the entity representation of a prefab resource without using a MonoBehavior? Maybe in an ICustomBootstrap?
     
    Last edited: Aug 19, 2019
  7. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Looks like GameObjectConversionUtility.ConvertGameObjectHierarchy() may do the trick, if you wanted to load a prefab resource and convert it manually.