refactor(engine-ambient): ViewerId/RealTimeNetworkAgent/BattleRecoveryInfo read ambient first

Step 4 of multi-instancing migration. Three additional per-battle statics
front-fronted by BattleAmbient.Current, each with a static fallback for
unwrapped callers. ViewerId's SavedataManager-persisting setter is preserved
on the fallback path; inside a scope, the setter is a no-op (the per-battle
perspective is fixed at scope entry).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-07 21:37:58 -04:00
parent 4e756a6c46
commit fe146fde50
9 changed files with 230 additions and 16 deletions

View File

@@ -0,0 +1,61 @@
Multi-instancing migration (Step 4): convert Cute.Certification.ViewerId — the per-battle
"who is the local seat" identity — to resolve through BattleAmbient.Current when a scope is
active, falling back to the legacy SavedataManager-backed static when not. The old `viewer_id`
backing field is renamed to `_viewerIdFallback` so unwrapped callers (real client, in-process
unit tests without a BattleAmbient scope) keep the original lazy-load-from-savedata + write-
through-savedata behavior bit-for-bit; scoped callers get a per-AsyncLocal id without writing
to savedata (design 2026-06-07-engine-multi-instancing, Task 4).
Inside a scope the setter is a NO-OP: ViewerId is an init-time identity (it's set at scope
entry via `new BattleAmbientContext { ViewerId = ... }`'s init-only property), and swallowing
the write avoids any in-battle setter from mutating sibling-battle perspective. The fallback
setter preserves the original sequence exactly: SetInt + Save + assign backing field.
In-file references (3 total) handled as follows:
- line 19 declaration → renamed to `_viewerIdFallback`
- line 41 ViewerId property → ambient-first getter; setter swallows when scoped
- line 147 InitializeFileds → resets the renamed `_viewerIdFallback`
External reflection follow-up (NOT in this patch — separate edit, same commit):
SVSim.BattleNode/Sessions/Engine/EngineGlobalInit.cs (~line 154) reads the private field
by name to seed the headless "who am I" perspective. Renamed there from "viewer_id" to
"_viewerIdFallback" to match.
--- Engine/Cute/Certification.cs (~line 19)
- private static int viewer_id;
+ private static int _viewerIdFallback;
--- Engine/Cute/Certification.cs (~lines 41-57)
public static int ViewerId
{
get
{
- if (viewer_id == 0)
+ var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
+ if (c != null) return c.ViewerId;
+ if (_viewerIdFallback == 0)
{
- viewer_id = Toolbox.SavedataManager.GetInt("VIEWER_ID");
+ _viewerIdFallback = Toolbox.SavedataManager.GetInt("VIEWER_ID");
}
- return viewer_id;
+ return _viewerIdFallback;
}
set
{
+ var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
+ if (c != null)
+ {
+ // Inside a scope, ViewerId is fixed at scope entry — swallow the write.
+ return;
+ }
Toolbox.SavedataManager.SetInt("VIEWER_ID", value);
Toolbox.SavedataManager.Save();
- viewer_id = value;
+ _viewerIdFallback = value;
}
}
--- Engine/Cute/Certification.cs (~line 147, InitializeFileds)
- viewer_id = 0;
+ _viewerIdFallback = 0;

View File

@@ -0,0 +1,34 @@
Multi-instancing migration (Step 4): convert Wizard.Data.BattleRecoveryInfo — the per-battle
mid-game recovery snapshot (replay/reconnect/late-join state, owned by RecoveryDataHandler /
RecoveryNetworkManagerBase / RecoveryController) — to resolve through BattleAmbient.Current
when a scope is active, falling back to a `_battleRecoveryInfoFallback` static when not.
The original was a plain auto-property `{ get; set; }`; converting to a manual property +
ambient-aware getter/setter preserves the public read/write surface byte-identical
(design 2026-06-07-engine-multi-instancing, Task 4).
This is the only of the three Step-4 conversions whose setter we DO route through ambient
(unlike ViewerId, where the in-scope setter is a no-op): recovery info is mutated during the
battle (new snapshots overwrite old ones as recovery checkpoints stream in), so scoped writes
must land on the AsyncLocal slot, not bleed into the process-wide fallback.
The existing line-348 reset inside `Data.Clear()` (`BattleRecoveryInfo = null;`) keeps working
unchanged because it goes through the public setter, which now correctly routes to whichever
slot the caller's scope (or lack thereof) points at.
In-file references (1 declaration; ~5 setter/getter call sites elsewhere in the file go through
the public property and need no edits) handled as follows:
- line 179 auto-property → manual ambient-first getter + ambient-routed setter
--- Engine/Wizard/Data.cs (~line 179)
- public static BattleRecoveryInfo BattleRecoveryInfo { get; set; }
+ private static BattleRecoveryInfo _battleRecoveryInfoFallback;
+ public static BattleRecoveryInfo BattleRecoveryInfo
+ {
+ get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.RecoveryInfo ?? _battleRecoveryInfoFallback;
+ set
+ {
+ var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
+ if (c != null) c.RecoveryInfo = value;
+ else _battleRecoveryInfoFallback = value;
+ }
+ }

View File

@@ -0,0 +1,51 @@
Multi-instancing migration (Step 4): convert Wizard.ToolboxGame.RealTimeNetworkAgent — the
per-battle handle to the live PvP/AI network agent (RealTimeNetworkAgent owns the Socket.IO
connection, OnEmit pipeline, and ack bookkeeping for one battle) — to resolve through
BattleAmbient.Current when a scope is active, falling back to a renamed
`_realTimeNetworkAgentFallback` static when not. The original was an auto-property with a
private setter, mutated only via SetRealTimeNetworkBattle / DestroyNetworkAgent; converting
to a manual property + ambient-aware mutators keeps the public surface byte-identical
(design 2026-06-07-engine-multi-instancing, Task 4).
DestroyNetworkAgent preserves the original Unity DestroyImmediate(.gameObject) call exactly,
just reading the "current" agent through the same ambient-first resolution and clearing the
slot it came from. Object.DestroyImmediate resolves via the file's existing `using UnityEngine;`
(no qualifier change needed — matches the file's existing pattern at line 36/79 that calls
`GameObject.Find` unqualified).
In-file references (3 sites) handled as follows:
- line 28 auto-property → renamed backing + manual ambient-first getter
- line 57 SetRealTimeNetworkBattle → writes through Current?.NetworkAgent or fallback
- line 62 DestroyNetworkAgent → reads current via ambient-first; clears the same slot
--- Engine/Wizard/ToolboxGame.cs (~line 28)
- public static RealTimeNetworkAgent RealTimeNetworkAgent { get; private set; }
+ private static RealTimeNetworkAgent _realTimeNetworkAgentFallback;
+ public static RealTimeNetworkAgent RealTimeNetworkAgent
+ {
+ get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.NetworkAgent ?? _realTimeNetworkAgentFallback;
+ }
--- Engine/Wizard/ToolboxGame.cs (~lines 57-69)
public static void SetRealTimeNetworkBattle(RealTimeNetworkAgent agent)
{
- RealTimeNetworkAgent = agent;
+ var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
+ if (c != null) c.NetworkAgent = agent;
+ else _realTimeNetworkAgentFallback = agent;
}
public static void DestroyNetworkAgent()
{
- if (RealTimeNetworkAgent != null)
+ var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
+ var current = c?.NetworkAgent ?? _realTimeNetworkAgentFallback;
+ if (current != null)
{
- Object.DestroyImmediate(RealTimeNetworkAgent.gameObject);
- RealTimeNetworkAgent = null;
+ Object.DestroyImmediate(current.gameObject);
+ if (c != null) c.NetworkAgent = null;
+ else _realTimeNetworkAgentFallback = null;
}
}