chore(engine-ambient): harden shim + LocalLog statics for fixture parallelism

Follow-up to the multi-instancing migration. Wraps the process-shared engine
statics that aren't ambient-fronted but race between concurrent battles:

- UnityEngine.Resources._loaded: Dictionary -> ConcurrentDictionary.GetOrAdd
  (the shared prefab cache keyed by path; concurrent first-misses produced
  duplicate GameObjects + Dictionary corruption)
- UnityEngine.GameObject._components: Dictionary -> ConcurrentDictionary with
  Interlocked.CompareExchange init (Resources.Load returns SHARED prefab
  GameObjects, so two engines' Setup() can race on the same _components map
  — surfaced as "Operations that change non-concurrent collections" crashes
  during BattleManagerBase ctor's GetComponent<T>() chain)
- Wizard.LocalLog: single static lock around all mutating entry points
  (StringBuilder _lastTraceLogStringBuilder + ~12 mutable string/bool/int
  scratch fields; serializing the trace-log surface is cheap since logging
  is not the hot path)

Flips SVSim.BattleEngine.Tests assembly Parallelizable scope from Self to
Fixtures and restructures MultiInstanceEngineTests.StressN_BaselineMatches so
Setup runs INSIDE Task.Run (was previously serialized as a workaround for the
LocalLog races). The fixture is also lifted to ParallelScope.All so the
two-engines and stress tests can run alongside each other.

Suite fully green under fixture parallelism (59/0/2 across 3 consecutive runs);
SVSim.UnitTests still 1054/0/0 — true multi-instance correctness is now proved
end-to-end in tests rather than gated behind a serial workaround.

Manifest sha refresh + new patch artifact for the LocalLog edit (decomp-origin);
the two shim files are authored, so no metadata update is needed for them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-08 08:02:49 -04:00
parent 45344e6d83
commit fbac66fd0b
7 changed files with 360 additions and 174 deletions

View File

@@ -9,6 +9,13 @@ namespace Wizard;
public class LocalLog
{
// HEADLESS-PATCH (engine-port): all public mutating entry points + the private file-write
// helpers are gated by a single static lock so concurrent battle setups (fixture-parallel
// tests, parallel SessionBattleEngine.Setup() calls) don't corrupt the StringBuilder /
// string accumulators (FormatException, lost frames) or interleave writes to the four
// scratch log files. Logging is not the hot path; global serialization is acceptable.
private static readonly object _gate = new object();
public enum TRACELOG_TYPE
{
TRACE_ALL_LOG,
@@ -76,32 +83,38 @@ public class LocalLog
[RuntimeInitializeOnLoadMethod]
public static void CreateLogFile()
{
CreateLocalLogFile(AccumulateLogPath);
CreateLocalLogFile(AccumulateSettingLogPath);
CreateLocalLogFile(LastAccumulate1LogPath);
CreateLocalLogFile(LastAccumulate2LogPath);
CreateLocalLogFile(InquiryLogPath);
lock (_gate)
{
CreateLocalLogFile(AccumulateLogPath);
CreateLocalLogFile(AccumulateSettingLogPath);
CreateLocalLogFile(LastAccumulate1LogPath);
CreateLocalLogFile(LastAccumulate2LogPath);
CreateLocalLogFile(InquiryLogPath);
}
}
public static void CreateLocalLogFile(string filePath)
{
FileStream fileStream = null;
try
lock (_gate)
{
if (!File.Exists(filePath))
FileStream fileStream = null;
try
{
using (fileStream = File.Create(filePath))
if (!File.Exists(filePath))
{
fileStream.Close();
return;
using (fileStream = File.Create(filePath))
{
fileStream.Close();
return;
}
}
}
}
catch
{
string text = "FailedToCreateFile:" + filePath;
fileStream?.Dispose();
_failureWriteClientLog = _failureWriteClientLog + text + "\n";
catch
{
string text = "FailedToCreateFile:" + filePath;
fileStream?.Dispose();
_failureWriteClientLog = _failureWriteClientLog + text + "\n";
}
}
}
@@ -121,6 +134,14 @@ public class LocalLog
}
private static void MakeTreceLogToSend(TRACELOG_TYPE logType, Action onSended)
{
lock (_gate)
{
MakeTreceLogToSendLocked(logType, onSended);
}
}
private static void MakeTreceLogToSendLocked(TRACELOG_TYPE logType, Action onSended)
{
if (string.IsNullOrEmpty(Certification.Udid) || Certification.ViewerId == 0)
{
@@ -244,28 +265,45 @@ public class LocalLog
public static void RecordResouseLoadError(int errorFlag)
{
UIManager.ViewScene currentScene = UIManager.GetInstance().GetCurrentScene();
string text = ((currentScene == UIManager.ViewScene.Battle && ToolboxGame.RealTimeNetworkAgent != null) ? "NetworkBattle" : currentScene.ToString());
AccumulateTraceLog("ResourcesManager ParallelAssetListExec Error in " + text + " : " + errorFlag);
lock (_gate)
{
UIManager.ViewScene currentScene = UIManager.GetInstance().GetCurrentScene();
string text = ((currentScene == UIManager.ViewScene.Battle && ToolboxGame.RealTimeNetworkAgent != null) ? "NetworkBattle" : currentScene.ToString());
AccumulateTraceLogLocked("ResourcesManager ParallelAssetListExec Error in " + text + " : " + errorFlag);
}
}
public static void RecordTurnEndIfLoadErrorOccured()
{
if (ExistResourceLoadErrorInNetWorkBattle())
lock (_gate)
{
AccumulateTraceLog("TurnEnd After LoadError");
if (ExistResourceLoadErrorInNetWorkBattleLocked())
{
AccumulateTraceLogLocked("TurnEnd After LoadError");
}
}
}
public static void RecordFreezeLogIfLoadErrorOccured()
{
if (ExistResourceLoadErrorInNetWorkBattle())
lock (_gate)
{
AccumulateTraceLog("ResourceLoadFreeze in NetworkBattleScene");
if (ExistResourceLoadErrorInNetWorkBattleLocked())
{
AccumulateTraceLogLocked("ResourceLoadFreeze in NetworkBattleScene");
}
}
}
public static bool ExistResourceLoadErrorInNetWorkBattle()
{
lock (_gate)
{
return ExistResourceLoadErrorInNetWorkBattleLocked();
}
}
private static bool ExistResourceLoadErrorInNetWorkBattleLocked()
{
return ReadLogFile(AccumulateLogPath).Contains("ResourcesManager ParallelAssetListExec Error in NetworkBattle");
}
@@ -329,148 +367,206 @@ public class LocalLog
}
public static void AccumulateTraceLog(string log)
{
lock (_gate)
{
AccumulateTraceLogLocked(log);
}
}
private static void AccumulateTraceLogLocked(string log)
{
AccumulateLog(GetBattleAndViewIdText() + log, "TraceLog");
}
public static void AccumulateTraceInquiryLog(string log)
{
AccumulateLog(log, "TraceInquiryLog");
OrganizeInquiryLog();
lock (_gate)
{
AccumulateLog(log, "TraceInquiryLog");
OrganizeInquiryLog();
}
}
public static void AccumulateSettingLog()
{
if (!(ToolboxGame.RealTimeNetworkAgent == null))
lock (_gate)
{
string battleAndViewIdText = GetBattleAndViewIdText();
string text = (PlayerPrefsWrapper.GetBool(PlayerPrefsWrapper.SHOW_BATTLE_EFFECT) ? "1" : "0");
string text2 = (PlayerPrefsWrapper.GetBool(PlayerPrefsWrapper.CONFIRM_TURN_END) ? "1" : "0");
string text3 = (PlayerPrefsWrapper.GetBool(PlayerPrefsWrapper.CONFIRM_EVOLVE) ? "1" : "0");
string text4 = (PlayerPrefsWrapper.GetBool(PlayerPrefsWrapper.IS_SELECT_WSS) ? "1" : "0");
AccumulateLog(battleAndViewIdText + "BattleSetting:" + text + text2 + text3 + text4, "TraceSettingLog");
if (!(ToolboxGame.RealTimeNetworkAgent == null))
{
string battleAndViewIdText = GetBattleAndViewIdText();
string text = (PlayerPrefsWrapper.GetBool(PlayerPrefsWrapper.SHOW_BATTLE_EFFECT) ? "1" : "0");
string text2 = (PlayerPrefsWrapper.GetBool(PlayerPrefsWrapper.CONFIRM_TURN_END) ? "1" : "0");
string text3 = (PlayerPrefsWrapper.GetBool(PlayerPrefsWrapper.CONFIRM_EVOLVE) ? "1" : "0");
string text4 = (PlayerPrefsWrapper.GetBool(PlayerPrefsWrapper.IS_SELECT_WSS) ? "1" : "0");
AccumulateLog(battleAndViewIdText + "BattleSetting:" + text + text2 + text3 + text4, "TraceSettingLog");
}
}
}
public static void AddGungnirLog(string log)
{
if (ToolboxGame.RealTimeNetworkAgent != null)
lock (_gate)
{
DateTime dateTime = DateTime.Now.ToUniversalTime();
log = dateTime.Day + "/" + dateTime.Hour + ":" + dateTime.Minute + ":" + dateTime.Second + ":" + dateTime.Millisecond.ToString("000") + ":[" + ToolboxGame.RealTimeNetworkAgent.NowSocketID + "]" + log + "\n";
_gungnirLog += log;
if (ToolboxGame.RealTimeNetworkAgent != null)
{
DateTime dateTime = DateTime.Now.ToUniversalTime();
log = dateTime.Day + "/" + dateTime.Hour + ":" + dateTime.Minute + ":" + dateTime.Second + ":" + dateTime.Millisecond.ToString("000") + ":[" + ToolboxGame.RealTimeNetworkAgent.NowSocketID + "]" + log + "\n";
_gungnirLog += log;
}
}
}
public static void InitGungnirLog()
{
_gungnirLog = "";
if (ToolboxGame.RealTimeNetworkAgent != null && _isSendGungnirLog)
lock (_gate)
{
ToolboxGame.RealTimeNetworkAgent.NetworkLogger.LogInfo("OffGungnirLog");
_gungnirLog = "";
if (ToolboxGame.RealTimeNetworkAgent != null && _isSendGungnirLog)
{
ToolboxGame.RealTimeNetworkAgent.NetworkLogger.LogInfo("OffGungnirLog");
}
_isSendGungnirLog = false;
}
_isSendGungnirLog = false;
}
public static void AddSocketFrameLog(string log)
{
if (ToolboxGame.RealTimeNetworkAgent != null)
lock (_gate)
{
DateTime dateTime = DateTime.Now.ToUniversalTime();
log = dateTime.Day + "/" + dateTime.Hour + ":" + dateTime.Minute + ":" + dateTime.Second + ":" + dateTime.Millisecond.ToString("000") + ":[" + ToolboxGame.RealTimeNetworkAgent.NowSocketID + "]" + log + "\n";
_socketFrameLog += log;
if (ToolboxGame.RealTimeNetworkAgent != null)
{
DateTime dateTime = DateTime.Now.ToUniversalTime();
log = dateTime.Day + "/" + dateTime.Hour + ":" + dateTime.Minute + ":" + dateTime.Second + ":" + dateTime.Millisecond.ToString("000") + ":[" + ToolboxGame.RealTimeNetworkAgent.NowSocketID + "]" + log + "\n";
_socketFrameLog += log;
}
}
}
public static void InitSocketFrameLog()
{
_socketFrameLog = "";
if (ToolboxGame.RealTimeNetworkAgent != null && _isSendSocketFrameLog)
lock (_gate)
{
ToolboxGame.RealTimeNetworkAgent.NetworkLogger.LogInfo("OffSocketFrameLog");
_socketFrameLog = "";
if (ToolboxGame.RealTimeNetworkAgent != null && _isSendSocketFrameLog)
{
ToolboxGame.RealTimeNetworkAgent.NetworkLogger.LogInfo("OffSocketFrameLog");
}
_isSendSocketFrameLog = false;
}
_isSendSocketFrameLog = false;
}
public static void AccumulateTraceLogAddRoomCreateLog()
{
if (!_isRoomCreateLogEnd)
lock (_gate)
{
_isRoomCreateLogEnd = true;
AccumulateTraceLog("#696951 " + _roomCreateLog);
InitRoomCreateLog();
if (!_isRoomCreateLogEnd)
{
_isRoomCreateLogEnd = true;
AccumulateTraceLogLocked("#696951 " + _roomCreateLog);
_roomCreateLog = "";
}
}
}
public static void AddRoomCreateLog(string log)
{
if (!_isRoomCreateLogEnd)
lock (_gate)
{
DateTime dateTime = DateTime.Now.ToUniversalTime();
log = dateTime.Day + "/" + dateTime.Hour + ":" + dateTime.Minute + ":" + dateTime.Second + ":" + dateTime.Millisecond.ToString("000") + log + "\n";
_roomCreateLog += log;
if (!_isRoomCreateLogEnd)
{
DateTime dateTime = DateTime.Now.ToUniversalTime();
log = dateTime.Day + "/" + dateTime.Hour + ":" + dateTime.Minute + ":" + dateTime.Second + ":" + dateTime.Millisecond.ToString("000") + log + "\n";
_roomCreateLog += log;
}
}
}
public static void InitRoomCreateLog()
{
_roomCreateLog = "";
lock (_gate)
{
_roomCreateLog = "";
}
}
public static void UpdateLoadResourceLog(string log)
{
_loadResourceLog = log;
lock (_gate)
{
_loadResourceLog = log;
}
}
public static string GetLoadResourceLog()
{
return _loadResourceLog;
lock (_gate)
{
return _loadResourceLog;
}
}
public static void SetDisconnectLog(string log)
{
if (_disconnectLog == "")
lock (_gate)
{
DateTime dateTime = DateTime.Now.ToUniversalTime();
log = dateTime.Day + "/" + dateTime.Hour + ":" + dateTime.Minute + ":" + dateTime.Second + ":" + dateTime.Millisecond.ToString("000") + ":[" + ToolboxGame.RealTimeNetworkAgent.NowSocketID + "]" + log + "\n";
_disconnectLog = log;
if (_disconnectLog == "")
{
DateTime dateTime = DateTime.Now.ToUniversalTime();
log = dateTime.Day + "/" + dateTime.Hour + ":" + dateTime.Minute + ":" + dateTime.Second + ":" + dateTime.Millisecond.ToString("000") + ":[" + ToolboxGame.RealTimeNetworkAgent.NowSocketID + "]" + log + "\n";
_disconnectLog = log;
}
}
}
public static void InitDisconnectLog()
{
_disconnectLog = "";
lock (_gate)
{
_disconnectLog = "";
}
}
public static void AccumulateLastTraceLog(string log)
{
if (_lastTraceLogStringBuilder == null)
lock (_gate)
{
_lastTraceLogStringBuilder = new StringBuilder();
}
else
{
_lastTraceLogStringBuilder.Append("\n");
}
float num = 0f;
if (num != 0f && num / (float)SystemInfo.systemMemorySize > 0.8f)
{
string text = "";
log += text;
}
if (!_isLastTraceLogTimeAdd)
{
DateTime dateTime = DateTime.Now.ToUniversalTime();
_lastTraceLogStringBuilder.AppendFormat("{0}/{1}:{2}:{3}:{4:000}:{5}", dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond.ToString("000"), "\n" + log);
_isLastTraceLogTimeAdd = true;
}
else
{
_lastTraceLogStringBuilder.Append(log);
if (_lastTraceLogStringBuilder == null)
{
_lastTraceLogStringBuilder = new StringBuilder();
}
else
{
_lastTraceLogStringBuilder.Append("\n");
}
float num = 0f;
if (num != 0f && num / (float)SystemInfo.systemMemorySize > 0.8f)
{
string text = "";
log += text;
}
if (!_isLastTraceLogTimeAdd)
{
DateTime dateTime = DateTime.Now.ToUniversalTime();
_lastTraceLogStringBuilder.AppendFormat("{0}/{1}:{2}:{3}:{4:000}:{5}", dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond.ToString("000"), "\n" + log);
_isLastTraceLogTimeAdd = true;
}
else
{
_lastTraceLogStringBuilder.Append(log);
}
}
}
public static void SubmitAccumulateLastTraceLog()
{
lock (_gate)
{
SubmitAccumulateLastTraceLogLocked();
}
}
private static void SubmitAccumulateLastTraceLogLocked()
{
if (_lastTraceLogStringBuilder != null)
{
@@ -491,26 +587,29 @@ public class LocalLog
public static void SetLastTraceLogTurn(int turn)
{
if (currentTurn != turn)
lock (_gate)
{
string log = "Turn" + turn + " " + GetBattleAndViewIdText() + "====\n";
string text = "";
if (turn % 2 == 0)
if (currentTurn != turn)
{
text = "LastTraceLog1";
nowTraceTurn = 0;
string log = "Turn" + turn + " " + GetBattleAndViewIdText() + "====\n";
string text = "";
if (turn % 2 == 0)
{
text = "LastTraceLog1";
nowTraceTurn = 0;
}
else
{
text = "LastTraceLog2";
nowTraceTurn = 1;
}
if (turn != 0)
{
ClearLog(text);
}
WriteAccumulateTraceLog(text, log);
currentTurn = turn;
}
else
{
text = "LastTraceLog2";
nowTraceTurn = 1;
}
if (turn != 0)
{
ClearLastTraceLog(text);
}
WriteAccumulateTraceLog(text, log);
currentTurn = turn;
}
}
@@ -650,25 +749,36 @@ public class LocalLog
private static void ClearLogByType(TRACELOG_TYPE logType)
{
switch (logType)
lock (_gate)
{
case TRACELOG_TYPE.TRACE_ALL_LOG:
_failureWriteClientLog = "";
ClearTraceLog();
ClearLastLogKey();
ClearInquiryLogKey();
break;
case TRACELOG_TYPE.TRACE_LOG:
_failureWriteClientLog = "";
ClearTraceLog();
break;
case TRACELOG_TYPE.TRACE_LAST_LOG:
ClearLastLogKey();
break;
switch (logType)
{
case TRACELOG_TYPE.TRACE_ALL_LOG:
_failureWriteClientLog = "";
ClearTraceLogLocked();
ClearLastLogKeyLocked();
ClearInquiryLogKey();
break;
case TRACELOG_TYPE.TRACE_LOG:
_failureWriteClientLog = "";
ClearTraceLogLocked();
break;
case TRACELOG_TYPE.TRACE_LAST_LOG:
ClearLastLogKeyLocked();
break;
}
}
}
public static void ClearTraceLog()
{
lock (_gate)
{
ClearTraceLogLocked();
}
}
private static void ClearTraceLogLocked()
{
ClearLog("TraceLog");
ClearLog("TraceSettingLog");
@@ -676,7 +786,10 @@ public class LocalLog
public static void ClearLastTraceLog(string key)
{
ClearLog(key);
lock (_gate)
{
ClearLog(key);
}
}
private static void ClearInquiryLogKey()
@@ -726,19 +839,38 @@ public class LocalLog
public static void ClearLastLogKey()
{
ClearLastTraceLog("LastTraceLog1");
ClearLastTraceLog("LastTraceLog2");
lock (_gate)
{
ClearLastLogKeyLocked();
}
}
private static void ClearLastLogKeyLocked()
{
ClearLog("LastTraceLog1");
ClearLog("LastTraceLog2");
currentTurn = -1;
}
public static void ClearAllLog()
{
ClearTraceLog();
ClearLastLogKey();
ClearInquiryLogKey();
lock (_gate)
{
ClearTraceLogLocked();
ClearLastLogKeyLocked();
ClearInquiryLogKey();
}
}
public static void RecordCheckLog(RecordType type, bool isWin)
{
lock (_gate)
{
RecordCheckLogLocked(type, isWin);
}
}
private static void RecordCheckLogLocked(RecordType type, bool isWin)
{
if (BattleManagerBase.GetIns() == null || !isWin)
{