- Checkpoint 1:清理 TickEnemies 侧 GC

- Checkpoint 2:解耦 Simulation 核心与 `Transform` 运行时依赖
- Checkpoint 3:收口 `EntitySync` 职责边界
- Checkpoint 4:拆分 Simulation Tick 阶段
- Checkpoint 5:补最小回归测试
- Checkpoint 6:补充 P1.5 结项文档
This commit is contained in:
SepComet
2026-02-21 13:39:14 +08:00
parent 200277a703
commit d55ead69a0
28 changed files with 1390 additions and 257 deletions
@@ -1,4 +1,3 @@
using System.Collections.Generic;
using UnityEngine;
@@ -6,70 +5,159 @@ namespace CustomUtility
{
public static class EnemySeparationSolverProvider
{
private struct Registration
private enum SolverType
{
GridBucket,
Naive
}
private struct LegacyRegistration
{
public int AgentId;
public float BodyRadius;
}
private static IEnemySeparationSolver _current = new GridBucketEnemySeparationSolver();
private static readonly Dictionary<Transform, Registration> Registrations = new();
private static SolverType _solverType = SolverType.GridBucket;
private static float _gridCellSize = 1f;
private static IEnemySeparationSolver _legacySolver = CreateSolver();
private static IEnemySeparationSolver _simulationSolver = CreateSolver();
public static IEnemySeparationSolver Current => _current;
public static string CurrentSolverName => _current.GetType().Name;
private static readonly Dictionary<Transform, LegacyRegistration> LegacyRegistrations = new();
private static readonly List<EnemySeparationAgent> LegacyAgents = new();
private static readonly List<Transform> LegacyRecycle = new();
private static int _legacySnapshotFrame = -1;
private static int _nextLegacyAgentId = 1;
public static IEnemySeparationSolver Current => _simulationSolver;
public static string CurrentSolverName => _simulationSolver.GetType().Name;
public static void SetSolver(IEnemySeparationSolver solver)
{
if (solver == null) return;
_current = solver;
ReRegisterAll();
_legacySolver = solver;
_simulationSolver = solver;
_legacySnapshotFrame = -1;
}
public static void UseGridBucketSolver(float cellSize = 1f)
{
SetSolver(new GridBucketEnemySeparationSolver(cellSize));
_solverType = SolverType.GridBucket;
_gridCellSize = Mathf.Max(0.1f, cellSize);
RecreateSolvers();
}
public static void UseNaiveSolver()
{
SetSolver(new NaiveEnemySeparationSolver());
_solverType = SolverType.Naive;
RecreateSolvers();
}
public static void Register(Transform transform, float bodyRadius)
{
if (transform == null) return;
var registration = new Registration
if (LegacyRegistrations.TryGetValue(transform, out LegacyRegistration registration))
{
BodyRadius = bodyRadius
};
Registrations[transform] = registration;
_current.Register(transform, bodyRadius);
registration.BodyRadius = bodyRadius;
LegacyRegistrations[transform] = registration;
}
else
{
LegacyRegistrations.Add(transform, new LegacyRegistration
{
AgentId = _nextLegacyAgentId++,
BodyRadius = bodyRadius
});
}
_legacySnapshotFrame = -1;
}
public static void Unregister(Transform transform)
{
if (transform == null) return;
if (!LegacyRegistrations.Remove(transform)) return;
_current.Unregister(transform);
Registrations.Remove(transform);
_legacySnapshotFrame = -1;
}
public static Vector3 Resolve(Transform transform, Vector3 desiredPosition, Vector3 fallbackDirection,
int iterations)
{
return _current.Resolve(transform, desiredPosition, fallbackDirection, iterations);
if (transform == null) return desiredPosition;
if (!LegacyRegistrations.TryGetValue(transform, out LegacyRegistration registration))
{
return desiredPosition;
}
EnsureLegacySnapshot();
return _legacySolver.Resolve(registration.AgentId, desiredPosition, fallbackDirection, iterations);
}
private static void ReRegisterAll()
public static void SetSimulationAgents(IReadOnlyList<EnemySeparationAgent> agents)
{
foreach (var pair in Registrations)
_simulationSolver.SetAgents(agents);
}
public static Vector3 ResolveSimulation(int agentId, Vector3 desiredPosition, Vector3 fallbackDirection,
int iterations)
{
return _simulationSolver.Resolve(agentId, desiredPosition, fallbackDirection, iterations);
}
private static void EnsureLegacySnapshot()
{
int frame = Time.frameCount;
if (_legacySnapshotFrame == frame) return;
_legacySnapshotFrame = frame;
LegacyAgents.Clear();
LegacyRecycle.Clear();
foreach (var pair in LegacyRegistrations)
{
Transform transform = pair.Key;
Registration registration = pair.Value;
if (transform == null) continue;
if (transform == null)
{
LegacyRecycle.Add(pair.Key);
continue;
}
_current.Register(transform, registration.BodyRadius);
Vector3 position = transform.position;
position.y = 0f;
LegacyAgents.Add(new EnemySeparationAgent
{
AgentId = pair.Value.AgentId,
Position = position,
Radius = Mathf.Max(0.01f, pair.Value.BodyRadius)
});
}
for (int i = 0; i < LegacyRecycle.Count; i++)
{
LegacyRegistrations.Remove(LegacyRecycle[i]);
}
_legacySolver.SetAgents(LegacyAgents);
}
}
private static void RecreateSolvers()
{
_legacySolver = CreateSolver();
_simulationSolver = CreateSolver();
_legacySnapshotFrame = -1;
}
private static IEnemySeparationSolver CreateSolver()
{
if (_solverType == SolverType.Naive)
{
return new NaiveEnemySeparationSolver();
}
return new GridBucketEnemySeparationSolver(_gridCellSize);
}
}
}
@@ -4,24 +4,20 @@ namespace CustomUtility
{
public sealed class GridBucketEnemySeparationSolver : IEnemySeparationSolver
{
private sealed class Agent
private struct Agent
{
public Transform Transform;
public float Radius;
public Vector3 Position;
public int CellX;
public int CellZ;
}
private readonly System.Collections.Generic.Dictionary<Transform, Agent> _agents = new();
private readonly System.Collections.Generic.Dictionary<long, System.Collections.Generic.List<Transform>>
_buckets = new();
private readonly System.Collections.Generic.List<Transform> _recycle = new();
private readonly System.Collections.Generic.Dictionary<int, Agent> _agents = new();
private readonly System.Collections.Generic.Dictionary<long, System.Collections.Generic.List<int>> _buckets = new();
private readonly System.Collections.Generic.Stack<System.Collections.Generic.List<int>> _bucketListPool = new();
private readonly System.Collections.Generic.List<long> _activeBucketKeys = new();
private readonly float _cellSize;
private int _snapshotFrame = -1;
private float _maxRadius = 0.45f;
public GridBucketEnemySeparationSolver(float cellSize = 1f)
@@ -29,44 +25,42 @@ namespace CustomUtility
_cellSize = Mathf.Max(0.1f, cellSize);
}
public void Register(Transform transform, float bodyRadius)
public void SetAgents(System.Collections.Generic.IReadOnlyList<EnemySeparationAgent> agents)
{
if (transform == null) return;
RecycleBucketsForSnapshot();
_agents.Clear();
_maxRadius = 0.01f;
if (!_agents.TryGetValue(transform, out var agent))
if (agents == null) return;
for (int i = 0; i < agents.Count; i++)
{
agent = new Agent();
_agents.Add(transform, agent);
}
EnemySeparationAgent input = agents[i];
Vector3 position = input.Position;
position.y = 0f;
float radius = Mathf.Max(0.01f, input.Radius);
agent.Transform = transform;
agent.Radius = Mathf.Max(0.01f, bodyRadius);
if (agent.Radius > _maxRadius)
{
_maxRadius = agent.Radius;
}
Agent agent = new Agent
{
Radius = radius,
Position = position,
CellX = ToCell(position.x),
CellZ = ToCell(position.z)
};
_snapshotFrame = -1;
_agents[input.AgentId] = agent;
AddToBucket(input.AgentId, agent.CellX, agent.CellZ);
if (radius > _maxRadius)
{
_maxRadius = radius;
}
}
}
public void Unregister(Transform transform)
public Vector3 Resolve(int agentId, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations)
{
if (transform == null) return;
if (!_agents.TryGetValue(transform, out var agent)) return;
RemoveFromBucket(transform, agent.CellX, agent.CellZ);
_agents.Remove(transform);
RecalculateMaxRadius();
_snapshotFrame = -1;
}
public Vector3 Resolve(Transform transform, Vector3 desiredPosition, Vector3 fallbackDirection,
int iterations)
{
if (transform == null) return desiredPosition;
if (!_agents.TryGetValue(transform, out var self)) return desiredPosition;
EnsureSnapshot();
if (!_agents.TryGetValue(agentId, out var self)) return desiredPosition;
Vector3 candidate = desiredPosition;
candidate.y = 0f;
@@ -89,9 +83,9 @@ namespace CustomUtility
for (int i = 0; i < bucket.Count; i++)
{
Transform otherTransform = bucket[i];
if (otherTransform == transform) continue;
if (!_agents.TryGetValue(otherTransform, out var other)) continue;
int otherAgentId = bucket[i];
if (otherAgentId == agentId) continue;
if (!_agents.TryGetValue(otherAgentId, out var other)) continue;
Vector3 toSelf = candidate - other.Position;
float minDistance = self.Radius + other.Radius;
@@ -114,98 +108,65 @@ namespace CustomUtility
}
}
SyncAgentPosition(transform, self, candidate);
SyncAgentPosition(agentId, ref self, candidate);
candidate.y = desiredPosition.y;
return candidate;
}
private void EnsureSnapshot()
private void RecycleBucketsForSnapshot()
{
int frame = Time.frameCount;
if (_snapshotFrame == frame) return;
_snapshotFrame = frame;
_buckets.Clear();
_recycle.Clear();
foreach (var pair in _agents)
for (int i = 0; i < _activeBucketKeys.Count; i++)
{
Transform transform = pair.Key;
Agent agent = pair.Value;
if (transform == null || agent.Transform == null)
{
_recycle.Add(transform);
continue;
}
long key = _activeBucketKeys[i];
if (!_buckets.TryGetValue(key, out var bucket)) continue;
Vector3 position = agent.Transform.position;
position.y = 0f;
agent.Position = position;
agent.CellX = ToCell(position.x);
agent.CellZ = ToCell(position.z);
AddToBucket(transform, agent.CellX, agent.CellZ);
bucket.Clear();
_bucketListPool.Push(bucket);
_buckets.Remove(key);
}
for (int i = 0; i < _recycle.Count; i++)
{
_agents.Remove(_recycle[i]);
}
_activeBucketKeys.Clear();
}
private void SyncAgentPosition(Transform transform, Agent agent, Vector3 position)
private void SyncAgentPosition(int agentId, ref Agent agent, Vector3 position)
{
int newCellX = ToCell(position.x);
int newCellZ = ToCell(position.z);
if (agent.CellX != newCellX || agent.CellZ != newCellZ)
{
RemoveFromBucket(transform, agent.CellX, agent.CellZ);
AddToBucket(transform, newCellX, newCellZ);
RemoveFromBucket(agentId, agent.CellX, agent.CellZ);
AddToBucket(agentId, newCellX, newCellZ);
agent.CellX = newCellX;
agent.CellZ = newCellZ;
}
agent.Position = position;
_agents[agentId] = agent;
}
private void AddToBucket(Transform transform, int cellX, int cellZ)
private void AddToBucket(int agentId, int cellX, int cellZ)
{
long key = CellKey(cellX, cellZ);
if (!_buckets.TryGetValue(key, out var list))
{
list = new System.Collections.Generic.List<Transform>(8);
list = _bucketListPool.Count > 0
? _bucketListPool.Pop()
: new System.Collections.Generic.List<int>(8);
_buckets.Add(key, list);
_activeBucketKeys.Add(key);
}
list.Add(transform);
list.Add(agentId);
}
private void RemoveFromBucket(Transform transform, int cellX, int cellZ)
private void RemoveFromBucket(int agentId, int cellX, int cellZ)
{
long key = CellKey(cellX, cellZ);
if (!_buckets.TryGetValue(key, out var list)) return;
list.Remove(transform);
if (list.Count == 0)
{
_buckets.Remove(key);
}
}
private void RecalculateMaxRadius()
{
float max = 0.01f;
foreach (var pair in _agents)
{
if (pair.Value.Radius > max)
{
max = pair.Value.Radius;
}
}
_maxRadius = max;
list.Remove(agentId);
}
private int ToCell(float value)
@@ -218,5 +179,4 @@ namespace CustomUtility
return ((long)x << 32) ^ (uint)z;
}
}
}
@@ -2,10 +2,16 @@ using UnityEngine;
namespace CustomUtility
{
public struct EnemySeparationAgent
{
public int AgentId;
public Vector3 Position;
public float Radius;
}
public interface IEnemySeparationSolver
{
void Register(Transform transform, float bodyRadius);
void Unregister(Transform transform);
Vector3 Resolve(Transform transform, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations);
void SetAgents(System.Collections.Generic.IReadOnlyList<EnemySeparationAgent> agents);
Vector3 Resolve(int agentId, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations);
}
}
@@ -4,39 +4,41 @@ namespace CustomUtility
{
public sealed class NaiveEnemySeparationSolver : IEnemySeparationSolver
{
private sealed class Agent
private struct Agent
{
public Transform Transform;
public float Radius;
public Vector3 Position;
}
private readonly System.Collections.Generic.Dictionary<Transform, Agent> _agents = new();
private readonly System.Collections.Generic.List<Transform> _agentKeys = new();
private readonly System.Collections.Generic.Dictionary<int, Agent> _agents = new();
private readonly System.Collections.Generic.List<int> _agentKeys = new();
public void Register(Transform transform, float bodyRadius)
public void SetAgents(System.Collections.Generic.IReadOnlyList<EnemySeparationAgent> agents)
{
if (transform == null) return;
_agents.Clear();
_agentKeys.Clear();
if (agents == null) return;
if (!_agents.TryGetValue(transform, out var agent))
for (int i = 0; i < agents.Count; i++)
{
agent = new Agent();
_agents.Add(transform, agent);
EnemySeparationAgent input = agents[i];
Vector3 position = input.Position;
position.y = 0f;
Agent agent = new Agent
{
Radius = Mathf.Max(0.01f, input.Radius),
Position = position
};
_agents[input.AgentId] = agent;
_agentKeys.Add(input.AgentId);
}
agent.Transform = transform;
agent.Radius = Mathf.Max(0.01f, bodyRadius);
}
public void Unregister(Transform transform)
public Vector3 Resolve(int agentId, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations)
{
if (transform == null) return;
_agents.Remove(transform);
}
public Vector3 Resolve(Transform transform, Vector3 desiredPosition, Vector3 fallbackDirection, int iterations)
{
if (transform == null) return desiredPosition;
if (!_agents.TryGetValue(transform, out var self)) return desiredPosition;
if (!_agents.TryGetValue(agentId, out var self)) return desiredPosition;
Vector3 candidate = desiredPosition;
candidate.y = 0f;
@@ -44,26 +46,16 @@ namespace CustomUtility
Vector3 fallback = fallbackDirection.sqrMagnitude > 0.0001f ? fallbackDirection.normalized : Vector3.right;
fallback.y = 0f;
_agentKeys.Clear();
foreach (var pair in _agents)
{
_agentKeys.Add(pair.Key);
}
int effectiveIterations = Mathf.Max(1, iterations);
for (int iter = 0; iter < effectiveIterations; iter++)
{
for (int i = 0; i < _agentKeys.Count; i++)
{
Transform otherTransform = _agentKeys[i];
if (otherTransform == transform) continue;
if (!_agents.TryGetValue(otherTransform, out var other)) continue;
if (other.Transform == null) continue;
int otherAgentId = _agentKeys[i];
if (otherAgentId == agentId) continue;
if (!_agents.TryGetValue(otherAgentId, out var other)) continue;
Vector3 otherPosition = other.Transform.position;
otherPosition.y = 0f;
Vector3 toSelf = candidate - otherPosition;
Vector3 toSelf = candidate - other.Position;
float minDistance = self.Radius + other.Radius;
float minDistanceSq = minDistance * minDistance;
float sqrDistance = toSelf.sqrMagnitude;