UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
////TODO: survive domain reload properly
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
internal class InputActionDebuggerWindow : EditorWindow
|
||||
{
|
||||
[NonSerialized] private InputAction m_Action = null;
|
||||
|
||||
public static void CreateOrShowExisting(InputAction action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new System.ArgumentNullException(nameof(action));
|
||||
|
||||
// See if we have an existing window for the action and if so pop it in front.
|
||||
if (s_OpenDebuggerWindows != null)
|
||||
{
|
||||
for (var i = 0; i < s_OpenDebuggerWindows.Count; ++i)
|
||||
{
|
||||
var existingWindow = s_OpenDebuggerWindows[i];
|
||||
if (ReferenceEquals(existingWindow.m_Action, action))
|
||||
{
|
||||
existingWindow.Show();
|
||||
existingWindow.Focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No, so create a new one.
|
||||
var window = CreateInstance<InputActionDebuggerWindow>();
|
||||
window.Show();
|
||||
window.titleContent = new GUIContent(action.name);
|
||||
window.AddToList();
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
}
|
||||
|
||||
private static List<InputActionDebuggerWindow> s_OpenDebuggerWindows;
|
||||
|
||||
private void AddToList()
|
||||
{
|
||||
if (s_OpenDebuggerWindows == null)
|
||||
s_OpenDebuggerWindows = new List<InputActionDebuggerWindow>();
|
||||
if (!s_OpenDebuggerWindows.Contains(this))
|
||||
s_OpenDebuggerWindows.Add(this);
|
||||
}
|
||||
|
||||
private void RemoveFromList()
|
||||
{
|
||||
if (s_OpenDebuggerWindows != null)
|
||||
s_OpenDebuggerWindows.Remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d659406a304446c599b9ec454663c46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3f24f77c633a444893b75d39ae4fa3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,628 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
|
||||
#if UNITY_6000_2_OR_NEWER
|
||||
using TreeViewState = UnityEditor.IMGUI.Controls.TreeViewState<int>;
|
||||
#endif
|
||||
|
||||
////TODO: allow selecting events and saving out only the selected ones
|
||||
|
||||
////TODO: add the ability for the debugger to just generate input on the device according to the controls it finds; good for testing
|
||||
|
||||
////TODO: add commands to event trace (also clickable)
|
||||
|
||||
////TODO: add diff-to-previous-event ability to event window
|
||||
|
||||
////FIXME: the repaint triggered from IInputStateCallbackReceiver somehow comes with a significant delay
|
||||
|
||||
////TODO: Add "Remote:" field in list that also has a button for local devices that allows to mirror them and their input
|
||||
//// into connected players
|
||||
|
||||
////TODO: this window should help diagnose problems in the event stream (e.g. ignored state events and why they were ignored)
|
||||
|
||||
////TODO: add toggle to that switches to displaying raw control values
|
||||
|
||||
////TODO: allow adding visualizers (or automatically add them in cases) to control that show value over time (using InputStateHistory)
|
||||
|
||||
////TODO: show default states of controls
|
||||
|
||||
////TODO: provide ability to save and load event traces; also ability to record directly to a file
|
||||
////TODO: provide ability to scrub back and forth through history
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
// Shows status and activity of a single input device in a separate window.
|
||||
// Can also be used to alter the state of a device by making up state events.
|
||||
internal sealed class InputDeviceDebuggerWindow : EditorWindow, ISerializationCallbackReceiver, IDisposable
|
||||
{
|
||||
// ATM the debugger window is super slow and repaints are very expensive. So keep the total
|
||||
// number of events we can fit at a relatively low size until we have fixed that problem.
|
||||
private const int kDefaultEventTraceSizeInKB = 512;
|
||||
private const int kMaxEventsPerTrace = 1024;
|
||||
|
||||
internal static InlinedArray<Action<InputDevice>> s_OnToolbarGUIActions;
|
||||
|
||||
public static event Action<InputDevice> onToolbarGUI
|
||||
{
|
||||
add => s_OnToolbarGUIActions.Append(value);
|
||||
remove => s_OnToolbarGUIActions.Remove(value);
|
||||
}
|
||||
|
||||
public static void CreateOrShowExisting(InputDevice device)
|
||||
{
|
||||
if (device == null)
|
||||
throw new ArgumentNullException(nameof(device));
|
||||
|
||||
// See if we have an existing window for the device and if so pop it
|
||||
// in front.
|
||||
if (s_OpenDebuggerWindows != null)
|
||||
{
|
||||
for (var i = 0; i < s_OpenDebuggerWindows.Count; ++i)
|
||||
{
|
||||
var existingWindow = s_OpenDebuggerWindows[i];
|
||||
if (existingWindow.m_DeviceId == device.deviceId)
|
||||
{
|
||||
existingWindow.Show();
|
||||
existingWindow.Focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No, so create a new one.
|
||||
var window = CreateInstance<InputDeviceDebuggerWindow>();
|
||||
window.InitializeWith(device);
|
||||
window.minSize = new Vector2(270, 300);
|
||||
window.Show();
|
||||
window.titleContent = new GUIContent(device.name);
|
||||
}
|
||||
|
||||
internal void OnDestroy()
|
||||
{
|
||||
if (m_Device != null)
|
||||
{
|
||||
RemoveFromList();
|
||||
|
||||
InputSystem.onDeviceChange -= OnDeviceChange;
|
||||
InputState.onChange -= OnDeviceStateChange;
|
||||
InputSystem.onSettingsChange -= NeedControlValueRefresh;
|
||||
Application.focusChanged -= OnApplicationFocusChange;
|
||||
EditorApplication.playModeStateChanged += OnPlayModeChange;
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
}
|
||||
|
||||
m_EventTrace?.Dispose();
|
||||
m_EventTrace = null;
|
||||
|
||||
m_ReplayController?.Dispose();
|
||||
m_ReplayController = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_EventTrace?.Dispose();
|
||||
m_ReplayController?.Dispose();
|
||||
}
|
||||
|
||||
internal void OnGUI()
|
||||
{
|
||||
// Find device again if we've gone through a domain reload.
|
||||
if (m_Device == null)
|
||||
{
|
||||
m_Device = InputSystem.GetDeviceById(m_DeviceId);
|
||||
|
||||
if (m_Device == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox(Styles.notFoundHelpText, MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeWith(m_Device);
|
||||
}
|
||||
|
||||
////FIXME: with ExpandHeight(false), editor still expands height for some reason....
|
||||
EditorGUILayout.BeginVertical("OL Box", GUILayout.Height(170));// GUILayout.ExpandHeight(false));
|
||||
EditorGUILayout.LabelField("Name", m_Device.name);
|
||||
EditorGUILayout.LabelField("Layout", m_Device.layout);
|
||||
EditorGUILayout.LabelField("Type", m_Device.GetType().Name);
|
||||
if (!string.IsNullOrEmpty(m_Device.description.interfaceName))
|
||||
EditorGUILayout.LabelField("Interface", m_Device.description.interfaceName);
|
||||
if (!string.IsNullOrEmpty(m_Device.description.product))
|
||||
EditorGUILayout.LabelField("Product", m_Device.description.product);
|
||||
if (!string.IsNullOrEmpty(m_Device.description.manufacturer))
|
||||
EditorGUILayout.LabelField("Manufacturer", m_Device.description.manufacturer);
|
||||
if (!string.IsNullOrEmpty(m_Device.description.version))
|
||||
EditorGUILayout.LabelField("Version", m_Device.description.version);
|
||||
if (!string.IsNullOrEmpty(m_Device.description.serial))
|
||||
EditorGUILayout.LabelField("Serial Number", m_Device.description.serial);
|
||||
EditorGUILayout.LabelField("Device ID", m_DeviceIdString);
|
||||
if (!string.IsNullOrEmpty(m_DeviceUsagesString))
|
||||
EditorGUILayout.LabelField("Usages", m_DeviceUsagesString);
|
||||
if (!string.IsNullOrEmpty(m_DeviceFlagsString))
|
||||
EditorGUILayout.LabelField("Flags", m_DeviceFlagsString);
|
||||
if (m_Device is Keyboard)
|
||||
EditorGUILayout.LabelField("Keyboard Layout", ((Keyboard)m_Device).keyboardLayout);
|
||||
const string sampleFrequencyTooltip = "Displays the current event or sample frequency of this device in Hertz (Hz) averaged over measurement period of 1 second. " +
|
||||
"The target frequency is device and backend dependent and may not be supported by all devices nor backends. " +
|
||||
"The Polling Frequency indicates system polling target frequency.";
|
||||
if (!string.IsNullOrEmpty(m_DeviceFrequencyString))
|
||||
EditorGUILayout.LabelField(new GUIContent("Sample Frequency", sampleFrequencyTooltip), new GUIContent(m_DeviceFrequencyString), EditorStyles.label);
|
||||
const string processingDelayTooltip =
|
||||
"Displays the average, minimum and maximum observed input processing delay. This shows the time from " +
|
||||
"when an input event is first created within Unity until its processed by the Input System. " +
|
||||
"Note that this excludes additional input latency introduced by OS, driver or device communication. " +
|
||||
"It also doesn't include output latency introduced by script processing, rendering, swap-chains, display refresh latency etc.";
|
||||
if (!string.IsNullOrEmpty(m_DeviceLatencyString))
|
||||
EditorGUILayout.LabelField(new GUIContent("Processing Delay", processingDelayTooltip),
|
||||
new GUIContent(m_DeviceLatencyString), EditorStyles.label);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
DrawControlTree();
|
||||
DrawEventList();
|
||||
}
|
||||
|
||||
private void DrawControlTree()
|
||||
{
|
||||
var label = m_InputUpdateTypeShownInControlTree == InputUpdateType.Editor
|
||||
? Contents.editorStateContent
|
||||
: Contents.playerStateContent;
|
||||
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
GUILayout.Label(label, GUILayout.MinWidth(100), GUILayout.ExpandWidth(true));
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
// Allow plugins to add toolbar buttons.
|
||||
for (var i = 0; i < s_OnToolbarGUIActions.length; ++i)
|
||||
s_OnToolbarGUIActions[i](m_Device);
|
||||
|
||||
if (GUILayout.Button(Contents.stateContent, EditorStyles.toolbarButton))
|
||||
{
|
||||
var window = CreateInstance<InputStateWindow>();
|
||||
window.InitializeWithControl(m_Device);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (m_NeedControlValueRefresh)
|
||||
{
|
||||
RefreshControlTreeValues();
|
||||
m_NeedControlValueRefresh = false;
|
||||
}
|
||||
|
||||
if (m_Device.disabledInFrontend)
|
||||
EditorGUILayout.HelpBox("Device is DISABLED. Control values will not receive updates. "
|
||||
+ "To force-enable the device, you can right-click it in the input debugger and use 'Enable Device'.", MessageType.Info);
|
||||
|
||||
var rect = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
|
||||
m_ControlTree.OnGUI(rect);
|
||||
}
|
||||
|
||||
private void DrawEventList()
|
||||
{
|
||||
GUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
GUILayout.Label("Events", GUILayout.MinWidth(100), GUILayout.ExpandWidth(true));
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (m_ReplayController != null && !m_ReplayController.finished)
|
||||
EditorGUILayout.LabelField("Playing...", EditorStyles.miniLabel);
|
||||
|
||||
// Text field to determine size of event trace.
|
||||
var currentTraceSizeInKb = m_EventTrace.allocatedSizeInBytes / 1024;
|
||||
var oldSizeText = currentTraceSizeInKb + " KB";
|
||||
var newSizeText = EditorGUILayout.DelayedTextField(oldSizeText, Styles.toolbarTextField, GUILayout.Width(75));
|
||||
if (oldSizeText != newSizeText && StringHelpers.FromNicifiedMemorySize(newSizeText, out var newSizeInBytes, defaultMultiplier: 1024))
|
||||
m_EventTrace.Resize(newSizeInBytes);
|
||||
|
||||
// Button to clear event trace.
|
||||
if (GUILayout.Button(Contents.clearContent, Styles.toolbarButton))
|
||||
{
|
||||
m_EventTrace.Clear();
|
||||
m_EventTree.Reload();
|
||||
}
|
||||
|
||||
// Button to disable event tracing.
|
||||
// NOTE: We force-disable event tracing while a replay is in progress.
|
||||
using (new EditorGUI.DisabledScope(m_ReplayController != null && !m_ReplayController.finished))
|
||||
{
|
||||
var eventTraceDisabledNow = GUILayout.Toggle(!m_EventTraceDisabled, Contents.pauseContent, Styles.toolbarButton);
|
||||
if (eventTraceDisabledNow != m_EventTraceDisabled)
|
||||
{
|
||||
m_EventTraceDisabled = eventTraceDisabledNow;
|
||||
if (eventTraceDisabledNow)
|
||||
m_EventTrace.Disable();
|
||||
else
|
||||
m_EventTrace.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
// Button to toggle recording of frame markers.
|
||||
m_EventTrace.recordFrameMarkers =
|
||||
GUILayout.Toggle(m_EventTrace.recordFrameMarkers, Contents.recordFramesContent, Styles.toolbarButton);
|
||||
|
||||
// Button to save event trace to file.
|
||||
if (GUILayout.Button(Contents.saveContent, Styles.toolbarButton))
|
||||
{
|
||||
var defaultName = m_Device?.displayName + ".inputtrace";
|
||||
var fileName = EditorUtility.SaveFilePanel("Choose where to save event trace", string.Empty, defaultName, "inputtrace");
|
||||
if (!string.IsNullOrEmpty(fileName))
|
||||
m_EventTrace.WriteTo(fileName);
|
||||
}
|
||||
|
||||
// Button to load event trace from file.
|
||||
if (GUILayout.Button(Contents.loadContent, Styles.toolbarButton))
|
||||
{
|
||||
var fileName = EditorUtility.OpenFilePanel("Choose event trace to load", string.Empty, "inputtrace");
|
||||
if (!string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
// If replay is in progress, stop it.
|
||||
if (m_ReplayController != null)
|
||||
{
|
||||
m_ReplayController.Dispose();
|
||||
m_ReplayController = null;
|
||||
}
|
||||
|
||||
// Make sure event trace isn't recording while we're playing.
|
||||
m_EventTrace.Disable();
|
||||
m_EventTraceDisabled = true;
|
||||
|
||||
m_EventTrace.ReadFrom(fileName);
|
||||
m_EventTree.Reload();
|
||||
|
||||
m_ReplayController = m_EventTrace.Replay()
|
||||
.PlayAllFramesOneByOne()
|
||||
.OnFinished(() =>
|
||||
{
|
||||
m_ReplayController.Dispose();
|
||||
m_ReplayController = null;
|
||||
Repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (m_ReloadEventTree)
|
||||
{
|
||||
m_ReloadEventTree = false;
|
||||
m_EventTree.Reload();
|
||||
}
|
||||
|
||||
var rect = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
|
||||
m_EventTree.OnGUI(rect);
|
||||
}
|
||||
|
||||
////FIXME: some of the state in here doesn't get refreshed when it's changed on the device
|
||||
private void InitializeWith(InputDevice device)
|
||||
{
|
||||
m_Device = device;
|
||||
m_DeviceId = device.deviceId;
|
||||
m_DeviceIdString = device.deviceId.ToString();
|
||||
m_DeviceUsagesString = string.Join(", ", device.usages.Select(x => x.ToString()).ToArray());
|
||||
|
||||
UpdateDeviceFlags();
|
||||
|
||||
// Query the sampling frequency of the device.
|
||||
// We do this synchronously here for simplicity.
|
||||
var queryFrequency = QuerySamplingFrequencyCommand.Create();
|
||||
var result = device.ExecuteCommand(ref queryFrequency);
|
||||
var targetFrequency = float.NaN;
|
||||
if (result >= 0)
|
||||
targetFrequency = queryFrequency.frequency;
|
||||
var realtimeSinceStartup = Time.realtimeSinceStartupAsDouble;
|
||||
m_SampleFrequencyCalculator = new SampleFrequencyCalculator(targetFrequency, realtimeSinceStartup);
|
||||
m_InputLatencyCalculator = new InputLatencyCalculator(realtimeSinceStartup);
|
||||
|
||||
// Set up event trace. The default trace size of 512kb fits a ton of events and will
|
||||
// likely bog down the UI if we try to display that many events. Instead, come up
|
||||
// with a more reasonable sized based on the state size of the device.
|
||||
if (m_EventTrace == null)
|
||||
{
|
||||
var deviceStateSize = (int)device.stateBlock.alignedSizeInBytes;
|
||||
var traceSizeInBytes = (kDefaultEventTraceSizeInKB * 1024).AlignToMultipleOf(deviceStateSize);
|
||||
if (traceSizeInBytes / deviceStateSize > kMaxEventsPerTrace)
|
||||
traceSizeInBytes = kMaxEventsPerTrace * deviceStateSize;
|
||||
|
||||
m_EventTrace =
|
||||
new InputEventTrace(traceSizeInBytes)
|
||||
{
|
||||
deviceId = device.deviceId
|
||||
};
|
||||
}
|
||||
|
||||
m_EventTrace.onEvent += _ => m_ReloadEventTree = true;
|
||||
if (!m_EventTraceDisabled)
|
||||
m_EventTrace.Enable();
|
||||
|
||||
// Set up event tree.
|
||||
m_EventTree = InputEventTreeView.Create(m_Device, m_EventTrace, ref m_EventTreeState, ref m_EventTreeHeaderState);
|
||||
|
||||
// Set up control tree.
|
||||
m_ControlTree = InputControlTreeView.Create(m_Device, 1, ref m_ControlTreeState, ref m_ControlTreeHeaderState);
|
||||
m_ControlTree.Reload();
|
||||
m_ControlTree.ExpandAll();
|
||||
|
||||
AddToList();
|
||||
|
||||
InputSystem.onSettingsChange += NeedControlValueRefresh;
|
||||
InputSystem.onDeviceChange += OnDeviceChange;
|
||||
InputState.onChange += OnDeviceStateChange;
|
||||
Application.focusChanged += OnApplicationFocusChange;
|
||||
EditorApplication.playModeStateChanged += OnPlayModeChange;
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
}
|
||||
|
||||
private void OnEditorUpdate()
|
||||
{
|
||||
StringBuilder sb = null;
|
||||
bool needControlValueRefresh = false;
|
||||
var realtimeSinceStartup = Time.realtimeSinceStartupAsDouble;
|
||||
if (m_SampleFrequencyCalculator.Update(realtimeSinceStartup))
|
||||
{
|
||||
m_DeviceFrequencyString = CreateDeviceFrequencyString(ref sb);
|
||||
needControlValueRefresh = true;
|
||||
}
|
||||
if (m_InputLatencyCalculator.Update(realtimeSinceStartup))
|
||||
{
|
||||
m_DeviceLatencyString = CreateDeviceLatencyString(ref sb);
|
||||
needControlValueRefresh = true;
|
||||
}
|
||||
if (needControlValueRefresh)
|
||||
NeedControlValueRefresh();
|
||||
}
|
||||
|
||||
private string CreateDeviceFrequencyString(ref StringBuilder sb)
|
||||
{
|
||||
if (sb == null)
|
||||
sb = new StringBuilder();
|
||||
else
|
||||
sb.Clear();
|
||||
|
||||
// Display achievable frequency for device
|
||||
const string frequencyFormat = "0.000 Hz";
|
||||
sb.Append(m_SampleFrequencyCalculator.frequency.ToString(frequencyFormat, CultureInfo.InvariantCulture));
|
||||
|
||||
// Display target frequency reported for device
|
||||
sb.Append(" (Target @ ");
|
||||
sb.Append(float.IsNaN(m_SampleFrequencyCalculator.targetFrequency)
|
||||
? "n/a"
|
||||
: m_SampleFrequencyCalculator.targetFrequency.ToString(frequencyFormat));
|
||||
|
||||
// Display system-wide polling frequency
|
||||
sb.Append(", Polling-Frequency @ ");
|
||||
sb.Append(InputSystem.pollingFrequency.ToString(frequencyFormat));
|
||||
sb.Append(')');
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void FormatLatency(StringBuilder sb, float value)
|
||||
{
|
||||
const string latencyFormat = "0.000 ms";
|
||||
if (float.IsNaN(value))
|
||||
{
|
||||
sb.Append("n/a");
|
||||
return;
|
||||
}
|
||||
|
||||
var millis = 1000.0f * value;
|
||||
sb.Append(millis <= 1000.0f
|
||||
? (millis).ToString(latencyFormat, CultureInfo.InvariantCulture)
|
||||
: ">1000.0 ms");
|
||||
}
|
||||
|
||||
private string CreateDeviceLatencyString(ref StringBuilder sb)
|
||||
{
|
||||
if (sb == null)
|
||||
sb = new StringBuilder();
|
||||
else
|
||||
sb.Clear();
|
||||
|
||||
// Display latency in seconds for device
|
||||
sb.Append("Average: ");
|
||||
FormatLatency(sb, m_InputLatencyCalculator.averageLatencySeconds);
|
||||
sb.Append(", Min: ");
|
||||
FormatLatency(sb, m_InputLatencyCalculator.minLatencySeconds);
|
||||
sb.Append(", Max: ");
|
||||
FormatLatency(sb, m_InputLatencyCalculator.maxLatencySeconds);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void UpdateDeviceFlags()
|
||||
{
|
||||
var flags = new List<string>();
|
||||
if (m_Device.native)
|
||||
flags.Add("Native");
|
||||
if (m_Device.remote)
|
||||
flags.Add("Remote");
|
||||
if (m_Device.updateBeforeRender)
|
||||
flags.Add("UpdateBeforeRender");
|
||||
if (m_Device.hasStateCallbacks)
|
||||
flags.Add("HasStateCallbacks");
|
||||
if (m_Device.hasEventMerger)
|
||||
flags.Add("HasEventMerger");
|
||||
if (m_Device.hasEventPreProcessor)
|
||||
flags.Add("HasEventPreProcessor");
|
||||
if (m_Device.disabledInFrontend)
|
||||
flags.Add("DisabledInFrontend");
|
||||
if (m_Device.disabledInRuntime)
|
||||
flags.Add("DisabledInRuntime");
|
||||
if (m_Device.disabledWhileInBackground)
|
||||
flags.Add("DisabledWhileInBackground");
|
||||
if (m_Device.canDeviceRunInBackground)
|
||||
flags.Add("CanRunInBackground");
|
||||
m_DeviceFlags = m_Device.m_DeviceFlags;
|
||||
m_DeviceFlagsString = string.Join(", ", flags.ToArray());
|
||||
}
|
||||
|
||||
private void RefreshControlTreeValues()
|
||||
{
|
||||
m_InputUpdateTypeShownInControlTree = DetermineUpdateTypeToShow(m_Device);
|
||||
var currentUpdateType = InputState.currentUpdateType;
|
||||
|
||||
InputStateBuffers.SwitchTo(InputSystem.s_Manager.m_StateBuffers, m_InputUpdateTypeShownInControlTree);
|
||||
m_ControlTree.RefreshControlValues();
|
||||
InputStateBuffers.SwitchTo(InputSystem.s_Manager.m_StateBuffers, currentUpdateType);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "device", Justification = "Keep this for future implementation")]
|
||||
internal static InputUpdateType DetermineUpdateTypeToShow(InputDevice device)
|
||||
{
|
||||
if (EditorApplication.isPlaying)
|
||||
{
|
||||
// In play mode, while playing, we show player state. Period.
|
||||
|
||||
switch (InputSystem.settings.updateMode)
|
||||
{
|
||||
case InputSettings.UpdateMode.ProcessEventsManually:
|
||||
return InputUpdateType.Manual;
|
||||
|
||||
case InputSettings.UpdateMode.ProcessEventsInFixedUpdate:
|
||||
return InputUpdateType.Fixed;
|
||||
|
||||
default:
|
||||
return InputUpdateType.Dynamic;
|
||||
}
|
||||
}
|
||||
|
||||
// Outside of play mode, always show editor state.
|
||||
return InputUpdateType.Editor;
|
||||
}
|
||||
|
||||
// We will lose our device on domain reload and then look it back up the first
|
||||
// time we hit a repaint after a reload. By that time, the input system should have
|
||||
// fully come back to life as well.
|
||||
private InputDevice m_Device;
|
||||
private string m_DeviceIdString;
|
||||
private string m_DeviceUsagesString;
|
||||
private string m_DeviceFlagsString;
|
||||
private string m_DeviceFrequencyString;
|
||||
private string m_DeviceLatencyString;
|
||||
private InputDevice.DeviceFlags m_DeviceFlags;
|
||||
private InputControlTreeView m_ControlTree;
|
||||
private InputEventTreeView m_EventTree;
|
||||
private bool m_NeedControlValueRefresh;
|
||||
private bool m_ReloadEventTree;
|
||||
private InputEventTrace.ReplayController m_ReplayController;
|
||||
private InputEventTrace m_EventTrace;
|
||||
private InputUpdateType m_InputUpdateTypeShownInControlTree;
|
||||
private InputLatencyCalculator m_InputLatencyCalculator;
|
||||
private SampleFrequencyCalculator m_SampleFrequencyCalculator;
|
||||
|
||||
[SerializeField] private int m_DeviceId = InputDevice.InvalidDeviceId;
|
||||
[SerializeField] private TreeViewState m_ControlTreeState;
|
||||
[SerializeField] private TreeViewState m_EventTreeState;
|
||||
[SerializeField] private MultiColumnHeaderState m_ControlTreeHeaderState;
|
||||
[SerializeField] private MultiColumnHeaderState m_EventTreeHeaderState;
|
||||
[SerializeField] private bool m_EventTraceDisabled;
|
||||
|
||||
private static List<InputDeviceDebuggerWindow> s_OpenDebuggerWindows;
|
||||
|
||||
private void AddToList()
|
||||
{
|
||||
if (s_OpenDebuggerWindows == null)
|
||||
s_OpenDebuggerWindows = new List<InputDeviceDebuggerWindow>();
|
||||
if (!s_OpenDebuggerWindows.Contains(this))
|
||||
s_OpenDebuggerWindows.Add(this);
|
||||
}
|
||||
|
||||
private void RemoveFromList()
|
||||
{
|
||||
s_OpenDebuggerWindows?.Remove(this);
|
||||
}
|
||||
|
||||
private void NeedControlValueRefresh()
|
||||
{
|
||||
m_NeedControlValueRefresh = true;
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void OnPlayModeChange(PlayModeStateChange change)
|
||||
{
|
||||
if (change == PlayModeStateChange.EnteredPlayMode || change == PlayModeStateChange.EnteredEditMode)
|
||||
NeedControlValueRefresh();
|
||||
}
|
||||
|
||||
private void OnApplicationFocusChange(bool focus)
|
||||
{
|
||||
NeedControlValueRefresh();
|
||||
}
|
||||
|
||||
private void OnDeviceChange(InputDevice device, InputDeviceChange change)
|
||||
{
|
||||
if (device.deviceId != m_DeviceId)
|
||||
return;
|
||||
|
||||
if (change == InputDeviceChange.Removed)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_DeviceFlags != device.m_DeviceFlags)
|
||||
UpdateDeviceFlags();
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDeviceStateChange(InputDevice device, InputEventPtr eventPtr)
|
||||
{
|
||||
if (device == m_Device)
|
||||
{
|
||||
m_InputLatencyCalculator.ProcessSample(eventPtr);
|
||||
m_SampleFrequencyCalculator.ProcessSample(eventPtr);
|
||||
|
||||
NeedControlValueRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Styles
|
||||
{
|
||||
public static string notFoundHelpText = "Device could not be found.";
|
||||
|
||||
public static GUIStyle toolbarTextField;
|
||||
public static GUIStyle toolbarButton;
|
||||
|
||||
static Styles()
|
||||
{
|
||||
toolbarTextField = new GUIStyle(EditorStyles.toolbarTextField);
|
||||
toolbarTextField.alignment = TextAnchor.MiddleRight;
|
||||
|
||||
toolbarButton = new GUIStyle(EditorStyles.toolbarButton);
|
||||
toolbarButton.alignment = TextAnchor.MiddleCenter;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Contents
|
||||
{
|
||||
public static GUIContent clearContent = new GUIContent("Clear");
|
||||
public static GUIContent pauseContent = new GUIContent("Pause");
|
||||
public static GUIContent saveContent = new GUIContent("Save");
|
||||
public static GUIContent loadContent = new GUIContent("Load");
|
||||
public static GUIContent recordFramesContent = new GUIContent("Record Frames");
|
||||
public static GUIContent stateContent = new GUIContent("State");
|
||||
public static GUIContent editorStateContent = new GUIContent("Controls (Editor State)");
|
||||
public static GUIContent playerStateContent = new GUIContent("Controls (Player State)");
|
||||
}
|
||||
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
AddToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea7d19a9cdd73486689f292cc9d38018
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,83 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// A utility for computing latency based on input events.
|
||||
/// </summary>
|
||||
internal struct InputLatencyCalculator
|
||||
{
|
||||
private double m_LastUpdateTime;
|
||||
private double m_AccumulatedLatencySeconds;
|
||||
private double m_AccumulatedMinLatencySeconds;
|
||||
private double m_AccumulatedMaxLatencySeconds;
|
||||
private int m_SampleCount;
|
||||
|
||||
public InputLatencyCalculator(double realtimeSinceStartup)
|
||||
{
|
||||
m_LastUpdateTime = realtimeSinceStartup;
|
||||
m_AccumulatedLatencySeconds = 0.0;
|
||||
m_AccumulatedMinLatencySeconds = 0.0;
|
||||
m_AccumulatedMaxLatencySeconds = 0.0;
|
||||
m_SampleCount = 0;
|
||||
averageLatencySeconds = float.NaN;
|
||||
minLatencySeconds = float.NaN;
|
||||
maxLatencySeconds = float.NaN;
|
||||
}
|
||||
|
||||
public void ProcessSample(InputEventPtr eventPtr) =>
|
||||
ProcessSample(eventPtr, Time.realtimeSinceStartupAsDouble);
|
||||
|
||||
public void ProcessSample(InputEventPtr eventPtr, double realtimeSinceStartup)
|
||||
{
|
||||
if (!eventPtr.valid)
|
||||
return;
|
||||
|
||||
var ageInSeconds = realtimeSinceStartup - eventPtr.time;
|
||||
m_AccumulatedLatencySeconds += ageInSeconds;
|
||||
if (++m_SampleCount == 1)
|
||||
{
|
||||
m_AccumulatedMinLatencySeconds = ageInSeconds;
|
||||
m_AccumulatedMaxLatencySeconds = ageInSeconds;
|
||||
}
|
||||
else if (ageInSeconds < m_AccumulatedMaxLatencySeconds)
|
||||
m_AccumulatedMinLatencySeconds = ageInSeconds;
|
||||
else if (ageInSeconds > m_AccumulatedMaxLatencySeconds)
|
||||
m_AccumulatedMaxLatencySeconds = ageInSeconds;
|
||||
}
|
||||
|
||||
public float averageLatencySeconds { get; private set; }
|
||||
public float minLatencySeconds { get; private set; }
|
||||
public float maxLatencySeconds { get; private set; }
|
||||
|
||||
public bool Update() => Update(Time.realtimeSinceStartupAsDouble);
|
||||
|
||||
public bool Update(double realtimeSinceStartup)
|
||||
{
|
||||
var timeSinceLastUpdate = realtimeSinceStartup - m_LastUpdateTime;
|
||||
if (timeSinceLastUpdate < 1.0)
|
||||
return false; // Only update once per second (and avoid division by zero)
|
||||
|
||||
if (m_SampleCount == 0)
|
||||
{
|
||||
averageLatencySeconds = float.NaN;
|
||||
minLatencySeconds = float.NaN;
|
||||
maxLatencySeconds = float.NaN;
|
||||
}
|
||||
else
|
||||
{
|
||||
averageLatencySeconds = (float)(m_AccumulatedLatencySeconds / m_SampleCount);
|
||||
minLatencySeconds = (float)m_AccumulatedMinLatencySeconds;
|
||||
maxLatencySeconds = (float)m_AccumulatedMaxLatencySeconds;
|
||||
}
|
||||
|
||||
m_LastUpdateTime = realtimeSinceStartup;
|
||||
m_SampleCount = 0;
|
||||
m_AccumulatedLatencySeconds = 0.0;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58dca4037ed747dba3154af0d815053a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
namespace UnityEngine.InputSystem.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// A utility for computing sample frequency based on input events.
|
||||
/// </summary>
|
||||
internal struct SampleFrequencyCalculator
|
||||
{
|
||||
private double m_LastUpdateTime;
|
||||
private int m_SampleCount;
|
||||
|
||||
public SampleFrequencyCalculator(float targetFrequency, double realtimeSinceStartup)
|
||||
{
|
||||
this.targetFrequency = targetFrequency;
|
||||
this.m_SampleCount = 0;
|
||||
this.frequency = 0.0f;
|
||||
this.m_LastUpdateTime = realtimeSinceStartup;
|
||||
}
|
||||
|
||||
public float targetFrequency { get; private set; }
|
||||
public float frequency { get; private set; }
|
||||
|
||||
public void ProcessSample(InputEventPtr eventPtr)
|
||||
{
|
||||
// Only count actual samples instead of device-state changes which may be reported anyway it seems.
|
||||
// For determining frequency we at least absolute do not want to count state changes not driven
|
||||
// by an associated event/sample.
|
||||
if (eventPtr != null)
|
||||
++m_SampleCount;
|
||||
}
|
||||
|
||||
public bool Update() => Update(Time.realtimeSinceStartupAsDouble);
|
||||
|
||||
public bool Update(double realtimeSinceStartup)
|
||||
{
|
||||
var timeSinceLastUpdate = realtimeSinceStartup - m_LastUpdateTime;
|
||||
if (timeSinceLastUpdate < 1.0)
|
||||
return false; // Only update once per second (and avoid division by zero)
|
||||
|
||||
m_LastUpdateTime = realtimeSinceStartup;
|
||||
frequency = (float)(m_SampleCount / timeSinceLastUpdate);
|
||||
m_SampleCount = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdd7c783de844fefa8df8acfd36b9dc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user