diff --git a/src/CounterDrone.Core/Simulation/SimulationEngine.cs b/src/CounterDrone.Core/Simulation/SimulationEngine.cs index d856830..fbc922c 100644 --- a/src/CounterDrone.Core/Simulation/SimulationEngine.cs +++ b/src/CounterDrone.Core/Simulation/SimulationEngine.cs @@ -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 _snapshotPool = new(); + private readonly List _frameEventPool = new(); + private readonly SimulationFrameResult _frameResultPool = new(); + private readonly List _removalIndices = new(); + private readonly List _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(); + _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 CollectSnapshots() { - var list = new List(); - 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; } } } diff --git a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll index 18bbd8e..2c70c3d 100644 Binary files a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll and b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll differ diff --git a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.pdb b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.pdb index 436e525..6c179ab 100644 Binary files a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.pdb and b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.pdb differ