A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.Collections.ObjectModel; 4using System.Diagnostics.CodeAnalysis; 5using System.Linq; 6using UnityEngine.Assertions; 7using UnityEngine.Rendering.UI; 8 9namespace UnityEngine.Rendering 10{ 11 using UnityObject = UnityEngine.Object; 12 13 /// <summary> 14 /// IDebugData interface. 15 /// </summary> 16 public interface IDebugData 17 { 18 /// <summary>Get the reset callback for this DebugData</summary> 19 /// <returns>The reset callback</returns> 20 Action GetReset(); 21 //Action GetLoad(); 22 //Action GetSave(); 23 } 24 25 /// <summary> 26 /// Manager class for the Debug Window. 27 /// </summary> 28 public sealed partial class DebugManager 29 { 30 static readonly Lazy<DebugManager> s_Instance = new Lazy<DebugManager>(() => new DebugManager()); 31 /// <summary> 32 /// Global instance of the DebugManager. 33 /// </summary> 34 public static DebugManager instance => s_Instance.Value; 35 36 ReadOnlyCollection<DebugUI.Panel> m_ReadOnlyPanels; 37 readonly List<DebugUI.Panel> m_Panels = new List<DebugUI.Panel>(); 38 39 void UpdateReadOnlyCollection() 40 { 41 m_Panels.Sort(); 42 m_ReadOnlyPanels = m_Panels.AsReadOnly(); 43 } 44 45 /// <summary> 46 /// List of currently registered debug panels. 47 /// </summary> 48 public ReadOnlyCollection<DebugUI.Panel> panels 49 { 50 get 51 { 52 if (m_ReadOnlyPanels == null) 53 UpdateReadOnlyCollection(); 54 return m_ReadOnlyPanels; 55 } 56 } 57 58 /// <summary> 59 /// Callback called when the runtime UI changed. 60 /// </summary> 61 public event Action<bool> onDisplayRuntimeUIChanged = delegate { }; 62 /// <summary> 63 /// Callback called when the debug window is dirty. 64 /// </summary> 65 public event Action onSetDirty = delegate { }; 66 67 event Action resetData; 68 69 /// <summary> 70 /// Force an editor request. 71 /// </summary> 72 public bool refreshEditorRequested; 73 74 int? m_RequestedPanelIndex; 75 76 GameObject m_Root; 77 DebugUIHandlerCanvas m_RootUICanvas; 78 79 GameObject m_PersistentRoot; 80 DebugUIHandlerPersistentCanvas m_RootUIPersistentCanvas; 81 82 /// <summary> 83 /// Is any debug window or UI currently active. 84 /// </summary> 85 public bool isAnyDebugUIActive 86 { 87 get 88 { 89 return 90 displayRuntimeUI || displayPersistentRuntimeUI 91#if UNITY_EDITOR 92 || displayEditorUI 93#endif 94 ; 95 } 96 } 97 98 DebugManager() 99 { 100#if DEVELOPMENT_BUILD || UNITY_EDITOR 101 RegisterInputs(); 102 RegisterActions(); 103#endif 104 } 105 106 /// <summary> 107 /// Refresh the debug window. 108 /// </summary> 109 public void RefreshEditor() 110 { 111 refreshEditorRequested = true; 112 } 113 114 /// <summary> 115 /// Reset the debug window. 116 /// </summary> 117 public void Reset() 118 { 119 resetData?.Invoke(); 120 ReDrawOnScreenDebug(); 121 } 122 123 /// <summary> 124 /// Request the runtime debug UI be redrawn on the next update. 125 /// </summary> 126 public void ReDrawOnScreenDebug() 127 { 128 if (displayRuntimeUI) 129 m_RootUICanvas?.RequestHierarchyReset(); 130 } 131 132 /// <summary> 133 /// Register debug data. 134 /// </summary> 135 /// <param name="data">Data to be registered.</param> 136 public void RegisterData(IDebugData data) => resetData += data.GetReset(); 137 138 /// <summary> 139 /// Register debug data. 140 /// </summary> 141 /// <param name="data">Data to be registered.</param> 142 public void UnregisterData(IDebugData data) => resetData -= data.GetReset(); 143 144 /// <summary> 145 /// Get hashcode state of the Debug Window. 146 /// </summary> 147 /// <returns>The calculated hashcode for the current state of the Debug Window.</returns> 148 public int GetState() 149 { 150 int hash = 17; 151 152 foreach (var panel in m_Panels) 153 hash = hash * 23 + panel.GetHashCode(); 154 155 return hash; 156 } 157 158 internal void RegisterRootCanvas(DebugUIHandlerCanvas root) 159 { 160 Assert.IsNotNull(root); 161 m_Root = root.gameObject; 162 m_RootUICanvas = root; 163 } 164 165 internal void ChangeSelection(DebugUIHandlerWidget widget, bool fromNext) 166 { 167 m_RootUICanvas.ChangeSelection(widget, fromNext); 168 } 169 170 internal void SetScrollTarget(DebugUIHandlerWidget widget) 171 { 172 if (m_RootUICanvas != null) 173 m_RootUICanvas.SetScrollTarget(widget); 174 } 175 176 void EnsurePersistentCanvas() 177 { 178 if (m_RootUIPersistentCanvas == null) 179 { 180 var uiManager = UnityObject.FindFirstObjectByType<DebugUIHandlerPersistentCanvas>(); 181 182 if (uiManager == null) 183 { 184 m_PersistentRoot = UnityObject.Instantiate(Resources.Load<Transform>("DebugUIPersistentCanvas")).gameObject; 185 m_PersistentRoot.name = "[Debug Canvas - Persistent]"; 186 m_PersistentRoot.transform.localPosition = Vector3.zero; 187 } 188 else 189 { 190 m_PersistentRoot = uiManager.gameObject; 191 } 192 193 m_RootUIPersistentCanvas = m_PersistentRoot.GetComponent<DebugUIHandlerPersistentCanvas>(); 194 } 195 } 196 197 internal void TogglePersistent(DebugUI.Widget widget, int? forceTupleIndex = null) 198 { 199 if (widget == null) 200 return; 201 202 EnsurePersistentCanvas(); 203 204 switch (widget) 205 { 206 case DebugUI.Value value: 207 m_RootUIPersistentCanvas.Toggle(value); 208 break; 209 case DebugUI.ValueTuple valueTuple: 210 m_RootUIPersistentCanvas.Toggle(valueTuple, forceTupleIndex); 211 break; 212 case DebugUI.Container container: 213 // When container is toggled, we make sure that if there are ValueTuples, they all get the same element index. 214 int pinnedIndex = container.children.Max(w => (w as DebugUI.ValueTuple)?.pinnedElementIndex ?? -1); 215 foreach (var child in container.children) 216 { 217 if (child is DebugUI.Value || child is DebugUI.ValueTuple) 218 TogglePersistent(child, pinnedIndex); 219 } 220 break; 221 default: 222 Debug.Log("Only readonly items can be made persistent."); 223 break; 224 } 225 } 226 227 void OnPanelDirty(DebugUI.Panel panel) 228 { 229 onSetDirty(); 230 } 231 232 /// <summary> 233 /// Returns the panel index 234 /// </summary> 235 /// <param name="displayName">The displayname for the panel</param> 236 /// <returns>The index for the panel or -1 if not found.</returns> 237 public int PanelIndex([DisallowNull] string displayName) 238 { 239 displayName ??= string.Empty; 240 241 for (int i = 0; i < m_Panels.Count; ++i) 242 { 243 if (displayName.Equals(m_Panels[i].displayName, StringComparison.InvariantCultureIgnoreCase)) 244 return i; 245 } 246 247 return -1; 248 } 249 250 /// <summary> 251 /// Returns the panel display name 252 /// </summary> 253 /// <param name="panelIndex">The panelIndex for the panel to get the name</param> 254 /// <returns>The display name of the panel, or empty string otherwise</returns> 255 public string PanelDiplayName([DisallowNull] int panelIndex) 256 { 257 if (panelIndex < 0 || panelIndex > m_Panels.Count - 1) 258 return string.Empty; 259 260 return m_Panels[panelIndex].displayName; 261 } 262 263 /// <summary> 264 /// Request DebugWindow to open the specified panel. 265 /// </summary> 266 /// <param name="index">Index of the debug window panel to activate.</param> 267 public void RequestEditorWindowPanelIndex(int index) 268 { 269 // Similar to RefreshEditor(), this function is required to bypass a dependency problem where DebugWindow 270 // cannot be accessed from the Core.Runtime assembly. Should there be a better way to allow editor-dependent 271 // features in DebugUI? 272 m_RequestedPanelIndex = index; 273 } 274 275 internal int? GetRequestedEditorWindowPanelIndex() 276 { 277 int? requestedIndex = m_RequestedPanelIndex; 278 m_RequestedPanelIndex = null; 279 return requestedIndex; 280 } 281 282 // TODO: Optimally we should use a query path here instead of a display name 283 /// <summary> 284 /// Returns a debug panel. 285 /// </summary> 286 /// <param name="displayName">Name of the debug panel.</param> 287 /// <param name="createIfNull">Create the panel if it does not exists.</param> 288 /// <param name="groupIndex">Group index.</param> 289 /// <param name="overrideIfExist">Replace an existing panel.</param> 290 /// <returns>The requested debug panel or null if it does not exist and createIfNull is set to false</returns> 291 public DebugUI.Panel GetPanel(string displayName, bool createIfNull = false, int groupIndex = 0, bool overrideIfExist = false) 292 { 293 int panelIndex = PanelIndex(displayName); 294 DebugUI.Panel p = panelIndex >= 0 ? m_Panels[panelIndex] : null; 295 296 if (p != null) 297 { 298 if (overrideIfExist) 299 { 300 p.onSetDirty -= OnPanelDirty; 301 RemovePanel(p); 302 p = null; 303 } 304 else 305 return p; 306 } 307 308 if (createIfNull) 309 { 310 p = new DebugUI.Panel { displayName = displayName, groupIndex = groupIndex }; 311 p.onSetDirty += OnPanelDirty; 312 m_Panels.Add(p); 313 UpdateReadOnlyCollection(); 314 } 315 316 return p; 317 } 318 319 /// <summary> 320 /// Find the index of the panel from it's display name. 321 /// </summary> 322 /// <param name="displayName">The display name of the panel to find.</param> 323 /// <returns>The index of the panel in the list. -1 if not found.</returns> 324 public int FindPanelIndex(string displayName) 325 => m_Panels.FindIndex(p => p.displayName == displayName); 326 327 // TODO: Use a query path here as well instead of a display name 328 /// <summary> 329 /// Remove a debug panel. 330 /// </summary> 331 /// <param name="displayName">Name of the debug panel to remove.</param> 332 public void RemovePanel(string displayName) 333 { 334 DebugUI.Panel panel = null; 335 336 foreach (var p in m_Panels) 337 { 338 if (p.displayName == displayName) 339 { 340 p.onSetDirty -= OnPanelDirty; 341 panel = p; 342 break; 343 } 344 } 345 346 RemovePanel(panel); 347 } 348 349 /// <summary> 350 /// Remove a debug panel. 351 /// </summary> 352 /// <param name="panel">Reference to the debug panel to remove.</param> 353 public void RemovePanel(DebugUI.Panel panel) 354 { 355 if (panel == null) 356 return; 357 358 m_Panels.Remove(panel); 359 UpdateReadOnlyCollection(); 360 } 361 362 /// <summary> 363 /// Gets an <see cref="DebugUI.Widget[]"/> matching the given <see cref="DebugUI.Flags"/> 364 /// </summary> 365 /// <param name="flags">The flags of the widget</param> 366 /// <returns>Reference to the requested debug item.</returns> 367 public DebugUI.Widget[] GetItems(DebugUI.Flags flags) 368 { 369 using (ListPool<DebugUI.Widget>.Get(out var temp)) 370 { 371 foreach (var panel in m_Panels) 372 { 373 var widgets = GetItemsFromContainer(flags, panel); 374 temp.AddRange(widgets); 375 } 376 377 return temp.ToArray(); 378 } 379 } 380 381 internal DebugUI.Widget[] GetItemsFromContainer(DebugUI.Flags flags, DebugUI.IContainer container) 382 { 383 using (ListPool<DebugUI.Widget>.Get(out var temp)) 384 { 385 foreach (var child in container.children) 386 { 387 if (child.flags.HasFlag(flags)) 388 { 389 temp.Add(child); 390 continue; 391 } 392 393 if (child is DebugUI.IContainer containerChild) 394 { 395 temp.AddRange(GetItemsFromContainer(flags, containerChild)); 396 } 397 } 398 399 return temp.ToArray(); 400 } 401 } 402 403 /// <summary> 404 /// Get a Debug Item. 405 /// </summary> 406 /// <param name="queryPath">Path of the debug item.</param> 407 /// <returns>Reference to the requested debug item.</returns> 408 public DebugUI.Widget GetItem(string queryPath) 409 { 410 foreach (var panel in m_Panels) 411 { 412 var w = GetItem(queryPath, panel); 413 if (w != null) 414 return w; 415 } 416 417 return null; 418 } 419 420 /// <summary> 421 /// Get a debug item from a specific container. 422 /// </summary> 423 /// <param name="queryPath">Path of the debug item.</param> 424 /// <param name="container">Container to query.</param> 425 /// <returns>Reference to the requested debug item.</returns> 426 DebugUI.Widget GetItem(string queryPath, DebugUI.IContainer container) 427 { 428 foreach (var child in container.children) 429 { 430 if (child.queryPath == queryPath) 431 return child; 432 433 if (child is DebugUI.IContainer containerChild) 434 { 435 var w = GetItem(queryPath, containerChild); 436 if (w != null) 437 return w; 438 } 439 } 440 441 return null; 442 } 443 } 444}