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