Search Unity

Is it bad form to store Entity lookup data outside of ECS?

Discussion in 'Entity Component System' started by jonwah00, May 11, 2020.

  1. jonwah00

    jonwah00

    Joined:
    Jul 21, 2019
    Posts:
    36
    To describe this question, first I'll describe the problem which is leading me to ask it:

    I'm creating a hex grid at runtime - an entity is created for each grid cell. There are many times when I want to know what the entity is at a particular co-ordinate - sometimes in a System, sometimes not. Sometimes I need to get one entity, sometimes I need to get a bunch (pathfinding etc).

    I know I can attach the coordinate component to each entity, but as far as I know there's no real way, while inside a job, to 'find' a particular entity based on it's component values. I can (and do) store the neighbours on each entity, but I don't want to have to recursively walk through each neighbour to find data - and sometimes that might not even be possible.

    So, what I'm thinking about doing is populating a static section of memory (array, dictionary etc) with references to each entity, and the hex coordinates. This will let me look up entities based on their coordinates quite quickly. I think it's ok to do this, as these entities get instantiated once and never change.

    But, it feels like a code smell. So, is this an anti-pattern? Is there a better way to acheive what I want (fast lookup of an entity based on their component's data)?
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    Acceleration structures using NativeContainers are totally appropriate in ECS. In most ECS architectures, you would store the container on a component on an entity just like anything else. But Unity prefers to store these containers on systems for some odd reason.
     
    jonwah00 likes this.
  3. jonwah00

    jonwah00

    Joined:
    Jul 21, 2019
    Posts:
    36
    Ok, and do you make these containers static?
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    No. I use a custom mechanism for associating containers with entities. I suggest going with whatever feels comfortable for you. However, make sure you always keep the JobHandle (or two if you want to differentiate ReadOnly) of whatever uses the collection near the collection. Otherwise you will usually find yourself in some kind of pain.
     
    jonwah00 likes this.
  5. jonwah00

    jonwah00

    Joined:
    Jul 21, 2019
    Posts:
    36

    Thanks for that, but what do you mean by the job handles? In my case the data is initialised once and never written to again; it's basically a read only lookup, used by many systems, for coordinates to entities and vice versa; so would that be ok to be static?

    It's weird, because systems are initialised by ECS for you - like a DI provider - but there's no way that I've seen to customise that initialisation and associate data with the system instance.

    Do you mind if I ask you what your tactic for associating data with systems?
     
  6. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    If this is your use case you may not need JobHandles. And even if you did and decided to make your container static, you would want to make the JobHandles static as well within the same class.

    ICustomBootstrap lets you customize the initialization process. There's not a lot of good documentation on it and the best beginner example is buried somewhere in the changelog.

    I don't usually associate data with systems. I associate collections with Entities. The feature is part of an overall much larger and more complicated framework that is available publicly. Link to specific feature: https://github.com/Dreaming381/Latios-Framework/tree/master/Documentation/Core#collection-components

    Note that the current public version of the framework (0.1.0) is broken. I am in the process of stabilizing 0.2.0, though it has been taken me a lot longer than I intended due to lack of time and energy.

    Here's an example of what usage of it looks like in an actual project:
    Defining the collection:
    Code (CSharp):
    1. public struct FactionShipsCollisionLayer : ICollectionComponent
    2.     {
    3.         public CollisionLayer layer;
    4.  
    5.         public Type AssociatedComponentType => typeof(FactionTag);
    6.  
    7.         public JobHandle Dispose(JobHandle inputDeps) => layer.Dispose(inputDeps);
    8.     }
    Usage in multiple systems (Note the Dependency property is automatically updated whenever this FactionShipCollisionLayer is fetched. Also the CompleteDependency call is simply a requirement for the debug method. In most cases I use Dependency as an inputDeps or even ignore it if using lambdas.):
    Code (CSharp):
    1. using Latios;
    2. using Latios.PhysicsEngine;
    3. using Unity.Collections;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6.  
    7. namespace Lsss
    8. {
    9.     public class BuildFactionShipsCollisionLayersSystem : SubSystem
    10.     {
    11.         private EntityQuery m_query;
    12.  
    13.         protected override void OnCreate()
    14.         {
    15.             m_query = Fluent.WithAll<ShipTag>(true).WithAll<FactionMember>().PatchQueryForBuildingCollisionLayer().Build();
    16.         }
    17.  
    18.         protected override void OnUpdate()
    19.         {
    20.             Entities.WithAll<FactionTag, Faction>()
    21.             .ForEach((Entity factionEntity) =>
    22.             {
    23.                 var factionMemberFilter = new FactionMember { factionEntity = factionEntity };
    24.                 m_query.SetSharedComponentFilter(factionMemberFilter);
    25.                 Dependency =
    26.                     Physics.BuildCollisionLayer(m_query, this).ScheduleParallel(out CollisionLayer layer, Allocator.Persistent, Dependency);
    27.                 EntityManager.SetCollectionComponentAndDisposeOld(factionEntity, new FactionShipsCollisionLayer { layer = layer });
    28.                 m_query.ResetFilter();
    29.             }).WithoutBurst().Run();
    30.         }
    31.     }
    32.  
    33.     public class DebugDrawFactionShipsCollisionLayersSystem : SubSystem
    34.     {
    35.         protected override void OnUpdate()
    36.         {
    37.             Entities.WithAll<FactionTag, Faction>()
    38.             .ForEach((Entity factionEntity) =>
    39.             {
    40.                 var layer = EntityManager.GetCollectionComponent<FactionShipsCollisionLayer>(factionEntity, true);
    41.                 CompleteDependency();
    42.                 PhysicsDebug.DrawLayer(layer.layer);
    43.             }).WithoutBurst().Run();
    44.         }
    45.     }
    46. }
    47.  
    48.