using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using HotUpdate.Gameplay; using Newtonsoft.Json; using TMPro; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using Object = UnityEngine.Object; public static class VerifyPrototypePanelBindings { static readonly Type PanelType = typeof(PrototypeFlow).GetNestedType("Panel", BindingFlags.NonPublic); static object Bind(GameObject go) => Activator.CreateInstance(PanelType, new object[] { go }); static void Check(bool condition, string message) { if (!condition) throw new Exception(message); } public static string All() { var scene = EditorSceneManager.NewPreviewScene(); var root = new GameObject("PanelBindingRegression", typeof(RectTransform), typeof(Canvas)); SceneManager.MoveGameObjectToScene(root, scene); root.GetComponent().renderMode = RenderMode.ScreenSpaceOverlay; var failures = new List(); int passed = 0, generated = 0; var paths = AssetDatabase.FindAssets("t:Prefab", new[] { "Assets/Bundles/UI/Prefab" }) .Select(AssetDatabase.GUIDToAssetPath) .Where(p => System.IO.Path.GetFileName(p).StartsWith("UI_Prototype", StringComparison.Ordinal)).ToArray(); try { foreach (string path in paths) { var go = Object.Instantiate(AssetDatabase.LoadAssetAtPath(path), root.transform, false); try { var texts = go.GetComponentsInChildren(true); Check(texts.Length >= 2, path + ": expected multiple labels"); foreach (var text in texts) text.ForceMeshUpdate(true, true); // Use TMP's own factory: two labels can render with the same material, // creating identically named internal nodes, as on the mini game. var font = texts[0].font; var material = texts[0].fontSharedMaterial; var reference = new MaterialReference(1, font, null, material, 0); TMP_SubMeshUI.AddSubTextObject(texts[0], reference); TMP_SubMeshUI.AddSubTextObject(texts[1], reference); generated += go.GetComponentsInChildren(true).Length; object panel = Bind(go); var nodes = (Dictionary)PanelType.GetField("_nodes", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(panel); var labels = (Dictionary)PanelType.GetField("_texts", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(panel); Check(nodes.Values.All(n => n.GetComponent() == null), path + ": internal renderer leaked into bindings"); foreach (var node in go.GetComponentsInChildren(true).Where(n => n.GetComponent() == null)) Check((Transform)PanelType.GetMethod("Node").Invoke(panel, new object[] { node.name }) == node, path + ": missing node " + node.name); foreach (var text in texts.Where(t => t.name.StartsWith("Txt_", StringComparison.Ordinal))) { Check(labels[text.name] == text, path + ": text lookup failed " + text.name); if (text.GetComponentInParent() != null) continue; string value = text.text + " "; PanelType.GetMethod("Text").Invoke(panel, new object[] { text.name.Substring(4), value }); Check(text.text == value, path + ": text binding failed " + text.name); } foreach (var button in go.GetComponentsInChildren