Current state and plans for ECS integration?

Sorry, been quite busy lately so I had no time to follow up on this. It’s quite amazing what you could achieve so far. I’m just playing around with your repo.

I’m not entirely sure however what causes the big performance impact of the baked followerentity scene. I get heave frame drops before reaching even 1000 agents - something that I’m pretty sure, I did in some other project with the unbaked followerEntities without problems.

EDIT:
In a build and setting the movement plane source to graph instead of raycast, I could run around 2000 agents at 60fps.

1 Like

For me, in either scenario (the scene that uses FollowerEntity and the scene that uses AStarPath.StartPath() + custom movement script), I max out at about 1,200 entities on my 3 year old gaming laptop. That’s good to know that graph is faster than raycast - good catch!

If you want to use the FollowerEntity method in your project I think all you need is FollowerAgentAuthoring.cs and FollowerAgentInitSystem.cs. But as you said… it won’t use ECS Physics.

The other method can sorta work with ECS physics if you add a collider+shape to the agents and modify PathMovementSystem.cs to push the agent with velocity forces instead of setting the local transform directly (which basically teleports each frame). I tried it before, and it mostly worked, though the agents got stuck sometimes if the hills were steep and occasionally they randomly started spinning or floating.

1 Like

I see there is now an official 5.0 release. Congrats, it’s really awesome how much dedication you give to this project Aron.
Is there a chance, we can get an “official” Baker for the FollowerEntity at some point? It’s ok, if we keep using a custom one, but it would be nicer to have an officially maintained one to avoid version conflicts etc.
The approach with the FollowerAgentInitSystem, that Ph0t0n outlined, to overcome the problems with the pointers in the ManagedState works pretty well. Could be part of the official implementation.

2 Likes

Sorry, don’t want to be annoying, but could we maybe get a quick statement on the option for an inbuilt authoring script? Is this actually something on the roadmap?

Hi

I have plans to make an authoring script. But I don’t think I’ll have time in the near future, I’m afraid. Too many other commitments.

Fair enough, it’s also not super urgent, would just be a big quality of life improvement :wink:

1 Like

I updated mine to Entities 1.2 a few days ago if you want to use it.

https://gitlab.com/lclemens/AStarPfpBenchmark/-/blob/master/Assets/Scripts/FollowerAgentAuthoring.cs?ref_type=heads

https://gitlab.com/lclemens/AStarPfpBenchmark/-/blob/master/Assets/Scripts/FollowerAgentInitSystem.cs?ref_type=heads

1 Like

That’s very kind, I adjusted my authoring script also a few weeks back to work with entities 1.2 and pathfinding 5.1, so all good at the moment. But thanks anyways.

Do you by any chance also get a lot of errors like:

Caught in a potentially infinite loop. The navmesh probably contains degenerate geometry.

I tried also your authoring scripts, but the same thing happens. I’m not sure when this error started to show up frequently, maybe around upgrade 5.09?

No I have never seen that error before. I’m using 5.1.0 with the Recast Graph.
Maybe try messing with some of the parameters in your navmesh and rebaking it?

Thanks. I think it must have something to do with the entities spawning, because I only get it the moment spawning them. The error is triggered by the Pathtracer which is called by the JobRepairPath. Maybe it’s because of an invalid path or starting position or alike.
Anyways, this is probably of topic…

If anyone wonders: Looks like this error came from setting an actual initial destination point in the authoring script instead of setting it to PositiveInfinity. At least now the error is gone.

1 Like

Hey, I’m having this same issue regarding the infinite loop. Can you expand on what you mean by setting the initial destination point? On start I’m setting followerAi.destination = Vector3.positiveInfinity; but still running into the errors unfortunately.

edit: just realized this thread is about ECS. Looks like I have the same problem outside of ECS, so I’m not sure I’ll be able to apply the same fix.

Yes setting the destination to positiveInfinity (in ecs case float3(float.positiveInfinity,float.positiveInfinity,float.positiveInfinity) fixed 90% of cases of this error. I also still get them now and then, but not that frequently anymore.

But I agree there seems to be a underlying problem which causes this error and it might be worth investigating further. But probably better in a new dedicated thread.

To clarify, currently Entities with Dots physics is not supported?

So to carve the navmesh an Entity requires a paired GameObject with old Physics component?

As far as I’m aware: I’m afraid so.
You’re gonna need basically duplicate colliders for everything navmesh related…

I have an A*PFP’s local avoidance and pathfinding project working with Entities and DOTS physics in Unity 6. It works well as far as I can tell (with NO game-objects except for the AStarPath). However, for navmesh cutting/carving, I have not found a solution without tethering a gameobject (NavmeshCut+Collider) to every entity that you want to be a navmesh cutter. I’m also using the Project Dawn Agents Navigation asset and I once asked the author (Lukas) about it and he said it has the same issue - you pretty much have to tether a game object (NavMeshObstacle) to every navmesh carving entity. It sucks, but it’s all we have right now. One of these days tertle in the Unity forums will release his pure ECS pathfinding, navmesh building, and local avoidance solution called Traverse which won’t require tethered game-objects.

1 Like

I found this ECS implementation

But it just uses Unity new Navigation system.

So it definitely can be done, I am just not tech savvy enough to do it for this Astar.

That solution is adequate if you don’t care about local avoidance. I’m pretty sure it also has the same navmesh carving mechanism which requires tethered game-objects. Unfortunately it uses the “New” unity navigation system (com.unity.ai.navigation, released less than a year ago) which is now deprecated, and there is no replacement yet for a low-level navmesh API :laughing:

I am also working on an authoring component, so far its working well. The performance of pure ECS has me blown away, those transform syncs add up when you get to a few thousand units.

For this to work you have to add [ChunkSerializable] to the ManagedState class or the authoring Baker isn’t able to add it.

using Pathfinding.ECS;
using Pathfinding.PID;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

namespace Pathfinding
{
	public class FollowerEntityAuthoring : MonoBehaviour
	{
		[SerializeField]
		AgentCylinderShape shape = new AgentCylinderShape
		{
			height = 2,
			radius = 0.5f,
		};
		[SerializeField]
		MovementSettings movement = new MovementSettings
		{
			follower = new PIDMovement
			{
				rotationSpeed = 600,
				speed = 5,
				maxRotationSpeed = 720,
				maxOnSpotRotationSpeed = 720,
				slowdownTime = 0.5f,
				desiredWallDistance = 0.5f,
				allowRotatingOnSpot = true,
				leadInRadiusWhenApproachingDestination = 1f,
			},
			stopDistance = 0.2f,
			rotationSmoothing = 0f,
			groundMask = -1,
			isStopped = false,
		};

		[SerializeField]
		OrientationMode orientationBacking = OrientationMode.ZAxisForward;
		[SerializeField]
		MovementPlaneSource movementPlaneSourceBacking = MovementPlaneSource.Graph;

		[SerializeField]
		public ManagedState managedState = new ManagedState
		{
			enableLocalAvoidance = false,
			pathfindingSettings = PathRequestSettings.Default,
		};

		[SerializeField]
		Pathfinding.ECS.AutoRepathPolicy autoRepathBacking = Pathfinding.ECS.AutoRepathPolicy.Default;

		public class Baker : Baker<FollowerEntityAuthoring>
		{
			public override void Bake(FollowerEntityAuthoring authoring)
			{
				Entity entity = GetEntity(TransformUsageFlags.Dynamic);
				var pos = authoring.transform.position;

				//Seems like no initial are values required
				AddComponent(entity, new MovementControl { });
				AddComponent(entity, new SearchState { });
				AddComponent(entity, new ResolvedMovement { });
				AddComponent(entity, new SimulateMovement { });
				AddComponent(entity, new SimulateMovementRepair { });
				AddComponent(entity, new SimulateMovementControl { });
				AddComponent(entity, new SimulateMovementFinalize { });

				//The components for follower entity to function
				AddComponent(entity, new MovementState(pos));
				AddComponent(entity, new AgentMovementPlane(authoring.transform.rotation));
				AddComponent(entity, new DestinationPoint
				{
					destination = new float3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity),
				});
				AddComponent(entity, authoring.autoRepathBacking);
				AddComponent(entity, authoring.movement);
				//We have to initialize the pathTracer temporarily in the Baker for it to succeed
				//Since this points to the authoring GameObject's ManagedState it will get disposed anyways
				var pathTracer = new PathTracer(Allocator.Temp);
				if (!authoring.managedState.pathTracer.isCreated)
				{
					authoring.managedState.pathTracer = pathTracer;
				}
				AddComponentObject(entity, authoring.managedState);
				AddComponent(entity, new ManagedStateInit { });
				AddComponent(entity, new MovementStatistics
				{
					estimatedVelocity = float3.zero,
					lastPosition = pos,
				});
				AddComponent(entity, authoring.shape);

				AddComponent(entity, new GravityState { });
				SetComponentEnabled<GravityState>(entity, authoring.managedState.enableGravity);

				if (authoring.orientationBacking == OrientationMode.YAxisForward)
				{
					AddComponent<OrientationYAxisForward>(entity);
				}

				AddComponent(entity, new ReadyToTraverseOffMeshLink { });
				SetComponentEnabled<ReadyToTraverseOffMeshLink>(entity, false);
				AddSharedComponent(entity, new AgentMovementPlaneSource { value = authoring.movementPlaneSourceBacking });
				pathTracer.Dispose();
			}
		}
	}

	//Tag to initialize ManagedState on an entity, the ManagedState should already be added to the entity
	public struct ManagedStateInit : IComponentData { }

	//Update in TransformSystemGroup ensures that the ManagedState is setup before any simulation occurs
	[UpdateInGroup(typeof(TransformSystemGroup))]
	public partial struct ManagedStateSetupSystem : ISystem
	{
		void OnUpdate(ref SystemState state)
		{
			var world = World.DefaultGameObjectInjectionWorld;
			NativeList<Entity> removeEntities = new NativeList<Entity>(Allocator.Temp);
			foreach (var (managedState, entity) in SystemAPI.Query<ManagedState>().WithAny<ManagedStateInit>().WithEntityAccess())
			{
				//The path tracer gets disposed in the authoring component we need to create it
				if (!managedState.pathTracer.isCreated)
				{
					managedState.pathTracer = new PathTracer(Allocator.Persistent);
					world.EntityManager.SetComponentData(entity, managedState);
				}
				removeEntities.Add(entity);
			}
			//Clear the init tag
			world.EntityManager.RemoveComponent(removeEntities.AsArray(), typeof(ManagedStateInit));
			removeEntities.Dispose();
		}
	}
}
1 Like