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}