com.alicizax.unity.framework/Editor/Timer/TimerComponentInspector.cs
2026-04-28 16:30:31 +08:00

171 lines
6.0 KiB
C#

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.25d;
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<ITimerService>(out ITimerService timerService))
{
EditorGUILayout.HelpBox("Timer service is not initialized.", MessageType.Info);
return;
}
timerService.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(timerService, _cachedActiveCount);
#if UNITY_EDITOR
DrawLeakDetection(timerService, _cachedActiveCount);
#endif
}
private void DrawTimerList(ITimerService debug, int activeCount)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Active Timers", EditorStyles.boldLabel);
if (activeCount <= 0)
{
EditorGUILayout.LabelField("No active timers.");
return;
}
EnsureTimerBuffer(activeCount);
int timerCount = debug.GetAllTimers(_timerBuffer);
int displayCount = Mathf.Min(timerCount, MAX_DISPLAY_COUNT);
if (displayCount < activeCount)
{
EditorGUILayout.HelpBox(Utility.Text.Format("Showing first {0} timers of {1}.", displayCount, activeCount), MessageType.Info);
}
for (int i = 0; i < displayCount; i++)
{
TimerDebugInfo timer = _timerBuffer[i];
bool isLoop = (timer.Flags & TimerDebugFlags.Loop) != 0;
bool isRunning = (timer.Flags & TimerDebugFlags.Running) != 0;
bool isUnscaled = (timer.Flags & TimerDebugFlags.Unscaled) != 0;
string label = Utility.Text.Format(
"ID {0} | {1} | {2} | {3}",
timer.TimerHandle,
isLoop ? "Loop" : "Once",
isUnscaled ? "Unscaled" : "Scaled",
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(ITimerService debug, int activeCount)
{
if (activeCount <= 0)
{
return;
}
EnsureLeakBuffer(activeCount);
int staleCount = debug.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.TimerHandle),
Utility.Text.Format("Created {0:F1}s ago", staleTimer.Age));
}
}
#endif
private void EnsureTimerBuffer(int count)
{
int capacity = count > 0 ? count : 1;
if (_timerBuffer == null || _timerBuffer.Length < capacity)
{
_timerBuffer = new TimerDebugInfo[capacity];
}
}
#if UNITY_EDITOR
private void EnsureLeakBuffer(int count)
{
int capacity = count > 0 ? count : 1;
if (_leakBuffer == null || _leakBuffer.Length < capacity)
{
_leakBuffer = new TimerDebugInfo[capacity];
}
}
#endif
}
}