InputStateBlock.cs 35 KB

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