fix: Manager OnDestroy 关闭 SQLite 连接,修复 domain reload 崩溃

- 所有 Manager 在 Awake 保存 _db 引用,OnDestroy 调用 Close()
- ScenarioManager/ModelManager/ReportManager/GroupManager/SimulationRunner 全部修复
- 崩溃原因:domain reload 时 GC finalizer 线程清理 prepared statements,DB 未关闭导致 sqlite3_finalize 竞态
This commit is contained in:
tian 2026-06-13 09:35:53 +08:00
parent b957a5e2b4
commit 2bb8ba2e1e
7 changed files with 40 additions and 24 deletions

View File

@ -76,19 +76,22 @@ pwsh scripts/check_unity_build.ps1
This automatically rebuilds Core.dll, copies it to Unity Plugins, and compiles Unity scripts. Exits 0 if all pass.
## 7. Run Targeted Tests, Not Full Suite
## 7. Run Targeted Tests First
**Never run all tests unless explicitly asked.** Always use `--filter` to run only the tests relevant to what you changed.
**Always run the narrowest relevant test first.** If you just changed `DefensePlannerTests`, run `--filter 'FullyQualifiedName~DefensePlannerTests'`. If you changed `FullPipelineTests`, run that filter. Don't start with `dotnet test` on the whole project — it wastes time and buries the failures you're looking for.
Run full suite only after the targeted tests pass, to verify nothing else broke.
```bash
# Run a single test:
pwsh -Command "dotnet test test/unit/CounterDrone.Core.Tests/ --filter 'FullyQualifiedName~TestName'"
# Run a test class:
pwsh -Command "dotnet test test/unit/CounterDrone.Core.Tests/ --filter 'FullyQualifiedName~DefensePlannerTests'"
```
Running all 125+ tests wastes ~30 seconds every time and buries the failures you actually care about.
# Run a single test:
pwsh -Command "dotnet test test/unit/CounterDrone.Core.Tests/ --filter 'FullyQualifiedName~Scenario_AirBased'"
# Run full suite (only after targeted passes):
pwsh -Command "dotnet test test/unit/CounterDrone.Core.Tests/"
```
---

View File

@ -11,16 +11,19 @@ namespace CounterDrone.Unity
public class GroupManager : MonoBehaviour
{
private IGroupService _service;
private SQLite.SQLiteConnection _db;
public IGroupService Service => _service;
public void Awake()
{
var paths = new UnityPathProvider();
var db = new DatabaseManager(paths).OpenMainDb();
_service = new GroupService(new GroupRepository(db));
_db = new DatabaseManager(paths).OpenMainDb();
_service = new GroupService(new GroupRepository(_db));
}
void OnDestroy() => _db?.Close();
public Group Create(string name, GroupType type, string desc = "")
=> _service.CreateGroup(name, type, desc);

View File

@ -13,16 +13,19 @@ namespace CounterDrone.Unity
{
private IModelService _service;
private IPathProvider _paths;
private SQLite.SQLiteConnection _db;
public IModelService Service => _service;
public void Awake()
{
_paths = new UnityPathProvider();
var db = new DatabaseManager(_paths).OpenMainDb();
_service = new ModelService(new ModelRepository(db), _paths);
_db = new DatabaseManager(_paths).OpenMainDb();
_service = new ModelService(new ModelRepository(_db), _paths);
}
void OnDestroy() => _db?.Close();
public ModelInfo Import(string filePath, string name) => _service.ImportModel(filePath, name);
public void Delete(string id) => _service.DeleteModel(id);
public List<ModelInfo> GetAll() => _service.GetAllModels();

View File

@ -11,16 +11,19 @@ namespace CounterDrone.Unity
{
private IReportService _service;
private IPathProvider _paths;
private SQLite.SQLiteConnection _db;
public IReportService Service => _service;
public void Awake()
{
_paths = new UnityPathProvider();
var db = new DatabaseManager(_paths).OpenMainDb();
_service = new ReportService(db, _paths);
_db = new DatabaseManager(_paths).OpenMainDb();
_service = new ReportService(_db, _paths);
}
void OnDestroy() => _db?.Close();
public SimulationReport Generate(string taskId, TaskFullConfig cfg, List<SimEvent> events, string status, float duration)
=> _service.Generate(taskId, cfg, events, status, duration);

View File

@ -11,6 +11,7 @@ namespace CounterDrone.Unity
public class ScenarioManager : MonoBehaviour
{
private IScenarioService _service;
private SQLite.SQLiteConnection _db;
public IScenarioService Service => _service;
@ -20,12 +21,12 @@ namespace CounterDrone.Unity
{
var paths = new UnityPathProvider();
Debug.Log($"ScenarioManager.Awake: dbPath={paths.GetMainDbPath()}");
var db = new DatabaseManager(paths).OpenMainDb();
_db = new DatabaseManager(paths).OpenMainDb();
_service = new ScenarioService(
new SimTaskRepository(db), new CombatSceneRepository(db),
new ControlZoneRepository(db), new TargetConfigRepository(db),
new EquipmentDeploymentRepository(db), new CloudDispersalRepository(db),
new RoutePlanRepository(db), new WaypointRepository(db));
new SimTaskRepository(_db), new CombatSceneRepository(_db),
new ControlZoneRepository(_db), new TargetConfigRepository(_db),
new EquipmentDeploymentRepository(_db), new CloudDispersalRepository(_db),
new RoutePlanRepository(_db), new WaypointRepository(_db));
Debug.Log("ScenarioManager.Awake: OK");
}
catch (System.Exception ex)
@ -34,6 +35,8 @@ namespace CounterDrone.Unity
}
}
void OnDestroy() => _db?.Close();
public SimTask CreateTask(string name, string number) => _service.CreateTask(name, number);
public void DeleteTask(string id) => _service.DeleteTask(id);
public TaskFullConfig GetDetail(string id) => _service.GetTaskDetail(id);

View File

@ -26,6 +26,7 @@ namespace CounterDrone.Unity
private IScenarioService _scenario;
private IPathProvider _paths;
private string _taskId;
private SQLite.SQLiteConnection _db;
private readonly Dictionary<string, GameObject> _entityVisuals = new();
private readonly Dictionary<string, GameObject> _clouds = new();
@ -37,13 +38,13 @@ namespace CounterDrone.Unity
public void Awake()
{
_paths = new UnityPathProvider();
var db = new DatabaseManager(_paths).OpenMainDb();
_db = new DatabaseManager(_paths).OpenMainDb();
_scenario = new ScenarioService(
new SimTaskRepository(db), new CombatSceneRepository(db),
new ControlZoneRepository(db), new TargetConfigRepository(db),
new EquipmentDeploymentRepository(db), new CloudDispersalRepository(db),
new RoutePlanRepository(db), new WaypointRepository(db));
new SimTaskRepository(_db), new CombatSceneRepository(_db),
new ControlZoneRepository(_db), new TargetConfigRepository(_db),
new EquipmentDeploymentRepository(_db), new CloudDispersalRepository(_db),
new RoutePlanRepository(_db), new WaypointRepository(_db));
_frameStore = new FrameDataStore(_paths);
_engine = new SimulationEngine(_scenario, _frameStore, new DamageModelRouter(), _paths);
@ -148,7 +149,7 @@ namespace CounterDrone.Unity
}
public void Stop() => _engine?.Stop();
void OnDestroy() => Stop();
void OnDestroy() { Stop(); _db?.Close(); }
private void SpawnDroneVisual(DroneEntity drone)
{