123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- #if UNITY_EDITOR
- using System;
- using System.Collections.Generic;
- using Unity.Collections.LowLevel.Unsafe;
- using UnityEditor;
- using UnityEditor.IMGUI.Controls;
- using UnityEngine.InputSystem.LowLevel;
-
- ////TODO: add ability to single-step through events
-
- ////TODO: annotate raw memory view with control offset and ranges (probably easiest to put the control tree and raw memory view side by side)
-
- ////TODO: find way to automatically dock the state windows next to their InputDeviceDebuggerWindows
- //// (probably needs an extension to the editor UI APIs as the only programmatic docking controls
- //// seem to be through GetWindow)
-
- ////TODO: allow setting a C# struct type that we can use to display the layout of the data
-
- ////TODO: for delta state events, highlight the controls included in the event (or show only those)
-
- ////FIXME: need to prevent extra controls appended at end from reading beyond the state buffer
-
- namespace UnityEngine.InputSystem.Editor
- {
- // Additional window that we can pop open to inspect raw state (either on events or on controls/devices).
- internal class InputStateWindow : EditorWindow
- {
- private const int kBytesPerHexGroup = 1;
- private const int kHexGroupsPerLine = 8;
- private const int kHexDumpLineHeight = 25;
- private const int kOffsetLabelWidth = 30;
- private const int kHexGroupWidth = 25;
- private const int kBitGroupWidth = 75;
-
- void Update()
- {
- if (m_PollControlState && m_Control != null)
- {
- PollBuffersFromControl(m_Control);
- Repaint();
- }
- }
-
- public void InitializeWithEvent(InputEventPtr eventPtr, InputControl control)
- {
- m_Control = control;
- m_PollControlState = false;
- m_StateBuffers = new byte[1][];
- m_StateBuffers[0] = GetEventStateBuffer(eventPtr, control);
- m_SelectedStateBuffer = 0;
-
- titleContent = new GUIContent(control.displayName);
- }
-
- public void InitializeWithEvents(InputEventPtr[] eventPtrs, InputControl control)
- {
- var numEvents = eventPtrs.Length;
-
- m_Control = control;
- m_PollControlState = false;
- m_StateBuffers = new byte[numEvents][];
- for (var i = 0; i < numEvents; ++i)
- m_StateBuffers[i] = GetEventStateBuffer(eventPtrs[i], control);
- m_CompareStateBuffers = true;
- m_ShowDifferentOnly = true;
-
- titleContent = new GUIContent(control.displayName);
- }
-
- private unsafe byte[] GetEventStateBuffer(InputEventPtr eventPtr, InputControl control)
- {
- // Must be an event carrying state.
- if (!eventPtr.IsA<StateEvent>() && !eventPtr.IsA<DeltaStateEvent>())
- throw new ArgumentException("Event must be state or delta event", nameof(eventPtr));
-
- // Get state data.
- void* dataPtr;
- uint dataSize;
- uint stateSize;
- uint stateOffset = 0;
-
- if (eventPtr.IsA<DeltaStateEvent>())
- {
- var deltaEventPtr = DeltaStateEvent.From(eventPtr);
- stateSize = control.stateBlock.alignedSizeInBytes;
- stateOffset = deltaEventPtr->stateOffset;
- dataPtr = deltaEventPtr->deltaState;
- dataSize = deltaEventPtr->deltaStateSizeInBytes;
- }
- else
- {
- var stateEventPtr = StateEvent.From(eventPtr);
- dataSize = stateSize = stateEventPtr->stateSizeInBytes;
- dataPtr = stateEventPtr->state;
- }
-
- // Copy event data.
- var buffer = new byte[stateSize];
- fixed(byte* bufferPtr = buffer)
- {
- UnsafeUtility.MemCpy(bufferPtr + stateOffset, dataPtr, dataSize);
- }
-
- return buffer;
- }
-
- public unsafe void InitializeWithControl(InputControl control)
- {
- m_Control = control;
- m_PollControlState = true;
- m_SelectedStateBuffer = (int)BufferSelector.Default;
-
- PollBuffersFromControl(control, selectBuffer: true);
-
- titleContent = new GUIContent(control.displayName);
- }
-
- private unsafe void PollBuffersFromControl(InputControl control, bool selectBuffer = false)
- {
- var bufferChoices = new List<GUIContent>();
- var bufferChoiceValues = new List<int>();
-
- // Copy front and back buffer state for each update that has valid buffers.
- var device = control.device;
- var stateSize = control.m_StateBlock.alignedSizeInBytes;
- var stateOffset = control.m_StateBlock.byteOffset;
- m_StateBuffers = new byte[(int)BufferSelector.COUNT][];
- for (var i = 0; i < (int)BufferSelector.COUNT; ++i)
- {
- var selector = (BufferSelector)i;
- var deviceState = TryGetDeviceState(device, selector);
- if (deviceState == null)
- continue;
-
- var buffer = new byte[stateSize];
- fixed(byte* stateDataPtr = buffer)
- {
- UnsafeUtility.MemCpy(stateDataPtr, (byte*)deviceState + (int)stateOffset, stateSize);
- }
- m_StateBuffers[i] = buffer;
-
- if (selectBuffer && m_StateBuffers[m_SelectedStateBuffer] == null)
- m_SelectedStateBuffer = (int)selector;
-
- bufferChoices.Add(Contents.bufferChoices[i]);
- bufferChoiceValues.Add(i);
- }
-
- m_BufferChoices = bufferChoices.ToArray();
- m_BufferChoiceValues = bufferChoiceValues.ToArray();
- }
-
- private static unsafe void* TryGetDeviceState(InputDevice device, BufferSelector selector)
- {
- var manager = InputSystem.s_Manager;
- var deviceIndex = device.m_DeviceIndex;
-
- switch (selector)
- {
- case BufferSelector.PlayerUpdateFrontBuffer:
- if (manager.m_StateBuffers.m_PlayerStateBuffers.valid)
- return manager.m_StateBuffers.m_PlayerStateBuffers.GetFrontBuffer(deviceIndex);
- break;
- case BufferSelector.PlayerUpdateBackBuffer:
- if (manager.m_StateBuffers.m_PlayerStateBuffers.valid)
- return manager.m_StateBuffers.m_PlayerStateBuffers.GetBackBuffer(deviceIndex);
- break;
- case BufferSelector.EditorUpdateFrontBuffer:
- if (manager.m_StateBuffers.m_EditorStateBuffers.valid)
- return manager.m_StateBuffers.m_EditorStateBuffers.GetFrontBuffer(deviceIndex);
- break;
- case BufferSelector.EditorUpdateBackBuffer:
- if (manager.m_StateBuffers.m_EditorStateBuffers.valid)
- return manager.m_StateBuffers.m_EditorStateBuffers.GetBackBuffer(deviceIndex);
- break;
- case BufferSelector.NoiseMaskBuffer:
- return manager.m_StateBuffers.noiseMaskBuffer;
- case BufferSelector.ResetMaskBuffer:
- return manager.m_StateBuffers.resetMaskBuffer;
- }
-
- return null;
- }
-
- public void OnGUI()
- {
- if (m_Control == null)
- m_ShowRawBytes = true;
-
- // If our state is no longer valid, just close the window.
- if (m_StateBuffers == null)
- {
- Close();
- return;
- }
-
- GUILayout.BeginHorizontal(EditorStyles.toolbar);
- m_PollControlState = GUILayout.Toggle(m_PollControlState, Contents.live, EditorStyles.toolbarButton);
-
- m_ShowRawBytes = GUILayout.Toggle(m_ShowRawBytes, Contents.showRawMemory, EditorStyles.toolbarButton,
- GUILayout.Width(150));
-
- m_ShowAsBits = GUILayout.Toggle(m_ShowAsBits, Contents.showBits, EditorStyles.toolbarButton);
-
- if (m_CompareStateBuffers)
- {
- var showDifferentOnly = GUILayout.Toggle(m_ShowDifferentOnly, Contents.showDifferentOnly,
- EditorStyles.toolbarButton, GUILayout.Width(150));
- if (showDifferentOnly != m_ShowDifferentOnly && m_ControlTree != null)
- {
- m_ControlTree.showDifferentOnly = showDifferentOnly;
- m_ControlTree.Reload();
- }
-
- m_ShowDifferentOnly = showDifferentOnly;
- }
-
- // If we have multiple state buffers to choose from and we're not comparing them to each other,
- // add dropdown that allows selecting which buffer to display.
- if (m_StateBuffers.Length > 1 && !m_CompareStateBuffers)
- {
- var selectedBuffer = EditorGUILayout.IntPopup(m_SelectedStateBuffer, m_BufferChoices,
- m_BufferChoiceValues, EditorStyles.toolbarPopup);
- if (selectedBuffer != m_SelectedStateBuffer)
- {
- m_SelectedStateBuffer = selectedBuffer;
- m_ControlTree = null;
- }
- }
-
- GUILayout.FlexibleSpace();
- GUILayout.EndHorizontal();
-
- if (m_ShowRawBytes)
- {
- DrawHexDump();
- }
- else
- {
- if (m_ControlTree == null)
- {
- if (m_CompareStateBuffers)
- {
- m_ControlTree = InputControlTreeView.Create(m_Control, m_StateBuffers.Length, ref m_ControlTreeState, ref m_ControlTreeHeaderState);
- m_ControlTree.multipleStateBuffers = m_StateBuffers;
- m_ControlTree.showDifferentOnly = m_ShowDifferentOnly;
- }
- else
- {
- m_ControlTree = InputControlTreeView.Create(m_Control, 1, ref m_ControlTreeState, ref m_ControlTreeHeaderState);
- m_ControlTree.stateBuffer = m_StateBuffers[m_SelectedStateBuffer];
- }
- m_ControlTree.Reload();
- m_ControlTree.ExpandAll();
- }
-
- var rect = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
- m_ControlTree.OnGUI(rect);
- }
- }
-
- private byte[] TryGetBackBufferForCurrentlySelected()
- {
- if (m_StateBuffers.Length != (int)BufferSelector.COUNT)
- return null;
-
- switch ((BufferSelector)m_SelectedStateBuffer)
- {
- case BufferSelector.PlayerUpdateFrontBuffer:
- return m_StateBuffers[(int)BufferSelector.PlayerUpdateBackBuffer];
- case BufferSelector.EditorUpdateFrontBuffer:
- return m_StateBuffers[(int)BufferSelector.EditorUpdateBackBuffer];
- default:
- return null;
- }
- }
-
- private string FormatByte(byte value)
- {
- if (m_ShowAsBits)
- return Convert.ToString(value, 2).PadLeft(8, '0');
- else
- return value.ToString("X2");
- }
-
- ////TODO: support dumping multiple state side-by-side when comparing
- private void DrawHexDump()
- {
- m_HexDumpScrollPosition = EditorGUILayout.BeginScrollView(m_HexDumpScrollPosition);
-
- var stateBuffer = m_StateBuffers[m_SelectedStateBuffer];
- var prevStateBuffer = TryGetBackBufferForCurrentlySelected();
- if (prevStateBuffer != null && prevStateBuffer.Length != stateBuffer.Length) // we assume they're same length, otherwise ignore prev buffer
- prevStateBuffer = null;
- var numBytes = stateBuffer.Length;
- var numHexGroups = numBytes / kBytesPerHexGroup + (numBytes % kBytesPerHexGroup > 0 ? 1 : 0);
- var numLines = numHexGroups / kHexGroupsPerLine + (numHexGroups % kHexGroupsPerLine > 0 ? 1 : 0);
- var currentOffset = 0;
- var currentLineRect = EditorGUILayout.GetControlRect(GUILayout.ExpandWidth(true));
- currentLineRect.height = kHexDumpLineHeight;
- var currentHexGroup = 0;
- var currentByte = 0;
-
- ////REVIEW: what would be totally awesome is if this not just displayed a hex dump but also the correlation to current
- //// control offset assignments
-
- for (var line = 0; line < numLines; ++line)
- {
- // Draw offset.
- var offsetLabelRect = currentLineRect;
- offsetLabelRect.width = kOffsetLabelWidth;
- GUI.Label(offsetLabelRect, currentOffset.ToString(), Styles.offsetLabel);
- currentOffset += kBytesPerHexGroup * kHexGroupsPerLine;
-
- // Draw hex groups.
- var hexGroupRect = offsetLabelRect;
- hexGroupRect.x += kOffsetLabelWidth + 10;
- hexGroupRect.width = m_ShowAsBits ? kBitGroupWidth : kHexGroupWidth;
- for (var group = 0;
- group < kHexGroupsPerLine && currentHexGroup < numHexGroups;
- ++group, ++currentHexGroup)
- {
- // Convert bytes to hex.
- var hex = string.Empty;
-
- for (var i = 0; i < kBytesPerHexGroup; ++i, ++currentByte)
- {
- if (currentByte >= numBytes)
- {
- hex += " ";
- continue;
- }
-
- var current = FormatByte(stateBuffer[currentByte]);
- if (prevStateBuffer == null)
- {
- hex += current;
- continue;
- }
-
- var prev = FormatByte(prevStateBuffer[currentByte]);
- if (prev.Length != current.Length)
- {
- hex += current;
- continue;
- }
-
- for (var j = 0; j < current.Length; ++j)
- {
- if (current[j] != prev[j])
- hex += $"<color=#C84B31FF>{current[j]}</color>";
- else
- hex += current[j];
- }
- }
-
- ////TODO: draw alternating backgrounds for the hex groups
-
- GUI.Label(hexGroupRect, hex, style: Styles.hexLabel);
- hexGroupRect.x += m_ShowAsBits ? kBitGroupWidth : kHexGroupWidth;
- }
-
- currentLineRect.y += kHexDumpLineHeight;
- }
-
- EditorGUILayout.EndScrollView();
- }
-
- // We copy the state we're inspecting to a buffer we own so that we're safe
- // against any mutations.
- // When inspecting controls (as opposed to events), we copy all their various
- // state buffers and allow switching between them.
- [SerializeField] private byte[][] m_StateBuffers;
- [SerializeField] private int m_SelectedStateBuffer;
- [SerializeField] private bool m_CompareStateBuffers;
- [SerializeField] private bool m_ShowDifferentOnly;
- [SerializeField] private bool m_ShowRawBytes;
- [SerializeField] private bool m_ShowAsBits;
- [SerializeField] private bool m_PollControlState;
- [SerializeField] private TreeViewState m_ControlTreeState;
- [SerializeField] private MultiColumnHeaderState m_ControlTreeHeaderState;
- [SerializeField] private Vector2 m_HexDumpScrollPosition;
-
- [NonSerialized] private InputControlTreeView m_ControlTree;
- [NonSerialized] private GUIContent[] m_BufferChoices;
- [NonSerialized] private int[] m_BufferChoiceValues;
-
- ////FIXME: we lose this on domain reload; how should we recover?
- [NonSerialized] private InputControl m_Control;
-
- private enum BufferSelector
- {
- PlayerUpdateFrontBuffer,
- PlayerUpdateBackBuffer,
- EditorUpdateFrontBuffer,
- EditorUpdateBackBuffer,
- NoiseMaskBuffer,
- ResetMaskBuffer,
- COUNT,
- Default = PlayerUpdateFrontBuffer
- }
-
- private static class Styles
- {
- public static GUIStyle offsetLabel = new GUIStyle
- {
- alignment = TextAnchor.UpperRight,
- fontStyle = FontStyle.BoldAndItalic,
- font = EditorStyles.boldFont,
- fontSize = EditorStyles.boldFont.fontSize - 2,
- normal = new GUIStyleState { textColor = Color.black }
- };
-
- public static GUIStyle hexLabel = new GUIStyle
- {
- fontStyle = FontStyle.Normal,
- font = EditorGUIUtility.Load("Fonts/RobotoMono/RobotoMono-Regular.ttf") as Font,
- fontSize = EditorStyles.label.fontSize + 2,
- normal = new GUIStyleState { textColor = Color.white },
- richText = true
- };
- }
-
- private static class Contents
- {
- public static GUIContent live = new GUIContent("Live");
- public static GUIContent showRawMemory = new GUIContent("Display Raw Memory");
- public static GUIContent showBits = new GUIContent("Bits/Hex");
- public static GUIContent showDifferentOnly = new GUIContent("Show Only Differences");
- public static GUIContent[] bufferChoices =
- {
- new GUIContent("Player (Current)"),
- new GUIContent("Player (Previous)"),
- new GUIContent("Editor (Current)"),
- new GUIContent("Editor (Previous)"),
- new GUIContent("Noise Mask"),
- new GUIContent("Reset Mask")
- };
- }
- }
- }
- #endif // UNITY_EDITOR
|