using System; using Unity.Collections.LowLevel.Unsafe; using UnityEngine.InputSystem.Utilities; ////TODO: the Debug.Asserts here should be also be made as checks ahead of time (on the layout) ////TODO: the read/write methods need a proper pass for consistency ////FIXME: some architectures have strict memory alignment requirements; we should honor them when //// we read/write primitive values or support stitching values together from bytes manually //// where needed ////TODO: allow bitOffset to be non-zero for byte-aligned control as long as result is byte-aligned ////REVIEW: The combination of byte and bit offset instead of just a single bit offset has turned out //// to be plenty awkward to use in practice; should be replace it? ////REVIEW: AutomaticOffset is a very awkward mechanism; it's primary use really is for "parking" unused //// controls for which a more elegant and robust mechanism can surely be devised namespace UnityEngine.InputSystem.LowLevel { /// /// Information about a memory region storing input state. /// /// /// Input state is kept in raw memory blocks. All state is centrally managed by the input system; /// controls cannot keep their own independent state. /// /// Each state block is tagged with a format code indicating the storage format used for the /// memory block. This can either be one out of a set of primitive formats (such as "INT") or a custom /// format code indicating a more complex format. /// /// Memory using primitive formats can be converted to and from primitive values directly by this struct. /// /// State memory is bit-addressable, meaning that it can be offset from a byte address in bits () /// and is sized in bits instead of bytes (). However, in practice, bit-addressing /// memory reads and writes are only supported on the bitfield primitive format. /// /// Input state memory is restricted to a maximum of 4GB in size. Offsets are recorded in 32 bits. /// /// public unsafe struct InputStateBlock { public const uint InvalidOffset = 0xffffffff; public const uint AutomaticOffset = 0xfffffffe; /// /// Format code for a variable-width bitfield representing an unsigned value, /// i.e. all bits including the highest one represent the magnitude of the value. /// /// public static readonly FourCC FormatBit = new FourCC('B', 'I', 'T'); /// /// Format code for a variable-width bitfield representing a signed value, i.e. the /// highest bit is used as a sign bit (0=unsigned, 1=signed) and the remaining bits represent /// the magnitude of the value. /// /// public static readonly FourCC FormatSBit = new FourCC('S', 'B', 'I', 'T'); /// /// Format code for a 32-bit signed integer value. /// /// public static readonly FourCC FormatInt = new FourCC('I', 'N', 'T'); /// /// Format code for a 32-bit unsigned integer value. /// /// public static readonly FourCC FormatUInt = new FourCC('U', 'I', 'N', 'T'); /// /// Format code for a 16-bit signed integer value. /// /// public static readonly FourCC FormatShort = new FourCC('S', 'H', 'R', 'T'); /// /// Format code for a 16-bit unsigned integer value. /// /// public static readonly FourCC FormatUShort = new FourCC('U', 'S', 'H', 'T'); /// /// Format code for an 8-bit unsigned integer value. /// /// public static readonly FourCC FormatByte = new FourCC('B', 'Y', 'T', 'E'); /// /// Format code for an 8-bit signed integer value. /// /// public static readonly FourCC FormatSByte = new FourCC('S', 'B', 'Y', 'T'); /// /// Format code for a 64-bit signed integer value. /// /// public static readonly FourCC FormatLong = new FourCC('L', 'N', 'G'); /// /// Format code for a 64-bit unsigned integer value. /// /// public static readonly FourCC FormatULong = new FourCC('U', 'L', 'N', 'G'); /// /// Format code for a 32-bit floating-point value. /// /// public static readonly FourCC FormatFloat = new FourCC('F', 'L', 'T'); /// /// Format code for a 64-bit floating-point value. /// /// public static readonly FourCC FormatDouble = new FourCC('D', 'B', 'L'); ////REVIEW: are these really useful? public static readonly FourCC FormatVector2 = new FourCC('V', 'E', 'C', '2'); public static readonly FourCC FormatVector3 = new FourCC('V', 'E', 'C', '3'); public static readonly FourCC FormatQuaternion = new FourCC('Q', 'U', 'A', 'T'); public static readonly FourCC FormatVector2Short = new FourCC('V', 'C', '2', 'S'); public static readonly FourCC FormatVector3Short = new FourCC('V', 'C', '3', 'S'); public static readonly FourCC FormatVector2Byte = new FourCC('V', 'C', '2', 'B'); public static readonly FourCC FormatVector3Byte = new FourCC('V', 'C', '3', 'B'); public static int GetSizeOfPrimitiveFormatInBits(FourCC type) { if (type == FormatBit || type == FormatSBit) return 1; if (type == FormatInt || type == FormatUInt) return 4 * 8; if (type == FormatShort || type == FormatUShort) return 2 * 8; if (type == FormatByte || type == FormatSByte) return 1 * 8; if (type == FormatFloat) return 4 * 8; if (type == FormatDouble) return 8 * 8; if (type == FormatLong || type == FormatULong) return 8 * 8; if (type == FormatVector2) return 2 * 4 * 8; if (type == FormatVector3) return 3 * 4 * 8; if (type == FormatQuaternion) return 4 * 4 * 8; if (type == FormatVector2Short) return 2 * 2 * 8; if (type == FormatVector3Short) return 3 * 2 * 8; if (type == FormatVector2Byte) return 2 * 1 * 8; if (type == FormatVector3Byte) return 3 * 1 * 8; return -1; } public static FourCC GetPrimitiveFormatFromType(Type type) { if (ReferenceEquals(type, typeof(int))) return FormatInt; if (ReferenceEquals(type, typeof(uint))) return FormatUInt; if (ReferenceEquals(type, typeof(short))) return FormatShort; if (ReferenceEquals(type, typeof(ushort))) return FormatUShort; if (ReferenceEquals(type, typeof(byte))) return FormatByte; if (ReferenceEquals(type, typeof(sbyte))) return FormatSByte; if (ReferenceEquals(type, typeof(float))) return FormatFloat; if (ReferenceEquals(type, typeof(double))) return FormatDouble; if (ReferenceEquals(type, typeof(long))) return FormatLong; if (ReferenceEquals(type, typeof(ulong))) return FormatULong; if (ReferenceEquals(type, typeof(Vector2))) return FormatVector2; if (ReferenceEquals(type, typeof(Vector3))) return FormatVector3; if (ReferenceEquals(type, typeof(Quaternion))) return FormatQuaternion; return new FourCC(); } /// /// Type identifier for the memory layout used by the state. /// /// /// Used for safety checks to make sure that when the system copies state memory, it /// copies between compatible layouts. If set to a primitive state format, also used to /// determine the size of the state block. /// public FourCC format { get; set; } // Offset into state buffer. After a device is added to the system, this is relative // to the global buffers; otherwise it is relative to the device root. // During setup, this can be InvalidOffset to indicate a control that should be placed // at an offset automatically; otherwise it denotes a fixed offset relative to the // parent control. public uint byteOffset { get; set; } // Bit offset from the given byte offset. Also zero-based (i.e. first bit is at bit // offset #0). public uint bitOffset { get; set; } // Size of the state in bits. If this % 8 is not 0, the control is considered a // bitfield control. // During setup, if this field is 0 it means the size of the control should be automatically // computed from either its children (if it has any) or its set format. If it has neither, // setup will throw. public uint sizeInBits { get; set; } internal uint alignedSizeInBytes => (sizeInBits + 7) >> 3; public int ReadInt(void* statePtr) { Debug.Assert(sizeInBits != 0); var valuePtr = (byte*)statePtr + (int)byteOffset; int value; if (format == FormatInt || format == FormatUInt) { Debug.Assert(sizeInBits == 32, "INT and UINT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "INT and UINT state must be byte-aligned"); value = *(int*)valuePtr; } else if (format == FormatBit) { if (sizeInBits == 1) value = MemoryHelpers.ReadSingleBit(valuePtr, bitOffset) ? 1 : 0; else value = MemoryHelpers.ReadIntFromMultipleBits(valuePtr, bitOffset, sizeInBits); } else if (format == FormatSBit) { if (sizeInBits == 1) { value = MemoryHelpers.ReadSingleBit(valuePtr, bitOffset) ? 1 : -1; } else { var halfMax = (1 << (int)sizeInBits) / 2; var unsignedValue = MemoryHelpers.ReadIntFromMultipleBits(valuePtr, bitOffset, sizeInBits); value = unsignedValue - halfMax; } } else if (format == FormatByte) { Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned"); value = *valuePtr; } else if (format == FormatSByte) { Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned"); value = *(sbyte*)valuePtr; } else if (format == FormatShort) { Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned"); value = *(short*)valuePtr; } else if (format == FormatUShort) { Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned"); value = *(ushort*)valuePtr; } else { throw new InvalidOperationException($"State format '{format}' is not supported as integer format"); } return value; } public void WriteInt(void* statePtr, int value) { Debug.Assert(sizeInBits != 0); var valuePtr = (byte*)statePtr + (int)byteOffset; if (format == FormatInt || format == FormatUInt) { Debug.Assert(sizeInBits == 32, "INT and UINT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "INT and UINT state must be byte-aligned"); *(int*)valuePtr = value; } else if (format == FormatBit) { if (sizeInBits == 1) MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value != 0); else MemoryHelpers.WriteIntFromMultipleBits(valuePtr, bitOffset, sizeInBits, value); } else if (format == FormatSBit) { if (sizeInBits == 1) { MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value > 0); } else { var halfMax = (1 << (int)sizeInBits) / 2; MemoryHelpers.WriteIntFromMultipleBits(valuePtr, bitOffset, sizeInBits, value + halfMax); } } else if (format == FormatByte) { Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned"); *valuePtr = (byte)value; } else if (format == FormatSByte) { Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned"); *(sbyte*)valuePtr = (sbyte)value; } else if (format == FormatShort) { Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned"); *(short*)valuePtr = (short)value; } else if (format == FormatUShort) { Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned"); *(ushort*)valuePtr = (ushort)value; } else { throw new Exception($"State format '{format}' is not supported as integer format"); } } public float ReadFloat(void* statePtr) { Debug.Assert(sizeInBits != 0); var valuePtr = (byte*)statePtr + (int)byteOffset; float value; if (format == FormatFloat) { Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned"); value = *(float*)valuePtr; } else if (format == FormatBit || format == FormatSBit) { if (sizeInBits == 1) { value = MemoryHelpers.ReadSingleBit(valuePtr, bitOffset) ? 1.0f : (format == FormatSBit ? -1.0f : 0.0f); } else if (sizeInBits <= 31) { var maxValue = (float)(1 << (int)sizeInBits); var rawValue = (float)(MemoryHelpers.ReadIntFromMultipleBits(valuePtr, bitOffset, sizeInBits)); if (format == FormatSBit) { var unclampedValue = (rawValue / maxValue) * 2.0f - 1.0f; value = Mathf.Clamp(unclampedValue, -1.0f, 1.0f); } else { value = Mathf.Clamp(rawValue / maxValue, 0.0f, 1.0f); } } else { throw new NotImplementedException("Cannot yet convert multi-bit fields greater than 31 bits to floats"); } } // If a control with an integer-based representation does not use the full range // of its integer size (e.g. only goes from [0..128]), processors or the parameters // above have to be used to re-process the resulting float values. else if (format == FormatShort) { Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned"); ////REVIEW: What's better here? This code reaches a clean -1 but doesn't reach a clean +1 as the range is [-32768..32767]. //// Should we cut off at -32767? Or just live with the fact that 0.999 is as high as it gets? value = *(short*)valuePtr / 32768.0f; } else if (format == FormatUShort) { Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned"); value = *(ushort*)valuePtr / 65535.0f; } else if (format == FormatByte) { Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned"); value = *valuePtr / 255.0f; } else if (format == FormatSByte) { Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned"); ////REVIEW: Same problem here as with 'short' value = *(sbyte*)valuePtr / 128.0f; } else if (format == FormatInt) { Debug.Assert(sizeInBits == 32, "INT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "INT state must be byte-aligned"); value = *(int*)valuePtr / 2147483647.0f; } else if (format == FormatUInt) { Debug.Assert(sizeInBits == 32, "UINT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "UINT state must be byte-aligned"); value = *(uint*)valuePtr / 4294967295.0f; } else if (format == FormatDouble) { Debug.Assert(sizeInBits == 64, "DBL state must have sizeInBits=64"); Debug.Assert(bitOffset == 0, "DBL state must be byte-aligned"); value = (float)*(double*)valuePtr; } else { throw new InvalidOperationException($"State format '{format}' is not supported as floating-point format"); } return value; } public void WriteFloat(void* statePtr, float value) { var valuePtr = (byte*)statePtr + (int)byteOffset; if (format == FormatFloat) { Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned"); *(float*)valuePtr = value; } else if (format == FormatBit) { if (sizeInBits == 1) { MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value >= 0.5f); } else { var maxValue = (1 << (int)sizeInBits) - 1; var intValue = (int)(value * maxValue); MemoryHelpers.WriteIntFromMultipleBits(valuePtr, bitOffset, sizeInBits, intValue); } } else if (format == FormatShort) { Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned"); *(short*)valuePtr = (short)(value * 32768.0f); } else if (format == FormatUShort) { Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned"); *(ushort*)valuePtr = (ushort)(value * 65535.0f); } else if (format == FormatByte) { Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned"); *valuePtr = (byte)(value * 255.0f); } else if (format == FormatSByte) { Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned"); *(sbyte*)valuePtr = (sbyte)(value * 128.0f); } else if (format == FormatDouble) { Debug.Assert(sizeInBits == 64, "DBL state must have sizeInBits=64"); Debug.Assert(bitOffset == 0, "DBL state must be byte-aligned"); *(double*)valuePtr = value; } else { throw new Exception($"State format '{format}' is not supported as floating-point format"); } } internal PrimitiveValue FloatToPrimitiveValue(float value) { if (format == FormatFloat) { Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned"); return value; } else if (format == FormatBit) { if (sizeInBits == 1) { return value >= 0.5f; } else { var maxValue = (1 << (int)sizeInBits) - 1; return (int)(value * maxValue); } } else if (format == FormatInt) { Debug.Assert(sizeInBits == 32, "INT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "INT state must be byte-aligned"); return (int)(value * 2147483647.0f); } else if (format == FormatUInt) { Debug.Assert(sizeInBits == 32, "UINT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "UINT state must be byte-aligned"); return (uint)(value * 4294967295.0f); } else if (format == FormatShort) { Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned"); return (short)(value * 32768.0f); } else if (format == FormatUShort) { Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned"); return (ushort)(value * 65535.0f); } else if (format == FormatByte) { Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned"); return (byte)(value * 255.0f); } else if (format == FormatSByte) { Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned"); return (sbyte)(value * 128.0f); } else if (format == FormatDouble) { Debug.Assert(sizeInBits == 64, "DBL state must have sizeInBits=64"); Debug.Assert(bitOffset == 0, "DBL state must be byte-aligned"); return value; } else { throw new Exception($"State format '{format}' is not supported as floating-point format"); } } ////REVIEW: This is some bad code duplication here between Read/WriteFloat&Double but given that there's no //// way to use a type argument here, not sure how to get rid of it. public double ReadDouble(void* statePtr) { Debug.Assert(sizeInBits != 0); var valuePtr = (byte*)statePtr + (int)byteOffset; double value; if (format == FormatFloat) { Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned"); value = *(float*)valuePtr; } else if (format == FormatBit || format == FormatSBit) { if (sizeInBits == 1) { value = MemoryHelpers.ReadSingleBit(valuePtr, bitOffset) ? 1.0f : (format == FormatSBit ? -1.0f : 0.0f); } else if (sizeInBits != 31) { var maxValue = (float)(1 << (int)sizeInBits); var rawValue = (float)(MemoryHelpers.ReadIntFromMultipleBits(valuePtr, bitOffset, sizeInBits)); if (format == FormatSBit) { var unclampedValue = (((rawValue / maxValue) * 2.0f) - 1.0f); value = Mathf.Clamp(unclampedValue, -1.0f, 1.0f); } else { value = Mathf.Clamp(rawValue / maxValue, 0.0f, 1.0f); } } else { throw new NotImplementedException("Cannot yet convert multi-bit fields greater than 31 bits to floats"); } } // If a control with an integer-based representation does not use the full range // of its integer size (e.g. only goes from [0..128]), processors or the parameters // above have to be used to re-process the resulting float values. else if (format == FormatShort) { Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned"); ////REVIEW: What's better here? This code reaches a clean -1 but doesn't reach a clean +1 as the range is [-32768..32767]. //// Should we cut off at -32767? Or just live with the fact that 0.999 is as high as it gets? value = *(short*)valuePtr / 32768.0f; } else if (format == FormatUShort) { Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned"); value = *(ushort*)valuePtr / 65535.0f; } else if (format == FormatByte) { Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned"); value = *valuePtr / 255.0f; } else if (format == FormatSByte) { Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned"); ////REVIEW: Same problem here as with 'short' value = *(sbyte*)valuePtr / 128.0f; } else if (format == FormatInt) { Debug.Assert(sizeInBits == 32, "INT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "INT state must be byte-aligned"); value = *(Int32*)valuePtr / 2147483647.0f; } else if (format == FormatUInt) { Debug.Assert(sizeInBits == 32, "UINT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "UINT state must be byte-aligned"); value = *(UInt32*)valuePtr / 4294967295.0f; } else if (format == FormatDouble) { Debug.Assert(sizeInBits == 64, "DBL state must have sizeInBits=64"); Debug.Assert(bitOffset == 0, "DBL state must be byte-aligned"); value = *(double*)valuePtr; } else { throw new Exception($"State format '{format}' is not supported as floating-point format"); } return value; } public void WriteDouble(void* statePtr, double value) { var valuePtr = (byte*)statePtr + (int)byteOffset; if (format == FormatFloat) { Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned"); *(float*)valuePtr = (float)value; } else if (format == FormatBit) { if (sizeInBits == 1) { MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value >= 0.5f); } else { var maxValue = (1 << (int)sizeInBits) - 1; var intValue = (int)(value * maxValue); MemoryHelpers.WriteIntFromMultipleBits(valuePtr, bitOffset, sizeInBits, intValue); } } else if (format == FormatShort) { Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned"); *(short*)valuePtr = (short)(value * 32768.0f); } else if (format == FormatUShort) { Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned"); *(ushort*)valuePtr = (ushort)(value * 65535.0f); } else if (format == FormatByte) { Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned"); *valuePtr = (byte)(value * 255.0f); } else if (format == FormatSByte) { Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned"); *(sbyte*)valuePtr = (sbyte)(value * 128.0f); } else if (format == FormatDouble) { Debug.Assert(sizeInBits == 64, "DBL state must have sizeInBits=64"); Debug.Assert(bitOffset == 0, "DBL state must be byte-aligned"); *(double*)valuePtr = value; } else { throw new InvalidOperationException($"State format '{format}' is not supported as floating-point format"); } } public void Write(void* statePtr, PrimitiveValue value) { var valuePtr = (byte*)statePtr + (int)byteOffset; if (format == FormatBit || format == FormatSBit) { if (sizeInBits > 32) throw new NotImplementedException( "Cannot yet write primitive values into bitfields wider than 32 bits"); if (sizeInBits == 1) MemoryHelpers.WriteSingleBit(valuePtr, bitOffset, value.ToBoolean()); else MemoryHelpers.WriteIntFromMultipleBits(valuePtr, bitOffset, sizeInBits, value.ToInt32()); } else if (format == FormatFloat) { Debug.Assert(sizeInBits == 32, "FLT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "FLT state must be byte-aligned"); *(float*)valuePtr = value.ToSingle(); } else if (format == FormatByte) { Debug.Assert(sizeInBits == 8, "BYTE state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "BYTE state must be byte-aligned"); *valuePtr = value.ToByte(); } else if (format == FormatShort) { Debug.Assert(sizeInBits == 16, "SHRT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "SHRT state must be byte-aligned"); *(short*)valuePtr = value.ToInt16(); } else if (format == FormatInt) { Debug.Assert(sizeInBits == 32, "INT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "INT state must be byte-aligned"); *(int*)valuePtr = value.ToInt32(); } else if (format == FormatSByte) { Debug.Assert(sizeInBits == 8, "SBYT state must have sizeInBits=8"); Debug.Assert(bitOffset == 0, "SBYT state must be byte-aligned"); *(sbyte*)valuePtr = value.ToSByte(); } else if (format == FormatUShort) { Debug.Assert(sizeInBits == 16, "USHT state must have sizeInBits=16"); Debug.Assert(bitOffset == 0, "USHT state must be byte-aligned"); *(ushort*)valuePtr = value.ToUInt16(); } else if (format == FormatUInt) { Debug.Assert(sizeInBits == 32, "UINT state must have sizeInBits=32"); Debug.Assert(bitOffset == 0, "UINT state must be byte-aligned"); *(uint*)valuePtr = value.ToUInt32(); } else { throw new NotImplementedException( $"Writing primitive value of type '{value.type}' into state block with format '{format}'"); } } public void CopyToFrom(void* toStatePtr, void* fromStatePtr) { if (bitOffset != 0 || sizeInBits % 8 != 0) throw new NotImplementedException("Copying bitfields"); var from = (byte*)fromStatePtr + byteOffset; var to = (byte*)toStatePtr + byteOffset; UnsafeUtility.MemCpy(to, from, alignedSizeInBytes); } } }