A game about forced loneliness, made by TACStudios
at master 22 kB view raw
1using System; 2using Unity.Collections.LowLevel.Unsafe; 3using UnityEngine.InputSystem.LowLevel; 4using UnityEngine.InputSystem.Utilities; 5 6namespace UnityEngine.InputSystem 7{ 8 internal partial class InputManager 9 { 10 // Indices correspond with those in m_Devices. 11 internal StateChangeMonitorsForDevice[] m_StateChangeMonitors; 12 private InlinedArray<StateChangeMonitorTimeout> m_StateChangeMonitorTimeouts; 13 14 ////TODO: support combining monitors for bitfields 15 public void AddStateChangeMonitor(InputControl control, IInputStateChangeMonitor monitor, long monitorIndex, uint groupIndex) 16 { 17 if (m_DevicesCount <= 0) return; 18 19 var device = control.device; 20 var deviceIndex = device.m_DeviceIndex; 21 Debug.Assert(deviceIndex != InputDevice.kInvalidDeviceIndex); 22 23 // Allocate/reallocate monitor arrays, if necessary. 24 // We lazy-sync it to array of devices. 25 if (m_StateChangeMonitors == null) 26 m_StateChangeMonitors = new StateChangeMonitorsForDevice[m_DevicesCount]; 27 else if (m_StateChangeMonitors.Length <= deviceIndex) 28 Array.Resize(ref m_StateChangeMonitors, m_DevicesCount); 29 30 // If we have removed monitors 31 if (!isProcessingEvents && m_StateChangeMonitors[deviceIndex].needToCompactArrays) 32 m_StateChangeMonitors[deviceIndex].CompactArrays(); 33 34 // Add record. 35 m_StateChangeMonitors[deviceIndex].Add(control, monitor, monitorIndex, groupIndex); 36 } 37 38 private void RemoveStateChangeMonitors(InputDevice device) 39 { 40 if (m_StateChangeMonitors == null) 41 return; 42 43 var deviceIndex = device.m_DeviceIndex; 44 Debug.Assert(deviceIndex != InputDevice.kInvalidDeviceIndex); 45 46 if (deviceIndex >= m_StateChangeMonitors.Length) 47 return; 48 49 m_StateChangeMonitors[deviceIndex].Clear(); 50 51 // Clear timeouts pending on any control on the device. 52 for (var i = 0; i < m_StateChangeMonitorTimeouts.length; ++i) 53 if (m_StateChangeMonitorTimeouts[i].control?.device == device) 54 m_StateChangeMonitorTimeouts[i] = default; 55 } 56 57 public void RemoveStateChangeMonitor(InputControl control, IInputStateChangeMonitor monitor, long monitorIndex) 58 { 59 if (m_StateChangeMonitors == null) 60 return; 61 62 var device = control.device; 63 var deviceIndex = device.m_DeviceIndex; 64 65 // Ignore if device has already been removed. 66 if (deviceIndex == InputDevice.kInvalidDeviceIndex) 67 return; 68 69 // Ignore if there are no state monitors set up for the device. 70 if (deviceIndex >= m_StateChangeMonitors.Length) 71 return; 72 73 m_StateChangeMonitors[deviceIndex].Remove(monitor, monitorIndex, isProcessingEvents); 74 75 // Remove pending timeouts on the monitor. 76 for (var i = 0; i < m_StateChangeMonitorTimeouts.length; ++i) 77 if (m_StateChangeMonitorTimeouts[i].monitor == monitor && 78 m_StateChangeMonitorTimeouts[i].monitorIndex == monitorIndex) 79 m_StateChangeMonitorTimeouts[i] = default; 80 } 81 82 public void AddStateChangeMonitorTimeout(InputControl control, IInputStateChangeMonitor monitor, double time, long monitorIndex, int timerIndex) 83 { 84 m_StateChangeMonitorTimeouts.Append( 85 new StateChangeMonitorTimeout 86 { 87 control = control, 88 time = time, 89 monitor = monitor, 90 monitorIndex = monitorIndex, 91 timerIndex = timerIndex, 92 }); 93 } 94 95 public void RemoveStateChangeMonitorTimeout(IInputStateChangeMonitor monitor, long monitorIndex, int timerIndex) 96 { 97 var timeoutCount = m_StateChangeMonitorTimeouts.length; 98 for (var i = 0; i < timeoutCount; ++i) 99 { 100 ////REVIEW: can we avoid the repeated array lookups without copying the struct out? 101 if (ReferenceEquals(m_StateChangeMonitorTimeouts[i].monitor, monitor) 102 && m_StateChangeMonitorTimeouts[i].monitorIndex == monitorIndex 103 && m_StateChangeMonitorTimeouts[i].timerIndex == timerIndex) 104 { 105 m_StateChangeMonitorTimeouts[i] = default; 106 break; 107 } 108 } 109 } 110 111 private void SortStateChangeMonitorsIfNecessary(int deviceIndex) 112 { 113 if (m_StateChangeMonitors != null && deviceIndex < m_StateChangeMonitors.Length && 114 m_StateChangeMonitors[deviceIndex].needToUpdateOrderingOfMonitors) 115 m_StateChangeMonitors[deviceIndex].SortMonitorsByIndex(); 116 } 117 118 public void SignalStateChangeMonitor(InputControl control, IInputStateChangeMonitor monitor) 119 { 120 var device = control.device; 121 var deviceIndex = device.m_DeviceIndex; 122 123 ref var monitorsForDevice = ref m_StateChangeMonitors[deviceIndex]; 124 for (var i = 0; i < monitorsForDevice.signalled.length; ++i) 125 { 126 SortStateChangeMonitorsIfNecessary(i); 127 128 ref var listener = ref monitorsForDevice.listeners[i]; 129 if (listener.control == control && listener.monitor == monitor) 130 monitorsForDevice.signalled.SetBit(i); 131 } 132 } 133 134 public unsafe void FireStateChangeNotifications() 135 { 136 var time = m_Runtime.currentTime; 137 var count = Math.Min(m_StateChangeMonitors.LengthSafe(), m_DevicesCount); 138 for (var i = 0; i < count; ++i) 139 FireStateChangeNotifications(i, time, null); 140 } 141 142 // Record for a timeout installed on a state change monitor. 143 private struct StateChangeMonitorTimeout 144 { 145 public InputControl control; 146 public double time; 147 public IInputStateChangeMonitor monitor; 148 public long monitorIndex; 149 public int timerIndex; 150 } 151 152 // Maps a single control to an action interested in the control. If 153 // multiple actions are interested in the same control, we will end up 154 // processing the control repeatedly but we assume this is the exception 155 // and so optimize for the case where there's only one action going to 156 // a control. 157 // 158 // Split into two structures to keep data needed only when there is an 159 // actual value change out of the data we need for doing the scanning. 160 internal struct StateChangeMonitorListener 161 { 162 public InputControl control; 163 public IInputStateChangeMonitor monitor; 164 public long monitorIndex; 165 public uint groupIndex; 166 } 167 168 internal struct StateChangeMonitorsForDevice 169 { 170 public MemoryHelpers.BitRegion[] memoryRegions; 171 public StateChangeMonitorListener[] listeners; 172 public DynamicBitfield signalled; 173 public bool needToUpdateOrderingOfMonitors; 174 public bool needToCompactArrays; 175 176 public int count => signalled.length; 177 178 public void Add(InputControl control, IInputStateChangeMonitor monitor, long monitorIndex, uint groupIndex) 179 { 180 // NOTE: This method must only *append* to arrays. This way we can safely add data while traversing 181 // the arrays in FireStateChangeNotifications. Note that appending *may* mean that the arrays 182 // are switched to larger arrays. 183 184 // Record listener. 185 var listenerCount = signalled.length; 186 ArrayHelpers.AppendWithCapacity(ref listeners, ref listenerCount, 187 new StateChangeMonitorListener 188 { monitor = monitor, monitorIndex = monitorIndex, groupIndex = groupIndex, control = control }); 189 190 // Record memory region. 191 ref var controlStateBlock = ref control.m_StateBlock; 192 var memoryRegionCount = signalled.length; 193 ArrayHelpers.AppendWithCapacity(ref memoryRegions, ref memoryRegionCount, 194 new MemoryHelpers.BitRegion(controlStateBlock.byteOffset - control.device.stateBlock.byteOffset, 195 controlStateBlock.bitOffset, controlStateBlock.sizeInBits)); 196 197 signalled.SetLength(signalled.length + 1); 198 199 needToUpdateOrderingOfMonitors = true; 200 } 201 202 public void Remove(IInputStateChangeMonitor monitor, long monitorIndex, bool deferRemoval) 203 { 204 if (listeners == null) 205 return; 206 207 for (var i = 0; i < signalled.length; ++i) 208 if (ReferenceEquals(listeners[i].monitor, monitor) && listeners[i].monitorIndex == monitorIndex) 209 { 210 if (deferRemoval) 211 { 212 listeners[i] = default; 213 memoryRegions[i] = default; 214 signalled.ClearBit(i); 215 needToCompactArrays = true; 216 } 217 else 218 { 219 RemoveAt(i); 220 } 221 222 break; 223 } 224 } 225 226 public void Clear() 227 { 228 // We don't actually release memory we've potentially allocated but rather just reset 229 // our count to zero. 230 listeners.Clear(count); 231 signalled.SetLength(0); 232 233 needToCompactArrays = false; 234 } 235 236 public void CompactArrays() 237 { 238 for (var i = count - 1; i >= 0; --i) 239 { 240 var memoryRegion = memoryRegions[i]; 241 if (memoryRegion.sizeInBits != 0) 242 continue; 243 244 RemoveAt(i); 245 } 246 247 needToCompactArrays = false; 248 } 249 250 private void RemoveAt(int i) 251 { 252 var numListeners = count; 253 var numMemoryRegions = count; 254 listeners.EraseAtWithCapacity(ref numListeners, i); 255 memoryRegions.EraseAtWithCapacity(ref numMemoryRegions, i); 256 signalled.SetLength(count - 1); 257 } 258 259 public void SortMonitorsByIndex() 260 { 261 // Insertion sort. 262 for (var i = 1; i < signalled.length; ++i) 263 { 264 for (var j = i; j > 0; --j) 265 { 266 // Sort by complexities only to keep the sort stable 267 // i.e. don't reverse the order of controls which have the same complexity 268 var firstComplexity = InputActionState.GetComplexityFromMonitorIndex(listeners[j - 1].monitorIndex); 269 var secondComplexity = InputActionState.GetComplexityFromMonitorIndex(listeners[j].monitorIndex); 270 if (firstComplexity >= secondComplexity) 271 break; 272 273 listeners.SwapElements(j, j - 1); 274 memoryRegions.SwapElements(j, j - 1); 275 276 // We can ignore the `signalled` array here as we call this method only 277 // when all monitors are in non-signalled state. 278 } 279 } 280 281 needToUpdateOrderingOfMonitors = false; 282 } 283 } 284 285 // NOTE: 'newState' can be a subset of the full state stored at 'oldState'. In this case, 286 // 'newStateOffsetInBytes' must give the offset into the full state and 'newStateSizeInBytes' must 287 // give the size of memory slice to be updated. 288 private unsafe bool ProcessStateChangeMonitors(int deviceIndex, void* newStateFromEvent, void* oldStateOfDevice, uint newStateSizeInBytes, uint newStateOffsetInBytes) 289 { 290 if (m_StateChangeMonitors == null) 291 return false; 292 293 // We resize the monitor arrays only when someone adds to them so they 294 // may be out of sync with the size of m_Devices. 295 if (deviceIndex >= m_StateChangeMonitors.Length) 296 return false; 297 298 var memoryRegions = m_StateChangeMonitors[deviceIndex].memoryRegions; 299 if (memoryRegions == null) 300 return false; // No one cares about state changes on this device. 301 302 var numMonitors = m_StateChangeMonitors[deviceIndex].count; 303 var signalled = false; 304 var signals = m_StateChangeMonitors[deviceIndex].signalled; 305 var haveChangedSignalsBitfield = false; 306 307 // For every memory region that overlaps what we got in the event, compare memory contents 308 // between the old device state and what's in the event. If the contents different, the 309 // respective state monitor signals. 310 var newEventMemoryRegion = new MemoryHelpers.BitRegion(newStateOffsetInBytes, 0, newStateSizeInBytes * 8); 311 for (var i = 0; i < numMonitors; ++i) 312 { 313 var memoryRegion = memoryRegions[i]; 314 315 // Check if the monitor record has been wiped in the meantime. If so, remove it. 316 if (memoryRegion.sizeInBits == 0) 317 { 318 ////REVIEW: Do we really care? It is nice that it's predictable this way but hardly a hard requirement 319 // NOTE: We're using EraseAtWithCapacity here rather than EraseAtByMovingTail to preserve 320 // order which makes the order of callbacks somewhat more predictable. 321 322 var listenerCount = numMonitors; 323 var memoryRegionCount = numMonitors; 324 m_StateChangeMonitors[deviceIndex].listeners.EraseAtWithCapacity(ref listenerCount, i); 325 memoryRegions.EraseAtWithCapacity(ref memoryRegionCount, i); 326 signals.SetLength(numMonitors - 1); 327 haveChangedSignalsBitfield = true; 328 --numMonitors; 329 --i; 330 continue; 331 } 332 333 var overlap = newEventMemoryRegion.Overlap(memoryRegion); 334 if (overlap.isEmpty || MemoryHelpers.Compare(oldStateOfDevice, (byte*)newStateFromEvent - newStateOffsetInBytes, overlap)) 335 continue; 336 337 signals.SetBit(i); 338 haveChangedSignalsBitfield = true; 339 signalled = true; 340 } 341 342 if (haveChangedSignalsBitfield) 343 m_StateChangeMonitors[deviceIndex].signalled = signals; 344 345 m_StateChangeMonitors[deviceIndex].needToCompactArrays = false; 346 347 return signalled; 348 } 349 350 internal unsafe void FireStateChangeNotifications(int deviceIndex, double internalTime, InputEvent* eventPtr) 351 { 352 if (m_StateChangeMonitors == null) 353 { 354 Debug.Assert(false, "m_StateChangeMonitors is null - has AddStateChangeMonitor been called?"); 355 return; 356 } 357 if (m_StateChangeMonitors.Length <= deviceIndex) 358 { 359 Debug.Assert(false, $"deviceIndex {deviceIndex} passed to FireStateChangeNotifications is out of bounds (current length {m_StateChangeMonitors.Length})."); 360 return; 361 } 362 363 // NOTE: This method must be safe for mutating the state change monitor arrays from *within* 364 // NotifyControlStateChanged()! This includes all monitors for the device being wiped 365 // completely or arbitrary additions and removals having occurred. 366 367 ref var signals = ref m_StateChangeMonitors[deviceIndex].signalled; 368 if (signals.AnyBitIsSet() && m_StateChangeMonitors[deviceIndex].listeners == null) 369 { 370 Debug.Assert(false, $"A state change for device {deviceIndex} has been set, but list of listeners is null."); 371 return; 372 } 373 374 ref var listeners = ref m_StateChangeMonitors[deviceIndex].listeners; 375 var time = internalTime - InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; 376 377 // If we don't have an event, gives us as dummy, invalid instance. 378 // What matters is that InputEventPtr.valid is false for these. 379 var tempEvent = new InputEvent(new FourCC('F', 'A', 'K', 'E'), InputEvent.kBaseEventSize, -1, internalTime); 380 if (eventPtr == null) 381 eventPtr = (InputEvent*)UnsafeUtility.AddressOf(ref tempEvent); 382 383 // Call IStateChangeMonitor.NotifyControlStateChange for every monitor that is in 384 // signalled state. 385 eventPtr->handled = false; 386 for (var i = 0; i < signals.length; ++i) 387 { 388 if (!signals.TestBit(i)) 389 continue; 390 391 var listener = listeners[i]; 392 try 393 { 394 listener.monitor.NotifyControlStateChanged(listener.control, time, eventPtr, 395 listener.monitorIndex); 396 } 397 catch (Exception exception) 398 { 399 Debug.LogError( 400 $"Exception '{exception.GetType().Name}' thrown from state change monitor '{listener.monitor.GetType().Name}' on '{listener.control}'"); 401 Debug.LogException(exception); 402 } 403 404 // If the monitor signalled that it has processed the state change, reset all signalled 405 // state monitors in the same group. This is what causes "SHIFT+B" to prevent "B" from 406 // also triggering. 407 if (eventPtr->handled) 408 { 409 var groupIndex = listeners[i].groupIndex; 410 for (var n = i + 1; n < signals.length; ++n) 411 { 412 // NOTE: We restrict the preemption logic here to a single monitor. Otherwise, 413 // we will have to require that group indices are stable *between* 414 // monitors. Two separate InputActionStates, for example, would have to 415 // agree on group indices that valid *between* the two states or we end 416 // up preempting unrelated inputs. 417 // 418 // Note that this implies there there is *NO* preemption between singleton 419 // InputActions. This isn't intuitive. 420 if (listeners[n].groupIndex == groupIndex && listeners[n].monitor == listener.monitor) 421 signals.ClearBit(n); 422 } 423 424 // Need to reset it back to false as we may have more signalled state monitors that 425 // aren't in the same group (i.e. have independent inputs). 426 eventPtr->handled = false; 427 } 428 429 signals.ClearBit(i); 430 } 431 } 432 433 private void ProcessStateChangeMonitorTimeouts() 434 { 435 if (m_StateChangeMonitorTimeouts.length == 0) 436 return; 437 438 // Go through the list and both trigger expired timers and remove any irrelevant 439 // ones by compacting the array. 440 // NOTE: We do not actually release any memory we may have allocated. 441 var currentTime = m_Runtime.currentTime - InputRuntime.s_CurrentTimeOffsetToRealtimeSinceStartup; 442 var remainingTimeoutCount = 0; 443 for (var i = 0; i < m_StateChangeMonitorTimeouts.length; ++i) 444 { 445 // If we have reset this entry in RemoveStateChangeMonitorTimeouts(), 446 // skip over it and let compaction get rid of it. 447 if (m_StateChangeMonitorTimeouts[i].control == null) 448 continue; 449 450 var timerExpirationTime = m_StateChangeMonitorTimeouts[i].time; 451 if (timerExpirationTime <= currentTime) 452 { 453 var timeout = m_StateChangeMonitorTimeouts[i]; 454 timeout.monitor.NotifyTimerExpired(timeout.control, 455 currentTime, timeout.monitorIndex, timeout.timerIndex); 456 457 // Compaction will get rid of the entry. 458 } 459 else 460 { 461 // Rather than repeatedly calling RemoveAt() and thus potentially 462 // moving the same data over and over again, we compact the array 463 // on the fly and move entries in the array down as needed. 464 if (i != remainingTimeoutCount) 465 m_StateChangeMonitorTimeouts[remainingTimeoutCount] = m_StateChangeMonitorTimeouts[i]; 466 ++remainingTimeoutCount; 467 } 468 } 469 470 m_StateChangeMonitorTimeouts.SetLength(remainingTimeoutCount); 471 } 472 } 473}