A game about forced loneliness, made by TACStudios
1using System; 2using Unity.Collections.LowLevel.Unsafe; 3using UnityEngine.InputSystem.Utilities; 4 5////TODO: method to get raw state pointer for device/control 6 7////REVIEW: allow to restrict state change monitors to specific updates? 8 9namespace UnityEngine.InputSystem.LowLevel 10{ 11 using NotifyControlValueChangeAction = Action<InputControl, double, InputEventPtr, long>; 12 using NotifyTimerExpiredAction = Action<InputControl, double, long, int>; 13 14 /// <summary> 15 /// Low-level APIs for working with input state memory. 16 /// </summary> 17 public static class InputState 18 { 19 /// <summary> 20 /// The type of update that was last run or is currently being run on the input state. 21 /// </summary> 22 /// <remarks> 23 /// This determines which set of buffers are currently active and thus determines which view code 24 /// that queries input state will receive. For example, during editor updates, this will be 25 /// <see cref="InputUpdateType.Editor"/> and the state buffers for the editor will be active. 26 /// </remarks> 27 public static InputUpdateType currentUpdateType => InputUpdate.s_LatestUpdateType; 28 29 ////FIXME: ATM this does not work for editor updates 30 /// <summary> 31 /// The number of times the current input state has been updated. 32 /// </summary> 33 public static uint updateCount => InputUpdate.s_UpdateStepCount; 34 35 public static double currentTime => InputRuntime.s_Instance.currentTime - InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; 36 37 /// <summary> 38 /// Callback that is triggered when the state of an input device changes. 39 /// </summary> 40 /// <remarks> 41 /// The first parameter is the device whose state was changed the second parameter is the event 42 /// that triggered the change in state. Note that the latter may be <c>null</c> in case the 43 /// change was performed directly through <see cref="Change"/> rather than through an event. 44 /// </remarks> 45 public static event Action<InputDevice, InputEventPtr> onChange 46 { 47 add => InputSystem.s_Manager.onDeviceStateChange += value; 48 remove => InputSystem.s_Manager.onDeviceStateChange -= value; 49 } 50 51 public static unsafe void Change(InputDevice device, InputEventPtr eventPtr, InputUpdateType updateType = default) 52 { 53 if (device == null) 54 throw new ArgumentNullException(nameof(device)); 55 if (!eventPtr.valid) 56 throw new ArgumentNullException(nameof(eventPtr)); 57 58 // Make sure event is a StateEvent or DeltaStateEvent and has a format matching the device. 59 FourCC stateFormat; 60 var eventType = eventPtr.type; 61 if (eventType == StateEvent.Type) 62 stateFormat = StateEvent.FromUnchecked(eventPtr)->stateFormat; 63 else if (eventType == DeltaStateEvent.Type) 64 stateFormat = DeltaStateEvent.FromUnchecked(eventPtr)->stateFormat; 65 else 66 { 67 #if UNITY_EDITOR 68 InputSystem.s_Manager.m_Diagnostics?.OnEventFormatMismatch(eventPtr, device); 69 #endif 70 return; 71 } 72 73 if (stateFormat != device.stateBlock.format) 74 throw new ArgumentException( 75 $"State format {stateFormat} from event does not match state format {device.stateBlock.format} of device {device}", 76 nameof(eventPtr)); 77 78 InputSystem.s_Manager.UpdateState(device, eventPtr, 79 updateType != default ? updateType : InputSystem.s_Manager.defaultUpdateType); 80 } 81 82 /// <summary> 83 /// Perform one update of input state. 84 /// </summary> 85 /// <remarks> 86 /// Incorporates the given state and triggers all state change monitors as needed. 87 /// 88 /// Note that input state changes performed with this method will not be visible on remotes as they will bypass 89 /// event processing. It is effectively equivalent to directly writing into input state memory except that it 90 /// also performs related tasks such as checking state change monitors, flipping buffers, or making the respective 91 /// device current. 92 /// </remarks> 93 public static void Change<TState>(InputControl control, TState state, InputUpdateType updateType = default, 94 InputEventPtr eventPtr = default) 95 where TState : struct 96 { 97 Change(control, ref state, updateType, eventPtr); 98 } 99 100 /// <summary> 101 /// Perform one update of input state. 102 /// </summary> 103 /// <remarks> 104 /// Incorporates the given state and triggers all state change monitors as needed. 105 /// 106 /// Note that input state changes performed with this method will not be visible on remotes as they will bypass 107 /// event processing. It is effectively equivalent to directly writing into input state memory except that it 108 /// also performs related tasks such as checking state change monitors, flipping buffers, or making the respective 109 /// device current. 110 /// </remarks> 111 public static unsafe void Change<TState>(InputControl control, ref TState state, InputUpdateType updateType = default, 112 InputEventPtr eventPtr = default) 113 where TState : struct 114 { 115 if (control == null) 116 throw new ArgumentNullException(nameof(control)); 117 if (control.stateBlock.bitOffset != 0 || control.stateBlock.sizeInBits % 8 != 0) 118 throw new ArgumentException($"Cannot change state of bitfield control '{control}' using this method", nameof(control)); 119 120 var device = control.device; 121 var stateSize = Math.Min(UnsafeUtility.SizeOf<TState>(), control.m_StateBlock.alignedSizeInBytes); 122 var statePtr = UnsafeUtility.AddressOf(ref state); 123 var stateOffset = control.stateBlock.byteOffset - device.stateBlock.byteOffset; 124 125 InputSystem.s_Manager.UpdateState(device, 126 updateType != default ? updateType : InputSystem.s_Manager.defaultUpdateType, statePtr, stateOffset, 127 (uint)stateSize, 128 eventPtr.valid 129 ? eventPtr.internalTime 130 : InputRuntime.s_Instance.currentTime, 131 eventPtr: eventPtr); 132 } 133 134 public static bool IsIntegerFormat(this FourCC format) 135 { 136 return format == InputStateBlock.FormatBit || 137 format == InputStateBlock.FormatInt || 138 format == InputStateBlock.FormatByte || 139 format == InputStateBlock.FormatShort || 140 format == InputStateBlock.FormatSBit || 141 format == InputStateBlock.FormatUInt || 142 format == InputStateBlock.FormatUShort || 143 format == InputStateBlock.FormatLong || 144 format == InputStateBlock.FormatULong; 145 } 146 147 /// <summary> 148 /// Add a monitor that gets triggered every time the state of <paramref name="control"/> changes. 149 /// </summary> 150 /// <param name="control">A control sitting on an <see cref="InputDevice"/> that has been <see cref="InputDevice.added"/>.</param> 151 /// <param name="monitor">Instance of the monitor that should be notified when state changes occur.</param> 152 /// <param name="monitorIndex">Numeric index of the monitors. Monitors on a device are ordered by <em>decreasing</em> monitor index 153 /// and invoked in that order.</param> 154 /// <param name="groupIndex">Numeric group of the monitor. See remarks.</param> 155 /// <exception cref="ArgumentNullException"><paramref name="control"/> is <c>null</c> -or- <paramref name="monitor"/> is <c>null</c>.</exception> 156 /// <exception cref="ArgumentException">The <see cref="InputDevice"/> of <paramref name="control"/> has not been <see cref="InputDevice.added"/>.</exception> 157 /// <remarks> 158 /// All monitors on an <see cref="InputDevice"/> are sorted by the complexity specified in their <paramref name="monitorIndex"/> (in decreasing order) and invoked 159 /// in that order. 160 /// 161 /// Every handler gets an opportunity to set <see cref="InputEventPtr.handled"/> to <c>true</c>. When doing so, all remaining pending monitors 162 /// from the same <paramref name="monitor"/> instance that have the same <paramref name="groupIndex"/> will be silenced and skipped over. 163 /// This can be used to establish an order of event "consumption" where one change monitor may prevent another change monitor from triggering. 164 /// 165 /// Monitors are invoked <em>after</em> a state change has been written to the device. If, for example, a <see cref="StateEvent"/> is 166 /// received that sets <see cref="Gamepad.leftTrigger"/> to <c>0.5</c>, the value is first applied to the control and then any state 167 /// monitors that may be listening to the change are invoked (thus getting <c>0.5</c> if calling <see cref="InputControl{TValue}.ReadValue()"/>). 168 /// 169 /// <example> 170 /// <code> 171 /// class InputMonitor : IInputStateChangeMonitor 172 /// { 173 /// public InputMonitor() 174 /// { 175 /// // Watch the left and right mouse button. 176 /// // By supplying monitor indices here, we not only receive the indices in NotifyControlStateChanged, 177 /// // we also create an ordering between the two monitors. The one on RMB will fire *before* the one 178 /// // on LMB in case there is a single event that changes both buttons. 179 /// InputState.AddChangeMonitor(Mouse.current.leftButton, this, monitorIndex: 1); 180 /// InputState.AddChangeMonitor(Mouse.current.rightButton, this, monitorIndex: 2); 181 /// } 182 /// 183 /// public void NotifyControlStateChanged(InputControl control, double currentTime, InputEventPtr eventPtr, long monitorIndex) 184 /// { 185 /// Debug.Log($"{control} changed"); 186 /// 187 /// // We can add a monitor timeout that will trigger in case the state of the 188 /// // given control is not changed within the given time. Let's watch the control 189 /// // for 2 seconds. If nothing happens, we will get a call to NotifyTimerExpired. 190 /// InputState.AddChangeMonitorTimeout(control, this, currentTime + 2); 191 /// } 192 /// 193 /// public void NotifyTimerExpired(InputControl control, double currentTime, long monitorIndex, int timerIndex) 194 /// { 195 /// Debug.Log($"{control} was not changed within 2 seconds"); 196 /// } 197 /// } 198 /// </code> 199 /// </example> 200 /// </remarks> 201 public static void AddChangeMonitor(InputControl control, IInputStateChangeMonitor monitor, long monitorIndex = -1, uint groupIndex = default) 202 { 203 if (control == null) 204 throw new ArgumentNullException(nameof(control)); 205 if (monitor == null) 206 throw new ArgumentNullException(nameof(monitor)); 207 if (!control.device.added) 208 throw new ArgumentException($"Device for control '{control}' has not been added to system"); 209 210 InputSystem.s_Manager.AddStateChangeMonitor(control, monitor, monitorIndex, groupIndex); 211 } 212 213 public static IInputStateChangeMonitor AddChangeMonitor(InputControl control, 214 NotifyControlValueChangeAction valueChangeCallback, int monitorIndex = -1, 215 NotifyTimerExpiredAction timerExpiredCallback = null) 216 { 217 if (valueChangeCallback == null) 218 throw new ArgumentNullException(nameof(valueChangeCallback)); 219 var monitor = new StateChangeMonitorDelegate 220 { 221 valueChangeCallback = valueChangeCallback, 222 timerExpiredCallback = timerExpiredCallback 223 }; 224 AddChangeMonitor(control, monitor, monitorIndex); 225 return monitor; 226 } 227 228 public static void RemoveChangeMonitor(InputControl control, IInputStateChangeMonitor monitor, long monitorIndex = -1) 229 { 230 if (control == null) 231 throw new ArgumentNullException(nameof(control)); 232 if (monitor == null) 233 throw new ArgumentNullException(nameof(monitor)); 234 235 InputSystem.s_Manager.RemoveStateChangeMonitor(control, monitor, monitorIndex); 236 } 237 238 /// <summary> 239 /// Put a timeout on a previously registered state change monitor. 240 /// </summary> 241 /// <param name="control"></param> 242 /// <param name="monitor"></param> 243 /// <param name="time"></param> 244 /// <param name="monitorIndex"></param> 245 /// <param name="timerIndex"></param> 246 /// <remarks> 247 /// If by the given <paramref name="time"/>, no state change has been registered on the control monitored 248 /// by the given <paramref name="monitor">state change monitor</paramref>, <see cref="IInputStateChangeMonitor.NotifyTimerExpired"/> 249 /// will be called on <paramref name="monitor"/>. 250 /// </remarks> 251 public static void AddChangeMonitorTimeout(InputControl control, IInputStateChangeMonitor monitor, double time, long monitorIndex = -1, int timerIndex = -1) 252 { 253 if (monitor == null) 254 throw new ArgumentNullException(nameof(monitor)); 255 256 InputSystem.s_Manager.AddStateChangeMonitorTimeout(control, monitor, time, monitorIndex, timerIndex); 257 } 258 259 public static void RemoveChangeMonitorTimeout(IInputStateChangeMonitor monitor, long monitorIndex = -1, int timerIndex = -1) 260 { 261 if (monitor == null) 262 throw new ArgumentNullException(nameof(monitor)); 263 264 InputSystem.s_Manager.RemoveStateChangeMonitorTimeout(monitor, monitorIndex, timerIndex); 265 } 266 267 private class StateChangeMonitorDelegate : IInputStateChangeMonitor 268 { 269 public NotifyControlValueChangeAction valueChangeCallback; 270 public NotifyTimerExpiredAction timerExpiredCallback; 271 272 public void NotifyControlStateChanged(InputControl control, double time, InputEventPtr eventPtr, long monitorIndex) 273 { 274 valueChangeCallback(control, time, eventPtr, monitorIndex); 275 } 276 277 public void NotifyTimerExpired(InputControl control, double time, long monitorIndex, int timerIndex) 278 { 279 timerExpiredCallback?.Invoke(control, time, monitorIndex, timerIndex); 280 } 281 } 282 } 283}