< Summary

Information
Class: CounterDrone.Core.Simulation.SimulationEngine
Assembly: CounterDrone.Core
File(s): C:\Users\Tellme\apps\CounterDroneBackend\src\CounterDrone.Core\Simulation\SimulationEngine.cs
Line coverage
99%
Covered lines: 215
Uncovered lines: 1
Coverable lines: 216
Total lines: 298
Line coverage: 99.5%
Branch coverage
90%
Covered branches: 63
Total branches: 70
Branch coverage: 90%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_State()100%11100%
get_FrameIndex()100%11100%
get_SimulationTime()100%11100%
get_TimeScale()100%11100%
.ctor(...)100%11100%
get_Drones()100%11100%
get_Clouds()100%11100%
get_Munitions()100%210%
get_Events()100%11100%
SetFireSchedule(...)100%11100%
Initialize(...)87.5%1616100%
Tick(...)90.9%4444100%
Pause()100%22100%
Resume()100%22100%
Stop()50%22100%
CollectSnapshots()100%44100%

File(s)

C:\Users\Tellme\apps\CounterDroneBackend\src\CounterDrone.Core\Simulation\SimulationEngine.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Text.Json;
 5using CounterDrone.Core.Algorithms;
 6using CounterDrone.Core.Models;
 7using CounterDrone.Core.Services;
 8using SQLite;
 9
 10namespace CounterDrone.Core.Simulation
 11{
 12    /// <summary>发射计划中的单次事件</summary>
 13    public class FireEvent
 14    {
 15        public float FireTime;
 16        public int PlatformIndex;       // 第几个发射平台(0-based)
 17        public float TargetX, TargetY, TargetZ;
 18        public float MuzzleVelocity;
 19    }
 20
 21    /// <summary>仿真引擎 — 纯执行器,不包含任何决策逻辑</summary>
 22    public class SimulationEngine
 23    {
 24        private readonly IScenarioService _scenarioService;
 25        private readonly FrameDataStore _frameStore;
 26        private readonly IDamageModel _damageModel;
 27        private readonly IPathProvider _paths;
 28
 1114229        public SimulationState State { get; private set; } = SimulationState.Idle;
 2218930        public int FrameIndex { get; private set; }
 2355931        public float SimulationTime { get; private set; }
 32        /// <summary>时间倍速:>1 加速仿真(仅测试或快速推演用)</summary>
 556333        public float TimeScale { get; set; } = 1f;
 34        private float _tickInterval;
 35
 1236        private List<DroneEntity> _drones = new();
 1237        private List<PlatformEntity> _platforms = new();
 1238        private List<MunitionEntity> _munitions = new();
 1239        private List<CloudEntity> _clouds = new();
 1240        private List<ControlZoneEntity> _zones = new();
 41
 1242        private CombatScene _scene = new();
 1243        private AmmunitionSpec _ammoSpec = new();
 1244        private int _tickRate = 20;
 45
 46        // 发射计划:算法预计算,引擎按时间表执行
 1247        private readonly List<FireEvent> _fireSchedule = new();
 48        private int _nextFireIndex;
 49
 1250        private readonly List<SimEvent> _allEvents = new();
 1251        private SQLiteConnection _frameDb = null!;
 1252        private string _taskId = string.Empty;
 53        private int _entityCounter;
 54
 955        public IReadOnlyList<DroneEntity> Drones => _drones;
 256        public IReadOnlyList<CloudEntity> Clouds => _clouds;
 057        public IReadOnlyList<MunitionEntity> Munitions => _munitions;
 858        public IReadOnlyList<SimEvent> Events => _allEvents;
 59
 1260        public SimulationEngine(IScenarioService scenarioService, FrameDataStore frameStore,
 1261            IDamageModel damageModel, IPathProvider paths)
 1262        {
 1263            _scenarioService = scenarioService;
 1264            _frameStore = frameStore;
 1265            _damageModel = damageModel;
 1266            _paths = paths;
 1267        }
 68
 69        /// <summary>设置发射计划(来自推荐算法),在 Initialize 之前调用</summary>
 70        public void SetFireSchedule(List<FireEvent> schedule)
 271        {
 272            _fireSchedule.Clear();
 1373            _fireSchedule.AddRange(schedule.OrderBy(f => f.FireTime));
 274            _nextFireIndex = 0;
 275        }
 76
 77        public void Initialize(string taskId)
 1278        {
 1279            _taskId = taskId;
 1280            var config = _scenarioService.GetTaskDetail(taskId);
 1281            if (config == null) throw new InvalidOperationException("Task not found");
 82
 1283            _scene = config.Scene;
 1284            _tickRate = config.Task.TickRate;
 1285            _tickInterval = 1.0f / _tickRate;
 86
 1287            using (var ammoDb = new SQLiteConnection(_paths.GetMainDbPath()))
 1288            {
 1289                _ammoSpec = ammoDb.Table<AmmunitionSpec>()
 1290                    .FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType)
 1291                    ?? new AmmunitionSpec { InitialRadius = 50, CoreDensity = 1.0f, DispersionRateBase = 5, MaxRadius = 
 1292            }
 93
 94            // 无人机
 1295            _drones.Clear();
 6096            foreach (var target in config.Targets)
 1297            {
 1298                var mode = (FormationMode)config.Route.FormationMode;
 1299                var spacing = (float)config.Route.FormationSpacing;
 48100                for (int i = 0; i < target.Quantity; i++)
 12101                    _drones.Add(new DroneEntity($"drone_{++_entityCounter}", target.GroupId,
 12102                        target, config.Waypoints, i, spacing, mode));
 12103            }
 104
 105            // 装备
 12106            _platforms.Clear();
 72107            foreach (var equip in config.Equipment)
 18108            {
 18109                if (equip.EquipmentRole != (int)EquipmentRole.Detection)
 16110                {
 64111                    for (int i = 0; i < equip.Quantity; i++)
 16112                        _platforms.Add(new PlatformEntity($"plat_{++_entityCounter}", equip));
 16113                }
 18114            }
 115
 116            // 管控区域
 12117            _zones.Clear();
 38118            foreach (var z in config.ControlZones)
 1119                _zones.Add(new ControlZoneEntity(z.Id, z.Name, z.VerticesJson,
 1120                    (float)z.MinAltitude, (float)z.MaxAltitude));
 121
 12122            _munitions.Clear();
 12123            _clouds.Clear();
 12124            _allEvents.Clear();
 12125            FrameIndex = 0;
 12126            SimulationTime = 0;
 12127            _entityCounter = 0;
 12128            _nextFireIndex = 0;
 129
 12130            _frameDb = _frameStore.CreateOrOpen(_taskId);
 12131            State = SimulationState.Running;
 12132        }
 133
 134        public SimulationFrameResult Tick(float deltaTime)
 5545135        {
 5545136            if (State != SimulationState.Running)
 1137                return new SimulationFrameResult { State = State, FrameIndex = FrameIndex, SimulationTime = SimulationTi
 138
 5544139            var scaledDt = deltaTime * TimeScale;
 5544140            SimulationTime += scaledDt;
 5544141            FrameIndex++;
 5544142            var frameEvents = new List<SimEvent>();
 143
 144            // 1. 按发射计划执行
 5555145            while (_nextFireIndex < _fireSchedule.Count &&
 5555146                   _fireSchedule[_nextFireIndex].FireTime <= SimulationTime)
 11147            {
 11148                var fe = _fireSchedule[_nextFireIndex++];
 11149                var platform = _platforms.Count > fe.PlatformIndex
 11150                    ? _platforms[fe.PlatformIndex] : null;
 11151                if (platform != null && platform.Ready)
 11152                {
 11153                    platform.Fire();
 11154                    var m = new MunitionEntity($"mun_{++_entityCounter}",
 11155                        platform.PlatformType ?? Models.PlatformType.GroundBased,
 11156                        platform.AerosolType ?? Models.AerosolType.InertGas,
 11157                        platform.PosX, platform.PosY, platform.PosZ,
 11158                        fe.TargetX, fe.TargetY, fe.TargetZ,
 11159                        fe.MuzzleVelocity,
 11160                        (float)_ammoSpec.InitialRadius > 0 ? 300f : 300f);
 11161                    _munitions.Add(m);
 11162                    frameEvents.Add(new SimEvent
 11163                    {
 11164                        Type = SimEventType.MunitionLaunched, OccurredAt = SimulationTime,
 11165                        SourceId = platform.Id, Description = $"计划发射 {m.Id}",
 11166                    });
 11167                }
 11168            }
 169
 170            // 2. 弹药飞行 → 云团生成
 17710171            foreach (var m in _munitions.ToList())
 539172            {
 539173                m.Update(scaledDt);
 539174                if (m.HasArrived)
 11175                {
 11176                    var disp = AlgorithmFactory.Create<ICloudDispersionModel>();
 11177                    disp.Initialize(_ammoSpec, _scene,
 11178                        new Algorithms.Vector3(m.TargetX, m.TargetY, m.TargetZ), SimulationTime);
 11179                    _clouds.Add(new CloudEntity($"cloud_{++_entityCounter}", m.AerosolType, disp, SimulationTime));
 11180                    _munitions.Remove(m);
 11181                    frameEvents.Add(new SimEvent
 11182                    {
 11183                        Type = SimEventType.CloudGenerated, OccurredAt = SimulationTime,
 11184                        Description = $"云团生成",
 11185                    });
 11186                }
 539187            }
 188
 189            // 3. 云团演化
 17360190            foreach (var c in _clouds.ToList())
 364191            {
 364192                c.Tick(scaledDt, (float)_scene.WindSpeed, (WindDirection)_scene.WindDirection);
 364193                if (c.IsDissipated) _clouds.Remove(c);
 364194            }
 195
 196            // 4. 毁伤判定
 33264197            foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
 5544198            {
 17360199                foreach (var cloud in _clouds)
 364200                {
 364201                    if (cloud.ContainsPoint(drone.PosX, drone.PosY, drone.PosZ))
 25202                    {
 25203                        drone.ExposureTime += scaledDt;
 25204                        var dmg = _damageModel.CalculateDamage(
 25205                            drone.TargetType, drone.PowerType, cloud.AerosolType,
 25206                            cloud.Dispersion.CoreDensity, drone.ExposureTime, scaledDt);
 25207                        drone.ApplyDamage(dmg);
 25208                        if (drone.Status == DroneStatus.Destroyed)
 3209                            frameEvents.Add(new SimEvent
 3210                            {
 3211                                Type = SimEventType.DroneDestroyed, OccurredAt = SimulationTime,
 3212                                TargetId = drone.Id, Description = $"无人机 {drone.Id} 被摧毁",
 3213                            });
 25214                    }
 364215                }
 5544216            }
 217
 218            // 5. 无人机移动
 33260219            foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
 5542220            {
 5542221                drone.Update(scaledDt, (float)_scene.WindSpeed, (WindDirection)_scene.WindDirection);
 5542222                if (drone.Status == DroneStatus.ReachedTarget)
 4223                    frameEvents.Add(new SimEvent
 4224                    {
 4225                        Type = SimEventType.DroneReachedTarget, OccurredAt = SimulationTime,
 4226                        SourceId = drone.Id, Description = "到达攻击目标",
 4227                    });
 5542228            }
 229
 230            // 6. 平台冷却
 40164231            foreach (var p in _platforms) p.Update(scaledDt);
 232
 233            // 7. 管控区域
 33252234            foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
 5538235            {
 16758236                foreach (var zone in _zones)
 72237                {
 72238                    if (zone.ContainsPoint(drone.PosX, drone.PosY, drone.PosZ))
 1239                    {
 1240                        drone.MarkZoneIntruded();
 1241                        frameEvents.Add(new SimEvent
 1242                        {
 1243                            Type = SimEventType.ZoneIntruded, OccurredAt = SimulationTime,
 1244                            SourceId = drone.Id, TargetId = zone.Id, Description = $"侵入 {zone.Name}",
 1245                        });
 1246                    }
 72247                }
 5538248            }
 249
 250            // 8. 结束判定
 11088251            if (_drones.All(d => d.Status != DroneStatus.Flying))
 7252            {
 7253                State = SimulationState.Completed;
 7254                frameEvents.Add(new SimEvent { Type = SimEventType.SimulationEnd, OccurredAt = SimulationTime });
 7255            }
 256
 5544257            _allEvents.AddRange(frameEvents);
 258
 259            // 录制帧
 5544260            var snapshots = CollectSnapshots();
 5544261            _frameStore.WriteFrame(_frameDb, _taskId, FrameIndex, SimulationTime, snapshots);
 5544262            _frameDb.Commit();
 263
 5544264            return new SimulationFrameResult
 5544265            {
 5544266                EntitySnapshots = snapshots, NewEvents = frameEvents,
 5544267                State = State, FrameIndex = FrameIndex, SimulationTime = SimulationTime,
 5544268            };
 5545269        }
 270
 4271        public void Pause() { if (State == SimulationState.Running) State = SimulationState.Paused; }
 4272        public void Resume() { if (State == SimulationState.Paused) State = SimulationState.Running; }
 52273        public void Stop() { State = SimulationState.Stopped; _frameDb?.Close(); }
 274
 275        private List<EntitySnapshot> CollectSnapshots()
 5544276        {
 5544277            var list = new List<EntitySnapshot>();
 27720278            foreach (var d in _drones)
 5544279            {
 5544280                var stage = _damageModel.GetDamageStage(1 - d.Hp);
 5544281                list.Add(new EntitySnapshot
 5544282                {
 5544283                    EntityId = d.Id, EntityType = Models.EntityType.Drone,
 5544284                    PosX = d.PosX, PosY = d.PosY, PosZ = d.PosZ,
 5544285                    StateData = JsonSerializer.Serialize(new { damageStage = (int)stage, hp = d.Hp }),
 5544286                });
 5544287            }
 17360288            foreach (var c in _clouds)
 364289                list.Add(new EntitySnapshot
 364290                {
 364291                    EntityId = c.Id, EntityType = Models.EntityType.Cloud,
 364292                    PosX = c.Dispersion.Center.X, PosY = c.Dispersion.Center.Y, PosZ = c.Dispersion.Center.Z,
 364293                    StateData = JsonSerializer.Serialize(new { radius = c.Dispersion.Radius, opacity = c.Dispersion.Part
 364294                });
 5544295            return list;
 5544296        }
 297    }
 298}