using System; using System.Globalization; using System.Runtime.InteropServices; using Unity.Collections.LowLevel.Unsafe; ////REVIEW: add Vector2 and Vector3 as primitive value types? namespace UnityEngine.InputSystem.Utilities { /// /// A union holding a primitive value. /// /// /// This structure is used for storing things such as default states for controls /// (see ). It can /// store one value of any primitive, non-reference C# type (bool, char, int, float, etc). /// [StructLayout(LayoutKind.Explicit)] public struct PrimitiveValue : IEquatable, IConvertible { [FieldOffset(0)] private TypeCode m_Type; [FieldOffset(4)] private bool m_BoolValue; [FieldOffset(4)] private char m_CharValue; [FieldOffset(4)] private byte m_ByteValue; [FieldOffset(4)] private sbyte m_SByteValue; [FieldOffset(4)] private short m_ShortValue; [FieldOffset(4)] private ushort m_UShortValue; [FieldOffset(4)] private int m_IntValue; [FieldOffset(4)] private uint m_UIntValue; [FieldOffset(4)] private long m_LongValue; [FieldOffset(4)] private ulong m_ULongValue; [FieldOffset(4)] private float m_FloatValue; [FieldOffset(4)] private double m_DoubleValue; /// /// Type of value stored in the struct. /// if the struct does not hold a value (i.e. has been default-initialized). /// /// Type of value stored in the struct. public TypeCode type => m_Type; /// /// If true, the struct does not contain a primitive value (i.e. has /// ). /// /// Whether the struct is holding a value or not. public bool isEmpty => type == TypeCode.Empty; /// /// Create a PrimitiveValue holding a bool. /// /// A boolean value. public PrimitiveValue(bool value) : this() { m_Type = TypeCode.Boolean; m_BoolValue = value; } /// /// Create a PrimitiveValue holding a character. /// /// A character. public PrimitiveValue(char value) : this() { m_Type = TypeCode.Char; m_CharValue = value; } /// /// Create a PrimitiveValue holding a byte. /// /// A byte value. public PrimitiveValue(byte value) : this() { m_Type = TypeCode.Byte; m_ByteValue = value; } /// /// Create a PrimitiveValue holding a signed byte. /// /// A signed byte value. public PrimitiveValue(sbyte value) : this() { m_Type = TypeCode.SByte; m_SByteValue = value; } /// /// Create a PrimitiveValue holding a short. /// /// A short value. public PrimitiveValue(short value) : this() { m_Type = TypeCode.Int16; m_ShortValue = value; } /// /// Create a PrimitiveValue holding an unsigned short. /// /// An unsigned short value. public PrimitiveValue(ushort value) : this() { m_Type = TypeCode.UInt16; m_UShortValue = value; } /// /// Create a PrimitiveValue holding an int. /// /// An int value. public PrimitiveValue(int value) : this() { m_Type = TypeCode.Int32; m_IntValue = value; } /// /// Create a PrimitiveValue holding an unsigned int. /// /// An unsigned int value. public PrimitiveValue(uint value) : this() { m_Type = TypeCode.UInt32; m_UIntValue = value; } /// /// Create a PrimitiveValue holding a long. /// /// A long value. public PrimitiveValue(long value) : this() { m_Type = TypeCode.Int64; m_LongValue = value; } /// /// Create a PrimitiveValue holding a ulong. /// /// An unsigned long value. public PrimitiveValue(ulong value) : this() { m_Type = TypeCode.UInt64; m_ULongValue = value; } /// /// Create a PrimitiveValue holding a float. /// /// A float value. public PrimitiveValue(float value) : this() { m_Type = TypeCode.Single; m_FloatValue = value; } /// /// Create a PrimitiveValue holding a double. /// /// A double value. public PrimitiveValue(double value) : this() { m_Type = TypeCode.Double; m_DoubleValue = value; } /// /// Convert to another type of value. /// /// Type of value to convert to. /// The converted value. /// There is no conversion from the /// PrimitiveValue's current to /// . /// /// This method simply calls the other conversion methods (, /// , etc) based on the current type of value. ArgumentException /// is thrown if there is no conversion from the current to the requested type. /// /// Every value can be converted to TypeCode.Empty. /// /// /// /// /// /// /// /// /// /// /// /// /// public PrimitiveValue ConvertTo(TypeCode type) { switch (type) { case TypeCode.Boolean: return ToBoolean(); case TypeCode.Char: return ToChar(); case TypeCode.Byte: return ToByte(); case TypeCode.SByte: return ToSByte(); case TypeCode.Int16: return ToInt16(); case TypeCode.Int32: return ToInt32(); case TypeCode.Int64: return ToInt64(); case TypeCode.UInt16: return ToInt16(); case TypeCode.UInt32: return ToInt32(); case TypeCode.UInt64: return ToInt64(); case TypeCode.Single: return ToSingle(); case TypeCode.Double: return ToDouble(); case TypeCode.Empty: return new PrimitiveValue(); } throw new ArgumentException($"Don't know how to convert PrimitiveValue to '{type}'", nameof(type)); } /// /// Compare this value to . /// /// Another value. /// True if the two values are equal. /// /// Equality is based on type and contents. The types of both values /// must be identical and the memory contents of each value must be /// bit-wise identical (i.e. things such as floating-point epsilons /// are not taken into account). /// public unsafe bool Equals(PrimitiveValue other) { if (m_Type != other.m_Type) return false; var thisValuePtr = UnsafeUtility.AddressOf(ref m_DoubleValue); var otherValuePtr = UnsafeUtility.AddressOf(ref other.m_DoubleValue); return UnsafeUtility.MemCmp(thisValuePtr, otherValuePtr, sizeof(double)) == 0; } /// /// Compare this value to the value of . /// /// Either another PrimitiveValue or a boxed primitive /// value such as a byte, bool, etc. /// True if the two values are equal. /// /// If is a boxed primitive value, it is automatically /// converted to a PrimitiveValue. /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (obj is PrimitiveValue value) return Equals(value); if (obj is bool || obj is char || obj is byte || obj is sbyte || obj is short || obj is ushort || obj is int || obj is uint || obj is long || obj is ulong || obj is float || obj is double) return Equals(FromObject(obj)); return false; } /// /// Compare two PrimitiveValues for equality. /// /// First value. /// Second value. /// True if the two values are equal. /// public static bool operator==(PrimitiveValue left, PrimitiveValue right) { return left.Equals(right); } /// /// Compare two PrimitiveValues for inequality. /// /// First value. /// Second value. /// True if the two values are not equal. /// public static bool operator!=(PrimitiveValue left, PrimitiveValue right) { return !left.Equals(right); } /// /// Compute a hash code for the value. /// /// A hash code. public override unsafe int GetHashCode() { unchecked { fixed(double* valuePtr = &m_DoubleValue) { var hashCode = m_Type.GetHashCode(); hashCode = (hashCode * 397) ^ valuePtr->GetHashCode(); return hashCode; } } } /// /// Return a string representation of the value. /// /// A string representation of the value. /// /// String versions of PrimitiveValues are always culture invariant. This means that /// floating-point values, for example, will not the decimal separator of /// the current culture. /// /// public override string ToString() { switch (type) { case TypeCode.Boolean: // Default ToString() uses "False" and "True". We want lowercase to match C# literals. return m_BoolValue ? "true" : "false"; case TypeCode.Char: return $"'{m_CharValue.ToString()}'"; case TypeCode.Byte: return m_ByteValue.ToString(CultureInfo.InvariantCulture.NumberFormat); case TypeCode.SByte: return m_SByteValue.ToString(CultureInfo.InvariantCulture.NumberFormat); case TypeCode.Int16: return m_ShortValue.ToString(CultureInfo.InvariantCulture.NumberFormat); case TypeCode.UInt16: return m_UShortValue.ToString(CultureInfo.InvariantCulture.NumberFormat); case TypeCode.Int32: return m_IntValue.ToString(CultureInfo.InvariantCulture.NumberFormat); case TypeCode.UInt32: return m_UIntValue.ToString(CultureInfo.InvariantCulture.NumberFormat); case TypeCode.Int64: return m_LongValue.ToString(CultureInfo.InvariantCulture.NumberFormat); case TypeCode.UInt64: return m_ULongValue.ToString(CultureInfo.InvariantCulture.NumberFormat); case TypeCode.Single: return m_FloatValue.ToString(CultureInfo.InvariantCulture.NumberFormat); case TypeCode.Double: return m_DoubleValue.ToString(CultureInfo.InvariantCulture.NumberFormat); default: return string.Empty; } } /// /// Parse the given string into a PrimitiveValue. /// /// A string containing a value. /// The PrimitiveValue parsed from the string. /// /// Integers are parsed as longs. Floating-point numbers are parsed as doubles. /// Hexadecimal notation is supported for integers. /// /// public static PrimitiveValue FromString(string value) { if (string.IsNullOrEmpty(value)) return new PrimitiveValue(); // Bool. if (value.Equals("true", StringComparison.InvariantCultureIgnoreCase)) return new PrimitiveValue(true); if (value.Equals("false", StringComparison.InvariantCultureIgnoreCase)) return new PrimitiveValue(false); // Double. if (value.Contains('.') || value.Contains("e") || value.Contains("E") || value.Contains("infinity", StringComparison.InvariantCultureIgnoreCase)) { if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleResult)) return new PrimitiveValue(doubleResult); } // Long. if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longResult)) { return new PrimitiveValue(longResult); } // Try hex format. For whatever reason, HexNumber does not allow a 0x prefix so we manually // get rid of it. if (value.IndexOf("0x", StringComparison.InvariantCultureIgnoreCase) != -1) { var hexDigits = value.TrimStart(); if (hexDigits.StartsWith("0x")) hexDigits = hexDigits.Substring(2); if (long.TryParse(hexDigits, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var hexResult)) return new PrimitiveValue(hexResult); } ////TODO: allow trailing width specifier throw new NotImplementedException(); } /// /// Equivalent to . /// /// Type code for value stored in struct. public TypeCode GetTypeCode() { return type; } /// /// Convert the value to a boolean. /// /// Ignored. /// Converted boolean value. public bool ToBoolean(IFormatProvider provider = null) { switch (type) { case TypeCode.Boolean: return m_BoolValue; case TypeCode.Char: return m_CharValue != default; case TypeCode.Byte: return m_ByteValue != default; case TypeCode.SByte: return m_SByteValue != default; case TypeCode.Int16: return m_ShortValue != default; case TypeCode.UInt16: return m_UShortValue != default; case TypeCode.Int32: return m_IntValue != default; case TypeCode.UInt32: return m_UIntValue != default; case TypeCode.Int64: return m_LongValue != default; case TypeCode.UInt64: return m_ULongValue != default; case TypeCode.Single: return !Mathf.Approximately(m_FloatValue, default); case TypeCode.Double: return NumberHelpers.Approximately(m_DoubleValue, default); default: return default; } } /// /// Convert the value to a byte. /// /// Ignored. /// Converted byte value. public byte ToByte(IFormatProvider provider = null) { return (byte)ToInt64(provider); } /// /// Convert the value to a char. /// /// Ignored. /// Converted char value. public char ToChar(IFormatProvider provider = null) { switch (type) { case TypeCode.Char: return m_CharValue; case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: return (char)ToInt64(provider); default: return default; } } /// /// Not supported. Throws NotSupportedException. /// /// Ignored. /// Does not return. /// Always thrown. public DateTime ToDateTime(IFormatProvider provider = null) { throw new NotSupportedException("Converting PrimitiveValue to DateTime"); } /// /// Convert the value to a decimal. /// /// Ignored. /// Value converted to decimal format. public decimal ToDecimal(IFormatProvider provider = null) { return new decimal(ToDouble(provider)); } /// /// Convert the value to a double. /// /// Ignored. /// Converted double value. public double ToDouble(IFormatProvider provider = null) { switch (type) { case TypeCode.Boolean: if (m_BoolValue) return 1; return 0; case TypeCode.Char: return m_CharValue; case TypeCode.Byte: return m_ByteValue; case TypeCode.SByte: return m_SByteValue; case TypeCode.Int16: return m_ShortValue; case TypeCode.UInt16: return m_UShortValue; case TypeCode.Int32: return m_IntValue; case TypeCode.UInt32: return m_UIntValue; case TypeCode.Int64: return m_LongValue; case TypeCode.UInt64: return m_ULongValue; case TypeCode.Single: return m_FloatValue; case TypeCode.Double: return m_DoubleValue; default: return default; } } /// /// Convert the value to a short. /// /// Ignored. /// Converted short value. public short ToInt16(IFormatProvider provider = null) { return (short)ToInt64(provider); } /// /// Convert the value to an int /// /// Ignored. /// Converted int value. public int ToInt32(IFormatProvider provider = null) { return (int)ToInt64(provider); } /// /// Convert the value to a long /// /// Ignored. /// Converted long value. public long ToInt64(IFormatProvider provider = null) { switch (type) { case TypeCode.Boolean: if (m_BoolValue) return 1; return 0; case TypeCode.Char: return m_CharValue; case TypeCode.Byte: return m_ByteValue; case TypeCode.SByte: return m_SByteValue; case TypeCode.Int16: return m_ShortValue; case TypeCode.UInt16: return m_UShortValue; case TypeCode.Int32: return m_IntValue; case TypeCode.UInt32: return m_UIntValue; case TypeCode.Int64: return m_LongValue; case TypeCode.UInt64: return (long)m_ULongValue; case TypeCode.Single: return (long)m_FloatValue; case TypeCode.Double: return (long)m_DoubleValue; default: return default; } } /// /// Convert the value to a sbyte. /// /// Ignored. /// Converted sbyte value. public sbyte ToSByte(IFormatProvider provider = null) { return (sbyte)ToInt64(provider); } /// /// Convert the value to a float. /// /// Ignored. /// Converted float value. public float ToSingle(IFormatProvider provider = null) { return (float)ToDouble(provider); } /// /// Convert the value to a string. /// /// Ignored. /// Converted string value. /// /// Same as calling . /// public string ToString(IFormatProvider provider) { return ToString(); } /// /// Not supported. /// /// Ignored. /// Ignored. /// Does not return. /// Always thrown. public object ToType(Type conversionType, IFormatProvider provider) { throw new NotSupportedException(); } /// /// Convert the value to a ushort. /// /// Ignored. /// Converted ushort value. public ushort ToUInt16(IFormatProvider provider = null) { return (ushort)ToUInt64(); } /// /// Convert the value to a uint. /// /// Ignored. /// Converted uint value. public uint ToUInt32(IFormatProvider provider = null) { return (uint)ToUInt64(); } /// /// Convert the value to a ulong. /// /// Ignored. /// Converted ulong value. public ulong ToUInt64(IFormatProvider provider = null) { switch (type) { case TypeCode.Boolean: if (m_BoolValue) return 1; return 0; case TypeCode.Char: return m_CharValue; case TypeCode.Byte: return m_ByteValue; case TypeCode.SByte: return (ulong)m_SByteValue; case TypeCode.Int16: return (ulong)m_ShortValue; case TypeCode.UInt16: return m_UShortValue; case TypeCode.Int32: return (ulong)m_IntValue; case TypeCode.UInt32: return m_UIntValue; case TypeCode.Int64: return (ulong)m_LongValue; case TypeCode.UInt64: return m_ULongValue; case TypeCode.Single: return (ulong)m_FloatValue; case TypeCode.Double: return (ulong)m_DoubleValue; default: return default; } } /// /// Return a boxed version of the value. /// /// A boxed GC heap object. /// /// This method always allocates GC heap memory. /// public object ToObject() { switch (m_Type) { case TypeCode.Boolean: return m_BoolValue; case TypeCode.Char: return m_CharValue; case TypeCode.Byte: return m_ByteValue; case TypeCode.SByte: return m_SByteValue; case TypeCode.Int16: return m_ShortValue; case TypeCode.UInt16: return m_UShortValue; case TypeCode.Int32: return m_IntValue; case TypeCode.UInt32: return m_UIntValue; case TypeCode.Int64: return m_LongValue; case TypeCode.UInt64: return m_ULongValue; case TypeCode.Single: return m_FloatValue; case TypeCode.Double: return m_DoubleValue; default: return null; } } /// /// Create a PrimitiveValue from the given "blittable"/struct value. /// /// A value. /// Type of value to convert. Must be either an enum /// or one of the C# primitive value types (bool, int, float, etc.). /// The PrimitiveValue converted from . If it is an /// enum type, the PrimitiveValue will hold a value of the enum's underlying /// type (i.e. Type.GetEnumUnderlyingType). /// No conversion exists from the given /// type. public static PrimitiveValue From(TValue value) where TValue : struct { var type = typeof(TValue); if (type.IsEnum) type = type.GetEnumUnderlyingType(); var typeCode = Type.GetTypeCode(type); switch (typeCode) { case TypeCode.Boolean: return new PrimitiveValue(Convert.ToBoolean(value)); case TypeCode.Char: return new PrimitiveValue(Convert.ToChar(value)); case TypeCode.Byte: return new PrimitiveValue(Convert.ToByte(value)); case TypeCode.SByte: return new PrimitiveValue(Convert.ToSByte(value)); case TypeCode.Int16: return new PrimitiveValue(Convert.ToInt16(value)); case TypeCode.Int32: return new PrimitiveValue(Convert.ToInt32(value)); case TypeCode.Int64: return new PrimitiveValue(Convert.ToInt64(value)); case TypeCode.UInt16: return new PrimitiveValue(Convert.ToUInt16(value)); case TypeCode.UInt32: return new PrimitiveValue(Convert.ToUInt32(value)); case TypeCode.UInt64: return new PrimitiveValue(Convert.ToUInt64(value)); case TypeCode.Single: return new PrimitiveValue(Convert.ToSingle(value)); case TypeCode.Double: return new PrimitiveValue(Convert.ToDouble(value)); } throw new ArgumentException( $"Cannot convert value '{value}' of type '{typeof(TValue).Name}' to PrimitiveValue", nameof(value)); } /// /// Create a PrimitiveValue from a boxed value. /// /// A value. If null, the result will be default(PrimitiveValue). /// If it is a string, is used. Otherwise must be either an enum /// or one of the C# primitive value types (bool, int, float, etc.). If it is an /// enum type, the PrimitiveValue will hold a value of the enum's underlying /// type (i.e. Type.GetEnumUnderlyingType). /// No conversion exists from the type of . public static PrimitiveValue FromObject(object value) { if (value == null) return new PrimitiveValue(); if (value is string stringValue) return FromString(stringValue); if (value is bool b) return new PrimitiveValue(b); if (value is char ch) return new PrimitiveValue(ch); if (value is byte bt) return new PrimitiveValue(bt); if (value is sbyte sbt) return new PrimitiveValue(sbt); if (value is short s) return new PrimitiveValue(s); if (value is ushort us) return new PrimitiveValue(us); if (value is int i) return new PrimitiveValue(i); if (value is uint ui) return new PrimitiveValue(ui); if (value is long l) return new PrimitiveValue(l); if (value is ulong ul) return new PrimitiveValue(ul); if (value is float f) return new PrimitiveValue(f); if (value is double d) return new PrimitiveValue(d); // Enum. if (value is Enum) { var underlyingType = value.GetType().GetEnumUnderlyingType(); var underlyingTypeCode = Type.GetTypeCode(underlyingType); switch (underlyingTypeCode) { case TypeCode.Byte: return new PrimitiveValue((byte)value); case TypeCode.SByte: return new PrimitiveValue((sbyte)value); case TypeCode.Int16: return new PrimitiveValue((short)value); case TypeCode.Int32: return new PrimitiveValue((int)value); case TypeCode.Int64: return new PrimitiveValue((long)value); case TypeCode.UInt16: return new PrimitiveValue((ushort)value); case TypeCode.UInt32: return new PrimitiveValue((uint)value); case TypeCode.UInt64: return new PrimitiveValue((ulong)value); } } throw new ArgumentException($"Cannot convert '{value}' to primitive value", nameof(value)); } /// /// Create a PrimitiveValue holding a bool. /// /// A boolean value. public static implicit operator PrimitiveValue(bool value) { return new PrimitiveValue(value); } /// /// Create a PrimitiveValue holding a character. /// /// A character. public static implicit operator PrimitiveValue(char value) { return new PrimitiveValue(value); } /// /// Create a PrimitiveValue holding a byte. /// /// A byte value. public static implicit operator PrimitiveValue(byte value) { return new PrimitiveValue(value); } /// /// Create a PrimitiveValue holding a signed byte. /// /// A signed byte value. public static implicit operator PrimitiveValue(sbyte value) { return new PrimitiveValue(value); } /// /// Create a PrimitiveValue holding a short. /// /// A short value. public static implicit operator PrimitiveValue(short value) { return new PrimitiveValue(value); } /// /// Create a PrimitiveValue holding an unsigned short. /// /// An unsigned short value. public static implicit operator PrimitiveValue(ushort value) { return new PrimitiveValue(value); } /// /// Create a PrimitiveValue holding an int. /// /// An int value. public static implicit operator PrimitiveValue(int value) { return new PrimitiveValue(value); } /// /// Create a PrimitiveValue holding an unsigned int. /// /// An unsigned int value. public static implicit operator PrimitiveValue(uint value) { return new PrimitiveValue(value); } /// /// Create a PrimitiveValue holding a long. /// /// A long value. public static implicit operator PrimitiveValue(long value) { return new PrimitiveValue(value); } /// /// Create a PrimitiveValue holding a ulong. /// /// An unsigned long value. public static implicit operator PrimitiveValue(ulong value) { return new PrimitiveValue(value); } /// /// Create a PrimitiveValue holding a float. /// /// A float value. public static implicit operator PrimitiveValue(float value) { return new PrimitiveValue(value); } /// /// Create a PrimitiveValue holding a double. /// /// A double value. public static implicit operator PrimitiveValue(double value) { return new PrimitiveValue(value); } // The following methods exist only to make the annoying Microsoft code analyzer happy. public static PrimitiveValue FromBoolean(bool value) { return new PrimitiveValue(value); } public static PrimitiveValue FromChar(char value) { return new PrimitiveValue(value); } public static PrimitiveValue FromByte(byte value) { return new PrimitiveValue(value); } public static PrimitiveValue FromSByte(sbyte value) { return new PrimitiveValue(value); } public static PrimitiveValue FromInt16(short value) { return new PrimitiveValue(value); } public static PrimitiveValue FromUInt16(ushort value) { return new PrimitiveValue(value); } public static PrimitiveValue FromInt32(int value) { return new PrimitiveValue(value); } public static PrimitiveValue FromUInt32(uint value) { return new PrimitiveValue(value); } public static PrimitiveValue FromInt64(long value) { return new PrimitiveValue(value); } public static PrimitiveValue FromUInt64(ulong value) { return new PrimitiveValue(value); } public static PrimitiveValue FromSingle(float value) { return new PrimitiveValue(value); } public static PrimitiveValue FromDouble(double value) { return new PrimitiveValue(value); } } }