using System;
using System.Collections.Generic;
using System.Linq;
using CounterDrone.Core.Algorithms;
using CounterDrone.Core.Models;
using CounterDrone.Core.Services;
using SQLite;
namespace CounterDrone.Core.Simulation
{
/// 仿真引擎 — 纯执行器,接收发射计划按时间表执行
public class SimulationEngine
{
private readonly IScenarioService _scenarioService;
private readonly FrameDataStore _frameStore;
private readonly IDamageModel _damageModel;
private readonly IPathProvider _paths;
public SimulationState State { get; private set; } = SimulationState.Idle;
public int FrameIndex { get; private set; }
public float SimulationTime { get; private set; }
public float TimeScale { get; set; } = 1f;
/// 是否录制逐帧数据(回放用)。默认开启。
public bool RecordFrames { get; set; } = true;
private float _tickInterval;
public IReadOnlyList Drones => _drones;
public IReadOnlyList Clouds => _clouds;
public IReadOnlyList Platforms => _platforms;
public IReadOnlyList Munitions => _munitions;
public IReadOnlyList DetectionEntities => _detectionEntities;
public IReadOnlyList Events => _allEvents;
public event Action? OnMunitionLaunched;
public event Action? OnCloudGenerated;
public event Action? OnDroneDestroyed;
public event Action? OnDroneReachedTarget;
public event Action? OnZoneIntruded;
public event Action? OnTargetDetected;
public event Action? OnSimulationEnded;
private List _drones = new();
private List _platforms = new();
private List _detectionEntities = new();
private List _munitions = new();
private List _clouds = new();
private List _zones = new();
private CombatScene _scene = new();
private float _disperseHeight;
private AmmunitionSpec _ammoSpec = new();
private int _tickRate = 20;
private readonly List _fireSchedule = new();
private int _nextFireIndex;
private bool _anyThreatDetected;
private float _detectionTime;
private readonly List _allEvents = new();
private string _scenarioId = string.Empty;
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)
{
_scenarioService = scenarioService;
_frameStore = frameStore;
_damageModel = damageModel;
_paths = paths;
_planner = planner;
}
public void SetFireSchedule(List schedule)
{
_fireSchedule.Clear();
_fireSchedule.AddRange(schedule.OrderBy(f => f.FireTime));
_nextFireIndex = 0;
}
public void Initialize(string scenarioId)
{
_scenarioId = scenarioId;
var config = _scenarioService.GetScenarioDetail(scenarioId);
if (config == null) throw new InvalidOperationException("scenario not found");
_scene = config.Scene;
_disperseHeight = (float)config.Cloud.DisperseHeight;
_tickRate = config.Info.TickRate;
_tickInterval = 1.0f / _tickRate;
using (var ammoDb = new SQLiteConnection(_paths.GetMainDbPath()))
{
_ammoSpec = ammoDb.Table()
.FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType);
}
if (_ammoSpec == null || _ammoSpec.Id == null)
_ammoSpec = DefaultData.Load(_paths).Ammunition
.First(a => a.AerosolType == config.Cloud.AerosolType);
_drones.Clear();
foreach (var target in config.Targets)
{
var route = config.Routes.FirstOrDefault(r => r.WaveId == target.WaveId);
var wps = config.WaypointGroups.GetValueOrDefault(target.WaveId, new List());
if (route == null || wps.Count == 0) continue;
var mode = (FormationMode)route.FormationMode;
var lateralSpacing = (float)route.LateralSpacing;
var longSpacing = (float)route.LongitudinalSpacing;
int latCount = route.LateralCount ?? target.Quantity;
int longCount = route.LongitudinalCount ?? 1;
for (int i = 0; i < target.Quantity; i++)
{
int latIdx = i % latCount;
int longIdx = i / latCount;
_drones.Add(new DroneEntity($"drone_{++_entityCounter}", target.WaveId,
target, wps, latIdx, lateralSpacing, longIdx, longSpacing, mode,
route.LateralAxis, route.LongitudinalAxis));
}
}
// 平台 ID 从 1 开始,确保与报告映射一致
_entityCounter = 0;
_platforms.Clear();
foreach (var equip in config.Equipment)
if (equip.EquipmentRole != (int)EquipmentRole.Detection)
for (int i = 0; i < equip.Quantity; i++)
{
int gunCount = equip.GunCount ?? 1;
int chPerGun = equip.ChannelsPerGun ?? 1;
for (int g = 0; g < gunCount; g++)
for (int ch = 0; ch < chPerGun; ch++)
_platforms.Add(new PlatformEntity($"plat_{++_entityCounter}", equip));
}
_zones.Clear();
foreach (var z in config.ControlZones)
_zones.Add(new ControlZoneEntity(z.Id, z.Name, z.VerticesJson,
(float)z.MinAltitude, (float)z.MaxAltitude));
// 探测设备运行时实体
_detectionEntities.Clear();
var detectionSrcs = BuildDetectionSources(config);
int detIdx = 0;
foreach (var src in detectionSrcs)
_detectionEntities.Add(new DetectionEntity($"det_{++detIdx}", src));
_munitions.Clear();
_clouds.Clear();
_allEvents.Clear();
FrameIndex = 0;
SimulationTime = 0;
_entityCounter = 0;
_nextFireIndex = 0;
// 自动从 Planner 生成发射计划
if (_fireSchedule.Count == 0)
{
var fireUnits = BuildFireUnits(config);
var threats = BuildDroneWaves(config);
if (fireUnits.Count > 0 && threats.Count > 0)
{
var detectionSources = BuildDetectionSources(config);
var result = _planner.Plan(fireUnits, threats, _scene, detectionSources);
SetFireSchedule(result.Best.MergedSchedule);
if (_fireSchedule.Count == 0)
_allEvents.Add(new SimEvent
{
Type = SimEventType.PlanningFailed,
OccurredAt = 0,
Description = result.Best.Summary,
});
}
}
if (RecordFrames)
_frameStore.BeginRecording(_scenarioId);
_anyThreatDetected = false;
State = SimulationState.Running;
}
public SimulationFrameResult Tick(float deltaTime)
{
if (State != SimulationState.Running)
{
_frameResultPool.State = State;
_frameResultPool.FrameIndex = FrameIndex;
_frameResultPool.SimulationTime = SimulationTime;
_frameResultPool.EntitySnapshots = null!;
_frameResultPool.NewEvents = null!;
return _frameResultPool;
}
var scaledDt = deltaTime * TimeScale;
SimulationTime += scaledDt;
FrameIndex++;
_frameEventPool.Clear();
// 1. 按发射计划执行(以探测时刻为时间起点)
if (_anyThreatDetected)
{
while (_nextFireIndex < _fireSchedule.Count && _fireSchedule[_nextFireIndex].FireTime + _detectionTime <= SimulationTime)
{
var fe = _fireSchedule[_nextFireIndex++];
float launchTime = fe.FireTime + _detectionTime;
var platform = _platforms.Count > fe.PlatformIndex ? _platforms[fe.PlatformIndex] : null;
if (platform == null) continue;
if (platform.PlatformType == Models.PlatformType.AirBased && platform.Ready)
{
// 空基从当前位置直接发射(与地基相同)
platform.Release();
var m = new MunitionEntity($"mun_{++_entityCounter}",
Models.PlatformType.GroundBased,
platform.AerosolType ?? Models.AerosolType.InertGas,
platform.PosX, platform.PosY, platform.PosZ,
fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight,
launchTime, fe.LaunchAngle, fe.FlightDuration);
_munitions.Add(m);
OnMunitionLaunched?.Invoke(m);
_frameEventPool.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = launchTime, SourceId = platform.Id, Description = $"空基发射 {m.Id} @({platform.PosX:F0},{platform.PosY:F0},{platform.PosZ:F0})→({fe.TargetX:F0},{fe.TargetY:F0},{fe.TargetZ:F0})" });
}
else if (platform.Ready)
{
platform.Release();
var m = new MunitionEntity($"mun_{++_entityCounter}",
platform.PlatformType ?? Models.PlatformType.GroundBased,
platform.AerosolType ?? Models.AerosolType.InertGas,
platform.PosX, platform.PosY, platform.PosZ,
fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight,
launchTime, fe.LaunchAngle, fe.FlightDuration);
_munitions.Add(m);
OnMunitionLaunched?.Invoke(m);
_frameEventPool.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = launchTime, SourceId = platform.Id, Description = $"计划发射 {m.Id} @({platform.PosX:F0},{platform.PosY:F0},{platform.PosZ:F0})→({fe.TargetX:F0},{fe.TargetY:F0},{fe.TargetZ:F0})" });
}
}
}
// 2. 弹药飞行 → 云团生成(手动遍历+移除索引,避免 ToList())
_removalIndices.Clear();
for (int i = 0; i < _munitions.Count; i++)
{
var m = _munitions[i];
m.Update(scaledDt);
if (m.HasArrived)
{
var disp = AlgorithmFactory.Create();
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);
OnCloudGenerated?.Invoke(cloud);
_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. 云团演化(手动遍历+移除索引)
_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) _removalIndices.Add(i);
}
for (int i = _removalIndices.Count - 1; i >= 0; i--)
_clouds.RemoveAt(_removalIndices[i]);
// 4. 无人机:缓存飞行状态列表,避免多次 Where
_flyingDrones.Clear();
for (int i = 0; i < _drones.Count; i++)
if (_drones[i].Status == DroneStatus.Flying)
_flyingDrones.Add(_drones[i]);
for (int i = 0; i < _flyingDrones.Count; i++)
_flyingDrones[i].SavePreviousPosition();
for (int i = 0; i < _flyingDrones.Count; i++)
{
_flyingDrones[i].Update(scaledDt);
if (_flyingDrones[i].Status == DroneStatus.ReachedTarget)
{
OnDroneReachedTarget?.Invoke(_flyingDrones[i]);
_frameEventPool.Add(new SimEvent { Type = SimEventType.DroneReachedTarget, OccurredAt = SimulationTime, SourceId = _flyingDrones[i].Id, Description = "到达攻击目标" });
}
}
// 5. 实时探测扫描(决策 1/2:离开回退、融合取最早/同刻取精度高)
if (_detectionEntities.Count > 0)
{
float visibility = (float)_scene.Visibility;
for (int di = 0; di < _flyingDrones.Count; di++)
{
var drone = _flyingDrones[di];
DetectionEntity? bestDetector = null;
float bestAccuracy = float.MaxValue;
for (int ei = 0; ei < _detectionEntities.Count; ei++)
{
var det = _detectionEntities[ei];
bool inRange = det.IsInRange(drone.PosX, drone.PosY, drone.PosZ, visibility, out _);
bool? stateChange = det.UpdateState(drone.Id, inRange);
if (stateChange == true)
{
// 决策 2:首次发现,同 tick 取精度最高(Accuracy 最小)
if (bestDetector == null || det.Source.Accuracy < bestAccuracy)
{
bestDetector = det;
bestAccuracy = det.Source.Accuracy;
}
}
}
if (bestDetector != null)
{
if (!_anyThreatDetected)
{
_anyThreatDetected = true;
_detectionTime = SimulationTime;
}
OnTargetDetected?.Invoke(drone, bestDetector);
_frameEventPool.Add(new SimEvent
{
Type = SimEventType.TargetDetected,
OccurredAt = SimulationTime,
SourceId = bestDetector.Id,
TargetId = drone.Id,
Description = $"探测设备 {bestDetector.Id} 发现 drone {drone.Id} @({drone.PosX:F0},{drone.PosY:F0},{drone.PosZ:F0})",
});
}
}
}
// 6. 毁伤判定:积分路径段在云内的时间(快速排斥:先检查包围盒)
var (wvx, wvy, wvz) = Kinematics.WindToVector((WindDirection)_scene.WindDirection, (float)_scene.WindSpeed);
float cloudDx = wvx * scaledDt, cloudDy = wvy * scaledDt, cloudDz = wvz * scaledDt;
float effectiveConcentration = (float)_ammoSpec.EffectiveConcentration;
for (int di = 0; di < _flyingDrones.Count; di++)
{
var drone = _flyingDrones[di];
for (int ci = 0; ci < _clouds.Count; ci++)
{
var cloud = _clouds[ci];
if (cloud.Dispersion.CoreDensity < effectiveConcentration) continue;
// 快速排斥:检查无人机与云团中心的距离是否在 2×有效半径内
float effR = cloud.Dispersion.EffectiveRadius;
float ccx = cloud.Dispersion.Center.X, ccy = cloud.Dispersion.Center.Y, ccz = cloud.Dispersion.Center.Z;
float dx = drone.PosX - ccx, dy = drone.PosY - ccy, dz = drone.PosZ - ccz;
float distSq = dx * dx + dy * dy + dz * dz;
if (distSq > effR * effR * 4) continue; // 距离 > 2R,不可能穿云
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,
0f, 0f, 0f, effR);
if (inCloudDistance > 0)
{
float exposureIncrement = inCloudDistance / (drone.CruiseSpeed / 3.6f);
drone.ExposureTime += exposureIncrement;
var dmg = _damageModel.CalculateDamage(drone.DroneType, drone.PowerType, cloud.AerosolType, cloud.Dispersion.CoreDensity, drone.ExposureTime, exposureIncrement);
drone.ApplyDamage(dmg);
if (drone.Status == DroneStatus.Destroyed)
{
OnDroneDestroyed?.Invoke(drone);
_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;
}
}
}
}
// 7. 平台冷却
for (int i = 0; i < _platforms.Count; i++) _platforms[i].Update(scaledDt);
// 8. 管控区域
for (int di = 0; di < _flyingDrones.Count; di++)
{
var drone = _flyingDrones[di];
for (int zi = 0; zi < _zones.Count; zi++)
{
if (_zones[zi].ContainsPoint(drone.PosX, drone.PosY, drone.PosZ))
{
drone.MarkZoneIntruded();
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}" });
}
}
}
// 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();
_frameEventPool.Add(new SimEvent { Type = SimEventType.SimulationEnd, OccurredAt = SimulationTime });
}
_allEvents.AddRange(_frameEventPool);
if (RecordFrames)
{
var snapshots = CollectSnapshots();
_frameStore.RecordFrame(FrameIndex, SimulationTime, snapshots);
_frameResultPool.EntitySnapshots = snapshots;
}
else
{
_frameResultPool.EntitySnapshots = null!;
}
_frameResultPool.NewEvents = _frameEventPool;
_frameResultPool.State = State;
_frameResultPool.FrameIndex = FrameIndex;
_frameResultPool.SimulationTime = SimulationTime;
return _frameResultPool;
}
public void Pause() { if (State == SimulationState.Running) State = SimulationState.Paused; }
public void Resume() { if (State == SimulationState.Paused) State = SimulationState.Running; }
public void Stop() { State = SimulationState.Stopped; _frameStore.Flush(); }
private static List BuildFireUnits(ScenarioConfig config)
{
var units = new List();
foreach (var eq in config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
{
int qty = Math.Max(1, eq.Quantity);
int gunCount = eq.GunCount ?? 1;
int chPerGun = eq.ChannelsPerGun ?? 1;
int channels = gunCount * chPerGun;
for (int i = 0; i < qty; i++)
{
units.Add(new FireUnit
{
Id = $"fu{units.Count}",
Type = (PlatformType)(eq.PlatformType ?? (int)PlatformType.GroundBased),
Position = new Algorithms.Vector3((float)eq.PositionX + i * 50, (float)eq.PositionY, (float)eq.PositionZ),
GunCount = gunCount,
ChannelsPerGun = chPerGun,
ChannelInterval = (float)(eq.ChannelInterval ?? 1),
CruiseSpeed = (float)(eq.CruiseSpeed ?? 0f),
ReleaseAltitude = (float)(eq.ReleaseAltitude ?? 0f),
MuzzleVelocity = (float)(eq.MuzzleVelocity ?? 0f),
TotalMunitions = eq.MunitionCount ?? channels,
AmmoTypes = new List { (AerosolType)(eq.AerosolType ?? 0) },
Cooldown = (float)eq.Cooldown,
// 探测能力(火力单元自带探测,激活原死字段)
RadarRange = (float)(eq.RadarRange ?? 0f),
EORange = (float)(eq.EORange ?? 0f),
IRRange = (float)(eq.IRRange ?? 0f),
});
}
}
return units;
}
/// 构建统一信息网络的探测源列表。
/// 独立探测设备(EquipmentRole.Detection)+ 火力单元自带探测能力。
private static List BuildDetectionSources(ScenarioConfig config)
{
var sources = new List();
foreach (var eq in config.Equipment)
{
// 只要有任何探测字段非 null 且 > 0,就是有效探测源(无论是否独立探测设备)
bool hasDetection = (eq.RadarRange ?? 0) > 0 || (eq.EORange ?? 0) > 0 || (eq.IRRange ?? 0) > 0;
if (!hasDetection) continue;
int qty = Math.Max(1, eq.Quantity);
for (int i = 0; i < qty; i++)
{
sources.Add(new DetectionSource
{
Position = new Algorithms.Vector3((float)eq.PositionX + i * 50, (float)eq.PositionY, (float)eq.PositionZ),
RadarRange = (float)(eq.RadarRange ?? 0f),
EORange = (float)(eq.EORange ?? 0f),
IRRange = (float)(eq.IRRange ?? 0f),
Accuracy = (float)(eq.DetectionAccuracy ?? 0f),
// 3D 球冠几何:缺失时退化为无限制(等价 2D),保证存量数据平滑过渡
MinElevation = (float)(eq.MinElevation ?? float.MaxValue),
MaxElevation = (float)(eq.MaxElevation ?? float.MaxValue),
MinDetectAlt = (float)(eq.MinDetectAlt ?? float.MaxValue),
MaxDetectAlt = (float)(eq.MaxDetectAlt ?? float.MaxValue),
});
}
}
return sources;
}
private static List BuildDroneWaves(ScenarioConfig config)
{
var groups = new List();
foreach (var t in config.Targets)
{
var route = config.Routes.FirstOrDefault(r => r.WaveId == t.WaveId);
var wps = config.WaypointGroups.GetValueOrDefault(t.WaveId, new List());
groups.Add(new DroneWave
{
WaveId = t.WaveId,
Target = t,
Route = route ?? new RoutePlan(),
Waypoints = wps,
});
}
return groups;
}
private List CollectSnapshots()
{
_snapshotPool.Clear();
for (int i = 0; i < _drones.Count; i++)
{
var d = _drones[i];
float vx = d.PosX - d.PrevX, vy = d.PosY - d.PrevY, vz = d.PosZ - d.PrevZ;
_snapshotPool.Add(new EntitySnapshot { EntityId = d.Id, EntityType = Models.EntityType.Drone, PosX = d.PosX, PosY = d.PosY, PosZ = d.PosZ, VelX = vx, VelY = vy, VelZ = vz, DamageStage = (int)_damageModel.GetDamageStage(1 - d.Hp), Hp = d.Hp });
}
for (int i = 0; i < _munitions.Count; i++)
{
var m = _munitions[i];
var (vx, vy, vz) = m.Velocity;
_snapshotPool.Add(new EntitySnapshot { EntityId = m.Id, EntityType = Models.EntityType.Munition, PosX = m.PosX, PosY = m.PosY, PosZ = m.PosZ, VelX = vx, VelY = vy, VelZ = vz });
}
for (int i = 0; i < _platforms.Count; i++)
{
var p = _platforms[i];
var (vx, vy, vz) = p.CurrentVelocity;
_snapshotPool.Add(new EntitySnapshot { EntityId = p.Id, EntityType = Models.EntityType.Platform, PosX = p.PosX, PosY = p.PosY, PosZ = p.PosZ, VelX = vx, VelY = vy, VelZ = vz, PlatformStateStr = p.State.ToString() });
}
for (int i = 0; i < _detectionEntities.Count; i++)
{
var d = _detectionEntities[i];
_snapshotPool.Add(new EntitySnapshot { EntityId = d.Id, EntityType = Models.EntityType.DetectionEquip, PosX = d.PosX, PosY = d.PosY, PosZ = d.PosZ });
}
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.PosX, PosY = c.PosY, PosZ = c.PosZ, CloudRadius = c.Radius, CloudOpacity = c.Dispersion.Particles.Opacity, CloudPhase = c.Phase, CloudElapsed = c.Elapsed });
}
return _snapshotPool;
}
}
}