perf: SimulationEngine GC优化

CollectSnapshots: 字符串拼接替代 JsonSerializer.Serialize (最大GC源)

Tick: 复用池替代每帧 new List/SimulationFrameResult

ToList/Where→手动for循环+移除索引, 避免LINQ分配

移除 System.Text.Json 依赖
This commit is contained in:
tian 2026-06-15 18:19:56 +08:00
parent c379e849f0
commit cc1a0628d8
3 changed files with 96 additions and 56 deletions

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using CounterDrone.Core.Algorithms;
using CounterDrone.Core.Models;
using CounterDrone.Core.Services;
@ -52,6 +51,13 @@ namespace CounterDrone.Core.Simulation
private readonly IDefensePlanner _planner;
private int _entityCounter;
// ── GC 优化:预分配复用池 ──
private readonly List<EntitySnapshot> _snapshotPool = new();
private readonly List<SimEvent> _frameEventPool = new();
private readonly SimulationFrameResult _frameResultPool = new();
private readonly List<int> _removalIndices = new();
private readonly List<DroneEntity> _flyingDrones = new();
public SimulationEngine(IScenarioService scenarioService, FrameDataStore frameStore,
IDamageModel damageModel, IPathProvider paths, IDefensePlanner planner)
{
@ -157,12 +163,19 @@ namespace CounterDrone.Core.Simulation
public SimulationFrameResult Tick(float deltaTime)
{
if (State != SimulationState.Running)
return new SimulationFrameResult { State = State, FrameIndex = FrameIndex, SimulationTime = SimulationTime };
{
_frameResultPool.State = State;
_frameResultPool.FrameIndex = FrameIndex;
_frameResultPool.SimulationTime = SimulationTime;
_frameResultPool.EntitySnapshots = null!;
_frameResultPool.NewEvents = null!;
return _frameResultPool;
}
var scaledDt = deltaTime * TimeScale;
SimulationTime += scaledDt;
FrameIndex++;
var frameEvents = new List<SimEvent>();
_frameEventPool.Clear();
// 1. 按发射计划执行
while (_nextFireIndex < _fireSchedule.Count && _fireSchedule[_nextFireIndex].FireTime <= SimulationTime)
@ -177,7 +190,6 @@ namespace CounterDrone.Core.Simulation
}
else if (platform.Ready)
{
// 地基:直接发射
platform.Release();
var m = new MunitionEntity($"mun_{++_entityCounter}",
platform.PlatformType ?? Models.PlatformType.GroundBased,
@ -187,13 +199,15 @@ namespace CounterDrone.Core.Simulation
fe.FireTime);
_munitions.Add(m);
OnMunitionLaunched?.Invoke(m);
frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = fe.FireTime, SourceId = platform.Id, Description = $"计划发射 {m.Id} @({platform.PosX:F0},{platform.PosY:F0},{platform.PosZ:F0})→({fe.TargetX:F0},{fe.TargetY:F0},{fe.TargetZ:F0})" });
_frameEventPool.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = fe.FireTime, SourceId = platform.Id, Description = $"计划发射 {m.Id} @({platform.PosX:F0},{platform.PosY:F0},{platform.PosZ:F0})→({fe.TargetX:F0},{fe.TargetY:F0},{fe.TargetZ:F0})" });
}
}
// 1b. 空基平台到达投放点 → 投弹
foreach (var platform in _platforms.Where(p => p.CanRelease))
// 1b. 空基平台到达投放点 → 投弹(手动遍历避免 LINQ
for (int i = 0; i < _platforms.Count; i++)
{
var platform = _platforms[i];
if (!platform.CanRelease) continue;
var (cvx, cvy, cvz) = platform.CurrentVelocity;
platform.Release();
var m = new MunitionEntity($"mun_{++_entityCounter}",
@ -206,12 +220,14 @@ namespace CounterDrone.Core.Simulation
cvx, cvy, cvz);
_munitions.Add(m);
OnMunitionLaunched?.Invoke(m);
frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = platform.ExactReleaseTime, SourceId = platform.Id, Description = $"空基投放 {m.Id}" });
_frameEventPool.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = platform.ExactReleaseTime, SourceId = platform.Id, Description = $"空基投放 {m.Id}" });
}
// 2. 弹药飞行 → 云团生成
foreach (var m in _munitions.ToList())
// 2. 弹药飞行 → 云团生成(手动遍历+移除索引,避免 ToList()
_removalIndices.Clear();
for (int i = 0; i < _munitions.Count; i++)
{
var m = _munitions[i];
m.Update(scaledDt);
if (m.HasArrived)
{
@ -219,49 +235,54 @@ namespace CounterDrone.Core.Simulation
disp.Initialize(_ammoSpec, _scene, new Algorithms.Vector3(m.PosX, m.PosY, m.PosZ), m.ArrivalTime);
var cloud = new CloudEntity($"cloud_{++_entityCounter}", m.AerosolType, disp, m.ArrivalTime);
_clouds.Add(cloud);
_munitions.Remove(m);
OnCloudGenerated?.Invoke(cloud);
frameEvents.Add(new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = m.ArrivalTime, Description = $"云团生成 @({m.PosX:F0},{m.PosY:F0},{m.PosZ:F0})" });
_frameEventPool.Add(new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = m.ArrivalTime, Description = $"云团生成 @({m.PosX:F0},{m.PosY:F0},{m.PosZ:F0})" });
_removalIndices.Add(i);
}
}
for (int i = _removalIndices.Count - 1; i >= 0; i--)
_munitions.RemoveAt(_removalIndices[i]);
// 3. 云团演化
foreach (var c in _clouds.ToList())
// 3. 云团演化(手动遍历+移除索引)
_removalIndices.Clear();
for (int i = 0; i < _clouds.Count; i++)
{
var c = _clouds[i];
c.Tick(scaledDt, (float)_scene.WindSpeed, (WindDirection)_scene.WindDirection);
if (c.IsDissipated) _clouds.Remove(c);
if (c.IsDissipated) _removalIndices.Add(i);
}
for (int i = _removalIndices.Count - 1; i >= 0; i--)
_clouds.RemoveAt(_removalIndices[i]);
// 4. 毁伤判定(路径积分替代点采样)
foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
{
// 保存上一帧位置用于积分
drone.SavePreviousPosition();
}
// 4-5. 无人机:缓存飞行状态列表,避免多次 Where
_flyingDrones.Clear();
for (int i = 0; i < _drones.Count; i++)
if (_drones[i].Status == DroneStatus.Flying)
_flyingDrones.Add(_drones[i]);
// 5. 无人机移动
foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
for (int i = 0; i < _flyingDrones.Count; i++)
_flyingDrones[i].SavePreviousPosition();
for (int i = 0; i < _flyingDrones.Count; i++)
{
drone.Update(scaledDt);
if (drone.Status == DroneStatus.ReachedTarget)
_flyingDrones[i].Update(scaledDt);
if (_flyingDrones[i].Status == DroneStatus.ReachedTarget)
{
OnDroneReachedTarget?.Invoke(drone);
frameEvents.Add(new SimEvent { Type = SimEventType.DroneReachedTarget, OccurredAt = SimulationTime, SourceId = drone.Id, Description = "到达攻击目标" });
OnDroneReachedTarget?.Invoke(_flyingDrones[i]);
_frameEventPool.Add(new SimEvent { Type = SimEventType.DroneReachedTarget, OccurredAt = SimulationTime, SourceId = _flyingDrones[i].Id, Description = "到达攻击目标" });
}
}
// 6. 毁伤判定:积分路径段在云内的时间
// 在云团参考系计算:云团中心在该参考系为原点。
// 云团本 tick 内也在移动(风偏),故 Prev 帧的云团中心 = 本帧中心 风位移。
// 不做此修正的话 PathInSphere 会把云团当静止球,每 tick 引入 ~2m 误差。
var (wvx, wvy, wvz) = Kinematics.WindToVector((WindDirection)_scene.WindDirection, (float)_scene.WindSpeed);
float cloudDx = wvx * scaledDt, cloudDy = wvy * scaledDt, cloudDz = wvz * scaledDt;
foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
for (int di = 0; di < _flyingDrones.Count; di++)
{
foreach (var cloud in _clouds)
var drone = _flyingDrones[di];
for (int ci = 0; ci < _clouds.Count; ci++)
{
var cloud = _clouds[ci];
float ccx = cloud.Dispersion.Center.X, ccy = cloud.Dispersion.Center.Y, ccz = cloud.Dispersion.Center.Z;
// 无人机线段换算到云团参考系:相对位置 = drone - cloud
float inCloudDistance = DamageAssessment.PathInSphere(
drone.PrevX - (ccx - cloudDx), drone.PrevY - (ccy - cloudDy), drone.PrevZ - (ccz - cloudDz),
drone.PosX - ccx, drone.PosY - ccy, drone.PosZ - ccz,
@ -276,45 +297,54 @@ namespace CounterDrone.Core.Simulation
if (drone.Status == DroneStatus.Destroyed)
{
OnDroneDestroyed?.Invoke(drone);
frameEvents.Add(new SimEvent { Type = SimEventType.DroneDestroyed, OccurredAt = SimulationTime, TargetId = drone.Id, Description = $"无人机 {drone.Id} 被摧毁 @({drone.PosX:F0},{drone.PosY:F0},{drone.PosZ:F0})" });
break; // 已击毁,不再判定本 tick 其他云团,避免重复触发
_frameEventPool.Add(new SimEvent { Type = SimEventType.DroneDestroyed, OccurredAt = SimulationTime, TargetId = drone.Id, Description = $"无人机 {drone.Id} 被摧毁 @({drone.PosX:F0},{drone.PosY:F0},{drone.PosZ:F0})" });
break;
}
}
}
}
// 6. 平台冷却
foreach (var p in _platforms) p.Update(scaledDt);
// 7. 平台冷却
for (int i = 0; i < _platforms.Count; i++) _platforms[i].Update(scaledDt);
// 7. 管控区域
foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
// 8. 管控区域
for (int di = 0; di < _flyingDrones.Count; di++)
{
foreach (var zone in _zones)
var drone = _flyingDrones[di];
for (int zi = 0; zi < _zones.Count; zi++)
{
if (zone.ContainsPoint(drone.PosX, drone.PosY, drone.PosZ))
if (_zones[zi].ContainsPoint(drone.PosX, drone.PosY, drone.PosZ))
{
drone.MarkZoneIntruded();
OnZoneIntruded?.Invoke(drone, zone);
frameEvents.Add(new SimEvent { Type = SimEventType.ZoneIntruded, OccurredAt = SimulationTime, SourceId = drone.Id, TargetId = zone.Id, Description = $"侵入 {zone.Name}" });
OnZoneIntruded?.Invoke(drone, _zones[zi]);
_frameEventPool.Add(new SimEvent { Type = SimEventType.ZoneIntruded, OccurredAt = SimulationTime, SourceId = drone.Id, TargetId = _zones[zi].Id, Description = $"侵入 {_zones[zi].Name}" });
}
}
}
// 8. 结束判定
if (_drones.All(d => d.Status != DroneStatus.Flying))
// 9. 结束判定
bool allDone = true;
for (int i = 0; i < _drones.Count; i++)
if (_drones[i].Status == DroneStatus.Flying) { allDone = false; break; }
if (allDone)
{
State = SimulationState.Completed;
OnSimulationEnded?.Invoke();
frameEvents.Add(new SimEvent { Type = SimEventType.SimulationEnd, OccurredAt = SimulationTime });
_frameEventPool.Add(new SimEvent { Type = SimEventType.SimulationEnd, OccurredAt = SimulationTime });
}
_allEvents.AddRange(frameEvents);
_allEvents.AddRange(_frameEventPool);
var snapshots = CollectSnapshots();
_frameStore.WriteFrame(_frameDb, _taskId, FrameIndex, SimulationTime, snapshots);
if (FrameIndex % 50 == 0) _frameDb.Commit();
return new SimulationFrameResult { EntitySnapshots = snapshots, NewEvents = frameEvents, State = State, FrameIndex = FrameIndex, SimulationTime = SimulationTime };
_frameResultPool.EntitySnapshots = snapshots;
_frameResultPool.NewEvents = _frameEventPool;
_frameResultPool.State = State;
_frameResultPool.FrameIndex = FrameIndex;
_frameResultPool.SimulationTime = SimulationTime;
return _frameResultPool;
}
public void Pause() { if (State == SimulationState.Running) State = SimulationState.Paused; }
@ -402,14 +432,24 @@ namespace CounterDrone.Core.Simulation
private List<EntitySnapshot> CollectSnapshots()
{
var list = new List<EntitySnapshot>();
foreach (var d in _drones)
list.Add(new EntitySnapshot { EntityId = d.Id, EntityType = Models.EntityType.Drone, PosX = d.PosX, PosY = d.PosY, PosZ = d.PosZ, StateData = JsonSerializer.Serialize(new { damageStage = (int)_damageModel.GetDamageStage(1 - d.Hp), hp = d.Hp }) });
foreach (var p in _platforms.Where(p => p.PlatformType == Models.PlatformType.AirBased))
list.Add(new EntitySnapshot { EntityId = p.Id, EntityType = Models.EntityType.Platform, PosX = p.PosX, PosY = p.PosY, PosZ = p.PosZ, StateData = JsonSerializer.Serialize(new { state = p.State.ToString() }) });
foreach (var c in _clouds)
list.Add(new EntitySnapshot { EntityId = c.Id, EntityType = Models.EntityType.Cloud, PosX = c.Dispersion.Center.X, PosY = c.Dispersion.Center.Y, PosZ = c.Dispersion.Center.Z, StateData = JsonSerializer.Serialize(new { radius = c.Dispersion.Radius, opacity = c.Dispersion.Particles.Opacity, phase = c.Dispersion.Phase, elapsed = c.Dispersion.Elapsed }) });
return list;
_snapshotPool.Clear();
for (int i = 0; i < _drones.Count; i++)
{
var d = _drones[i];
_snapshotPool.Add(new EntitySnapshot { EntityId = d.Id, EntityType = Models.EntityType.Drone, PosX = d.PosX, PosY = d.PosY, PosZ = d.PosZ, StateData = "{\"damageStage\":" + (int)_damageModel.GetDamageStage(1 - d.Hp) + ",\"hp\":" + d.Hp.ToString("F2") + "}" });
}
for (int i = 0; i < _platforms.Count; i++)
{
var p = _platforms[i];
if (p.PlatformType != Models.PlatformType.AirBased) continue;
_snapshotPool.Add(new EntitySnapshot { EntityId = p.Id, EntityType = Models.EntityType.Platform, PosX = p.PosX, PosY = p.PosY, PosZ = p.PosZ, StateData = "{\"state\":\"" + p.State + "\"}" });
}
for (int i = 0; i < _clouds.Count; i++)
{
var c = _clouds[i];
_snapshotPool.Add(new EntitySnapshot { EntityId = c.Id, EntityType = Models.EntityType.Cloud, PosX = c.Dispersion.Center.X, PosY = c.Dispersion.Center.Y, PosZ = c.Dispersion.Center.Z, StateData = "{\"radius\":" + c.Dispersion.Radius.ToString("F1") + ",\"opacity\":" + c.Dispersion.Particles.Opacity.ToString("F2") + ",\"phase\":" + c.Dispersion.Phase + ",\"elapsed\":" + c.Dispersion.Elapsed.ToString("F1") + "}" });
}
return _snapshotPool;
}
}
}