#if ENABLE_UIELEMENTS_MODULE && UNITY_ENABLE_CHECKS #define ENABLE_RENDERING_DEBUGGER_UI #endif using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; namespace UnityEngine.Rendering { /// /// Implementing this interface enables integration with Unity's Rendering debugger, providing a way to manage and control the state of debug data. /// /// /// /// Use the `IDebugData` interface to register custom debug data. You can reset the data when necessary, which makes it suitable for debugging scenarios /// where you need to clear or reset specific data. For example, when the application state changes or during gameplay session resets, /// or when the **Reset** button is selected in the **Rendering Debugger** window in the Editor or at runtime. /// /// /// /// /// public class MyDebugData : IDebugData /// { /// private int _value; /// /// /// Constructor of the debug data that will receive the reset callback /// public MyDebugData() /// { /// _value = 0; /// } /// /// public Action GetReset() /// { /// /// Specify the callback when the reset operation is being called. /// return () => _value = 0; // Resets the value to 0 /// } /// } /// /// public interface IDebugData { /// Provides the reset callback for resetting the debug data. /// The reset callback Action GetReset(); } /// /// The class provides a centralized manager for handling Unity's Rendering debugger. /// /// /// The DebugManager allows you to register, unregister, and manipulate debug data, panels, and widgets for runtime or editor debugging. /// - Register and manage debug panels and widgets. /// - Refresh and reset the debug UI based on runtime changes. /// - Provides a global instance for easy access from anywhere in the codebase. /// /// /// /// /// { /// /// Create a list to store the new DebugUI widgets /// /// by creating different DebugUI.Values that display a label with a value. /// var list = new System.Collections.Generic.List<DebugUI.Widget> /// { /// new DebugUI.Value { displayName = "Lighting Intensity", getter = () => 1.0f, setter = value => Debug.Log($"Lighting Intensity set to {value}") }, /// new DebugUI.Value { displayName = "Light Color", getter = () => Color.white, setter = value => Debug.Log($"Light Color set to {value}") } /// }; /// var items = list.ToArray(); /// /// /// Obtain the panel from the DebugManager instance, and add the Widgets that we want to display there. /// var panel = DebugManager.instance.GetPanel("Lighting", true); /// panel.children.AddRange(items); /// } /// /// public sealed partial class DebugManager { static readonly Lazy s_Instance = new Lazy(() => new DebugManager()); /// /// Global instance of the DebugManager. /// public static DebugManager instance => s_Instance.Value; ReadOnlyCollection m_ReadOnlyPanels; readonly List m_Panels = new List(); void UpdateReadOnlyCollection() { m_Panels.Sort(); m_ReadOnlyPanels = m_Panels.AsReadOnly(); } /// /// List of currently registered debug panels. /// public ReadOnlyCollection panels { get { if (m_ReadOnlyPanels == null) UpdateReadOnlyCollection(); return m_ReadOnlyPanels; } } void ForEachWidgetInContainer(DebugUI.Widget widget, Action action) { action(widget); if (widget is DebugUI.IContainer container) { foreach (var child in container.children) ForEachWidgetInContainer(child, action); } } /// /// Iterate over all widgets in all panels and invoke the provided action on each widget. /// /// Action to invoke. internal void ForEachWidget(Action action) { foreach (var panel in m_Panels) foreach (var widget in panel.children) ForEachWidgetInContainer(widget, action); } /// /// Callback called when the runtime UI changed. /// public event Action onDisplayRuntimeUIChanged = delegate { }; /// /// Callback called when the debug window is dirty. /// public event Action onSetDirty = delegate { }; event Action resetData; /// /// Event invoked when debug UI needs to be recreated (panels torn down and rebuilt). /// internal event Action onRecreateDebugUI; /// /// Event invoked when a panel selection is requested. /// internal event Action onPanelSelectionRequested; string m_RequestedPanel; #if ENABLE_RENDERING_DEBUGGER_UI internal RuntimeDebugWindow m_RuntimeDebugWindow; RuntimePersistentDebugUI m_RuntimePersistentDebugUI; internal SchedulerTracker schedulerTracker { get; } = new(); #endif internal DebugUI.Widget selectedWidget { get; set; } /// /// Is any debug window or UI currently active. /// public bool isAnyDebugUIActive { get { return displayRuntimeUI || displayPersistentRuntimeUI #if UNITY_EDITOR || displayEditorUI #endif ; } } DebugManager() { #if UNITY_ENABLE_CHECKS RegisterDebugInputs(); #endif } /// /// Recreates the debug UI for all panels. Use this when the panel structure needs to be rebuilt /// (e.g., when widgets need to be added/removed dynamically based on feature availability). /// For simple value refreshes, the UI automatically updates through widget getters. /// public void RecreateDebugUI() { onRecreateDebugUI?.Invoke(); } /// /// Reset the debug window. /// public void Reset() { DebugDisplaySerializer.SaveFoldoutStates(); DebugDisplaySerializer.Clear(); resetData?.Invoke(); #if ENABLE_RENDERING_DEBUGGER_UI displayPersistentRuntimeUI = false; ForEachWidget(w => { if (w is DebugUI.ValueTuple vt) vt.pinnedElementIndex = -1; }); #endif } /// /// Register debug data. /// /// Data to be registered. public void RegisterData(IDebugData data) => resetData += data.GetReset(); /// /// Register debug data. /// /// Data to be registered. public void UnregisterData(IDebugData data) => resetData -= data.GetReset(); int GetMaxPinnedIndex(DebugUI.Container container) { int pinnedIndex = -1; foreach (var child in container.children) { if (child is DebugUI.ValueTuple valueTuple && valueTuple.pinnedElementIndex > pinnedIndex) { pinnedIndex = valueTuple.pinnedElementIndex; } } return pinnedIndex; } internal void TogglePersistent() { if (selectedWidget != null) TogglePersistent(selectedWidget); } void TogglePersistent(DebugUI.Widget widget, int? forceTupleIndex = null) { #if ENABLE_RENDERING_DEBUGGER_UI if (widget == null) return; displayPersistentRuntimeUI = true; switch (widget) { case DebugUI.Value value: m_RuntimePersistentDebugUI.Toggle(value); break; case DebugUI.ValueTuple valueTuple: m_RuntimePersistentDebugUI.Toggle(valueTuple, forceTupleIndex); break; case DebugUI.Container container: // When container is toggled, we make sure that if there are ValueTuples, they all get the same element index. int pinnedIndex = GetMaxPinnedIndex(container); foreach (var child in container.children) { if (child is DebugUI.Value || child is DebugUI.ValueTuple) TogglePersistent(child, pinnedIndex); } break; default: Debug.Log("Only readonly items can be made persistent."); break; } if (m_RuntimePersistentDebugUI != null && m_RuntimePersistentDebugUI.IsEmpty()) displayPersistentRuntimeUI = false; #endif } void OnPanelDirty(DebugUI.Panel panel) { onSetDirty(); } /// /// Returns the panel index /// /// The displayname for the panel /// The index for the panel or -1 if not found. public int PanelIndex([DisallowNull] string displayName) { displayName ??= string.Empty; for (int i = 0; i < m_Panels.Count; ++i) { if (displayName.Equals(m_Panels[i].displayName, StringComparison.InvariantCultureIgnoreCase)) return i; } return -1; } /// /// Returns the panel display name /// /// The panelIndex for the panel to get the name /// The display name of the panel, or empty string otherwise public string PanelDisplayName(int panelIndex) { if (panelIndex < 0 || panelIndex > m_Panels.Count - 1) return string.Empty; return m_Panels[panelIndex].displayName; } /// /// Request DebugWindow to open the specified panel. /// /// Name of window panel to activate. public void RequestPanelSelection(string panelName) { int panelIndex = FindPanelIndex(panelName); if (panelIndex != -1) { onPanelSelectionRequested?.Invoke(panelName); } else { Debug.LogWarning($"No panel with name {panelName} has been registered."); } } // TODO: Optimally we should use a query path here instead of a display name /// /// Returns a debug panel. /// /// Name of the debug panel. /// Create the panel if it does not exists. /// Group index. /// Replace an existing panel. /// The requested debug panel or null if it does not exist and createIfNull is set to false public DebugUI.Panel GetPanel(string displayName, bool createIfNull = false, int groupIndex = 0, bool overrideIfExist = false) { int panelIndex = PanelIndex(displayName); DebugUI.Panel p = panelIndex >= 0 ? m_Panels[panelIndex] : null; if (p != null) { if (overrideIfExist) { p.onSetDirty -= OnPanelDirty; RemovePanel(p); p = null; } else return p; } if (createIfNull) { p = new DebugUI.Panel { displayName = displayName, groupIndex = groupIndex }; p.onSetDirty += OnPanelDirty; m_Panels.Add(p); UpdateReadOnlyCollection(); } return p; } /// /// Find the index of the panel from it's display name. /// /// The display name of the panel to find. /// The index of the panel in the list. -1 if not found. public int FindPanelIndex(string displayName) => m_Panels.FindIndex(p => p.displayName == displayName); // TODO: Use a query path here as well instead of a display name /// /// Remove a debug panel. /// /// Name of the debug panel to remove. public void RemovePanel(string displayName) { DebugUI.Panel panel = null; foreach (var p in m_Panels) { if (p.displayName == displayName) { p.onSetDirty -= OnPanelDirty; panel = p; break; } } RemovePanel(panel); } /// /// Remove a debug panel. /// /// Reference to the debug panel to remove. public void RemovePanel(DebugUI.Panel panel) { if (panel == null) return; m_Panels.Remove(panel); UpdateReadOnlyCollection(); } /// /// Gets an matching the given /// /// The flags of the widget /// Reference to the requested debug item. public DebugUI.Widget[] GetItems(DebugUI.Flags flags) { using (ListPool.Get(out var temp)) { foreach (var panel in m_Panels) { var widgets = GetItemsFromContainer(flags, panel); temp.AddRange(widgets); } return temp.ToArray(); } } internal DebugUI.Widget[] GetItemsFromContainer(DebugUI.Flags flags, DebugUI.IContainer container) { using (ListPool.Get(out var temp)) { foreach (var child in container.children) { if (child.flags.HasFlag(flags)) { temp.Add(child); continue; } if (child is DebugUI.IContainer containerChild) { temp.AddRange(GetItemsFromContainer(flags, containerChild)); } } return temp.ToArray(); } } /// /// Get a Debug Item. /// /// Path of the debug item. /// Reference to the requested debug item. public DebugUI.Widget GetItem(string queryPath) { foreach (var panel in m_Panels) { var w = GetItem(queryPath, panel); if (w != null) return w; } return null; } /// /// Get a debug item from a specific container. /// /// Path of the debug item. /// Container to query. /// Reference to the requested debug item. DebugUI.Widget GetItem(string queryPath, DebugUI.IContainer container) { foreach (var child in container.children) { if (child.queryPath == queryPath) return child; if (child is DebugUI.IContainer containerChild) { var w = GetItem(queryPath, containerChild); if (w != null) return w; } } return null; } } }