From a19dced5055604cf6fca1ab3faa8048e60bf7f5d Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sat, 13 Jun 2026 12:30:48 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20SqliteConnectionTracker=20=E2=80=94=20As?= =?UTF-8?q?semblyReloadEvents.beforeAssemblyReload=20=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E5=85=B3=E9=97=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 静态追踪所有 SQLiteConnection,domain reload 前 Dispose 全部 - 替换所有 GC.Collect+WaitForPendingFinalizers 为 Tracker 模式 - DatabaseManager、ScenarioManager、中、SimulationRunner 等全部 Track/Untrack --- .../Assets/Scripts/Managers/GroupManager.cs | 3 +- .../Scripts/Managers/ManagerVerification.cs | 4 +- .../Assets/Scripts/Managers/ModelManager.cs | 3 +- .../Assets/Scripts/Managers/ReportManager.cs | 3 +- .../Scripts/Managers/ScenarioManager.cs | 3 +- .../Scripts/Managers/SimulationBootstrap.cs | 4 +- .../Scripts/Managers/SimulationRunner.cs | 3 +- .../Managers/SqliteConnectionTracker.cs | 45 +++++++++++++++++++ 8 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 src/Unity/Assets/Scripts/Managers/SqliteConnectionTracker.cs diff --git a/src/Unity/Assets/Scripts/Managers/GroupManager.cs b/src/Unity/Assets/Scripts/Managers/GroupManager.cs index c2ea6ff..a8ce477 100644 --- a/src/Unity/Assets/Scripts/Managers/GroupManager.cs +++ b/src/Unity/Assets/Scripts/Managers/GroupManager.cs @@ -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); diff --git a/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs b/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs index 09bef2d..4319b2c 100644 --- a/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs +++ b/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs @@ -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().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}"); diff --git a/src/Unity/Assets/Scripts/Managers/ModelManager.cs b/src/Unity/Assets/Scripts/Managers/ModelManager.cs index 941af42..de6078d 100644 --- a/src/Unity/Assets/Scripts/Managers/ModelManager.cs +++ b/src/Unity/Assets/Scripts/Managers/ModelManager.cs @@ -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); diff --git a/src/Unity/Assets/Scripts/Managers/ReportManager.cs b/src/Unity/Assets/Scripts/Managers/ReportManager.cs index a784f20..03bb0d3 100644 --- a/src/Unity/Assets/Scripts/Managers/ReportManager.cs +++ b/src/Unity/Assets/Scripts/Managers/ReportManager.cs @@ -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 events, string status, float duration) => _service.Generate(taskId, cfg, events, status, duration); diff --git a/src/Unity/Assets/Scripts/Managers/ScenarioManager.cs b/src/Unity/Assets/Scripts/Managers/ScenarioManager.cs index f6a5f32..be2bedb 100644 --- a/src/Unity/Assets/Scripts/Managers/ScenarioManager.cs +++ b/src/Unity/Assets/Scripts/Managers/ScenarioManager.cs @@ -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); diff --git a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs index c098281..0985d8c 100644 --- a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs +++ b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs @@ -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}"); diff --git a/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs b/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs index c156455..9ebe475 100644 --- a/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs +++ b/src/Unity/Assets/Scripts/Managers/SimulationRunner.cs @@ -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) diff --git a/src/Unity/Assets/Scripts/Managers/SqliteConnectionTracker.cs b/src/Unity/Assets/Scripts/Managers/SqliteConnectionTracker.cs new file mode 100644 index 0000000..68a6fb6 --- /dev/null +++ b/src/Unity/Assets/Scripts/Managers/SqliteConnectionTracker.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using SQLite; +using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace CounterDrone.Unity +{ + /// 追踪所有打开的 SQLite 连接,在 domain reload 前安全关闭 + public static class SqliteConnectionTracker + { + private static readonly List _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(); + } + } + } +}