CounterDroneBackend/src/CounterDrone.Core/Simulation/FrameDataStore.cs
tian 5c2b95d777 perf: FrameDataStore 改为内存缓存+结束时批量写入
RecordFrame: 仅内存追加, 零磁盘IO

Flush: 仿真Stop时单次事务批量InsertAll

BeginRecording/Discard/BufferedFrameCount 新API

SimulationEngine: 移除 _frameDb 持久连接, RecordFrames默认true

SimulationRunner: 移除所有 Debug.Log 每帧日志

ReplayController: 适配 ReadFrames 新API

FrameDataStoreTests: 6项覆盖 录制/刷新/读取/丢弃/清理
2026-06-15 18:25:21 +08:00

108 lines
3.8 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.IO;
using CounterDrone.Core.Models;
using SQLite;
namespace CounterDrone.Core.Simulation
{
/// <summary>帧数据存储 — 内存缓存,仿真结束时批量写入 SQLite</summary>
public class FrameDataStore
{
private readonly IPathProvider _paths;
private readonly List<SimFrameRecord> _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 FrameDataStore(IPathProvider paths)
{
_paths = paths;
}
/// <summary>开始录制。清空缓冲区,准备接收帧数据。</summary>
public void BeginRecording(string taskId)
{
_taskId = taskId;
_buffer.Clear();
}
/// <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
{
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,
});
}
}
/// <summary>将缓冲区所有帧数据批量写入 SQLite事务级别单次 IO</summary>
public void Flush()
{
if (_buffer.Count == 0) return;
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();
}
public void Discard() => _buffer.Clear();
public List<SimFrameRecord> ReadFrames(string taskId, int fromFrame, int toFrame)
{
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{taskId}.db");
if (!File.Exists(dbPath)) return new List<SimFrameRecord>();
using var db = new SQLiteConnection(dbPath);
return db.Table<SimFrameRecord>()
.Where(f => f.TaskId == taskId && f.FrameIndex >= fromFrame && f.FrameIndex <= toFrame)
.OrderBy(f => f.FrameIndex)
.ToList();
}
public void DeleteFrameDb(string taskId)
{
var dbPath = Path.Combine(_paths.GetFramesDir(), $"{taskId}.db");
if (File.Exists(dbPath)) File.Delete(dbPath);
}
/// <summary>清理过期的帧数据库文件</summary>
public int CleanupExpired()
{
var dir = _paths.GetFramesDir();
if (!Directory.Exists(dir)) return 0;
var cutoff = DateTime.Now.AddDays(-RetentionDays);
var deleted = 0;
foreach (var file in Directory.GetFiles(dir, "*.db"))
{
if (File.GetCreationTime(file) < cutoff)
{
File.Delete(file);
deleted++;
}
}
return deleted;
}
}
}