Search Unity

Best current way to store reference to an entity so it can be referred back to in other job/system?

Discussion in 'Entity Component System' started by MostHated, Feb 11, 2019.

  1. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    Hello all,
    I am working on trying to convert my pathfinding / waypoint system to ECS I am wondering what might be the best way to save reference to an entity so that I can reference it again in a different job? My plan was to have a struct that essentially would get passed around with the entitys info in it. One system would determine that the entity needs a path to follow so it would use a NativeQueue and send a struct through containing that current entitys data, get a path for it, then go back in another system and give the path to it so it can go on about its business.

    I am using IJobProcessComponentDataWithEntity which comes along with Entity entity and int index. To properly reference back to a particular entity am I going to need both of those values? I was trying to look through past code examples but most things I could find seem to be from mid last year and don't quite seem relevant anymore as a lot of them used inject and other things associated with it.

    Would it be easiest or ok to perhaps just add a unique identifier directly to my entity via storing it in IComponentData as an int and just have that be the way I identify the particular entity and do what I need to do with it, or could / should I use the IJobProcessComponentDataWithEntity's Entity and index and store those in my struct and be able to reference it that way? I read places that it's possible that those might not remain the same though just depending on what else you have going on if things end up moving indexes and what not.

    Thanks all!
    -MH
     
  2. NoDumbQuestion

    NoDumbQuestion

    Joined:
    Nov 10, 2017
    Posts:
    186
    For saving reference of waypoints or result of pathfinding inside Entity, simply use dynamic buffer. It's a list<> tag along with entity. You only have to pass an Entity to another Job and get buffer data from it.

    If you want to try make multithread navmesh pathfinding, I would advise you against it since it still a mess when I try to make one. Use single thread from old monobehavior nav agent is better.
    You can check sample from this guy
    And you can see how Unity use experiment navmeshquery (allow you to pass navmesh to Job and multithread) in Unite austin demo.
     
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    I've got navmeshes working pretty well with ecs/jobs, what problems did you have? @NoDumbQuestion
     
  4. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    I appreciate the reply. I have the paths being saved just fine, I am just trying to figure out the best way to reference the actual entity in which needed the path. I am not using navmesh or navmeshagent with any of my systems, it is a custom one I had created which just ended up working best for my current game.

    I am familiar with the sample you posted, I had given that a look over (which is what gave me the idea to try out the NativeQueue, which I quite like), but that is one of the ones that uses injection and what not, so I am not quite sure if it's the right way to go about it these days after changes to ECS and what not. Which is what I am trying to find out, just what the best/most reliable way to give the waypoint back to the particular entity who needed it would be.
     
  5. NoDumbQuestion

    NoDumbQuestion

    Joined:
    Nov 10, 2017
    Posts:
    186
    I got trouble with set max limit query steps for job which is my bad code (I just gave up on it).
    And another trouble with experiment NavmeshQuery internal API error. There is nothing much for to do beside waiting.

    I need to have dynamic navmesh surface which it need to constantly rebuild itself in some area.
    When navmesh got rebuild, some polygon on navmesh become invalid and cause all current navmeshquery that do some math solving related on that polygon return ERROR (I get around that by stop navmeshquery at end frame and return half finish path).
    And the thing is when you do multithread query navmesh, some big Job(long path query) will drag out to another frame and due to some navmesh become invalid and return short, half-finish path. And it cause me lots of headache so i just move on.
     
  6. NoDumbQuestion

    NoDumbQuestion

    Joined:
    Nov 10, 2017
    Posts:
    186
    To me, using enum inside struct for agent status and iterate over 1000 of them is fine.
    Or if you want to simplify thing, You can add "NeedPathQuerycomponent" tag to Entity through PostCommandUpdate or whatever. Then another System IProcessComponentData<AgentData,NeedPathQuerycomponent> to get all agent need path.
    The speed of Entity move between chunk when add/remove component tag is negligible.
    Just make sure the System order is right.
    Check agent need to get new path -> QueryPath -> Move (in PostUpdate or LateUpdate)
     
  7. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    I definitely know what you are saying, that is not what I am needing though. As I mentioned I am just trying to figure out the best way to reference the actual entity in which needed the path so I can give it back to him.Perhaps I am wording it incorrectly? I am not sure how else to do so.

    If I have 3 entities, A, B, and C. Each one is a vehicle. Vehicle B needs a path, so he requests it, I calculate the path in another system and it is ready to go, I have all that taken care of. I need to reference back specifically to entity B so I can give it to him. What is the best way to save entity B only within the struct I am passing to the path generation system so that it knows which entity to give the path back to so when it is done I can give entity B back his path points.
     
  8. NoDumbQuestion

    NoDumbQuestion

    Joined:
    Nov 10, 2017
    Posts:
    186
    You can
    EntityManager.GetAllEntities
    then use entity index to find/set component as simple one-thread way.

    And for execution of my current system(Not Optimal but workable for my current setup). The nav system don't care about return data to request entity. They just read NativeArray<> from entity and set Data directly. What happen to the data during that time is not nav system job to care.

    I would have NativeQueue<Agent query path data> => NavAgentQueryPathSystem: get current destination -> find path -> Set Data to DynamicBuffer or NativeArray<Path or whatever> => MoveSystem: check if path.length >0 then move.

    Edit: I dont think most ECS system not suppose to return data backward or give data to this and that system. You should think in a way about control data flow than System.
     
    Last edited: Feb 11, 2019
  9. AndesSunset

    AndesSunset

    Joined:
    Jan 28, 2019
    Posts:
    60
    Hi, MostHated. I think the miscommunications on this thread are based around a key ECS concept:

    Entities don’t actually hold any data. And they aren’t objects which act as containers for data, like GameObjects. And Entity is only an ID (literally, two integers), which you can use to look up and access data elsewhere in ram.

    Entities are also structs. There’s no way to hold a reference to one, the way you might be used to with GameObjects. But, storing an ‘Entity’ variable type should be enough to access the component data associated with that entity. Check out the EntityManager and EntityCommandBuffer APIs. Methods like EntityManager.GetComponentData will take an ‘Entity’ as an argument. There you can see how it’s being used as an Id, or key.
     
  10. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    987
    Hey tertle. Do you have example code for that? I want to compare my own implementation and that of Unity's.
     
  11. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    Yeah I think Andes summed it up well. I was also confused about what you were asking.
    I don't know how you've got it set up so can't be specific but in general, lets say you have a PathFind component which is what the path finding job system looks for.

    To pass Entity into the pathing job system
    1. You can add PathFind component directly to the car entity in which case the job can save results to the car from within the job. Easiest approach.
    2. You can spawn a new Entity separate from the car and add a PathFind component to it which includes an Entity property referencing the car.
    Code (CSharp):
    1. struct PathFind : IComponentData {
    2.    Entity car;
    3.    ...
    4. }
    To pass results back to the Entity from the pathing job
    1. If you went with approach 1 above, you just write directly to a component on the car Entity itself from within the job.
    2. If you went with 2 above:
      1. you can also write directly to the car using ComponentDataFromEntity from within the job.
      2. you can add a results component to, or SetComponentData on the car entity from within the job using a barrier and command buffer.
     
  12. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    Thanks for the reply. #2 is how I am going about it. I have a VehicleData component on each vehicle which stores things like currentWaypoint, goalWaypoint, etc. As I mentioned, I am using IJobProcessComponentDataWithEntity for a monitorsystem to keep track of each vehicle, when they reach the end goal it sends out a request over NativeQueue with the info of that entity as seen below.

    Code (CSharp):
    1.   createPath.Enqueue(new WaypointManager {vehicleData = vehicleData, entity = entity, index = index});
    Being that the vehicleData contains the goal waypoint, it uses that as the start point of the new path it generates so the car can keep going from where it left off. I am capturing the entity and the index, but my question was if they were reliable to use, and which one should I be using, considering there is index, but then I believe there is also an entity[index]. I was just trying to clarify which of those things I should be using to find the original entity that made the request so that when it is complete I can go back and update his VehicleData with the new path info.

    Unless I can find a way to make a class pretend to be and return as a float3 (which I don't think is possible) I am going to have to spend some time reworking the system anyways, as each waypoint are of class Waypoint and since I have to specifically use value types, I am going to have to change things around a bit before I can actually test it, but other than that, I was just trying to make sure I gave the right data back to the right entity.
     
    Last edited: Feb 11, 2019
  13. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    Ok, all you need is the Entity, not the index.
    The Entity itself contains an Index and a Version that uniquely identifies it.

    Using one as a reference is reliable so long as that Entity still exists.
    In cases where you're unsure, you can use EntityManager.Exists(entity)

    The index in IJobProcessComponentDataWithEntity is just the index of that entity within the current ComponentGroup collection. It's not a unique identifier for the Entity. That same Entity could show up in another job with a different index.
    Behind the scenes it's similar to
    Code (CSharp):
    1. int index = 0
    2. foreach (chunk in ComponentGroup chunks)
    3.     foreach (entity in chunk)
    4.         IJPCDWE.Execute(entity, index++, ref whatever, ..)
     
    MostHated likes this.
  14. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,780
    Just the possible direct answer to thread title, could be using BufferArrays. Then you can store multiple sets of waipoints as needed.
     
  15. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    987
    What you can do is create an A* request component that holds the requester (Entity), start and goal. You create an entity with this request component and a "waiting" component that has something like a done flag. The process then goes like this:
    1. Create request entity (Request, Waiting)
    2. System that executes the A* will do so and sets Waiting.done = true.
    3. Another system that just checks for Waiting.done will then do what it intends to do with the path. Assuming that you added a DynamicBuffer for the waypoints in the owner entity, the system can access this data via Request.requester.
    4. Remove the request entity.
     
  16. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    I appreciate all the replies. I believe I have the overall workflow worked out now, but I won't be able to test it out until later this afternoon after work. I do have the buffer within my VehicleData IComponentData, I have not tested saving anything to it yet, but I found it in a thread the other day so I assume it should work fine.

    My system was fairly heavily based off of this one:

    https://github.com/dubit/unity-waypoints/blob/master/Waypoint.cs

    As seen in the link, the waypoints themselves are a class that contains connection details via the WaypointConnection so as to help dictate one-way and two-way connections which I use for traffic flow. The actual pathing calculation requires the Waypoints class so that it has those one/two-way connection details (as well as if certain waypoints are blocked or perhaps have a high cost), so I cannot simply pass them back and forth as one might with just a list of float3 positions.

    My idea is to just keep a cache of all the Waypoints in a lookup table so that the VehicleData can have just the positions it needs and then the index number of the waypoints being used for the path it is on (my actual navigation system I created is pretty basic and just uses the float3 of the next path to turn toward it and then a forward movement), and then when I need the actual Waypoints to generate the next path I can just pull them out of the table and send the start and end through and when I get it back just pull the transform.position of each Waypoint off of the returned WaypointPath list. Should be fairly simple and straight forward and will give me what I need.