A game about forced loneliness, made by TACStudios
at master 375 lines 14 kB view raw
1using System; 2using System.Collections.Generic; 3using UnityEngine.InputSystem.Layouts; 4using UnityEngine.InputSystem.LowLevel; 5 6////TODO: add way to plot values over time 7 8// Goal is to build this out into something that can visualize a large number of 9// aspects about an InputControl/InputDevice especially with an eye towards making 10// it a good deal to debug any input collection/processing irregularities that may 11// be seen in players (or the editor, for that matter). 12 13// Some fields assigned through only through serialization. 14#pragma warning disable CS0649 15 16namespace UnityEngine.InputSystem.Samples 17{ 18 /// <summary> 19 /// A component for debugging purposes that adds an on-screen display which shows 20 /// activity on an input control over time. 21 /// </summary> 22 /// <remarks> 23 /// This component is most useful for debugging input directly on the source device. 24 /// </remarks> 25 /// <seealso cref="InputActionVisualizer"/> 26 [AddComponentMenu("Input/Debug/Input Control Visualizer")] 27 [ExecuteInEditMode] 28 public class InputControlVisualizer : InputVisualizer 29 { 30 /// <summary> 31 /// What kind of visualization to show. 32 /// </summary> 33 public Mode visualization 34 { 35 get => m_Visualization; 36 set 37 { 38 if (m_Visualization == value) 39 return; 40 m_Visualization = value; 41 SetupVisualizer(); 42 } 43 } 44 45 /// <summary> 46 /// Path of the control that is to be visualized. 47 /// </summary> 48 /// <seealso cref="InputControlPath"/> 49 /// <seealso cref="InputControl.path"/> 50 public string controlPath 51 { 52 get => m_ControlPath; 53 set 54 { 55 m_ControlPath = value; 56 if (m_Control != null) 57 ResolveControl(); 58 } 59 } 60 61 /// <summary> 62 /// If, at runtime, multiple controls are matching <see cref="controlPath"/>, this property 63 /// determines the index of the control that is retrieved from the possible options. 64 /// </summary> 65 public int controlIndex 66 { 67 get => m_ControlIndex; 68 set 69 { 70 m_ControlIndex = value; 71 if (m_Control != null) 72 ResolveControl(); 73 } 74 } 75 76 /// <summary> 77 /// The control resolved from <see cref="controlPath"/> at runtime. May be null. 78 /// </summary> 79 public InputControl control => m_Control; 80 81 protected new void OnEnable() 82 { 83 if (m_Visualization == Mode.None) 84 return; 85 86 if (s_EnabledInstances == null) 87 s_EnabledInstances = new List<InputControlVisualizer>(); 88 if (s_EnabledInstances.Count == 0) 89 { 90 InputSystem.onDeviceChange += OnDeviceChange; 91 InputSystem.onEvent += OnEvent; 92 } 93 s_EnabledInstances.Add(this); 94 95 ResolveControl(); 96 97 base.OnEnable(); 98 } 99 100 protected new void OnDisable() 101 { 102 if (m_Visualization == Mode.None) 103 return; 104 105 if (s_EnabledInstances != null) 106 { 107 s_EnabledInstances.Remove(this); 108 if (s_EnabledInstances.Count == 0) 109 { 110 InputSystem.onDeviceChange -= OnDeviceChange; 111 InputSystem.onEvent -= OnEvent; 112 } 113 } 114 115 m_Control = null; 116 117 base.OnDisable(); 118 } 119 120 protected new void OnGUI() 121 { 122 if (m_Visualization == Mode.None) 123 return; 124 125 base.OnGUI(); 126 } 127 128 protected new void OnValidate() 129 { 130 ResolveControl(); 131 base.OnValidate(); 132 } 133 134 [Tooltip("The type of visualization to perform for the control.")] 135 [SerializeField] private Mode m_Visualization; 136 [Tooltip("Path of the control that should be visualized. If at runtime, multiple " 137 + "controls match the given path, the 'Control Index' property can be used to decide " 138 + "which of the controls to visualize.")] 139 [InputControl, SerializeField] private string m_ControlPath; 140 [Tooltip("If multiple controls match 'Control Path' at runtime, this property decides " 141 + "which control to visualize from the list of candidates. It is a zero-based index.")] 142 [SerializeField] private int m_ControlIndex; 143 144 [NonSerialized] private InputControl m_Control; 145 146 private static List<InputControlVisualizer> s_EnabledInstances; 147 148 private void ResolveControl() 149 { 150 m_Control = null; 151 if (string.IsNullOrEmpty(m_ControlPath)) 152 return; 153 154 using (var candidates = InputSystem.FindControls(m_ControlPath)) 155 { 156 var numCandidates = candidates.Count; 157 if (numCandidates > 1 && m_ControlIndex < numCandidates && m_ControlIndex >= 0) 158 m_Control = candidates[m_ControlIndex]; 159 else if (numCandidates > 0) 160 m_Control = candidates[0]; 161 } 162 163 SetupVisualizer(); 164 } 165 166 private void SetupVisualizer() 167 { 168 if (m_Control == null) 169 { 170 m_Visualizer = null; 171 return; 172 } 173 174 switch (m_Visualization) 175 { 176 case Mode.Value: 177 { 178 var valueType = m_Control.valueType; 179 if (valueType == typeof(Vector2)) 180 m_Visualizer = new VisualizationHelpers.Vector2Visualizer(m_HistorySamples); 181 else if (valueType == typeof(float)) 182 m_Visualizer = new VisualizationHelpers.ScalarVisualizer<float>(m_HistorySamples) 183 { 184 ////TODO: pass actual min/max limits of control 185 limitMax = 1, 186 limitMin = 0 187 }; 188 else if (valueType == typeof(int)) 189 m_Visualizer = new VisualizationHelpers.ScalarVisualizer<int>(m_HistorySamples) 190 { 191 ////TODO: pass actual min/max limits of control 192 limitMax = 1, 193 limitMin = 0 194 }; 195 else 196 { 197 ////TODO: generic visualizer 198 } 199 break; 200 } 201 202 case Mode.Events: 203 { 204 var visualizer = new VisualizationHelpers.TimelineVisualizer(m_HistorySamples) 205 { 206 timeUnit = VisualizationHelpers.TimelineVisualizer.TimeUnit.Frames, 207 historyDepth = m_HistorySamples, 208 showLimits = true, 209 limitsY = new Vector2(0, 5) // Will expand upward automatically 210 }; 211 m_Visualizer = visualizer; 212 visualizer.AddTimeline("Events", Color.green, 213 VisualizationHelpers.TimelineVisualizer.PlotType.BarChart); 214 break; 215 } 216 217 case Mode.MaximumLag: 218 { 219 var visualizer = new VisualizationHelpers.TimelineVisualizer(m_HistorySamples) 220 { 221 timeUnit = VisualizationHelpers.TimelineVisualizer.TimeUnit.Frames, 222 historyDepth = m_HistorySamples, 223 valueUnit = new GUIContent("ms"), 224 showLimits = true, 225 limitsY = new Vector2(0, 6) 226 }; 227 m_Visualizer = visualizer; 228 visualizer.AddTimeline("MaxLag", Color.red, 229 VisualizationHelpers.TimelineVisualizer.PlotType.BarChart); 230 break; 231 } 232 233 case Mode.Bytes: 234 { 235 var visualizer = new VisualizationHelpers.TimelineVisualizer(m_HistorySamples) 236 { 237 timeUnit = VisualizationHelpers.TimelineVisualizer.TimeUnit.Frames, 238 valueUnit = new GUIContent("bytes"), 239 historyDepth = m_HistorySamples, 240 showLimits = true, 241 limitsY = new Vector2(0, 64) 242 }; 243 m_Visualizer = visualizer; 244 visualizer.AddTimeline("Bytes", Color.red, 245 VisualizationHelpers.TimelineVisualizer.PlotType.BarChart); 246 break; 247 } 248 249 case Mode.DeviceCurrent: 250 { 251 m_Visualizer = new VisualizationHelpers.CurrentDeviceVisualizer(); 252 break; 253 } 254 255 default: 256 throw new NotImplementedException(); 257 } 258 } 259 260 private static void OnDeviceChange(InputDevice device, InputDeviceChange change) 261 { 262 if (change != InputDeviceChange.Added && change != InputDeviceChange.Removed) 263 return; 264 265 for (var i = 0; i < s_EnabledInstances.Count; ++i) 266 { 267 var component = s_EnabledInstances[i]; 268 if (change == InputDeviceChange.Removed && component.m_Control != null && 269 component.m_Control.device == device) 270 component.ResolveControl(); 271 else if (change == InputDeviceChange.Added) 272 component.ResolveControl(); 273 } 274 } 275 276 private static void OnEvent(InputEventPtr eventPtr, InputDevice device) 277 { 278 // Ignore very first update as we usually get huge lag spikes and event count 279 // spikes in it from stuff that has accumulated while going into play mode or 280 // starting up the player. 281 if (InputState.updateCount <= 1) 282 return; 283 284 if (InputState.currentUpdateType == InputUpdateType.Editor) 285 return; 286 287 if (!eventPtr.IsA<StateEvent>() && !eventPtr.IsA<DeltaStateEvent>()) 288 return; 289 290 for (var i = 0; i < s_EnabledInstances.Count; ++i) 291 { 292 var component = s_EnabledInstances[i]; 293 if (component.m_Control?.device != device || component.m_Visualizer == null) 294 continue; 295 296 component.OnEventImpl(eventPtr, device); 297 } 298 } 299 300 private unsafe void OnEventImpl(InputEventPtr eventPtr, InputDevice device) 301 { 302 switch (m_Visualization) 303 { 304 case Mode.Value: 305 { 306 var statePtr = m_Control.GetStatePtrFromStateEvent(eventPtr); 307 if (statePtr == null) 308 return; // No value for control in event. 309 var value = m_Control.ReadValueFromStateAsObject(statePtr); 310 m_Visualizer.AddSample(value, eventPtr.time); 311 break; 312 } 313 314 case Mode.Events: 315 { 316 var visualizer = (VisualizationHelpers.TimelineVisualizer)m_Visualizer; 317 var frame = (int)InputState.updateCount; 318 ref var valueRef = ref visualizer.GetOrCreateSample(0, frame); 319 var value = valueRef.ToInt32() + 1; 320 valueRef = value; 321 visualizer.limitsY = 322 new Vector2(0, Mathf.Max(value, visualizer.limitsY.y)); 323 break; 324 } 325 326 case Mode.MaximumLag: 327 { 328 var visualizer = (VisualizationHelpers.TimelineVisualizer)m_Visualizer; 329 var lag = (Time.realtimeSinceStartup - eventPtr.time) * 1000; // In milliseconds. 330 var frame = (int)InputState.updateCount; 331 ref var valueRef = ref visualizer.GetOrCreateSample(0, frame); 332 333 if (lag > valueRef.ToDouble()) 334 { 335 valueRef = lag; 336 if (lag > visualizer.limitsY.y) 337 visualizer.limitsY = new Vector2(0, Mathf.Ceil((float)lag)); 338 } 339 break; 340 } 341 342 case Mode.Bytes: 343 { 344 var visualizer = (VisualizationHelpers.TimelineVisualizer)m_Visualizer; 345 var frame = (int)InputState.updateCount; 346 ref var valueRef = ref visualizer.GetOrCreateSample(0, frame); 347 var value = valueRef.ToInt32() + eventPtr.sizeInBytes; 348 valueRef = value; 349 visualizer.limitsY = 350 new Vector2(0, Mathf.Max(value, visualizer.limitsY.y)); 351 break; 352 } 353 354 case Mode.DeviceCurrent: 355 { 356 m_Visualizer.AddSample(device, eventPtr.time); 357 break; 358 } 359 } 360 } 361 362 /// <summary> 363 /// Determines which aspect of the control should be visualized. 364 /// </summary> 365 public enum Mode 366 { 367 None = 0, 368 Value = 1, 369 Events = 4, 370 MaximumLag = 6, 371 Bytes = 7, 372 DeviceCurrent = 8, 373 } 374 } 375}