using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using UnityEngine.Assertions; using UnityEngine.Rendering.UI; namespace UnityEngine.Rendering { using UnityObject = UnityEngine.Object; /// /// IDebugData interface. /// public interface IDebugData { /// Get the reset callback for this DebugData /// The reset callback Action GetReset(); //Action GetLoad(); //Action GetSave(); } /// /// Manager class for the Debug Window. /// 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; } } /// /// 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; /// /// Force an editor request. /// public bool refreshEditorRequested; int? m_RequestedPanelIndex; GameObject m_Root; DebugUIHandlerCanvas m_RootUICanvas; GameObject m_PersistentRoot; DebugUIHandlerPersistentCanvas m_RootUIPersistentCanvas; /// /// Is any debug window or UI currently active. /// public bool isAnyDebugUIActive { get { return displayRuntimeUI || displayPersistentRuntimeUI #if UNITY_EDITOR || displayEditorUI #endif ; } } DebugManager() { #if DEVELOPMENT_BUILD || UNITY_EDITOR RegisterInputs(); RegisterActions(); #endif } /// /// Refresh the debug window. /// public void RefreshEditor() { refreshEditorRequested = true; } /// /// Reset the debug window. /// public void Reset() { resetData?.Invoke(); ReDrawOnScreenDebug(); } /// /// Request the runtime debug UI be redrawn on the next update. /// public void ReDrawOnScreenDebug() { if (displayRuntimeUI) m_RootUICanvas?.RequestHierarchyReset(); } /// /// 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(); /// /// Get hashcode state of the Debug Window. /// /// public int GetState() { int hash = 17; foreach (var panel in m_Panels) hash = hash * 23 + panel.GetHashCode(); return hash; } internal void RegisterRootCanvas(DebugUIHandlerCanvas root) { Assert.IsNotNull(root); m_Root = root.gameObject; m_RootUICanvas = root; } internal void ChangeSelection(DebugUIHandlerWidget widget, bool fromNext) { m_RootUICanvas.ChangeSelection(widget, fromNext); } internal void SetScrollTarget(DebugUIHandlerWidget widget) { if (m_RootUICanvas != null) m_RootUICanvas.SetScrollTarget(widget); } void EnsurePersistentCanvas() { if (m_RootUIPersistentCanvas == null) { var uiManager = UnityObject.FindObjectOfType(); if (uiManager == null) { m_PersistentRoot = UnityObject.Instantiate(Resources.Load("DebugUIPersistentCanvas")).gameObject; m_PersistentRoot.name = "[Debug Canvas - Persistent]"; m_PersistentRoot.transform.localPosition = Vector3.zero; } else { m_PersistentRoot = uiManager.gameObject; } m_RootUIPersistentCanvas = m_PersistentRoot.GetComponent(); } } internal void TogglePersistent(DebugUI.Widget widget, int? forceTupleIndex = null) { if (widget == null) return; EnsurePersistentCanvas(); switch (widget) { case DebugUI.Value value: m_RootUIPersistentCanvas.Toggle(value); break; case DebugUI.ValueTuple valueTuple: m_RootUIPersistentCanvas.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 = container.children.Max(w => (w as DebugUI.ValueTuple)?.pinnedElementIndex ?? -1); 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; } } 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; } /// /// Request DebugWindow to open the specified panel. /// /// Index of the debug window panel to activate. public void RequestEditorWindowPanelIndex(int index) { // Similar to RefreshEditor(), this function is required to bypass a dependency problem where DebugWindow // cannot be accessed from the Core.Runtime assembly. Should there be a better way to allow editor-dependent // features in DebugUI? m_RequestedPanelIndex = index; } internal int? GetRequestedEditorWindowPanelIndex() { int? requestedIndex = m_RequestedPanelIndex; m_RequestedPanelIndex = null; return requestedIndex; } // 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. /// 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; } } }