Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Advice on how to structure things in ECS

Discussion in 'Entity Component System' started by Joshdbb, Dec 9, 2020.

  1. Joshdbb

    Joshdbb

    Joined:
    Oct 28, 2014
    Posts:
    50
    I am trying to implement a kind of flow field navigation in ECS and am struggling with how to structure things.

    For each possible target I need to calculate a 'flow field' (which is a grid of direction vectors, flattened into an array) then for each navigation agent I need to access the flow field that corresponds to that agent's target and check the direction in the occupied cell.

    The issue that I'm running into is the limitation on nested NativeContainers. Ideally I want to have a hashmap containing all of the different flow fields so an agent can access the correct flow field based on the id of their target. Unfortunately this doesn't seem to be possible as a NativeHashMap can't contain a NativeArray (which is what the flow field is).

    I have managed to calculate the flow fields and store them in a Dictionary of NativeArrays but now I am unsure of how to access the correct flow field while iterating through the nav agents as you can't access a Dictionary from a job unless it is run on the main thread.

    So one solution would be to do this on the main thread but there are a lot of agents so would rather avoid that if possible.

    Another possible solution would be to flatten all of the flow fields into a single array but this could be tricky as targets will be changing and being added/removed during gameplay. Also the array would be vast. So once again would rather avoid if possible.

    Currently what I am doing:
    1. Create an entity for each possible target in the level.
    2. Use an 'Entities.ForEach' to find all targets that need their flow field calculated and add them to a NativeQueue.
    3. Initialise an empty flow field if needed (NativeArray) and add it to a Dictionary.
    4. Take some of the targets from the queue and run a job for each of them, passing in the correct flow field (NativeArray) for them to fill.
    5. Scratch my head in confusion about how to access this data when looping through nav agents.

    I feel like there may be some kind of route using DynamicBuffers but I am unsure if this makes sense or will work. Maybe I can store the flow field in a dynamic buffer attached to the target entity. I am unable to store a NativeHashMap containing DynamicBuffers but maybe I can store references to the Entities themselves in the hashmap then use that to fetch the buffer containing the flow field while iterating through the agents.

    Edit - Actually it seems that I can't use the EntityManager from within a job so no way to fetch the dynamic buffer from the Entity. I am stumped.

    This seems like a bit of a round about way of doing things and I suspect that I am just missing some better way of approaching this so I thought I should ask. I am very new to ECS so this may all be completely wrong.
     
    Last edited: Dec 9, 2020
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,937
    Storing the entities containing the dynamic buffers in a hashmap is a valid approach.

    A second approach would be a struct like this:
    Code (CSharp):
    1. public struct MultiFlowField
    2. {
    3.     [NoAlias] internal NativeList<float2> flowFieldBuffer;
    4.     [NoAlias] internal NativeHashMap<float2, int> targetToFlowFieldIndexMap;
    5.     [NoAlias] internal NativeList<int> flowFieldFreeList;
    6.  
    7.     //...public API...
    8. }
    flowFieldBuffer is a 3-dimensional resizable array serving as a pool of flow field grids. The hashmap maps the target to the index in the pool. And the free list is a stack of currently unused indices in the pool.

    A third approach would be writing a custom container using UnsafeHashMap containing pointer arrays.
     
    friflo likes this.
  3. Joshdbb

    Joshdbb

    Joined:
    Oct 28, 2014
    Posts:
    50
    Thanks for the quick reply.

    With the dynamic buffers approach I realised that I can't use the EntityManager from a job to fetch the buffer for an entity. Do you know of another way that I could access the DynamicBuffer attached to the stored target Entity while looping through the nav agents in an 'Entities.ForEach'?

    Thanks for the second approach, that is a nice way to handle flattening the entire thing. I had a quick look at the 'NoAlias' attribute and it went over my head. My understanding is that it is an optimisation that you need to be careful about using. Would it definitely be safe for me to use it here in my implementation? Any tips on when to use it?

    The third approach sounds interesting but maybe beyond me at this point. Do you happen to know where I could find an example of something similar?
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,937
    BufferFromEntity. It works just like ComponentDataFromEntity.

    If you only have one of these structs in a job and the struct fully owns the containers and does not give out copies of these containers such that they could also be stored as fields (direct or nested inside other structs) within the same job, then [NoAlias] is safe. As complicated as that rule sounds, this is usually the case. But if you aren't sure, drop the attribute. It will still work.

    DOTS is still too new to find many examples of custom containers. While I have written custom containers myself, they work very differently from what you need.
     
    Joshdbb likes this.
  5. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    232
    I'm doing something similar I'm just creating the flow field and having each cell as an entity, that's for only one flow field though, not sure if it's good way but. I have yet to decide what to do for multiple fields maybe tags or a component array to denote which flow field they are a member of.