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}