113 lines
7.2 KiB
C#
113 lines
7.2 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 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
|
|
}
|
|
}
|