A game about forced loneliness, made by TACStudios
1using System;
2using System.Runtime.CompilerServices;
3using Unity.Collections.LowLevel.Unsafe;
4using UnityEngine.InputSystem.Utilities;
5
6////TODO: the Debug.Asserts here should be also be made as checks ahead of time (on the layout)
7
8////TODO: the read/write methods need a proper pass for consistency
9
10////FIXME: some architectures have strict memory alignment requirements; we should honor them when
11//// we read/write primitive values or support stitching values together from bytes manually
12//// where needed
13
14////TODO: allow bitOffset to be non-zero for byte-aligned control as long as result is byte-aligned
15
16////REVIEW: The combination of byte and bit offset instead of just a single bit offset has turned out
17//// to be plenty awkward to use in practice; should be replace it?
18
19////REVIEW: AutomaticOffset is a very awkward mechanism; it's primary use really is for "parking" unused
20//// controls for which a more elegant and robust mechanism can surely be devised
21
22namespace UnityEngine.InputSystem.LowLevel
23{
24 /// <summary>
25 /// Information about a memory region storing input state.
26 /// </summary>
27 /// <remarks>
28 /// Input state is kept in raw memory blocks. All state is centrally managed by the input system;
29 /// controls cannot keep their own independent state.
30 ///
31 /// Each state block is tagged with a format code indicating the storage format used for the
32 /// memory block. This can either be one out of a set of primitive formats (such as "INT") or a custom
33 /// format code indicating a more complex format.
34 ///
35 /// Memory using primitive formats can be converted to and from primitive values directly by this struct.
36 ///
37 /// State memory is bit-addressable, meaning that it can be offset from a byte address in bits (<see cref="bitOffset"/>)
38 /// and is sized in bits instead of bytes (<see cref="sizeInBits"/>). However, in practice, bit-addressing
39 /// memory reads and writes are only supported on the <see cref="FormatBit">bitfield primitive format</see>.
40 ///
41 /// Input state memory is restricted to a maximum of 4GB in size. Offsets are recorded in 32 bits.
42 /// </remarks>
43 /// <seealso cref="InputControl.stateBlock"/>
44 public unsafe struct InputStateBlock
45 {
46 public const uint InvalidOffset = 0xffffffff;
47 public const uint AutomaticOffset = 0xfffffffe;
48
49 /// <summary>
50 /// Format code for invalid value type
51 /// </summary>
52 /// <seealso cref="format"/>
53 public static readonly FourCC FormatInvalid = new FourCC(0);
54 internal const int kFormatInvalid = 0;
55
56 /// <summary>
57 /// Format code for a variable-width bitfield representing an unsigned value,
58 /// i.e. all bits including the highest one represent the magnitude of the value.
59 /// </summary>
60 /// <seealso cref="format"/>
61 public static readonly FourCC FormatBit = new FourCC('B', 'I', 'T');
62 internal const int kFormatBit = 'B' << 24 | 'I' << 16 | 'T' << 8 | ' ';
63
64 /// <summary>
65 /// Format code for a variable-width bitfield representing a signed value, i.e. the
66 /// highest bit is used as a sign bit (0=unsigned, 1=signed) and the remaining bits represent
67 /// the magnitude of the value.
68 /// </summary>
69 /// <seealso cref="format"/>
70 public static readonly FourCC FormatSBit = new FourCC('S', 'B', 'I', 'T');
71 internal const int kFormatSBit = 'S' << 24 | 'B' << 16 | 'I' << 8 | 'T';
72
73 /// <summary>
74 /// Format code for a 32-bit signed integer value.
75 /// </summary>
76 /// <seealso cref="format"/>
77 public static readonly FourCC FormatInt = new FourCC('I', 'N', 'T');
78 internal const int kFormatInt = 'I' << 24 | 'N' << 16 | 'T' << 8 | ' ';
79
80 /// <summary>
81 /// Format code for a 32-bit unsigned integer value.
82 /// </summary>
83 /// <seealso cref="format"/>
84 public static readonly FourCC FormatUInt = new FourCC('U', 'I', 'N', 'T');
85 internal const int kFormatUInt = 'U' << 24 | 'I' << 16 | 'N' << 8 | 'T';
86
87 /// <summary>
88 /// Format code for a 16-bit signed integer value.
89 /// </summary>
90 /// <seealso cref="format"/>
91 public static readonly FourCC FormatShort = new FourCC('S', 'H', 'R', 'T');
92 internal const int kFormatShort = 'S' << 24 | 'H' << 16 | 'R' << 8 | 'T';
93
94 /// <summary>
95 /// Format code for a 16-bit unsigned integer value.
96 /// </summary>
97 /// <seealso cref="format"/>
98 public static readonly FourCC FormatUShort = new FourCC('U', 'S', 'H', 'T');
99 internal const int kFormatUShort = 'U' << 24 | 'S' << 16 | 'H' << 8 | 'T';
100
101 /// <summary>
102 /// Format code for an 8-bit unsigned integer value.
103 /// </summary>
104 /// <seealso cref="format"/>
105 public static readonly FourCC FormatByte = new FourCC('B', 'Y', 'T', 'E');
106 internal const int kFormatByte = 'B' << 24 | 'Y' << 16 | 'T' << 8 | 'E';
107
108 /// <summary>
109 /// Format code for an 8-bit signed integer value.
110 /// </summary>
111 /// <seealso cref="format"/>
112 public static readonly FourCC FormatSByte = new FourCC('S', 'B', 'Y', 'T');
113 internal const int kFormatSByte = 'S' << 24 | 'B' << 16 | 'Y' << 8 | 'T';
114
115 /// <summary>
116 /// Format code for a 64-bit signed integer value.
117 /// </summary>
118 /// <seealso cref="format"/>
119 public static readonly FourCC FormatLong = new FourCC('L', 'N', 'G');
120 internal const int kFormatLong = 'L' << 24 | 'N' << 16 | 'G' << 8 | ' ';
121
122 /// <summary>
123 /// Format code for a 64-bit unsigned integer value.
124 /// </summary>
125 /// <seealso cref="format"/>
126 public static readonly FourCC FormatULong = new FourCC('U', 'L', 'N', 'G');
127 internal const int kFormatULong = 'U' << 24 | 'L' << 16 | 'N' << 8 | 'G';
128
129 /// <summary>
130 /// Format code for a 32-bit floating-point value.
131 /// </summary>
132 /// <seealso cref="format"/>
133 public static readonly FourCC FormatFloat = new FourCC('F', 'L', 'T');
134 internal const int kFormatFloat = 'F' << 24 | 'L' << 16 | 'T' << 8 | ' ';
135
136 /// <summary>
137 /// Format code for a 64-bit floating-point value.
138 /// </summary>
139 /// <seealso cref="format"/>
140 public static readonly FourCC FormatDouble = new FourCC('D', 'B', 'L');
141 internal const int kFormatDouble = 'D' << 24 | 'B' << 16 | 'L' << 8 | ' ';
142
143 ////REVIEW: are these really useful?
144 public static readonly FourCC FormatVector2 = new FourCC('V', 'E', 'C', '2');
145 internal const int kFormatVector2 = 'V' << 24 | 'E' << 16 | 'C' << 8 | '2';
146 public static readonly FourCC FormatVector3 = new FourCC('V', 'E', 'C', '3');
147 internal const int kFormatVector3 = 'V' << 24 | 'E' << 16 | 'C' << 8 | '3';
148 public static readonly FourCC FormatQuaternion = new FourCC('Q', 'U', 'A', 'T');
149 internal const int kFormatQuaternion = 'Q' << 24 | 'U' << 16 | 'A' << 8 | 'T';
150 public static readonly FourCC FormatVector2Short = new FourCC('V', 'C', '2', 'S');
151 public static readonly FourCC FormatVector3Short = new FourCC('V', 'C', '3', 'S');
152 public static readonly FourCC FormatVector2Byte = new FourCC('V', 'C', '2', 'B');
153 public static readonly FourCC FormatVector3Byte = new FourCC('V', 'C', '3', 'B');
154 public static readonly FourCC FormatPose = new FourCC('P', 'o', 's', 'e');
155 internal const int kFormatPose = 'P' << 24 | 'o' << 16 | 's' << 8 | 'e';
156
157 public static int GetSizeOfPrimitiveFormatInBits(FourCC type)
158 {
159 if (type == FormatBit || type == FormatSBit)
160 return 1;
161 if (type == FormatInt || type == FormatUInt)
162 return 4 * 8;
163 if (type == FormatShort || type == FormatUShort)
164 return 2 * 8;
165 if (type == FormatByte || type == FormatSByte)
166 return 1 * 8;
167 if (type == FormatLong || type == FormatULong)
168 return 8 * 8;
169 if (type == FormatFloat)
170 return 4 * 8;
171 if (type == FormatDouble)
172 return 8 * 8;
173 if (type == FormatVector2)
174 return 2 * 4 * 8;
175 if (type == FormatVector3)
176 return 3 * 4 * 8;
177 if (type == FormatQuaternion)
178 return 4 * 4 * 8;
179 if (type == FormatVector2Short)
180 return 2 * 2 * 8;
181 if (type == FormatVector3Short)
182 return 3 * 2 * 8;
183 if (type == FormatVector2Byte)
184 return 2 * 1 * 8;
185 if (type == FormatVector3Byte)
186 return 3 * 1 * 8;
187 return -1;
188 }
189
190 public static FourCC GetPrimitiveFormatFromType(Type type)
191 {
192 if (ReferenceEquals(type, typeof(int)))
193 return FormatInt;
194 if (ReferenceEquals(type, typeof(uint)))
195 return FormatUInt;
196 if (ReferenceEquals(type, typeof(short)))
197 return FormatShort;
198 if (ReferenceEquals(type, typeof(ushort)))
199 return FormatUShort;
200 if (ReferenceEquals(type, typeof(byte)))
201 return FormatByte;
202 if (ReferenceEquals(type, typeof(sbyte)))
203 return FormatSByte;
204 if (ReferenceEquals(type, typeof(long)))
205 return FormatLong;
206 if (ReferenceEquals(type, typeof(ulong)))
207 return FormatULong;
208 if (ReferenceEquals(type, typeof(float)))
209 return FormatFloat;
210 if (ReferenceEquals(type, typeof(double)))
211 return FormatDouble;
212 if (ReferenceEquals(type, typeof(Vector2)))
213 return FormatVector2;
214 if (ReferenceEquals(type, typeof(Vector3)))
215 return FormatVector3;
216 if (ReferenceEquals(type, typeof(Quaternion)))
217 return FormatQuaternion;
218 return new FourCC();
219 }
220
221 /// <summary>
222 /// Type identifier for the memory layout used by the state.
223 /// </summary>
224 /// <remarks>
225 /// Used for safety checks to make sure that when the system copies state memory, it
226 /// copies between compatible layouts. If set to a primitive state format, also used to
227 /// determine the size of the state block.
228 /// </remarks>
229 public FourCC format { get; set; }
230
231 ////TODO: collapse byteOffset and bitOffset into a single 'offset' field
232 // Offset into state buffer. After a device is added to the system, this is relative
233 // to the global buffers; otherwise it is relative to the device root.
234 // During setup, this can be InvalidOffset to indicate a control that should be placed
235 // at an offset automatically; otherwise it denotes a fixed offset relative to the
236 // parent control.
237 public uint byteOffset
238 {
239 get => m_ByteOffset;
240 set
241 {
242 m_ByteOffset = value;
243 }
244 }
245
246 // Needed for fast access to avoid a call to getter in some places
247 internal uint m_ByteOffset;
248
249 // Bit offset from the given byte offset. Also zero-based (i.e. first bit is at bit
250 // offset #0).
251 public uint bitOffset { get; set; }
252
253 // Size of the state in bits. If this % 8 is not 0, the control is considered a
254 // bitfield control.
255 // During setup, if this field is 0 it means the size of the control should be automatically
256 // computed from either its children (if it has any) or its set format. If it has neither,
257 // setup will throw.
258 public uint sizeInBits { get; set; }
259
260 internal uint alignedSizeInBytes => (sizeInBits + 7) >> 3;
261 internal uint effectiveByteOffset => byteOffset + (bitOffset >> 3);
262 internal uint effectiveBitOffset => byteOffset * 8 + bitOffset;
263
264 public int ReadInt(void* statePtr)
265 {
266 Debug.Assert(sizeInBits != 0);
267
268 var valuePtr = (byte*)statePtr + (int)byteOffset;
269
270 var fmt = (int)format;
271 switch (fmt)
272 {
273 case kFormatBit:
274 if (sizeInBits == 1)
275 return MemoryHelpers.ReadSingleBit(valuePtr, bitOffset) ? 1 : 0;
276 return (int)MemoryHelpers.ReadMultipleBitsAsUInt(valuePtr, bitOffset, sizeInBits);
277 case kFormatSBit:
278 if (sizeInBits == 1)
279 return MemoryHelpers.ReadSingleBit(valuePtr, bitOffset) ? 1 : -1;
280 return MemoryHelpers.ReadExcessKMultipleBitsAsInt(valuePtr, bitOffset, sizeInBits);
281 case kFormatInt:
282 case kFormatUInt:
283 Debug.Assert(sizeInBits == 32, "INT and UINT state must have sizeInBits=32");
284 Debug.Assert(bitOffset == 0, "INT and UINT state must be byte-aligned");
285 if (fmt == kFormatUInt)
286 Debug.Assert(*(uint*)valuePtr <= int.MaxValue, "UINT must fit in the int");
287 return *(int*)valuePtr;
288 case kFormatShort:
289 Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16");
290 Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned");
291 return *(short*)valuePtr;
292 case kFormatUShort:
293 Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16");
294 Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned");
295 return *(ushort*)valuePtr;
296 case kFormatByte:
297 Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8");
298 Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned");
299 return *valuePtr;
300 case kFormatSByte:
301 Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8");
302 Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned");
303 return *(sbyte*)valuePtr;
304 // Not supported:
305 // - kFormatLong
306 // - kFormatULong
307 // - kFormatFloat
308 // - kFormatDouble
309 default:
310 throw new InvalidOperationException($"State format '{format}' is not supported as integer format");
311 }
312 }
313
314 public void WriteInt(void* statePtr, int value)
315 {
316 Debug.Assert(sizeInBits != 0);
317
318 var valuePtr = (byte*)statePtr + (int)byteOffset;
319
320 var fmt = (int)format;
321 switch (fmt)
322 {
323 case kFormatBit:
324 if (sizeInBits == 1)
325 MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value != 0);
326 else
327 MemoryHelpers.WriteUIntAsMultipleBits(valuePtr, bitOffset, sizeInBits, (uint)value);
328 break;
329 case kFormatSBit:
330 if (sizeInBits == 1)
331 MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value > 0);
332 else
333 MemoryHelpers.WriteIntAsExcessKMultipleBits(valuePtr, bitOffset, sizeInBits, value);
334 break;
335 case kFormatInt:
336 case kFormatUInt:
337 Debug.Assert(sizeInBits == 32, "INT and UINT state must have sizeInBits=32");
338 Debug.Assert(bitOffset == 0, "INT and UINT state must be byte-aligned");
339 *(int*)valuePtr = value;
340 break;
341 case kFormatShort:
342 Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16");
343 Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned");
344 *(short*)valuePtr = (short)value;
345 break;
346 case kFormatUShort:
347 Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16");
348 Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned");
349 *(ushort*)valuePtr = (ushort)value;
350 break;
351 case kFormatByte:
352 Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8");
353 Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned");
354 *valuePtr = (byte)value;
355 break;
356 case kFormatSByte:
357 Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8");
358 Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned");
359 *(sbyte*)valuePtr = (sbyte)value;
360 break;
361 // Not supported:
362 // - kFormatLong
363 // - kFormatULong
364 // - kFormatFloat
365 // - kFormatDouble
366 default:
367 throw new Exception($"State format '{format}' is not supported as integer format");
368 }
369 }
370
371 public float ReadFloat(void* statePtr)
372 {
373 Debug.Assert(sizeInBits != 0);
374
375 var valuePtr = (byte*)statePtr + (int)byteOffset;
376
377 var fmt = (int)format;
378 switch (fmt)
379 {
380 // If a control with an integer-based representation does not use the full range
381 // of its integer size (e.g. only goes from [0..128]), processors or the parameters
382 // above have to be used to re-process the resulting float values.
383 case kFormatBit:
384 if (sizeInBits == 1)
385 return MemoryHelpers.ReadSingleBit(valuePtr, bitOffset) ? 1.0f : 0.0f;
386 return MemoryHelpers.ReadMultipleBitsAsNormalizedUInt(valuePtr, bitOffset, sizeInBits);
387 case kFormatSBit:
388 if (sizeInBits == 1)
389 return MemoryHelpers.ReadSingleBit(valuePtr, bitOffset) ? 1.0f : -1.0f;
390 return MemoryHelpers.ReadMultipleBitsAsNormalizedUInt(valuePtr, bitOffset, sizeInBits) * 2.0f - 1.0f;
391 case kFormatInt:
392 Debug.Assert(sizeInBits == 32, "INT state must have sizeInBits=32");
393 Debug.Assert(bitOffset == 0, "INT state must be byte-aligned");
394 return NumberHelpers.IntToNormalizedFloat(*(int*)valuePtr, int.MinValue, int.MaxValue) * 2.0f - 1.0f;
395 case kFormatUInt:
396 Debug.Assert(sizeInBits == 32, "UINT state must have sizeInBits=32");
397 Debug.Assert(bitOffset == 0, "UINT state must be byte-aligned");
398 return NumberHelpers.UIntToNormalizedFloat(*(uint*)valuePtr, uint.MinValue, uint.MaxValue);
399 case kFormatShort:
400 Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16");
401 Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned");
402 return NumberHelpers.IntToNormalizedFloat(*(short*)valuePtr, short.MinValue, short.MaxValue) * 2.0f - 1.0f;
403 case kFormatUShort:
404 Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16");
405 Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned");
406 return NumberHelpers.UIntToNormalizedFloat(*(ushort*)valuePtr, ushort.MinValue, ushort.MaxValue);
407 case kFormatByte:
408 Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8");
409 Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned");
410 return NumberHelpers.UIntToNormalizedFloat(*valuePtr, byte.MinValue, byte.MaxValue);
411 case kFormatSByte:
412 Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8");
413 Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned");
414 return NumberHelpers.IntToNormalizedFloat(*(sbyte*)valuePtr, sbyte.MinValue, sbyte.MaxValue) * 2.0f - 1.0f;
415 case kFormatFloat:
416 Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32");
417 Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned");
418 return *(float*)valuePtr;
419 case kFormatDouble:
420 Debug.Assert(sizeInBits == 64, "DBL state must have sizeInBits=64");
421 Debug.Assert(bitOffset == 0, "DBL state must be byte-aligned");
422 return (float)*(double*)valuePtr;
423 // Not supported:
424 // - kFormatLong
425 // - kFormatULong
426 default:
427 throw new InvalidOperationException($"State format '{format}' is not supported as floating-point format");
428 }
429 }
430
431 public void WriteFloat(void* statePtr, float value)
432 {
433 var valuePtr = (byte*)statePtr + (int)byteOffset;
434
435 var fmt = (int)format;
436 switch (fmt)
437 {
438 case kFormatBit:
439 if (sizeInBits == 1)
440 MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value >= 0.5f);////REVIEW: Shouldn't this be the global button press point?
441 else
442 MemoryHelpers.WriteNormalizedUIntAsMultipleBits(valuePtr, bitOffset, sizeInBits, value);
443 break;
444 case kFormatSBit:
445 if (sizeInBits == 1)
446 MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value >= 0.0f);
447 else
448 MemoryHelpers.WriteNormalizedUIntAsMultipleBits(valuePtr, bitOffset, sizeInBits, value * 0.5f + 0.5f);
449 break;
450 case kFormatInt:
451 Debug.Assert(sizeInBits == 32, "INT state must have sizeInBits=32");
452 Debug.Assert(bitOffset == 0, "INT state must be byte-aligned");
453 *(int*)valuePtr = (int)NumberHelpers.NormalizedFloatToInt(value * 0.5f + 0.5f, int.MinValue, int.MaxValue);
454 break;
455 case kFormatUInt:
456 Debug.Assert(sizeInBits == 32, "UINT state must have sizeInBits=32");
457 Debug.Assert(bitOffset == 0, "UINT state must be byte-aligned");
458 *(uint*)valuePtr = NumberHelpers.NormalizedFloatToUInt(value, uint.MinValue, uint.MaxValue);
459 break;
460 case kFormatShort:
461 Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16");
462 Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned");
463 *(short*)valuePtr = (short)NumberHelpers.NormalizedFloatToInt(value * 0.5f + 0.5f, short.MinValue, short.MaxValue);
464 break;
465 case kFormatUShort:
466 Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16");
467 Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned");
468 *(ushort*)valuePtr = (ushort)NumberHelpers.NormalizedFloatToUInt(value, ushort.MinValue, ushort.MaxValue);
469 break;
470 case kFormatByte:
471 Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8");
472 Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned");
473 *valuePtr = (byte)NumberHelpers.NormalizedFloatToUInt(value, byte.MinValue, byte.MaxValue);
474 break;
475 case kFormatSByte:
476 Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8");
477 Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned");
478 *(sbyte*)valuePtr = (sbyte)NumberHelpers.NormalizedFloatToInt(value * 0.5f + 0.5f, sbyte.MinValue, sbyte.MaxValue);
479 break;
480 case kFormatFloat:
481 Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32");
482 Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned");
483 *(float*)valuePtr = value;
484 break;
485 case kFormatDouble:
486 Debug.Assert(sizeInBits == 64, "DBL state must have sizeInBits=64");
487 Debug.Assert(bitOffset == 0, "DBL state must be byte-aligned");
488 *(double*)valuePtr = value;
489 break;
490 // Not supported:
491 // - kFormatLong
492 // - kFormatULong
493 default:
494 throw new Exception($"State format '{format}' is not supported as floating-point format");
495 }
496 }
497
498 internal PrimitiveValue FloatToPrimitiveValue(float value)
499 {
500 var fmt = (int)format;
501 switch (fmt)
502 {
503 case kFormatBit:
504 if (sizeInBits == 1)
505 return value >= 0.5f;
506 ////FIXME: is this supposed to be int or uint?
507 return (int)NumberHelpers.NormalizedFloatToUInt(value, 0, (uint)((1UL << (int)sizeInBits) - 1));
508 case kFormatSBit:
509 {
510 if (sizeInBits == 1)
511 return value >= 0.0f;
512 var minValue = (int)-(long)(1UL << ((int)sizeInBits - 1));
513 var maxValue = (int)((1UL << ((int)sizeInBits - 1)) - 1);
514 return NumberHelpers.NormalizedFloatToInt(value, minValue, maxValue);
515 }
516 case kFormatInt:
517 Debug.Assert(sizeInBits == 32, "INT state must have sizeInBits=32");
518 Debug.Assert(bitOffset == 0, "INT state must be byte-aligned");
519 return NumberHelpers.NormalizedFloatToInt(value * 0.5f + 0.5f, int.MinValue, int.MaxValue);
520 case kFormatUInt:
521 Debug.Assert(sizeInBits == 32, "UINT state must have sizeInBits=32");
522 Debug.Assert(bitOffset == 0, "UINT state must be byte-aligned");
523 return NumberHelpers.NormalizedFloatToUInt(value, uint.MinValue, uint.MaxValue);
524 case kFormatShort:
525 Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16");
526 Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned");
527 return (short)NumberHelpers.NormalizedFloatToInt(value * 0.5f + 0.5f, short.MinValue, short.MaxValue);
528 case kFormatUShort:
529 Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16");
530 Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned");
531 return (ushort)NumberHelpers.NormalizedFloatToUInt(value, ushort.MinValue, ushort.MaxValue);
532 case kFormatByte:
533 Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8");
534 Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned");
535 return (byte)NumberHelpers.NormalizedFloatToUInt(value, byte.MinValue, byte.MaxValue);
536 case kFormatSByte:
537 Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8");
538 Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned");
539 return (sbyte)NumberHelpers.NormalizedFloatToInt(value * 0.5f + 0.5f, sbyte.MinValue, sbyte.MaxValue);
540 case kFormatFloat:
541 Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32");
542 Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned");
543 return value;
544 case kFormatDouble:
545 Debug.Assert(sizeInBits == 64, "DBL state must have sizeInBits=64");
546 Debug.Assert(bitOffset == 0, "DBL state must be byte-aligned");
547 return value;
548 // Not supported:
549 // - kFormatLong
550 // - kFormatULong
551 default:
552 throw new Exception($"State format '{format}' is not supported as floating-point format");
553 }
554 }
555
556 ////REVIEW: This is some bad code duplication here between Read/WriteFloat&Double but given that there's no
557 //// way to use a type argument here, not sure how to get rid of it.
558
559 public double ReadDouble(void* statePtr)
560 {
561 Debug.Assert(sizeInBits != 0);
562
563 var valuePtr = (byte*)statePtr + (int)byteOffset;
564
565 var fmt = (int)format;
566 switch (fmt)
567 {
568 // If a control with an integer-based representation does not use the full range
569 // of its integer size (e.g. only goes from [0..128]), processors or the parameters
570 // above have to be used to re-process the resulting float values.
571 case kFormatBit:
572 if (sizeInBits == 1)
573 return MemoryHelpers.ReadSingleBit(valuePtr, bitOffset) ? 1.0f : 0.0f;
574 return MemoryHelpers.ReadMultipleBitsAsNormalizedUInt(valuePtr, bitOffset, sizeInBits);
575 case kFormatSBit:
576 if (sizeInBits == 1)
577 return MemoryHelpers.ReadSingleBit(valuePtr, bitOffset) ? 1.0f : -1.0f;
578 return MemoryHelpers.ReadMultipleBitsAsNormalizedUInt(valuePtr, bitOffset, sizeInBits) * 2.0f - 1.0f;
579 case kFormatInt:
580 Debug.Assert(sizeInBits == 32, "INT state must have sizeInBits=32");
581 Debug.Assert(bitOffset == 0, "INT state must be byte-aligned");
582 return NumberHelpers.IntToNormalizedFloat(*(int*)valuePtr, int.MinValue, int.MaxValue) * 2.0f - 1.0f;
583 case kFormatUInt:
584 Debug.Assert(sizeInBits == 32, "UINT state must have sizeInBits=32");
585 Debug.Assert(bitOffset == 0, "UINT state must be byte-aligned");
586 return NumberHelpers.UIntToNormalizedFloat(*(uint*)valuePtr, uint.MinValue, uint.MaxValue);
587 case kFormatShort:
588 Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16");
589 Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned");
590 return NumberHelpers.IntToNormalizedFloat(*(short*)valuePtr, short.MinValue, short.MaxValue) * 2.0f - 1.0f;
591 case kFormatUShort:
592 Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16");
593 Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned");
594 return NumberHelpers.UIntToNormalizedFloat(*(ushort*)valuePtr, ushort.MinValue, ushort.MaxValue);
595 case kFormatByte:
596 Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8");
597 Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned");
598 return NumberHelpers.UIntToNormalizedFloat(*valuePtr, byte.MinValue, byte.MaxValue);
599 case kFormatSByte:
600 Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8");
601 Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned");
602 return NumberHelpers.IntToNormalizedFloat(*(sbyte*)valuePtr, sbyte.MinValue, sbyte.MaxValue) * 2.0f - 1.0f;
603 case kFormatFloat:
604 Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32");
605 Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned");
606 return *(float*)valuePtr;
607 case kFormatDouble:
608 Debug.Assert(sizeInBits == 64, "DBL state must have sizeInBits=64");
609 Debug.Assert(bitOffset == 0, "DBL state must be byte-aligned");
610 return *(double*)valuePtr;
611 // Not supported:
612 // - kFormatLong
613 // - kFormatULong
614 // - kFormatFloat
615 // - kFormatDouble
616 default:
617 throw new Exception($"State format '{format}' is not supported as floating-point format");
618 }
619 }
620
621 public void WriteDouble(void* statePtr, double value)
622 {
623 var valuePtr = (byte*)statePtr + (int)byteOffset;
624
625 var fmt = (int)format;
626 switch (fmt)
627 {
628 case kFormatBit:
629 if (sizeInBits == 1)
630 MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value >= 0.5f);
631 else
632 MemoryHelpers.WriteNormalizedUIntAsMultipleBits(valuePtr, bitOffset, sizeInBits, (float)value);
633 break;
634 case kFormatSBit:
635 if (sizeInBits == 1)
636 MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value >= 0.0f);
637 else
638 MemoryHelpers.WriteNormalizedUIntAsMultipleBits(valuePtr, bitOffset, sizeInBits, (float)value * 0.5f + 0.5f);
639 break;
640 case kFormatInt:
641 Debug.Assert(sizeInBits == 32, "INT state must have sizeInBits=16");
642 Debug.Assert(bitOffset == 0, "INT state must be byte-aligned");
643 *(int*)valuePtr = NumberHelpers.NormalizedFloatToInt((float)value * 0.5f + 0.5f, int.MinValue, int.MaxValue);
644 break;
645 case kFormatUInt:
646 Debug.Assert(sizeInBits == 32, "UINT state must have sizeInBits=16");
647 Debug.Assert(bitOffset == 0, "UINT state must be byte-aligned");
648 *(uint*)valuePtr = NumberHelpers.NormalizedFloatToUInt((float)value, uint.MinValue, uint.MaxValue);
649 break;
650 case kFormatShort:
651 Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16");
652 Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned");
653 *(short*)valuePtr = (short)NumberHelpers.NormalizedFloatToInt((float)value * 0.5f + 0.5f, short.MinValue, short.MaxValue);
654 break;
655 case kFormatUShort:
656 Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16");
657 Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned");
658 *(ushort*)valuePtr = (ushort)NumberHelpers.NormalizedFloatToUInt((float)value, ushort.MinValue, ushort.MaxValue);
659 break;
660 case kFormatByte:
661 Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8");
662 Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned");
663 *valuePtr = (byte)NumberHelpers.NormalizedFloatToUInt((float)value, byte.MinValue, byte.MaxValue);
664 break;
665 case kFormatSByte:
666 Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8");
667 Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned");
668 *(sbyte*)valuePtr = (sbyte)NumberHelpers.NormalizedFloatToInt((float)value * 0.5f + 0.5f, sbyte.MinValue, sbyte.MaxValue);
669 break;
670 case kFormatFloat:
671 Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32");
672 Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned");
673 *(float*)valuePtr = (float)value;
674 break;
675 case kFormatDouble:
676 Debug.Assert(sizeInBits == 64, "DBL state must have sizeInBits=64");
677 Debug.Assert(bitOffset == 0, "DBL state must be byte-aligned");
678 *(double*)valuePtr = value;
679 break;
680 // Not supported:
681 // - kFormatLong
682 // - kFormatULong
683 // - kFormatFloat
684 // - kFormatDouble
685 default:
686 throw new InvalidOperationException($"State format '{format}' is not supported as floating-point format");
687 }
688 }
689
690 public void Write(void* statePtr, PrimitiveValue value)
691 {
692 var valuePtr = (byte*)statePtr + (int)byteOffset;
693
694 var fmt = (int)format;
695 switch (fmt)
696 {
697 case kFormatBit:
698 if (sizeInBits == 1)
699 MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value.ToBoolean());
700 else
701 MemoryHelpers.WriteUIntAsMultipleBits(valuePtr, bitOffset, sizeInBits, value.ToUInt32());
702 break;
703 case kFormatSBit:
704 if (sizeInBits == 1)
705 MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value.ToBoolean());
706 else
707 ////REVIEW: previous implementation was writing int32 as two's complement here
708 MemoryHelpers.WriteIntAsExcessKMultipleBits(valuePtr, bitOffset, sizeInBits, value.ToInt32());
709 break;
710 case kFormatInt:
711 Debug.Assert(sizeInBits == 32, "INT state must have sizeInBits=32");
712 Debug.Assert(bitOffset == 0, "INT state must be byte-aligned");
713 *(int*)valuePtr = value.ToInt32();
714 break;
715 case kFormatUInt:
716 Debug.Assert(sizeInBits == 32, "UINT state must have sizeInBits=32");
717 Debug.Assert(bitOffset == 0, "UINT state must be byte-aligned");
718 *(uint*)valuePtr = value.ToUInt32();
719 break;
720 case kFormatShort:
721 Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16");
722 Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned");
723 *(short*)valuePtr = value.ToInt16();
724 break;
725 case kFormatUShort:
726 Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16");
727 Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned");
728 *(ushort*)valuePtr = value.ToUInt16();
729 break;
730 case kFormatByte:
731 Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8");
732 Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned");
733 *valuePtr = value.ToByte();
734 break;
735 case kFormatSByte:
736 Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8");
737 Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned");
738 *(sbyte*)valuePtr = value.ToSByte();
739 break;
740 case kFormatFloat:
741 Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32");
742 Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned");
743 *(float*)valuePtr = value.ToSingle();
744 break;
745 // Not supported:
746 // - kFormatLong
747 // - kFormatULong
748 // - kFormatDouble
749 default:
750 throw new NotImplementedException(
751 $"Writing primitive value of type '{value.type}' into state block with format '{format}'");
752 }
753 }
754
755 public void CopyToFrom(void* toStatePtr, void* fromStatePtr)
756 {
757 if (bitOffset != 0 || sizeInBits % 8 != 0)
758 throw new NotImplementedException("Copying bitfields");
759
760 var from = (byte*)fromStatePtr + byteOffset;
761 var to = (byte*)toStatePtr + byteOffset;
762
763 UnsafeUtility.MemCpy(to, from, alignedSizeInBytes);
764 }
765 }
766}