fix: SqliteConnectionTracker — AssemblyReloadEvents.beforeAssemblyReload 安全关闭

- 静态追踪所有 SQLiteConnection,domain reload 前 Dispose 全部
- 替换所有 GC.Collect+WaitForPendingFinalizers 为 Tracker 模式
- DatabaseManager、ScenarioManager、中、SimulationRunner 等全部 Track/Untrack
This commit is contained in:
tian 2026-06-13 12:30:48 +08:00
parent 5c23e92587
commit a19dced505
8 changed files with 61 additions and 7 deletions

View File

@ -19,10 +19,11 @@ namespace CounterDrone.Unity
{
var paths = new UnityPathProvider();
_db = new DatabaseManager(paths).OpenMainDb();
SqliteConnectionTracker.Track(_db);
_service = new GroupService(new GroupRepository(_db));
}
void OnDisable() { _db?.Dispose(); _db = null; GC.Collect(); GC.WaitForPendingFinalizers(); }
void OnDisable() { SqliteConnectionTracker.Untrack(_db); _db?.Dispose(); _db = null; }
public Group Create(string name, GroupType type, string desc = "")
=> _service.CreateGroup(name, type, desc);

View File

@ -49,6 +49,7 @@ namespace CounterDrone.Unity
Debug.Log("3. Scenario configured");
var db = new DatabaseManager(paths).OpenMainDb();
SqliteConnectionTracker.Track(db);
foreach (var a in DefaultAmmunition.GetAll()) db.Insert(a);
var ammoCatalog = db.Table<AmmunitionSpec>().ToList();
@ -93,7 +94,8 @@ namespace CounterDrone.Unity
ReleaseAltitude = u.ReleaseAltitude > 0 ? u.ReleaseAltitude : null,
});
scenarioMgr.SaveDeployment(taskId, equips);
db.Close();
SqliteConnectionTracker.Untrack(db);
db.Dispose();
var plan = result.Best;
Debug.Log($"4. Plan: {plan.ThreatsEngaged} threats engaged, {plan.MergedSchedule.Count} rounds, prob={plan.OverallProbability:P0}");

View File

@ -21,10 +21,11 @@ namespace CounterDrone.Unity
{
_paths = new UnityPathProvider();
_db = new DatabaseManager(_paths).OpenMainDb();
SqliteConnectionTracker.Track(_db);
_service = new ModelService(new ModelRepository(_db), _paths);
}
void OnDisable() { _db?.Dispose(); _db = null; GC.Collect(); GC.WaitForPendingFinalizers(); }
void OnDisable() { SqliteConnectionTracker.Untrack(_db); _db?.Dispose(); _db = null; }
public ModelInfo Import(string filePath, string name) => _service.ImportModel(filePath, name);
public void Delete(string id) => _service.DeleteModel(id);

View File

@ -19,10 +19,11 @@ namespace CounterDrone.Unity
{
_paths = new UnityPathProvider();
_db = new DatabaseManager(_paths).OpenMainDb();
SqliteConnectionTracker.Track(_db);
_service = new ReportService(_db, _paths);
}
void OnDisable() { _db?.Dispose(); _db = null; GC.Collect(); GC.WaitForPendingFinalizers(); }
void OnDisable() { SqliteConnectionTracker.Untrack(_db); _db?.Dispose(); _db = null; }
public SimulationReport Generate(string taskId, TaskFullConfig cfg, List<SimEvent> events, string status, float duration)
=> _service.Generate(taskId, cfg, events, status, duration);

View File

@ -22,6 +22,7 @@ namespace CounterDrone.Unity
var paths = new UnityPathProvider();
Debug.Log($"ScenarioManager.Awake: dbPath={paths.GetMainDbPath()}");
_db = new DatabaseManager(paths).OpenMainDb();
SqliteConnectionTracker.Track(_db);
_service = new ScenarioService(
new SimTaskRepository(_db), new CombatSceneRepository(_db),
new ControlZoneRepository(_db), new TargetConfigRepository(_db),
@ -35,7 +36,7 @@ namespace CounterDrone.Unity
}
}
void OnDisable() { _db?.Dispose(); _db = null; GC.Collect(); GC.WaitForPendingFinalizers(); }
void OnDisable() { SqliteConnectionTracker.Untrack(_db); _db?.Dispose(); _db = null; }
public SimTask CreateTask(string name, string number) => _service.CreateTask(name, number);
public void DeleteTask(string id) => _service.DeleteTask(id);

View File

@ -33,6 +33,7 @@ namespace CounterDrone.Unity
var dbm = new DatabaseManager(paths);
var db = dbm.OpenMainDb();
SqliteConnectionTracker.Track(db);
foreach (var a in DefaultAmmunition.GetAll()) db.Insert(a);
// 创建想定
@ -109,7 +110,8 @@ namespace CounterDrone.Unity
});
scenario.SaveDeployment(taskId, equips);
db.Close();
SqliteConnectionTracker.Untrack(db);
db.Dispose();
var plan = result.Best;
Debug.Log($"规划方案: {plan.ThreatsEngaged} 威胁被分配, {plan.MergedSchedule.Count} 发, 概率 {plan.OverallProbability:P0}");

View File

@ -39,6 +39,7 @@ namespace CounterDrone.Unity
{
_paths = new UnityPathProvider();
_db = new DatabaseManager(_paths).OpenMainDb();
SqliteConnectionTracker.Track(_db);
_scenario = new ScenarioService(
new SimTaskRepository(_db), new CombatSceneRepository(_db),
@ -149,7 +150,7 @@ namespace CounterDrone.Unity
}
public void Stop() => _engine?.Stop();
void OnDisable() { _db?.Dispose(); _db = null; GC.Collect(); GC.WaitForPendingFinalizers(); }
void OnDisable() { SqliteConnectionTracker.Untrack(_db); _db?.Dispose(); _db = null; }
void OnDestroy() => Stop();
private void SpawnDroneVisual(DroneEntity drone)

View File

@ -0,0 +1,45 @@
using System.Collections.Generic;
using SQLite;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace CounterDrone.Unity
{
/// <summary>追踪所有打开的 SQLite 连接,在 domain reload 前安全关闭</summary>
public static class SqliteConnectionTracker
{
private static readonly List<SQLiteConnection> _connections = new();
static SqliteConnectionTracker()
{
#if UNITY_EDITOR
AssemblyReloadEvents.beforeAssemblyReload += DisposeAll;
#endif
}
public static void Track(SQLiteConnection db)
{
lock (_connections) { _connections.Add(db); }
}
public static void Untrack(SQLiteConnection db)
{
lock (_connections) { _connections.Remove(db); }
}
private static void DisposeAll()
{
lock (_connections)
{
foreach (var db in _connections)
{
try { db.Dispose(); }
catch { /* absorb */ }
}
_connections.Clear();
}
}
}
}