A game about forced loneliness, made by TACStudios
1using System; 2using Unity.Collections.LowLevel.Unsafe; 3 4namespace UnityEngine.InputSystem.Utilities 5{ 6 internal static unsafe class MemoryHelpers 7 { 8 public struct BitRegion 9 { 10 public uint bitOffset; 11 public uint sizeInBits; 12 13 public bool isEmpty => sizeInBits == 0; 14 15 public BitRegion(uint bitOffset, uint sizeInBits) 16 { 17 this.bitOffset = bitOffset; 18 this.sizeInBits = sizeInBits; 19 } 20 21 public BitRegion(uint byteOffset, uint bitOffset, uint sizeInBits) 22 { 23 this.bitOffset = byteOffset * 8 + bitOffset; 24 this.sizeInBits = sizeInBits; 25 } 26 27 public BitRegion Overlap(BitRegion other) 28 { 29 ////REVIEW: too many branches; this can probably be done much smarter 30 31 var thisEnd = bitOffset + sizeInBits; 32 var otherEnd = other.bitOffset + other.sizeInBits; 33 34 if (thisEnd <= other.bitOffset || otherEnd <= bitOffset) 35 return default; 36 37 var end = Math.Min(thisEnd, otherEnd); 38 var start = Math.Max(bitOffset, other.bitOffset); 39 40 return new BitRegion(start, end - start); 41 } 42 } 43 44 public static bool Compare(void* ptr1, void* ptr2, BitRegion region) 45 { 46 if (region.sizeInBits == 1) 47 return ReadSingleBit(ptr1, region.bitOffset) == ReadSingleBit(ptr2, region.bitOffset); 48 return MemCmpBitRegion(ptr1, ptr2, region.bitOffset, region.sizeInBits); 49 } 50 51 public static uint ComputeFollowingByteOffset(uint byteOffset, uint sizeInBits) 52 { 53 return (uint)(byteOffset + sizeInBits / 8 + (sizeInBits % 8 > 0 ? 1 : 0)); 54 } 55 56 public static void WriteSingleBit(void* ptr, uint bitOffset, bool value) 57 { 58 var byteOffset = bitOffset >> 3; 59 bitOffset &= 7; 60 if (value) 61 *((byte*)ptr + byteOffset) |= (byte)(1U << (int)bitOffset); 62 else 63 *((byte*)ptr + byteOffset) &= (byte)~(1U << (int)bitOffset); 64 } 65 66 public static bool ReadSingleBit(void* ptr, uint bitOffset) 67 { 68 var byteOffset = bitOffset >> 3; 69 bitOffset &= 7; 70 return (*((byte*)ptr + byteOffset) & (1U << (int)bitOffset)) != 0; 71 } 72 73 public static void MemCpyBitRegion(void* destination, void* source, uint bitOffset, uint bitCount) 74 { 75 var destPtr = (byte*)destination; 76 var sourcePtr = (byte*)source; 77 78 // If we're offset by more than a byte, adjust our pointers. 79 if (bitOffset >= 8) 80 { 81 var skipBytes = bitOffset / 8; 82 destPtr += skipBytes; 83 sourcePtr += skipBytes; 84 bitOffset %= 8; 85 } 86 87 // Copy unaligned prefix, if any. 88 if (bitOffset > 0) 89 { 90 var byteMask = 0xFF << (int)bitOffset; 91 if (bitCount + bitOffset < 8) 92 byteMask &= 0xFF >> (int)(8 - (bitCount + bitOffset)); 93 94 *destPtr = (byte)(((*destPtr & ~byteMask) | (*sourcePtr & byteMask)) & 0xFF); 95 96 // If the total length of the memory region is equal or less than a byte, 97 // we're done. 98 if (bitCount + bitOffset <= 8) 99 return; 100 101 ++destPtr; 102 ++sourcePtr; 103 104 bitCount -= 8 - bitOffset; 105 } 106 107 // Copy contiguous bytes in-between, if any. 108 var byteCount = bitCount / 8; 109 if (byteCount >= 1) 110 UnsafeUtility.MemCpy(destPtr, sourcePtr, byteCount); 111 112 // Copy unaligned suffix, if any. 113 var remainingBitCount = bitCount % 8; 114 if (remainingBitCount > 0) 115 { 116 destPtr += byteCount; 117 sourcePtr += byteCount; 118 119 // We want the lowest remaining bits. 120 var byteMask = 0xFF >> (int)(8 - remainingBitCount); 121 122 *destPtr = (byte)(((*destPtr & ~byteMask) | (*sourcePtr & byteMask)) & 0xFF); 123 } 124 } 125 126 /// <summary> 127 /// Compare two memory regions that may be offset by a bit count and have a length expressed 128 /// in bits. 129 /// </summary> 130 /// <param name="ptr1">Pointer to start of first memory region.</param> 131 /// <param name="ptr2">Pointer to start of second memory region.</param> 132 /// <param name="bitOffset">Offset in bits from each of the pointers to the start of the memory region to compare.</param> 133 /// <param name="bitCount">Number of bits to compare in the memory region.</param> 134 /// <param name="mask">If not null, only compare bits set in the mask. This allows comparing two memory regions while 135 /// ignoring specific bits.</param> 136 /// <returns>True if the two memory regions are identical, false otherwise.</returns> 137 public static bool MemCmpBitRegion(void* ptr1, void* ptr2, uint bitOffset, uint bitCount, void* mask = null) 138 { 139 var bytePtr1 = (byte*)ptr1; 140 var bytePtr2 = (byte*)ptr2; 141 var maskPtr = (byte*)mask; 142 143 // If we're offset by more than a byte, adjust our pointers. 144 if (bitOffset >= 8) 145 { 146 var skipBytes = bitOffset / 8; 147 bytePtr1 += skipBytes; 148 bytePtr2 += skipBytes; 149 if (maskPtr != null) 150 maskPtr += skipBytes; 151 bitOffset %= 8; 152 } 153 154 // Compare unaligned prefix, if any. 155 if (bitOffset > 0) 156 { 157 // If the total length of the memory region is less than a byte, we need 158 // to mask out parts of the bits we're reading. 159 var byteMask = 0xFF << (int)bitOffset; 160 if (bitCount + bitOffset < 8) 161 byteMask &= 0xFF >> (int)(8 - (bitCount + bitOffset)); 162 163 if (maskPtr != null) 164 { 165 byteMask &= *maskPtr; 166 ++maskPtr; 167 } 168 169 var byte1 = *bytePtr1 & byteMask; 170 var byte2 = *bytePtr2 & byteMask; 171 172 if (byte1 != byte2) 173 return false; 174 175 // If the total length of the memory region is equal or less than a byte, 176 // we're done. 177 if (bitCount + bitOffset <= 8) 178 return true; 179 180 ++bytePtr1; 181 ++bytePtr2; 182 183 bitCount -= 8 - bitOffset; 184 } 185 186 // Compare contiguous bytes in-between, if any. 187 var byteCount = bitCount / 8; 188 if (byteCount >= 1) 189 { 190 if (maskPtr != null) 191 { 192 ////REVIEW: could go int by int here for as long as we can 193 // Have to go byte-by-byte in order to apply the masking. 194 for (var i = 0; i < byteCount; ++i) 195 { 196 var byte1 = bytePtr1[i]; 197 var byte2 = bytePtr2[i]; 198 var byteMask = maskPtr[i]; 199 200 if ((byte1 & byteMask) != (byte2 & byteMask)) 201 return false; 202 } 203 } 204 else 205 { 206 if (UnsafeUtility.MemCmp(bytePtr1, bytePtr2, byteCount) != 0) 207 return false; 208 } 209 } 210 211 // Compare unaligned suffix, if any. 212 var remainingBitCount = bitCount % 8; 213 if (remainingBitCount > 0) 214 { 215 bytePtr1 += byteCount; 216 bytePtr2 += byteCount; 217 218 // We want the lowest remaining bits. 219 var byteMask = 0xFF >> (int)(8 - remainingBitCount); 220 221 if (maskPtr != null) 222 { 223 maskPtr += byteCount; 224 byteMask &= *maskPtr; 225 } 226 227 var byte1 = *bytePtr1 & byteMask; 228 var byte2 = *bytePtr2 & byteMask; 229 230 if (byte1 != byte2) 231 return false; 232 } 233 234 return true; 235 } 236 237 public static void MemSet(void* destination, int numBytes, byte value) 238 { 239 var to = (byte*)destination; 240 var pos = 0; 241 242 unchecked 243 { 244 // 64bit blocks. 245 #if UNITY_64 246 while (numBytes >= 8) 247 { 248 *(ulong*)&to[pos] = ((ulong)value << 56) | ((ulong)value << 48) | ((ulong)value << 40) | ((ulong)value << 32) 249 | ((ulong)value << 24) | ((ulong)value << 16) | ((ulong)value << 8) | value; 250 numBytes -= 8; 251 pos += 8; 252 } 253 #endif 254 255 // 32bit blocks. 256 while (numBytes >= 4) 257 { 258 *(uint*)&to[pos] = ((uint)value << 24) | ((uint)value << 16) | ((uint)value << 8) | value; 259 numBytes -= 4; 260 pos += 4; 261 } 262 263 // Remaining bytes. 264 while (numBytes > 0) 265 { 266 to[pos] = value; 267 numBytes -= 1; 268 pos += 1; 269 } 270 } 271 } 272 273 /// <summary> 274 /// Copy from <paramref name="source"/> to <paramref name="destination"/> all the bits that 275 /// ARE set in <paramref name="mask"/>. 276 /// </summary> 277 /// <param name="destination">Memory to copy to.</param> 278 /// <param name="source">Memory to copy from.</param> 279 /// <param name="numBytes">Number of bytes to copy.</param> 280 /// <param name="mask">Bitmask that determines which bits to copy. Bits that are set WILL be copied.</param> 281 public static void MemCpyMasked(void* destination, void* source, int numBytes, void* mask) 282 { 283 var from = (byte*)source; 284 var to = (byte*)destination; 285 var bits = (byte*)mask; 286 var pos = 0; 287 288 unchecked 289 { 290 // Copy 64bit blocks. 291 #if UNITY_64 292 while (numBytes >= 8) 293 { 294 *(ulong*)(to + pos) &= ~*(ulong*)(bits + pos); // Preserve unmasked bits. 295 *(ulong*)(to + pos) |= *(ulong*)(from + pos) & *(ulong*)(bits + pos); // Copy masked bits. 296 numBytes -= 8; 297 pos += 8; 298 } 299 #endif 300 301 // Copy 32bit blocks. 302 while (numBytes >= 4) 303 { 304 *(uint*)(to + pos) &= ~*(uint*)(bits + pos); // Preserve unmasked bits. 305 *(uint*)(to + pos) |= *(uint*)(from + pos) & *(uint*)(bits + pos); // Copy masked bits. 306 numBytes -= 4; 307 pos += 4; 308 } 309 310 // Copy remaining bytes. 311 while (numBytes > 0) 312 { 313 unchecked 314 { 315 to[pos] &= (byte)~bits[pos]; // Preserve unmasked bits. 316 to[pos] |= (byte)(from[pos] & bits[pos]); // Copy masked bits. 317 } 318 numBytes -= 1; 319 pos += 1; 320 } 321 } 322 } 323 324 /// <summary> 325 /// Reads bits memory region as unsigned int, up to and including 32 bits, least-significant bit first (LSB). 326 /// </summary> 327 /// <param name="ptr">Pointer to memory region.</param> 328 /// <param name="bitOffset">Offset in bits from the pointer to the start of the unsigned integer.</param> 329 /// <param name="bitCount">Number of bits to read.</param> 330 /// <returns>Read unsigned integer.</returns> 331 public static uint ReadMultipleBitsAsUInt(void* ptr, uint bitOffset, uint bitCount) 332 { 333 if (ptr == null) 334 throw new ArgumentNullException(nameof(ptr)); 335 if (bitCount > sizeof(int) * 8) 336 throw new ArgumentException("Trying to read more than 32 bits as int", nameof(bitCount)); 337 338 // Shift the pointer up on larger bitmasks and retry. 339 if (bitOffset > 32) 340 { 341 var newBitOffset = (int)bitOffset % 32; 342 var intOffset = ((int)bitOffset - newBitOffset) / 32; 343 ptr = (byte*)ptr + (intOffset * 4); 344 bitOffset = (uint)newBitOffset; 345 } 346 347 // Bits out of byte. 348 if (bitOffset + bitCount <= 8) 349 { 350 var value = *(byte*)ptr; 351 value >>= (int)bitOffset; 352 var mask = 0xFFu >> (8 - (int)bitCount); 353 return value & mask; 354 } 355 356 // Bits out of short. 357 if (bitOffset + bitCount <= 16) 358 { 359 var value = *(ushort*)ptr; 360 value >>= (int)bitOffset; 361 var mask = 0xFFFFu >> (16 - (int)bitCount); 362 return value & mask; 363 } 364 365 // Bits out of int. 366 if (bitOffset + bitCount <= 32) 367 { 368 var value = *(uint*)ptr; 369 value >>= (int)bitOffset; 370 var mask = 0xFFFFFFFFu >> (32 - (int)bitCount); 371 return value & mask; 372 } 373 374 throw new NotImplementedException("Reading int straddling int boundary"); 375 } 376 377 /// <summary> 378 /// Writes unsigned int as bits to memory region, up to and including 32 bits, least-significant bit first (LSB). 379 /// </summary> 380 /// <param name="ptr">Pointer to memory region.</param> 381 /// <param name="bitOffset">Offset in bits from the pointer to the start of the unsigned integer.</param> 382 /// <param name="bitCount">Number of bits to read.</param> 383 /// <param name="value">Value to write.</param> 384 public static void WriteUIntAsMultipleBits(void* ptr, uint bitOffset, uint bitCount, uint value) 385 { 386 if (ptr == null) 387 throw new ArgumentNullException(nameof(ptr)); 388 if (bitCount > sizeof(int) * 8) 389 throw new ArgumentException("Trying to write more than 32 bits as int", nameof(bitCount)); 390 391 // Shift the pointer up on larger bitmasks and retry. 392 if (bitOffset > 32) 393 { 394 var newBitOffset = (int)bitOffset % 32; 395 var intOffset = ((int)bitOffset - newBitOffset) / 32; 396 ptr = (byte*)ptr + (intOffset * 4); 397 bitOffset = (uint)newBitOffset; 398 } 399 400 // Bits out of byte. 401 if (bitOffset + bitCount <= 8) 402 { 403 var byteValue = (byte)value; 404 byteValue <<= (int)bitOffset; 405 var mask = ~((0xFFU >> (8 - (int)bitCount)) << (int)bitOffset); 406 *(byte*)ptr = (byte)((*(byte*)ptr & mask) | byteValue); 407 return; 408 } 409 410 // Bits out of short. 411 if (bitOffset + bitCount <= 16) 412 { 413 var ushortValue = (ushort)value; 414 ushortValue <<= (int)bitOffset; 415 var mask = ~((0xFFFFU >> (16 - (int)bitCount)) << (int)bitOffset); 416 *(ushort*)ptr = (ushort)((*(ushort*)ptr & mask) | ushortValue); 417 return; 418 } 419 420 // Bits out of int. 421 if (bitOffset + bitCount <= 32) 422 { 423 var uintValue = (uint)value; 424 uintValue <<= (int)bitOffset; 425 var mask = ~((0xFFFFFFFFU >> (32 - (int)bitCount)) << (int)bitOffset); 426 *(uint*)ptr = (*(uint*)ptr & mask) | uintValue; 427 return; 428 } 429 430 throw new NotImplementedException("Writing int straddling int boundary"); 431 } 432 433 /// <summary> 434 /// Reads bits memory region as two's complement integer, up to and including 32 bits, least-significant bit first (LSB). 435 /// For example reading 0xff as 8 bits will result in -1. 436 /// </summary> 437 /// <param name="ptr">Pointer to memory region.</param> 438 /// <param name="bitOffset">Offset in bits from the pointer to the start of the integer.</param> 439 /// <param name="bitCount">Number of bits to read.</param> 440 /// <returns>Read integer.</returns> 441 public static int ReadTwosComplementMultipleBitsAsInt(void* ptr, uint bitOffset, uint bitCount) 442 { 443 // int is already represented as two's complement 444 return (int)ReadMultipleBitsAsUInt(ptr, bitOffset, bitCount); 445 } 446 447 /// <summary> 448 /// Writes bits memory region as two's complement integer, up to and including 32 bits, least-significant bit first (LSB). 449 /// </summary> 450 /// <param name="ptr">Pointer to memory region.</param> 451 /// <param name="bitOffset">Offset in bits from the pointer to the start of the integer.</param> 452 /// <param name="bitCount">Number of bits to read.</param> 453 /// <param name="value">Value to write.</param> 454 public static void WriteIntAsTwosComplementMultipleBits(void* ptr, uint bitOffset, uint bitCount, int value) 455 { 456 // int is already represented as two's complement, so write as-is 457 WriteUIntAsMultipleBits(ptr, bitOffset, bitCount, (uint)value); 458 } 459 460 /// <summary> 461 /// Reads bits memory region as excess-K integer where K is set to (2^bitCount)/2, up to and including 32 bits, least-significant bit first (LSB). 462 /// For example reading 0 as 8 bits will result in -128. Reading 0xff as 8 bits will result in 127. 463 /// </summary> 464 /// <param name="ptr">Pointer to memory region.</param> 465 /// <param name="bitOffset">Offset in bits from the pointer to the start of the integer.</param> 466 /// <param name="bitCount">Number of bits to read.</param> 467 /// <returns>Read integer.</returns> 468 public static int ReadExcessKMultipleBitsAsInt(void* ptr, uint bitOffset, uint bitCount) 469 { 470 // https://en.wikipedia.org/wiki/Signed_number_representations#Offset_binary 471 var value = (long)ReadMultipleBitsAsUInt(ptr, bitOffset, bitCount); 472 var halfMax = (long)((1UL << (int)bitCount) / 2); 473 return (int)(value - halfMax); 474 } 475 476 /// <summary> 477 /// Writes bits memory region as excess-K integer where K is set to (2^bitCount)/2, up to and including 32 bits, least-significant bit first (LSB). 478 /// </summary> 479 /// <param name="ptr">Pointer to memory region.</param> 480 /// <param name="bitOffset">Offset in bits from the pointer to the start of the integer.</param> 481 /// <param name="bitCount">Number of bits to read.</param> 482 /// <param name="value">Value to write.</param> 483 public static void WriteIntAsExcessKMultipleBits(void* ptr, uint bitOffset, uint bitCount, int value) 484 { 485 // https://en.wikipedia.org/wiki/Signed_number_representations#Offset_binary 486 var halfMax = (long)((1UL << (int)bitCount) / 2); 487 var unsignedValue = halfMax + value; 488 WriteUIntAsMultipleBits(ptr, bitOffset, bitCount, (uint)unsignedValue); 489 } 490 491 /// <summary> 492 /// Reads bits memory region as normalized unsigned integer, up to and including 32 bits, least-significant bit first (LSB). 493 /// For example reading 0 as 8 bits will result in 0.0f. Reading 0xff as 8 bits will result in 1.0f. 494 /// </summary> 495 /// <param name="ptr">Pointer to memory region.</param> 496 /// <param name="bitOffset">Offset in bits from the pointer to the start of the unsigned integer.</param> 497 /// <param name="bitCount">Number of bits to read.</param> 498 /// <returns>Normalized unsigned integer.</returns> 499 public static float ReadMultipleBitsAsNormalizedUInt(void* ptr, uint bitOffset, uint bitCount) 500 { 501 var uintValue = ReadMultipleBitsAsUInt(ptr, bitOffset, bitCount); 502 var maxValue = (uint)((1UL << (int)bitCount) - 1); 503 return NumberHelpers.UIntToNormalizedFloat(uintValue, 0, maxValue); 504 } 505 506 /// <summary> 507 /// Writes bits memory region as normalized unsigned integer, up to and including 32 bits, least-significant bit first (LSB). 508 /// </summary> 509 /// <param name="ptr">Pointer to memory region.</param> 510 /// <param name="bitOffset">Offset in bits from the pointer to the start of the unsigned integer.</param> 511 /// <param name="bitCount">Number of bits to read.</param> 512 /// <param name="value">Normalized value to write.</param> 513 public static void WriteNormalizedUIntAsMultipleBits(void* ptr, uint bitOffset, uint bitCount, float value) 514 { 515 var maxValue = (uint)((1UL << (int)bitCount) - 1); 516 var uintValue = NumberHelpers.NormalizedFloatToUInt(value, 0, maxValue); 517 WriteUIntAsMultipleBits(ptr, bitOffset, bitCount, uintValue); 518 } 519 520 public static void SetBitsInBuffer(void* buffer, int byteOffset, int bitOffset, int sizeInBits, bool value) 521 { 522 if (buffer == null) 523 throw new ArgumentException("A buffer must be provided to apply the bitmask on", nameof(buffer)); 524 if (sizeInBits < 0) 525 throw new ArgumentException("Negative sizeInBits", nameof(sizeInBits)); 526 if (bitOffset < 0) 527 throw new ArgumentException("Negative bitOffset", nameof(bitOffset)); 528 if (byteOffset < 0) 529 throw new ArgumentException("Negative byteOffset", nameof(byteOffset)); 530 531 // If we're offset by more than a byte, adjust our pointers. 532 if (bitOffset >= 8) 533 { 534 var skipBytes = bitOffset / 8; 535 byteOffset += skipBytes; 536 bitOffset %= 8; 537 } 538 539 var bytePos = (byte*)buffer + byteOffset; 540 var sizeRemainingInBits = sizeInBits; 541 542 // Handle first byte separately if unaligned to byte boundary. 543 if (bitOffset != 0) 544 { 545 var mask = 0xFF << bitOffset; 546 if (sizeRemainingInBits + bitOffset < 8) 547 { 548 mask &= 0xFF >> (8 - (sizeRemainingInBits + bitOffset)); 549 } 550 551 if (value) 552 *bytePos |= (byte)mask; 553 else 554 *bytePos &= (byte)~mask; 555 ++bytePos; 556 sizeRemainingInBits -= 8 - bitOffset; 557 } 558 559 // Handle full bytes in-between. 560 while (sizeRemainingInBits >= 8) 561 { 562 *bytePos = value ? (byte)0xFF : (byte)0; 563 ++bytePos; 564 sizeRemainingInBits -= 8; 565 } 566 567 // Handle unaligned trailing byte, if present. 568 if (sizeRemainingInBits > 0) 569 { 570 var mask = (byte)(0xFF >> 8 - sizeRemainingInBits); 571 if (value) 572 *bytePos |= mask; 573 else 574 *bytePos &= (byte)~mask; 575 } 576 577 Debug.Assert(bytePos <= (byte*)buffer + 578 ComputeFollowingByteOffset((uint)byteOffset, (uint)bitOffset + (uint)sizeInBits)); 579 } 580 581 public static void Swap<TValue>(ref TValue a, ref TValue b) 582 { 583 var temp = a; 584 a = b; 585 b = temp; 586 } 587 588 public static uint AlignNatural(uint offset, uint sizeInBytes) 589 { 590 var alignment = Math.Min(8, sizeInBytes); 591 return offset.AlignToMultipleOf(alignment); 592 } 593 } 594}