Без опису
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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