// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using UltimateXR.Core.Math; using UltimateXR.Core.Serialization; using UltimateXR.Core.Unique; using UnityEngine; namespace UltimateXR.Extensions.System.IO { /// /// extensions. /// public static class BinaryWriterExt { #region Public Methods /// Writes a 32-bit integer in a compressed format, so that only the required number of bytes are used. /// Writer /// Int32 value public static void WriteCompressedInt32(this BinaryWriter writer, int value) { WriteCompressedUInt32(writer, (uint)value); } /// Writes a 32-bit unsigned integer in a compressed format, so that only the required number of bytes are used. /// Writer /// UInt32 value public static void WriteCompressedUInt32(this BinaryWriter writer, uint value) { uint num; for (num = value; num >= 128U; num >>= 7) { writer.Write((byte)(num | 128U)); } writer.Write((byte)num); } /// Writes a 64-bit integer in a compressed format, so that only the required number of bytes are used. /// Writer /// Int64 value public static void WriteCompressedInt64(this BinaryWriter writer, long value) { WriteCompressedUInt64(writer, (ulong)value); } /// Writes a 64-bit unsigned integer in a compressed format, so that only the required number of bytes are used. /// Writer /// UInt64 value public static void WriteCompressedUInt64(this BinaryWriter writer, ulong value) { ulong num; for (num = value; num >= 128UL; num >>= 7) { writer.Write((byte)(num | 128UL)); } writer.Write((byte)num); } /// /// Outputs an enum value. /// /// Writer /// Enum value public static void WriteEnum(this BinaryWriter writer, Enum enumValue) { writer.WriteCompressedInt32((int)(object)enumValue); } /// /// Outputs a string with null support. Strings should be read back using /// . /// /// Writer /// String value or null public static void WriteStringWithNullCheck(this BinaryWriter writer, string value) { // Serialized as: null-check (bool), string writer.Write(value != null); if (value != null) { writer.Write(value); } } /// /// Outputs a type. It will output two strings: the type full name and the assembly name. If the type belongs to the /// UltimateXR assembly, the assembly will be an empty string. /// /// Writer /// The type public static void Write(this BinaryWriter writer, Type type) { if (type == null) { writer.Write(string.Empty); return; } bool isUxrAssembly = type.Assembly == typeof(BinaryWriterExt).Assembly; writer.Write(type.FullName); writer.Write(isUxrAssembly ? string.Empty : type.Assembly.GetName().Name); } /// /// Outputs a Guid as a 16 byte array. /// /// Writer /// The Guid public static void Write(this BinaryWriter writer, Guid guid) { writer.Write(guid.ToByteArray()); } /// /// Outputs a tuple. /// /// Writer /// The tuple public static void Write(this BinaryWriter writer, (T1, T2) tuple) { writer.Write(tuple.Item1); writer.Write(tuple.Item2); } /// /// Outputs an array. /// /// Writer /// Values /// Tye element type /// A type is not supported /// /// A method obtained through reflection that was required for serialization could /// not be found /// public static void Write(this BinaryWriter writer, T[] values) { // Serialized as: null-check (bool), count (int32), elements writer.Write(values != null); if (values != null) { writer.WriteCompressedInt32(values.Length); foreach (T value in values) { writer.Write(value); } } } /// /// Outputs an array where each element can be of a different type. /// /// Writer /// Values /// A type is not supported /// /// A method obtained through reflection that was required for serialization could /// not be found /// public static void Write(this BinaryWriter writer, object[] values) { // Serialized as: null-check (bool), count (int32), elements writer.Write(values != null); if (values != null) { writer.WriteCompressedInt32(values.Length); foreach (object value in values) { writer.WriteAnyVar(value); } } } /// /// Outputs a list. /// /// Writer /// Values /// The element type /// A type is not supported /// /// A method obtained through reflection that was required for serialization could /// not be found /// public static void Write(this BinaryWriter writer, List values) { // Serialized as: null-check (bool), count (int32), elements writer.Write(values != null); if (values != null) { writer.WriteCompressedInt32(values.Count); foreach (T value in values) { writer.Write(value); } } } /// /// Outputs a list where each element can be of a different type. /// /// Writer /// Values /// A type is not supported /// /// A method obtained through reflection that was required for serialization could /// not be found /// public static void Write(this BinaryWriter writer, List values) { // Serialized as: null-check (bool), count (int32), elements writer.Write(values != null); if (values != null) { writer.WriteCompressedInt32(values.Count); foreach (object value in values) { writer.WriteAnyVar(value); } } } /// /// Outputs a dictionary. /// /// Writer /// The dictionary /// The type of the key /// The type of the values /// A type is not supported /// /// A method obtained through reflection that was required for serialization could /// not be found /// public static void Write(this BinaryWriter writer, Dictionary dictionary) { // Serialized as: null-check (bool), count (int32), elements (pairs) writer.Write(dictionary != null); if (dictionary != null) { writer.WriteCompressedInt32(dictionary.Count); foreach (KeyValuePair element in dictionary) { writer.Write(element.Key); writer.Write(element.Value); } } } /// /// Outputs a HashSet. /// /// Writer /// Values /// A type is not supported /// /// A method obtained through reflection that was required for serialization could /// not be found /// public static void Write(this BinaryWriter writer, HashSet hashSet) { // Serialized as: null-check (bool), count (int32), elements writer.Write(hashSet != null); if (hashSet != null) { writer.WriteCompressedInt32(hashSet.Count); foreach (T value in hashSet) { writer.Write(value); } } } /// /// Outputs a hash set where each element can be of a different type. /// /// Writer /// Values /// A type is not supported /// /// A method obtained through reflection that was required for serialization could /// not be found /// public static void Write(this BinaryWriter writer, HashSet values) { // Serialized as: null-check (bool), count (int32), elements writer.Write(values != null); if (values != null) { writer.WriteCompressedInt32(values.Count); foreach (object value in values) { writer.WriteAnyVar(value); } } } /// /// Outputs a . /// /// Writer /// Value public static void Write(this BinaryWriter writer, in DateTime value) { writer.WriteCompressedInt64(value.Ticks); } /// /// Outputs a . /// /// Writer /// Value public static void Write(this BinaryWriter writer, in TimeSpan value) { writer.WriteCompressedInt64(value.Ticks); } /// /// Outputs a . /// /// Writer /// Vector public static void Write(this BinaryWriter writer, in Vector2 vec) { writer.Write(vec.x); writer.Write(vec.y); } /// /// Outputs a . /// /// Writer /// Vector public static void Write(this BinaryWriter writer, in Vector3 vec) { writer.Write(vec.x); writer.Write(vec.y); writer.Write(vec.z); } /// /// Outputs a . /// /// Writer /// Vector public static void Write(this BinaryWriter writer, in Vector4 vec) { writer.Write(vec.x); writer.Write(vec.y); writer.Write(vec.z); writer.Write(vec.w); } /// /// Outputs a . /// /// Writer /// Color public static void Write(this BinaryWriter writer, in Color color) { writer.Write(color.r); writer.Write(color.g); writer.Write(color.b); writer.Write(color.a); } /// /// Outputs a . /// /// Writer /// Color32 public static void Write(this BinaryWriter writer, in Color32 color) { writer.Write(color.r); writer.Write(color.g); writer.Write(color.b); writer.Write(color.a); } /// /// Outputs a . /// /// Writer /// Quaternion public static void Write(this BinaryWriter writer, in Quaternion quaternion) { writer.Write(quaternion.x); writer.Write(quaternion.y); writer.Write(quaternion.z); writer.Write(quaternion.w); } /// /// Outputs a . /// /// Writer /// Matrix public static void Write(this BinaryWriter writer, in Matrix4x4 matrix) { writer.Write(matrix.m00); writer.Write(matrix.m10); writer.Write(matrix.m20); writer.Write(matrix.m30); writer.Write(matrix.m01); writer.Write(matrix.m11); writer.Write(matrix.m21); writer.Write(matrix.m31); writer.Write(matrix.m02); writer.Write(matrix.m12); writer.Write(matrix.m22); writer.Write(matrix.m32); writer.Write(matrix.m03); writer.Write(matrix.m13); writer.Write(matrix.m23); writer.Write(matrix.m33); } /// /// Outputs a component with the interface. Only the unique ID will be serialized. /// The component can be retrieved using the ID with . It has /// support for null references. /// /// Writer /// Component with the interface public static void WriteUniqueComponent(this BinaryWriter writer, IUxrUniqueId component) { writer.Write(component != null); if (component != null) { writer.Write(component.UniqueId); } } /// /// Outputs an object that implements the interface. It has support for null /// references. /// /// Writer /// Object with the interface /// A type is not supported /// /// A method obtained through reflection that was required for serialization could /// not be found /// public static void WriteUxrSerializable(this BinaryWriter writer, IUxrSerializable serializable) { if (serializable == null) { writer.Write(false); return; } writer.Write(true); // Write serialization version of the serializable, to provide backwards compatibility writer.WriteCompressedInt32(serializable.SerializationVersion); // Write serializable type writer.Write(serializable.GetType()); // Serialization using interface serializable.Serialize(new UxrBinarySerializer(writer), serializable.SerializationVersion); } /// /// Outputs an UxrAxis value. /// /// Writer /// value public static void WriteAxis(this BinaryWriter writer, UxrAxis axis) { // Serialize as a compressed int (0, 1 or 2). writer.WriteCompressedInt32((int)(axis ?? 0)); } /// /// Outputs an object supported by together with its type, so that it can be /// deserialized using . This can be useful when /// the serialized object type is only known at runtime. /// /// Writer /// Value /// The type is not supported /// /// A method obtained through reflection that was required for serialization could /// not be found /// public static void WriteAnyVar(this BinaryWriter writer, T obj) { // First write type UxrVarType varType = UxrVarTypeExt.GetType(obj); writer.Write((byte)varType); if (varType == UxrVarType.Unknown) { return; } // Write additional data for some types Type type = obj.GetType(); if (obj is Enum enumValue) { // Enum type writer.Write(enumValue.GetType()); } if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Tuple<,>) || type.GetGenericTypeDefinition() == typeof(ValueTuple<,>))) { // Item types writer.Write(type.GetGenericArguments()[0]); writer.Write(type.GetGenericArguments()[1]); } if (type.IsArray && type.GetElementType() != typeof(object)) { // Array element type writer.Write(type.GetElementType()); } if (type.IsGenericType && type.GetElementType() != typeof(object) && type.GetGenericTypeDefinition() == typeof(List<>)) { // List element type writer.Write(type.GetGenericArguments()[0]); } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { // Key & value types writer.Write(type.GetGenericArguments()[0]); writer.Write(type.GetGenericArguments()[1]); } // Write value writer.Write(obj, type); } /// /// Generic method that outputs an object supported by . /// /// Writer /// The object /// The type /// The type is not supported /// /// A method obtained through reflection that was required for serialization could /// not be found /// public static void Write(this BinaryWriter writer, T obj) { Type type = typeof(T); if (type == typeof(bool)) { writer.Write(obj is true); return; } if (type == typeof(sbyte)) { writer.Write(obj is sbyte sbyteValue ? sbyteValue : (sbyte)0); return; } if (type == typeof(byte)) { writer.Write(obj is byte byteValue ? byteValue : (byte)0); return; } if (type == typeof(char)) { writer.Write(obj is char charValue ? charValue : (char)0); return; } if (type == typeof(int)) { writer.WriteCompressedInt32(obj is int intValue ? intValue : 0); return; } if (type == typeof(uint)) { writer.WriteCompressedUInt32(obj is uint uintValue ? uintValue : 0); return; } if (type == typeof(long)) { writer.WriteCompressedInt64(obj is long longValue ? longValue : 0); return; } if (type == typeof(ulong)) { writer.WriteCompressedUInt64(obj is ulong ulongValue ? ulongValue : 0); return; } if (type == typeof(float)) { writer.Write(obj is float floatValue ? floatValue : 0); return; } if (type == typeof(double)) { writer.Write(obj is double doubleValue ? doubleValue : 0); return; } if (type == typeof(decimal)) { writer.Write(obj is decimal decimalValue ? decimalValue : 0); return; } if (type == typeof(string)) { writer.Write(obj as string ?? string.Empty); return; } if (obj is Enum enumValue) { writer.WriteEnum(enumValue); return; } if (type == typeof(Type)) { writer.Write(obj as Type); return; } if (type == typeof(Guid)) { writer.Write(obj is Guid guid ? guid : default); return; } if (type == typeof(Guid)) { writer.Write(obj is Guid guid ? guid : default); return; } if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Tuple<,>) || type.GetGenericTypeDefinition() == typeof(ValueTuple<,>))) { Type typeItem1 = type.GetGenericArguments()[0]; Type typeItem2 = type.GetGenericArguments()[1]; GetGenericWriteTupleMethod().MakeGenericMethod(typeItem1, typeItem2).Invoke(writer, new object[] { writer, obj }); return; } if (type.IsArray) { Type elementType = type.GetElementType(); if (elementType != typeof(object)) { GetGenericWriteArrayMethod().MakeGenericMethod(elementType).Invoke(writer, new object[] { writer, obj }); } else { writer.Write(obj as object[]); } return; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) { Type elementType = type.GetGenericArguments()[0]; if (elementType != typeof(object)) { GetGenericWriteListMethod().MakeGenericMethod(elementType).Invoke(writer, new object[] { writer, obj }); } else { writer.Write(obj as List); } return; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { Type keyType = type.GetGenericArguments()[0]; Type valueType = type.GetGenericArguments()[1]; GetDictionaryWriteMethod().MakeGenericMethod(keyType, valueType).Invoke(writer, new object[] { writer, obj }); return; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>)) { Type elementType = type.GetGenericArguments()[0]; if (elementType != typeof(object)) { GetGenericWriteHashSetMethod().MakeGenericMethod(elementType).Invoke(writer, new object[] { writer, obj }); } else { writer.Write(obj as HashSet); } return; } if (type == typeof(DateTime)) { writer.Write(obj is DateTime dateTime ? dateTime : default); return; } if (type == typeof(TimeSpan)) { writer.Write(obj is TimeSpan timeSpan ? timeSpan : default); return; } if (type == typeof(Vector2)) { writer.Write(obj is Vector2 vector2 ? vector2 : default); return; } if (type == typeof(Vector3)) { writer.Write(obj is Vector3 vector3 ? vector3 : default); return; } if (type == typeof(Vector4)) { writer.Write(obj is Vector4 vector4 ? vector4 : default); return; } if (type == typeof(Color)) { writer.Write(obj is Color color ? color : default); return; } if (type == typeof(Color32)) { writer.Write(obj is Color32 color ? color : default); return; } if (type == typeof(Quaternion)) { writer.Write(obj is Quaternion quaternion ? quaternion : default); return; } if (type == typeof(Matrix4x4)) { writer.Write(obj is Matrix4x4 matrix ? matrix : default); return; } if (typeof(IUxrUniqueId).IsAssignableFrom(type)) { writer.WriteUniqueComponent(obj as IUxrUniqueId); return; } if (typeof(IUxrSerializable).IsAssignableFrom(type)) { writer.WriteUxrSerializable(obj as IUxrSerializable); return; } if (type == typeof(UxrAxis)) { writer.WriteAxis(obj as UxrAxis); return; } throw new ArgumentOutOfRangeException(nameof(obj), obj, $"Serializing unknown target type {typeof(T).FullName}"); } #endregion #region Private Methods /// /// Outputs an object supported by . /// /// Writer /// The object /// The type of the object /// The type is not supported /// /// A method obtained through reflection that was required for serialization could /// not be found /// private static void Write(this BinaryWriter writer, T obj, Type type) { if (s_genericWriteMethod == null) { // Try to find and cache generic Write method for T types. s_genericWriteMethod = typeof(BinaryWriterExt).GetMethods().Where(m => m.Name == nameof(Write) && m.IsGenericMethod && m.GetGenericArguments().Count() == 1 && m.GetParameters().Count() == 2 && m.GetParameters()[1].ParameterType.GetInterface(nameof(IEnumerable)) == null) .FirstOrDefault(); if (s_genericWriteMethod == null) { throw new NotSupportedException($"Cannot serialize because generic {nameof(Write)} method was not found"); } } s_genericWriteMethod.MakeGenericMethod(type).Invoke(writer, new object[] { writer, obj }); } /// /// Tries to find the to use the Write method for tuples. /// /// MethodInfo /// /// A method obtained through reflection that was required for serialization could /// not be found /// private static MethodInfo GetGenericWriteTupleMethod() { if (s_genericWriteTupleMethod != null) { return s_genericWriteTupleMethod; } // Try to find and cache Write method for tuples. s_genericWriteTupleMethod = typeof(BinaryWriterExt).GetMethods().Where(m => m.Name == nameof(Write) && m.IsGenericMethod && m.GetGenericArguments().Count() == 2 && m.GetParameters().Count() == 2 && m.GetParameters()[1].ParameterType.IsGenericType && (m.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Tuple<,>) || m.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(ValueTuple<,>))).FirstOrDefault(); if (s_genericWriteTupleMethod == null) { throw new NotSupportedException($"Cannot serialize because {nameof(Write)} method for generic tuples was not found"); } return s_genericWriteTupleMethod; } /// /// Tries to find the to use the Write method for arrays. /// /// MethodInfo /// /// A method obtained through reflection that was required for serialization could /// not be found /// private static MethodInfo GetGenericWriteArrayMethod() { if (s_genericWriteArrayMethod != null) { return s_genericWriteArrayMethod; } // Try to find and cache Write method for arrays of T. s_genericWriteArrayMethod = typeof(BinaryWriterExt).GetMethods().Where(m => m.Name == nameof(Write) && m.IsGenericMethod && m.GetGenericArguments().Count() == 1 && m.GetParameters().Count() == 2 && m.GetParameters()[1].ParameterType.IsArray).FirstOrDefault(); if (s_genericWriteArrayMethod == null) { throw new NotSupportedException($"Cannot serialize because {nameof(Write)} method for generic arrays was not found"); } return s_genericWriteArrayMethod; } /// /// Tries to find the to use the Write method for lists. /// /// MethodInfo /// /// A method obtained through reflection that was required for serialization could /// not be found /// private static MethodInfo GetGenericWriteListMethod() { if (s_genericWriteListMethod != null) { return s_genericWriteListMethod; } // Try to find and cache Write method for lists of T. s_genericWriteListMethod = typeof(BinaryWriterExt).GetMethods().Where(m => m.Name == nameof(Write) && m.IsGenericMethod && m.GetGenericArguments().Count() == 1 && m.GetParameters().Count() == 2 && m.GetParameters()[1].ParameterType.IsGenericType && m.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(List<>)).FirstOrDefault(); if (s_genericWriteListMethod == null) { throw new NotSupportedException($"Cannot serialize because {nameof(Write)} method for generic lists was not found"); } return s_genericWriteListMethod; } /// /// Tries to find the to use the Write method for hash sets. /// /// MethodInfo /// /// A method obtained through reflection that was required for serialization could /// not be found /// private static MethodInfo GetGenericWriteHashSetMethod() { if (s_genericWriteHashSetMethod != null) { return s_genericWriteHashSetMethod; } // Try to find and cache Write method for hash sets of T. s_genericWriteHashSetMethod = typeof(BinaryWriterExt).GetMethods().Where(m => m.Name == nameof(Write) && m.IsGenericMethod && m.GetGenericArguments().Count() == 1 && m.GetParameters().Count() == 2 && m.GetParameters()[1].ParameterType.IsGenericType && m.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(HashSet<>)).FirstOrDefault(); if (s_genericWriteHashSetMethod == null) { throw new NotSupportedException($"Cannot serialize because {nameof(Write)} method for generic hash sets was not found"); } return s_genericWriteHashSetMethod; } /// /// Tries to find the to use the Write method for dictionaries. /// /// MethodInfo /// /// A method obtained through reflection that was required for serialization could /// not be found /// private static MethodInfo GetDictionaryWriteMethod() { if (s_dictionaryWriteMethod != null) { return s_dictionaryWriteMethod; } // Try to find and cache Write method for dictionaries. s_dictionaryWriteMethod = typeof(BinaryWriterExt).GetMethods().Where(m => m.Name == nameof(Write) && m.IsGenericMethod && m.GetGenericArguments().Count() == 2 && m.GetParameters().Count() == 2 && m.GetParameters()[1].ParameterType.IsGenericType && m.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Dictionary<,>)).FirstOrDefault(); if (s_dictionaryWriteMethod == null) { throw new NotSupportedException($"Cannot serialize because {nameof(Write)} method for dictionaries was not found"); } return s_dictionaryWriteMethod; } #endregion #region Private Types & Data private static MethodInfo s_genericWriteTupleMethod; private static MethodInfo s_genericWriteArrayMethod; private static MethodInfo s_genericWriteListMethod; private static MethodInfo s_genericWriteHashSetMethod; private static MethodInfo s_dictionaryWriteMethod; private static MethodInfo s_genericWriteMethod; #endregion } }