using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace AlicizaX { [CustomEditor(typeof(GameObjectPoolManager))] public sealed class GameObjectPoolEditor : UnityEditor.Editor { private readonly Dictionary _foldoutState = new Dictionary(StringComparer.Ordinal); public override void OnInspectorGUI() { serializedObject.Update(); DrawDefaultInspector(); serializedObject.ApplyModifiedProperties(); var poolManager = (GameObjectPoolManager)target; if (!Application.isPlaying) { EditorGUILayout.HelpBox("Enter Play Mode to inspect the runtime pool.", MessageType.Info); return; } if (!poolManager.showDetailedInfo) { return; } EditorGUILayout.Space(); DrawRuntimeState(poolManager); } public override bool RequiresConstantRepaint() { var poolManager = target as GameObjectPoolManager; return poolManager != null && Application.isPlaying && poolManager.showDetailedInfo; } private void DrawRuntimeState(GameObjectPoolManager poolManager) { GameObjectPoolSummarySnapshot summary = poolManager.GetDebugSummary(); DrawSummary(summary); if (summary.WaitingForBootstrap) { EditorGUILayout.HelpBox("Waiting for YooAssets bootstrap before pool catalog build.", MessageType.Info); return; } if (!summary.IsReady) { EditorGUILayout.HelpBox("Pool manager is not ready.", MessageType.Warning); return; } List snapshots = poolManager.GetDebugSnapshots(); if (snapshots.Count == 0) { EditorGUILayout.HelpBox("No pooled runtime instances exist yet.", MessageType.Info); return; } for (int i = 0; i < snapshots.Count; i++) { DrawSnapshot(snapshots[i]); } } private static void DrawSummary(in GameObjectPoolSummarySnapshot summary) { EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("Runtime Summary", EditorStyles.boldLabel); EditorGUILayout.LabelField("Ready", summary.IsReady ? "Yes" : "No"); EditorGUILayout.LabelField("Waiting Bootstrap", summary.WaitingForBootstrap ? "Yes" : "No"); EditorGUILayout.LabelField("Pools", summary.PoolCount.ToString()); EditorGUILayout.LabelField("Loaded Prefabs", summary.LoadedPrefabCount.ToString()); EditorGUILayout.LabelField("Instances", summary.TotalInstanceCount.ToString()); EditorGUILayout.LabelField("Active", summary.ActiveInstanceCount.ToString()); EditorGUILayout.LabelField("Inactive", summary.InactiveInstanceCount.ToString()); EditorGUILayout.LabelField("Pending Maintenance", summary.PendingMaintenanceCount.ToString()); EditorGUILayout.EndVertical(); EditorGUILayout.Space(); } private void DrawSnapshot(GameObjectPoolSnapshot snapshot) { if (snapshot == null) { return; } string entryLabel = string.IsNullOrWhiteSpace(snapshot.entryName) ? snapshot.assetPath : snapshot.entryName; string foldoutKey = string.Concat(entryLabel, "|", snapshot.assetPath); if (!_foldoutState.ContainsKey(foldoutKey)) { _foldoutState[foldoutKey] = false; } string foldoutLabel = string.Format( "{0} [{1}/{2}] Hit:{3}/{4}", entryLabel, snapshot.activeCount, snapshot.totalCount, snapshot.hitCount, snapshot.acquireCount); EditorGUILayout.BeginVertical("box"); _foldoutState[foldoutKey] = EditorGUILayout.Foldout(_foldoutState[foldoutKey], foldoutLabel, true); if (_foldoutState[foldoutKey]) { EditorGUILayout.LabelField("Rule", entryLabel); EditorGUILayout.LabelField("Asset Path", snapshot.assetPath); EditorGUILayout.LabelField("Loader", snapshot.loaderType.ToString()); EditorGUILayout.LabelField("Retain Target", snapshot.retainTarget.ToString()); EditorGUILayout.LabelField("Soft Capacity", snapshot.softCapacity.ToString()); EditorGUILayout.LabelField("Capacity", snapshot.hardCapacity.ToString()); EditorGUILayout.LabelField("Runtime Capacity", snapshot.runtimeHardCapacity.ToString()); EditorGUILayout.LabelField("Inactive", snapshot.inactiveCount.ToString()); EditorGUILayout.LabelField("Prefab Loaded", snapshot.prefabLoaded ? "Yes" : "No"); EditorGUILayout.LabelField("Prefab Cold", FormatSeconds(snapshot.prefabIdleDuration)); EditorGUILayout.LabelField("Wake Count", snapshot.prefabWakeCount.ToString()); EditorGUILayout.LabelField("Wake Gap", snapshot.prefabWakeGap < 0f ? "N/A" : FormatSeconds(snapshot.prefabWakeGap)); EditorGUILayout.LabelField("Unload Delay", snapshot.prefabUnloadDelay < 0f ? "N/A" : FormatSeconds(snapshot.prefabUnloadDelay)); EditorGUILayout.LabelField("Next Maintenance", snapshot.nextMaintenanceIn < 0f ? "None" : FormatSeconds(snapshot.nextMaintenanceIn)); EditorGUILayout.Space(); EditorGUILayout.LabelField("Acquire", snapshot.acquireCount.ToString()); EditorGUILayout.LabelField("Release", snapshot.releaseCount.ToString()); EditorGUILayout.LabelField("Hit", snapshot.hitCount.ToString()); EditorGUILayout.LabelField("Miss", snapshot.missCount.ToString()); EditorGUILayout.LabelField("Expand", snapshot.expandCount.ToString()); EditorGUILayout.LabelField("Destroy", snapshot.destroyCount.ToString()); EditorGUILayout.LabelField("Peak Active", snapshot.peakActive.ToString()); EditorGUILayout.LabelField("Peak Active Short", snapshot.peakActiveShort.ToString()); EditorGUILayout.LabelField("Peak Active Long", snapshot.peakActiveLong.ToString()); if (snapshot.instances.Count > 0) { EditorGUILayout.Space(); EditorGUILayout.LabelField("Instances", EditorStyles.boldLabel); for (int i = 0; i < snapshot.instances.Count; i++) { DrawInstance(snapshot.instances[i]); } } } EditorGUILayout.EndVertical(); EditorGUILayout.Space(); } private static void DrawInstance(GameObjectPoolInstanceSnapshot snapshot) { if (snapshot == null) { return; } EditorGUILayout.BeginHorizontal("box"); EditorGUILayout.BeginVertical(); EditorGUILayout.LabelField(snapshot.instanceName, EditorStyles.boldLabel); EditorGUILayout.LabelField("State", snapshot.isActive ? "Active" : "Inactive"); EditorGUILayout.LabelField("Life", FormatSeconds(snapshot.lifeDuration)); if (!snapshot.isActive) { EditorGUILayout.LabelField("Idle", FormatSeconds(snapshot.idleDuration)); } EditorGUILayout.EndVertical(); EditorGUILayout.ObjectField(snapshot.gameObject, typeof(GameObject), true, GUILayout.Width(140f)); EditorGUILayout.EndHorizontal(); } private static string FormatSeconds(float seconds) { return seconds.ToString("F2") + "s"; } } }