WL #957 스킬 테스트 씬 부트 실패 수정 — 지난 Play 의 파괴된 싱글턴 static 정리(WLStaleStaticReset)
- 실측: 테스트 씬 Play → stage=pc-fail · MissingReferenceException 'MoneyForm' destroyed @ MoneyForm.Set ← TopMoney.Set ← ServerData 아이템 지급(if TopMoney.isIns) · TestMap Play → LoadingUI.End_Loading(go 파괴) ← MobControlMgrList.Start · 편집 모드 TopMoney · LoadingUI · NewGameUI 의 isIns True + Ins 파괴됨 - 원인: #947-2 가 Enter Play Mode 도메인 리로드를 껐는데 원본 싱글턴(MonoBehaviourSingletonTemplate<T> · LoadingUI 등)은 static Ins/isIns 를 지우지 않아 다른 씬 Play 뒤 다음 Play 가 파괴된 객체를 건드림 - 수정: Play 시작(SubsystemRegistration)마다 Assembly-CSharp static 필드 중 파괴된 UnityEngine.Object 참조 → null · List 의 파괴된 항목 제거 · 같은 형식 Ins 가 비면 isIns false(에디터 전용 · 살아 있는 객체 무접촉) · 하네스 예외 호출 위치 4 → 8줄 - 검증: 파괴된 상태 재현 뒤 테스트 씬 ready 0.6 s(정리 참조 121 · isIns 16 · 9 ms) · TestMap 의 LoadingUI 오류 재발 0(TestMap 단독은 AddrResourceMgr 가 Title 부팅에서만 생겨 LoadMapMgr.Awake NRE = 새 편집기 첫 Play 와 같은 상태) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b318bc0243
commit
02f16661d0
|
|
@ -123,7 +123,7 @@ namespace WL.SkillTest
|
|||
Lap("awake");
|
||||
}
|
||||
|
||||
// 단계마다 try — 예외는 상태 파일(stage=<단계>-fail · 예외 종류 · 메시지 · 호출 위치 4줄)에 남긴다. 코루틴이 조용히 죽으면 콘솔 스팸에 묻혀 찾기 어려웠다(#947 1차 Play · PC.Set NRE 가 Freeze 1000건 아래)
|
||||
// 단계마다 try — 예외는 상태 파일(stage=<단계>-fail · 예외 종류 · 메시지 · 호출 위치 8줄)에 남긴다. 코루틴이 조용히 죽으면 콘솔 스팸에 묻혀 찾기 어려웠다(#947 1차 Play · PC.Set NRE 가 Freeze 1000건 아래)
|
||||
IEnumerator Start()
|
||||
{
|
||||
var cfg = Cfg;
|
||||
|
|
@ -195,11 +195,11 @@ namespace WL.SkillTest
|
|||
catch (System.Exception ex) { Fail(stage + "-fail", ex.GetType().Name + ": " + ex.Message + " @ " + Where(ex)); return false; }
|
||||
}
|
||||
|
||||
/// <summary>예외 호출 위치 4줄 — 인자 목록·IL 오프셋·절대 경로를 지워 「at 클래스.메서드 파일.cs:줄」 만 남긴다.</summary>
|
||||
/// <summary>예외 호출 위치 8줄 — 인자 목록·IL 오프셋·절대 경로를 지워 「at 클래스.메서드 파일.cs:줄」 만 남긴다(#957: 4줄이면 Unity 내부 3줄에 묻혀 호출자가 안 보였다).</summary>
|
||||
static string Where(System.Exception ex)
|
||||
{
|
||||
var lines = (ex.StackTrace ?? "").Split('\n'); var sb = new System.Text.StringBuilder();
|
||||
for (int i = 0; i < lines.Length && i < 4; i++)
|
||||
for (int i = 0; i < lines.Length && i < 8; i++)
|
||||
sb.Append(System.Text.RegularExpressions.Regex.Replace(lines[i].Trim(), @"\s*\(.*?\)\s*\[0x[0-9a-fA-F]+\]\s*in\s*.*[\\/]", " ")).Append(" | ");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WLStaleStaticReset.cs — Play 시작마다 「지난 Play 에서 파괴된 객체」를 가리키는 static 참조를 비운다 (PD #957 · 2026-10-21)
|
||||
//
|
||||
// PD 원문(#957): 「스킬 테스트맵을 실행할 때 에러 때문에 진행이 안되는거 같아 왜 그런지 실측해보고 수정해줘.」
|
||||
// ■ 실측(2026-10-21): 스킬 테스트 씬 Play → stage=pc-fail · MissingReferenceException 'MoneyForm' destroyed @ MoneyForm.Set
|
||||
// ← TopMoney.Set ← ServerData 아이템 지급(`if (TopMoney.isIns) TopMoney.Ins.Set()`) = PC 생성(스타터 킷 지급) 단계에서 멈춤.
|
||||
// TestMap Play → LoadingUI.End_Loading(go 파괴) ← MobControlMgrList.Start(`if (LoadingUI.isIns)`).
|
||||
// 편집 모드 실측: TopMoney · LoadingUI · NewGameUI 의 isIns = True 인데 Ins = 파괴됨 · MoneyForm 목록 2개 모두 파괴됨.
|
||||
// ■ 원인: #947-2 가 Enter Play Mode 의 도메인 리로드를 껐다(EditorSettings · 테스트 씬 빠른 재시작). 원본 싱글턴(MonoBehaviourSingletonTemplate<T> ·
|
||||
// LoadingUI 등)은 Awake 에서 static Ins · isIns 를 세우고 지우지 않는다 → 앞 Play(다른 씬)의 객체가 Play 종료 때 파괴돼도 static 이 남아
|
||||
// 다음 Play 의 `if (X.isIns) X.Ins…` 가 파괴된 객체를 건드린다(도메인 리로드가 켜져 있으면 static 이 초기화돼 생기지 않던 오류).
|
||||
// ■ 처리: Play 시작(SubsystemRegistration = 씬 Awake 전)에 Assembly-CSharp 형식(닫힌 제네릭 부모 포함)의 static 필드 중
|
||||
// ① UnityEngine.Object 형인데 값이 파괴된 것 → null ② List<UnityEngine.Object 계열> 의 파괴된 항목 제거
|
||||
// ③ 같은 형식의 static bool isIns 는 그 형식의 Ins 가 비었으면 false.
|
||||
// 살아 있는 객체(편집 모드 객체 · 에셋)는 건드리지 않는다 · 에디터 전용(빌드는 매번 새로 시작해 해당 없음) · readonly/const 제외 ·
|
||||
// 실패는 조용히 건너뜀(LogError 금지 — 원본 ErrorLogHook 팝업) · 결과 = LastLog(정리한 것이 있으면 콘솔 Log 1줄).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WL.Start
|
||||
{
|
||||
public static class WLStaleStaticReset
|
||||
{
|
||||
public static int Cleared, ListItems, Flags, TypesScanned;
|
||||
public static float LastMs;
|
||||
public static string LastLog = "";
|
||||
|
||||
#if UNITY_EDITOR
|
||||
const BindingFlags SF = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void Run()
|
||||
{
|
||||
Cleared = ListItems = Flags = TypesScanned = 0;
|
||||
string err = "";
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
var names = new List<string>();
|
||||
try
|
||||
{
|
||||
var seen = new HashSet<Type>();
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
if (asm.GetName().Name != "Assembly-CSharp") continue;
|
||||
Type[] types;
|
||||
try { types = asm.GetTypes(); } catch (ReflectionTypeLoadException e) { types = e.Types; }
|
||||
foreach (var t0 in types)
|
||||
for (var t = t0; t != null && t.Assembly == asm; t = t.BaseType) // 닫힌 제네릭 부모(MonoBehaviourSingletonTemplate<TopMoney>)의 static 은 형식마다 따로 있다
|
||||
{
|
||||
if (t.ContainsGenericParameters || !seen.Add(t)) break; // 이미 본 형식이면 그 부모 사슬도 이미 봤다
|
||||
Clean(t, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { err = " · 실패 " + ex.Message; }
|
||||
LastMs = (float)sw.Elapsed.TotalMilliseconds;
|
||||
LastLog = "형식 " + TypesScanned + " · 참조 " + Cleared + " · 목록 항목 " + ListItems + " · isIns " + Flags + " · " + LastMs.ToString("F0") + " ms"
|
||||
+ (names.Count > 0 ? " · " + string.Join(", ", names.GetRange(0, Mathf.Min(12, names.Count))) + (names.Count > 12 ? " 외 " + (names.Count - 12) : "") : "") + err;
|
||||
if (Cleared + ListItems + Flags > 0) Debug.Log("[WL957] 지난 Play 에서 파괴된 static 참조 정리 — " + LastLog);
|
||||
}
|
||||
|
||||
static void Clean(Type t, List<string> names)
|
||||
{
|
||||
FieldInfo[] fs;
|
||||
try { fs = t.GetFields(SF); } catch { return; }
|
||||
if (fs.Length == 0) return;
|
||||
TypesScanned++;
|
||||
FieldInfo isIns = null; bool hasIns = false, insGone = false;
|
||||
foreach (var f in fs)
|
||||
{
|
||||
if (f.IsLiteral || f.IsInitOnly) continue;
|
||||
var ft = f.FieldType;
|
||||
try
|
||||
{
|
||||
if (ft == typeof(bool)) { if (f.Name == "isIns") isIns = f; continue; }
|
||||
if (typeof(UnityEngine.Object).IsAssignableFrom(ft))
|
||||
{
|
||||
var v = f.GetValue(null) as UnityEngine.Object;
|
||||
bool gone = (object)v != null && v == null; // 참조는 남았는데 Unity 객체는 파괴됨
|
||||
if (gone) { f.SetValue(null, null); Cleared++; names.Add(Name(t) + "." + f.Name); }
|
||||
if (f.Name == "Ins") { hasIns = true; insGone = gone || (object)v == null; }
|
||||
continue;
|
||||
}
|
||||
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(List<>) && typeof(UnityEngine.Object).IsAssignableFrom(ft.GetGenericArguments()[0]))
|
||||
{
|
||||
var list = f.GetValue(null) as IList; if (list == null) continue;
|
||||
int before = ListItems;
|
||||
for (int i = list.Count - 1; i >= 0; i--) { var o = list[i] as UnityEngine.Object; if ((object)o != null && o == null) { list.RemoveAt(i); ListItems++; } }
|
||||
if (ListItems > before) names.Add(Name(t) + "." + f.Name + "[" + (ListItems - before) + "]");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
if (isIns != null && hasIns && insGone)
|
||||
{
|
||||
try { if ((bool)isIns.GetValue(null)) { isIns.SetValue(null, false); Flags++; names.Add(Name(t) + ".isIns"); } } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>닫힌 제네릭은 「MonoBehaviourSingletonTemplate<TopMoney>」 꼴로.</summary>
|
||||
static string Name(Type t)
|
||||
{
|
||||
if (!t.IsGenericType) return t.Name;
|
||||
var n = t.Name; int k = n.IndexOf('`'); if (k > 0) n = n.Substring(0, k);
|
||||
return n + "<" + string.Join(",", Array.ConvertAll(t.GetGenericArguments(), a => a.Name)) + ">";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 95e45dad8e3d8f844bbc338d67656dbf
|
||||
Loading…
Reference in New Issue