配置运行时重载 IConfigService + 防御推荐 + 文档更新
新增: - IConfigService / ConfigService:读写 defaults.json / planner_config.json - SavePlannerConfig / SaveDefaults / Reload,无需重启 Unity - PlannerConfigReloaded 事件 → ScenarioManager 自动重建 planner - SimulationEngine.UpdatePlanner / ScenarioService.UpdatePlanner - SimulationRunner.ReloadPlanner 桥接 - DefenseRecommendation / RecommendOption 返回类型 - IScenarioService.GetDefenseRecommendation(配置阶段生成最佳抛撒参数) - ConfigServiceTests 5 个测试 + DefenseRecommendationTests 3 个测试 修复: - 云团间距半径基准不一致(间距用 R30、穿越用 R27 → 统一用 effectiveRadius) - 消除硬编码 30f(DefensePlanner + GaussianPuffDispersion 读 Phase2Duration) - TryGenerateFireEvents 复用 ComputeEffectiveRadius,消除重复计算 - 概率计算间距加重叠系数 - DatabaseManager 不再 DROP SimulationReport(保留历史报告) - 清理迁移代码(MetaEntry / 旧表 DROP / 版本检查) 文档: - 对接文档 V2.1:加 IConfigService API + 配置重载用法 + 修正 DLL 更新说明 - 实施跟踪 V1.8:8.2.1 更新 + 新增 8.2.3/8.2.4 + M14 里程碑 - 270 测试通过,Unity 编译通过
This commit is contained in:
parent
a3ceae5c14
commit
36ab4fad7b
@ -1,8 +1,8 @@
|
|||||||
# 实施计划与任务跟踪
|
# 实施计划与任务跟踪
|
||||||
|
|
||||||
> **项目**:反无人机仿真系统后端
|
> **项目**:反无人机仿真系统后端
|
||||||
> **文档版本**:V1.7
|
> **文档版本**:V1.8
|
||||||
> **更新日期**:2026-06-20
|
> **更新日期**:2026-06-22
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ Phase 13 ✅ 模型重构:分层架构 + 代码审查 + 探测分离
|
|||||||
|
|
||||||
| 指标 | 值 |
|
| 指标 | 值 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 测试总数 | **262**(全部通过) |
|
| 测试总数 | **270**(全部通过) |
|
||||||
| 行覆盖率 | **95%+** |
|
| 行覆盖率 | **95%+** |
|
||||||
| 分支覆盖率 | **80%+** |
|
| 分支覆盖率 | **80%+** |
|
||||||
| 执行时间 | ~15 秒 |
|
| 执行时间 | ~15 秒 |
|
||||||
@ -190,8 +190,10 @@ Phase 13 ✅ 模型重构:分层架构 + 代码审查 + 探测分离
|
|||||||
|
|
||||||
| # | 功能 | 说明 |
|
| # | 功能 | 说明 |
|
||||||
|---|------|------|
|
|---|------|------|
|
||||||
| 8.2.1 | PDF 导出 | ✅ PdfSharpCore(netstandard2.0,纯托管 Unity 兼容)+ ReportData 结构化模型 + StandardPdfTemplate 内置标准模板 + CJK 字体嵌入。`IReportService.ExportReport(id, "pdf")` 按需导出,仿真后自动生成 MD 到报告目录 |
|
| 8.2.1 | PDF 导出 | ✅ PdfSharpCore + ReportData 结构化模型 + StandardPdfTemplate + CJK 字体嵌入 + IConfigService 运行时重载 |
|
||||||
| 8.2.2 | Word 导出 | ⬜ 调研可行方案 |
|
| 8.2.2 | Word 导出 | ⬜ 调研可行方案 |
|
||||||
|
| 8.2.3 | 防御推荐方案 | ✅ `IScenarioService.GetDefenseRecommendation`,配置阶段调 planner 生成最佳抛撒参数,前端"一键应用" |
|
||||||
|
| 8.2.4 | 配置运行时重载 | ✅ `IConfigService` 读写 defaults.json / planner_config.json,SavePlannerConfig/SaveDefaults/Reload 无需重启 |
|
||||||
|
|
||||||
### 8.3 第三方对接
|
### 8.3 第三方对接
|
||||||
|
|
||||||
@ -453,6 +455,7 @@ T1(数据层)→ T2(算法层 IsInCoverage)
|
|||||||
| M11 — 运动学重构 + 实体暴露 + 基础数据 CRUD | ✅ |
|
| M11 — 运动学重构 + 实体暴露 + 基础数据 CRUD | ✅ |
|
||||||
| M12 — 空基/地基统一规划 + 编队轴 + LaneDivider | ✅ |
|
| M12 — 空基/地基统一规划 + 编队轴 + LaneDivider | ✅ |
|
||||||
| M13 — PDF 导出 + 报告模板架构 | ✅ |
|
| M13 — PDF 导出 + 报告模板架构 | ✅ |
|
||||||
|
| M14 — 防御推荐 GetDefenseRecommendation + 配置运行时重载 | ✅ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# 后端对接文档(Unity 前端)
|
# 后端对接文档(Unity 前端)
|
||||||
|
|
||||||
> **版本**:V2.0
|
> **版本**:V2.1
|
||||||
> **日期**:2026-06-20
|
> **日期**:2026-06-22
|
||||||
> **Unity 版本**:2022.3.62f3c1
|
> **Unity 版本**:2022.3.62f3c1
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -60,7 +60,7 @@ dotnet publish -c Release -o ../../unity_plugins
|
|||||||
# 然后将 unity_plugins/*.dll 全部复制到 src/Unity/Assets/Plugins/CounterDrone.Core/
|
# 然后将 unity_plugins/*.dll 全部复制到 src/Unity/Assets/Plugins/CounterDrone.Core/
|
||||||
```
|
```
|
||||||
|
|
||||||
> **重要**:更新 DLL 后首次运行 Unity 会重建数据库,**历史仿真报告会丢失**(基础数据和预设想定自动重新种子)。这是预期行为。
|
> **重要**:更新 DLL 后首次运行 Unity 会建表+种子默认数据。**历史仿真报告不再丢失**(SimulationReport 表不再被 DROP)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -272,6 +272,7 @@ cam.transform.LookAt(new Vector3(midX, 500, 0)); // 看向 500m 高度
|
|||||||
```csharp
|
```csharp
|
||||||
public IScenarioService Service { get; }
|
public IScenarioService Service { get; }
|
||||||
public IDataService DataService { get; } // 基础数据 CRUD(7 类)
|
public IDataService DataService { get; } // 基础数据 CRUD(7 类)
|
||||||
|
public IConfigService Config { get; } // 配置读写 + 运行时重载
|
||||||
|
|
||||||
// Scenario CRUD
|
// Scenario CRUD
|
||||||
public Scenario CreateScenario(string name, string number);
|
public Scenario CreateScenario(string name, string number);
|
||||||
@ -354,6 +355,47 @@ else
|
|||||||
{
|
{
|
||||||
Debug.Log($"推荐失败: {rec.Summary}");
|
Debug.Log($"推荐失败: {rec.Summary}");
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### IConfigService(通过 `ScenarioManager.Config` 访问)
|
||||||
|
|
||||||
|
配置文件读写 + 运行时重载,**无需重启 Unity**:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IConfigService
|
||||||
|
{
|
||||||
|
string GetPlannerConfigJson(); // 读取 planner_config.json 原文
|
||||||
|
void SavePlannerConfig(string json); // 保存并立即重载(planner 自动重建)
|
||||||
|
string GetDefaultsJson(); // 读取 defaults.json 原文
|
||||||
|
void SaveDefaults(string json); // 保存并重载种子数据到数据库
|
||||||
|
void Reload(); // 从磁盘重新加载(前端直接改文件后通知后端刷新)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**用法 1:通过 API 修改配置**(推荐)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var cfg = GetComponent<ScenarioManager>().Config;
|
||||||
|
|
||||||
|
// 读当前 planner 配置
|
||||||
|
string json = cfg.GetPlannerConfigJson();
|
||||||
|
|
||||||
|
// 修改后保存 — 自动重载,planner 立即用新参数
|
||||||
|
cfg.SavePlannerConfig(modifiedJson);
|
||||||
|
|
||||||
|
// 修改 defaults.json — 自动重载,数据库 InsertOrReplace 覆盖同 Id 记录
|
||||||
|
cfg.SaveDefaults(modifiedDefaultsJson);
|
||||||
|
```
|
||||||
|
|
||||||
|
**用法 2:直接改文件后通知后端重载**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 前端直接修改 persistentDataPath 下的 JSON 文件
|
||||||
|
// 然后通知后端重新加载
|
||||||
|
cfg.Reload(); // 重新读磁盘 → 重建 planner → 刷新种子数据
|
||||||
|
```
|
||||||
|
|
||||||
|
> `SavePlannerConfig` 触发后,`ScenarioManager` 内部的 `DefensePlanner` 会自动重建。`SimulationRunner` 中的 planner 在下次 `LoadAndStart` 时自动用新配置。运行中的仿真不受影响(planner 在 `Initialize` 时调用,不在运行中调用)。
|
||||||
|
|
||||||
### IDataService(通过 `ScenarioManager.DataService` 访问)
|
### IDataService(通过 `ScenarioManager.DataService` 访问)
|
||||||
|
|
||||||
@ -910,4 +952,4 @@ ScenarioConfig
|
|||||||
|------|------|
|
|------|------|
|
||||||
| 架构设计 | `docs/design/architecture/总体架构设计.md` |
|
| 架构设计 | `docs/design/architecture/总体架构设计.md` |
|
||||||
| 实体事件映射 | `docs/design/technical/仿真器实体与事件映射.md` |
|
| 实体事件映射 | `docs/design/technical/仿真器实体与事件映射.md` |
|
||||||
| 测试状态 | **262 测试,15 秒(全部通过)** |
|
| 测试状态 | **270 测试,14 秒(全部通过)** |
|
||||||
|
|||||||
93
src/CounterDrone.Core/Services/ConfigService.cs
Normal file
93
src/CounterDrone.Core/Services/ConfigService.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using CounterDrone.Core.Algorithms;
|
||||||
|
using CounterDrone.Core.Models;
|
||||||
|
using SQLite;
|
||||||
|
|
||||||
|
namespace CounterDrone.Core.Services
|
||||||
|
{
|
||||||
|
/// <summary>配置服务实现 — 读写 JSON 配置文件,重载时触发事件通知消费方刷新</summary>
|
||||||
|
public class ConfigService : IConfigService
|
||||||
|
{
|
||||||
|
private readonly IPathProvider _paths;
|
||||||
|
private readonly SQLiteConnection _db;
|
||||||
|
|
||||||
|
/// <summary>planner 配置重载后触发(消费方应重建 DefensePlanner)</summary>
|
||||||
|
public event Action<PlannerConfig>? PlannerConfigReloaded;
|
||||||
|
|
||||||
|
/// <summary>默认数据重载后触发(消费方应刷新缓存)</summary>
|
||||||
|
public event Action<DefaultData>? DefaultsReloaded;
|
||||||
|
|
||||||
|
public ConfigService(IPathProvider paths, SQLiteConnection db)
|
||||||
|
{
|
||||||
|
_paths = paths;
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPlannerConfigJson()
|
||||||
|
{
|
||||||
|
var path = Path.Combine(_paths.GetDataRoot(), "planner_config.json");
|
||||||
|
if (!File.Exists(path))
|
||||||
|
throw new FileNotFoundException($"planner 配置文件不存在: {path}");
|
||||||
|
return File.ReadAllText(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SavePlannerConfig(string json)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(_paths.GetDataRoot(), "planner_config.json");
|
||||||
|
File.WriteAllText(path, json);
|
||||||
|
ReloadPlannerConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDefaultsJson()
|
||||||
|
{
|
||||||
|
var path = Path.Combine(_paths.GetDataRoot(), "defaults.json");
|
||||||
|
if (!File.Exists(path))
|
||||||
|
throw new FileNotFoundException($"默认数据文件不存在: {path}");
|
||||||
|
return File.ReadAllText(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveDefaults(string json)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(_paths.GetDataRoot(), "defaults.json");
|
||||||
|
File.WriteAllText(path, json);
|
||||||
|
ReloadDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reload()
|
||||||
|
{
|
||||||
|
ReloadPlannerConfig();
|
||||||
|
ReloadDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReloadPlannerConfig()
|
||||||
|
{
|
||||||
|
var config = PlannerConfig.Load(_paths);
|
||||||
|
PlannerConfigReloaded?.Invoke(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReloadDefaults()
|
||||||
|
{
|
||||||
|
var defaults = DefaultData.Load(_paths);
|
||||||
|
ReseedDatabase(defaults);
|
||||||
|
DefaultsReloaded?.Invoke(defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReseedDatabase(DefaultData defaults)
|
||||||
|
{
|
||||||
|
foreach (var a in defaults.Ammunition) _db.InsertOrReplace(a);
|
||||||
|
foreach (var f in defaults.FireUnits) _db.InsertOrReplace(f);
|
||||||
|
foreach (var l in defaults.LaunchPlatforms) _db.InsertOrReplace(l);
|
||||||
|
foreach (var d in defaults.Drones) _db.InsertOrReplace(d);
|
||||||
|
foreach (var s in defaults.Sensors) _db.InsertOrReplace(s);
|
||||||
|
foreach (var e in defaults.Environments) _db.InsertOrReplace(e);
|
||||||
|
foreach (var f in defaults.Formations) _db.InsertOrReplace(f);
|
||||||
|
foreach (var r in defaults.Routes)
|
||||||
|
{
|
||||||
|
r.WaypointsJson = JsonSerializer.Serialize(r.Waypoints);
|
||||||
|
_db.InsertOrReplace(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/CounterDrone.Core/Services/IConfigService.cs
Normal file
23
src/CounterDrone.Core/Services/IConfigService.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using CounterDrone.Core.Algorithms;
|
||||||
|
|
||||||
|
namespace CounterDrone.Core.Services
|
||||||
|
{
|
||||||
|
/// <summary>配置服务 — 读写 defaults.json / planner_config.json,支持运行时重载</summary>
|
||||||
|
public interface IConfigService
|
||||||
|
{
|
||||||
|
/// <summary>读取 planner 配置 JSON 原文</summary>
|
||||||
|
string GetPlannerConfigJson();
|
||||||
|
|
||||||
|
/// <summary>保存 planner 配置 JSON 并立即重载</summary>
|
||||||
|
void SavePlannerConfig(string json);
|
||||||
|
|
||||||
|
/// <summary>读取 defaults.json 原文</summary>
|
||||||
|
string GetDefaultsJson();
|
||||||
|
|
||||||
|
/// <summary>保存 defaults.json 并重载种子数据到数据库(InsertOrReplace 覆盖同 Id 记录)</summary>
|
||||||
|
void SaveDefaults(string json);
|
||||||
|
|
||||||
|
/// <summary>重新从磁盘加载配置(前端直接改文件后调此方法通知后端刷新)</summary>
|
||||||
|
void Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -60,7 +60,10 @@ namespace CounterDrone.Core.Services
|
|||||||
/// <summary>返回所有枚举定义(供前端下拉框使用)</summary>
|
/// <summary>返回所有枚举定义(供前端下拉框使用)</summary>
|
||||||
EnumMetadata GetEnums();
|
EnumMetadata GetEnums();
|
||||||
|
|
||||||
/// <summary>生成防御推荐方案(配置阶段调用,仿真前基于已配置参数计算最佳/最危险抛撒参数)</summary>
|
/// <summary>生成防御推荐方案(配置阶段调用,仿真前基于已配置参数计算最佳抛撒参数)</summary>
|
||||||
DefenseRecommendation GetDefenseRecommendation(string scenarioId);
|
DefenseRecommendation GetDefenseRecommendation(string scenarioId);
|
||||||
|
|
||||||
|
/// <summary>替换 planner 实例(配置重载后由 ConfigService 触发)</summary>
|
||||||
|
void UpdatePlanner(Algorithms.IDefensePlanner planner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ namespace CounterDrone.Core.Services
|
|||||||
private readonly RoutePlanRepository _routeRepo;
|
private readonly RoutePlanRepository _routeRepo;
|
||||||
private readonly WaypointRepository _waypointRepo;
|
private readonly WaypointRepository _waypointRepo;
|
||||||
private readonly IPathProvider? _paths;
|
private readonly IPathProvider? _paths;
|
||||||
private readonly IDefensePlanner? _planner;
|
private IDefensePlanner? _planner;
|
||||||
|
|
||||||
public ScenarioService(
|
public ScenarioService(
|
||||||
ScenarioRepository scenarioRepo,
|
ScenarioRepository scenarioRepo,
|
||||||
@ -46,6 +46,9 @@ namespace CounterDrone.Core.Services
|
|||||||
_planner = planner;
|
_planner = planner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>替换 planner 实例(配置重载后由 ConfigService 触发)</summary>
|
||||||
|
public void UpdatePlanner(IDefensePlanner planner) => _planner = planner;
|
||||||
|
|
||||||
// ========== scenario CRUD ==========
|
// ========== scenario CRUD ==========
|
||||||
|
|
||||||
public Scenario CreateScenario(string name, string ScenarioNumber)
|
public Scenario CreateScenario(string name, string ScenarioNumber)
|
||||||
|
|||||||
@ -59,7 +59,7 @@ namespace CounterDrone.Core.Simulation
|
|||||||
private float _detectionTime;
|
private float _detectionTime;
|
||||||
private readonly List<SimEvent> _allEvents = new();
|
private readonly List<SimEvent> _allEvents = new();
|
||||||
private string _scenarioId = string.Empty;
|
private string _scenarioId = string.Empty;
|
||||||
private readonly IDefensePlanner _planner;
|
private IDefensePlanner _planner;
|
||||||
private int _entityCounter;
|
private int _entityCounter;
|
||||||
|
|
||||||
// ── GC 优化:预分配复用池 ──
|
// ── GC 优化:预分配复用池 ──
|
||||||
@ -79,6 +79,9 @@ namespace CounterDrone.Core.Simulation
|
|||||||
_planner = planner;
|
_planner = planner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>替换 planner 实例(配置重载后调用)</summary>
|
||||||
|
public void UpdatePlanner(IDefensePlanner planner) => _planner = planner;
|
||||||
|
|
||||||
public void SetFireSchedule(List<FireEvent> schedule)
|
public void SetFireSchedule(List<FireEvent> schedule)
|
||||||
{
|
{
|
||||||
_fireSchedule.Clear();
|
_fireSchedule.Clear();
|
||||||
|
|||||||
Binary file not shown.
@ -14,32 +14,45 @@ namespace CounterDrone.Unity
|
|||||||
private IScenarioService _service;
|
private IScenarioService _service;
|
||||||
private SQLite.SQLiteConnection _db;
|
private SQLite.SQLiteConnection _db;
|
||||||
private IDataService _dataService;
|
private IDataService _dataService;
|
||||||
|
private ConfigService _configService;
|
||||||
|
private IPathProvider _paths;
|
||||||
|
|
||||||
public IScenarioService Service => _service;
|
public IScenarioService Service => _service;
|
||||||
public IDataService DataService => _dataService;
|
public IDataService DataService => _dataService;
|
||||||
|
public IConfigService Config => _configService;
|
||||||
|
|
||||||
public void Awake()
|
public void Awake()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var paths = new UnityPathProvider();
|
_paths = new UnityPathProvider();
|
||||||
Debug.Log($"ScenarioManager.Awake: dbPath={paths.GetMainDbPath()}");
|
Debug.Log($"ScenarioManager.Awake: dbPath={_paths.GetMainDbPath()}");
|
||||||
_db = new DatabaseManager(paths).OpenMainDb();
|
_db = new DatabaseManager(_paths).OpenMainDb();
|
||||||
SqliteConnectionTracker.Track(_db);
|
SqliteConnectionTracker.Track(_db);
|
||||||
var planner = new DefensePlanner(
|
var planner = new DefensePlanner(
|
||||||
DefaultData.Load(paths).Ammunition,
|
DefaultData.Load(_paths).Ammunition,
|
||||||
PlannerConfig.Load(paths));
|
PlannerConfig.Load(_paths));
|
||||||
_service = new ScenarioService(
|
_service = new ScenarioService(
|
||||||
new ScenarioRepository(_db), new CombatSceneRepository(_db),
|
new ScenarioRepository(_db), new CombatSceneRepository(_db),
|
||||||
new ControlZoneRepository(_db), new ScenarioDroneRepository(_db),
|
new ControlZoneRepository(_db), new ScenarioDroneRepository(_db),
|
||||||
new ScenarioUnitRepository(_db), new CloudDispersalRepository(_db),
|
new ScenarioUnitRepository(_db), new CloudDispersalRepository(_db),
|
||||||
new RoutePlanRepository(_db), new WaypointRepository(_db),
|
new RoutePlanRepository(_db), new WaypointRepository(_db),
|
||||||
paths, planner);
|
_paths, planner);
|
||||||
_dataService = new DataService(
|
_dataService = new DataService(
|
||||||
new AmmunitionSpecRepository(_db), new FireUnitSpecRepository(_db),
|
new AmmunitionSpecRepository(_db), new FireUnitSpecRepository(_db),
|
||||||
new DroneSpecRepository(_db), new SensorSpecRepository(_db),
|
new DroneSpecRepository(_db), new SensorSpecRepository(_db),
|
||||||
new EnvironmentSpecRepository(_db), new FormationTemplateRepository(_db),
|
new EnvironmentSpecRepository(_db), new FormationTemplateRepository(_db),
|
||||||
new RouteTemplateRepository(_db));
|
new RouteTemplateRepository(_db));
|
||||||
|
|
||||||
|
// 配置服务:订阅重载事件,自动重建 planner
|
||||||
|
_configService = new ConfigService(_paths, _db);
|
||||||
|
_configService.PlannerConfigReloaded += cfg =>
|
||||||
|
{
|
||||||
|
var newPlanner = new DefensePlanner(DefaultData.Load(_paths).Ammunition, cfg);
|
||||||
|
_service.UpdatePlanner(newPlanner);
|
||||||
|
Debug.Log($"ScenarioManager: planner 配置已重载");
|
||||||
|
};
|
||||||
|
|
||||||
Debug.Log("ScenarioManager.Awake: OK");
|
Debug.Log("ScenarioManager.Awake: OK");
|
||||||
}
|
}
|
||||||
catch (System.Exception ex)
|
catch (System.Exception ex)
|
||||||
|
|||||||
@ -25,6 +25,7 @@ namespace CounterDrone.Unity
|
|||||||
private IPathProvider _paths;
|
private IPathProvider _paths;
|
||||||
private string _scenarioId;
|
private string _scenarioId;
|
||||||
private SQLite.SQLiteConnection _db;
|
private SQLite.SQLiteConnection _db;
|
||||||
|
private IDefensePlanner _planner;
|
||||||
|
|
||||||
private readonly Dictionary<string, GameObject> _entityVisuals = new();
|
private readonly Dictionary<string, GameObject> _entityVisuals = new();
|
||||||
private readonly Dictionary<string, GameObject> _clouds = new();
|
private readonly Dictionary<string, GameObject> _clouds = new();
|
||||||
@ -49,7 +50,16 @@ namespace CounterDrone.Unity
|
|||||||
new RoutePlanRepository(_db), new WaypointRepository(_db));
|
new RoutePlanRepository(_db), new WaypointRepository(_db));
|
||||||
|
|
||||||
_frameStore = new FrameDataStore(_paths);
|
_frameStore = new FrameDataStore(_paths);
|
||||||
_engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths, new DefensePlanner(DefaultData.Load(_paths).Ammunition, PlannerConfig.Load(_paths)));
|
_planner = new DefensePlanner(DefaultData.Load(_paths).Ammunition, PlannerConfig.Load(_paths));
|
||||||
|
_engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths, _planner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>配置重载后重建 planner(由 ConfigService 触发)</summary>
|
||||||
|
public void ReloadPlanner()
|
||||||
|
{
|
||||||
|
_planner = new DefensePlanner(DefaultData.Load(_paths).Ammunition, PlannerConfig.Load(_paths));
|
||||||
|
if (_engine != null)
|
||||||
|
_engine.UpdatePlanner(_planner);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadAndStart(string scenarioId)
|
public void LoadAndStart(string scenarioId)
|
||||||
|
|||||||
118
test/unit/CounterDrone.Core.Tests/ConfigServiceTests.cs
Normal file
118
test/unit/CounterDrone.Core.Tests/ConfigServiceTests.cs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using CounterDrone.Core;
|
||||||
|
using CounterDrone.Core.Algorithms;
|
||||||
|
using CounterDrone.Core.Services;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace CounterDrone.Core.Tests
|
||||||
|
{
|
||||||
|
public class ConfigServiceTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _testDir;
|
||||||
|
private readonly IPathProvider _paths;
|
||||||
|
private readonly ConfigService _config;
|
||||||
|
private readonly SQLite.SQLiteConnection _db;
|
||||||
|
|
||||||
|
public ConfigServiceTests()
|
||||||
|
{
|
||||||
|
_testDir = Path.Combine(Path.GetTempPath(), $"cd_cfg_{Guid.NewGuid():N}");
|
||||||
|
_paths = new TestPathProvider(_testDir);
|
||||||
|
var dbm = new DatabaseManager(_paths);
|
||||||
|
_db = dbm.OpenMainDb();
|
||||||
|
_config = new ConfigService(_paths, _db);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_db?.Close();
|
||||||
|
if (Directory.Exists(_testDir)) Directory.Delete(_testDir, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetPlannerConfigJson_ReturnsValidJson()
|
||||||
|
{
|
||||||
|
var json = _config.GetPlannerConfigJson();
|
||||||
|
var cfg = JsonSerializer.Deserialize<PlannerConfig>(json, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
});
|
||||||
|
Assert.NotNull(cfg);
|
||||||
|
Assert.True(cfg.CloudOverlapRatio >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SavePlannerConfig_PersistsAndReloads()
|
||||||
|
{
|
||||||
|
var original = _config.GetPlannerConfigJson();
|
||||||
|
var cfg = JsonSerializer.Deserialize<PlannerConfig>(original, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
})!;
|
||||||
|
cfg.CloudOverlapRatio = 0.3f;
|
||||||
|
var modifiedJson = JsonSerializer.Serialize(cfg, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
|
||||||
|
});
|
||||||
|
|
||||||
|
_config.SavePlannerConfig(modifiedJson);
|
||||||
|
|
||||||
|
var reloaded = _config.GetPlannerConfigJson();
|
||||||
|
var reloadedCfg = JsonSerializer.Deserialize<PlannerConfig>(reloaded, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
})!;
|
||||||
|
Assert.Equal(0.3f, reloadedCfg.CloudOverlapRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PlannerConfigReloaded_EventFires()
|
||||||
|
{
|
||||||
|
PlannerConfig? received = null;
|
||||||
|
_config.PlannerConfigReloaded += cfg => received = cfg;
|
||||||
|
|
||||||
|
var json = _config.GetPlannerConfigJson();
|
||||||
|
_config.SavePlannerConfig(json);
|
||||||
|
|
||||||
|
Assert.NotNull(received);
|
||||||
|
Assert.True(received.CloudOverlapRatio >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Reload_FromDiskRefreshesConfig()
|
||||||
|
{
|
||||||
|
// 直接改文件
|
||||||
|
var path = Path.Combine(_testDir, "planner_config.json");
|
||||||
|
var json = File.ReadAllText(path);
|
||||||
|
var cfg = JsonSerializer.Deserialize<PlannerConfig>(json, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
})!;
|
||||||
|
cfg.ExpansionFactor = 0.8f;
|
||||||
|
File.WriteAllText(path, JsonSerializer.Serialize(cfg, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() },
|
||||||
|
}));
|
||||||
|
|
||||||
|
PlannerConfig? received = null;
|
||||||
|
_config.PlannerConfigReloaded += cfg => received = cfg;
|
||||||
|
|
||||||
|
_config.Reload();
|
||||||
|
|
||||||
|
Assert.NotNull(received);
|
||||||
|
Assert.Equal(0.8f, received.ExpansionFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetDefaultsJson_ReturnsValidJson()
|
||||||
|
{
|
||||||
|
var json = _config.GetDefaultsJson();
|
||||||
|
Assert.Contains("ammunition", json.ToLower());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user