feat(engine-ambient): delete static fallbacks; add MultiInstanceEngineTests
Step 8 (final) of multi-instancing migration. All per-battle statics now require a BattleAmbient scope — unwrapped writes throw InvalidOperationException (fail-fast forcing function). MultiInstanceEngineTests proves correctness: two parallel battles resolve independently, N=4/8/16 stress matches sequential baseline, GameMgr.GetIns throws without scope. Migration complete. EngineSessionGate gone. Suite fully green. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -410,26 +410,14 @@ public class BattleManagerBase
|
||||
|
||||
private NetworkTouchControl _networkTouchControl;
|
||||
|
||||
private static BattleManagerBase _mainFallback;
|
||||
|
||||
private static bool _isRandomDrawFallback = false;
|
||||
public static bool IsRandomDraw {
|
||||
get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.IsRandomDraw ?? _isRandomDrawFallback;
|
||||
set {
|
||||
var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
if (c != null) c.IsRandomDraw = value;
|
||||
else _isRandomDrawFallback = value;
|
||||
}
|
||||
get => SVSim.BattleEngine.Ambient.BattleAmbient.Require().IsRandomDraw;
|
||||
set => SVSim.BattleEngine.Ambient.BattleAmbient.Require().IsRandomDraw = value;
|
||||
}
|
||||
|
||||
private static bool _isForecastFallback = false;
|
||||
public static bool IsForecast {
|
||||
get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.IsForecast ?? _isForecastFallback;
|
||||
set {
|
||||
var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
if (c != null) c.IsForecast = value;
|
||||
else _isForecastFallback = value;
|
||||
}
|
||||
get => SVSim.BattleEngine.Ambient.BattleAmbient.Require().IsForecast;
|
||||
set => SVSim.BattleEngine.Ambient.BattleAmbient.Require().IsForecast = value;
|
||||
}
|
||||
|
||||
public BattleLifeTimeSharedObject BattleLifeTimeSharedObject;
|
||||
@@ -724,12 +712,15 @@ public class BattleManagerBase
|
||||
|
||||
public static BattleManagerBase GetIns()
|
||||
{
|
||||
return SVSim.BattleEngine.Ambient.BattleAmbient.Current?.Mgr ?? _mainFallback;
|
||||
// Soft read: returns null when no scope is active, preserving engine call sites that pattern
|
||||
// `GetIns()?.Foo ?? default` (or null-tolerant successors). Inside a scope, returns the
|
||||
// scope's Mgr (which may itself be null mid-Setup — see SessionBattleEngine.SetupInternal,
|
||||
// which publishes Mgr before WireMulliganPhase).
|
||||
return SVSim.BattleEngine.Ambient.BattleAmbient.Current?.Mgr;
|
||||
}
|
||||
|
||||
protected BattleManagerBase(IBattleMgrContentsCreator contentsCreator)
|
||||
{
|
||||
_mainFallback = this;
|
||||
BattleLifeTimeSharedObject = new BattleLifeTimeSharedObject();
|
||||
PublishedSkillList = new List<SkillBase>();
|
||||
_contentsCreator = contentsCreator;
|
||||
@@ -1488,7 +1479,6 @@ public class BattleManagerBase
|
||||
GameMgr.GetIns().GetPrefabMgr().DisposeAllClonedObject();
|
||||
GameMgr.GetIns().GetGameObjMgr().DisposeUIGameObj();
|
||||
GameMgr.GetIns().GetPrefabMgr().AllUnLoad();
|
||||
_mainFallback = null;
|
||||
}
|
||||
|
||||
private void DisposeBattleGameObj_DestroyImmediate(GameObject obj)
|
||||
|
||||
@@ -16,8 +16,6 @@ public class Certification : MonoBehaviour
|
||||
|
||||
private static string udid;
|
||||
|
||||
private static int _viewerIdFallback;
|
||||
|
||||
private static int short_udid;
|
||||
|
||||
private static string sessionId;
|
||||
@@ -40,28 +38,13 @@ public class Certification : MonoBehaviour
|
||||
|
||||
public static int ViewerId
|
||||
{
|
||||
get
|
||||
{
|
||||
var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
if (c != null) return c.ViewerId;
|
||||
if (_viewerIdFallback == 0)
|
||||
{
|
||||
_viewerIdFallback = Toolbox.SavedataManager.GetInt("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();
|
||||
_viewerIdFallback = value;
|
||||
}
|
||||
// Post-Task-8: strictly ambient. The historical SavedataManager-backed lazy decode was the
|
||||
// client process's single-viewer-id source; in the headless multi-instance world the viewer
|
||||
// id MUST come from the per-session ambient context. Setter is a no-op (BattleAmbientContext
|
||||
// .ViewerId is `init`-only — fixed at scope entry per design — and the historical caller
|
||||
// (SavedataManager.SetInt + Save) is dead in the server world).
|
||||
get => SVSim.BattleEngine.Ambient.BattleAmbient.Require().ViewerId;
|
||||
set { /* ambient ViewerId is init-only; SavedataManager path is dead headless */ }
|
||||
}
|
||||
|
||||
public static int ShortUdid
|
||||
@@ -152,7 +135,6 @@ public class Certification : MonoBehaviour
|
||||
{
|
||||
sessionId = null;
|
||||
udid = null;
|
||||
_viewerIdFallback = 0;
|
||||
short_udid = 0;
|
||||
Toolbox.SavedataManager.SetInt("VIEWER_ID", 0);
|
||||
Toolbox.SavedataManager.SetInt("SHORT_UDID", 0);
|
||||
|
||||
@@ -176,16 +176,16 @@ public static class Data
|
||||
|
||||
public static ReplayDetailInfo ReplayBattleInfo { 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;
|
||||
}
|
||||
// Soft read: returns null when no scope is active. The MulliganMgrBase.StartDeal call site
|
||||
// reads this with a null-tolerant ??=-style pattern, so a null degrade is the historical
|
||||
// fallback. Inside a scope, returns the per-session RecoveryInfo (SessionBattleEngine
|
||||
// pre-seeds an uninitialized BattleRecoveryInfo on its ctx field initializer).
|
||||
get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.RecoveryInfo;
|
||||
// Strict setter: writes must land on the per-session ctx. No historical production caller
|
||||
// writes this outside a scope; an unwrapped write now fails fast (forcing function).
|
||||
set => SVSim.BattleEngine.Ambient.BattleAmbient.Require().RecoveryInfo = value;
|
||||
}
|
||||
|
||||
public static VoteData VoteInfo { get; set; }
|
||||
|
||||
@@ -25,10 +25,12 @@ public static class ToolboxGame
|
||||
|
||||
private static Transform _gameTransform = null;
|
||||
|
||||
private static RealTimeNetworkAgent _realTimeNetworkAgentFallback;
|
||||
public static RealTimeNetworkAgent RealTimeNetworkAgent
|
||||
{
|
||||
get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.NetworkAgent ?? _realTimeNetworkAgentFallback;
|
||||
// Soft read: returns null when no scope is active, mirroring GetIns. Engine code reads this
|
||||
// via `ToolboxGame.RealTimeNetworkAgent?.Foo`-style patterns (network-send paths gated on a
|
||||
// non-null agent), so a null on the unwrapped path is the correct degrade — not a throw.
|
||||
get => SVSim.BattleEngine.Ambient.BattleAmbient.Current?.NetworkAgent;
|
||||
}
|
||||
|
||||
public static Transform GameTransform
|
||||
@@ -60,20 +62,19 @@ public static class ToolboxGame
|
||||
|
||||
public static void SetRealTimeNetworkBattle(RealTimeNetworkAgent agent)
|
||||
{
|
||||
var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
if (c != null) c.NetworkAgent = agent;
|
||||
else _realTimeNetworkAgentFallback = agent;
|
||||
// Strict: must be inside a scope to set the per-session agent. Forcing-function — any
|
||||
// historical unwrapped caller (no production callsite remains) now fails fast.
|
||||
SVSim.BattleEngine.Ambient.BattleAmbient.Require().NetworkAgent = agent;
|
||||
}
|
||||
|
||||
public static void DestroyNetworkAgent()
|
||||
{
|
||||
var c = SVSim.BattleEngine.Ambient.BattleAmbient.Current;
|
||||
var current = c?.NetworkAgent ?? _realTimeNetworkAgentFallback;
|
||||
var current = c?.NetworkAgent;
|
||||
if (current != null)
|
||||
{
|
||||
Object.DestroyImmediate(current.gameObject);
|
||||
if (c != null) c.NetworkAgent = null;
|
||||
else _realTimeNetworkAgentFallback = null;
|
||||
c.NetworkAgent = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user