Threading and Handling PathRequests in ECS Jobs

Hello guys, I have a question about how i would go about keeping a handle on the pathing requests for my entities. Currently i use a IComponentData to request the path for an agent using its current location to its destination. The Issue is that we currently have to use BlockUntilCalculated() so we don’t lose our handle on the entity that requested the path. I know that in Mono you can have the OnPathComplete callback to handle this but it doesnt seem to be an option in SystemBase. If you guys have any suggestions please let me know. Heres the Entities.ForEach job.

        Entities.WithNone<InitializeUnit>().ForEach((Entity entity, ref CalculatePathData requestPath, ref DynamicBuffer<UnitMovementBuffer> movementBuffer ,in LocalTransform transform) =>
        {
            // Set the starting position for the path.
            requestPath.startPosition = transform.Position;
            
            //Create a new ABPath.
            ABPath path;
            
            // Construct the path. Not sure what this does exactly but i saw this in a previous post on the forum.
            path = ABPath.Construct(requestPath.startPosition, requestPath.endPosition);
            
            // Start the Path.
            AstarPath.StartPath(path);
           
            // Block the path so we can keep a handle on the Entity that requested it.
            path.BlockUntilCalculated();
            //var pathPoints = path.path;
            
            // apply the modifier to the path.
            new StartEndModifier().Apply(path);
            
            // path simplification for RecastGraph below.
            var parts = Funnel.SplitIntoParts(path);
            var nodes = path.path;
            Funnel.Simplify(parts, ref nodes);
            
            // Clearing our movement buffer so we can add the new path.
            movementBuffer.Clear();
            
            // Funnel the final path.
            for (int i = 0; i < parts.Count; i++)
            {
                var part = parts[i];
                var portals = Funnel.ConstructFunnelPortals(nodes, part);
                var pathThroughPortals = Funnel.Calculate(portals, splitAtEveryPortal: false);
                for (int j = 0; j < pathThroughPortals.Count; j++)
                {
                    movementBuffer.Add(new UnitMovementBuffer { Waypoint = pathThroughPortals[j] });
                }
            }
            // Turning off our Path Calculation Data so it doesnt reprocess.
            EntityManager.SetComponentEnabled<CalculatePathData>(entity,false);
            
        }).WithStructuralChanges().WithoutBurst().Run();

Hi

The absolute easiest is to just capture the entity reference in the onpathComplete callback.

If you then get the world’s entity manager, you can update any components on the entity that you want.

The systems for the FollowerEntity does things a bit differently. It stores the path object as a field of type object (to prevent Unity from complaining too much), and then it checks every frame if the path has been calculated. It also runs this system manually when the AstarPath.OnPathsCalculated event fires.

You can look at the code for reference in PollPendingPathsSystem.cs (available in the beta).

So i might just be missing something here, but am i trying to add a callback like this

System.Action<Entity> pathComplete = (Entity test) =>
            {
                //Do things with the entity here
            };
AstarPath(path, pathComplete(entity));

I tried this, and the StartPath() doesnt accept these types of events. It says “Argument type ‘void’ is not assignable to parameter type ‘bool’”.

If you could give me an example of what you mean by capture the entity reference in the onpathcomplete that would be awesome. I read over your PollPendingPathsSystem and the callback that is used isn’t able to accept the Entity type, at least as far as i can see.

Thank you for the help :slight_smile:

Hi

You can capture the reference. You can read up more on C# delegates to learn more about this.

ForEach((Entity entity, ...) => {
    StartPath(path, (Path path) => {
        // Entity reference still exists in the callback
        entityManager.DoSomethingWith(entity);
    });
});

Thanks Aron,

We got it to work outside the Entities.ForEach by just querying for them instead. Both Lamda’s and Delegates are new concepts for me so I appreciate the help. I’ll post my working code below for those that fall upon this.

var requestedPathQuery = EntityManager.CreateEntityQuery(typeof(RequestPathData));

        
        if (requestedPathQuery.CalculateEntityCount() > 0)
        {
            // Convert the query to an Entity Array to loop through and grab handles for the callback.
            var requestingEntities = requestedPathQuery.ToEntityArray(Allocator.Temp);
            
            // component lookup handles.
            var GetCalculatePathData = GetComponentLookup<RequestPathData>();
            
            for (int i = 0; i < requestingEntities.Length; i++)
            {
                // Entity handle from the Array.
                var requestingEntity = requestingEntities[i];

                // Get the Active Request Data.
                var requestPath = GetCalculatePathData[requestingEntity];

                // Create a new path.
                ABPath path;
                
                // Construct the path, and attach the Callback method.
                path = ABPath.Construct(requestPath.startPosition, requestPath.endPosition, (Path path) => OnPathComplete(path, requestingEntity));
                
                // Start Processing that Path.
                AstarPath.StartPath(path);
                
                // Turning off our Request Path Data so it doesnt reprocess.
                EntityManager.SetComponentEnabled<RequestPathData>(requestingEntity,false);
            }
        }

The Callback looks like this:

private void OnPathComplete(Path path, Entity requestedEntity)
    {
        // apply the modifier to the path.
        new StartEndModifier().Apply(path);
            
        // path simplification for RecastGraph below.
        var parts = Funnel.SplitIntoParts(path);
        var nodes = path.path;
        Funnel.Simplify(parts, ref nodes);

        var movementBuffer = EntityManager.GetBuffer<UnitMovementBuffer>(requestedEntity);
        // Clearing our movement buffer so we can add the new path.
        movementBuffer.Clear();
            
        // Funnel the final path.
        for (int i = 0; i < parts.Count; i++)
        {
            var part = parts[i];
            var portals = Funnel.ConstructFunnelPortals(nodes, part);
            var pathThroughPortals = Funnel.Calculate(portals, splitAtEveryPortal: false);
            for (int j = 0; j < pathThroughPortals.Count; j++)
            {
                movementBuffer.Add(new UnitMovementBuffer { Waypoint = pathThroughPortals[j] });
            }
        }
    }

I’m sure you can get the Callback directly from the AstarPath.StartPath, but i was unable to figure that out.

Thanks again!

1 Like