#pragma warning disable CS0282 #if MODULE_ENTITIES using Unity.Entities; using Unity.Transforms; using Unity.Burst; using Unity.Jobs; using Unity.Collections; using UnityEngine; using UnityEngine.Jobs; using GCHandle = System.Runtime.InteropServices.GCHandle; namespace Pathfinding.ECS { using Pathfinding; using Pathfinding.ECS.RVO; using Pathfinding.Drawing; using Pathfinding.Util; using Unity.Profiling; using UnityEngine.Profiling; [BurstCompile] [UpdateAfter(typeof(FollowerControlSystem))] [UpdateAfter(typeof(RVOSystem))] [UpdateAfter(typeof(FallbackResolveMovementSystem))] [UpdateInGroup(typeof(AIMovementSystemGroup))] [RequireMatchingQueriesForUpdate] public partial struct AIMoveSystem : ISystem { EntityQuery entityQueryPrepareMovement; EntityQuery entityQueryWithGravity; EntityQuery entityQueryMove; EntityQuery entityQueryRotation; EntityQuery entityQueryGizmos; EntityQuery entityQueryMovementOverride; JobRepairPath.Scheduler jobRepairPathScheduler; ComponentTypeHandle MovementStateTypeHandleRO; ComponentTypeHandle ResolvedMovementHandleRO; public static EntityQueryBuilder EntityQueryPrepareMovement () { return new EntityQueryBuilder(Allocator.Temp) .WithAllRW() .WithAllRW() .WithAllRW() .WithAll() // .WithAny() // TODO: Use WithPresent in newer versions .WithAbsent(); } public void OnCreate (ref SystemState state) { jobRepairPathScheduler = new JobRepairPath.Scheduler(ref state); MovementStateTypeHandleRO = state.GetComponentTypeHandle(true); ResolvedMovementHandleRO = state.GetComponentTypeHandle(true); entityQueryRotation = state.GetEntityQuery( ComponentType.ReadWrite(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadWrite(), ComponentType.ReadOnly(), ComponentType.ReadOnly() ); entityQueryMove = state.GetEntityQuery( ComponentType.ReadWrite(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadWrite(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadWrite(), ComponentType.ReadOnly(), ComponentType.ReadOnly() ); entityQueryWithGravity = state.GetEntityQuery( ComponentType.ReadWrite(), ComponentType.ReadOnly(), ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ReadOnly(), ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ReadOnly(), ComponentType.ReadWrite(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadOnly() ); entityQueryPrepareMovement = jobRepairPathScheduler.GetEntityQuery(Allocator.Temp).WithAll().Build(ref state); entityQueryGizmos = state.GetEntityQuery( ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadOnly(), ComponentType.ReadOnly() ); entityQueryMovementOverride = state.GetEntityQuery( ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.ReadWrite(), ComponentType.Exclude(), ComponentType.ReadOnly(), ComponentType.ReadOnly() ); } static readonly ProfilerMarker MarkerMovementOverride = new ProfilerMarker("MovementOverrideBeforeMovement"); public void OnDestroy (ref SystemState state) { jobRepairPathScheduler.Dispose(); } public void OnUpdate (ref SystemState systemState) { var draw = DrawingManager.GetBuilder(); // This system is executed at least every frame to make sure the agent is moving smoothly even at high fps. // The control loop and local avoidance may be running less often. // So this is designated a "cheap" system, and we use the corresponding delta time for that. var dt = AIMovementSystemGroup.TimeScaledRateManager.CheapStepDeltaTime; systemState.Dependency = new JobAlignAgentWithMovementDirection { dt = dt, }.Schedule(entityQueryRotation, systemState.Dependency); RunMovementOverrideBeforeMovement(ref systemState, dt); // Move all agents which do not have a GravityState component systemState.Dependency = new JobMoveAgent { dt = dt, }.ScheduleParallel(entityQueryMove, systemState.Dependency); ScheduleApplyGravity(ref systemState, draw, dt); var gizmosDependency = systemState.Dependency; UpdateTypeHandles(ref systemState); systemState.Dependency = ScheduleRepairPaths(ref systemState, systemState.Dependency); // Draw gizmos only in the editor, and at most once per frame. // The movement calculations may run multiple times per frame when using high time-scales, // but rendering gizmos more than once would just lead to clutter. if (Application.isEditor && AIMovementSystemGroup.TimeScaledRateManager.IsLastSubstep) { gizmosDependency = ScheduleDrawGizmos(draw, systemState.Dependency); } // Render gizmos as soon as all relevant jobs are done draw.DisposeAfter(gizmosDependency); systemState.Dependency = ScheduleSyncEntitiesToTransforms(ref systemState, systemState.Dependency); systemState.Dependency = JobHandle.CombineDependencies(systemState.Dependency, gizmosDependency); } void ScheduleApplyGravity (ref SystemState systemState, CommandBuilder draw, float dt) { Profiler.BeginSample("Gravity"); // Note: We cannot use CalculateEntityCountWithoutFiltering here, because the GravityState component can be disabled var count = entityQueryWithGravity.CalculateEntityCount(); var raycastCommands = CollectionHelper.CreateNativeArray(count, systemState.WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory); var raycastHits = CollectionHelper.CreateNativeArray(count, systemState.WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory); // Prepare raycasts for all entities that have a GravityState component systemState.Dependency = new JobPrepareAgentRaycasts { raycastQueryParameters = new QueryParameters(-1, false, QueryTriggerInteraction.Ignore, false), raycastCommands = raycastCommands, draw = draw, dt = dt, gravity = Physics.gravity.y, }.ScheduleParallel(entityQueryWithGravity, systemState.Dependency); var raycastJob = RaycastCommand.ScheduleBatch(raycastCommands, raycastHits, 32, 1, systemState.Dependency); // Apply gravity and move all agents that have a GravityState component systemState.Dependency = new JobApplyGravity { raycastHits = raycastHits, raycastCommands = raycastCommands, draw = draw, dt = dt, }.ScheduleParallel(entityQueryWithGravity, JobHandle.CombineDependencies(systemState.Dependency, raycastJob)); Profiler.EndSample(); } void RunMovementOverrideBeforeMovement (ref SystemState systemState, float dt) { if (!entityQueryMovementOverride.IsEmptyIgnoreFilter) { MarkerMovementOverride.Begin(); // The movement overrides always run on the main thread. // This adds a sync point, but only if people actually add a movement override (which is rare). systemState.CompleteDependency(); new JobManagedMovementOverrideBeforeMovement { dt = dt, // TODO: Add unit test to make sure it fires/not fires when it should }.Run(entityQueryMovementOverride); MarkerMovementOverride.End(); } } void UpdateTypeHandles (ref SystemState systemState) { MovementStateTypeHandleRO.Update(ref systemState); ResolvedMovementHandleRO.Update(ref systemState); } JobHandle ScheduleRepairPaths (ref SystemState systemState, JobHandle dependency) { Profiler.BeginSample("RepairPaths"); // This job accesses graph data, but this is safe because the AIMovementSystemGroup // holds a read lock on the graph data while its subsystems are running. dependency = jobRepairPathScheduler.ScheduleParallel(ref systemState, entityQueryPrepareMovement, dependency); Profiler.EndSample(); return dependency; } JobHandle ScheduleDrawGizmos (CommandBuilder commandBuilder, JobHandle dependency) { // Note: The ScheduleRepairPaths job runs right before this, so those handles are still valid return new JobDrawFollowerGizmos { draw = commandBuilder, entityManagerHandle = jobRepairPathScheduler.entityManagerHandle, LocalTransformTypeHandleRO = jobRepairPathScheduler.LocalTransformTypeHandleRO, AgentCylinderShapeHandleRO = jobRepairPathScheduler.AgentCylinderShapeTypeHandleRO, MovementSettingsHandleRO = jobRepairPathScheduler.MovementSettingsTypeHandleRO, AgentMovementPlaneHandleRO = jobRepairPathScheduler.AgentMovementPlaneTypeHandleRO, ManagedStateHandleRW = jobRepairPathScheduler.ManagedStateTypeHandleRW, MovementStateHandleRO = MovementStateTypeHandleRO, ResolvedMovementHandleRO = ResolvedMovementHandleRO, }.ScheduleParallel(entityQueryGizmos, dependency); } JobHandle ScheduleSyncEntitiesToTransforms (ref SystemState systemState, JobHandle dependency) { Profiler.BeginSample("SyncEntitiesToTransforms"); int numComponents = BatchedEvents.GetComponents(BatchedEvents.Event.None, out var transforms, out var components); if (numComponents == 0) { Profiler.EndSample(); return dependency; } var entities = CollectionHelper.CreateNativeArray(numComponents, systemState.WorldUpdateAllocator); for (int i = 0; i < numComponents; i++) entities[i] = components[i].entity; dependency = new JobSyncEntitiesToTransforms { entities = entities, syncPositionWithTransform = SystemAPI.GetComponentLookup(true), syncRotationWithTransform = SystemAPI.GetComponentLookup(true), orientationYAxisForward = SystemAPI.GetComponentLookup(true), entityPositions = SystemAPI.GetComponentLookup(true), movementState = SystemAPI.GetComponentLookup(true), }.Schedule(transforms, dependency); Profiler.EndSample(); return dependency; } } } #endif