using AlicizaX.Timer.Runtime; using UnityEngine; using UnityEngine.UIElements; namespace AlicizaX.Debugger.Runtime { public sealed partial class DebuggerComponent { private sealed class TimerInformationWindow : ScrollableDebuggerWindowBase { private const int MAX_DISPLAY_COUNT = 50; private const float REFRESH_INTERVAL = 0.25f; private const string OVERFLOW_NOTE = "Showing first 50 active timers."; private const string EMPTY_NOTE = "No active timers."; private const string SAMPLE_NOTE = "Zero-allocation runtime sample view."; private struct RowView { public VisualElement Root; public VisualElement LoopIndicator; public VisualElement ScaleIndicator; public VisualElement StateIndicator; public VisualElement Fill; } private ITimerServiceDebug _mTimerDebug; private readonly TimerDebugInfo[] m_TimerInfos = new TimerDebugInfo[MAX_DISPLAY_COUNT]; private readonly RowView[] m_TimerRows = new RowView[MAX_DISPLAY_COUNT]; private VisualElement m_ActiveUsageFill; private VisualElement m_PeakUsageFill; private VisualElement m_FreeUsageFill; private VisualElement m_OverflowNote; private VisualElement m_EmptyNote; private float m_RefreshCountdown; public override void Initialize(params object[] args) { _mTimerDebug = AppServices.Require() as ITimerServiceDebug; } public override void OnEnter() { m_RefreshCountdown = 0f; RefreshContent(); } public override void OnUpdate(float elapseSeconds, float realElapseSeconds) { m_RefreshCountdown -= realElapseSeconds; if (m_RefreshCountdown > 0f) { return; } m_RefreshCountdown = REFRESH_INTERVAL; RefreshContent(); } protected override void BuildWindow(VisualElement root) { if (_mTimerDebug == null) { return; } root.Add(CreateActionButton("Refresh", RefreshContent, DebuggerTheme.ButtonSurfaceActive, DebuggerTheme.PrimaryText)); VisualElement overview = CreateSection("Timer Pool Overview", out VisualElement overviewCard); overviewCard.Add(CreateUsageRow("Active Usage", DebuggerTheme.Accent, out m_ActiveUsageFill)); overviewCard.Add(CreateUsageRow("Peak Usage", DebuggerTheme.Warning, out m_PeakUsageFill)); overviewCard.Add(CreateUsageRow("Free Capacity", DebuggerTheme.Positive, out m_FreeUsageFill)); root.Add(overview); VisualElement sample = CreateSection("Timer Sample", out VisualElement sampleCard); sampleCard.Add(CreateNoteLabel(SAMPLE_NOTE, DebuggerTheme.SecondaryText)); m_OverflowNote = CreateNoteLabel(OVERFLOW_NOTE, new Color(1f, 0.5f, 0f)); m_OverflowNote.style.display = DisplayStyle.None; sampleCard.Add(m_OverflowNote); m_EmptyNote = CreateNoteLabel(EMPTY_NOTE, DebuggerTheme.SecondaryText); m_EmptyNote.style.display = DisplayStyle.None; sampleCard.Add(m_EmptyNote); for (int i = 0; i < MAX_DISPLAY_COUNT; i++) { m_TimerRows[i] = CreateTimerRow(sampleCard); m_TimerRows[i].Root.style.display = DisplayStyle.None; } root.Add(sample); RefreshContent(); } private void RefreshContent() { if (_mTimerDebug == null || m_ActiveUsageFill == null) { return; } _mTimerDebug.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount); float capacity = poolCapacity > 0 ? poolCapacity : 1f; SetFillRatio(m_ActiveUsageFill, activeCount / capacity); SetFillRatio(m_PeakUsageFill, peakActiveCount / capacity); SetFillRatio(m_FreeUsageFill, freeCount / capacity); if (activeCount <= 0) { m_EmptyNote.style.display = DisplayStyle.Flex; m_OverflowNote.style.display = DisplayStyle.None; SetTimerRowsVisible(0); return; } m_EmptyNote.style.display = DisplayStyle.None; int timerCount = _mTimerDebug.GetAllTimers(m_TimerInfos); int displayCount = timerCount > MAX_DISPLAY_COUNT ? MAX_DISPLAY_COUNT : timerCount; m_OverflowNote.style.display = activeCount > MAX_DISPLAY_COUNT ? DisplayStyle.Flex : DisplayStyle.None; for (int i = 0; i < displayCount; i++) { UpdateTimerRow(ref m_TimerRows[i], ref m_TimerInfos[i]); } SetTimerRowsVisible(displayCount); } private void SetTimerRowsVisible(int visibleCount) { for (int i = 0; i < MAX_DISPLAY_COUNT; i++) { m_TimerRows[i].Root.style.display = i < visibleCount ? DisplayStyle.Flex : DisplayStyle.None; } } private static VisualElement CreateUsageRow(string title, Color fillColor, out VisualElement fill) { float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f; VisualElement row = new VisualElement(); row.style.flexDirection = FlexDirection.Column; row.style.marginBottom = 8f * scale; Label titleLabel = new Label(title); titleLabel.style.color = DebuggerTheme.SecondaryText; titleLabel.style.fontSize = 16f * scale; titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold; titleLabel.style.marginBottom = 4f * scale; row.Add(titleLabel); VisualElement track = new VisualElement(); track.style.height = 14f * scale; track.style.backgroundColor = DebuggerTheme.PanelSurfaceStrong; track.style.borderTopLeftRadius = 4f * scale; track.style.borderTopRightRadius = 4f * scale; track.style.borderBottomLeftRadius = 4f * scale; track.style.borderBottomRightRadius = 4f * scale; track.style.overflow = Overflow.Hidden; fill = new VisualElement(); fill.style.height = Length.Percent(100f); fill.style.width = Length.Percent(0f); fill.style.backgroundColor = fillColor; track.Add(fill); row.Add(track); return row; } private static VisualElement CreateNoteLabel(string text, Color color) { float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f; Label label = new Label(text); label.style.color = color; label.style.fontSize = 15f * scale; label.style.marginBottom = 6f * scale; label.style.whiteSpace = WhiteSpace.Normal; return label; } private static RowView CreateTimerRow(VisualElement parent) { float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f; VisualElement row = new VisualElement(); row.style.flexDirection = FlexDirection.Row; row.style.alignItems = Align.Center; row.style.height = 20f * scale; row.style.marginBottom = 4f * scale; VisualElement loopIndicator = CreateIndicator(6f * scale, DebuggerTheme.Accent); VisualElement scaleIndicator = CreateIndicator(6f * scale, DebuggerTheme.Warning); VisualElement stateIndicator = CreateIndicator(6f * scale, DebuggerTheme.Positive); row.Add(loopIndicator); row.Add(scaleIndicator); row.Add(stateIndicator); VisualElement track = new VisualElement(); track.style.flexGrow = 1f; track.style.height = 14f * scale; track.style.backgroundColor = DebuggerTheme.PanelSurfaceStrong; track.style.borderTopLeftRadius = 4f * scale; track.style.borderTopRightRadius = 4f * scale; track.style.borderBottomLeftRadius = 4f * scale; track.style.borderBottomRightRadius = 4f * scale; track.style.overflow = Overflow.Hidden; VisualElement fill = new VisualElement(); fill.style.height = Length.Percent(100f); fill.style.width = Length.Percent(0f); fill.style.backgroundColor = DebuggerTheme.Positive; track.Add(fill); row.Add(track); parent.Add(row); RowView view; view.Root = row; view.LoopIndicator = loopIndicator; view.ScaleIndicator = scaleIndicator; view.StateIndicator = stateIndicator; view.Fill = fill; return view; } private static VisualElement CreateIndicator(float size, Color color) { VisualElement indicator = new VisualElement(); indicator.style.width = size; indicator.style.height = size; indicator.style.marginRight = size; indicator.style.backgroundColor = color; indicator.style.opacity = 0.2f; return indicator; } private static void UpdateTimerRow(ref RowView row, ref TimerDebugInfo info) { byte flags = info.Flags; bool isRunning = (flags & TimerDebugFlags.Running) != 0; bool isLoop = (flags & TimerDebugFlags.Loop) != 0; bool isUnscaled = (flags & TimerDebugFlags.Unscaled) != 0; float duration = info.Duration; float ratio = duration > 0f ? info.LeftTime / duration : 0f; if (ratio < 0f) { ratio = 0f; } else if (ratio > 1f) { ratio = 1f; } row.Root.style.display = DisplayStyle.Flex; row.LoopIndicator.style.opacity = isLoop ? 1f : 0.2f; row.ScaleIndicator.style.opacity = isUnscaled ? 1f : 0.35f; row.ScaleIndicator.style.backgroundColor = isUnscaled ? DebuggerTheme.Warning : DebuggerTheme.Accent; row.StateIndicator.style.opacity = isRunning ? 1f : 0.45f; row.StateIndicator.style.backgroundColor = isRunning ? DebuggerTheme.Positive : DebuggerTheme.Warning; row.Fill.style.width = Length.Percent(ratio * 100f); row.Fill.style.backgroundColor = isRunning ? ratio <= 0.2f ? DebuggerTheme.Warning : DebuggerTheme.Positive : DebuggerTheme.SecondaryText; } private static void SetFillRatio(VisualElement fill, float ratio) { if (ratio < 0f) { ratio = 0f; } else if (ratio > 1f) { ratio = 1f; } fill.style.width = Length.Percent(ratio * 100f); } } } }