CounterDroneBackend/src/CounterDrone.Core/Simulation/SimulationEngine.cs
tian 197f8b6e5c perf: Commit批量50帧 + Calc回到+1(11发最优)
- _frameDb.Commit() 从每帧改为每50帧批量提交
- CalcRoundsNeeded 回退到 +1(12发反更差)
- Piston: 10机50m间距8/10击毁
2026-06-13 15:17:25 +08:00

359 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
using SQLite;
namespace CounterDrone.Core.Simulation
{
/// <summary>仿真引擎 — 纯执行器,接收发射计划按时间表执行</summary>
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;
private float _tickInterval;
public IReadOnlyList<DroneEntity> Drones => _drones;
public IReadOnlyList<CloudEntity> Clouds => _clouds;
public IReadOnlyList<MunitionEntity> Munitions => _munitions;
public IReadOnlyList<SimEvent> Events => _allEvents;
public event Action<MunitionEntity> OnMunitionLaunched;
public event Action<CloudEntity> OnCloudGenerated;
public event Action<DroneEntity> OnDroneDestroyed;
public event Action<DroneEntity> OnDroneReachedTarget;
public event Action<DroneEntity, ControlZoneEntity> OnZoneIntruded;
public event Action OnSimulationEnded;
private List<DroneEntity> _drones = new();
private List<PlatformEntity> _platforms = new();
private List<MunitionEntity> _munitions = new();
private List<CloudEntity> _clouds = new();
private List<ControlZoneEntity> _zones = new();
private CombatScene _scene = new();
private float _disperseHeight;
private AmmunitionSpec _ammoSpec = new();
private int _tickRate = 20;
private readonly List<FireEvent> _fireSchedule = new();
private int _nextFireIndex;
private readonly List<SimEvent> _allEvents = new();
private SQLiteConnection _frameDb = null!;
private string _taskId = string.Empty;
private readonly IDefensePlanner _planner;
private int _entityCounter;
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<FireEvent> schedule)
{
_fireSchedule.Clear();
_fireSchedule.AddRange(schedule.OrderBy(f => f.FireTime));
_nextFireIndex = 0;
}
public void Initialize(string taskId)
{
_taskId = taskId;
var config = _scenarioService.GetTaskDetail(taskId);
if (config == null) throw new InvalidOperationException("Task not found");
_scene = config.Scene;
_disperseHeight = (float)config.Cloud.DisperseHeight;
_tickRate = config.Task.TickRate;
_tickInterval = 1.0f / _tickRate;
using (var ammoDb = new SQLiteConnection(_paths.GetMainDbPath()))
_ammoSpec = ammoDb.Table<AmmunitionSpec>()
.FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType)
?? DefaultAmmunition.GetByType((AerosolType)config.Cloud.AerosolType);
_drones.Clear();
foreach (var target in config.Targets)
{
var route = config.Routes.FirstOrDefault(r => r.GroupId == target.GroupId);
var wps = config.WaypointGroups.GetValueOrDefault(target.GroupId, new List<Waypoint>());
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.GroupId,
target, wps, latIdx, lateralSpacing, longIdx, longSpacing, mode));
}
}
_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));
_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 = BuildDroneGroups(config);
if (fireUnits.Count > 0 && threats.Count > 0)
{
var result = _planner.Plan(fireUnits, threats, _scene);
SetFireSchedule(result.Best.MergedSchedule);
}
}
_frameDb = _frameStore.CreateOrOpen(_taskId);
State = SimulationState.Running;
}
public SimulationFrameResult Tick(float deltaTime)
{
if (State != SimulationState.Running)
return new SimulationFrameResult { State = State, FrameIndex = FrameIndex, SimulationTime = SimulationTime };
var scaledDt = deltaTime * TimeScale;
SimulationTime += scaledDt;
FrameIndex++;
var frameEvents = new List<SimEvent>();
// 1. 按发射计划执行
while (_nextFireIndex < _fireSchedule.Count && _fireSchedule[_nextFireIndex].FireTime <= SimulationTime)
{
var fe = _fireSchedule[_nextFireIndex++];
var platform = _platforms.Count > fe.PlatformIndex ? _platforms[fe.PlatformIndex] : null;
if (platform == null) continue;
if (platform.PlatformType == Models.PlatformType.AirBased && platform.Ready)
{
platform.CommandFlyTo(fe.TargetX, platform.ReleaseAltitude, fe.TargetZ);
}
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);
_munitions.Add(m);
OnMunitionLaunched?.Invoke(m);
frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = SimulationTime, SourceId = platform.Id, Description = $"计划发射 {m.Id}" });
}
}
// 1b. 空基平台到达投放点 → 投弹
foreach (var platform in _platforms.Where(p => p.CanRelease))
{
var (cvx, cvy, cvz) = platform.CurrentVelocity;
platform.Release();
var m = new MunitionEntity($"mun_{++_entityCounter}",
Models.PlatformType.AirBased,
platform.AerosolType ?? Models.AerosolType.InertGas,
platform.PosX, platform.PosY, platform.PosZ,
platform.PosX, _disperseHeight, platform.PosZ,
0f, _disperseHeight,
cvx, cvy, cvz);
_munitions.Add(m);
OnMunitionLaunched?.Invoke(m);
frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = SimulationTime, SourceId = platform.Id, Description = $"空基投放 {m.Id}" });
}
// 2. 弹药飞行 → 云团生成
foreach (var m in _munitions.ToList())
{
m.Update(scaledDt);
if (m.HasArrived)
{
var disp = AlgorithmFactory.Create<ICloudDispersionModel>();
disp.Initialize(_ammoSpec, _scene, new Algorithms.Vector3(m.TargetX, m.TargetY, m.TargetZ), SimulationTime);
var cloud = new CloudEntity($"cloud_{++_entityCounter}", m.AerosolType, disp, SimulationTime);
_clouds.Add(cloud);
_munitions.Remove(m);
OnCloudGenerated?.Invoke(cloud);
frameEvents.Add(new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = SimulationTime, Description = "云团生成" });
}
}
// 3. 云团演化
foreach (var c in _clouds.ToList())
{
c.Tick(scaledDt, (float)_scene.WindSpeed, (WindDirection)_scene.WindDirection);
if (c.IsDissipated) _clouds.Remove(c);
}
// 4. 毁伤判定
foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
{
foreach (var cloud in _clouds)
{
if (cloud.ContainsPoint(drone.PosX, drone.PosY, drone.PosZ))
{
drone.ExposureTime += scaledDt;
var dmg = _damageModel.CalculateDamage(drone.TargetType, drone.PowerType, cloud.AerosolType, cloud.Dispersion.CoreDensity, drone.ExposureTime, scaledDt);
drone.ApplyDamage(dmg);
if (drone.Status == DroneStatus.Destroyed)
{
OnDroneDestroyed?.Invoke(drone);
frameEvents.Add(new SimEvent { Type = SimEventType.DroneDestroyed, OccurredAt = SimulationTime, TargetId = drone.Id, Description = $"无人机 {drone.Id} 被摧毁" });
}
}
}
}
// 5. 无人机移动
foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
{
drone.Update(scaledDt, (float)_scene.WindSpeed, (WindDirection)_scene.WindDirection);
if (drone.Status == DroneStatus.ReachedTarget)
{
OnDroneReachedTarget?.Invoke(drone);
frameEvents.Add(new SimEvent { Type = SimEventType.DroneReachedTarget, OccurredAt = SimulationTime, SourceId = drone.Id, Description = "到达攻击目标" });
}
}
// 6. 平台冷却
foreach (var p in _platforms) p.Update(scaledDt);
// 7. 管控区域
foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
{
foreach (var zone in _zones)
{
if (zone.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}" });
}
}
}
// 8. 结束判定
if (_drones.All(d => d.Status != DroneStatus.Flying))
{
State = SimulationState.Completed;
OnSimulationEnded?.Invoke();
frameEvents.Add(new SimEvent { Type = SimEventType.SimulationEnd, OccurredAt = SimulationTime });
}
_allEvents.AddRange(frameEvents);
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 };
}
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; _frameDb?.Close(); }
private static List<FireUnit> BuildFireUnits(TaskFullConfig config)
{
var units = new List<FireUnit>();
int idx = 0;
foreach (var eq in config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
{
int qty = Math.Max(1, eq.Quantity);
for (int i = 0; i < qty; i++)
{
// 每个火力单元展开为独立通道GunCount × ChannelsPerGun
int gunCount = eq.GunCount ?? 1;
int chPerGun = eq.ChannelsPerGun ?? 1;
for (int g = 0; g < gunCount; g++)
for (int ch = 0; ch < chPerGun; ch++)
{
units.Add(new FireUnit
{
Id = $"fu{idx++}",
Type = (PlatformType)(eq.PlatformType ?? (int)PlatformType.GroundBased),
Position = new Algorithms.Vector3((float)eq.PositionX + i * 50, (float)eq.PositionY, (float)eq.PositionZ),
GunCount = 1, ChannelsPerGun = 1, ChannelInterval = (float)(eq.ChannelInterval ?? 1),
CruiseSpeed = (float)(eq.CruiseSpeed ?? 0f),
ReleaseAltitude = (float)(eq.ReleaseAltitude ?? 0f),
MuzzleVelocity = (float)(eq.MuzzleVelocity ?? 0f),
// 总弹药均分到每个通道
TotalMunitions = (eq.MunitionCount ?? 1) / (gunCount * chPerGun),
AmmoTypes = new List<AerosolType> { (AerosolType)(eq.AerosolType ?? 0) },
Cooldown = (float)eq.Cooldown,
});
}
}
}
return units;
}
private static List<DroneGroup> BuildDroneGroups(TaskFullConfig config)
{
var groups = new List<DroneGroup>();
foreach (var t in config.Targets)
{
var route = config.Routes.FirstOrDefault(r => r.GroupId == t.GroupId);
var wps = config.WaypointGroups.GetValueOrDefault(t.GroupId, new List<Waypoint>());
groups.Add(new DroneGroup
{
GroupId = t.GroupId,
Target = t,
Route = route ?? new RoutePlan(),
Waypoints = wps,
});
}
return groups;
}
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;
}
}
}