Checkpoint 8
1. 统一调度收口为单点 Complete 2. 移除 Job 内部 Complete(),改为返回 JobHandle 3. 修复调度后 NativeList 安全冲突(关键) - 将 PrepareEnemyJobOutputBuffer/PrepareProjectileJobOutputBuffer/PrepareEnemySeparationJobBuffers 前置到 BuildInput - 互斥候选统计改为读取 _enemyJobInputs,不再在 Complete 前读取 _enemyJobOutputs 4. 新增 CP8 profiler markers 5. 新增回归用例(验证使用最新敌人移动结果构建碰撞候选)
This commit is contained in:
parent
e6d722ee1d
commit
1a45b513f2
|
|
@ -8,6 +8,9 @@ namespace CustomDebugger
|
||||||
public static readonly ProfilerMarker TickEnemies_BuildInput = new ProfilerMarker("TickEnemies.BuildInput");
|
public static readonly ProfilerMarker TickEnemies_BuildInput = new ProfilerMarker("TickEnemies.BuildInput");
|
||||||
public static readonly ProfilerMarker TickEnemies_MoveSeparation = new ProfilerMarker("TickEnemies.MoveSeparation");
|
public static readonly ProfilerMarker TickEnemies_MoveSeparation = new ProfilerMarker("TickEnemies.MoveSeparation");
|
||||||
public static readonly ProfilerMarker TickEnemies_StateUpdate = new ProfilerMarker("TickEnemies.StateUpdate");
|
public static readonly ProfilerMarker TickEnemies_StateUpdate = new ProfilerMarker("TickEnemies.StateUpdate");
|
||||||
|
public static readonly ProfilerMarker TickEnemies_Schedule = new ProfilerMarker("TickEnemies.Schedule");
|
||||||
|
public static readonly ProfilerMarker TickEnemies_Complete = new ProfilerMarker("TickEnemies.Complete");
|
||||||
|
public static readonly ProfilerMarker TickEnemies_MainThreadCommit = new ProfilerMarker("TickEnemies.MainThreadCommit");
|
||||||
public static readonly ProfilerMarker TickEnemies_WriteBack = new ProfilerMarker("TickEnemies.WriteBack");
|
public static readonly ProfilerMarker TickEnemies_WriteBack = new ProfilerMarker("TickEnemies.WriteBack");
|
||||||
public static readonly ProfilerMarker Collision_BuildQueries = new("Collision.BuildQueries");
|
public static readonly ProfilerMarker Collision_BuildQueries = new("Collision.BuildQueries");
|
||||||
public static readonly ProfilerMarker Collision_BuildBuckets = new("Collision.BuildBuckets");
|
public static readonly ProfilerMarker Collision_BuildBuckets = new("Collision.BuildBuckets");
|
||||||
|
|
|
||||||
|
|
@ -133,9 +133,45 @@ namespace Simulation
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JobHandle enemyMovementHandle = default;
|
||||||
|
JobHandle projectileMovementHandle = default;
|
||||||
|
JobHandle enemySeparationHandle = default;
|
||||||
|
bool hasEnemySeparationJob = false;
|
||||||
|
bool hasEnemySeparationCandidates = false;
|
||||||
|
int enemySeparationCount = 0;
|
||||||
|
float enemySeparationMaxRadius = 0.45f;
|
||||||
|
|
||||||
using (CustomProfilerMarker.TickEnemies_BuildInput.Auto())
|
using (CustomProfilerMarker.TickEnemies_BuildInput.Auto())
|
||||||
{
|
{
|
||||||
SyncSimulationToJobInput();
|
SyncSimulationToJobInput();
|
||||||
|
int enemyCount = _enemyJobInputs.Length;
|
||||||
|
int projectileCount = _projectileJobInputs.Length;
|
||||||
|
PrepareEnemyJobOutputBuffer(enemyCount);
|
||||||
|
PrepareProjectileJobOutputBuffer(projectileCount);
|
||||||
|
|
||||||
|
enemySeparationCount = enemyCount;
|
||||||
|
for (int i = 0; i < enemyCount; i++)
|
||||||
|
{
|
||||||
|
EnemyJobInputData input = _enemyJobInputs[i];
|
||||||
|
if (!input.AvoidEnemyOverlap)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEnemySeparationCandidates = true;
|
||||||
|
float radius = input.EnemyBodyRadius > 0f ? input.EnemyBodyRadius : 0.45f;
|
||||||
|
if (radius > enemySeparationMaxRadius)
|
||||||
|
{
|
||||||
|
enemySeparationMaxRadius = radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasEnemySeparationCandidates)
|
||||||
|
{
|
||||||
|
int separationBucketCapacity = Mathf.Max(128, enemyCount * 2);
|
||||||
|
PrepareEnemySeparationJobBuffers(enemyCount, separationBucketCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
int projectileQueryCount = _projectiles.Count;
|
int projectileQueryCount = _projectiles.Count;
|
||||||
int areaQueryCount = GetPendingAreaCollisionQueryCount();
|
int areaQueryCount = GetPendingAreaCollisionQueryCount();
|
||||||
int queryCount = projectileQueryCount + areaQueryCount;
|
int queryCount = projectileQueryCount + areaQueryCount;
|
||||||
|
|
@ -148,48 +184,71 @@ namespace Simulation
|
||||||
|
|
||||||
using (CustomProfilerMarker.TickEnemies_StateUpdate.Auto())
|
using (CustomProfilerMarker.TickEnemies_StateUpdate.Auto())
|
||||||
{
|
{
|
||||||
ExecuteEnemyMovementJob(in context);
|
enemyMovementHandle = ExecuteEnemyMovementJob(in context);
|
||||||
ExecuteProjectileMovementJob(in context);
|
projectileMovementHandle = ExecuteProjectileMovementJob(in context);
|
||||||
|
}
|
||||||
|
|
||||||
|
JobHandle simulationHandle;
|
||||||
|
using (CustomProfilerMarker.TickEnemies_Schedule.Auto())
|
||||||
|
{
|
||||||
|
hasEnemySeparationJob = TryScheduleEnemySeparationForJobOutput(
|
||||||
|
in context,
|
||||||
|
enemyMovementHandle,
|
||||||
|
hasEnemySeparationCandidates,
|
||||||
|
enemySeparationCount,
|
||||||
|
enemySeparationMaxRadius,
|
||||||
|
out enemySeparationHandle);
|
||||||
|
JobHandle enemyHandle = hasEnemySeparationJob ? enemySeparationHandle : enemyMovementHandle;
|
||||||
|
simulationHandle = JobHandle.CombineDependencies(enemyHandle, projectileMovementHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (CustomProfilerMarker.TickEnemies_Complete.Auto())
|
||||||
|
{
|
||||||
|
simulationHandle.Complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
using (CustomProfilerMarker.TickEnemies_MoveSeparation.Auto())
|
using (CustomProfilerMarker.TickEnemies_MoveSeparation.Auto())
|
||||||
{
|
{
|
||||||
ApplyEnemySeparationForJobOutput(in context);
|
if (hasEnemySeparationJob)
|
||||||
|
{
|
||||||
|
CommitEnemySeparationForJobOutput(enemySeparationCount);
|
||||||
|
}
|
||||||
|
|
||||||
BuildProjectileCollisionCandidates();
|
BuildProjectileCollisionCandidates();
|
||||||
}
|
}
|
||||||
|
|
||||||
using (CustomProfilerMarker.TickEnemies_WriteBack.Auto())
|
using (CustomProfilerMarker.TickEnemies_WriteBack.Auto())
|
||||||
{
|
{
|
||||||
ApplyJobOutputToSimulation();
|
using (CustomProfilerMarker.TickEnemies_MainThreadCommit.Auto())
|
||||||
ResolveProjectileCollisionCandidatesMainThread();
|
{
|
||||||
RecycleInactiveProjectiles();
|
ApplyJobOutputToSimulation();
|
||||||
|
ResolveProjectileCollisionCandidatesMainThread();
|
||||||
|
RecycleInactiveProjectiles();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MarkEnemyTargetSpatialIndexDirty();
|
MarkEnemyTargetSpatialIndexDirty();
|
||||||
BuildEnemyTargetSpatialIndexIfNeeded();
|
BuildEnemyTargetSpatialIndexIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteEnemyMovementJob(in SimulationTickContext context)
|
private JobHandle ExecuteEnemyMovementJob(in SimulationTickContext context)
|
||||||
{
|
{
|
||||||
int enemyCount = _enemyJobInputs.Length;
|
int enemyCount = _enemyJobInputs.Length;
|
||||||
PrepareEnemyJobOutputBuffer(enemyCount);
|
|
||||||
|
|
||||||
if (enemyCount == 0)
|
if (enemyCount == 0)
|
||||||
{
|
{
|
||||||
return;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.DeltaTime <= 0f)
|
if (context.DeltaTime <= 0f)
|
||||||
{
|
{
|
||||||
CopyEnemyInputToOutput();
|
CopyEnemyInputToOutput();
|
||||||
return;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
float3 playerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
float3 playerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
||||||
NativeArray<EnemyJobInputData> inputArray = _enemyJobInputs.AsArray();
|
NativeArray<EnemyJobInputData> inputArray = _enemyJobInputs.AsArray();
|
||||||
NativeArray<EnemyJobOutputData> outputArray = _enemyJobOutputs.AsArray();
|
NativeArray<EnemyJobOutputData> outputArray = _enemyJobOutputs.AsArray();
|
||||||
|
|
||||||
JobHandle handle;
|
|
||||||
if (_useBurstJobs)
|
if (_useBurstJobs)
|
||||||
{
|
{
|
||||||
EnemyMovementBurstJob burstJob = new EnemyMovementBurstJob
|
EnemyMovementBurstJob burstJob = new EnemyMovementBurstJob
|
||||||
|
|
@ -199,21 +258,17 @@ namespace Simulation
|
||||||
DeltaTime = context.DeltaTime,
|
DeltaTime = context.DeltaTime,
|
||||||
PlayerPosition = playerPosition
|
PlayerPosition = playerPosition
|
||||||
};
|
};
|
||||||
handle = burstJob.Schedule(enemyCount, 64);
|
return burstJob.Schedule(enemyCount, 64);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
EnemyMovementJob job = new EnemyMovementJob
|
|
||||||
{
|
|
||||||
Inputs = inputArray,
|
|
||||||
Outputs = outputArray,
|
|
||||||
DeltaTime = context.DeltaTime,
|
|
||||||
PlayerPosition = playerPosition
|
|
||||||
};
|
|
||||||
handle = job.Schedule(enemyCount, 64);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handle.Complete();
|
EnemyMovementJob job = new EnemyMovementJob
|
||||||
|
{
|
||||||
|
Inputs = inputArray,
|
||||||
|
Outputs = outputArray,
|
||||||
|
DeltaTime = context.DeltaTime,
|
||||||
|
PlayerPosition = playerPosition
|
||||||
|
};
|
||||||
|
return job.Schedule(enemyCount, 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CopyEnemyInputToOutput()
|
private void CopyEnemyInputToOutput()
|
||||||
|
|
@ -238,42 +293,18 @@ namespace Simulation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyEnemySeparationForJobOutput(in SimulationTickContext context)
|
private bool TryScheduleEnemySeparationForJobOutput(in SimulationTickContext context, JobHandle dependency,
|
||||||
|
bool hasSeparationCandidates, int enemyCount, float maxRadius, out JobHandle separationHandle)
|
||||||
{
|
{
|
||||||
int enemyCount = _enemyJobOutputs.Length;
|
separationHandle = dependency;
|
||||||
if (enemyCount == 0)
|
if (enemyCount <= 0 || !hasSeparationCandidates)
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
bool hasSeparationCandidates = false;
|
|
||||||
float maxRadius = 0.45f;
|
|
||||||
for (int i = 0; i < enemyCount; i++)
|
|
||||||
{
|
|
||||||
EnemyJobOutputData output = _enemyJobOutputs[i];
|
|
||||||
if (!output.AvoidEnemyOverlap)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasSeparationCandidates = true;
|
|
||||||
float radius = output.EnemyBodyRadius > 0f ? output.EnemyBodyRadius : 0.45f;
|
|
||||||
if (radius > maxRadius)
|
|
||||||
{
|
|
||||||
maxRadius = radius;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasSeparationCandidates)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float autoCellSize = maxRadius * 2f;
|
float autoCellSize = maxRadius * 2f;
|
||||||
float configuredCellSize = _enemySeparationCellSize > 0f ? _enemySeparationCellSize : autoCellSize;
|
float configuredCellSize = _enemySeparationCellSize > 0f ? _enemySeparationCellSize : autoCellSize;
|
||||||
float cellSize = Mathf.Max(0.1f, configuredCellSize);
|
float cellSize = Mathf.Max(0.1f, configuredCellSize);
|
||||||
int bucketCapacity = Mathf.Max(128, enemyCount * 2);
|
|
||||||
PrepareEnemySeparationJobBuffers(enemyCount, bucketCapacity);
|
|
||||||
float3 playerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
float3 playerPosition = new float3(context.PlayerPosition.x, 0f, context.PlayerPosition.z);
|
||||||
float pushDamping = Mathf.Clamp(_enemySeparationPushDamping, 0f, 2f);
|
float pushDamping = Mathf.Clamp(_enemySeparationPushDamping, 0f, 2f);
|
||||||
float maxStepScale = Mathf.Max(0.1f, _enemySeparationMaxStepScale);
|
float maxStepScale = Mathf.Max(0.1f, _enemySeparationMaxStepScale);
|
||||||
|
|
@ -284,8 +315,6 @@ namespace Simulation
|
||||||
NativeArray<EnemyJobOutputData> separatedOutputArray = _enemyJobSeparationOutputs.AsArray();
|
NativeArray<EnemyJobOutputData> separatedOutputArray = _enemyJobSeparationOutputs.AsArray();
|
||||||
NativeArray<float2> previousPushes = _enemySeparationPreviousPushes.AsArray();
|
NativeArray<float2> previousPushes = _enemySeparationPreviousPushes.AsArray();
|
||||||
NativeArray<float2> currentPushes = _enemySeparationCurrentPushes.AsArray();
|
NativeArray<float2> currentPushes = _enemySeparationCurrentPushes.AsArray();
|
||||||
JobHandle buildHandle;
|
|
||||||
|
|
||||||
if (_useBurstJobs)
|
if (_useBurstJobs)
|
||||||
{
|
{
|
||||||
BuildEnemySeparationBucketsBurstJob buildJob = new BuildEnemySeparationBucketsBurstJob
|
BuildEnemySeparationBucketsBurstJob buildJob = new BuildEnemySeparationBucketsBurstJob
|
||||||
|
|
@ -294,22 +323,7 @@ namespace Simulation
|
||||||
Buckets = _enemySeparationBuckets.AsParallelWriter(),
|
Buckets = _enemySeparationBuckets.AsParallelWriter(),
|
||||||
CellSize = cellSize
|
CellSize = cellSize
|
||||||
};
|
};
|
||||||
buildHandle = buildJob.Schedule(enemyCount, 64);
|
JobHandle buildHandle = buildJob.Schedule(enemyCount, 64, dependency);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BuildEnemySeparationBucketsJob buildJob = new BuildEnemySeparationBucketsJob
|
|
||||||
{
|
|
||||||
Inputs = inputArray,
|
|
||||||
Buckets = _enemySeparationBuckets.AsParallelWriter(),
|
|
||||||
CellSize = cellSize
|
|
||||||
};
|
|
||||||
buildHandle = buildJob.Schedule(enemyCount, 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
JobHandle separationHandle;
|
|
||||||
if (_useBurstJobs)
|
|
||||||
{
|
|
||||||
EnemySeparationBurstJob separationJob = new EnemySeparationBurstJob
|
EnemySeparationBurstJob separationJob = new EnemySeparationBurstJob
|
||||||
{
|
{
|
||||||
Inputs = inputArray,
|
Inputs = inputArray,
|
||||||
|
|
@ -326,28 +340,42 @@ namespace Simulation
|
||||||
PushSmoothing = pushSmoothing
|
PushSmoothing = pushSmoothing
|
||||||
};
|
};
|
||||||
separationHandle = separationJob.Schedule(enemyCount, 64, buildHandle);
|
separationHandle = separationJob.Schedule(enemyCount, 64, buildHandle);
|
||||||
}
|
return true;
|
||||||
else
|
}
|
||||||
{
|
|
||||||
EnemySeparationJob separationJob = new EnemySeparationJob
|
BuildEnemySeparationBucketsJob nonBurstBuildJob = new BuildEnemySeparationBucketsJob
|
||||||
{
|
{
|
||||||
Inputs = inputArray,
|
Inputs = inputArray,
|
||||||
Buckets = _enemySeparationBuckets,
|
Buckets = _enemySeparationBuckets.AsParallelWriter(),
|
||||||
PreviousPushes = previousPushes,
|
CellSize = cellSize
|
||||||
Outputs = separatedOutputArray,
|
};
|
||||||
CurrentPushes = currentPushes,
|
JobHandle nonBurstBuildHandle = nonBurstBuildJob.Schedule(enemyCount, 64, dependency);
|
||||||
CellSize = cellSize,
|
EnemySeparationJob nonBurstSeparationJob = new EnemySeparationJob
|
||||||
MaxRadius = maxRadius,
|
{
|
||||||
PlayerPosition = playerPosition,
|
Inputs = inputArray,
|
||||||
PushDamping = pushDamping,
|
Buckets = _enemySeparationBuckets,
|
||||||
MaxStepScale = maxStepScale,
|
PreviousPushes = previousPushes,
|
||||||
UseTangentialInAttackRange = useTangentialInAttackRange,
|
Outputs = separatedOutputArray,
|
||||||
PushSmoothing = pushSmoothing
|
CurrentPushes = currentPushes,
|
||||||
};
|
CellSize = cellSize,
|
||||||
separationHandle = separationJob.Schedule(enemyCount, 64, buildHandle);
|
MaxRadius = maxRadius,
|
||||||
|
PlayerPosition = playerPosition,
|
||||||
|
PushDamping = pushDamping,
|
||||||
|
MaxStepScale = maxStepScale,
|
||||||
|
UseTangentialInAttackRange = useTangentialInAttackRange,
|
||||||
|
PushSmoothing = pushSmoothing
|
||||||
|
};
|
||||||
|
separationHandle = nonBurstSeparationJob.Schedule(enemyCount, 64, nonBurstBuildHandle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CommitEnemySeparationForJobOutput(int enemyCount)
|
||||||
|
{
|
||||||
|
if (enemyCount <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
separationHandle.Complete();
|
|
||||||
CommitEnemySeparationTemporalBuffers(enemyCount);
|
CommitEnemySeparationTemporalBuffers(enemyCount);
|
||||||
for (int i = 0; i < enemyCount; i++)
|
for (int i = 0; i < enemyCount; i++)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -148,20 +148,18 @@ namespace Simulation
|
||||||
return expectedCount;
|
return expectedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteProjectileMovementJob(in SimulationTickContext context)
|
private JobHandle ExecuteProjectileMovementJob(in SimulationTickContext context)
|
||||||
{
|
{
|
||||||
int projectileCount = _projectileJobInputs.Length;
|
int projectileCount = _projectileJobInputs.Length;
|
||||||
PrepareProjectileJobOutputBuffer(projectileCount);
|
|
||||||
|
|
||||||
if (projectileCount == 0)
|
if (projectileCount == 0)
|
||||||
{
|
{
|
||||||
return;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.DeltaTime <= 0f)
|
if (context.DeltaTime <= 0f)
|
||||||
{
|
{
|
||||||
CopyProjectileInputToOutput();
|
CopyProjectileInputToOutput();
|
||||||
return;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
float maxDistance = Mathf.Max(0f, _projectileMaxDistanceFromPlayer);
|
float maxDistance = Mathf.Max(0f, _projectileMaxDistanceFromPlayer);
|
||||||
|
|
@ -172,7 +170,6 @@ namespace Simulation
|
||||||
NativeArray<ProjectileJobInputData> inputArray = _projectileJobInputs.AsArray();
|
NativeArray<ProjectileJobInputData> inputArray = _projectileJobInputs.AsArray();
|
||||||
NativeArray<ProjectileJobOutputData> outputArray = _projectileJobOutputs.AsArray();
|
NativeArray<ProjectileJobOutputData> outputArray = _projectileJobOutputs.AsArray();
|
||||||
|
|
||||||
JobHandle handle;
|
|
||||||
if (_useBurstJobs)
|
if (_useBurstJobs)
|
||||||
{
|
{
|
||||||
ProjectileMovementBurstJob burstJob = new ProjectileMovementBurstJob
|
ProjectileMovementBurstJob burstJob = new ProjectileMovementBurstJob
|
||||||
|
|
@ -184,23 +181,19 @@ namespace Simulation
|
||||||
MaxSqrDistanceFromPlayer = maxSqrDistanceFromPlayer,
|
MaxSqrDistanceFromPlayer = maxSqrDistanceFromPlayer,
|
||||||
MaxVerticalOffsetFromPlayer = maxVerticalOffsetFromPlayer
|
MaxVerticalOffsetFromPlayer = maxVerticalOffsetFromPlayer
|
||||||
};
|
};
|
||||||
handle = burstJob.Schedule(projectileCount, 64);
|
return burstJob.Schedule(projectileCount, 64);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ProjectileMovementJob job = new ProjectileMovementJob
|
|
||||||
{
|
|
||||||
Inputs = inputArray,
|
|
||||||
Outputs = outputArray,
|
|
||||||
DeltaTime = context.DeltaTime,
|
|
||||||
PlayerPosition = playerPosition,
|
|
||||||
MaxSqrDistanceFromPlayer = maxSqrDistanceFromPlayer,
|
|
||||||
MaxVerticalOffsetFromPlayer = maxVerticalOffsetFromPlayer
|
|
||||||
};
|
|
||||||
handle = job.Schedule(projectileCount, 64);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handle.Complete();
|
ProjectileMovementJob job = new ProjectileMovementJob
|
||||||
|
{
|
||||||
|
Inputs = inputArray,
|
||||||
|
Outputs = outputArray,
|
||||||
|
DeltaTime = context.DeltaTime,
|
||||||
|
PlayerPosition = playerPosition,
|
||||||
|
MaxSqrDistanceFromPlayer = maxSqrDistanceFromPlayer,
|
||||||
|
MaxVerticalOffsetFromPlayer = maxVerticalOffsetFromPlayer
|
||||||
|
};
|
||||||
|
return job.Schedule(projectileCount, 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildProjectileCollisionCandidates()
|
private void BuildProjectileCollisionCandidates()
|
||||||
|
|
|
||||||
|
|
@ -535,6 +535,20 @@ namespace Simulation.Tests.Editor
|
||||||
Assert.That(GetCollisionCandidateCount(), Is.GreaterThan(0));
|
Assert.That(GetCollisionCandidateCount(), Is.GreaterThan(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TickProjectiles_BuildsCollisionCandidates_WithLatestEnemyMovement_WhenJobSimulationEnabled()
|
||||||
|
{
|
||||||
|
SetUseJobSimulationMethod.Invoke(_worldComponent, new object[] { true });
|
||||||
|
UpsertEnemy(CreateEnemy(entityId: 5211, position: new Vector3(2f, 0f, 0f), speed: 1f, attackRange: 0.1f));
|
||||||
|
UpsertProjectile(CreateProjectile(entityId: 5212, position: new Vector3(1f, 0f, 0f),
|
||||||
|
forward: Vector3.forward, velocity: Vector3.zero, speed: 0f, lifeTime: 2f, age: 0f, active: true,
|
||||||
|
remainingLifetime: 2f, state: 0));
|
||||||
|
|
||||||
|
InvokeTick(deltaTime: 1f, realDeltaTime: 1f, playerPosition: Vector3.zero);
|
||||||
|
|
||||||
|
Assert.That(GetCollisionCandidateCount(), Is.GreaterThan(0));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TickProjectiles_ExpiresAfterCollisionCandidateConsumed_WhenJobSimulationEnabled()
|
public void TickProjectiles_ExpiresAfterCollisionCandidateConsumed_WhenJobSimulationEnabled()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -565,6 +565,21 @@ namespace Simulation.Tests.PlayMode
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[UnityTest]
|
||||||
|
public IEnumerator TickProjectiles_BuildsCollisionCandidates_WithLatestEnemyMovement_WhenJobSimulationEnabled()
|
||||||
|
{
|
||||||
|
SetUseJobSimulationMethod.Invoke(_worldComponent, new object[] { true });
|
||||||
|
UpsertEnemy(CreateEnemy(entityId: 5511, position: new Vector3(2f, 0f, 0f), speed: 1f, attackRange: 0.1f));
|
||||||
|
UpsertProjectile(CreateProjectile(entityId: 5512, position: new Vector3(1f, 0f, 0f),
|
||||||
|
forward: Vector3.forward, velocity: Vector3.zero, speed: 0f, lifeTime: 2f, age: 0f, active: true,
|
||||||
|
remainingLifetime: 2f, state: 0));
|
||||||
|
|
||||||
|
InvokeTick(deltaTime: 1f, realDeltaTime: 1f, playerPosition: Vector3.zero);
|
||||||
|
|
||||||
|
Assert.That(GetCollisionCandidateCount(), Is.GreaterThan(0));
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
public IEnumerator TickProjectiles_ExpiresAfterCollisionCandidateConsumed_WhenJobSimulationEnabled()
|
public IEnumerator TickProjectiles_ExpiresAfterCollisionCandidateConsumed_WhenJobSimulationEnabled()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -156,13 +156,13 @@
|
||||||
- 建立命中事件缓冲区,统一在主线程提交表现层事件。
|
- 建立命中事件缓冲区,统一在主线程提交表现层事件。
|
||||||
- 完成标准:命中结果与现有逻辑一致,候选数量与耗时显著下降。
|
- 完成标准:命中结果与现有逻辑一致,候选数量与耗时显著下降。
|
||||||
|
|
||||||
- [ ] Checkpoint 7:Burst 策略落地与热路径约束
|
- [x] Checkpoint 7:Burst 策略落地与热路径约束
|
||||||
- 热路径 Job 全部添加 `[BurstCompile]`,并在 Burst Inspector 确认已生效。
|
- 热路径 Job 全部添加 `[BurstCompile]`,并在 Burst Inspector 确认已生效。
|
||||||
- 清理 Job 内不兼容写法:托管分配、虚调用、LINQ、异常路径热调用。
|
- 清理 Job 内不兼容写法:托管分配、虚调用、LINQ、异常路径热调用。
|
||||||
- 数学计算统一迁移到 `Unity.Mathematics`。
|
- 数学计算统一迁移到 `Unity.Mathematics`。
|
||||||
- 完成标准:核心 Job 均由 Burst 编译,且无安全检查错误/降级回 Mono 的关键路径。
|
- 完成标准:核心 Job 均由 Burst 编译,且无安全检查错误/降级回 Mono 的关键路径。
|
||||||
|
|
||||||
- [ ] Checkpoint 8:主线程职责收口与调度稳定
|
- [x] Checkpoint 8:主线程职责收口与调度稳定
|
||||||
- 明确主线程只做:输入采样、状态切换、UI 同步、实体显隐、最终写回。
|
- 明确主线程只做:输入采样、状态切换、UI 同步、实体显隐、最终写回。
|
||||||
- 统一 `Schedule -> Dependency Combine -> Complete` 位置,防止隐式同步抖动。
|
- 统一 `Schedule -> Dependency Combine -> Complete` 位置,防止隐式同步抖动。
|
||||||
- 清理战斗帧中不必要的主线程循环(尤其逐实体逻辑)。
|
- 清理战斗帧中不必要的主线程循环(尤其逐实体逻辑)。
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue