using AlicizaX.Editor; using AlicizaX.Timer.Runtime; using UnityEditor; using UnityEngine; namespace AlicizaX.Timer.Editor { [CustomEditor(typeof(TimerComponent))] internal sealed class TimerComponentInspector : GameFrameworkInspector { private const double UPDATE_INTERVAL = 0.001d; private const int MAX_DISPLAY_COUNT = 20; private TimerDebugInfo[] _timerBuffer; #if UNITY_EDITOR private TimerDebugInfo[] _leakBuffer; #endif private double _lastUpdateTime; private int _cachedActiveCount; private int _cachedPoolCapacity; private int _cachedPeakActiveCount; private int _cachedFreeCount; private string _cachedUsageText; public override void OnInspectorGUI() { base.OnInspectorGUI(); serializedObject.Update(); serializedObject.ApplyModifiedProperties(); DrawRuntimeDebugInfo(); if (EditorApplication.isPlaying) { double currentTime = EditorApplication.timeSinceStartup; if (currentTime - _lastUpdateTime >= UPDATE_INTERVAL) { _lastUpdateTime = currentTime; Repaint(); } } } private void DrawRuntimeDebugInfo() { if (!EditorApplication.isPlaying) { EditorGUILayout.HelpBox("Available during runtime only.", MessageType.Info); return; } if (!AppServices.TryGet(out ITimerService timerService)) { EditorGUILayout.HelpBox("Timer service is not initialized.", MessageType.Info); return; } if (timerService is not ITimerServiceDebugView debugView) { return; } debugView.GetStatistics(out _cachedActiveCount, out _cachedPoolCapacity, out _cachedPeakActiveCount, out _cachedFreeCount); _cachedUsageText = _cachedPoolCapacity > 0 ? Utility.Text.Format("{0:F1}%", (float)_cachedActiveCount / _cachedPoolCapacity * 100f) : "0.0%"; EditorGUILayout.Space(); EditorGUILayout.LabelField("Runtime Debug", EditorStyles.boldLabel); EditorGUILayout.LabelField("Active Timers", _cachedActiveCount.ToString()); EditorGUILayout.LabelField("Pool Capacity", _cachedPoolCapacity.ToString()); EditorGUILayout.LabelField("Peak Active Count", _cachedPeakActiveCount.ToString()); EditorGUILayout.LabelField("Free Slots", _cachedFreeCount.ToString()); EditorGUILayout.LabelField("Pool Usage", _cachedUsageText); DrawTimerList(debugView, _cachedActiveCount); #if UNITY_EDITOR DrawLeakDetection(debugView, _cachedActiveCount); #endif } private void DrawTimerList(ITimerServiceDebugView debugView, int activeCount) { EditorGUILayout.Space(); EditorGUILayout.LabelField("Active Timers", EditorStyles.boldLabel); if (activeCount <= 0) { EditorGUILayout.LabelField("No active timers."); return; } EnsureTimerBuffer(activeCount); int timerCount = debugView.GetAllTimers(_timerBuffer); int displayCount = Mathf.Min(timerCount, MAX_DISPLAY_COUNT); if (displayCount < timerCount) { EditorGUILayout.HelpBox(Utility.Text.Format("Showing first {0} timers of {1}.", displayCount, timerCount), MessageType.Info); } for (int i = 0; i < displayCount; i++) { TimerDebugInfo timer = _timerBuffer[i]; string label = Utility.Text.Format( "ID {0} | {1} | {2} | {3}", timer.TimerId, timer.IsLoop ? "Loop" : "Once", timer.IsUnscaled ? "Unscaled" : "Scaled", timer.IsRunning ? "Running" : "Paused"); string value = Utility.Text.Format( "Left {0:F2}s | Duration {1:F2}s", timer.LeftTime, timer.Duration); EditorGUILayout.LabelField(label, value); } } #if UNITY_EDITOR private void DrawLeakDetection(ITimerServiceDebugView debugView, int activeCount) { if (activeCount <= 0) { return; } EnsureLeakBuffer(activeCount); int staleCount = debugView.GetStaleOneShotTimers(_leakBuffer); if (staleCount <= 0) { return; } EditorGUILayout.Space(); EditorGUILayout.LabelField(Utility.Text.Format("Stale One-Shot Timers ({0})", staleCount), EditorStyles.boldLabel); EditorGUILayout.HelpBox("Non-loop timers older than 5 minutes. This may indicate long-delay tasks or paused timers.", MessageType.Warning); for (int i = 0; i < staleCount; i++) { TimerDebugInfo staleTimer = _leakBuffer[i]; EditorGUILayout.LabelField( Utility.Text.Format("ID {0}", staleTimer.TimerId), Utility.Text.Format("Created {0:F1}s ago", staleTimer.CreationTime)); } } #endif private void EnsureTimerBuffer(int count) { if (_timerBuffer == null || _timerBuffer.Length < count) { _timerBuffer = new TimerDebugInfo[count]; } } #if UNITY_EDITOR private void EnsureLeakBuffer(int count) { if (_leakBuffer == null || _leakBuffer.Length < count) { _leakBuffer = new TimerDebugInfo[count]; } } #endif } }