diff --git a/src/CounterDrone.Core/Simulation/FrameDataStore.cs b/src/CounterDrone.Core/Simulation/FrameDataStore.cs index 688b37d..bf9b1d1 100644 --- a/src/CounterDrone.Core/Simulation/FrameDataStore.cs +++ b/src/CounterDrone.Core/Simulation/FrameDataStore.cs @@ -10,13 +10,10 @@ namespace CounterDrone.Core.Simulation public class FrameDataStore { private readonly IPathProvider _paths; - private readonly List _buffer = new(); + private readonly List _buffer = new(); private string _taskId = string.Empty; - /// 帧数据保留天数,默认 180 天(6 个月) public int RetentionDays { get; set; } = 180; - public int BufferedFrameCount => _buffer.Count > 0 - ? _buffer[^1].FrameIndex + 1 - : 0; + public int BufferedFrameCount { get; private set; } public FrameDataStore(IPathProvider paths) { @@ -28,46 +25,66 @@ namespace CounterDrone.Core.Simulation { _taskId = taskId; _buffer.Clear(); + BufferedFrameCount = 0; } - /// 记录一帧所有实体的快照(仅内存,不写磁盘) + /// 记录一帧所有实体的快照(仅内存,零字符串分配) public void RecordFrame(int frameIdx, float timestamp, List snapshots) { for (int i = 0; i < snapshots.Count; i++) { var s = snapshots[i]; - _buffer.Add(new SimFrameRecord + _buffer.Add(new FrameRecord { - TaskId = _taskId, - FrameIndex = frameIdx, - Timestamp = timestamp, - EntityId = s.EntityId, - EntityType = (int)s.EntityType, - PosX = s.PosX, - PosY = s.PosY, - PosZ = s.PosZ, - RotX = s.RotX, - RotY = s.RotY, - RotZ = s.RotZ, - StateData = s.StateData, + FrameIndex = frameIdx, Timestamp = timestamp, + Snapshot = new EntitySnapshot + { + EntityId = s.EntityId, EntityType = s.EntityType, + PosX = s.PosX, PosY = s.PosY, PosZ = s.PosZ, + DamageStage = s.DamageStage, Hp = s.Hp, + PlatformStateStr = s.PlatformStateStr, + CloudRadius = s.CloudRadius, CloudOpacity = s.CloudOpacity, + CloudPhase = s.CloudPhase, CloudElapsed = s.CloudElapsed, + } }); } + BufferedFrameCount = frameIdx + 1; } - /// 将缓冲区所有帧数据批量写入 SQLite(事务级别,单次 IO) + /// 将缓冲区序列化为 JSON 并批量写入 SQLite public void Flush() { if (_buffer.Count == 0) return; + var records = new List(_buffer.Count); + for (int i = 0; i < _buffer.Count; i++) + { + var fr = _buffer[i]; + var s = fr.Snapshot; + records.Add(new SimFrameRecord + { + TaskId = _taskId, FrameIndex = fr.FrameIndex, Timestamp = fr.Timestamp, + EntityId = s.EntityId, EntityType = (int)s.EntityType, + PosX = s.PosX, PosY = s.PosY, PosZ = s.PosZ, + StateData = s.EntityType switch + { + Models.EntityType.Drone => $"{{\"damageStage\":{s.DamageStage},\"hp\":{s.Hp:F2}}}", + Models.EntityType.Platform => $"{{\"state\":\"{s.PlatformStateStr}\"}}", + Models.EntityType.Cloud => $"{{\"radius\":{s.CloudRadius:F1},\"opacity\":{s.CloudOpacity:F2},\"phase\":{s.CloudPhase},\"elapsed\":{s.CloudElapsed:F1}}}", + _ => "{}", + }, + }); + } + _buffer.Clear(); + var dbPath = Path.Combine(_paths.GetFramesDir(), $"{_taskId}.db"); Directory.CreateDirectory(_paths.GetFramesDir()); using var db = new SQLiteConnection(dbPath); db.CreateTable(); db.CreateIndex("SimFrameRecord", new[] { "TaskId", "FrameIndex" }); - db.InsertAll(_buffer); - _buffer.Clear(); + db.InsertAll(records); } - public void Discard() => _buffer.Clear(); + public void Discard() { _buffer.Clear(); BufferedFrameCount = 0; } public List ReadFrames(string taskId, int fromFrame, int toFrame) { @@ -104,4 +121,11 @@ namespace CounterDrone.Core.Simulation return deleted; } } + + internal struct FrameRecord + { + public int FrameIndex; + public float Timestamp; + public EntitySnapshot Snapshot; + } } diff --git a/src/CounterDrone.Core/Simulation/SimTypes.cs b/src/CounterDrone.Core/Simulation/SimTypes.cs index e28383e..e04a0dc 100644 --- a/src/CounterDrone.Core/Simulation/SimTypes.cs +++ b/src/CounterDrone.Core/Simulation/SimTypes.cs @@ -28,7 +28,18 @@ namespace CounterDrone.Core.Simulation public string EntityId { get; set; } = string.Empty; public Models.EntityType EntityType { get; set; } public float PosX, PosY, PosZ, RotX, RotY, RotZ; - public string StateData { get; set; } = "{}"; + + // ── 原始状态字段(引擎直接填充,前端直接读取,无需 JSON)── + public int DamageStage; + public float Hp; + public string? PlatformStateStr; + public float CloudRadius; + public float CloudOpacity; + public int CloudPhase; + public float CloudElapsed; + + /// 仅 Flush 入库时使用,仿真期间保持 null + public string? StateData; } public enum DroneStatus { Flying, Destroyed, ReachedTarget, ZoneIntruded } diff --git a/src/CounterDrone.Core/Simulation/SimulationEngine.cs b/src/CounterDrone.Core/Simulation/SimulationEngine.cs index e448098..f63c47f 100644 --- a/src/CounterDrone.Core/Simulation/SimulationEngine.cs +++ b/src/CounterDrone.Core/Simulation/SimulationEngine.cs @@ -449,30 +449,20 @@ namespace CounterDrone.Core.Simulation for (int i = 0; i < _drones.Count; i++) { var d = _drones[i]; - int stage = (int)_damageModel.GetDamageStage(1 - d.Hp); - _snapshotPool.Add(new EntitySnapshot { EntityId = d.Id, EntityType = Models.EntityType.Drone, PosX = d.PosX, PosY = d.PosY, PosZ = d.PosZ, StateData = Str($"{{\"damageStage\":{stage},\"hp\":{d.Hp:F2}}}") }); + _snapshotPool.Add(new EntitySnapshot { EntityId = d.Id, EntityType = Models.EntityType.Drone, PosX = d.PosX, PosY = d.PosY, PosZ = d.PosZ, DamageStage = (int)_damageModel.GetDamageStage(1 - d.Hp), Hp = d.Hp }); } 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 = Str($"{{\"state\":\"{p.State}\"}}") }); + _snapshotPool.Add(new EntitySnapshot { EntityId = p.Id, EntityType = Models.EntityType.Platform, PosX = p.PosX, PosY = p.PosY, PosZ = p.PosZ, PlatformStateStr = p.State.ToString() }); } 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 = Str($"{{\"radius\":{c.Dispersion.Radius:F1},\"opacity\":{c.Dispersion.Particles.Opacity:F2},\"phase\":{c.Dispersion.Phase},\"elapsed\":{c.Dispersion.Elapsed:F1}}}") }); + _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, CloudRadius = c.Dispersion.Radius, CloudOpacity = c.Dispersion.Particles.Opacity, CloudPhase = c.Dispersion.Phase, CloudElapsed = c.Dispersion.Elapsed }); } return _snapshotPool; } - - /// GC 友好字符串构造:复用 StringBuilder,避免拼接中间字符串 - private static string Str(FormattableString fs) - { - _sb.Clear(); - _sb.AppendFormat(fs.Format, fs.GetArguments()); - return _sb.ToString(); - } - private static readonly System.Text.StringBuilder _sb = new(256); } } diff --git a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll index b64fa72..af9ba72 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 a283e53..77ab2b2 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 diff --git a/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs b/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs index e145713..edcb22c 100644 --- a/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs +++ b/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System.Text.Json; using CounterDrone.Core; using CounterDrone.Core.Algorithms; using CounterDrone.Core.Models; @@ -83,11 +82,9 @@ namespace CounterDrone.Unity if (snap.EntityType == EntityType.Cloud) { - float r = ParseCloudRadius(snap.StateData); - float opacity = ParseCloudOpacity(snap.StateData); - go.transform.localScale = UVector3.one * r; + go.transform.localScale = UVector3.one * snap.CloudRadius; var mat = go.GetComponent().material; - mat.color = new Color(1, 0, 0, opacity); + mat.color = new Color(1, 0, 0, snap.CloudOpacity); } } @@ -161,29 +158,5 @@ namespace CounterDrone.Unity mat.color = new Color(0.2f, 0.5f, 1f, 0.8f); // 蓝色半透明 = 空基平台 _entityVisuals[snap.EntityId] = go; } - - private float ParseCloudOpacity(string stateData) - { - try - { - using var doc = JsonDocument.Parse(stateData); - if (doc.RootElement.TryGetProperty("opacity", out var o)) - return o.GetSingle(); - } - catch { } - return 0.5f; - } - - private float ParseCloudRadius(string stateData) - { - try - { - using var doc = JsonDocument.Parse(stateData); - if (doc.RootElement.TryGetProperty("radius", out var r)) - return r.GetSingle(); - } - catch { } - return 1f; - } } }