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}