com.alicizax.unity.framework/Editor/Timer/TimerComponentInspector.cs

189 lines
6.9 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.02d;
private const int DISPLAY_COUNT = 32;
private const int MIN_INITIAL_CAPACITY = 256;
private const int MAX_INITIAL_CAPACITY = 16384;
private const int CAPACITY_STEP = 256;
private readonly TimerDebugInfo[] _timerBuffer = new TimerDebugInfo[DISPLAY_COUNT];
private readonly TimerDebugInfo[] _staleBuffer = new TimerDebugInfo[DISPLAY_COUNT];
private double _lastUpdateTime;
private SerializedProperty _initialCapacityProperty;
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
DrawConfiguration();
serializedObject.ApplyModifiedProperties();
DrawRuntimeDebugInfo();
RequestRuntimeRepaint();
}
private void OnEnable()
{
_initialCapacityProperty = serializedObject.FindProperty("_initialCapacity");
}
private void DrawConfiguration()
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Configuration", EditorStyles.boldLabel);
int capacity = _initialCapacityProperty.intValue;
int sliderValue = EditorGUILayout.IntSlider("Initial Capacity", capacity, MIN_INITIAL_CAPACITY, MAX_INITIAL_CAPACITY);
sliderValue = AlignCapacity(sliderValue);
if (sliderValue != capacity)
{
_initialCapacityProperty.intValue = sliderValue;
}
EditorGUILayout.HelpBox(Utility.Text.Format("Rounded by {0}. Runtime allocates timer pages during Awake/prewarm.", CAPACITY_STEP), MessageType.None);
}
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;
}
if (!(timerService is ITimerDebugService timerDebugService))
{
EditorGUILayout.HelpBox("Timer debug service is not available.", MessageType.Info);
return;
}
timerDebugService.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Runtime Debug", EditorStyles.boldLabel);
DrawStatistic("Active Timers", activeCount);
DrawStatistic("Pool Capacity", poolCapacity);
DrawStatistic("Peak Active Count", peakActiveCount);
DrawStatistic("Free Slots", freeCount);
DrawUsageBar("Active Usage", activeCount, poolCapacity);
DrawUsageBar("Peak Usage", peakActiveCount, poolCapacity);
DrawTimerList(timerDebugService, activeCount);
DrawStaleTimerList(timerDebugService, activeCount);
}
private void DrawTimerList(ITimerDebugService timerDebugService, int activeCount)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Active Timer Sample", EditorStyles.boldLabel);
if (activeCount <= 0)
{
EditorGUILayout.LabelField("No active timers.");
return;
}
int timerCount = timerDebugService.GetAllTimers(_timerBuffer);
if (activeCount > DISPLAY_COUNT)
{
EditorGUILayout.HelpBox(Utility.Text.Format("Showing first {0} timers of {1}.", timerCount, activeCount), MessageType.Info);
}
for (int i = 0; i < timerCount; i++)
{
DrawTimerInfo(ref _timerBuffer[i]);
}
}
private void DrawStaleTimerList(ITimerDebugService timerDebugService, int activeCount)
{
if (activeCount <= 0)
{
return;
}
if (!(timerDebugService is ITimerEditorDebugService editorDebugService))
{
return;
}
int staleCount = editorDebugService.GetStaleOneShotTimers(_staleBuffer);
if (staleCount <= 0)
{
return;
}
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Long-lived one-shot timers detected.", MessageType.Warning);
for (int i = 0; i < staleCount; i++)
{
TimerDebugInfo info = _staleBuffer[i];
EditorGUILayout.LabelField(Utility.Text.Format("ID {0}", info.TimerHandle), Utility.Text.Format("Age {0:F1}s | Left {1:F2}s", info.Age, info.LeftTime));
}
}
private static void DrawTimerInfo(ref TimerDebugInfo info)
{
byte flags = info.Flags;
string mode = (flags & TimerDebugFlags.Loop) != 0 ? "Loop" : "Once";
string scale = (flags & TimerDebugFlags.Unscaled) != 0 ? "Unscaled" : "Scaled";
string state = (flags & TimerDebugFlags.Running) != 0 ? "Running" : "Paused";
EditorGUILayout.LabelField(
Utility.Text.Format("ID {0} | {1} | {2} | {3}", info.TimerHandle, mode, scale, state),
Utility.Text.Format("Left {0:F2}s | Duration {1:F2}s", info.LeftTime, info.Duration));
}
private static void DrawStatistic(string label, int value)
{
EditorGUILayout.LabelField(label, value.ToString(), EditorStyles.boldLabel);
}
private static void DrawUsageBar(string label, int value, int capacity)
{
float ratio = capacity > 0 ? (float)value / capacity : 0f;
EditorGUILayout.Slider(label, ratio, 0f, 1f);
}
private static int AlignCapacity(int value)
{
int aligned = ((value + CAPACITY_STEP - 1) / CAPACITY_STEP) * CAPACITY_STEP;
if (aligned < MIN_INITIAL_CAPACITY)
{
return MIN_INITIAL_CAPACITY;
}
return aligned > MAX_INITIAL_CAPACITY ? MAX_INITIAL_CAPACITY : aligned;
}
private void RequestRuntimeRepaint()
{
if (!EditorApplication.isPlaying)
{
return;
}
double currentTime = EditorApplication.timeSinceStartup;
if (currentTime - _lastUpdateTime < UPDATE_INTERVAL)
{
return;
}
_lastUpdateTime = currentTime;
Repaint();
}
}
}