A game about forced loneliness, made by TACStudios
1using System; 2using UnityEngine.InputSystem.Utilities; 3using Unity.Collections; 4using Unity.Collections.LowLevel.Unsafe; 5 6////REVIEW: Can we change this into a setup where the buffering depth isn't fixed to 2 but rather 7//// can be set on a per device basis? 8 9namespace UnityEngine.InputSystem.LowLevel 10{ 11 // The raw memory blocks which are indexed by InputStateBlocks. 12 // 13 // Internally, we perform only a single combined unmanaged allocation for all state 14 // buffers needed by the system. Externally, we expose them as if they are each separate 15 // buffers. 16 internal unsafe struct InputStateBuffers 17 { 18 // State buffers are set up in a double buffering scheme where the "back buffer" 19 // represents the previous state of devices and the "front buffer" represents 20 // the current state. 21 // 22 // Edit mode and play mode each get their own double buffering. Updates to them 23 // are tied to focus and only one mode will actually receive state events while the 24 // other mode is dormant. In the player, we only get play mode buffers, of course. 25 26 ////TODO: need to clear the current buffers when switching between edit and play mode 27 //// (i.e. if you click an editor window while in play mode, the play mode 28 //// device states will all go back to default) 29 //// actually, if we really reset on mode change, can't we just keep a single set buffers? 30 31 public uint sizePerBuffer; 32 public uint totalSize; 33 34 /// <summary> 35 /// Buffer that has state for each device initialized with default values. 36 /// </summary> 37 public void* defaultStateBuffer; 38 39 /// <summary> 40 /// Buffer that contains a bit mask that masks out all noisy controls. 41 /// </summary> 42 public void* noiseMaskBuffer; 43 44 /// <summary> 45 /// Buffer that contains a bit mask that masks out all dontReset controls. 46 /// </summary> 47 public void* resetMaskBuffer; 48 49 // Secretly we perform only a single allocation. 50 // This allocation also contains the device-to-state mappings. 51 private void* m_AllBuffers; 52 53 // Contains information about a double buffer setup. 54 [Serializable] 55 internal struct DoubleBuffers 56 { 57 ////REVIEW: store timestamps along with each device-to-buffer mapping? 58 // An array of pointers that maps devices to their respective 59 // front and back buffer. Mapping is [deviceIndex*2] is front 60 // buffer and [deviceIndex*2+1] is back buffer. Each device 61 // has its buffers swapped individually with SwapDeviceBuffers(). 62 public void** deviceToBufferMapping; 63 public int deviceCount; 64 65 public bool valid => deviceToBufferMapping != null; 66 67 public void SetFrontBuffer(int deviceIndex, void* ptr) 68 { 69 if (deviceIndex < deviceCount) 70 deviceToBufferMapping[deviceIndex * 2] = ptr; 71 } 72 73 public void SetBackBuffer(int deviceIndex, void* ptr) 74 { 75 if (deviceIndex < deviceCount) 76 deviceToBufferMapping[deviceIndex * 2 + 1] = ptr; 77 } 78 79 public void* GetFrontBuffer(int deviceIndex) 80 { 81 if (deviceIndex < deviceCount) 82 return deviceToBufferMapping[deviceIndex * 2]; 83 return null; 84 } 85 86 public void* GetBackBuffer(int deviceIndex) 87 { 88 if (deviceIndex < deviceCount) 89 return deviceToBufferMapping[deviceIndex * 2 + 1]; 90 return null; 91 } 92 93 public void SwapBuffers(int deviceIndex) 94 { 95 // Ignore if the double buffer set has not been initialized. 96 // Means the respective update type is disabled. 97 if (!valid) 98 return; 99 100 var front = GetFrontBuffer(deviceIndex); 101 var back = GetBackBuffer(deviceIndex); 102 103 SetFrontBuffer(deviceIndex, back); 104 SetBackBuffer(deviceIndex, front); 105 } 106 } 107 108 internal DoubleBuffers m_PlayerStateBuffers; 109 110#if UNITY_EDITOR 111 internal DoubleBuffers m_EditorStateBuffers; 112#endif 113 114 public DoubleBuffers GetDoubleBuffersFor(InputUpdateType updateType) 115 { 116 switch (updateType) 117 { 118 case InputUpdateType.BeforeRender: 119 case InputUpdateType.Fixed: 120 case InputUpdateType.Dynamic: 121 case InputUpdateType.Manual: 122 return m_PlayerStateBuffers; 123#if UNITY_EDITOR 124 case InputUpdateType.Editor: 125 return m_EditorStateBuffers; 126#endif 127 } 128 129 throw new ArgumentException("Unrecognized InputUpdateType: " + updateType, nameof(updateType)); 130 } 131 132 internal static void* s_DefaultStateBuffer; 133 internal static void* s_NoiseMaskBuffer; 134 internal static void* s_ResetMaskBuffer; 135 internal static DoubleBuffers s_CurrentBuffers; 136 137 public static void* GetFrontBufferForDevice(int deviceIndex) 138 { 139 return s_CurrentBuffers.GetFrontBuffer(deviceIndex); 140 } 141 142 public static void* GetBackBufferForDevice(int deviceIndex) 143 { 144 return s_CurrentBuffers.GetBackBuffer(deviceIndex); 145 } 146 147 // Switch the current set of buffers used by the system. 148 public static void SwitchTo(InputStateBuffers buffers, InputUpdateType update) 149 { 150 s_CurrentBuffers = buffers.GetDoubleBuffersFor(update); 151 } 152 153 // Allocates all buffers to serve the given updates and comes up with a spot 154 // for the state block of each device. Returns the new state blocks for the 155 // devices (it will *NOT* install them on the devices). 156 public void AllocateAll(InputDevice[] devices, int deviceCount) 157 { 158 sizePerBuffer = ComputeSizeOfSingleStateBuffer(devices, deviceCount); 159 if (sizePerBuffer == 0) 160 return; 161 sizePerBuffer = sizePerBuffer.AlignToMultipleOf(4); 162 163 // Determine how much memory we need. 164 var mappingTableSizePerBuffer = (uint)(deviceCount * sizeof(void*) * 2); 165 166 totalSize = 0; 167 168 totalSize += sizePerBuffer * 2; 169 totalSize += mappingTableSizePerBuffer; 170 171 #if UNITY_EDITOR 172 totalSize += sizePerBuffer * 2; 173 totalSize += mappingTableSizePerBuffer; 174 #endif 175 176 // Plus 3 more buffers (one for default states, one for noise masks, and one for dontReset masks). 177 totalSize += sizePerBuffer * 3; 178 179 // Allocate. 180 m_AllBuffers = UnsafeUtility.Malloc(totalSize, 4, Allocator.Persistent); 181 UnsafeUtility.MemClear(m_AllBuffers, totalSize); 182 183 // Set up device to buffer mappings. 184 var ptr = (byte*)m_AllBuffers; 185 m_PlayerStateBuffers = 186 SetUpDeviceToBufferMappings(deviceCount, ref ptr, sizePerBuffer, 187 mappingTableSizePerBuffer); 188 189 #if UNITY_EDITOR 190 m_EditorStateBuffers = 191 SetUpDeviceToBufferMappings(deviceCount, ref ptr, sizePerBuffer, mappingTableSizePerBuffer); 192 #endif 193 194 // Default state and noise filter buffers go last. 195 defaultStateBuffer = ptr; 196 noiseMaskBuffer = ptr + sizePerBuffer; 197 resetMaskBuffer = ptr + sizePerBuffer * 2; 198 } 199 200 private static DoubleBuffers SetUpDeviceToBufferMappings(int deviceCount, ref byte* bufferPtr, uint sizePerBuffer, uint mappingTableSizePerBuffer) 201 { 202 var front = bufferPtr; 203 var back = bufferPtr + sizePerBuffer; 204 var mappings = (void**)(bufferPtr + sizePerBuffer * 2); // Put mapping table at end. 205 bufferPtr += sizePerBuffer * 2 + mappingTableSizePerBuffer; 206 207 var buffers = new DoubleBuffers 208 { 209 deviceToBufferMapping = mappings, 210 deviceCount = deviceCount 211 }; 212 213 for (var i = 0; i < deviceCount; ++i) 214 { 215 var deviceIndex = i; 216 buffers.SetFrontBuffer(deviceIndex, front); 217 buffers.SetBackBuffer(deviceIndex, back); 218 } 219 220 return buffers; 221 } 222 223 public void FreeAll() 224 { 225 if (m_AllBuffers != null) 226 { 227 UnsafeUtility.Free(m_AllBuffers, Allocator.Persistent); 228 m_AllBuffers = null; 229 } 230 231 m_PlayerStateBuffers = new DoubleBuffers(); 232 233#if UNITY_EDITOR 234 m_EditorStateBuffers = new DoubleBuffers(); 235#endif 236 237 s_CurrentBuffers = new DoubleBuffers(); 238 239 if (s_DefaultStateBuffer == defaultStateBuffer) 240 s_DefaultStateBuffer = null; 241 242 defaultStateBuffer = null; 243 244 if (s_NoiseMaskBuffer == noiseMaskBuffer) 245 s_NoiseMaskBuffer = null; 246 247 if (s_ResetMaskBuffer == resetMaskBuffer) 248 s_ResetMaskBuffer = null; 249 250 noiseMaskBuffer = null; 251 resetMaskBuffer = null; 252 253 totalSize = 0; 254 sizePerBuffer = 0; 255 } 256 257 // Migrate state data for all devices from a previous set of buffers to the current set of buffers. 258 // Copies all state from their old locations to their new locations and bakes the new offsets into 259 // the control hierarchies of the given devices. 260 // NOTE: When having oldBuffers, this method only works properly if the only alteration compared to the 261 // new buffers is that either devices have been removed or devices have been added. Cannot be 262 // a mix of the two. Also, new devices MUST be added to the end and cannot be inserted in the middle. 263 // NOTE: Also, state formats MUST not change from before. A device that has changed its format must 264 // be treated as a newly device that didn't exist before. 265 public void MigrateAll(InputDevice[] devices, int deviceCount, InputStateBuffers oldBuffers) 266 { 267 // If we have old data, perform migration. 268 if (oldBuffers.totalSize > 0) 269 { 270 MigrateDoubleBuffer(m_PlayerStateBuffers, devices, deviceCount, oldBuffers.m_PlayerStateBuffers); 271 272#if UNITY_EDITOR 273 MigrateDoubleBuffer(m_EditorStateBuffers, devices, deviceCount, oldBuffers.m_EditorStateBuffers); 274#endif 275 276 MigrateSingleBuffer(defaultStateBuffer, devices, deviceCount, oldBuffers.defaultStateBuffer); 277 MigrateSingleBuffer(noiseMaskBuffer, devices, deviceCount, oldBuffers.noiseMaskBuffer); 278 MigrateSingleBuffer(resetMaskBuffer, devices, deviceCount, oldBuffers.resetMaskBuffer); 279 } 280 281 // Assign state blocks. This is where devices will receive their updates state offsets. Up 282 // until now we've left any previous m_StateBlocks alone. 283 var newOffset = 0u; 284 for (var i = 0; i < deviceCount; ++i) 285 { 286 var device = devices[i]; 287 var oldOffset = device.m_StateBlock.byteOffset; 288 289 if (oldOffset == InputStateBlock.InvalidOffset) 290 { 291 // Device is new and has no offset yet baked into it. 292 device.m_StateBlock.byteOffset = 0; 293 if (newOffset != 0) 294 device.BakeOffsetIntoStateBlockRecursive(newOffset); 295 } 296 else 297 { 298 // Device is not new and still has its old offset baked into it. We could first unbake the old offset 299 // and then bake the new one but instead just bake a relative offset. 300 var delta = newOffset - oldOffset; 301 if (delta != 0) 302 device.BakeOffsetIntoStateBlockRecursive(delta); 303 } 304 305 Debug.Assert(device.m_StateBlock.byteOffset == newOffset, "Device state offset not set correctly"); 306 307 newOffset = NextDeviceOffset(newOffset, device); 308 } 309 } 310 311 private static void MigrateDoubleBuffer(DoubleBuffers newBuffer, InputDevice[] devices, int deviceCount, DoubleBuffers oldBuffer) 312 { 313 // Nothing to migrate if we no longer keep a buffer of the corresponding type. 314 if (!newBuffer.valid) 315 return; 316 317 // We do the same if we don't had a corresponding buffer before. 318 if (!oldBuffer.valid) 319 return; 320 321 // Migrate every device that has allocated state blocks. 322 var newStateBlockOffset = 0u; 323 for (var i = 0; i < deviceCount; ++i) 324 { 325 var device = devices[i]; 326 327 // Stop as soon as we're hitting a new device. Newly added devices *must* be *appended* to the 328 // array as otherwise our computing of offsets into the old buffer may be wrong. 329 // NOTE: This also means that device indices of 330 if (device.m_StateBlock.byteOffset == InputStateBlock.InvalidOffset) 331 { 332 #if DEVELOPMENT_BUILD || UNITY_EDITOR 333 for (var n = i + 1; n < deviceCount; ++n) 334 Debug.Assert(devices[n].m_StateBlock.byteOffset == InputStateBlock.InvalidOffset, 335 "New devices must be appended to the array; found an old device coming in the array after a newly added device"); 336 #endif 337 break; 338 } 339 340 var oldDeviceIndex = device.m_DeviceIndex; 341 var newDeviceIndex = i; 342 var numBytes = device.m_StateBlock.alignedSizeInBytes; 343 344 var oldFrontPtr = (byte*)oldBuffer.GetFrontBuffer(oldDeviceIndex) + (int)device.m_StateBlock.byteOffset; // m_StateBlock still refers to oldBuffer. 345 var oldBackPtr = (byte*)oldBuffer.GetBackBuffer(oldDeviceIndex) + (int)device.m_StateBlock.byteOffset; 346 347 var newFrontPtr = (byte*)newBuffer.GetFrontBuffer(newDeviceIndex) + (int)newStateBlockOffset; 348 var newBackPtr = (byte*)newBuffer.GetBackBuffer(newDeviceIndex) + (int)newStateBlockOffset; 349 350 // Copy state. 351 UnsafeUtility.MemCpy(newFrontPtr, oldFrontPtr, numBytes); 352 UnsafeUtility.MemCpy(newBackPtr, oldBackPtr, numBytes); 353 354 newStateBlockOffset = NextDeviceOffset(newStateBlockOffset, device); 355 } 356 } 357 358 private static void MigrateSingleBuffer(void* newBuffer, InputDevice[] devices, int deviceCount, void* oldBuffer) 359 { 360 // Migrate every device that has allocated state blocks. 361 var newDeviceCount = deviceCount; 362 var newStateBlockOffset = 0u; 363 for (var i = 0; i < newDeviceCount; ++i) 364 { 365 var device = devices[i]; 366 367 // Stop if we've reached newly added devices. 368 if (device.m_StateBlock.byteOffset == InputStateBlock.InvalidOffset) 369 break; 370 371 var numBytes = device.m_StateBlock.alignedSizeInBytes; 372 var oldStatePtr = (byte*)oldBuffer + (int)device.m_StateBlock.byteOffset; 373 var newStatePtr = (byte*)newBuffer + (int)newStateBlockOffset; 374 375 UnsafeUtility.MemCpy(newStatePtr, oldStatePtr, numBytes); 376 377 newStateBlockOffset = NextDeviceOffset(newStateBlockOffset, device); 378 } 379 } 380 381 private static uint ComputeSizeOfSingleStateBuffer(InputDevice[] devices, int deviceCount) 382 { 383 var sizeInBytes = 0u; 384 for (var i = 0; i < deviceCount; ++i) 385 sizeInBytes = NextDeviceOffset(sizeInBytes, devices[i]); 386 return sizeInBytes; 387 } 388 389 private static uint NextDeviceOffset(uint currentOffset, InputDevice device) 390 { 391 var sizeOfDevice = device.m_StateBlock.alignedSizeInBytes; 392 if (sizeOfDevice == 0) // Shouldn't happen as we don't allow empty layouts but make sure we catch this if something slips through. 393 throw new ArgumentException($"Device '{device}' has a zero-size state buffer", nameof(device)); 394 return currentOffset + sizeOfDevice.AlignToMultipleOf(4); 395 } 396 } 397}