perf: 仿真期间零字符串分配,JSON 序列化推迟到 Flush
EntitySnapshot 新增原始字段(DamageStage/Hp/CloudRadius等), CollectSnapshots 直接填值 FrameDataStore buffer 改为 FrameRecord 结构体, Flush 时统一拼 JSON SimulationRunner 直接读 snap.CloudRadius/CloudOpacity, 移除 JsonDocument.Parse 删除 SimulationEngine 的 StringBuilder 和 Str() 方法
This commit is contained in:
parent
fb22605fa3
commit
ce9d44db7e
@ -10,13 +10,10 @@ namespace CounterDrone.Core.Simulation
|
||||
public class FrameDataStore
|
||||
{
|
||||
private readonly IPathProvider _paths;
|
||||
private readonly List<SimFrameRecord> _buffer = new();
|
||||
private readonly List<FrameRecord> _buffer = new();
|
||||
private string _taskId = string.Empty;
|
||||
/// <summary>帧数据保留天数,默认 180 天(6 个月)</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>记录一帧所有实体的快照(仅内存,不写磁盘)</summary>
|
||||
/// <summary>记录一帧所有实体的快照(仅内存,零字符串分配)</summary>
|
||||
public void RecordFrame(int frameIdx, float timestamp, List<EntitySnapshot> 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;
|
||||
}
|
||||
|
||||
/// <summary>将缓冲区所有帧数据批量写入 SQLite(事务级别,单次 IO)</summary>
|
||||
/// <summary>将缓冲区序列化为 JSON 并批量写入 SQLite</summary>
|
||||
public void Flush()
|
||||
{
|
||||
if (_buffer.Count == 0) return;
|
||||
var records = new List<SimFrameRecord>(_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<SimFrameRecord>();
|
||||
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<SimFrameRecord> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
/// <summary>仅 Flush 入库时使用,仿真期间保持 null</summary>
|
||||
public string? StateData;
|
||||
}
|
||||
|
||||
public enum DroneStatus { Flying, Destroyed, ReachedTarget, ZoneIntruded }
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>GC 友好字符串构造:复用 StringBuilder,避免拼接中间字符串</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -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<Renderer>().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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user