暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

InputStateWindow.cs 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. #if UNITY_EDITOR
  2. using System;
  3. using System.Collections.Generic;
  4. using Unity.Collections.LowLevel.Unsafe;
  5. using UnityEditor;
  6. using UnityEditor.IMGUI.Controls;
  7. using UnityEngine.InputSystem.LowLevel;
  8. ////TODO: add ability to single-step through events
  9. ////TODO: annotate raw memory view with control offset and ranges (probably easiest to put the control tree and raw memory view side by side)
  10. ////TODO: find way to automatically dock the state windows next to their InputDeviceDebuggerWindows
  11. //// (probably needs an extension to the editor UI APIs as the only programmatic docking controls
  12. //// seem to be through GetWindow)
  13. ////TODO: allow setting a C# struct type that we can use to display the layout of the data
  14. ////TODO: for delta state events, highlight the controls included in the event (or show only those)
  15. ////FIXME: need to prevent extra controls appended at end from reading beyond the state buffer
  16. namespace UnityEngine.InputSystem.Editor
  17. {
  18. // Additional window that we can pop open to inspect raw state (either on events or on controls/devices).
  19. internal class InputStateWindow : EditorWindow
  20. {
  21. private const int kBytesPerHexGroup = 1;
  22. private const int kHexGroupsPerLine = 8;
  23. private const int kHexDumpLineHeight = 25;
  24. private const int kOffsetLabelWidth = 30;
  25. private const int kHexGroupWidth = 25;
  26. private const int kBitGroupWidth = 75;
  27. void Update()
  28. {
  29. if (m_PollControlState && m_Control != null)
  30. {
  31. PollBuffersFromControl(m_Control);
  32. Repaint();
  33. }
  34. }
  35. public void InitializeWithEvent(InputEventPtr eventPtr, InputControl control)
  36. {
  37. m_Control = control;
  38. m_PollControlState = false;
  39. m_StateBuffers = new byte[1][];
  40. m_StateBuffers[0] = GetEventStateBuffer(eventPtr, control);
  41. m_SelectedStateBuffer = 0;
  42. titleContent = new GUIContent(control.displayName);
  43. }
  44. public void InitializeWithEvents(InputEventPtr[] eventPtrs, InputControl control)
  45. {
  46. var numEvents = eventPtrs.Length;
  47. m_Control = control;
  48. m_PollControlState = false;
  49. m_StateBuffers = new byte[numEvents][];
  50. for (var i = 0; i < numEvents; ++i)
  51. m_StateBuffers[i] = GetEventStateBuffer(eventPtrs[i], control);
  52. m_CompareStateBuffers = true;
  53. m_ShowDifferentOnly = true;
  54. titleContent = new GUIContent(control.displayName);
  55. }
  56. private unsafe byte[] GetEventStateBuffer(InputEventPtr eventPtr, InputControl control)
  57. {
  58. // Must be an event carrying state.
  59. if (!eventPtr.IsA<StateEvent>() && !eventPtr.IsA<DeltaStateEvent>())
  60. throw new ArgumentException("Event must be state or delta event", nameof(eventPtr));
  61. // Get state data.
  62. void* dataPtr;
  63. uint dataSize;
  64. uint stateSize;
  65. uint stateOffset = 0;
  66. if (eventPtr.IsA<DeltaStateEvent>())
  67. {
  68. var deltaEventPtr = DeltaStateEvent.From(eventPtr);
  69. stateSize = control.stateBlock.alignedSizeInBytes;
  70. stateOffset = deltaEventPtr->stateOffset;
  71. dataPtr = deltaEventPtr->deltaState;
  72. dataSize = deltaEventPtr->deltaStateSizeInBytes;
  73. }
  74. else
  75. {
  76. var stateEventPtr = StateEvent.From(eventPtr);
  77. dataSize = stateSize = stateEventPtr->stateSizeInBytes;
  78. dataPtr = stateEventPtr->state;
  79. }
  80. // Copy event data.
  81. var buffer = new byte[stateSize];
  82. fixed(byte* bufferPtr = buffer)
  83. {
  84. UnsafeUtility.MemCpy(bufferPtr + stateOffset, dataPtr, dataSize);
  85. }
  86. return buffer;
  87. }
  88. public unsafe void InitializeWithControl(InputControl control)
  89. {
  90. m_Control = control;
  91. m_PollControlState = true;
  92. m_SelectedStateBuffer = (int)BufferSelector.Default;
  93. PollBuffersFromControl(control, selectBuffer: true);
  94. titleContent = new GUIContent(control.displayName);
  95. }
  96. private unsafe void PollBuffersFromControl(InputControl control, bool selectBuffer = false)
  97. {
  98. var bufferChoices = new List<GUIContent>();
  99. var bufferChoiceValues = new List<int>();
  100. // Copy front and back buffer state for each update that has valid buffers.
  101. var device = control.device;
  102. var stateSize = control.m_StateBlock.alignedSizeInBytes;
  103. var stateOffset = control.m_StateBlock.byteOffset;
  104. m_StateBuffers = new byte[(int)BufferSelector.COUNT][];
  105. for (var i = 0; i < (int)BufferSelector.COUNT; ++i)
  106. {
  107. var selector = (BufferSelector)i;
  108. var deviceState = TryGetDeviceState(device, selector);
  109. if (deviceState == null)
  110. continue;
  111. var buffer = new byte[stateSize];
  112. fixed(byte* stateDataPtr = buffer)
  113. {
  114. UnsafeUtility.MemCpy(stateDataPtr, (byte*)deviceState + (int)stateOffset, stateSize);
  115. }
  116. m_StateBuffers[i] = buffer;
  117. if (selectBuffer && m_StateBuffers[m_SelectedStateBuffer] == null)
  118. m_SelectedStateBuffer = (int)selector;
  119. bufferChoices.Add(Contents.bufferChoices[i]);
  120. bufferChoiceValues.Add(i);
  121. }
  122. m_BufferChoices = bufferChoices.ToArray();
  123. m_BufferChoiceValues = bufferChoiceValues.ToArray();
  124. }
  125. private static unsafe void* TryGetDeviceState(InputDevice device, BufferSelector selector)
  126. {
  127. var manager = InputSystem.s_Manager;
  128. var deviceIndex = device.m_DeviceIndex;
  129. switch (selector)
  130. {
  131. case BufferSelector.PlayerUpdateFrontBuffer:
  132. if (manager.m_StateBuffers.m_PlayerStateBuffers.valid)
  133. return manager.m_StateBuffers.m_PlayerStateBuffers.GetFrontBuffer(deviceIndex);
  134. break;
  135. case BufferSelector.PlayerUpdateBackBuffer:
  136. if (manager.m_StateBuffers.m_PlayerStateBuffers.valid)
  137. return manager.m_StateBuffers.m_PlayerStateBuffers.GetBackBuffer(deviceIndex);
  138. break;
  139. case BufferSelector.EditorUpdateFrontBuffer:
  140. if (manager.m_StateBuffers.m_EditorStateBuffers.valid)
  141. return manager.m_StateBuffers.m_EditorStateBuffers.GetFrontBuffer(deviceIndex);
  142. break;
  143. case BufferSelector.EditorUpdateBackBuffer:
  144. if (manager.m_StateBuffers.m_EditorStateBuffers.valid)
  145. return manager.m_StateBuffers.m_EditorStateBuffers.GetBackBuffer(deviceIndex);
  146. break;
  147. case BufferSelector.NoiseMaskBuffer:
  148. return manager.m_StateBuffers.noiseMaskBuffer;
  149. case BufferSelector.ResetMaskBuffer:
  150. return manager.m_StateBuffers.resetMaskBuffer;
  151. }
  152. return null;
  153. }
  154. public void OnGUI()
  155. {
  156. if (m_Control == null)
  157. m_ShowRawBytes = true;
  158. // If our state is no longer valid, just close the window.
  159. if (m_StateBuffers == null)
  160. {
  161. Close();
  162. return;
  163. }
  164. GUILayout.BeginHorizontal(EditorStyles.toolbar);
  165. m_PollControlState = GUILayout.Toggle(m_PollControlState, Contents.live, EditorStyles.toolbarButton);
  166. m_ShowRawBytes = GUILayout.Toggle(m_ShowRawBytes, Contents.showRawMemory, EditorStyles.toolbarButton,
  167. GUILayout.Width(150));
  168. m_ShowAsBits = GUILayout.Toggle(m_ShowAsBits, Contents.showBits, EditorStyles.toolbarButton);
  169. if (m_CompareStateBuffers)
  170. {
  171. var showDifferentOnly = GUILayout.Toggle(m_ShowDifferentOnly, Contents.showDifferentOnly,
  172. EditorStyles.toolbarButton, GUILayout.Width(150));
  173. if (showDifferentOnly != m_ShowDifferentOnly && m_ControlTree != null)
  174. {
  175. m_ControlTree.showDifferentOnly = showDifferentOnly;
  176. m_ControlTree.Reload();
  177. }
  178. m_ShowDifferentOnly = showDifferentOnly;
  179. }
  180. // If we have multiple state buffers to choose from and we're not comparing them to each other,
  181. // add dropdown that allows selecting which buffer to display.
  182. if (m_StateBuffers.Length > 1 && !m_CompareStateBuffers)
  183. {
  184. var selectedBuffer = EditorGUILayout.IntPopup(m_SelectedStateBuffer, m_BufferChoices,
  185. m_BufferChoiceValues, EditorStyles.toolbarPopup);
  186. if (selectedBuffer != m_SelectedStateBuffer)
  187. {
  188. m_SelectedStateBuffer = selectedBuffer;
  189. m_ControlTree = null;
  190. }
  191. }
  192. GUILayout.FlexibleSpace();
  193. GUILayout.EndHorizontal();
  194. if (m_ShowRawBytes)
  195. {
  196. DrawHexDump();
  197. }
  198. else
  199. {
  200. if (m_ControlTree == null)
  201. {
  202. if (m_CompareStateBuffers)
  203. {
  204. m_ControlTree = InputControlTreeView.Create(m_Control, m_StateBuffers.Length, ref m_ControlTreeState, ref m_ControlTreeHeaderState);
  205. m_ControlTree.multipleStateBuffers = m_StateBuffers;
  206. m_ControlTree.showDifferentOnly = m_ShowDifferentOnly;
  207. }
  208. else
  209. {
  210. m_ControlTree = InputControlTreeView.Create(m_Control, 1, ref m_ControlTreeState, ref m_ControlTreeHeaderState);
  211. m_ControlTree.stateBuffer = m_StateBuffers[m_SelectedStateBuffer];
  212. }
  213. m_ControlTree.Reload();
  214. m_ControlTree.ExpandAll();
  215. }
  216. var rect = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
  217. m_ControlTree.OnGUI(rect);
  218. }
  219. }
  220. private byte[] TryGetBackBufferForCurrentlySelected()
  221. {
  222. if (m_StateBuffers.Length != (int)BufferSelector.COUNT)
  223. return null;
  224. switch ((BufferSelector)m_SelectedStateBuffer)
  225. {
  226. case BufferSelector.PlayerUpdateFrontBuffer:
  227. return m_StateBuffers[(int)BufferSelector.PlayerUpdateBackBuffer];
  228. case BufferSelector.EditorUpdateFrontBuffer:
  229. return m_StateBuffers[(int)BufferSelector.EditorUpdateBackBuffer];
  230. default:
  231. return null;
  232. }
  233. }
  234. private string FormatByte(byte value)
  235. {
  236. if (m_ShowAsBits)
  237. return Convert.ToString(value, 2).PadLeft(8, '0');
  238. else
  239. return value.ToString("X2");
  240. }
  241. ////TODO: support dumping multiple state side-by-side when comparing
  242. private void DrawHexDump()
  243. {
  244. m_HexDumpScrollPosition = EditorGUILayout.BeginScrollView(m_HexDumpScrollPosition);
  245. var stateBuffer = m_StateBuffers[m_SelectedStateBuffer];
  246. var prevStateBuffer = TryGetBackBufferForCurrentlySelected();
  247. if (prevStateBuffer != null && prevStateBuffer.Length != stateBuffer.Length) // we assume they're same length, otherwise ignore prev buffer
  248. prevStateBuffer = null;
  249. var numBytes = stateBuffer.Length;
  250. var numHexGroups = numBytes / kBytesPerHexGroup + (numBytes % kBytesPerHexGroup > 0 ? 1 : 0);
  251. var numLines = numHexGroups / kHexGroupsPerLine + (numHexGroups % kHexGroupsPerLine > 0 ? 1 : 0);
  252. var currentOffset = 0;
  253. var currentLineRect = EditorGUILayout.GetControlRect(GUILayout.ExpandWidth(true));
  254. currentLineRect.height = kHexDumpLineHeight;
  255. var currentHexGroup = 0;
  256. var currentByte = 0;
  257. ////REVIEW: what would be totally awesome is if this not just displayed a hex dump but also the correlation to current
  258. //// control offset assignments
  259. for (var line = 0; line < numLines; ++line)
  260. {
  261. // Draw offset.
  262. var offsetLabelRect = currentLineRect;
  263. offsetLabelRect.width = kOffsetLabelWidth;
  264. GUI.Label(offsetLabelRect, currentOffset.ToString(), Styles.offsetLabel);
  265. currentOffset += kBytesPerHexGroup * kHexGroupsPerLine;
  266. // Draw hex groups.
  267. var hexGroupRect = offsetLabelRect;
  268. hexGroupRect.x += kOffsetLabelWidth + 10;
  269. hexGroupRect.width = m_ShowAsBits ? kBitGroupWidth : kHexGroupWidth;
  270. for (var group = 0;
  271. group < kHexGroupsPerLine && currentHexGroup < numHexGroups;
  272. ++group, ++currentHexGroup)
  273. {
  274. // Convert bytes to hex.
  275. var hex = string.Empty;
  276. for (var i = 0; i < kBytesPerHexGroup; ++i, ++currentByte)
  277. {
  278. if (currentByte >= numBytes)
  279. {
  280. hex += " ";
  281. continue;
  282. }
  283. var current = FormatByte(stateBuffer[currentByte]);
  284. if (prevStateBuffer == null)
  285. {
  286. hex += current;
  287. continue;
  288. }
  289. var prev = FormatByte(prevStateBuffer[currentByte]);
  290. if (prev.Length != current.Length)
  291. {
  292. hex += current;
  293. continue;
  294. }
  295. for (var j = 0; j < current.Length; ++j)
  296. {
  297. if (current[j] != prev[j])
  298. hex += $"<color=#C84B31FF>{current[j]}</color>";
  299. else
  300. hex += current[j];
  301. }
  302. }
  303. ////TODO: draw alternating backgrounds for the hex groups
  304. GUI.Label(hexGroupRect, hex, style: Styles.hexLabel);
  305. hexGroupRect.x += m_ShowAsBits ? kBitGroupWidth : kHexGroupWidth;
  306. }
  307. currentLineRect.y += kHexDumpLineHeight;
  308. }
  309. EditorGUILayout.EndScrollView();
  310. }
  311. // We copy the state we're inspecting to a buffer we own so that we're safe
  312. // against any mutations.
  313. // When inspecting controls (as opposed to events), we copy all their various
  314. // state buffers and allow switching between them.
  315. [SerializeField] private byte[][] m_StateBuffers;
  316. [SerializeField] private int m_SelectedStateBuffer;
  317. [SerializeField] private bool m_CompareStateBuffers;
  318. [SerializeField] private bool m_ShowDifferentOnly;
  319. [SerializeField] private bool m_ShowRawBytes;
  320. [SerializeField] private bool m_ShowAsBits;
  321. [SerializeField] private bool m_PollControlState;
  322. [SerializeField] private TreeViewState m_ControlTreeState;
  323. [SerializeField] private MultiColumnHeaderState m_ControlTreeHeaderState;
  324. [SerializeField] private Vector2 m_HexDumpScrollPosition;
  325. [NonSerialized] private InputControlTreeView m_ControlTree;
  326. [NonSerialized] private GUIContent[] m_BufferChoices;
  327. [NonSerialized] private int[] m_BufferChoiceValues;
  328. ////FIXME: we lose this on domain reload; how should we recover?
  329. [NonSerialized] private InputControl m_Control;
  330. private enum BufferSelector
  331. {
  332. PlayerUpdateFrontBuffer,
  333. PlayerUpdateBackBuffer,
  334. EditorUpdateFrontBuffer,
  335. EditorUpdateBackBuffer,
  336. NoiseMaskBuffer,
  337. ResetMaskBuffer,
  338. COUNT,
  339. Default = PlayerUpdateFrontBuffer
  340. }
  341. private static class Styles
  342. {
  343. public static GUIStyle offsetLabel = new GUIStyle
  344. {
  345. alignment = TextAnchor.UpperRight,
  346. fontStyle = FontStyle.BoldAndItalic,
  347. font = EditorStyles.boldFont,
  348. fontSize = EditorStyles.boldFont.fontSize - 2,
  349. normal = new GUIStyleState { textColor = Color.black }
  350. };
  351. public static GUIStyle hexLabel = new GUIStyle
  352. {
  353. fontStyle = FontStyle.Normal,
  354. font = EditorGUIUtility.Load("Fonts/RobotoMono/RobotoMono-Regular.ttf") as Font,
  355. fontSize = EditorStyles.label.fontSize + 2,
  356. normal = new GUIStyleState { textColor = Color.white },
  357. richText = true
  358. };
  359. }
  360. private static class Contents
  361. {
  362. public static GUIContent live = new GUIContent("Live");
  363. public static GUIContent showRawMemory = new GUIContent("Display Raw Memory");
  364. public static GUIContent showBits = new GUIContent("Bits/Hex");
  365. public static GUIContent showDifferentOnly = new GUIContent("Show Only Differences");
  366. public static GUIContent[] bufferChoices =
  367. {
  368. new GUIContent("Player (Current)"),
  369. new GUIContent("Player (Previous)"),
  370. new GUIContent("Editor (Current)"),
  371. new GUIContent("Editor (Previous)"),
  372. new GUIContent("Noise Mask"),
  373. new GUIContent("Reset Mask")
  374. };
  375. }
  376. }
  377. }
  378. #endif // UNITY_EDITOR