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}