diff options
| author | tylermurphy534 <tylermurphy534@gmail.com> | 2022-11-06 15:12:42 -0500 |
|---|---|---|
| committer | tylermurphy534 <tylermurphy534@gmail.com> | 2022-11-06 15:12:42 -0500 |
| commit | eb84bb298d2b95aec7b2ae12cbf25ac64f25379a (patch) | |
| tree | efd616a157df06ab661c6d56651853431ac6b08b /VRCSDK3Worlds/Assets/Udon/Serialization | |
| download | unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.tar.gz unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.tar.bz2 unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.zip | |
move to self host
Diffstat (limited to 'VRCSDK3Worlds/Assets/Udon/Serialization')
395 files changed, 43142 insertions, 0 deletions
diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters.meta new file mode 100644 index 00000000..9a28bf30 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 56ee621fd77c3d045ae77ba8959513b9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonGameObjectComponentReferenceFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonGameObjectComponentReferenceFormatter.cs new file mode 100644 index 00000000..677e3219 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonGameObjectComponentReferenceFormatter.cs @@ -0,0 +1,35 @@ +using System; +using VRC.Udon.Common; +using VRC.Udon.Serialization.OdinSerializer; +using VRC.Udon.Serialization.Formatters; + +[assembly: RegisterFormatter(typeof(UdonGameObjectComponentReferenceFormatter))] + +namespace VRC.Udon.Serialization.Formatters +{ + public sealed class UdonGameObjectComponentReferenceFormatter : BaseFormatter<UdonGameObjectComponentHeapReference> + { + private static readonly Serializer<Type> _typeSerializer = Serializer.Get<Type>(); + + protected override UdonGameObjectComponentHeapReference GetUninitializedObject() + { + return null; + } + + // ReSharper disable once RedundantAssignment + protected override void DeserializeImplementation(ref UdonGameObjectComponentHeapReference value, IDataReader reader) + { + Type type = _typeSerializer.ReadValue(reader); + + value = new UdonGameObjectComponentHeapReference(type); + + RegisterReferenceID(value, reader); + InvokeOnDeserializingCallbacks(ref value, reader.Context); + } + + protected override void SerializeImplementation(ref UdonGameObjectComponentHeapReference value, IDataWriter writer) + { + _typeSerializer.WriteValue(value.type, writer); + } + } +} diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonGameObjectComponentReferenceFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonGameObjectComponentReferenceFormatter.cs.meta new file mode 100644 index 00000000..7af055f9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonGameObjectComponentReferenceFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b1d0b8aa8084bcd44a572d524d7a31bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonProgramFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonProgramFormatter.cs new file mode 100644 index 00000000..7db14523 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonProgramFormatter.cs @@ -0,0 +1,56 @@ +using VRC.Udon.Common; +using VRC.Udon.Common.Interfaces; +using VRC.Udon.Serialization.OdinSerializer; +using VRC.Udon.Serialization.Formatters; + +[assembly: RegisterFormatter(typeof(UdonProgramFormatter))] + +namespace VRC.Udon.Serialization.Formatters +{ + public sealed class UdonProgramFormatter : BaseFormatter<UdonProgram> + { + private static readonly Serializer<byte[]> _byteArrayReaderWriter = Serializer.Get<byte[]>(); + private static readonly Serializer<IUdonHeap> _udonHeapReaderWriter = Serializer.Get<IUdonHeap>(); + private static readonly Serializer<IUdonSymbolTable> _udonSymbolTableReaderWriter = Serializer.Get<IUdonSymbolTable>(); + private static readonly Serializer<IUdonSyncMetadataTable> _udonSyncMetadataTableReaderWriter = Serializer.Get<IUdonSyncMetadataTable>(); + + protected override UdonProgram GetUninitializedObject() + { + return null; + } + + // ReSharper disable once RedundantAssignment + protected override void DeserializeImplementation(ref UdonProgram value, IDataReader reader) + { + reader.ReadString(out string instructionSetIdentifier); + reader.ReadInt32(out int instructionSetVersion); + byte[] byteCode = _byteArrayReaderWriter.ReadValue(reader); + IUdonHeap heap = _udonHeapReaderWriter.ReadValue(reader); + IUdonSymbolTable entryPoints = _udonSymbolTableReaderWriter.ReadValue(reader); + IUdonSymbolTable symbolTable = _udonSymbolTableReaderWriter.ReadValue(reader); + IUdonSyncMetadataTable syncMetadataTable = _udonSyncMetadataTableReaderWriter.ReadValue(reader); + + if(!reader.ReadInt32(out int updateOrder)) + { + updateOrder = 0; + } + + value = new UdonProgram(instructionSetIdentifier, instructionSetVersion, byteCode, heap, entryPoints, symbolTable, syncMetadataTable, updateOrder); + + RegisterReferenceID(value, reader); + InvokeOnDeserializingCallbacks(ref value, reader.Context); + } + + protected override void SerializeImplementation(ref UdonProgram value, IDataWriter writer) + { + writer.WriteString("InstructionSetIdentifier", value.InstructionSetIdentifier); + writer.WriteInt32("InstructionSetVersion", value.InstructionSetVersion); + _byteArrayReaderWriter.WriteValue("ByteCode", value.ByteCode, writer); + _udonHeapReaderWriter.WriteValue("Heap", value.Heap, writer); + _udonSymbolTableReaderWriter.WriteValue("EntryPoints", value.EntryPoints, writer); + _udonSymbolTableReaderWriter.WriteValue("SymbolTable", value.SymbolTable, writer); + _udonSyncMetadataTableReaderWriter.WriteValue("SyncMetadataTable", value.SyncMetadataTable, writer); + writer.WriteInt32("UpdateOrder", value.UpdateOrder); + } + } +} diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonProgramFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonProgramFormatter.cs.meta new file mode 100644 index 00000000..565153dd --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/Formatters/UdonProgramFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2626352b2a60eb41adc3580ae44c750 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer.meta new file mode 100644 index 00000000..3be476a9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c997bef93af6b114d8719d6bd29dd32a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Config.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Config.meta new file mode 100644 index 00000000..fcbed50d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Config.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d597f5699e44ab14cb8311daf563667c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Config/GlobalSerializationConfig.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Config/GlobalSerializationConfig.cs new file mode 100644 index 00000000..bb3b2b5c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Config/GlobalSerializationConfig.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// <copyright file="GlobalSerializationConfig.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Contains global configuration options for the serialization system. + /// </summary> + public class GlobalSerializationConfig + { + private static readonly GlobalSerializationConfig instance = new GlobalSerializationConfig(); + + /// <summary> + /// Gets the global configuration instance. + /// </summary> + public static GlobalSerializationConfig Instance { get { return GlobalSerializationConfig.instance; } } + + /// <summary> + /// Gets the logger. + /// </summary> + public ILogger Logger { get { return DefaultLoggers.UnityLogger; } } + + /// <summary> + /// Gets the editor serialization format. + /// </summary> + public DataFormat EditorSerializationFormat { get { return DataFormat.Nodes; } } + + /// <summary> + /// Gets the build serialization format. + /// </summary> + public DataFormat BuildSerializationFormat { get { return DataFormat.Binary; } } + + /// <summary> + /// Gets the logging policy. + /// </summary> + public LoggingPolicy LoggingPolicy { get { return LoggingPolicy.LogErrors; } } + + /// <summary> + /// Gets the error handling policy. + /// </summary> + public ErrorHandlingPolicy ErrorHandlingPolicy { get { return ErrorHandlingPolicy.Resilient; } } + + internal static void LoadInstanceIfAssetExists() + { + // TODO: @Integration: If you store your config in an asset or file somewhere, load it here. + } + + internal static bool HasInstanceLoaded + { + get + { + // TODO: @Integration: If you store your config in an asset or file somewhere, return true here if it is loaded, otherwise false. + // If your config is stored in a Unity asset, do NOT load it here; this property is often called from the + // serialization thread, meaning you are not allowed to use Unity's API for loading assets here. + // If this value is false, default configuration values will be used - the same defaults as are set in this class. + return true; + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Config/GlobalSerializationConfig.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Config/GlobalSerializationConfig.cs.meta new file mode 100644 index 00000000..1d52e9fa --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Config/GlobalSerializationConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bfaf18dca1f68cc99ebeb0b862179265 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core.meta new file mode 100644 index 00000000..63be3359 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4dbb1e5505a8daf4b8893ed8f7574292 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters.meta new file mode 100644 index 00000000..34d3ff01 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b8c4a9d5f0193894e8e4a4a756588c43 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReader.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReader.cs new file mode 100644 index 00000000..912a8e1d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReader.cs @@ -0,0 +1,544 @@ +//----------------------------------------------------------------------- +// <copyright file="BaseDataReader.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.IO; + + /// <summary> + /// Provides basic functionality and overridable abstract methods for implementing a data reader. + /// <para /> + /// If you inherit this class, it is VERY IMPORTANT that you implement each abstract method to the *exact* specifications the documentation specifies. + /// </summary> + /// <seealso cref="BaseDataReaderWriter" /> + /// <seealso cref="IDataReader" /> + public abstract class BaseDataReader : BaseDataReaderWriter, IDataReader + { + private DeserializationContext context; + private Stream stream; + + /// <summary> + /// Initializes a new instance of the <see cref="BaseDataReader" /> class. + /// </summary> + /// <param name="stream">The base stream of the reader.</param> + /// <param name="context">The deserialization context to use.</param> + /// <exception cref="System.ArgumentNullException">The stream or context is null.</exception> + /// <exception cref="System.ArgumentException">Cannot read from stream.</exception> + protected BaseDataReader(Stream stream, DeserializationContext context) + { + this.context = context; + + if (stream != null) + { + this.Stream = stream; + } + } + + /// <summary> + /// Gets the current node id. If this is less than zero, the current node has no id. + /// </summary> + /// <value> + /// The current node id. + /// </value> + public int CurrentNodeId { get { return this.CurrentNode.Id; } } + + /// <summary> + /// Gets the current node depth. In other words, the current count of the node stack. + /// </summary> + /// <value> + /// The current node depth. + /// </value> + public int CurrentNodeDepth { get { return this.NodeDepth; } } + + /// <summary> + /// Gets the name of the current node. + /// </summary> + /// <value> + /// The name of the current node. + /// </value> + public string CurrentNodeName { get { return this.CurrentNode.Name; } } + + /// <summary> + /// Gets or sets the base stream of the reader. + /// </summary> + /// <value> + /// The base stream of the reader. + /// </value> + /// <exception cref="System.ArgumentNullException">value</exception> + /// <exception cref="System.ArgumentException">Cannot read from stream</exception> + public virtual Stream Stream + { + get + { + return this.stream; + } + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + if (value.CanRead == false) + { + throw new ArgumentException("Cannot read from stream"); + } + + this.stream = value; + } + } + + /// <summary> + /// Gets the deserialization context. + /// </summary> + /// <value> + /// The deserialization context. + /// </value> + public DeserializationContext Context + { + get + { + if (this.context == null) + { + this.context = new DeserializationContext(); + } + + return this.context; + } + set + { + this.context = value; + } + } + + /// <summary> + /// Tries to enter a node. This will succeed if the next entry is an <see cref="EntryType.StartOfNode"/>. + /// <para /> + /// This call MUST (eventually) be followed by a corresponding call to <see cref="IDataReader.ExitNode(DeserializationContext)"/> + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> properties to the correct values for the current node. + /// </summary> + /// <param name="type">The type of the node. This value will be null if there was no metadata, or if the reader's serialization binder failed to resolve the type name.</param> + /// <returns><c>true</c> if entering a node succeeded, otherwise <c>false</c></returns> + public abstract bool EnterNode(out Type type); + + /// <summary> + /// Exits the current node. This method will keep skipping entries using <see cref="IDataReader.SkipEntry(DeserializationContext)"/> until an <see cref="EntryType.EndOfNode"/> is reached, or the end of the stream is reached. + /// <para /> + /// This call MUST have been preceded by a corresponding call to <see cref="IDataReader.EnterNode(out Type)"/>. + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> to the correct values for the node that was prior to the current node. + /// </summary> + /// <returns><c>true</c> if the method exited a node, <c>false</c> if it reached the end of the stream.</returns> + public abstract bool ExitNode(); + + /// <summary> + /// Tries to enters an array node. This will succeed if the next entry is an <see cref="EntryType.StartOfArray"/>. + /// <para /> + /// This call MUST (eventually) be followed by a corresponding call to <see cref="IDataReader.ExitArray(DeserializationContext)"/> + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> properties to the correct values for the current array node. + /// </summary> + /// <param name="length">The length of the array that was entered.</param> + /// <returns><c>true</c> if an array was entered, otherwise <c>false</c></returns> + public abstract bool EnterArray(out long length); + + /// <summary> + /// Exits the closest array. This method will keep skipping entries using <see cref="IDataReader.SkipEntry(DeserializationContext)"/> until an <see cref="EntryType.EndOfArray"/> is reached, or the end of the stream is reached. + /// <para /> + /// This call MUST have been preceded by a corresponding call to <see cref="IDataReader.EnterArray(out long)"/>. + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> to the correct values for the node that was prior to the exited array node. + /// </summary> + /// <returns><c>true</c> if the method exited an array, <c>false</c> if it reached the end of the stream.</returns> + public abstract bool ExitArray(); + + /// <summary> + /// Reads a primitive array value. This call will succeed if the next entry is an <see cref="EntryType.PrimitiveArray"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)"/>.</typeparam> + /// <param name="array">The resulting primitive array.</param> + /// <returns><c>true</c> if reading a primitive array succeeded, otherwise <c>false</c></returns> + public abstract bool ReadPrimitiveArray<T>(out T[] array) where T : struct; + + /// <summary> + /// Peeks ahead and returns the type of the next entry in the stream. + /// </summary> + /// <param name="name">The name of the next entry, if it has one.</param> + /// <returns>The type of the next entry.</returns> + public abstract EntryType PeekEntry(out string name); + + /// <summary> + /// Reads an internal reference id. This call will succeed if the next entry is an <see cref="EntryType.InternalReference"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="id">The internal reference id.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadInternalReference(out int id); + + /// <summary> + /// Reads an external reference index. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByIndex"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="index">The external reference index.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadExternalReference(out int index); + + /// <summary> + /// Reads an external reference guid. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByGuid"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="guid">The external reference guid.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadExternalReference(out Guid guid); + + /// <summary> + /// Reads an external reference string. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByString" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="id">The external reference string.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public abstract bool ReadExternalReference(out string id); + + /// <summary> + /// Reads a <see cref="char"/> value. This call will succeed if the next entry is an <see cref="EntryType.String"/>. + /// <para /> + /// If the string of the entry is longer than 1 character, the first character of the string will be taken as the result. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadChar(out char value); + + /// <summary> + /// Reads a <see cref="string"/> value. This call will succeed if the next entry is an <see cref="EntryType.String"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadString(out string value); + + /// <summary> + /// Reads a <see cref="Guid"/> value. This call will succeed if the next entry is an <see cref="EntryType.Guid"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadGuid(out Guid value); + + /// <summary> + /// Reads an <see cref="sbyte"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="sbyte.MinValue"/> or larger than <see cref="sbyte.MaxValue"/>, the result will be default(<see cref="sbyte"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadSByte(out sbyte value); + + /// <summary> + /// Reads a <see cref="short"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="short.MinValue"/> or larger than <see cref="short.MaxValue"/>, the result will be default(<see cref="short"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadInt16(out short value); + + /// <summary> + /// Reads an <see cref="int"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="int.MinValue"/> or larger than <see cref="int.MaxValue"/>, the result will be default(<see cref="int"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadInt32(out int value); + + /// <summary> + /// Reads a <see cref="long"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="long.MinValue"/> or larger than <see cref="long.MaxValue"/>, the result will be default(<see cref="long"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadInt64(out long value); + + /// <summary> + /// Reads a <see cref="byte"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="byte.MinValue"/> or larger than <see cref="byte.MaxValue"/>, the result will be default(<see cref="byte"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadByte(out byte value); + + /// <summary> + /// Reads an <see cref="ushort"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="ushort.MinValue"/> or larger than <see cref="ushort.MaxValue"/>, the result will be default(<see cref="ushort"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadUInt16(out ushort value); + + /// <summary> + /// Reads an <see cref="uint"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="uint.MinValue"/> or larger than <see cref="uint.MaxValue"/>, the result will be default(<see cref="uint"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadUInt32(out uint value); + + /// <summary> + /// Reads an <see cref="ulong"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="ulong.MinValue"/> or larger than <see cref="ulong.MaxValue"/>, the result will be default(<see cref="ulong"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadUInt64(out ulong value); + + /// <summary> + /// Reads a <see cref="decimal"/> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint"/> or an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="decimal.MinValue"/> or larger than <see cref="decimal.MaxValue"/>, the result will be default(<see cref="decimal"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadDecimal(out decimal value); + + /// <summary> + /// Reads a <see cref="float"/> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint"/> or an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="float.MinValue"/> or larger than <see cref="float.MaxValue"/>, the result will be default(<see cref="float"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadSingle(out float value); + + /// <summary> + /// Reads a <see cref="double"/> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint"/> or an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="double.MinValue"/> or larger than <see cref="double.MaxValue"/>, the result will be default(<see cref="double"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadDouble(out double value); + + /// <summary> + /// Reads a <see cref="bool"/> value. This call will succeed if the next entry is an <see cref="EntryType.Boolean"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadBoolean(out bool value); + + /// <summary> + /// Reads a <c>null</c> value. This call will succeed if the next entry is an <see cref="EntryType.Null"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + public abstract bool ReadNull(); + + /// <summary> + /// Skips the next entry value, unless it is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. If the next entry value is an <see cref="EntryType.StartOfNode"/> or an <see cref="EntryType.StartOfArray"/>, all of its contents will be processed, deserialized and registered in the deserialization context, so that internal reference values are not lost to entries further down the stream. + /// </summary> + public virtual void SkipEntry() + { + var peekedEntry = this.PeekEntry(); + + if (peekedEntry == EntryType.StartOfNode) + { + Type type; + + bool exitNode = true; + + this.EnterNode(out type); + + try + { + if (type != null) + { + // We have the necessary metadata to read this type, and register all of its reference values (perhaps including itself) in the serialization context + // Sadly, we have no choice but to risk boxing of structs here + // Luckily, this is a rare case + + if (FormatterUtilities.IsPrimitiveType(type)) + { + // It is a boxed primitive type; we read the value and register it + var serializer = Serializer.Get(type); + object value = serializer.ReadValueWeak(this); + + if (this.CurrentNodeId >= 0) + { + this.Context.RegisterInternalReference(this.CurrentNodeId, value); + } + } + else + { + var formatter = FormatterLocator.GetFormatter(type, this.Context.Config.SerializationPolicy); + object value = formatter.Deserialize(this); + + if (this.CurrentNodeId >= 0) + { + this.Context.RegisterInternalReference(this.CurrentNodeId, value); + } + } + } + else + { + // We have no metadata, and reference values might be lost + // We must read until a node on the same level terminates + while (true) + { + peekedEntry = this.PeekEntry(); + + if (peekedEntry == EntryType.EndOfStream) + { + break; + } + else if (peekedEntry == EntryType.EndOfNode) + { + break; + } + else if (peekedEntry == EntryType.EndOfArray) + { + this.ReadToNextEntry(); // Consume end of arrays that we can potentially get stuck on + } + else + { + this.SkipEntry(); + } + } + } + } + catch (SerializationAbortException ex) + { + exitNode = false; + throw ex; + } + finally + { + if (exitNode) + { + this.ExitNode(); + } + } + } + else if (peekedEntry == EntryType.StartOfArray) + { + // We must read until an array on the same level terminates + this.ReadToNextEntry(); // Consume start of array + + while (true) + { + peekedEntry = this.PeekEntry(); + + if (peekedEntry == EntryType.EndOfStream) + { + break; + } + else if (peekedEntry == EntryType.EndOfArray) + { + this.ReadToNextEntry(); // Consume end of array and break + break; + } + else if (peekedEntry == EntryType.EndOfNode) + { + this.ReadToNextEntry(); // Consume end of nodes that we can potentially get stuck on + } + else + { + this.SkipEntry(); + } + } + } + else if (peekedEntry != EntryType.EndOfArray && peekedEntry != EntryType.EndOfNode) // We can't skip end of arrays and end of nodes + { + this.ReadToNextEntry(); // We can just skip a single value entry + } + } + + /// <summary> + /// Disposes all resources and streams kept by the data reader. + /// </summary> + public abstract void Dispose(); + + /// <summary> + /// Tells the reader that a new serialization session is about to begin, and that it should clear all cached values left over from any prior serialization sessions. + /// This method is only relevant when the same reader is used to deserialize several different, unrelated values. + /// </summary> + public virtual void PrepareNewSerializationSession() + { + this.ClearNodes(); + } + + /// <summary> + /// Gets a dump of the data being read by the writer. The format of this dump varies, but should be useful for debugging purposes. + /// </summary> + public abstract string GetDataDump(); + + /// <summary> + /// Peeks the current entry. + /// </summary> + /// <returns>The peeked entry.</returns> + protected abstract EntryType PeekEntry(); + + /// <summary> + /// Consumes the current entry, and reads to the next one. + /// </summary> + /// <returns>The next entry.</returns> + protected abstract EntryType ReadToNextEntry(); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReader.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReader.cs.meta new file mode 100644 index 00000000..afb5c658 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ac1e1612275111bd50db8a3de8ba9c4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReaderWriter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReaderWriter.cs new file mode 100644 index 00000000..8c0c5c9d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReaderWriter.cs @@ -0,0 +1,212 @@ +//----------------------------------------------------------------------- +// <copyright file="BaseDataReaderWriter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Implements functionality that is shared by both data readers and data writers. + /// </summary> + public abstract class BaseDataReaderWriter + { + // Once, there was a stack here. But stacks are slow, so now there's no longer + // a stack here and we just do it ourselves. + private NodeInfo[] nodes = new NodeInfo[32]; + private int nodesLength = 0; + + /// <summary> + /// Gets or sets the context's or writer's serialization binder. + /// </summary> + /// <value> + /// The reader's or writer's serialization binder. + /// </value> + [Obsolete("Use the Binder member on the writer's SerializationContext/DeserializationContext instead.", error: false)] + public TwoWaySerializationBinder Binder + { + get + { + if (this is IDataWriter) + { + return (this as IDataWriter).Context.Binder; + } + else if (this is IDataReader) + { + return (this as IDataReader).Context.Binder; + } + + return TwoWaySerializationBinder.Default; + } + + set + { + if (this is IDataWriter) + { + (this as IDataWriter).Context.Binder = value; + } + else if (this is IDataReader) + { + (this as IDataReader).Context.Binder = value; + } + } + } + + /// <summary> + /// Gets a value indicating whether the reader or writer is in an array node. + /// </summary> + /// <value> + /// <c>true</c> if the reader or writer is in an array node; otherwise, <c>false</c>. + /// </value> + public bool IsInArrayNode { get { return this.nodesLength == 0 ? false : this.nodes[this.nodesLength - 1].IsArray; } } + + /// <summary> + /// Gets the current node depth. In other words, the current count of the node stack. + /// </summary> + /// <value> + /// The current node depth. + /// </value> + protected int NodeDepth { get { return this.nodesLength; } } + + /// <summary> + /// Gets the current node, or <see cref="NodeInfo.Empty"/> if there is no current node. + /// </summary> + /// <value> + /// The current node. + /// </value> + protected NodeInfo CurrentNode { get { return this.nodesLength == 0 ? NodeInfo.Empty : this.nodes[this.nodesLength - 1]; } } + + /// <summary> + /// Pushes a node onto the node stack. + /// </summary> + /// <param name="node">The node to push.</param> + protected void PushNode(NodeInfo node) + { + if (this.nodesLength == this.nodes.Length) + { + this.ExpandNodes(); + } + + this.nodes[this.nodesLength] = node; + this.nodesLength++; + } + + /// <summary> + /// Pushes a node with the given name, id and type onto the node stack. + /// </summary> + /// <param name="name">The name of the node.</param> + /// <param name="id">The id of the node.</param> + /// <param name="type">The type of the node.</param> + protected void PushNode(string name, int id, Type type) + { + if (this.nodesLength == this.nodes.Length) + { + this.ExpandNodes(); + } + + this.nodes[this.nodesLength] = new NodeInfo(name, id, type, false); + this.nodesLength++; + } + + /// <summary> + /// Pushes an array node onto the node stack. This uses values from the current node to provide extra info about the array node. + /// </summary> + protected void PushArray() + { + if (this.nodesLength == this.nodes.Length) + { + this.ExpandNodes(); + } + + if (this.nodesLength == 0 || this.nodes[this.nodesLength - 1].IsArray) + { + this.nodes[this.nodesLength] = new NodeInfo(null, -1, null, true); + } + else + { + var current = this.nodes[this.nodesLength - 1]; + this.nodes[this.nodesLength] = new NodeInfo(current.Name, current.Id, current.Type, true); + } + + this.nodesLength++; + } + + private void ExpandNodes() + { + var newArr = new NodeInfo[this.nodes.Length * 2]; + + var oldNodes = this.nodes; + + for (int i = 0; i < oldNodes.Length; i++) + { + newArr[i] = oldNodes[i]; + } + + this.nodes = newArr; + } + + /// <summary> + /// Pops the current node off of the node stack. + /// </summary> + /// <param name="name">The name of the node to pop.</param> + /// <exception cref="System.InvalidOperationException"> + /// There are no nodes to pop. + /// or + /// Tried to pop node with given name, but the current node's name was different. + /// </exception> + protected void PopNode(string name) + { + if (this.nodesLength == 0) + { + throw new InvalidOperationException("There are no nodes to pop."); + } + + // @Speedup - this safety isn't worth the performance hit, and never happens with properly written writers + //var current = this.CurrentNode; + + //if (current.Name != name) + //{ + // throw new InvalidOperationException("Tried to pop node with name " + name + " but current node's name is " + current.Name); + //} + + this.nodesLength--; + } + + /// <summary> + /// Pops the current node if the current node is an array node. + /// </summary> + protected void PopArray() + { + if (this.nodesLength == 0) + { + throw new InvalidOperationException("There are no nodes to pop."); + } + + if (this.nodes[this.nodesLength - 1].IsArray == false) + { + throw new InvalidOperationException("Was not in array when exiting array."); + } + + this.nodesLength--; + } + + protected void ClearNodes() + { + this.nodesLength = 0; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReaderWriter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReaderWriter.cs.meta new file mode 100644 index 00000000..1b13c72a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataReaderWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 501a7e1356f5fdc8e9bbefcd61a56490 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataWriter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataWriter.cs new file mode 100644 index 00000000..34cadebe --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataWriter.cs @@ -0,0 +1,315 @@ +//----------------------------------------------------------------------- +// <copyright file="BaseDataWriter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.IO; + + /// <summary> + /// Provides basic functionality and overridable abstract methods for implementing a data writer. + /// <para /> + /// If you inherit this class, it is VERY IMPORTANT that you implement each abstract method to the *exact* specifications the documentation specifies. + /// </summary> + /// <seealso cref="BaseDataReaderWriter" /> + /// <seealso cref="IDataWriter" /> + public abstract class BaseDataWriter : BaseDataReaderWriter, IDataWriter + { + private SerializationContext context; + private Stream stream; + + /// <summary> + /// Initializes a new instance of the <see cref="BaseDataWriter" /> class. + /// </summary> + /// <param name="stream">The base stream of the writer.</param> + /// <param name="context">The serialization context to use.</param> + /// <exception cref="System.ArgumentNullException">The stream or context is null.</exception> + /// <exception cref="System.ArgumentException">Cannot write to the stream.</exception> + protected BaseDataWriter(Stream stream, SerializationContext context) + { + this.context = context; + + if (stream != null) + { + this.Stream = stream; + } + } + + /// <summary> + /// Gets or sets the base stream of the writer. + /// </summary> + /// <value> + /// The base stream of the writer. + /// </value> + /// <exception cref="System.ArgumentNullException">value</exception> + /// <exception cref="System.ArgumentException">Cannot write to stream</exception> + public virtual Stream Stream + { + get + { + return this.stream; + } + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + if (value.CanWrite == false) + { + throw new ArgumentException("Cannot write to stream"); + } + + this.stream = value; + } + } + + /// <summary> + /// Gets the serialization context. + /// </summary> + /// <value> + /// The serialization context. + /// </value> + public SerializationContext Context + { + get + { + if (this.context == null) + { + this.context = new SerializationContext(); + } + + return this.context; + } + set + { + this.context = value; + } + } + + /// <summary> + /// Flushes everything that has been written so far to the writer's base stream. + /// </summary> + public virtual void FlushToStream() + { + this.Stream.Flush(); + } + + /// <summary> + /// Writes the beginning of a reference node. + /// <para /> + /// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)"/>, with the same name. + /// </summary> + /// <param name="name">The name of the reference node.</param> + /// <param name="type">The type of the reference node. If null, no type metadata will be written.</param> + /// <param name="id">The id of the reference node. This id is acquired by calling <see cref="SerializationContext.TryRegisterInternalReference(object, out int)"/>.</param> + public abstract void BeginReferenceNode(string name, Type type, int id); + + /// <summary> + /// Begins a struct/value type node. This is essentially the same as a reference node, except it has no internal reference id. + /// <para /> + /// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)"/>, with the same name. + /// </summary> + /// <param name="name">The name of the struct node.</param> + /// <param name="type">The type of the struct node. If null, no type metadata will be written.</param> + public abstract void BeginStructNode(string name, Type type); + + /// <summary> + /// Ends the current node with the given name. If the current node has another name, an <see cref="InvalidOperationException"/> is thrown. + /// </summary> + /// <param name="name">The name of the node to end. This has to be the name of the current node.</param> + public abstract void EndNode(string name); + + /// <summary> + /// Begins an array node of the given length. + /// </summary> + /// <param name="length">The length of the array to come.</param> + public abstract void BeginArrayNode(long length); + + /// <summary> + /// Ends the current array node, if the current node is an array node. + /// </summary> + public abstract void EndArrayNode(); + + /// <summary> + /// Writes a primitive array to the stream. + /// </summary> + /// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)"/>.</typeparam> + /// <param name="array">The primitive array to write.</param> + public abstract void WritePrimitiveArray<T>(T[] array) where T : struct; + + /// <summary> + /// Writes a null value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + public abstract void WriteNull(string name); + + /// <summary> + /// Writes an internal reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="id">The value to write.</param> + public abstract void WriteInternalReference(string name, int id); + + /// <summary> + /// Writes an external index reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="index">The value to write.</param> + public abstract void WriteExternalReference(string name, int index); + + /// <summary> + /// Writes an external guid reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="guid">The value to write.</param> + public abstract void WriteExternalReference(string name, Guid guid); + + /// <summary> + /// Writes an external string reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="id">The value to write.</param> + public abstract void WriteExternalReference(string name, string id); + + /// <summary> + /// Writes a <see cref="char"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteChar(string name, char value); + + /// <summary> + /// Writes a <see cref="string"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteString(string name, string value); + + /// <summary> + /// Writes a <see cref="Guid"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteGuid(string name, Guid value); + + /// <summary> + /// Writes an <see cref="sbyte"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteSByte(string name, sbyte value); + + /// <summary> + /// Writes a <see cref="short"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteInt16(string name, short value); + + /// <summary> + /// Writes an <see cref="int"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteInt32(string name, int value); + + /// <summary> + /// Writes a <see cref="long"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteInt64(string name, long value); + + /// <summary> + /// Writes a <see cref="byte"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteByte(string name, byte value); + + /// <summary> + /// Writes an <see cref="ushort"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteUInt16(string name, ushort value); + + /// <summary> + /// Writes an <see cref="uint"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteUInt32(string name, uint value); + + /// <summary> + /// Writes an <see cref="ulong"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteUInt64(string name, ulong value); + + /// <summary> + /// Writes a <see cref="decimal"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteDecimal(string name, decimal value); + + /// <summary> + /// Writes a <see cref="float"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteSingle(string name, float value); + + /// <summary> + /// Writes a <see cref="double"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteDouble(string name, double value); + + /// <summary> + /// Writes a <see cref="bool"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public abstract void WriteBoolean(string name, bool value); + + /// <summary> + /// Disposes all resources and streams kept by the data writer. + /// </summary> + public abstract void Dispose(); + + /// <summary> + /// Tells the writer that a new serialization session is about to begin, and that it should clear all cached values left over from any prior serialization sessions. + /// This method is only relevant when the same writer is used to serialize several different, unrelated values. + /// </summary> + public virtual void PrepareNewSerializationSession() + { + this.ClearNodes(); + } + + /// <summary> + /// Gets a dump of the data currently written by the writer. The format of this dump varies, but should be useful for debugging purposes. + /// </summary> + public abstract string GetDataDump(); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataWriter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataWriter.cs.meta new file mode 100644 index 00000000..f4fe868c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/BaseDataWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9638b18c6b6b6532b3b3cd3a73fefc2a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary.meta new file mode 100644 index 00000000..9459fc06 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 16581b6793d594a40b064cf2aa0103ce +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataReader.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataReader.cs new file mode 100644 index 00000000..fb030520 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataReader.cs @@ -0,0 +1,2786 @@ +//----------------------------------------------------------------------- +// <copyright file="BinaryDataReader.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using VRC.Udon.Serialization.OdinSerializer.Utilities.Unsafe; + using System; + using System.Collections.Generic; + using System.IO; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + +#pragma warning disable 0649 // Field is never assigned to and will have its default value // VRC + + /// <summary> + /// Reads data from a stream that has been written by a <see cref="BinaryDataWriter"/>. + /// </summary> + /// <seealso cref="BaseDataReader" /> + public unsafe class BinaryDataReader : BaseDataReader + { + private static readonly Dictionary<Type, Delegate> PrimitiveFromByteMethods = new Dictionary<Type, Delegate>() + { + { typeof(char), (Func<byte[], int, char>) ((b, i) => (char)ProperBitConverter.ToUInt16(b, i)) }, + { typeof(byte), (Func<byte[], int, byte>) ((b, i) => b[i]) }, + { typeof(sbyte), (Func<byte[], int, sbyte>) ((b, i) => (sbyte)b[i]) }, + { typeof(bool), (Func<byte[], int, bool>) ((b, i) => (b[i] == 0) ? false : true) }, + { typeof(short), (Func<byte[], int, short>) ProperBitConverter.ToInt16 }, + { typeof(int), (Func<byte[], int, int>) ProperBitConverter.ToInt32 }, + { typeof(long), (Func<byte[], int, long>) ProperBitConverter.ToInt64 }, + { typeof(ushort), (Func<byte[], int, ushort>) ProperBitConverter.ToUInt16 }, + { typeof(uint), (Func<byte[], int, uint>) ProperBitConverter.ToUInt32 }, + { typeof(ulong), (Func<byte[], int, ulong>) ProperBitConverter.ToUInt64 }, + { typeof(decimal), (Func<byte[], int, decimal>) ProperBitConverter.ToDecimal }, + { typeof(float), (Func<byte[], int, float>) ProperBitConverter.ToSingle }, + { typeof(double), (Func<byte[], int, double>) ProperBitConverter.ToDouble }, + { typeof(Guid), (Func<byte[], int, Guid>) ProperBitConverter.ToGuid } + }; + + private byte[] internalBufferBackup; + private byte[] buffer = new byte[1024 * 100]; + + private int bufferIndex; + private int bufferEnd; + + private EntryType? peekedEntryType; + private BinaryEntryType peekedBinaryEntryType; + private string peekedEntryName; + private Dictionary<int, Type> types = new Dictionary<int, Type>(16); + + public BinaryDataReader() : base(null, null) + { + this.internalBufferBackup = this.buffer; + } + + /// <summary> + /// Initializes a new instance of the <see cref="BinaryDataReader" /> class. + /// </summary> + /// <param name="stream">The base stream of the reader.</param> + /// <param name="context">The deserialization context to use.</param> + public BinaryDataReader(Stream stream, DeserializationContext context) : base(stream, context) + { + this.internalBufferBackup = this.buffer; + } + + /// <summary> + /// Disposes all resources kept by the data reader, except the stream, which can be reused later. + /// </summary> + public override void Dispose() + { + //this.Stream.Dispose(); + } + + /// <summary> + /// Peeks ahead and returns the type of the next entry in the stream. + /// </summary> + /// <param name="name">The name of the next entry, if it has one.</param> + /// <returns> + /// The type of the next entry. + /// </returns> + public override EntryType PeekEntry(out string name) + { + if (this.peekedEntryType != null) + { + name = this.peekedEntryName; + return (EntryType)this.peekedEntryType; + } + + this.peekedBinaryEntryType = this.HasBufferData(1) ? (BinaryEntryType)this.buffer[this.bufferIndex++] : BinaryEntryType.EndOfStream; + + // Switch on entry type + switch (this.peekedBinaryEntryType) + { + case BinaryEntryType.EndOfStream: + name = null; + this.peekedEntryName = null; + this.peekedEntryType = EntryType.EndOfStream; + break; + + case BinaryEntryType.NamedStartOfReferenceNode: + case BinaryEntryType.NamedStartOfStructNode: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.StartOfNode; + break; + + case BinaryEntryType.UnnamedStartOfReferenceNode: + case BinaryEntryType.UnnamedStartOfStructNode: + name = null; + this.peekedEntryType = EntryType.StartOfNode; + break; + + case BinaryEntryType.EndOfNode: + name = null; + this.peekedEntryType = EntryType.EndOfNode; + break; + + case BinaryEntryType.StartOfArray: + name = null; + this.peekedEntryType = EntryType.StartOfArray; + break; + + case BinaryEntryType.EndOfArray: + name = null; + this.peekedEntryType = EntryType.EndOfArray; + break; + + case BinaryEntryType.PrimitiveArray: + name = null; + this.peekedEntryType = EntryType.PrimitiveArray; + break; + + case BinaryEntryType.NamedInternalReference: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.InternalReference; + break; + + case BinaryEntryType.UnnamedInternalReference: + name = null; + this.peekedEntryType = EntryType.InternalReference; + break; + + case BinaryEntryType.NamedExternalReferenceByIndex: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.ExternalReferenceByIndex; + break; + + case BinaryEntryType.UnnamedExternalReferenceByIndex: + name = null; + this.peekedEntryType = EntryType.ExternalReferenceByIndex; + break; + + case BinaryEntryType.NamedExternalReferenceByGuid: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.ExternalReferenceByGuid; + break; + + case BinaryEntryType.UnnamedExternalReferenceByGuid: + name = null; + this.peekedEntryType = EntryType.ExternalReferenceByGuid; + break; + + case BinaryEntryType.NamedExternalReferenceByString: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.ExternalReferenceByString; + break; + + case BinaryEntryType.UnnamedExternalReferenceByString: + name = null; + this.peekedEntryType = EntryType.ExternalReferenceByString; + break; + + case BinaryEntryType.NamedSByte: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.UnnamedSByte: + name = null; + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.NamedByte: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.UnnamedByte: + name = null; + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.NamedShort: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.UnnamedShort: + name = null; + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.NamedUShort: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.UnnamedUShort: + name = null; + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.NamedInt: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.UnnamedInt: + name = null; + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.NamedUInt: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.UnnamedUInt: + name = null; + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.NamedLong: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.UnnamedLong: + name = null; + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.NamedULong: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.UnnamedULong: + name = null; + this.peekedEntryType = EntryType.Integer; + break; + + case BinaryEntryType.NamedFloat: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.FloatingPoint; + break; + + case BinaryEntryType.UnnamedFloat: + name = null; + this.peekedEntryType = EntryType.FloatingPoint; + break; + + case BinaryEntryType.NamedDouble: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.FloatingPoint; + break; + + case BinaryEntryType.UnnamedDouble: + name = null; + this.peekedEntryType = EntryType.FloatingPoint; + break; + + case BinaryEntryType.NamedDecimal: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.FloatingPoint; + break; + + case BinaryEntryType.UnnamedDecimal: + name = null; + this.peekedEntryType = EntryType.FloatingPoint; + break; + + case BinaryEntryType.NamedChar: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.String; + break; + + case BinaryEntryType.UnnamedChar: + name = null; + this.peekedEntryType = EntryType.String; + break; + + case BinaryEntryType.NamedString: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.String; + break; + + case BinaryEntryType.UnnamedString: + name = null; + this.peekedEntryType = EntryType.String; + break; + + case BinaryEntryType.NamedGuid: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.Guid; + break; + + case BinaryEntryType.UnnamedGuid: + name = null; + this.peekedEntryType = EntryType.Guid; + break; + + case BinaryEntryType.NamedBoolean: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.Boolean; + break; + + case BinaryEntryType.UnnamedBoolean: + name = null; + this.peekedEntryType = EntryType.Boolean; + break; + + case BinaryEntryType.NamedNull: + name = this.ReadStringValue(); + this.peekedEntryType = EntryType.Null; + break; + + case BinaryEntryType.UnnamedNull: + name = null; + this.peekedEntryType = EntryType.Null; + break; + + case BinaryEntryType.TypeName: + case BinaryEntryType.TypeID: + this.peekedBinaryEntryType = BinaryEntryType.Invalid; + this.peekedEntryType = EntryType.Invalid; + throw new InvalidOperationException("Invalid binary data stream: BinaryEntryType.TypeName and BinaryEntryType.TypeID must never be peeked by the binary reader."); + + case BinaryEntryType.Invalid: + default: + name = null; + this.peekedBinaryEntryType = BinaryEntryType.Invalid; + this.peekedEntryType = EntryType.Invalid; + throw new InvalidOperationException("Invalid binary data stream: could not parse peeked BinaryEntryType byte '" + (byte)this.peekedBinaryEntryType + "' into a known entry type."); + } + + this.peekedEntryName = name; + return this.peekedEntryType.Value; + } + + /// <summary> + /// Tries to enters an array node. This will succeed if the next entry is an <see cref="EntryType.StartOfArray" />. + /// <para /> + /// This call MUST (eventually) be followed by a corresponding call to <see cref="IDataReader.ExitArray()" /><para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode" />, <see cref="IDataReader.CurrentNodeName" />, <see cref="IDataReader.CurrentNodeId" /> and <see cref="IDataReader.CurrentNodeDepth" /> properties to the correct values for the current array node. + /// </summary> + /// <param name="length">The length of the array that was entered.</param> + /// <returns> + /// <c>true</c> if an array was entered, otherwise <c>false</c> + /// </returns> + public override bool EnterArray(out long length) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedEntryType == EntryType.StartOfArray) + { + this.PushArray(); + this.MarkEntryContentConsumed(); + + if (this.UNSAFE_Read_8_Int64(out length)) + { + if (length < 0) + { + length = 0; + this.Context.Config.DebugContext.LogError("Invalid array length: " + length + "."); + return false; + } + else return true; + } + else return false; + } + else + { + this.SkipEntry(); + length = 0; + return false; + } + } + + /// <summary> + /// Tries to enter a node. This will succeed if the next entry is an <see cref="EntryType.StartOfNode" />. + /// <para /> + /// This call MUST (eventually) be followed by a corresponding call to <see cref="IDataReader.ExitNode()" /><para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode" />, <see cref="IDataReader.CurrentNodeName" />, <see cref="IDataReader.CurrentNodeId" /> and <see cref="IDataReader.CurrentNodeDepth" /> properties to the correct values for the current node. + /// </summary> + /// <param name="type">The type of the node. This value will be null if there was no metadata, or if the reader's serialization binder failed to resolve the type name.</param> + /// <returns> + /// <c>true</c> if entering a node succeeded, otherwise <c>false</c> + /// </returns> + public override bool EnterNode(out Type type) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedStartOfReferenceNode || this.peekedBinaryEntryType == BinaryEntryType.UnnamedStartOfReferenceNode) + { + this.MarkEntryContentConsumed(); + type = this.ReadTypeEntry(); + int id; + + if (!this.UNSAFE_Read_4_Int32(out id)) + { + type = null; + return false; + } + + this.PushNode(this.peekedEntryName, id, type); + return true; + } + else if (this.peekedBinaryEntryType == BinaryEntryType.NamedStartOfStructNode || this.peekedBinaryEntryType == BinaryEntryType.UnnamedStartOfStructNode) + { + type = this.ReadTypeEntry(); + this.PushNode(this.peekedEntryName, -1, type); + this.MarkEntryContentConsumed(); + return true; + } + else + { + this.SkipEntry(); + type = null; + return false; + } + } + + /// <summary> + /// Exits the closest array. This method will keep skipping entries using <see cref="IDataReader.SkipEntry()" /> until an <see cref="EntryType.EndOfArray" /> is reached, or the end of the stream is reached. + /// <para /> + /// This call MUST have been preceded by a corresponding call to <see cref="IDataReader.EnterArray(out long)" />. + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode" />, <see cref="IDataReader.CurrentNodeName" />, <see cref="IDataReader.CurrentNodeId" /> and <see cref="IDataReader.CurrentNodeDepth" /> to the correct values for the node that was prior to the exited array node. + /// </summary> + /// <returns> + /// <c>true</c> if the method exited an array, <c>false</c> if it reached the end of the stream. + /// </returns> + public override bool ExitArray() + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + while (this.peekedBinaryEntryType != BinaryEntryType.EndOfArray && this.peekedBinaryEntryType != BinaryEntryType.EndOfStream) + { + if (this.peekedEntryType == EntryType.EndOfNode) + { + this.Context.Config.DebugContext.LogError("Data layout mismatch; skipping past node boundary when exiting array."); + this.MarkEntryContentConsumed(); + } + + this.SkipEntry(); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.EndOfArray) + { + this.MarkEntryContentConsumed(); + this.PopArray(); + return true; + } + + return false; + } + + /// <summary> + /// Exits the current node. This method will keep skipping entries using <see cref="IDataReader.SkipEntry()" /> until an <see cref="EntryType.EndOfNode" /> is reached, or the end of the stream is reached. + /// <para /> + /// This call MUST have been preceded by a corresponding call to <see cref="IDataReader.EnterNode(out Type)" />. + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode" />, <see cref="IDataReader.CurrentNodeName" />, <see cref="IDataReader.CurrentNodeId" /> and <see cref="IDataReader.CurrentNodeDepth" /> to the correct values for the node that was prior to the current node. + /// </summary> + /// <returns> + /// <c>true</c> if the method exited a node, <c>false</c> if it reached the end of the stream. + /// </returns> + public override bool ExitNode() + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + while (this.peekedBinaryEntryType != BinaryEntryType.EndOfNode && this.peekedBinaryEntryType != BinaryEntryType.EndOfStream) + { + if (this.peekedEntryType == EntryType.EndOfArray) + { + this.Context.Config.DebugContext.LogError("Data layout mismatch; skipping past array boundary when exiting node."); + this.MarkEntryContentConsumed(); + } + + this.SkipEntry(); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.EndOfNode) + { + this.MarkEntryContentConsumed(); + this.PopNode(this.CurrentNodeName); + return true; + } + + return false; + } + + /// <summary> + /// Reads a primitive array value. This call will succeed if the next entry is an <see cref="EntryType.PrimitiveArray" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)" />.</typeparam> + /// <param name="array">The resulting primitive array.</param> + /// <returns> + /// <c>true</c> if reading a primitive array succeeded, otherwise <c>false</c> + /// </returns> + /// <exception cref="System.ArgumentException">Type + typeof(T).Name + is not a valid primitive array type.</exception> + public override bool ReadPrimitiveArray<T>(out T[] array) + { + if (FormatterUtilities.IsPrimitiveArrayType(typeof(T)) == false) + { + throw new ArgumentException("Type " + typeof(T).Name + " is not a valid primitive array type."); + } + + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedEntryType == EntryType.PrimitiveArray) + { + this.MarkEntryContentConsumed(); + + int elementCount; + int bytesPerElement; + + if (!this.UNSAFE_Read_4_Int32(out elementCount) || !this.UNSAFE_Read_4_Int32(out bytesPerElement)) + { + array = null; + return false; + } + + int byteCount = elementCount * bytesPerElement; + + if (!this.HasBufferData(byteCount)) + { + this.bufferIndex = this.bufferEnd; // We're done! + array = null; + return false; + } + + // Read the actual array content + if (typeof(T) == typeof(byte)) + { + // We can include a special case for byte arrays, as there's no need to copy that to a buffer + var byteArray = new byte[byteCount]; + + Buffer.BlockCopy(this.buffer, this.bufferIndex, byteArray, 0, byteCount); + + array = (T[])(object)byteArray; + + this.bufferIndex += byteCount; + + return true; + } + else + { + array = new T[elementCount]; + + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + if (BitConverter.IsLittleEndian) + { + var toHandle = GCHandle.Alloc(array, GCHandleType.Pinned); + + try + { + fixed (byte* fromBase = this.buffer) + { + void* from = (fromBase + this.bufferIndex); + void* to = toHandle.AddrOfPinnedObject().ToPointer(); + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + } + finally { toHandle.Free(); } + } + else + { + // We have to convert each individual element from bytes, since the byte order has to be reversed + Func<byte[], int, T> fromBytes = (Func<byte[], int, T>)PrimitiveFromByteMethods[typeof(T)]; + + for (int i = 0; i < elementCount; i++) + { + array[i] = fromBytes(this.buffer, this.bufferIndex + i * bytesPerElement); + } + } + + this.bufferIndex += byteCount; + return true; + } + } + else + { + this.SkipEntry(); + array = null; + return false; + } + } + + /// <summary> + /// Reads a <see cref="bool" /> value. This call will succeed if the next entry is an <see cref="EntryType.Boolean" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadBoolean(out bool value) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedEntryType == EntryType.Boolean) + { + this.MarkEntryContentConsumed(); + + if (this.HasBufferData(1)) + { + value = this.buffer[this.bufferIndex++] == 1; + return true; + } + else + { + value = false; + return false; + } + } + else + { + this.SkipEntry(); + value = default(bool); + return false; + } + } + + /// <summary> + /// Reads an <see cref="sbyte" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="sbyte.MinValue" /> or larger than <see cref="sbyte.MaxValue" />, the result will be default(<see cref="sbyte" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadSByte(out sbyte value) + { + long longValue; + if (this.ReadInt64(out longValue)) + { + checked + { + try + { + value = (sbyte)longValue; + } + catch (OverflowException) + { + value = default(sbyte); + } + } + + return true; + } + else + { + value = default(sbyte); + return false; + } + } + + /// <summary> + /// Reads a <see cref="byte" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="byte.MinValue" /> or larger than <see cref="byte.MaxValue" />, the result will be default(<see cref="byte" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadByte(out byte value) + { + ulong ulongValue; + if (this.ReadUInt64(out ulongValue)) + { + checked + { + try + { + value = (byte)ulongValue; + } + catch (OverflowException) + { + value = default(byte); + } + } + + return true; + } + else + { + value = default(byte); + return false; + } + } + + /// <summary> + /// Reads a <see cref="short" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="short.MinValue" /> or larger than <see cref="short.MaxValue" />, the result will be default(<see cref="short" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadInt16(out short value) + { + long longValue; + if (this.ReadInt64(out longValue)) + { + checked + { + try + { + value = (short)longValue; + } + catch (OverflowException) + { + value = default(short); + } + } + + return true; + } + else + { + value = default(short); + return false; + } + } + + /// <summary> + /// Reads an <see cref="ushort" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="ushort.MinValue" /> or larger than <see cref="ushort.MaxValue" />, the result will be default(<see cref="ushort" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadUInt16(out ushort value) + { + ulong ulongValue; + if (this.ReadUInt64(out ulongValue)) + { + checked + { + try + { + value = (ushort)ulongValue; + } + catch (OverflowException) + { + value = default(ushort); + } + } + + return true; + } + else + { + value = default(ushort); + return false; + } + } + + /// <summary> + /// Reads an <see cref="int" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="int.MinValue" /> or larger than <see cref="int.MaxValue" />, the result will be default(<see cref="int" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadInt32(out int value) + { + long longValue; + if (this.ReadInt64(out longValue)) + { + checked + { + try + { + value = (int)longValue; + } + catch (OverflowException) + { + value = default(int); + } + } + + return true; + } + else + { + value = default(int); + return false; + } + } + + /// <summary> + /// Reads an <see cref="uint" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="uint.MinValue" /> or larger than <see cref="uint.MaxValue" />, the result will be default(<see cref="uint" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadUInt32(out uint value) + { + ulong ulongValue; + if (this.ReadUInt64(out ulongValue)) + { + checked + { + try + { + value = (uint)ulongValue; + } + catch (OverflowException) + { + value = default(uint); + } + } + + return true; + } + else + { + value = default(uint); + return false; + } + } + + /// <summary> + /// Reads a <see cref="long" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="long.MinValue" /> or larger than <see cref="long.MaxValue" />, the result will be default(<see cref="long" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadInt64(out long value) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedEntryType == EntryType.Integer) + { + try + { + switch (this.peekedBinaryEntryType) + { + case BinaryEntryType.NamedSByte: + case BinaryEntryType.UnnamedSByte: + sbyte i8; + if (this.UNSAFE_Read_1_SByte(out i8)) + { + value = i8; + } + else + { + value = 0; + return false; + } + break; + case BinaryEntryType.NamedByte: + case BinaryEntryType.UnnamedByte: + byte ui8; + if (this.UNSAFE_Read_1_Byte(out ui8)) + { + value = ui8; + } + else + { + value = 0; + return false; + } + break; + + case BinaryEntryType.NamedShort: + case BinaryEntryType.UnnamedShort: + short i16; + if (this.UNSAFE_Read_2_Int16(out i16)) + { + value = i16; + } + else + { + value = 0; + return false; + } + break; + + case BinaryEntryType.NamedUShort: + case BinaryEntryType.UnnamedUShort: + ushort ui16; + if (this.UNSAFE_Read_2_UInt16(out ui16)) + { + value = ui16; + } + else + { + value = 0; + return false; + } + break; + + case BinaryEntryType.NamedInt: + case BinaryEntryType.UnnamedInt: + int i32; + if (this.UNSAFE_Read_4_Int32(out i32)) + { + value = i32; + } + else + { + value = 0; + return false; + } + break; + + case BinaryEntryType.NamedUInt: + case BinaryEntryType.UnnamedUInt: + uint ui32; + if (this.UNSAFE_Read_4_UInt32(out ui32)) + { + value = ui32; + } + else + { + value = 0; + return false; + } + break; + + case BinaryEntryType.NamedLong: + case BinaryEntryType.UnnamedLong: + if (!this.UNSAFE_Read_8_Int64(out value)) + { + return false; + } + break; + + case BinaryEntryType.NamedULong: + case BinaryEntryType.UnnamedULong: + ulong uint64; + if (this.UNSAFE_Read_8_UInt64(out uint64)) + { + if (uint64 > long.MaxValue) + { + value = 0; + return false; + } + else + { + value = (long)uint64; + } + } + else + { + value = 0; + return false; + } + break; + + default: + throw new InvalidOperationException(); + } + + return true; + } + finally + { + this.MarkEntryContentConsumed(); + } + } + else + { + this.SkipEntry(); + value = default(long); + return false; + } + } + + /// <summary> + /// Reads an <see cref="ulong" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="ulong.MinValue" /> or larger than <see cref="ulong.MaxValue" />, the result will be default(<see cref="ulong" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadUInt64(out ulong value) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedEntryType == EntryType.Integer) + { + try + { + switch (this.peekedBinaryEntryType) + { + case BinaryEntryType.NamedSByte: + case BinaryEntryType.UnnamedSByte: + case BinaryEntryType.NamedByte: + case BinaryEntryType.UnnamedByte: + byte i8; + if (this.UNSAFE_Read_1_Byte(out i8)) + { + value = i8; + } + else + { + value = 0; + return false; + } + break; + + case BinaryEntryType.NamedShort: + case BinaryEntryType.UnnamedShort: + short i16; + if (this.UNSAFE_Read_2_Int16(out i16)) + { + if (i16 >= 0) + { + value = (ulong)i16; + } + else + { + value = 0; + return false; + } + } + else + { + value = 0; + return false; + } + break; + + case BinaryEntryType.NamedUShort: + case BinaryEntryType.UnnamedUShort: + ushort ui16; + if (this.UNSAFE_Read_2_UInt16(out ui16)) + { + value = ui16; + } + else + { + value = 0; + return false; + } + break; + + case BinaryEntryType.NamedInt: + case BinaryEntryType.UnnamedInt: + int i32; + if (this.UNSAFE_Read_4_Int32(out i32)) + { + if (i32 >= 0) + { + value = (ulong)i32; + } + else + { + value = 0; + return false; + } + } + else + { + value = 0; + return false; + } + break; + + case BinaryEntryType.NamedUInt: + case BinaryEntryType.UnnamedUInt: + uint ui32; + if (this.UNSAFE_Read_4_UInt32(out ui32)) + { + value = ui32; + } + else + { + value = 0; + return false; + } + break; + + case BinaryEntryType.NamedLong: + case BinaryEntryType.UnnamedLong: + long i64; + if (this.UNSAFE_Read_8_Int64(out i64)) + { + if (i64 >= 0) + { + value = (ulong)i64; + } + else + { + value = 0; + return false; + } + } + else + { + value = 0; + return false; + } + break; + + case BinaryEntryType.NamedULong: + case BinaryEntryType.UnnamedULong: + if (!this.UNSAFE_Read_8_UInt64(out value)) + { + return false; + } + break; + + default: + throw new InvalidOperationException(); + } + + return true; + } + finally + { + this.MarkEntryContentConsumed(); + } + } + else + { + this.SkipEntry(); + value = default(ulong); + return false; + } + } + + /// <summary> + /// Reads a <see cref="char" /> value. This call will succeed if the next entry is an <see cref="EntryType.String" />. + /// <para /> + /// If the string of the entry is longer than 1 character, the first character of the string will be taken as the result. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadChar(out char value) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedChar || this.peekedBinaryEntryType == BinaryEntryType.UnnamedChar) + { + this.MarkEntryContentConsumed(); + return this.UNSAFE_Read_2_Char(out value); + } + else if (this.peekedBinaryEntryType == BinaryEntryType.NamedString || this.peekedBinaryEntryType == BinaryEntryType.UnnamedString) + { + this.MarkEntryContentConsumed(); + var str = this.ReadStringValue(); + + if (str == null || str.Length == 0) + { + value = default(char); + return false; + } + else + { + value = str[0]; + return true; + } + } + else + { + this.SkipEntry(); + value = default(char); + return false; + } + } + + /// <summary> + /// Reads a <see cref="float" /> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint" /> or an <see cref="EntryType.Integer" />. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="float.MinValue" /> or larger than <see cref="float.MaxValue" />, the result will be default(<see cref="float" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadSingle(out float value) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedFloat || this.peekedBinaryEntryType == BinaryEntryType.UnnamedFloat) + { + this.MarkEntryContentConsumed(); + return this.UNSAFE_Read_4_Float32(out value); + } + else if (this.peekedBinaryEntryType == BinaryEntryType.NamedDouble || this.peekedBinaryEntryType == BinaryEntryType.UnnamedDouble) + { + this.MarkEntryContentConsumed(); + + double d; + if (!this.UNSAFE_Read_8_Float64(out d)) + { + value = 0; + return false; + } + + try + { + checked + { + value = (float)d; + } + } + catch (OverflowException) + { + value = default(float); + } + + return true; + } + else if (this.peekedBinaryEntryType == BinaryEntryType.NamedDecimal || this.peekedBinaryEntryType == BinaryEntryType.UnnamedDecimal) + { + this.MarkEntryContentConsumed(); + + decimal d; + if (!this.UNSAFE_Read_16_Decimal(out d)) + { + value = 0; + return false; + } + + try + { + checked + { + value = (float)d; + } + } + catch (OverflowException) + { + value = default(float); + } + + return true; + } + else if (this.peekedEntryType == EntryType.Integer) + { + long val; + if (!this.ReadInt64(out val)) + { + value = 0; + return false; + } + + try + { + checked + { + value = val; + } + } + catch (OverflowException) + { + value = default(float); + } + + return true; + } + else + { + this.SkipEntry(); + value = default(float); + return false; + } + } + + /// <summary> + /// Reads a <see cref="double" /> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint" /> or an <see cref="EntryType.Integer" />. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="double.MinValue" /> or larger than <see cref="double.MaxValue" />, the result will be default(<see cref="double" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadDouble(out double value) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedDouble || this.peekedBinaryEntryType == BinaryEntryType.UnnamedDouble) + { + this.MarkEntryContentConsumed(); + return this.UNSAFE_Read_8_Float64(out value); + } + else if (this.peekedBinaryEntryType == BinaryEntryType.NamedFloat || this.peekedBinaryEntryType == BinaryEntryType.UnnamedFloat) + { + this.MarkEntryContentConsumed(); + + float s; + if (!this.UNSAFE_Read_4_Float32(out s)) + { + value = 0; + return false; + } + + value = s; + return true; + } + else if (this.peekedBinaryEntryType == BinaryEntryType.NamedDecimal || this.peekedBinaryEntryType == BinaryEntryType.UnnamedDecimal) + { + this.MarkEntryContentConsumed(); + + decimal d; + if (!this.UNSAFE_Read_16_Decimal(out d)) + { + value = 0; + return false; + } + + try + { + checked + { + value = (double)d; + } + } + catch (OverflowException) + { + value = 0; + } + + return true; + } + else if (this.peekedEntryType == EntryType.Integer) + { + long val; + if (!this.ReadInt64(out val)) + { + value = 0; + return false; + } + + try + { + checked + { + value = val; + } + } + catch (OverflowException) + { + value = 0; + } + + return true; + } + else + { + this.SkipEntry(); + value = default(double); + return false; + } + } + + /// <summary> + /// Reads a <see cref="decimal" /> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint" /> or an <see cref="EntryType.Integer" />. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="decimal.MinValue" /> or larger than <see cref="decimal.MaxValue" />, the result will be default(<see cref="decimal" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadDecimal(out decimal value) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedDecimal || this.peekedBinaryEntryType == BinaryEntryType.UnnamedDecimal) + { + this.MarkEntryContentConsumed(); + return this.UNSAFE_Read_16_Decimal(out value); + } + else if (this.peekedBinaryEntryType == BinaryEntryType.NamedDouble || this.peekedBinaryEntryType == BinaryEntryType.UnnamedDouble) + { + this.MarkEntryContentConsumed(); + + double d; + if (!this.UNSAFE_Read_8_Float64(out d)) + { + value = 0; + return false; + } + + try + { + checked + { + value = (decimal)d; + } + } + catch (OverflowException) + { + value = default(decimal); + } + + return true; + } + else if (this.peekedBinaryEntryType == BinaryEntryType.NamedFloat || this.peekedBinaryEntryType == BinaryEntryType.UnnamedFloat) + { + this.MarkEntryContentConsumed(); + + float f; + if (!this.UNSAFE_Read_4_Float32(out f)) + { + value = 0; + return false; + } + + try + { + checked + { + value = (decimal)f; + } + } + catch (OverflowException) + { + value = default(decimal); + } + + return true; + } + else if (this.peekedEntryType == EntryType.Integer) + { + long val; + if (!this.ReadInt64(out val)) + { + value = 0; + return false; + } + + try + { + checked + { + value = val; + } + } + catch (OverflowException) + { + value = default(decimal); + } + + return true; + } + else + { + this.SkipEntry(); + value = default(decimal); + return false; + } + } + + /// <summary> + /// Reads an external reference guid. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByGuid" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="guid">The external reference guid.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadExternalReference(out Guid guid) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedExternalReferenceByGuid || this.peekedBinaryEntryType == BinaryEntryType.UnnamedExternalReferenceByGuid) + { + this.MarkEntryContentConsumed(); + return this.UNSAFE_Read_16_Guid(out guid); + } + else + { + this.SkipEntry(); + guid = default(Guid); + return false; + } + } + + /// <summary> + /// Reads a <see cref="Guid" /> value. This call will succeed if the next entry is an <see cref="EntryType.Guid" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadGuid(out Guid value) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedGuid || this.peekedBinaryEntryType == BinaryEntryType.UnnamedGuid) + { + this.MarkEntryContentConsumed(); + return this.UNSAFE_Read_16_Guid(out value); + } + else + { + this.SkipEntry(); + value = default(Guid); + return false; + } + } + + /// <summary> + /// Reads an external reference index. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByIndex" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="index">The external reference index.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadExternalReference(out int index) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedExternalReferenceByIndex || this.peekedBinaryEntryType == BinaryEntryType.UnnamedExternalReferenceByIndex) + { + this.MarkEntryContentConsumed(); + return this.UNSAFE_Read_4_Int32(out index); + } + else + { + this.SkipEntry(); + index = -1; + return false; + } + } + + /// <summary> + /// Reads an external reference string. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByString" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="id">The external reference string.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadExternalReference(out string id) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedExternalReferenceByString || this.peekedBinaryEntryType == BinaryEntryType.UnnamedExternalReferenceByString) + { + id = this.ReadStringValue(); + this.MarkEntryContentConsumed(); + return id != null; + } + else + { + this.SkipEntry(); + id = null; + return false; + } + } + + /// <summary> + /// Reads a <c>null</c> value. This call will succeed if the next entry is an <see cref="EntryType.Null" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadNull() + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedNull || this.peekedBinaryEntryType == BinaryEntryType.UnnamedNull) + { + this.MarkEntryContentConsumed(); + return true; + } + else + { + this.SkipEntry(); + return false; + } + } + + /// <summary> + /// Reads an internal reference id. This call will succeed if the next entry is an <see cref="EntryType.InternalReference" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="id">The internal reference id.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadInternalReference(out int id) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedInternalReference || this.peekedBinaryEntryType == BinaryEntryType.UnnamedInternalReference) + { + this.MarkEntryContentConsumed(); + return this.UNSAFE_Read_4_Int32(out id); + } + else + { + this.SkipEntry(); + id = -1; + return false; + } + } + + /// <summary> + /// Reads a <see cref="string" /> value. This call will succeed if the next entry is an <see cref="EntryType.String" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadString(out string value) + { + if (!this.peekedEntryType.HasValue) + { + string name; + this.PeekEntry(out name); + } + + if (this.peekedBinaryEntryType == BinaryEntryType.NamedString || this.peekedBinaryEntryType == BinaryEntryType.UnnamedString) + { + value = this.ReadStringValue(); + this.MarkEntryContentConsumed(); + return value != null; + } + else + { + this.SkipEntry(); + value = null; + return false; + } + } + + /// <summary> + /// Tells the reader that a new serialization session is about to begin, and that it should clear all cached values left over from any prior serialization sessions. + /// This method is only relevant when the same reader is used to deserialize several different, unrelated values. + /// </summary> + public override void PrepareNewSerializationSession() + { + base.PrepareNewSerializationSession(); + this.peekedEntryType = null; + this.peekedEntryName = null; + this.peekedBinaryEntryType = BinaryEntryType.Invalid; + this.types.Clear(); + this.bufferIndex = 0; + this.bufferEnd = 0; + this.buffer = this.internalBufferBackup; + } + + public override string GetDataDump() + { + byte[] bytes; + + if (this.bufferEnd == this.buffer.Length) + { + bytes = this.buffer; + } + else + { + bytes = new byte[this.bufferEnd]; + + fixed (void* from = this.buffer) + fixed (void* to = bytes) + { + UnsafeUtilities.MemoryCopy(from, to, bytes.Length); + } + } + + return "Binary hex dump: " + ProperBitConverter.BytesToHexString(bytes); + } + + private struct Struct256Bit + { + public decimal d1; + public decimal d2; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private string ReadStringValue() + { + byte charSizeFlag; + + if (!this.UNSAFE_Read_1_Byte(out charSizeFlag)) + { + return null; + } + + int length; + + if (!this.UNSAFE_Read_4_Int32(out length)) + { + return null; + } + + string str = new string('\0', length); + + if (charSizeFlag == 0) + { + // 8 bit + + fixed (byte* baseFromPtr = this.buffer) + fixed (char* baseToPtr = str) + { + byte* fromPtr = baseFromPtr + this.bufferIndex; + byte* toPtr = (byte*)baseToPtr; + + if (BitConverter.IsLittleEndian) + { + for (int i = 0; i < length; i++) + { + *toPtr++ = *fromPtr++; + toPtr++; // Skip every other string byte + } + } + else + { + for (int i = 0; i < length; i++) + { + toPtr++; // Skip every other string byte + *toPtr++ = *fromPtr++; + } + } + } + + this.bufferIndex += length; + return str; + } + else + { + // 16 bit + int bytes = length * 2; + + fixed (byte* baseFromPtr = this.buffer) + fixed (char* baseToPtr = str) + { + if (BitConverter.IsLittleEndian) + { + Struct256Bit* fromLargePtr = (Struct256Bit*)(baseFromPtr + this.bufferIndex); + Struct256Bit* toLargePtr = (Struct256Bit*)baseToPtr; + + byte* end = (byte*)baseToPtr + bytes; + + while ((toLargePtr + 1) < end) + { + *toLargePtr++ = *fromLargePtr++; + } + + byte* fromSmallPtr = (byte*)fromLargePtr; + byte* toSmallPtr = (byte*)toLargePtr; + + while (toSmallPtr < end) + { + *toSmallPtr++ = *fromSmallPtr++; + } + } + else + { + byte* fromPtr = baseFromPtr + this.bufferIndex; + byte* toPtr = (byte*)baseToPtr; + + for (int i = 0; i < length; i++) + { + *toPtr = *(fromPtr + 1); + *(toPtr + 1) = *fromPtr; + + fromPtr += 2; + toPtr += 2; + } + } + } + + this.bufferIndex += bytes; + return str; + } + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void SkipStringValue() + { + byte charSizeFlag; + + if (!this.UNSAFE_Read_1_Byte(out charSizeFlag)) + { + return; + } + + int skipBytes; + + if (!this.UNSAFE_Read_4_Int32(out skipBytes)) + { + return; + } + + if (charSizeFlag != 0) + { + skipBytes *= 2; + } + + if (this.HasBufferData(skipBytes)) + { + this.bufferIndex += skipBytes; + } + else + { + this.bufferIndex = this.bufferEnd; + } + } + + private void SkipPeekedEntryContent() + { + if (this.peekedEntryType != null) + { + try + { + switch (this.peekedBinaryEntryType) + { + case BinaryEntryType.NamedStartOfReferenceNode: + case BinaryEntryType.UnnamedStartOfReferenceNode: + this.ReadTypeEntry(); // Never actually skip type entries; they might contain type ids that we'll need later + if (!this.SkipBuffer(4)) return; // Skip reference id int + break; + + case BinaryEntryType.NamedStartOfStructNode: + case BinaryEntryType.UnnamedStartOfStructNode: + this.ReadTypeEntry(); // Never actually skip type entries; they might contain type ids that we'll need later + break; + + case BinaryEntryType.StartOfArray: + // Skip length long + this.SkipBuffer(8); + + break; + + case BinaryEntryType.PrimitiveArray: + // We must skip the whole entry array content + int elements; + int bytesPerElement; + + if (!this.UNSAFE_Read_4_Int32(out elements) || !this.UNSAFE_Read_4_Int32(out bytesPerElement)) + { + return; + } + + this.SkipBuffer(elements * bytesPerElement); + break; + + case BinaryEntryType.NamedSByte: + case BinaryEntryType.UnnamedSByte: + case BinaryEntryType.NamedByte: + case BinaryEntryType.UnnamedByte: + case BinaryEntryType.NamedBoolean: + case BinaryEntryType.UnnamedBoolean: + this.SkipBuffer(1); + break; + + case BinaryEntryType.NamedChar: + case BinaryEntryType.UnnamedChar: + case BinaryEntryType.NamedShort: + case BinaryEntryType.UnnamedShort: + case BinaryEntryType.NamedUShort: + case BinaryEntryType.UnnamedUShort: + this.SkipBuffer(2); + break; + + case BinaryEntryType.NamedInternalReference: + case BinaryEntryType.UnnamedInternalReference: + case BinaryEntryType.NamedInt: + case BinaryEntryType.UnnamedInt: + case BinaryEntryType.NamedUInt: + case BinaryEntryType.UnnamedUInt: + case BinaryEntryType.NamedExternalReferenceByIndex: + case BinaryEntryType.UnnamedExternalReferenceByIndex: + case BinaryEntryType.NamedFloat: + case BinaryEntryType.UnnamedFloat: + this.SkipBuffer(4); + break; + + case BinaryEntryType.NamedLong: + case BinaryEntryType.UnnamedLong: + case BinaryEntryType.NamedULong: + case BinaryEntryType.UnnamedULong: + case BinaryEntryType.NamedDouble: + case BinaryEntryType.UnnamedDouble: + this.SkipBuffer(8); + break; + + case BinaryEntryType.NamedGuid: + case BinaryEntryType.UnnamedGuid: + case BinaryEntryType.NamedExternalReferenceByGuid: + case BinaryEntryType.UnnamedExternalReferenceByGuid: + case BinaryEntryType.NamedDecimal: + case BinaryEntryType.UnnamedDecimal: + this.SkipBuffer(8); + break; + + case BinaryEntryType.NamedString: + case BinaryEntryType.UnnamedString: + case BinaryEntryType.NamedExternalReferenceByString: + case BinaryEntryType.UnnamedExternalReferenceByString: + this.SkipStringValue(); + break; + + case BinaryEntryType.TypeName: + this.Context.Config.DebugContext.LogError("Parsing error in binary data reader: should not be able to peek a TypeName entry."); + this.SkipBuffer(4); + this.ReadStringValue(); + break; + + case BinaryEntryType.TypeID: + this.Context.Config.DebugContext.LogError("Parsing error in binary data reader: should not be able to peek a TypeID entry."); + this.SkipBuffer(4); + break; + + case BinaryEntryType.EndOfArray: + case BinaryEntryType.EndOfNode: + case BinaryEntryType.NamedNull: + case BinaryEntryType.UnnamedNull: + case BinaryEntryType.EndOfStream: + case BinaryEntryType.Invalid: + default: + // Skip nothing - there is no content to skip + break; + } + } + finally + { + this.MarkEntryContentConsumed(); + } + } + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool SkipBuffer(int amount) + { + int newIndex = this.bufferIndex + amount; + + if (newIndex > this.bufferEnd) + { + this.bufferIndex = this.bufferEnd; + return false; + } + + this.bufferIndex = newIndex; + return true; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private Type ReadTypeEntry() + { + if (!this.HasBufferData(1)) + return null; + + BinaryEntryType entryType = (BinaryEntryType)this.buffer[this.bufferIndex++]; + + Type type; + int id; + + if (entryType == BinaryEntryType.TypeID) + { + if (!this.UNSAFE_Read_4_Int32(out id)) + { + return null; + } + + if (this.types.TryGetValue(id, out type) == false) + { + this.Context.Config.DebugContext.LogError("Missing type ID during deserialization: " + id + " at node " + this.CurrentNodeName + " and depth " + this.CurrentNodeDepth + " and id " + this.CurrentNodeId); + } + } + else if (entryType == BinaryEntryType.TypeName) + { + if (!this.UNSAFE_Read_4_Int32(out id)) + { + return null; + } + + string name = this.ReadStringValue(); + type = name == null ? null : this.Context.Binder.BindToType(name, this.Context.Config.DebugContext); + this.types.Add(id, type); + } + else if (entryType == BinaryEntryType.UnnamedNull) + { + type = null; + } + else + { + type = null; + this.Context.Config.DebugContext.LogError("Expected TypeName, TypeID or UnnamedNull entry flag for reading type data, but instead got the entry flag: " + entryType + "."); + } + + return type; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void MarkEntryContentConsumed() + { + this.peekedEntryType = null; + this.peekedEntryName = null; + this.peekedBinaryEntryType = BinaryEntryType.Invalid; + } + + /// <summary> + /// Peeks the current entry. + /// </summary> + /// <returns>The peeked entry.</returns> + protected override EntryType PeekEntry() + { + string name; + return this.PeekEntry(out name); + } + + /// <summary> + /// Consumes the current entry, and reads to the next one. + /// </summary> + /// <returns>The next entry.</returns> + protected override EntryType ReadToNextEntry() + { + string name; + this.SkipPeekedEntryContent(); + return this.PeekEntry(out name); + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_1_Byte(out byte value) + { + if (this.HasBufferData(1)) + { + value = this.buffer[this.bufferIndex++]; + return true; + } + + value = 0; + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_1_SByte(out sbyte value) + { + if (this.HasBufferData(1)) + { + unchecked + { + value = (sbyte)this.buffer[this.bufferIndex++]; + } + + return true; + } + + value = 0; + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_2_Int16(out short value) + { + if (this.HasBufferData(2)) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + value = *((short*)(basePtr + this.bufferIndex)); + } + else + { + short val = 0; + byte* toPtr = (byte*)&val + 1; + byte* fromPtr = basePtr + this.bufferIndex; + + *toPtr-- = *fromPtr++; + *toPtr = *fromPtr; + + value = val; + } + } + + this.bufferIndex += 2; + return true; + } + + this.bufferIndex = this.bufferEnd; + value = 0; + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_2_UInt16(out ushort value) + { + if (this.HasBufferData(2)) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + value = *((ushort*)(basePtr + this.bufferIndex)); + } + else + { + ushort val = 0; + byte* toPtr = (byte*)&val + 1; + byte* fromPtr = basePtr + this.bufferIndex; + + *toPtr-- = *fromPtr++; + *toPtr = *fromPtr; + + value = val; + } + } + + this.bufferIndex += 2; + return true; + } + + this.bufferIndex = this.bufferEnd; + value = 0; + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_2_Char(out char value) + { + if (this.HasBufferData(2)) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + value = *((char*)(basePtr + this.bufferIndex)); + } + else + { + char val = default(char); + byte* toPtr = (byte*)&val + 1; + byte* fromPtr = basePtr + this.bufferIndex; + + *toPtr-- = *fromPtr++; + *toPtr = *fromPtr; + + value = val; + } + } + + this.bufferIndex += 2; + return true; + } + + this.bufferIndex = this.bufferEnd; + value = default(char); + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_4_Int32(out int value) + { + if (this.HasBufferData(4)) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + value = *((int*)(basePtr + this.bufferIndex)); + } + else + { + int val = 0; + byte* toPtr = (byte*)&val + 3; + byte* fromPtr = basePtr + this.bufferIndex; + + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr = *fromPtr; + + value = val; + } + } + + this.bufferIndex += 4; + return true; + } + + this.bufferIndex = this.bufferEnd; + value = 0; + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_4_UInt32(out uint value) + { + if (this.HasBufferData(4)) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + value = *((uint*)(basePtr + this.bufferIndex)); + } + else + { + uint val = 0; + byte* toPtr = (byte*)&val + 3; + byte* fromPtr = basePtr + this.bufferIndex; + + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr = *fromPtr; + + value = val; + } + } + + this.bufferIndex += 4; + return true; + } + + this.bufferIndex = this.bufferEnd; + value = 0; + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_4_Float32(out float value) + { + if (this.HasBufferData(4)) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_Unaligned_Float32_Reads) + { + // We can read directly from the buffer, safe in the knowledge that any potential unaligned reads will work + value = *((float*)(basePtr + this.bufferIndex)); + } + else + { + // We do a read through a 32-bit int and a locally addressed float instead, should be almost as fast as the real deal + float result = 0; + *(int*)&result = *(int*)(basePtr + this.bufferIndex); + value = result; + } + } + else + { + float val = 0; + byte* toPtr = (byte*)&val + 3; + byte* fromPtr = basePtr + this.bufferIndex; + + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr = *fromPtr; + + value = val; + } + } + + this.bufferIndex += 4; + return true; + } + + this.bufferIndex = this.bufferEnd; + value = 0; + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_8_Int64(out long value) + { + if (this.HasBufferData(8)) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_All_Unaligned_ReadWrites) + { + // We can read directly from the buffer, safe in the knowledge that any potential unaligned reads will work + value = *((long*)(basePtr + this.bufferIndex)); + } + else + { + // We do an int-by-int read instead, into an address that we know is aligned + long result = 0; + int* toPtr = (int*)&result; + int* fromPtr = (int*)(basePtr + this.bufferIndex); + + *toPtr++ = *fromPtr++; + *toPtr = *fromPtr; + + value = result; + } + } + else + { + long val = 0; + byte* toPtr = (byte*)&val + 7; + byte* fromPtr = basePtr + this.bufferIndex; + + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr = *fromPtr; + + value = val; + } + } + + this.bufferIndex += 8; + return true; + } + + this.bufferIndex = this.bufferEnd; + value = 0; + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_8_UInt64(out ulong value) + { + if (this.HasBufferData(8)) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_All_Unaligned_ReadWrites) + { + // We can read directly from the buffer, safe in the knowledge that any potential unaligned reads will work + value = *((ulong*)(basePtr + this.bufferIndex)); + } + else + { + // We do an int-by-int read instead, into an address that we know is aligned + ulong result = 0; + + int* toPtr = (int*)&result; + int* fromPtr = (int*)(basePtr + this.bufferIndex); + + *toPtr++ = *fromPtr++; + *toPtr = *fromPtr; + + value = result; + } + } + else + { + ulong val = 0; + byte* toPtr = (byte*)&val + 7; + byte* fromPtr = basePtr + this.bufferIndex; + + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr = *fromPtr; + + value = val; + } + } + + this.bufferIndex += 8; + return true; + } + + this.bufferIndex = this.bufferEnd; + value = 0; + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_8_Float64(out double value) + { + if (this.HasBufferData(8)) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_All_Unaligned_ReadWrites) + { + // We can read directly from the buffer, safe in the knowledge that any potential unaligned reads will work + value = *((double*)(basePtr + this.bufferIndex)); + } + else + { + // We do an int-by-int read instead, into an address that we know is aligned + double result = 0; + + int* toPtr = (int*)&result; + int* fromPtr = (int*)(basePtr + this.bufferIndex); + + *toPtr++ = *fromPtr++; + *toPtr = *fromPtr; + + value = result; + } + } + else + { + double val = 0; + byte* toPtr = (byte*)&val + 7; + byte* fromPtr = basePtr + this.bufferIndex; + + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr = *fromPtr; + + value = val; + } + } + + this.bufferIndex += 8; + return true; + } + + this.bufferIndex = this.bufferEnd; + value = 0; + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_16_Decimal(out decimal value) + { + if (this.HasBufferData(16)) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_All_Unaligned_ReadWrites) + { + // We can read directly from the buffer, safe in the knowledge that any potential unaligned reads will work + value = *((decimal*)(basePtr + this.bufferIndex)); + } + else + { + // We do an int-by-int read instead, into an address that we know is aligned + decimal result = 0; + + int* toPtr = (int*)&result; + int* fromPtr = (int*)(basePtr + this.bufferIndex); + + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr = *fromPtr; + + value = result; + } + } + else + { + decimal val = 0; + byte* toPtr = (byte*)&val + 15; + byte* fromPtr = basePtr + this.bufferIndex; + + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr = *fromPtr; + + value = val; + } + } + + this.bufferIndex += 16; + return true; + } + + this.bufferIndex = this.bufferEnd; + value = 0; + return false; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool UNSAFE_Read_16_Guid(out Guid value) + { + if (this.HasBufferData(16)) + { + // First 10 bytes of a guid are always little endian + // Last 6 bytes depend on architecture endianness + // See http://stackoverflow.com/questions/10190817/guid-byte-order-in-net + + // TODO: Test if this actually works on big-endian architecture. Where the hell do we find that? + + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_All_Unaligned_ReadWrites) + { + // We can read directly from the buffer, safe in the knowledge that any potential unaligned reads will work + value = *((Guid*)(basePtr + this.bufferIndex)); + } + else + { + // We do an int-by-int read instead, into an address that we know is aligned + Guid result = default(Guid); + + int* toPtr = (int*)&result; + int* fromPtr = (int*)(basePtr + this.bufferIndex); + + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr = *fromPtr; + + value = result; + } + } + else + { + Guid val = default(Guid); + byte* toPtr = (byte*)&val; + byte* fromPtr = basePtr + this.bufferIndex; + + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr = *fromPtr++; + + toPtr += 6; + + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr-- = *fromPtr++; + *toPtr = *fromPtr; + + value = val; + } + } + + this.bufferIndex += 16; + return true; + } + + this.bufferIndex = this.bufferEnd; + value = default(Guid); + return false; + } + + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool HasBufferData(int amount) + { + if (this.bufferEnd == 0) + { + this.ReadEntireStreamToBuffer(); + } + + return this.bufferIndex + amount <= this.bufferEnd; + } + + private void ReadEntireStreamToBuffer() + { + this.bufferIndex = 0; + + if (this.Stream is MemoryStream) + { + // We can do a small trick and just steal the memory stream's internal buffer + // and totally avoid copying from the stream's internal buffer that way. + // + // This is pretty great, since most of the time we will be deserializing from + // a memory stream. + + try + { + this.buffer = (this.Stream as MemoryStream).GetBuffer(); + this.bufferEnd = (int)this.Stream.Length; + this.bufferIndex = (int)this.Stream.Position; + return; + } + catch (UnauthorizedAccessException) + { + // Sometimes we're not actually allowed to get the internal buffer + // in that case, we can just copy from the stream as we normally do. + } + } + + this.buffer = this.internalBufferBackup; + + int remainder = (int)(this.Stream.Length - this.Stream.Position); + + if (this.buffer.Length >= remainder) + { + this.Stream.Read(this.buffer, 0, remainder); + } + else + { + this.buffer = new byte[remainder]; + this.Stream.Read(this.buffer, 0, remainder); + + if (remainder <= 1024 * 1024 * 10) + { + // We've made a larger buffer - might as well keep that, so long as it's not too ridiculously big (>10 MB) + // We don't want to be too much of a memory hog - at least there will usually only be one reader instance + // instantiated, ever. + this.internalBufferBackup = this.buffer; + } + } + + this.bufferIndex = 0; + this.bufferEnd = remainder; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataReader.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataReader.cs.meta new file mode 100644 index 00000000..0bb871a5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee0465a1838833eb878447b34339d4f4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataWriter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataWriter.cs new file mode 100644 index 00000000..62f4f190 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataWriter.cs @@ -0,0 +1,2202 @@ +//----------------------------------------------------------------------- +// <copyright file="BinaryDataWriter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using VRC.Udon.Serialization.OdinSerializer.Utilities; + using VRC.Udon.Serialization.OdinSerializer.Utilities.Unsafe; + using System; + using System.Collections.Generic; + using System.IO; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + +#pragma warning disable 0649 // Field is never assigned to and will have its default value // VRC + + /// <summary> + /// Writes data to a stream that can be read by a <see cref="BinaryDataReader"/>. + /// </summary> + /// <seealso cref="BaseDataWriter" /> + public unsafe class BinaryDataWriter : BaseDataWriter + { + private static readonly Dictionary<Type, Delegate> PrimitiveGetBytesMethods = new Dictionary<Type, Delegate>(FastTypeComparer.Instance) + { + { typeof(char), (Action<byte[], int, char>) ((byte[] b, int i, char v) => { ProperBitConverter.GetBytes(b, i, (ushort)v); }) }, + { typeof(byte), (Action<byte[], int, byte>) ((b, i, v) => { b[i] = v; }) }, + { typeof(sbyte), (Action<byte[], int, sbyte>) ((b, i, v) => { b[i] = (byte)v; }) }, + { typeof(bool), (Action<byte[], int, bool>) ((b, i, v) => { b[i] = v ? (byte)1 : (byte)0; }) }, + { typeof(short), (Action<byte[], int, short>) ProperBitConverter.GetBytes }, + { typeof(int), (Action<byte[], int, int>) ProperBitConverter.GetBytes }, + { typeof(long), (Action<byte[], int, long>) ProperBitConverter.GetBytes }, + { typeof(ushort), (Action<byte[], int, ushort>) ProperBitConverter.GetBytes }, + { typeof(uint), (Action<byte[], int, uint>) ProperBitConverter.GetBytes }, + { typeof(ulong), (Action<byte[], int, ulong>) ProperBitConverter.GetBytes }, + { typeof(decimal), (Action<byte[], int, decimal>) ProperBitConverter.GetBytes }, + { typeof(float), (Action<byte[], int, float>) ProperBitConverter.GetBytes }, + { typeof(double), (Action<byte[], int, double>) ProperBitConverter.GetBytes }, + { typeof(Guid), (Action<byte[], int, Guid>) ProperBitConverter.GetBytes } + }; + + private static readonly Dictionary<Type, int> PrimitiveSizes = new Dictionary<Type, int>(FastTypeComparer.Instance) + { + { typeof(char), 2 }, + { typeof(byte), 1 }, + { typeof(sbyte), 1 }, + { typeof(bool), 1 }, + { typeof(short), 2 }, + { typeof(int), 4 }, + { typeof(long), 8 }, + { typeof(ushort), 2 }, + { typeof(uint), 4 }, + { typeof(ulong), 8 }, + { typeof(decimal), 16 }, + { typeof(float), 4 }, + { typeof(double), 8 }, + { typeof(Guid), 16 } + }; + + // For byte caching while writing values up to sizeof(decimal) using the old ProperBitConverter method + // (still occasionally used) and to provide a permanent buffer to read into. + private readonly byte[] small_buffer = new byte[16]; + private readonly byte[] buffer = new byte[1024 * 100]; // 100 Kb buffer should be enough for most things, and enough to prevent flushing to stream too often + private int bufferIndex = 0; + + // A dictionary over all seen types, so short type ids can be written after a type's full name has already been written to the stream once + private readonly Dictionary<Type, int> types = new Dictionary<Type, int>(16, FastTypeComparer.Instance); + + public bool CompressStringsTo8BitWhenPossible = false; + + public BinaryDataWriter() : base(null, null) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="BinaryDataWriter" /> class. + /// </summary> + /// <param name="stream">The base stream of the writer.</param> + /// <param name="context">The serialization context to use.</param> + public BinaryDataWriter(Stream stream, SerializationContext context) : base(stream, context) + { + } + + /// <summary> + /// Begins an array node of the given length. + /// </summary> + /// <param name="length">The length of the array to come.</param> + public override void BeginArrayNode(long length) + { + this.EnsureBufferSpace(9); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.StartOfArray; + this.UNSAFE_WriteToBuffer_8_Int64(length); + this.PushArray(); + } + + /// <summary> + /// Writes the beginning of a reference node. + /// <para /> + /// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)" />, with the same name. + /// </summary> + /// <param name="name">The name of the reference node.</param> + /// <param name="type">The type of the reference node. If null, no type metadata will be written.</param> + /// <param name="id">The id of the reference node. This id is acquired by calling <see cref="SerializationContext.TryRegisterInternalReference(object, out int)" />.</param> + public override void BeginReferenceNode(string name, Type type, int id) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedStartOfReferenceNode; + this.WriteStringFast(name); + } + else + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedStartOfReferenceNode; + } + + this.WriteType(type); + this.EnsureBufferSpace(4); + this.UNSAFE_WriteToBuffer_4_Int32(id); + this.PushNode(name, id, type); + } + + /// <summary> + /// Begins a struct/value type node. This is essentially the same as a reference node, except it has no internal reference id. + /// <para /> + /// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)" />, with the same name. + /// </summary> + /// <param name="name">The name of the struct node.</param> + /// <param name="type">The type of the struct node. If null, no type metadata will be written.</param> + public override void BeginStructNode(string name, Type type) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedStartOfStructNode; + this.WriteStringFast(name); + } + else + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedStartOfStructNode; + } + + this.WriteType(type); + this.PushNode(name, -1, type); + } + + /// <summary> + /// Disposes all resources kept by the data writer, except the stream, which can be reused later. + /// </summary> + public override void Dispose() + { + //this.Stream.Dispose(); + this.FlushToStream(); + } + + /// <summary> + /// Ends the current array node, if the current node is an array node. + /// </summary> + public override void EndArrayNode() + { + this.PopArray(); + + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.EndOfArray; + } + + /// <summary> + /// Ends the current node with the given name. If the current node has another name, an <see cref="InvalidOperationException" /> is thrown. + /// </summary> + /// <param name="name">The name of the node to end. This has to be the name of the current node.</param> + public override void EndNode(string name) + { + this.PopNode(name); + + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.EndOfNode; + } + + private static readonly Dictionary<Type, Action<BinaryDataWriter, object>> PrimitiveArrayWriters = new Dictionary<Type, Action<BinaryDataWriter, object>>(FastTypeComparer.Instance) + { + { typeof(char), WritePrimitiveArray_char }, + { typeof(sbyte), WritePrimitiveArray_sbyte }, + { typeof(short), WritePrimitiveArray_short }, + { typeof(int), WritePrimitiveArray_int }, + { typeof(long), WritePrimitiveArray_long }, + { typeof(byte), WritePrimitiveArray_byte }, + { typeof(ushort), WritePrimitiveArray_ushort }, + { typeof(uint), WritePrimitiveArray_uint }, + { typeof(ulong), WritePrimitiveArray_ulong }, + { typeof(decimal), WritePrimitiveArray_decimal }, + { typeof(bool), WritePrimitiveArray_bool }, + { typeof(float), WritePrimitiveArray_float }, + { typeof(double), WritePrimitiveArray_double }, + { typeof(Guid), WritePrimitiveArray_Guid }, + }; + + private static void WritePrimitiveArray_byte(BinaryDataWriter writer, object o) + { + byte[] array = o as byte[]; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(1); + + // We can include a special case for byte arrays, as there's no need to copy that to a buffer + // First we ensure that the stream is up to date with the buffer, then we write directly to + // the stream. + writer.FlushToStream(); + writer.Stream.Write(array, 0, array.Length); + } + + private static void WritePrimitiveArray_sbyte(BinaryDataWriter writer, object o) + { + sbyte[] array = o as sbyte[]; + int bytesPerElement = sizeof(sbyte); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + // No need to check endianness, since sbyte has a size of 1 + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_bool(BinaryDataWriter writer, object o) + { + bool[] array = o as bool[]; + int bytesPerElement = sizeof(bool); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + // No need to check endianness, since bool has a size of 1 + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_char(BinaryDataWriter writer, object o) + { + char[] array = o as char[]; + int bytesPerElement = sizeof(char); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + for (int i = 0; i < array.Length; i++) + { + writer.UNSAFE_WriteToBuffer_2_Char(array[i]); + } + } + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + } + else + { + // We have to convert each individual element to bytes, since the byte order has to be reversed + var b = tempBuffer.Array; + + for (int i = 0; i < array.Length; i++) + { + ProperBitConverter.GetBytes(b, i * bytesPerElement, array[i]); + } + } + + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_short(BinaryDataWriter writer, object o) + { + short[] array = o as short[]; + int bytesPerElement = sizeof(short); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + for (int i = 0; i < array.Length; i++) + { + writer.UNSAFE_WriteToBuffer_2_Int16(array[i]); + } + } + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + } + else + { + // We have to convert each individual element to bytes, since the byte order has to be reversed + var b = tempBuffer.Array; + + for (int i = 0; i < array.Length; i++) + { + ProperBitConverter.GetBytes(b, i * bytesPerElement, array[i]); + } + } + + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_int(BinaryDataWriter writer, object o) + { + int[] array = o as int[]; + int bytesPerElement = sizeof(int); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + for (int i = 0; i < array.Length; i++) + { + writer.UNSAFE_WriteToBuffer_4_Int32(array[i]); + } + } + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + } + else + { + // We have to convert each individual element to bytes, since the byte order has to be reversed + var b = tempBuffer.Array; + + for (int i = 0; i < array.Length; i++) + { + ProperBitConverter.GetBytes(b, i * bytesPerElement, array[i]); + } + } + + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_long(BinaryDataWriter writer, object o) + { + long[] array = o as long[]; + int bytesPerElement = sizeof(long); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + for (int i = 0; i < array.Length; i++) + { + writer.UNSAFE_WriteToBuffer_8_Int64(array[i]); + } + } + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + } + else + { + // We have to convert each individual element to bytes, since the byte order has to be reversed + var b = tempBuffer.Array; + + for (int i = 0; i < array.Length; i++) + { + ProperBitConverter.GetBytes(b, i * bytesPerElement, array[i]); + } + } + + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_ushort(BinaryDataWriter writer, object o) + { + ushort[] array = o as ushort[]; + int bytesPerElement = sizeof(ushort); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + for (int i = 0; i < array.Length; i++) + { + writer.UNSAFE_WriteToBuffer_2_UInt16(array[i]); + } + } + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + } + else + { + // We have to convert each individual element to bytes, since the byte order has to be reversed + var b = tempBuffer.Array; + + for (int i = 0; i < array.Length; i++) + { + ProperBitConverter.GetBytes(b, i * bytesPerElement, array[i]); + } + } + + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_uint(BinaryDataWriter writer, object o) + { + uint[] array = o as uint[]; + int bytesPerElement = sizeof(uint); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + for (int i = 0; i < array.Length; i++) + { + writer.UNSAFE_WriteToBuffer_4_UInt32(array[i]); + } + } + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + } + else + { + // We have to convert each individual element to bytes, since the byte order has to be reversed + var b = tempBuffer.Array; + + for (int i = 0; i < array.Length; i++) + { + ProperBitConverter.GetBytes(b, i * bytesPerElement, array[i]); + } + } + + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_ulong(BinaryDataWriter writer, object o) + { + ulong[] array = o as ulong[]; + int bytesPerElement = sizeof(ulong); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + for (int i = 0; i < array.Length; i++) + { + writer.UNSAFE_WriteToBuffer_8_UInt64(array[i]); + } + } + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + } + else + { + // We have to convert each individual element to bytes, since the byte order has to be reversed + var b = tempBuffer.Array; + + for (int i = 0; i < array.Length; i++) + { + ProperBitConverter.GetBytes(b, i * bytesPerElement, array[i]); + } + } + + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_decimal(BinaryDataWriter writer, object o) + { + decimal[] array = o as decimal[]; + int bytesPerElement = sizeof(decimal); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + for (int i = 0; i < array.Length; i++) + { + writer.UNSAFE_WriteToBuffer_16_Decimal(array[i]); + } + } + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + } + else + { + // We have to convert each individual element to bytes, since the byte order has to be reversed + var b = tempBuffer.Array; + + for (int i = 0; i < array.Length; i++) + { + ProperBitConverter.GetBytes(b, i * bytesPerElement, array[i]); + } + } + + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_float(BinaryDataWriter writer, object o) + { + float[] array = o as float[]; + int bytesPerElement = sizeof(float); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + for (int i = 0; i < array.Length; i++) + { + writer.UNSAFE_WriteToBuffer_4_Float32(array[i]); + } + } + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + } + else + { + // We have to convert each individual element to bytes, since the byte order has to be reversed + var b = tempBuffer.Array; + + for (int i = 0; i < array.Length; i++) + { + ProperBitConverter.GetBytes(b, i * bytesPerElement, array[i]); + } + } + + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_double(BinaryDataWriter writer, object o) + { + double[] array = o as double[]; + int bytesPerElement = sizeof(double); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + for (int i = 0; i < array.Length; i++) + { + writer.UNSAFE_WriteToBuffer_8_Float64(array[i]); + } + } + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + } + else + { + // We have to convert each individual element to bytes, since the byte order has to be reversed + var b = tempBuffer.Array; + + for (int i = 0; i < array.Length; i++) + { + ProperBitConverter.GetBytes(b, i * bytesPerElement, array[i]); + } + } + + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + private static void WritePrimitiveArray_Guid(BinaryDataWriter writer, object o) + { + Guid[] array = o as Guid[]; + int bytesPerElement = sizeof(Guid); + int byteCount = array.Length * bytesPerElement; + + writer.EnsureBufferSpace(9); + + // Write entry flag + writer.buffer[writer.bufferIndex++] = (byte)BinaryEntryType.PrimitiveArray; + + writer.UNSAFE_WriteToBuffer_4_Int32(array.Length); + writer.UNSAFE_WriteToBuffer_4_Int32(bytesPerElement); + + // We copy to a buffer in order to write the entire array into the stream with one call + // We use our internal buffer if there's space in it, otherwise we claim a buffer from a cache. + if (writer.TryEnsureBufferSpace(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + fixed (byte* toBase = writer.buffer) + fixed (void* from = array) + { + void* to = toBase + writer.bufferIndex; + + UnsafeUtilities.MemoryCopy(from, to, byteCount); + } + + writer.bufferIndex += byteCount; + } + else + { + for (int i = 0; i < array.Length; i++) + { + writer.UNSAFE_WriteToBuffer_16_Guid(array[i]); + } + } + } + else + { + // Ensure stream is up to date with the buffer before we write directly to it + writer.FlushToStream(); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + if (BitConverter.IsLittleEndian) + { + // We always store in little endian, so we can do a direct memory mapping, which is a lot faster + UnsafeUtilities.MemoryCopy(array, tempBuffer.Array, byteCount, 0, 0); + } + else + { + // We have to convert each individual element to bytes, since the byte order has to be reversed + var b = tempBuffer.Array; + + for (int i = 0; i < array.Length; i++) + { + ProperBitConverter.GetBytes(b, i * bytesPerElement, array[i]); + } + } + + writer.Stream.Write(tempBuffer.Array, 0, byteCount); + } + } + } + + /// <summary> + /// Writes a primitive array to the stream. + /// </summary> + /// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)" />.</typeparam> + /// <param name="array">The primitive array to write.</param> + /// <exception cref="System.ArgumentException">Type + typeof(T).Name + is not a valid primitive array type.</exception> + public override void WritePrimitiveArray<T>(T[] array) + { + Action<BinaryDataWriter, object> writer; + + if (!PrimitiveArrayWriters.TryGetValue(typeof(T), out writer)) + { + throw new ArgumentException("Type " + typeof(T).Name + " is not a valid primitive array type."); + } + + writer(this, array); + } + + /// <summary> + /// Writes a <see cref="bool" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteBoolean(string name, bool value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedBoolean; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = value ? (byte)1 : (byte)0; + } + else + { + this.EnsureBufferSpace(2); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedBoolean; + this.buffer[this.bufferIndex++] = value ? (byte)1 : (byte)0; + } + + } + + /// <summary> + /// Writes a <see cref="byte" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteByte(string name, byte value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedByte; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = value; + } + else + { + this.EnsureBufferSpace(2); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedByte; + this.buffer[this.bufferIndex++] = value; + } + } + + /// <summary> + /// Writes a <see cref="char" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteChar(string name, char value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedChar; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(2); + this.UNSAFE_WriteToBuffer_2_Char(value); + } + else + { + this.EnsureBufferSpace(3); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedChar; + this.UNSAFE_WriteToBuffer_2_Char(value); + } + + } + + /// <summary> + /// Writes a <see cref="decimal" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteDecimal(string name, decimal value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedDecimal; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(16); + this.UNSAFE_WriteToBuffer_16_Decimal(value); + } + else + { + this.EnsureBufferSpace(17); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedDecimal; + this.UNSAFE_WriteToBuffer_16_Decimal(value); + } + } + + /// <summary> + /// Writes a <see cref="double" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteDouble(string name, double value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedDouble; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(8); + this.UNSAFE_WriteToBuffer_8_Float64(value); + } + else + { + this.EnsureBufferSpace(9); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedDouble; + this.UNSAFE_WriteToBuffer_8_Float64(value); + } + + } + + /// <summary> + /// Writes a <see cref="Guid" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteGuid(string name, Guid value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedGuid; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(16); + this.UNSAFE_WriteToBuffer_16_Guid(value); + } + else + { + this.EnsureBufferSpace(17); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedGuid; + this.UNSAFE_WriteToBuffer_16_Guid(value); + } + + } + + /// <summary> + /// Writes an external guid reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="guid">The value to write.</param> + public override void WriteExternalReference(string name, Guid guid) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedExternalReferenceByGuid; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(16); + this.UNSAFE_WriteToBuffer_16_Guid(guid); + } + else + { + this.EnsureBufferSpace(17); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedExternalReferenceByGuid; + this.UNSAFE_WriteToBuffer_16_Guid(guid); + } + } + + /// <summary> + /// Writes an external index reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="index">The value to write.</param> + public override void WriteExternalReference(string name, int index) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedExternalReferenceByIndex; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(4); + this.UNSAFE_WriteToBuffer_4_Int32(index); + } + else + { + this.EnsureBufferSpace(5); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedExternalReferenceByIndex; + this.UNSAFE_WriteToBuffer_4_Int32(index); + } + } + + /// <summary> + /// Writes an external string reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="id">The value to write.</param> + public override void WriteExternalReference(string name, string id) + { + if (id == null) + { + throw new ArgumentNullException("id"); + } + + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedExternalReferenceByString; + this.WriteStringFast(name); + } + else + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedExternalReferenceByString; + } + + this.WriteStringFast(id); + } + + /// <summary> + /// Writes an <see cref="int" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteInt32(string name, int value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedInt; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(4); + this.UNSAFE_WriteToBuffer_4_Int32(value); + } + else + { + this.EnsureBufferSpace(5); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedInt; + this.UNSAFE_WriteToBuffer_4_Int32(value); + } + } + + /// <summary> + /// Writes a <see cref="long" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteInt64(string name, long value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedLong; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(8); + this.UNSAFE_WriteToBuffer_8_Int64(value); + } + else + { + this.EnsureBufferSpace(9); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedLong; + this.UNSAFE_WriteToBuffer_8_Int64(value); + } + } + + /// <summary> + /// Writes a null value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + public override void WriteNull(string name) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedNull; + this.WriteStringFast(name); + } + else + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedNull; + } + } + + /// <summary> + /// Writes an internal reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="id">The value to write.</param> + public override void WriteInternalReference(string name, int id) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedInternalReference; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(4); + this.UNSAFE_WriteToBuffer_4_Int32(id); + } + else + { + this.EnsureBufferSpace(5); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedInternalReference; + this.UNSAFE_WriteToBuffer_4_Int32(id); + } + } + + /// <summary> + /// Writes an <see cref="sbyte" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteSByte(string name, sbyte value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedSByte; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(1); + + unchecked + { + this.buffer[this.bufferIndex++] = (byte)value; + } + } + else + { + this.EnsureBufferSpace(2); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedSByte; + + unchecked + { + this.buffer[this.bufferIndex++] = (byte)value; + } + } + } + + /// <summary> + /// Writes a <see cref="short" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteInt16(string name, short value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedShort; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(2); + this.UNSAFE_WriteToBuffer_2_Int16(value); + } + else + { + this.EnsureBufferSpace(3); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedShort; + this.UNSAFE_WriteToBuffer_2_Int16(value); + } + } + + /// <summary> + /// Writes a <see cref="float" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteSingle(string name, float value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedFloat; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(4); + this.UNSAFE_WriteToBuffer_4_Float32(value); + } + else + { + this.EnsureBufferSpace(5); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedFloat; + this.UNSAFE_WriteToBuffer_4_Float32(value); + } + } + + /// <summary> + /// Writes a <see cref="string" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteString(string name, string value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedString; + + this.WriteStringFast(name); + } + else + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedString; + } + + this.WriteStringFast(value); + } + + /// <summary> + /// Writes an <see cref="uint" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteUInt32(string name, uint value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedUInt; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(4); + this.UNSAFE_WriteToBuffer_4_UInt32(value); + } + else + { + this.EnsureBufferSpace(5); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedUInt; + this.UNSAFE_WriteToBuffer_4_UInt32(value); + } + } + + /// <summary> + /// Writes an <see cref="ulong" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteUInt64(string name, ulong value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedULong; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(8); + this.UNSAFE_WriteToBuffer_8_UInt64(value); + } + else + { + this.EnsureBufferSpace(9); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedULong; + this.UNSAFE_WriteToBuffer_8_UInt64(value); + } + } + + /// <summary> + /// Writes an <see cref="ushort" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteUInt16(string name, ushort value) + { + if (name != null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.NamedUShort; + + this.WriteStringFast(name); + + this.EnsureBufferSpace(2); + this.UNSAFE_WriteToBuffer_2_UInt16(value); + } + else + { + this.EnsureBufferSpace(3); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedUShort; + this.UNSAFE_WriteToBuffer_2_UInt16(value); + } + } + + /// <summary> + /// Tells the writer that a new serialization session is about to begin, and that it should clear all cached values left over from any prior serialization sessions. + /// This method is only relevant when the same writer is used to serialize several different, unrelated values. + /// </summary> + public override void PrepareNewSerializationSession() + { + base.PrepareNewSerializationSession(); + this.types.Clear(); + this.bufferIndex = 0; + } + + public override string GetDataDump() + { + if (!this.Stream.CanRead) + { + return "Binary data stream for writing cannot be read; cannot dump data."; + } + + if (!this.Stream.CanSeek) + { + return "Binary data stream cannot seek; cannot dump data."; + } + + this.FlushToStream(); + + var oldPosition = this.Stream.Position; + + var bytes = new byte[oldPosition]; + + this.Stream.Position = 0; + this.Stream.Read(bytes, 0, (int)oldPosition); + + this.Stream.Position = oldPosition; + + return "Binary hex dump: " + ProperBitConverter.BytesToHexString(bytes); + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void WriteType(Type type) + { + if (type == null) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.UnnamedNull; + } + else + { + int id; + + if (this.types.TryGetValue(type, out id)) + { + this.EnsureBufferSpace(5); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.TypeID; + this.UNSAFE_WriteToBuffer_4_Int32(id); + } + else + { + id = this.types.Count; + this.types.Add(type, id); + + this.EnsureBufferSpace(5); + this.buffer[this.bufferIndex++] = (byte)BinaryEntryType.TypeName; + this.UNSAFE_WriteToBuffer_4_Int32(id); + this.WriteStringFast(this.Context.Binder.BindToName(type, this.Context.Config.DebugContext)); + } + } + } + + private struct Struct256Bit + { + public decimal d1; + public decimal d2; + } + + private void WriteStringFast(string value) + { + bool needs16BitsPerChar = true; + int byteCount; + + if (this.CompressStringsTo8BitWhenPossible) + { + needs16BitsPerChar = false; + + // Check if the string requires 16 bit support + for (int i = 0; i < value.Length; i++) + { + if (value[i] > 255) + { + needs16BitsPerChar = true; + break; + } + } + } + + if (needs16BitsPerChar) + { + byteCount = value.Length * 2; + + if (this.TryEnsureBufferSpace(byteCount + 5)) + { + this.buffer[this.bufferIndex++] = 1; // Write 16 bit flag + this.UNSAFE_WriteToBuffer_4_Int32(value.Length); + + if (BitConverter.IsLittleEndian) + { + fixed (byte* baseToPtr = this.buffer) + fixed (char* baseFromPtr = value) + { + Struct256Bit* toPtr = (Struct256Bit*)(baseToPtr + this.bufferIndex); + Struct256Bit* fromPtr = (Struct256Bit*)baseFromPtr; + + byte* toEnd = (byte*)toPtr + byteCount; + + while ((toPtr + 1) <= toEnd) + { + *toPtr++ = *fromPtr++; + } + + char* toPtrRest = (char*)toPtr; + char* fromPtrRest = (char*)fromPtr; + + while (toPtrRest < toEnd) + { + *toPtrRest++ = *fromPtrRest++; + } + } + } + else + { + fixed (byte* baseToPtr = this.buffer) + fixed (char* baseFromPtr = value) + { + byte* toPtr = baseToPtr + this.bufferIndex; + byte* fromPtr = (byte*)baseFromPtr; + + for (int i = 0; i < byteCount; i += 2) + { + *toPtr = *(fromPtr + 1); + *(toPtr + 1) = *fromPtr; + + fromPtr += 2; + toPtr += 2; + } + } + } + + this.bufferIndex += byteCount; + } + else + { + // Our internal buffer doesn't have space for this string - use the stream directly + this.FlushToStream(); // Ensure stream is up to date with buffer before we write directly to it + this.Stream.WriteByte(1); // Write 16 bit flag + + ProperBitConverter.GetBytes(this.small_buffer, 0, value.Length); + this.Stream.Write(this.small_buffer, 0, 4); + + using (var tempBuffer = Buffer<byte>.Claim(byteCount)) + { + var array = tempBuffer.Array; + UnsafeUtilities.StringToBytes(array, value, true); + this.Stream.Write(array, 0, byteCount); + } + } + } + else + { + byteCount = value.Length; + + if (this.TryEnsureBufferSpace(byteCount + 5)) + { + this.buffer[this.bufferIndex++] = 0; // Write 8 bit flag + this.UNSAFE_WriteToBuffer_4_Int32(value.Length); + + for (int i = 0; i < byteCount; i++) + { + this.buffer[this.bufferIndex++] = (byte)value[i]; + } + } + else + { + // Our internal buffer doesn't have space for this string - use the stream directly + this.FlushToStream(); // Ensure stream is up to date with buffer before we write directly to it + this.Stream.WriteByte(0); // Write 8 bit flag + + ProperBitConverter.GetBytes(this.small_buffer, 0, value.Length); + this.Stream.Write(this.small_buffer, 0, 4); + + using (var tempBuffer = Buffer<byte>.Claim(value.Length)) + { + var array = tempBuffer.Array; + + for (int i = 0; i < value.Length; i++) + { + array[i] = (byte)value[i]; + } + + this.Stream.Write(array, 0, value.Length); + } + } + } + } + + public override void FlushToStream() + { + if (this.bufferIndex > 0) + { + this.Stream.Write(this.buffer, 0, this.bufferIndex); + this.bufferIndex = 0; + } + + base.FlushToStream(); + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void UNSAFE_WriteToBuffer_2_Char(char value) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + *(char*)(basePtr + this.bufferIndex) = value; + } + else + { + byte* ptrTo = basePtr + this.bufferIndex; + byte* ptrFrom = (byte*)&value + 1; + + *ptrTo++ = *ptrFrom--; + *ptrTo = *ptrFrom; + } + } + + this.bufferIndex += 2; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void UNSAFE_WriteToBuffer_2_Int16(short value) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + *(short*)(basePtr + this.bufferIndex) = value; + } + else + { + byte* ptrTo = basePtr + this.bufferIndex; + byte* ptrFrom = (byte*)&value + 1; + + *ptrTo++ = *ptrFrom--; + *ptrTo = *ptrFrom; + } + } + + this.bufferIndex += 2; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void UNSAFE_WriteToBuffer_2_UInt16(ushort value) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + *(ushort*)(basePtr + this.bufferIndex) = value; + } + else + { + byte* ptrTo = basePtr + this.bufferIndex; + byte* ptrFrom = (byte*)&value + 1; + + *ptrTo++ = *ptrFrom--; + *ptrTo = *ptrFrom; + } + } + + this.bufferIndex += 2; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void UNSAFE_WriteToBuffer_4_Int32(int value) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + *(int*)(basePtr + this.bufferIndex) = value; + } + else + { + byte* ptrTo = basePtr + this.bufferIndex; + byte* ptrFrom = (byte*)&value + 3; + + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo = *ptrFrom; + } + } + + this.bufferIndex += 4; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void UNSAFE_WriteToBuffer_4_UInt32(uint value) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + *(uint*)(basePtr + this.bufferIndex) = value; + } + else + { + byte* ptrTo = basePtr + this.bufferIndex; + byte* ptrFrom = (byte*)&value + 3; + + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo = *ptrFrom; + } + } + + this.bufferIndex += 4; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void UNSAFE_WriteToBuffer_4_Float32(float value) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_All_Unaligned_ReadWrites) + { + // We can write directly to the buffer, safe in the knowledge that any potential unaligned writes will work + *(float*)(basePtr + this.bufferIndex) = value; + } + else + { + // We do a slower but safer byte-by-byte write instead. + // Apparently doing this bit through an int pointer alias can also crash sometimes. + // Hence, we just do a byte-by-byte write to be safe. + byte* from = (byte*)&value; + byte* to = basePtr + this.bufferIndex; + + *to++ = *from++; + *to++ = *from++; + *to++ = *from++; + *to = *from; + } + } + else + { + byte* ptrTo = basePtr + this.bufferIndex; + byte* ptrFrom = (byte*)&value + 3; + + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo = *ptrFrom; + } + } + + this.bufferIndex += 4; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void UNSAFE_WriteToBuffer_8_Int64(long value) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_All_Unaligned_ReadWrites) + { + // We can write directly to the buffer, safe in the knowledge that any potential unaligned writes will work + *(long*)(basePtr + this.bufferIndex) = value; + } + else + { + // We do a slower but safer int-by-int write instead + int* fromPtr = (int*)&value; + int* toPtr = (int*)(basePtr + this.bufferIndex); + + *toPtr++ = *fromPtr++; + *toPtr = *fromPtr; + } + } + else + { + byte* ptrTo = basePtr + this.bufferIndex; + byte* ptrFrom = (byte*)&value + 7; + + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo = *ptrFrom; + } + } + + this.bufferIndex += 8; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void UNSAFE_WriteToBuffer_8_UInt64(ulong value) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_All_Unaligned_ReadWrites) + { + // We can write directly to the buffer, safe in the knowledge that any potential unaligned writes will work + *(ulong*)(basePtr + this.bufferIndex) = value; + } + else + { + // We do a slower but safer int-by-int write instead + int* fromPtr = (int*)&value; + int* toPtr = (int*)(basePtr + this.bufferIndex); + + *toPtr++ = *fromPtr++; + *toPtr = *fromPtr; + } + } + else + { + byte* ptrTo = basePtr + this.bufferIndex; + byte* ptrFrom = (byte*)&value + 7; + + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo = *ptrFrom; + } + } + + this.bufferIndex += 8; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void UNSAFE_WriteToBuffer_8_Float64(double value) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_All_Unaligned_ReadWrites) + { + // We can write directly to the buffer, safe in the knowledge that any potential unaligned writes will work + *(double*)(basePtr + this.bufferIndex) = value; + } + else + { + // We do a slower but safer int-by-int write instead + int* fromPtr = (int*)&value; + int* toPtr = (int*)(basePtr + this.bufferIndex); + + *toPtr++ = *fromPtr++; + *toPtr = *fromPtr; + } + } + else + { + byte* ptrTo = basePtr + this.bufferIndex; + byte* ptrFrom = (byte*)&value + 7; + + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo = *ptrFrom; + } + } + + this.bufferIndex += 8; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void UNSAFE_WriteToBuffer_16_Decimal(decimal value) + { + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_All_Unaligned_ReadWrites) + { + // We can write directly to the buffer, safe in the knowledge that any potential unaligned writes will work + *(decimal*)(basePtr + this.bufferIndex) = value; + } + else + { + // We do a slower but safer int-by-int write instead + int* fromPtr = (int*)&value; + int* toPtr = (int*)(basePtr + this.bufferIndex); + + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr = *fromPtr; + } + } + else + { + byte* ptrTo = basePtr + this.bufferIndex; + byte* ptrFrom = (byte*)&value + 15; + + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo = *ptrFrom; + } + } + + this.bufferIndex += 16; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void UNSAFE_WriteToBuffer_16_Guid(Guid value) + { + // First 10 bytes of a guid are always little endian + // Last 6 bytes depend on architecture endianness + // See http://stackoverflow.com/questions/10190817/guid-byte-order-in-net + + // TODO: Test if this actually works on big-endian architecture. Where the hell do we find that? + + fixed (byte* basePtr = this.buffer) + { + if (BitConverter.IsLittleEndian) + { + if (ArchitectureInfo.Architecture_Supports_All_Unaligned_ReadWrites) + { + // We can write directly to the buffer, safe in the knowledge that any potential unaligned writes will work + *(Guid*)(basePtr + this.bufferIndex) = value; + } + else + { + // We do a slower but safer int-by-int write instead + int* fromPtr = (int*)&value; + int* toPtr = (int*)(basePtr + this.bufferIndex); + + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr++ = *fromPtr++; + *toPtr = *fromPtr; + } + } + else + { + byte* ptrTo = basePtr + this.bufferIndex; + byte* ptrFrom = (byte*)&value; + + *ptrTo++ = *ptrFrom++; + *ptrTo++ = *ptrFrom++; + *ptrTo++ = *ptrFrom++; + *ptrTo++ = *ptrFrom++; + *ptrTo++ = *ptrFrom++; + *ptrTo++ = *ptrFrom++; + *ptrTo++ = *ptrFrom++; + *ptrTo++ = *ptrFrom++; + *ptrTo++ = *ptrFrom++; + *ptrTo++ = *ptrFrom; + + ptrFrom += 6; + + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo++ = *ptrFrom--; + *ptrTo = *ptrFrom; + } + } + + this.bufferIndex += 16; + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private void EnsureBufferSpace(int space) + { + var length = this.buffer.Length; + + if (space > length) + { + throw new Exception("Insufficient buffer capacity"); + } + + if (this.bufferIndex + space > length) + { + this.FlushToStream(); + } + } + + [MethodImpl((MethodImplOptions)0x100)] // Set aggressive inlining flag, for the runtimes that understand that + private bool TryEnsureBufferSpace(int space) + { + var length = this.buffer.Length; + + if (space > length) + { + return false; + } + + if (this.bufferIndex + space > length) + { + this.FlushToStream(); + } + + return true; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataWriter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataWriter.cs.meta new file mode 100644 index 00000000..6a77dfd1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryDataWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1bc9e2503afdd0290574ebc14cf4a16d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryEntryType.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryEntryType.cs new file mode 100644 index 00000000..31f9aa9a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryEntryType.cs @@ -0,0 +1,286 @@ +//----------------------------------------------------------------------- +// <copyright file="BinaryEntryType.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Entry types in the binary format written by <see cref="BinaryDataWriter"/>. + /// </summary> + public enum BinaryEntryType : byte + { + /// <summary> + /// An invalid entry. + /// </summary> + Invalid = 0x0, + + /// <summary> + /// Entry denoting a named start of a reference node. + /// </summary> + NamedStartOfReferenceNode = 0x1, + + /// <summary> + /// Entry denoting an unnamed start of a reference node. + /// </summary> + UnnamedStartOfReferenceNode = 0x2, + + /// <summary> + /// Entry denoting a named start of a struct node. + /// </summary> + NamedStartOfStructNode = 0x3, + + /// <summary> + /// Entry denoting an unnamed start of a struct node. + /// </summary> + UnnamedStartOfStructNode = 0x4, + + /// <summary> + /// Entry denoting an end of node. + /// </summary> + EndOfNode = 0x5, + + /// <summary> + /// Entry denoting the start of an array. + /// </summary> + StartOfArray = 0x6, + + /// <summary> + /// Entry denoting the end of an array. + /// </summary> + EndOfArray = 0x7, + + /// <summary> + /// Entry denoting a primitive array. + /// </summary> + PrimitiveArray = 0x8, + + /// <summary> + /// Entry denoting a named internal reference. + /// </summary> + NamedInternalReference = 0x9, + + /// <summary> + /// Entry denoting an unnamed internal reference. + /// </summary> + UnnamedInternalReference = 0xA, + + /// <summary> + /// Entry denoting a named external reference by index. + /// </summary> + NamedExternalReferenceByIndex = 0xB, + + /// <summary> + /// Entry denoting an unnamed external reference by index. + /// </summary> + UnnamedExternalReferenceByIndex = 0xC, + + /// <summary> + /// Entry denoting a named external reference by guid. + /// </summary> + NamedExternalReferenceByGuid = 0xD, + + /// <summary> + /// Entry denoting an unnamed external reference by guid. + /// </summary> + UnnamedExternalReferenceByGuid = 0xE, + + /// <summary> + /// Entry denoting a named sbyte. + /// </summary> + NamedSByte = 0xF, + + /// <summary> + /// Entry denoting an unnamed sbyte. + /// </summary> + UnnamedSByte = 0x10, + + /// <summary> + /// Entry denoting a named byte. + /// </summary> + NamedByte = 0x11, + + /// <summary> + /// Entry denoting an unnamed byte. + /// </summary> + UnnamedByte = 0x12, + + /// <summary> + /// Entry denoting a named short. + /// </summary> + NamedShort = 0x13, + + /// <summary> + /// Entry denoting an unnamed short. + /// </summary> + UnnamedShort = 0x14, + + /// <summary> + /// Entry denoting a named ushort. + /// </summary> + NamedUShort = 0x15, + + /// <summary> + /// Entry denoting an unnamed ushort. + /// </summary> + UnnamedUShort = 0x16, + + /// <summary> + /// Entry denoting a named int. + /// </summary> + NamedInt = 0x17, + + /// <summary> + /// Entry denoting an unnamed int. + /// </summary> + UnnamedInt = 0x18, + + /// <summary> + /// Entry denoting a named uint. + /// </summary> + NamedUInt = 0x19, + + /// <summary> + /// Entry denoting an unnamed uint. + /// </summary> + UnnamedUInt = 0x1A, + + /// <summary> + /// Entry denoting a named long. + /// </summary> + NamedLong = 0x1B, + + /// <summary> + /// Entry denoting an unnamed long. + /// </summary> + UnnamedLong = 0x1C, + + /// <summary> + /// Entry denoting a named ulong. + /// </summary> + NamedULong = 0x1D, + + /// <summary> + /// Entry denoting an unnamed ulong. + /// </summary> + UnnamedULong = 0x1E, + + /// <summary> + /// Entry denoting a named float. + /// </summary> + NamedFloat = 0x1F, + + /// <summary> + /// Entry denoting an unnamed float. + /// </summary> + UnnamedFloat = 0x20, + + /// <summary> + /// Entry denoting a named double. + /// </summary> + NamedDouble = 0x21, + + /// <summary> + /// Entry denoting an unnamed double. + /// </summary> + UnnamedDouble = 0x22, + + /// <summary> + /// Entry denoting a named decimal. + /// </summary> + NamedDecimal = 0x23, + + /// <summary> + /// Entry denoting an unnamed decimal. + /// </summary> + UnnamedDecimal = 0x24, + + /// <summary> + /// Entry denoting a named char. + /// </summary> + NamedChar = 0x25, + + /// <summary> + /// Entry denoting an unnamed char. + /// </summary> + UnnamedChar = 0x26, + + /// <summary> + /// Entry denoting a named string. + /// </summary> + NamedString = 0x27, + + /// <summary> + /// Entry denoting an unnamed string. + /// </summary> + UnnamedString = 0x28, + + /// <summary> + /// Entry denoting a named guid. + /// </summary> + NamedGuid = 0x29, + + /// <summary> + /// Entry denoting an unnamed guid. + /// </summary> + UnnamedGuid = 0x2A, + + /// <summary> + /// Entry denoting a named boolean. + /// </summary> + NamedBoolean = 0x2B, + + /// <summary> + /// Entry denoting an unnamed boolean. + /// </summary> + UnnamedBoolean = 0x2C, + + /// <summary> + /// Entry denoting a named null. + /// </summary> + NamedNull = 0x2D, + + /// <summary> + /// Entry denoting an unnamed null. + /// </summary> + UnnamedNull = 0x2E, + + /// <summary> + /// Entry denoting a type name. + /// </summary> + TypeName = 0x2F, + + /// <summary> + /// Entry denoting a type id. + /// </summary> + TypeID = 0x30, + + /// <summary> + /// Entry denoting that the end of the stream has been reached. + /// </summary> + EndOfStream = 0x31, + + /// <summary> + /// Entry denoting a named external reference by string. + /// </summary> + NamedExternalReferenceByString = 0x32, + + /// <summary> + /// Entry denoting an unnamed external reference by string. + /// </summary> + UnnamedExternalReferenceByString = 0x33 + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryEntryType.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryEntryType.cs.meta new file mode 100644 index 00000000..0af71524 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Binary/BinaryEntryType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1361420bc2b384389a065fd2fe59fb22 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataReader.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataReader.cs new file mode 100644 index 00000000..a6930760 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataReader.cs @@ -0,0 +1,369 @@ +//----------------------------------------------------------------------- +// <copyright file="IDataReader.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.IO; + + /// <summary> + /// Provides a set of methods for reading data stored in a format written by a corresponding <see cref="IDataWriter"/> class. + /// <para /> + /// If you implement this interface, it is VERY IMPORTANT that you implement each method to the *exact* specifications the documentation specifies. + /// <para /> + /// It is strongly recommended to inherit from the <see cref="BaseDataReader"/> class if you wish to implement a new data reader. + /// </summary> + /// <seealso cref="System.IDisposable" /> + public interface IDataReader : IDisposable + { + /// <summary> + /// Gets or sets the reader's serialization binder. + /// </summary> + /// <value> + /// The reader's serialization binder. + /// </value> + TwoWaySerializationBinder Binder { get; set; } + + /// <summary> + /// Gets or sets the base stream of the reader. + /// </summary> + /// <value> + /// The base stream of the reader. + /// </value> + [Obsolete("Data readers and writers don't necessarily have streams any longer, so this API has been made obsolete. Using this property may result in NotSupportedExceptions being thrown.", false)] + Stream Stream { get; set; } + + /// <summary> + /// Gets a value indicating whether the reader is in an array node. + /// </summary> + /// <value> + /// <c>true</c> if the reader is in an array node; otherwise, <c>false</c>. + /// </value> + bool IsInArrayNode { get; } + + /// <summary> + /// Gets the name of the current node. + /// </summary> + /// <value> + /// The name of the current node. + /// </value> + string CurrentNodeName { get; } + + /// <summary> + /// Gets the current node id. If this is less than zero, the current node has no id. + /// </summary> + /// <value> + /// The current node id. + /// </value> + int CurrentNodeId { get; } + + /// <summary> + /// Gets the current node depth. In other words, the current count of the node stack. + /// </summary> + /// <value> + /// The current node depth. + /// </value> + int CurrentNodeDepth { get; } + + /// <summary> + /// Gets the deserialization context. + /// </summary> + /// <value> + /// The deserialization context. + /// </value> + DeserializationContext Context { get; set; } + + /// <summary> + /// Gets a dump of the data being read by the writer. The format of this dump varies, but should be useful for debugging purposes. + /// </summary> + string GetDataDump(); + + /// <summary> + /// Tries to enter a node. This will succeed if the next entry is an <see cref="EntryType.StartOfNode"/>. + /// <para /> + /// This call MUST (eventually) be followed by a corresponding call to <see cref="IDataReader.ExitNode(DeserializationContext)"/> + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> properties to the correct values for the current node. + /// </summary> + /// <param name="type">The type of the node. This value will be null if there was no metadata, or if the reader's serialization binder failed to resolve the type name.</param> + /// <returns><c>true</c> if entering a node succeeded, otherwise <c>false</c></returns> + bool EnterNode(out Type type); + + /// <summary> + /// Exits the current node. This method will keep skipping entries using <see cref="IDataReader.SkipEntry(DeserializationContext)"/> until an <see cref="EntryType.EndOfNode"/> is reached, or the end of the stream is reached. + /// <para /> + /// This call MUST have been preceded by a corresponding call to <see cref="IDataReader.EnterNode(out Type)"/>. + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> to the correct values for the node that was prior to the current node. + /// </summary> + /// <returns><c>true</c> if the method exited a node, <c>false</c> if it reached the end of the stream.</returns> + bool ExitNode(); + + /// <summary> + /// Tries to enters an array node. This will succeed if the next entry is an <see cref="EntryType.StartOfArray"/>. + /// <para /> + /// This call MUST (eventually) be followed by a corresponding call to <see cref="IDataReader.ExitArray(DeserializationContext)"/> + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> properties to the correct values for the current array node. + /// </summary> + /// <param name="length">The length of the array that was entered.</param> + /// <returns><c>true</c> if an array was entered, otherwise <c>false</c></returns> + bool EnterArray(out long length); + + /// <summary> + /// Exits the closest array. This method will keep skipping entries using <see cref="IDataReader.SkipEntry(DeserializationContext)"/> until an <see cref="EntryType.EndOfArray"/> is reached, or the end of the stream is reached. + /// <para /> + /// This call MUST have been preceded by a corresponding call to <see cref="IDataReader.EnterArray(out long)"/>. + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> to the correct values for the node that was prior to the exited array node. + /// </summary> + /// <returns><c>true</c> if the method exited an array, <c>false</c> if it reached the end of the stream.</returns> + bool ExitArray(); + + /// <summary> + /// Reads a primitive array value. This call will succeed if the next entry is an <see cref="EntryType.PrimitiveArray"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)"/>.</typeparam> + /// <param name="array">The resulting primitive array.</param> + /// <returns><c>true</c> if reading a primitive array succeeded, otherwise <c>false</c></returns> + bool ReadPrimitiveArray<T>(out T[] array) where T : struct; + + /// <summary> + /// Peeks ahead and returns the type of the next entry in the stream. + /// </summary> + /// <param name="name">The name of the next entry, if it has one.</param> + /// <returns>The type of the next entry.</returns> + EntryType PeekEntry(out string name); + + /// <summary> + /// Reads an internal reference id. This call will succeed if the next entry is an <see cref="EntryType.InternalReference"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="id">The internal reference id.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadInternalReference(out int id); + + /// <summary> + /// Reads an external reference index. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByIndex"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="index">The external reference index.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadExternalReference(out int index); + + /// <summary> + /// Reads an external reference guid. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByGuid"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="guid">The external reference guid.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadExternalReference(out Guid guid); + + /// <summary> + /// Reads an external reference string. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByString"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="id">The external reference string.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadExternalReference(out string id); + + /// <summary> + /// Reads a <see cref="char"/> value. This call will succeed if the next entry is an <see cref="EntryType.String"/>. + /// <para /> + /// If the string of the entry is longer than 1 character, the first character of the string will be taken as the result. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadChar(out char value); + + /// <summary> + /// Reads a <see cref="string"/> value. This call will succeed if the next entry is an <see cref="EntryType.String"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadString(out string value); + + /// <summary> + /// Reads a <see cref="Guid"/> value. This call will succeed if the next entry is an <see cref="EntryType.Guid"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadGuid(out Guid value); + + /// <summary> + /// Reads an <see cref="sbyte"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="sbyte.MinValue"/> or larger than <see cref="sbyte.MaxValue"/>, the result will be default(<see cref="sbyte"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadSByte(out sbyte value); + + /// <summary> + /// Reads a <see cref="short"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="short.MinValue"/> or larger than <see cref="short.MaxValue"/>, the result will be default(<see cref="short"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadInt16(out short value); + + /// <summary> + /// Reads an <see cref="int"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="int.MinValue"/> or larger than <see cref="int.MaxValue"/>, the result will be default(<see cref="int"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadInt32(out int value); + + /// <summary> + /// Reads a <see cref="long"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="long.MinValue"/> or larger than <see cref="long.MaxValue"/>, the result will be default(<see cref="long"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadInt64(out long value); + + /// <summary> + /// Reads a <see cref="byte"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="byte.MinValue"/> or larger than <see cref="byte.MaxValue"/>, the result will be default(<see cref="byte"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadByte(out byte value); + + /// <summary> + /// Reads an <see cref="ushort"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="ushort.MinValue"/> or larger than <see cref="ushort.MaxValue"/>, the result will be default(<see cref="ushort"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadUInt16(out ushort value); + + /// <summary> + /// Reads an <see cref="uint"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="uint.MinValue"/> or larger than <see cref="uint.MaxValue"/>, the result will be default(<see cref="uint"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadUInt32(out uint value); + + /// <summary> + /// Reads an <see cref="ulong"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="ulong.MinValue"/> or larger than <see cref="ulong.MaxValue"/>, the result will be default(<see cref="ulong"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadUInt64(out ulong value); + + /// <summary> + /// Reads a <see cref="decimal"/> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint"/> or an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="decimal.MinValue"/> or larger than <see cref="decimal.MaxValue"/>, the result will be default(<see cref="decimal"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadDecimal(out decimal value); + + /// <summary> + /// Reads a <see cref="float"/> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint"/> or an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="float.MinValue"/> or larger than <see cref="float.MaxValue"/>, the result will be default(<see cref="float"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadSingle(out float value); + + /// <summary> + /// Reads a <see cref="double"/> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint"/> or an <see cref="EntryType.Integer"/>. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="double.MinValue"/> or larger than <see cref="double.MaxValue"/>, the result will be default(<see cref="double"/>). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadDouble(out double value); + + /// <summary> + /// Reads a <see cref="bool"/> value. This call will succeed if the next entry is an <see cref="EntryType.Boolean"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadBoolean(out bool value); + + /// <summary> + /// Reads a <c>null</c> value. This call will succeed if the next entry is an <see cref="EntryType.Null"/>. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. + /// </summary> + /// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns> + bool ReadNull(); + + /// <summary> + /// Skips the next entry value, unless it is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. If the next entry value is an <see cref="EntryType.StartOfNode"/> or an <see cref="EntryType.StartOfArray"/>, all of its contents will be processed, deserialized and registered in the deserialization context, so that internal reference values are not lost to entries further down the stream. + /// </summary> + void SkipEntry(); + + /// <summary> + /// Tells the reader that a new serialization session is about to begin, and that it should clear all cached values left over from any prior serialization sessions. + /// This method is only relevant when the same reader is used to deserialize several different, unrelated values. + /// </summary> + void PrepareNewSerializationSession(); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataReader.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataReader.cs.meta new file mode 100644 index 00000000..0ed41f28 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc1fe25e670cf981ed66b3e85c3e4249 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataWriter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataWriter.cs new file mode 100644 index 00000000..e4a6e539 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataWriter.cs @@ -0,0 +1,265 @@ +//----------------------------------------------------------------------- +// <copyright file="IDataWriter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.IO; + + /// <summary> + /// Provides a set of methods for reading data stored in a format that can be read by a corresponding <see cref="IDataReader"/> class. + /// <para /> + /// If you implement this interface, it is VERY IMPORTANT that you implement each method to the *exact* specifications the documentation specifies. + /// <para /> + /// It is strongly recommended to inherit from the <see cref="BaseDataWriter"/> class if you wish to implement a new data writer. + /// </summary> + /// <seealso cref="System.IDisposable" /> + public interface IDataWriter : IDisposable + { + /// <summary> + /// Gets or sets the reader's serialization binder. + /// </summary> + /// <value> + /// The reader's serialization binder. + /// </value> + TwoWaySerializationBinder Binder { get; set; } + + /// <summary> + /// Gets or sets the base stream of the writer. + /// </summary> + /// <value> + /// The base stream of the writer. + /// </value> + [Obsolete("Data readers and writers don't necessarily have streams any longer, so this API has been made obsolete. Using this property may result in NotSupportedExceptions being thrown.", false)] + Stream Stream { get; set; } + + /// <summary> + /// Gets a value indicating whether the writer is in an array node. + /// </summary> + /// <value> + /// <c>true</c> if the writer is in an array node; otherwise, <c>false</c>. + /// </value> + bool IsInArrayNode { get; } + + /// <summary> + /// Gets the serialization context. + /// </summary> + /// <value> + /// The serialization context. + /// </value> + SerializationContext Context { get; set; } + + /// <summary> + /// Gets a dump of the data currently written by the writer. The format of this dump varies, but should be useful for debugging purposes. + /// </summary> + string GetDataDump(); + + /// <summary> + /// Flushes everything that has been written so far to the writer's base stream. + /// </summary> + void FlushToStream(); + + /// <summary> + /// Writes the beginning of a reference node. + /// <para /> + /// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)"/>, with the same name. + /// </summary> + /// <param name="name">The name of the reference node.</param> + /// <param name="type">The type of the reference node. If null, no type metadata will be written.</param> + /// <param name="id">The id of the reference node. This id is acquired by calling <see cref="SerializationContext.TryRegisterInternalReference(object, out int)"/>.</param> + void BeginReferenceNode(string name, Type type, int id); + + /// <summary> + /// Begins a struct/value type node. This is essentially the same as a reference node, except it has no internal reference id. + /// <para /> + /// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)"/>, with the same name. + /// </summary> + /// <param name="name">The name of the struct node.</param> + /// <param name="type">The type of the struct node. If null, no type metadata will be written.</param> + void BeginStructNode(string name, Type type); + + /// <summary> + /// Ends the current node with the given name. If the current node has another name, an <see cref="InvalidOperationException"/> is thrown. + /// </summary> + /// <param name="name">The name of the node to end. This has to be the name of the current node.</param> + void EndNode(string name); + + /// <summary> + /// Begins an array node of the given length. + /// </summary> + /// <param name="length">The length of the array to come.</param> + void BeginArrayNode(long length); + + /// <summary> + /// Ends the current array node, if the current node is an array node. + /// </summary> + void EndArrayNode(); + + /// <summary> + /// Writes a primitive array to the stream. + /// </summary> + /// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)"/>.</typeparam> + /// <param name="array">The primitive array to write.</param> + void WritePrimitiveArray<T>(T[] array) where T : struct; + + /// <summary> + /// Writes a null value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + void WriteNull(string name); + + /// <summary> + /// Writes an internal reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="id">The value to write.</param> + void WriteInternalReference(string name, int id); + + /// <summary> + /// Writes an external index reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="index">The value to write.</param> + void WriteExternalReference(string name, int index); + + /// <summary> + /// Writes an external guid reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="guid">The value to write.</param> + void WriteExternalReference(string name, Guid guid); + + /// <summary> + /// Writes an external string reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="id">The value to write.</param> + void WriteExternalReference(string name, string id); + + /// <summary> + /// Writes a <see cref="char"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteChar(string name, char value); + + /// <summary> + /// Writes a <see cref="string"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteString(string name, string value); + + /// <summary> + /// Writes a <see cref="Guid"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteGuid(string name, Guid value); + + /// <summary> + /// Writes an <see cref="sbyte"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteSByte(string name, sbyte value); + + /// <summary> + /// Writes a <see cref="short"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteInt16(string name, short value); + + /// <summary> + /// Writes an <see cref="int"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteInt32(string name, int value); + + /// <summary> + /// Writes a <see cref="long"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteInt64(string name, long value); + + /// <summary> + /// Writes a <see cref="byte"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteByte(string name, byte value); + + /// <summary> + /// Writes an <see cref="ushort"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteUInt16(string name, ushort value); + + /// <summary> + /// Writes an <see cref="uint"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteUInt32(string name, uint value); + + /// <summary> + /// Writes an <see cref="ulong"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteUInt64(string name, ulong value); + + /// <summary> + /// Writes a <see cref="decimal"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteDecimal(string name, decimal value); + + /// <summary> + /// Writes a <see cref="float"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteSingle(string name, float value); + + /// <summary> + /// Writes a <see cref="double"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteDouble(string name, double value); + + /// <summary> + /// Writes a <see cref="bool"/> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + void WriteBoolean(string name, bool value); + + /// <summary> + /// Tells the writer that a new serialization session is about to begin, and that it should clear all cached values left over from any prior serialization sessions. + /// This method is only relevant when the same writer is used to serialize several different, unrelated values. + /// </summary> + void PrepareNewSerializationSession(); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataWriter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataWriter.cs.meta new file mode 100644 index 00000000..ad9efa79 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/IDataWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af6696e41807b3c3f9a1d73667f76701 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json.meta new file mode 100644 index 00000000..b3f2dacc --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 477f18f35286a8c4f9c16c6b5336cf6e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonConfig.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonConfig.cs new file mode 100644 index 00000000..91a2611f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonConfig.cs @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------- +// <copyright file="JsonConfig.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Contains various string constants used by the <see cref="JsonDataWriter"/>, <see cref="JsonDataReader"/> and <see cref="JsonTextReader"/> classes. + /// </summary> + public static class JsonConfig + { + /// <summary> + /// The named of a node id entry. + /// </summary> + public const string ID_SIG = "$id"; + + /// <summary> + /// The name of a type entry. + /// </summary> + public const string TYPE_SIG = "$type"; + + /// <summary> + /// The name of a regular array length entry. + /// </summary> + public const string REGULAR_ARRAY_LENGTH_SIG = "$rlength"; + + /// <summary> + /// The name of a primitive array length entry. + /// </summary> + public const string PRIMITIVE_ARRAY_LENGTH_SIG = "$plength"; + + /// <summary> + /// The name of a regular array content entry. + /// </summary> + public const string REGULAR_ARRAY_CONTENT_SIG = "$rcontent"; + + /// <summary> + /// The name of a primitive array content entry. + /// </summary> + public const string PRIMITIVE_ARRAY_CONTENT_SIG = "$pcontent"; + + /// <summary> + /// The beginning of the content of an internal reference entry. + /// </summary> + public const string INTERNAL_REF_SIG = "$iref"; + + /// <summary> + /// The beginning of the content of an external reference by index entry. + /// </summary> + public const string EXTERNAL_INDEX_REF_SIG = "$eref"; + + /// <summary> + /// The beginning of the content of an external reference by guid entry. + /// </summary> + public const string EXTERNAL_GUID_REF_SIG = "$guidref"; + + /// <summary> + /// The beginning of the content of an external reference by string entry. + /// </summary> + public const string EXTERNAL_STRING_REF_SIG = "$strref"; + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonConfig.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonConfig.cs.meta new file mode 100644 index 00000000..2fce2d37 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a3a6dce9e0b8317b3804b35f48f6a97 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataReader.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataReader.cs new file mode 100644 index 00000000..980ff710 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataReader.cs @@ -0,0 +1,1270 @@ +//----------------------------------------------------------------------- +// <copyright file="JsonDataReader.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Text; + + /// <summary> + /// Reads json data from a stream that has been written by a <see cref="JsonDataWriter"/>. + /// </summary> + /// <seealso cref="BaseDataReader" /> + public class JsonDataReader : BaseDataReader + { + private JsonTextReader reader; + private EntryType? peekedEntryType; + private string peekedEntryName; + private string peekedEntryContent; + private Dictionary<int, Type> seenTypes = new Dictionary<int, Type>(16); + + private readonly Dictionary<Type, Delegate> primitiveArrayReaders; + + public JsonDataReader() : this(null, null) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="JsonDataReader" /> class. + /// </summary> + /// <param name="stream">The base stream of the reader.</param> + /// <param name="context">The deserialization context to use.</param> + public JsonDataReader(Stream stream, DeserializationContext context) : base(stream, context) + { + this.primitiveArrayReaders = new Dictionary<Type, Delegate>() + { + { typeof(char), (Func<char>)(() => { char v; this.ReadChar(out v); return v; }) }, + { typeof(sbyte), (Func<sbyte>)(() => { sbyte v; this.ReadSByte(out v); return v; }) }, + { typeof(short), (Func<short>)(() => { short v; this.ReadInt16(out v); return v; }) }, + { typeof(int), (Func<int>)(() => { int v; this.ReadInt32(out v); return v; }) }, + { typeof(long), (Func<long>)(() => { long v; this.ReadInt64(out v); return v; }) }, + { typeof(byte), (Func<byte>)(() => { byte v; this.ReadByte(out v); return v; }) }, + { typeof(ushort), (Func<ushort>)(() => { ushort v; this.ReadUInt16(out v); return v; }) }, + { typeof(uint), (Func<uint>)(() => { uint v; this.ReadUInt32(out v); return v; }) }, + { typeof(ulong), (Func<ulong>)(() => { ulong v; this.ReadUInt64(out v); return v; }) }, + { typeof(decimal), (Func<decimal>)(() => { decimal v; this.ReadDecimal(out v); return v; }) }, + { typeof(bool), (Func<bool>)(() => { bool v; this.ReadBoolean(out v); return v; }) }, + { typeof(float), (Func<float>)(() => { float v; this.ReadSingle(out v); return v; }) }, + { typeof(double), (Func<double>)(() => { double v; this.ReadDouble(out v); return v; }) }, + { typeof(Guid), (Func<Guid>)(() => { Guid v; this.ReadGuid(out v); return v; }) } + }; + } + +#if !UNITY_EDITOR // This causes a warning when using source in Unity +#pragma warning disable IDE0009 // Member access should be qualified. +#endif + + /// <summary> + /// Gets or sets the base stream of the reader. + /// </summary> + /// <value> + /// The base stream of the reader. + /// </value> + public override Stream Stream + { + get + { + return base.Stream; + } + + set + { + base.Stream = value; + this.reader = new JsonTextReader(base.Stream, this.Context); + } + } + +#if !UNITY_EDITOR +#pragma warning restore IDE0009 // Member access should be qualified. +#endif + + /// <summary> + /// Disposes all resources kept by the data reader, except the stream, which can be reused later. + /// </summary> + public override void Dispose() + { + this.reader.Dispose(); + } + + /// <summary> + /// Peeks ahead and returns the type of the next entry in the stream. + /// </summary> + /// <param name="name">The name of the next entry, if it has one.</param> + /// <returns> + /// The type of the next entry. + /// </returns> + public override EntryType PeekEntry(out string name) + { + if (this.peekedEntryType != null) + { + name = this.peekedEntryName; + return this.peekedEntryType.Value; + } + + EntryType entry; + this.reader.ReadToNextEntry(out name, out this.peekedEntryContent, out entry); + this.peekedEntryName = name; + this.peekedEntryType = entry; + + return entry; + } + + /// <summary> + /// Tries to enter a node. This will succeed if the next entry is an <see cref="EntryType.StartOfNode" />. + /// <para /> + /// This call MUST (eventually) be followed by a corresponding call to <see cref="IDataReader.ExitNode(DeserializationContext)" /><para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode" />, <see cref="IDataReader.CurrentNodeName" />, <see cref="IDataReader.CurrentNodeId" /> and <see cref="IDataReader.CurrentNodeDepth" /> properties to the correct values for the current node. + /// </summary> + /// <param name="type">The type of the node. This value will be null if there was no metadata, or if the reader's serialization binder failed to resolve the type name.</param> + /// <returns> + /// <c>true</c> if entering a node succeeded, otherwise <c>false</c> + /// </returns> + public override bool EnterNode(out Type type) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.StartOfNode) + { + string nodeName = this.peekedEntryName; + int id = -1; + + this.ReadToNextEntry(); + + if (this.peekedEntryName == JsonConfig.ID_SIG) + { + if (int.TryParse(this.peekedEntryContent, NumberStyles.Any, CultureInfo.InvariantCulture, out id) == false) + { + this.Context.Config.DebugContext.LogError("Failed to parse id: " + this.peekedEntryContent); + id = -1; + } + + this.ReadToNextEntry(); + } + + if (this.peekedEntryName == JsonConfig.TYPE_SIG && this.peekedEntryContent != null && this.peekedEntryContent.Length > 0) + { + if (this.peekedEntryType == EntryType.Integer) + { + int typeID; + + if (this.ReadInt32(out typeID)) + { + if (this.seenTypes.TryGetValue(typeID, out type) == false) + { + this.Context.Config.DebugContext.LogError("Missing type id for node with reference id " + id + ": " + typeID); + } + } + else + { + this.Context.Config.DebugContext.LogError("Failed to read type id for node with reference id " + id); + type = null; + } + } + else + { + int typeNameStartIndex = 1; + int typeID = -1; + int idSplitIndex = this.peekedEntryContent.IndexOf('|'); + + if (idSplitIndex >= 0) + { + typeNameStartIndex = idSplitIndex + 1; + string idStr = this.peekedEntryContent.Substring(1, idSplitIndex - 1); + + if (int.TryParse(idStr, NumberStyles.Any, CultureInfo.InvariantCulture, out typeID) == false) + { + typeID = -1; + } + } + + type = this.Context.Binder.BindToType(this.peekedEntryContent.Substring(typeNameStartIndex, this.peekedEntryContent.Length - (1 + typeNameStartIndex)), this.Context.Config.DebugContext); + + if (typeID >= 0) + { + this.seenTypes[typeID] = type; + } + + this.peekedEntryType = null; + } + } + else + { + type = null; + } + + this.PushNode(nodeName, id, type); + return true; + } + else + { + this.SkipEntry(); + type = null; + return false; + } + } + + /// <summary> + /// Exits the current node. This method will keep skipping entries using <see cref="IDataReader.SkipEntry(DeserializationContext)" /> until an <see cref="EntryType.EndOfNode" /> is reached, or the end of the stream is reached. + /// <para /> + /// This call MUST have been preceded by a corresponding call to <see cref="IDataReader.EnterNode(out Type)" />. + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode" />, <see cref="IDataReader.CurrentNodeName" />, <see cref="IDataReader.CurrentNodeId" /> and <see cref="IDataReader.CurrentNodeDepth" /> to the correct values for the node that was prior to the current node. + /// </summary> + /// <returns> + /// <c>true</c> if the method exited a node, <c>false</c> if it reached the end of the stream. + /// </returns> + public override bool ExitNode() + { + this.PeekEntry(); + + // Read to next end of node + while (this.peekedEntryType != EntryType.EndOfNode && this.peekedEntryType != EntryType.EndOfStream) + { + if (this.peekedEntryType == EntryType.EndOfArray) + { + this.Context.Config.DebugContext.LogError("Data layout mismatch; skipping past array boundary when exiting node."); + this.peekedEntryType = null; + //this.MarkEntryConsumed(); + } + + this.SkipEntry(); + } + + if (this.peekedEntryType == EntryType.EndOfNode) + { + this.peekedEntryType = null; + this.PopNode(this.CurrentNodeName); + return true; + } + + return false; + } + + /// <summary> + /// Tries to enters an array node. This will succeed if the next entry is an <see cref="EntryType.StartOfArray" />. + /// <para /> + /// This call MUST (eventually) be followed by a corresponding call to <see cref="IDataReader.ExitArray(DeserializationContext)" /><para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode" />, <see cref="IDataReader.CurrentNodeName" />, <see cref="IDataReader.CurrentNodeId" /> and <see cref="IDataReader.CurrentNodeDepth" /> properties to the correct values for the current array node. + /// </summary> + /// <param name="length">The length of the array that was entered.</param> + /// <returns> + /// <c>true</c> if an array was entered, otherwise <c>false</c> + /// </returns> + public override bool EnterArray(out long length) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.StartOfArray) + { + this.PushArray(); + + if (this.peekedEntryName != JsonConfig.REGULAR_ARRAY_LENGTH_SIG) + { + this.Context.Config.DebugContext.LogError("Array entry wasn't preceded by an array length entry!"); + length = 0; // No array content for you! + return true; + } + else + { + int intLength; + + if (int.TryParse(this.peekedEntryContent, NumberStyles.Any, CultureInfo.InvariantCulture, out intLength) == false) + { + this.Context.Config.DebugContext.LogError("Failed to parse array length: " + this.peekedEntryContent); + length = 0; // No array content for you! + return true; + } + + length = intLength; + + this.ReadToNextEntry(); + + if (this.peekedEntryName != JsonConfig.REGULAR_ARRAY_CONTENT_SIG) + { + this.Context.Config.DebugContext.LogError("Failed to find regular array content entry after array length entry!"); + length = 0; // No array content for you! + return true; + } + + this.peekedEntryType = null; + return true; + } + } + else + { + this.SkipEntry(); + length = 0; + return false; + } + } + + /// <summary> + /// Exits the closest array. This method will keep skipping entries using <see cref="IDataReader.SkipEntry(DeserializationContext)" /> until an <see cref="EntryType.EndOfArray" /> is reached, or the end of the stream is reached. + /// <para /> + /// This call MUST have been preceded by a corresponding call to <see cref="IDataReader.EnterArray(out long)" />. + /// <para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode" />, <see cref="IDataReader.CurrentNodeName" />, <see cref="IDataReader.CurrentNodeId" /> and <see cref="IDataReader.CurrentNodeDepth" /> to the correct values for the node that was prior to the exited array node. + /// </summary> + /// <returns> + /// <c>true</c> if the method exited an array, <c>false</c> if it reached the end of the stream. + /// </returns> + public override bool ExitArray() + { + this.PeekEntry(); + + // Read to next end of array + while (this.peekedEntryType != EntryType.EndOfArray && this.peekedEntryType != EntryType.EndOfStream) + { + if (this.peekedEntryType == EntryType.EndOfNode) + { + this.Context.Config.DebugContext.LogError("Data layout mismatch; skipping past node boundary when exiting array."); + this.peekedEntryType = null; + //this.MarkEntryConsumed(); + } + + this.SkipEntry(); + } + + if (this.peekedEntryType == EntryType.EndOfArray) + { + this.peekedEntryType = null; + this.PopArray(); + return true; + } + + return false; + } + + /// <summary> + /// Reads a primitive array value. This call will succeed if the next entry is an <see cref="EntryType.PrimitiveArray" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)" />.</typeparam> + /// <param name="array">The resulting primitive array.</param> + /// <returns> + /// <c>true</c> if reading a primitive array succeeded, otherwise <c>false</c> + /// </returns> + /// <exception cref="System.ArgumentException">Type + typeof(T).Name + is not a valid primitive array type.</exception> + public override bool ReadPrimitiveArray<T>(out T[] array) + { + if (FormatterUtilities.IsPrimitiveArrayType(typeof(T)) == false) + { + throw new ArgumentException("Type " + typeof(T).Name + " is not a valid primitive array type."); + } + + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.PrimitiveArray) + { + this.PushArray(); + + if (this.peekedEntryName != JsonConfig.PRIMITIVE_ARRAY_LENGTH_SIG) + { + this.Context.Config.DebugContext.LogError("Array entry wasn't preceded by an array length entry!"); + array = null; // No array content for you! + return false; + } + else + { + int intLength; + + if (int.TryParse(this.peekedEntryContent, NumberStyles.Any, CultureInfo.InvariantCulture, out intLength) == false) + { + this.Context.Config.DebugContext.LogError("Failed to parse array length: " + this.peekedEntryContent); + array = null; // No array content for you! + return false; + } + + this.ReadToNextEntry(); + + if (this.peekedEntryName != JsonConfig.PRIMITIVE_ARRAY_CONTENT_SIG) + { + this.Context.Config.DebugContext.LogError("Failed to find primitive array content entry after array length entry!"); + array = null; // No array content for you! + return false; + } + + this.peekedEntryType = null; + + Func<T> reader = (Func<T>)this.primitiveArrayReaders[typeof(T)]; + array = new T[intLength]; + + for (int i = 0; i < intLength; i++) + { + array[i] = reader(); + } + + this.ExitArray(); + return true; + } + } + else + { + this.SkipEntry(); + array = null; + return false; + } + } + + /// <summary> + /// Reads a <see cref="bool" /> value. This call will succeed if the next entry is an <see cref="EntryType.Boolean" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadBoolean(out bool value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.Boolean) + { + try + { + value = this.peekedEntryContent == "true"; + return true; + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + value = default(bool); + return false; + } + } + + /// <summary> + /// Reads an internal reference id. This call will succeed if the next entry is an <see cref="EntryType.InternalReference" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="id">The internal reference id.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadInternalReference(out int id) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.InternalReference) + { + try + { + return this.ReadAnyIntReference(out id); + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + id = -1; + return false; + } + } + + /// <summary> + /// Reads an external reference index. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByIndex" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="index">The external reference index.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadExternalReference(out int index) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.ExternalReferenceByIndex) + { + try + { + return this.ReadAnyIntReference(out index); + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + index = -1; + return false; + } + } + + /// <summary> + /// Reads an external reference guid. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByGuid" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="guid">The external reference guid.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadExternalReference(out Guid guid) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.ExternalReferenceByGuid) + { + var guidStr = this.peekedEntryContent; + + if (guidStr.StartsWith(JsonConfig.EXTERNAL_GUID_REF_SIG)) + { + guidStr = guidStr.Substring(JsonConfig.EXTERNAL_GUID_REF_SIG.Length + 1); + } + + try + { + guid = new Guid(guidStr); + return true; + } + catch (FormatException) + { + guid = Guid.Empty; + return false; + } + catch (OverflowException) + { + guid = Guid.Empty; + return false; + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + guid = Guid.Empty; + return false; + } + } + + /// <summary> + /// Reads an external reference string. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByString" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="id">The external reference string.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadExternalReference(out string id) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.ExternalReferenceByString) + { + id = this.peekedEntryContent; + + if (id.StartsWith(JsonConfig.EXTERNAL_STRING_REF_SIG)) + { + id = id.Substring(JsonConfig.EXTERNAL_STRING_REF_SIG.Length + 1); + } + + this.MarkEntryConsumed(); + return true; + } + else + { + this.SkipEntry(); + id = null; + return false; + } + } + + /// <summary> + /// Reads a <see cref="char" /> value. This call will succeed if the next entry is an <see cref="EntryType.String" />. + /// <para /> + /// If the string of the entry is longer than 1 character, the first character of the string will be taken as the result. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadChar(out char value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.String) + { + try + { + value = this.peekedEntryContent[1]; + return true; + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + value = default(char); + return false; + } + } + + /// <summary> + /// Reads a <see cref="string" /> value. This call will succeed if the next entry is an <see cref="EntryType.String" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadString(out string value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.String) + { + try + { + value = this.peekedEntryContent.Substring(1, this.peekedEntryContent.Length - 2); + return true; + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + value = null; + return false; + } + } + + /// <summary> + /// Reads a <see cref="Guid" /> value. This call will succeed if the next entry is an <see cref="EntryType.Guid" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadGuid(out Guid value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.Guid) + { + try + { + try + { + value = new Guid(this.peekedEntryContent); + return true; + } + //// These exceptions can safely be swallowed - it just means the parse failed + catch (FormatException) + { + value = Guid.Empty; + return false; + } + catch (OverflowException) + { + value = Guid.Empty; + return false; + } + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + value = Guid.Empty; + return false; + } + } + + /// <summary> + /// Reads an <see cref="sbyte" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="sbyte.MinValue" /> or larger than <see cref="sbyte.MaxValue" />, the result will be default(<see cref="sbyte" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadSByte(out sbyte value) + { + long longValue; + + if (this.ReadInt64(out longValue)) + { + checked + { + try + { + value = (sbyte)longValue; + } + catch (OverflowException) + { + value = default(sbyte); + } + } + + return true; + } + + value = default(sbyte); + return false; + } + + /// <summary> + /// Reads a <see cref="short" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="short.MinValue" /> or larger than <see cref="short.MaxValue" />, the result will be default(<see cref="short" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadInt16(out short value) + { + long longValue; + + if (this.ReadInt64(out longValue)) + { + checked + { + try + { + value = (short)longValue; + } + catch (OverflowException) + { + value = default(short); + } + } + + return true; + } + + value = default(short); + return false; + } + + /// <summary> + /// Reads an <see cref="int" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="int.MinValue" /> or larger than <see cref="int.MaxValue" />, the result will be default(<see cref="int" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadInt32(out int value) + { + long longValue; + + if (this.ReadInt64(out longValue)) + { + checked + { + try + { + value = (int)longValue; + } + catch (OverflowException) + { + value = default(int); + } + } + + return true; + } + + value = default(int); + return false; + } + + /// <summary> + /// Reads a <see cref="long" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="long.MinValue" /> or larger than <see cref="long.MaxValue" />, the result will be default(<see cref="long" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadInt64(out long value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.Integer) + { + try + { + if (long.TryParse(this.peekedEntryContent, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + return true; + } + else + { + this.Context.Config.DebugContext.LogError("Failed to parse long from: " + this.peekedEntryContent); + return false; + } + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + value = default(long); + return false; + } + } + + /// <summary> + /// Reads a <see cref="byte" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="byte.MinValue" /> or larger than <see cref="byte.MaxValue" />, the result will be default(<see cref="byte" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadByte(out byte value) + { + ulong ulongValue; + + if (this.ReadUInt64(out ulongValue)) + { + checked + { + try + { + value = (byte)ulongValue; + } + catch (OverflowException) + { + value = default(byte); + } + } + + return true; + } + + value = default(byte); + return false; + } + + /// <summary> + /// Reads an <see cref="ushort" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="ushort.MinValue" /> or larger than <see cref="ushort.MaxValue" />, the result will be default(<see cref="ushort" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadUInt16(out ushort value) + { + ulong ulongValue; + + if (this.ReadUInt64(out ulongValue)) + { + checked + { + try + { + value = (ushort)ulongValue; + } + catch (OverflowException) + { + value = default(ushort); + } + } + + return true; + } + + value = default(ushort); + return false; + } + + /// <summary> + /// Reads an <see cref="uint" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="uint.MinValue" /> or larger than <see cref="uint.MaxValue" />, the result will be default(<see cref="uint" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadUInt32(out uint value) + { + ulong ulongValue; + + if (this.ReadUInt64(out ulongValue)) + { + checked + { + try + { + value = (uint)ulongValue; + } + catch (OverflowException) + { + value = default(uint); + } + } + + return true; + } + + value = default(uint); + return false; + } + + /// <summary> + /// Reads an <see cref="ulong" /> value. This call will succeed if the next entry is an <see cref="EntryType.Integer" />. + /// <para /> + /// If the value of the stored integer is smaller than <see cref="ulong.MinValue" /> or larger than <see cref="ulong.MaxValue" />, the result will be default(<see cref="ulong" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadUInt64(out ulong value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.Integer) + { + try + { + if (ulong.TryParse(this.peekedEntryContent, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + return true; + } + else + { + this.Context.Config.DebugContext.LogError("Failed to parse ulong from: " + this.peekedEntryContent); + return false; + } + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + value = default(ulong); + return false; + } + } + + /// <summary> + /// Reads a <see cref="decimal" /> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint" /> or an <see cref="EntryType.Integer" />. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="decimal.MinValue" /> or larger than <see cref="decimal.MaxValue" />, the result will be default(<see cref="decimal" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadDecimal(out decimal value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.FloatingPoint || this.peekedEntryType == EntryType.Integer) + { + try + { + if (decimal.TryParse(this.peekedEntryContent, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + return true; + } + else + { + this.Context.Config.DebugContext.LogError("Failed to parse decimal from: " + this.peekedEntryContent); + return false; + } + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + value = default(decimal); + return false; + } + } + + /// <summary> + /// Reads a <see cref="float" /> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint" /> or an <see cref="EntryType.Integer" />. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="float.MinValue" /> or larger than <see cref="float.MaxValue" />, the result will be default(<see cref="float" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadSingle(out float value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.FloatingPoint || this.peekedEntryType == EntryType.Integer) + { + try + { + if (float.TryParse(this.peekedEntryContent, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + return true; + } + else + { + this.Context.Config.DebugContext.LogError("Failed to parse float from: " + this.peekedEntryContent); + return false; + } + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + value = default(float); + return false; + } + } + + /// <summary> + /// Reads a <see cref="double" /> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint" /> or an <see cref="EntryType.Integer" />. + /// <para /> + /// If the stored integer or floating point value is smaller than <see cref="double.MinValue" /> or larger than <see cref="double.MaxValue" />, the result will be default(<see cref="double" />). + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <param name="value">The value that has been read.</param> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadDouble(out double value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.FloatingPoint || this.peekedEntryType == EntryType.Integer) + { + try + { + if (double.TryParse(this.peekedEntryContent, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + return true; + } + else + { + this.Context.Config.DebugContext.LogError("Failed to parse double from: " + this.peekedEntryContent); + return false; + } + } + finally + { + this.MarkEntryConsumed(); + } + } + else + { + this.SkipEntry(); + value = default(double); + return false; + } + } + + /// <summary> + /// Reads a <c>null</c> value. This call will succeed if the next entry is an <see cref="EntryType.Null" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <returns> + /// <c>true</c> if reading the value succeeded, otherwise <c>false</c> + /// </returns> + public override bool ReadNull() + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.Null) + { + this.MarkEntryConsumed(); + return true; + } + else + { + this.SkipEntry(); + return false; + } + } + + /// <summary> + /// Tells the reader that a new serialization session is about to begin, and that it should clear all cached values left over from any prior serialization sessions. + /// This method is only relevant when the same reader is used to deserialize several different, unrelated values. + /// </summary> + public override void PrepareNewSerializationSession() + { + base.PrepareNewSerializationSession(); + this.peekedEntryType = null; + this.peekedEntryContent = null; + this.peekedEntryName = null; + this.seenTypes.Clear(); + this.reader.Reset(); + } + + public override string GetDataDump() + { + if (!this.Stream.CanSeek) + { + return "Json data stream cannot seek; cannot dump data."; + } + + var oldPosition = this.Stream.Position; + + var bytes = new byte[this.Stream.Length]; + + this.Stream.Position = 0; + this.Stream.Read(bytes, 0, bytes.Length); + + this.Stream.Position = oldPosition; + + return "Json: " + Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + + /// <summary> + /// Peeks the current entry. + /// </summary> + /// <returns>The peeked entry.</returns> + protected override EntryType PeekEntry() + { + string name; + return this.PeekEntry(out name); + } + + /// <summary> + /// Consumes the current entry, and reads to the next one. + /// </summary> + /// <returns>The next entry.</returns> + protected override EntryType ReadToNextEntry() + { + this.peekedEntryType = null; + string name; + return this.PeekEntry(out name); + } + + private void MarkEntryConsumed() + { + // After a common read, we cannot skip EndOfArray and EndOfNode entries (meaning the read has failed), + // as only the ExitArray and ExitNode methods are allowed to exit nodes and arrays + if (this.peekedEntryType != EntryType.EndOfArray && this.peekedEntryType != EntryType.EndOfNode) + { + this.peekedEntryType = null; + } + } + + private bool ReadAnyIntReference(out int value) + { + int separatorIndex = -1; + + for (int i = 0; i < this.peekedEntryContent.Length; i++) + { + if (this.peekedEntryContent[i] == ':') + { + separatorIndex = i; + break; + } + } + + if (separatorIndex == -1 || separatorIndex == this.peekedEntryContent.Length - 1) + { + this.Context.Config.DebugContext.LogError("Failed to parse id from: " + this.peekedEntryContent); + } + + string idStr = this.peekedEntryContent.Substring(separatorIndex + 1); + + if (int.TryParse(idStr, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + return true; + } + else + { + this.Context.Config.DebugContext.LogError("Failed to parse id: " + idStr); + } + + value = -1; + return false; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataReader.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataReader.cs.meta new file mode 100644 index 00000000..6dcb6c64 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2ecc39ef0dc55ec10f83bb7eefd4f1db +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataWriter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataWriter.cs new file mode 100644 index 00000000..2eac5997 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataWriter.cs @@ -0,0 +1,749 @@ +//----------------------------------------------------------------------- +// <copyright file="JsonDataWriter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Text; + + /// <summary> + /// Writes json data to a stream that can be read by a <see cref="JsonDataReader"/>. + /// </summary> + /// <seealso cref="BaseDataWriter" /> + public class JsonDataWriter : BaseDataWriter + { + private static readonly uint[] ByteToHexCharLookup = CreateByteToHexLookup(); + private static readonly string NEW_LINE = Environment.NewLine; + + private bool justStarted; + private bool forceNoSeparatorNextLine; + + //private StringBuilder escapeStringBuilder; + //private StreamWriter writer; + + private Dictionary<Type, Delegate> primitiveTypeWriters; + private Dictionary<Type, int> seenTypes = new Dictionary<Type, int>(16); + + private byte[] buffer = new byte[1024 * 100]; + private int bufferIndex = 0; + + public JsonDataWriter() : this(null, null, true) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="JsonDataWriter" /> class. + /// </summary> + /// <param name="stream">The base stream of the writer.</param> + /// <param name="context">The serialization context to use.</param>> + /// <param name="formatAsReadable">Whether the json should be packed, or formatted as human-readable.</param> + public JsonDataWriter(Stream stream, SerializationContext context, bool formatAsReadable = true) : base(stream, context) + { + this.FormatAsReadable = formatAsReadable; + this.justStarted = true; + this.EnableTypeOptimization = true; + + this.primitiveTypeWriters = new Dictionary<Type, Delegate>() + { + { typeof(char), (Action<string, char>)this.WriteChar }, + { typeof(sbyte), (Action<string, sbyte>)this.WriteSByte }, + { typeof(short), (Action<string, short>)this.WriteInt16 }, + { typeof(int), (Action<string, int>)this.WriteInt32 }, + { typeof(long), (Action<string, long>)this.WriteInt64 }, + { typeof(byte), (Action<string, byte>)this.WriteByte }, + { typeof(ushort), (Action<string, ushort>)this.WriteUInt16 }, + { typeof(uint), (Action<string, uint>)this.WriteUInt32 }, + { typeof(ulong), (Action<string, ulong>)this.WriteUInt64 }, + { typeof(decimal), (Action<string, decimal>)this.WriteDecimal }, + { typeof(bool), (Action<string, bool>)this.WriteBoolean }, + { typeof(float), (Action<string, float>)this.WriteSingle }, + { typeof(double), (Action<string, double>)this.WriteDouble }, + { typeof(Guid), (Action<string, Guid>)this.WriteGuid } + }; + } + + /// <summary> + /// Gets or sets a value indicating whether the json should be packed, or formatted as human-readable. + /// </summary> + /// <value> + /// <c>true</c> if the json should be formatted as human-readable; otherwise, <c>false</c>. + /// </value> + public bool FormatAsReadable; + + /// <summary> + /// Whether to enable an optimization that ensures any given type name is only written once into the json stream, and thereafter kept track of by ID. + /// </summary> + public bool EnableTypeOptimization; + + /// <summary> + /// Enable the "just started" flag, causing the writer to start a new "base" json object container. + /// </summary> + public void MarkJustStarted() + { + this.justStarted = true; + } + + /// <summary> + /// Flushes everything that has been written so far to the writer's base stream. + /// </summary> + public override void FlushToStream() + { + if (this.bufferIndex > 0) + { + this.Stream.Write(this.buffer, 0, this.bufferIndex); + this.bufferIndex = 0; + } + + base.FlushToStream(); + } + + /// <summary> + /// Writes the beginning of a reference node. + /// <para /> + /// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)" />, with the same name. + /// </summary> + /// <param name="name">The name of the reference node.</param> + /// <param name="type">The type of the reference node. If null, no type metadata will be written.</param> + /// <param name="id">The id of the reference node. This id is acquired by calling <see cref="SerializationContext.TryRegisterInternalReference(object, out int)" />.</param> + public override void BeginReferenceNode(string name, Type type, int id) + { + this.WriteEntry(name, "{"); + this.PushNode(name, id, type); + this.forceNoSeparatorNextLine = true; + this.WriteInt32(JsonConfig.ID_SIG, id); + + if (type != null) + { + this.WriteTypeEntry(type); + } + } + + /// <summary> + /// Begins a struct/value type node. This is essentially the same as a reference node, except it has no internal reference id. + /// <para /> + /// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)" />, with the same name. + /// </summary> + /// <param name="name">The name of the struct node.</param> + /// <param name="type">The type of the struct node. If null, no type metadata will be written.</param> + public override void BeginStructNode(string name, Type type) + { + this.WriteEntry(name, "{"); + this.PushNode(name, -1, type); + this.forceNoSeparatorNextLine = true; + + if (type != null) + { + this.WriteTypeEntry(type); + } + } + + /// <summary> + /// Ends the current node with the given name. If the current node has another name, an <see cref="InvalidOperationException" /> is thrown. + /// </summary> + /// <param name="name">The name of the node to end. This has to be the name of the current node.</param> + public override void EndNode(string name) + { + this.PopNode(name); + this.StartNewLine(true); + + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)'}'; + } + + /// <summary> + /// Begins an array node of the given length. + /// </summary> + /// <param name="length">The length of the array to come.</param> + public override void BeginArrayNode(long length) + { + this.WriteInt64(JsonConfig.REGULAR_ARRAY_LENGTH_SIG, length); + this.WriteEntry(JsonConfig.REGULAR_ARRAY_CONTENT_SIG, "["); + this.forceNoSeparatorNextLine = true; + this.PushArray(); + } + + /// <summary> + /// Ends the current array node, if the current node is an array node. + /// </summary> + public override void EndArrayNode() + { + this.PopArray(); + this.StartNewLine(true); + + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)']'; + } + + /// <summary> + /// Writes a primitive array to the stream. + /// </summary> + /// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)" />.</typeparam> + /// <param name="array">The primitive array to write.</param> + /// <exception cref="System.ArgumentException">Type + typeof(T).Name + is not a valid primitive array type.</exception> + /// <exception cref="System.ArgumentNullException">array</exception> + public override void WritePrimitiveArray<T>(T[] array) + { + if (FormatterUtilities.IsPrimitiveArrayType(typeof(T)) == false) + { + throw new ArgumentException("Type " + typeof(T).Name + " is not a valid primitive array type."); + } + + if (array == null) + { + throw new ArgumentNullException("array"); + } + + Action<string, T> writer = (Action<string, T>)this.primitiveTypeWriters[typeof(T)]; + + this.WriteInt64(JsonConfig.PRIMITIVE_ARRAY_LENGTH_SIG, array.Length); + this.WriteEntry(JsonConfig.PRIMITIVE_ARRAY_CONTENT_SIG, "["); + this.forceNoSeparatorNextLine = true; + this.PushArray(); + + for (int i = 0; i < array.Length; i++) + { + writer(null, array[i]); + } + + this.PopArray(); + this.StartNewLine(true); + + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)']'; + } + + /// <summary> + /// Writes a <see cref="bool" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteBoolean(string name, bool value) + { + this.WriteEntry(name, value ? "true" : "false"); + } + + /// <summary> + /// Writes a <see cref="byte" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteByte(string name, byte value) + { + this.WriteUInt64(name, value); + } + + /// <summary> + /// Writes a <see cref="char" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteChar(string name, char value) + { + this.WriteString(name, value.ToString(CultureInfo.InvariantCulture)); + } + + /// <summary> + /// Writes a <see cref="decimal" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteDecimal(string name, decimal value) + { + this.WriteEntry(name, value.ToString("G", CultureInfo.InvariantCulture)); + } + + /// <summary> + /// Writes a <see cref="double" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteDouble(string name, double value) + { + this.WriteEntry(name, value.ToString("R", CultureInfo.InvariantCulture)); + } + + /// <summary> + /// Writes an <see cref="int" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteInt32(string name, int value) + { + this.WriteInt64(name, value); + } + + /// <summary> + /// Writes a <see cref="long" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteInt64(string name, long value) + { + this.WriteEntry(name, value.ToString("D", CultureInfo.InvariantCulture)); + } + + /// <summary> + /// Writes a null value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + public override void WriteNull(string name) + { + this.WriteEntry(name, "null"); + } + + /// <summary> + /// Writes an internal reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="id">The value to write.</param> + public override void WriteInternalReference(string name, int id) + { + this.WriteEntry(name, JsonConfig.INTERNAL_REF_SIG + ":" + id.ToString("D", CultureInfo.InvariantCulture)); + } + + /// <summary> + /// Writes an <see cref="sbyte" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteSByte(string name, sbyte value) + { + this.WriteInt64(name, value); + } + + /// <summary> + /// Writes a <see cref="short" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteInt16(string name, short value) + { + this.WriteInt64(name, value); + } + + /// <summary> + /// Writes a <see cref="float" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteSingle(string name, float value) + { + this.WriteEntry(name, value.ToString("R", CultureInfo.InvariantCulture)); + } + + /// <summary> + /// Writes a <see cref="string" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteString(string name, string value) + { + this.StartNewLine(); + + if (name != null) + { + this.EnsureBufferSpace(name.Length + value.Length + 6); + + this.buffer[this.bufferIndex++] = (byte)'"'; + + for (int i = 0; i < name.Length; i++) + { + this.buffer[this.bufferIndex++] = (byte)name[i]; + } + + this.buffer[this.bufferIndex++] = (byte)'"'; + this.buffer[this.bufferIndex++] = (byte)':'; + + if (this.FormatAsReadable) + { + this.buffer[this.bufferIndex++] = (byte)' '; + } + } + else this.EnsureBufferSpace(value.Length + 2); + + this.buffer[this.bufferIndex++] = (byte)'"'; + + this.Buffer_WriteString_WithEscape(value); + + this.buffer[this.bufferIndex++] = (byte)'"'; + } + + /// <summary> + /// Writes a <see cref="Guid" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteGuid(string name, Guid value) + { + this.WriteEntry(name, value.ToString("D", CultureInfo.InvariantCulture)); + } + + /// <summary> + /// Writes an <see cref="uint" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteUInt32(string name, uint value) + { + this.WriteUInt64(name, value); + } + + /// <summary> + /// Writes an <see cref="ulong" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteUInt64(string name, ulong value) + { + this.WriteEntry(name, value.ToString("D", CultureInfo.InvariantCulture)); + } + + /// <summary> + /// Writes an external index reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="index">The value to write.</param> + public override void WriteExternalReference(string name, int index) + { + this.WriteEntry(name, JsonConfig.EXTERNAL_INDEX_REF_SIG + ":" + index.ToString("D", CultureInfo.InvariantCulture)); + } + + /// <summary> + /// Writes an external guid reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="guid">The value to write.</param> + public override void WriteExternalReference(string name, Guid guid) + { + this.WriteEntry(name, JsonConfig.EXTERNAL_GUID_REF_SIG + ":" + guid.ToString("D", CultureInfo.InvariantCulture)); + } + + /// <summary> + /// Writes an external string reference to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="id">The value to write.</param> + public override void WriteExternalReference(string name, string id) + { + if (id == null) + { + throw new ArgumentNullException("id"); + } + + this.WriteEntry(name, JsonConfig.EXTERNAL_STRING_REF_SIG + ":" + id); + } + + /// <summary> + /// Writes an <see cref="ushort" /> value to the stream. + /// </summary> + /// <param name="name">The name of the value. If this is null, no name will be written.</param> + /// <param name="value">The value to write.</param> + public override void WriteUInt16(string name, ushort value) + { + this.WriteUInt64(name, value); + } + + /// <summary> + /// Disposes all resources kept by the data writer, except the stream, which can be reused later. + /// </summary> + public override void Dispose() + { + //this.writer.Dispose(); + } + + /// <summary> + /// Tells the writer that a new serialization session is about to begin, and that it should clear all cached values left over from any prior serialization sessions. + /// This method is only relevant when the same writer is used to serialize several different, unrelated values. + /// </summary> + public override void PrepareNewSerializationSession() + { + base.PrepareNewSerializationSession(); + this.seenTypes.Clear(); + this.justStarted = true; + } + + public override string GetDataDump() + { + if (!this.Stream.CanRead) + { + return "Json data stream for writing cannot be read; cannot dump data."; + } + + if (!this.Stream.CanSeek) + { + return "Json data stream cannot seek; cannot dump data."; + } + + var oldPosition = this.Stream.Position; + + var bytes = new byte[oldPosition]; + + this.Stream.Position = 0; + this.Stream.Read(bytes, 0, (int)oldPosition); + + this.Stream.Position = oldPosition; + + return "Json: " + Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + + private void WriteEntry(string name, string contents) + { + this.StartNewLine(); + + if (name != null) + { + this.EnsureBufferSpace(name.Length + contents.Length + 4); + + this.buffer[this.bufferIndex++] = (byte)'"'; + + for (int i = 0; i < name.Length; i++) + { + this.buffer[this.bufferIndex++] = (byte)name[i]; + } + + this.buffer[this.bufferIndex++] = (byte)'"'; + this.buffer[this.bufferIndex++] = (byte)':'; + + if (this.FormatAsReadable) + { + this.buffer[this.bufferIndex++] = (byte)' '; + } + } + else this.EnsureBufferSpace(contents.Length); + + for (int i = 0; i < contents.Length; i++) + { + this.buffer[this.bufferIndex++] = (byte)contents[i]; + } + } + + private void WriteEntry(string name, string contents, char surroundContentsWith) + { + this.StartNewLine(); + + if (name != null) + { + this.EnsureBufferSpace(name.Length + contents.Length + 6); + + this.buffer[this.bufferIndex++] = (byte)'"'; + + for (int i = 0; i < name.Length; i++) + { + this.buffer[this.bufferIndex++] = (byte)name[i]; + } + + this.buffer[this.bufferIndex++] = (byte)'"'; + this.buffer[this.bufferIndex++] = (byte)':'; + + if (this.FormatAsReadable) + { + this.buffer[this.bufferIndex++] = (byte)' '; + } + } + else this.EnsureBufferSpace(contents.Length + 2); + + this.buffer[this.bufferIndex++] = (byte)surroundContentsWith; + + for (int i = 0; i < contents.Length; i++) + { + this.buffer[this.bufferIndex++] = (byte)contents[i]; + } + + this.buffer[this.bufferIndex++] = (byte)surroundContentsWith; + } + + private void WriteTypeEntry(Type type) + { + int id; + + if (this.EnableTypeOptimization) + { + if (this.seenTypes.TryGetValue(type, out id)) + { + this.WriteInt32(JsonConfig.TYPE_SIG, id); + } + else + { + id = this.seenTypes.Count; + this.seenTypes.Add(type, id); + this.WriteString(JsonConfig.TYPE_SIG, id + "|" + this.Context.Binder.BindToName(type, this.Context.Config.DebugContext)); + } + } + else + { + this.WriteString(JsonConfig.TYPE_SIG, this.Context.Binder.BindToName(type, this.Context.Config.DebugContext)); + } + } + + private void StartNewLine(bool noSeparator = false) + { + if (this.justStarted) + { + this.justStarted = false; + return; + } + + if (noSeparator == false && this.forceNoSeparatorNextLine == false) + { + this.EnsureBufferSpace(1); + this.buffer[this.bufferIndex++] = (byte)','; + } + + this.forceNoSeparatorNextLine = false; + + if (this.FormatAsReadable) + { + int count = this.NodeDepth * 4; + + this.EnsureBufferSpace(NEW_LINE.Length + count); + + for (int i = 0; i < NEW_LINE.Length; i++) + { + this.buffer[this.bufferIndex++] = (byte)NEW_LINE[i]; + } + + for (int i = 0; i < count; i++) + { + this.buffer[this.bufferIndex++] = (byte)' '; + } + } + } + + + private void EnsureBufferSpace(int space) + { + var length = this.buffer.Length; + + if (space > length) + { + throw new Exception("Insufficient buffer capacity"); + } + + if (this.bufferIndex + space > length) + { + this.FlushToStream(); + } + } + + private void Buffer_WriteString_WithEscape(string str) + { + this.EnsureBufferSpace(str.Length); + + for (int i = 0; i < str.Length; i++) + { + char c = str[i]; + + if (c < 0 || c > 127) + { + // We're outside the "standard" character range - so we write the character as a hexadecimal value instead + // This ensures that we don't break the Json formatting. + + this.EnsureBufferSpace((str.Length - i) + 6); + + this.buffer[this.bufferIndex++] = (byte)'\\'; + this.buffer[this.bufferIndex++] = (byte)'u'; + + var byte1 = c >> 8; + var byte2 = (byte)c; + + var lookup = ByteToHexCharLookup[byte1]; + + this.buffer[this.bufferIndex++] = (byte)lookup; + this.buffer[this.bufferIndex++] = (byte)(lookup >> 16); + + lookup = ByteToHexCharLookup[byte2]; + + this.buffer[this.bufferIndex++] = (byte)lookup; + this.buffer[this.bufferIndex++] = (byte)(lookup >> 16); + continue; + } + + this.EnsureBufferSpace(2); + + // Escape any characters that need to be escaped, default to no escape + switch (c) + { + case '"': + this.buffer[this.bufferIndex++] = (byte)'\\'; + this.buffer[this.bufferIndex++] = (byte)'"'; + break; + + case '\\': + this.buffer[this.bufferIndex++] = (byte)'\\'; + this.buffer[this.bufferIndex++] = (byte)'\\'; + break; + + case '\a': + this.buffer[this.bufferIndex++] = (byte)'\\'; + this.buffer[this.bufferIndex++] = (byte)'a'; + break; + + case '\b': + this.buffer[this.bufferIndex++] = (byte)'\\'; + this.buffer[this.bufferIndex++] = (byte)'b'; + break; + + case '\f': + this.buffer[this.bufferIndex++] = (byte)'\\'; + this.buffer[this.bufferIndex++] = (byte)'f'; + break; + + case '\n': + this.buffer[this.bufferIndex++] = (byte)'\\'; + this.buffer[this.bufferIndex++] = (byte)'n'; + break; + + case '\r': + this.buffer[this.bufferIndex++] = (byte)'\\'; + this.buffer[this.bufferIndex++] = (byte)'r'; + break; + + case '\t': + this.buffer[this.bufferIndex++] = (byte)'\\'; + this.buffer[this.bufferIndex++] = (byte)'t'; + break; + + case '\0': + this.buffer[this.bufferIndex++] = (byte)'\\'; + this.buffer[this.bufferIndex++] = (byte)'0'; + break; + + default: + this.buffer[this.bufferIndex++] = (byte)c; + break; + } + } + } + + private static uint[] CreateByteToHexLookup() + { + var result = new uint[256]; + + for (int i = 0; i < 256; i++) + { + string s = i.ToString("x2", CultureInfo.InvariantCulture); + result[i] = ((uint)s[0]) + ((uint)s[1] << 16); + } + + return result; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataWriter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataWriter.cs.meta new file mode 100644 index 00000000..2decb4a3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonDataWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3e05b98a26be61fa9203d4a45bfc1e95 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonTextReader.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonTextReader.cs new file mode 100644 index 00000000..032a84ae --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonTextReader.cs @@ -0,0 +1,741 @@ +//----------------------------------------------------------------------- +// <copyright file="JsonTextReader.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using System.IO; + + /// <summary> + /// Parses json entries from a stream. + /// </summary> + /// <seealso cref="System.IDisposable" /> + public class JsonTextReader : IDisposable + { + private static readonly Dictionary<char, EntryType?> EntryDelineators = new Dictionary<char, EntryType?> + { + { '{', EntryType.StartOfNode }, + { '}', EntryType.EndOfNode }, + { ',', null }, + { '[', EntryType.PrimitiveArray }, + { ']', EntryType.EndOfArray }, + }; + + private static readonly Dictionary<char, char> UnescapeDictionary = new Dictionary<char, char>() + { + { 'a', '\a' }, + { 'b', '\b' }, + { 'f', '\f' }, + { 'n', '\n' }, + { 'r', '\r' }, + { 't', '\t' }, + { '0', '\0' } + }; + + private StreamReader reader; + private int bufferIndex = 0; + private char[] buffer = new char[256]; + private char? lastReadChar; + private char? peekedChar; + private Queue<char> emergencyPlayback; + + /// <summary> + /// The current deserialization context used by the text reader. + /// </summary> + public DeserializationContext Context { get; private set; } + + /// <summary> + /// Initializes a new instance of the <see cref="JsonTextReader" /> class. + /// </summary> + /// <param name="stream">The stream to parse from.</param> + /// <param name="context">The deserialization context to use.</param> + /// <exception cref="System.ArgumentNullException">The stream is null.</exception> + /// <exception cref="System.ArgumentException">Cannot read from the stream.</exception> + public JsonTextReader(Stream stream, DeserializationContext context) + { + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + if (context == null) + { + throw new ArgumentNullException("context"); + } + + if (stream.CanRead == false) + { + throw new ArgumentException("Cannot read from stream"); + } + + this.reader = new StreamReader(stream); + this.Context = context; + } + + /// <summary> + /// Resets the reader instance's currently peeked char and emergency playback queue. + /// </summary> + public void Reset() + { + this.peekedChar = null; + + if (this.emergencyPlayback != null) + { + this.emergencyPlayback.Clear(); + } + } + + /// <summary> + /// Disposes all resources kept by the text reader, except the stream, which can be reused later. + /// </summary> + public void Dispose() + { + //this.reader.Dispose(); + } + + /// <summary> + /// Reads to (but not past) the beginning of the next json entry, and returns the entry name, contents and type. + /// </summary> + /// <param name="name">The name of the entry that was parsed.</param> + /// <param name="valueContent">The content of the entry that was parsed.</param> + /// <param name="entry">The type of the entry that was parsed.</param> + public void ReadToNextEntry(out string name, out string valueContent, out EntryType entry) + { + // This is sort of complicated, so the method is heavily commented. + int valueSeparatorIndex = -1; + bool insideString = false; + EntryType? foundEntryType; + + this.bufferIndex = -1; // Reset buffer + + while (this.reader.EndOfStream == false) + { + char c = this.PeekChar(); + + if (insideString && this.lastReadChar == '\\') + { + // A special character or hex value (\uXXXX format) has possibly been escaped - we resolve that escape here + if (c == '\\') + { + // An escape character has been escaped by a previous escape character + // We consume this escape character without adding it to the buffer, and clear the last read char, + // so that each escape character can only escape another escape character once + this.lastReadChar = null; + this.SkipChar(); + continue; + } + else + { + switch (c) // '\"' is handled further down + { + case 'a': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + case '0': + // These are normally escaped "short" characters - tabs, carriage returns, newlines, etc. + // We substitute the prior escape char with the escaped character + c = UnescapeDictionary[c]; + + this.lastReadChar = c; + this.buffer[this.bufferIndex] = c; + this.SkipChar(); + continue; + + case 'u': + // This signifies the beginning of a hexadecimal sequence of four chars, describing one Unicode char + this.SkipChar(); // Skip u + + char c1 = this.ConsumeChar(); + char c2 = this.ConsumeChar(); + char c3 = this.ConsumeChar(); + char c4 = this.ConsumeChar(); + + if (this.IsHex(c1) && this.IsHex(c2) && this.IsHex(c3) && this.IsHex(c4)) + { + // We substitute the prior escape char with the parsed hex char + c = this.ParseHexChar(c1, c2, c3, c4); + + this.lastReadChar = c; + this.buffer[this.bufferIndex] = c; + continue; + } + else + { + this.Context.Config.DebugContext.LogError("A wild non-hex value appears at position " + this.reader.BaseStream.Position + "! \\-u-" + c1 + "-" + c2 + "-" + c3 + "-" + c4 + "; current buffer: '" + new string(this.buffer, 0, this.bufferIndex + 1) + "'. If the error handling policy is resilient, an attempt will be made to recover from this emergency without a fatal parse error..."); + + // Queue values plainly in emergency playback queue - u,c1,c2,c3,c4, and wipe lastReadChar to avoid the escape character triggering again + this.lastReadChar = null; + + if (this.emergencyPlayback == null) + { + this.emergencyPlayback = new Queue<char>(5); + } + + this.emergencyPlayback.Enqueue('u'); + this.emergencyPlayback.Enqueue(c1); + this.emergencyPlayback.Enqueue(c2); + this.emergencyPlayback.Enqueue(c3); + this.emergencyPlayback.Enqueue(c4); + continue; + } + } + } + } + + if (insideString == false && c == ':' && valueSeparatorIndex == -1) + { + // We've found a value separator + valueSeparatorIndex = this.bufferIndex + 1; + } + + if (c == '"') + { + if (insideString && this.lastReadChar == '\\') + { + // We're currently inside a string and this quotation mark has been escaped + // Replace the escape character with the quotation mark instead, and read one character ahead + this.lastReadChar = '"'; + this.buffer[this.bufferIndex] = '"'; + this.SkipChar(); + continue; + } + else + { + // This quotation mark hasn't been escaped; toggle the inside string bool + this.ReadCharIntoBuffer(); + insideString = !insideString; + continue; + } + } + + if (insideString) + { + // Currently reading a string; read everything verbatim (escaped quotes handled above) + this.ReadCharIntoBuffer(); + } + else + { + // While not inside strings, skip all whitespaces (this includes newlines) + if (char.IsWhiteSpace(c)) + { + this.SkipChar(); + continue; + } + + if (EntryDelineators.TryGetValue(c, out foundEntryType)) + { + // We've hit an entry delineator + if (foundEntryType == null) + { + // This was a value entry, which could be a lot of things + // We consume the character without adding it to the buffer + this.SkipChar(); + + if (this.bufferIndex == -1) + { + // We encountered a value separator without having read anything into the buffer first + // We probably just finished with a node. Either way, we read on + continue; + } + else + { + // We parse a value entry from the buffer and return that information + this.ParseEntryFromBuffer(out name, out valueContent, out entry, valueSeparatorIndex, null); + return; + } + } + else + { + entry = foundEntryType.Value; + + switch (entry) + { + case EntryType.StartOfNode: + { + // We're starting a node. + // We consume the start of node character without adding it to the buffer, + // then parse the entry information if it's there. + EntryType dummy; + this.ConsumeChar(); + this.ParseEntryFromBuffer(out name, out valueContent, out dummy, valueSeparatorIndex, EntryType.StartOfNode); + return; + } + + case EntryType.PrimitiveArray: + { + // We're starting a primitive array (regular arrays are caught by parsing entries prior to this) + // We consume the start of array character without adding it to the buffer, + // then parse the entry information if it's there + EntryType dummy; + this.ConsumeChar(); + this.ParseEntryFromBuffer(out name, out valueContent, out dummy, valueSeparatorIndex, EntryType.PrimitiveArray); + return; + } + + case EntryType.EndOfNode: + if (this.bufferIndex == -1) + { + // This is an actual end of node, as we haven't read anything before this + // So we consume it, and return as end of node + this.ConsumeChar(); + name = null; + valueContent = null; + return; + } + else + { + // We just finished reading the last value entry in a node, as there's content in the buffer + // We don't consume the end of node character (which has only been peeked) - that we leave for the next call to find + // Instead we parse the entry from the buffer and return that entry information + this.ParseEntryFromBuffer(out name, out valueContent, out entry, valueSeparatorIndex, null); + return; + } + + case EntryType.EndOfArray: + { + if (this.bufferIndex == -1) + { + // This is an actual end of array, as we haven't read anything before this + // So we consume it, and return as end of array + this.ConsumeChar(); + name = null; + valueContent = null; + return; + } + else + { + // We just finished reading the last value entry in an array, as there's content in the buffer + // We don't consume the end of array character (which has only been peeked) - that we leave for the next call to find + // Instead we parse the entry from the buffer and return that entry information + this.ParseEntryFromBuffer(out name, out valueContent, out entry, valueSeparatorIndex, null); + return; + } + } + + default: + throw new NotImplementedException(); + } + } + } + else + { + this.ReadCharIntoBuffer(); + } + } + } + + // We've hit the end of stream + if (this.bufferIndex == -1) + { + // We didn't manage to read any info before reaching end of stream + name = null; + valueContent = null; + entry = EntryType.EndOfStream; + } + else + { + // We managed to read some stuff before we reached the end of stream + // We can try to parse that as an entry + this.ParseEntryFromBuffer(out name, out valueContent, out entry, valueSeparatorIndex, EntryType.EndOfStream); + } + } + + private void ParseEntryFromBuffer(out string name, out string valueContent, out EntryType entry, int valueSeparatorIndex, EntryType? hintEntry) + { + if (this.bufferIndex >= 0) + { + if (valueSeparatorIndex == -1) + { + // There is no value separator on this line at all + if (hintEntry != null) + { + // We have a hint, so we'll try to handle it and assume that the entry's content is the whole thing + name = null; + valueContent = new string(this.buffer, 0, this.bufferIndex + 1); + entry = hintEntry.Value; + return; + } + else + { + // We've got no hint and no separator; we must assume that this is a primitive, and that the entry's content is the whole thing + // This will happen while reading primitive arrays + name = null; + valueContent = new string(this.buffer, 0, this.bufferIndex + 1); + + var guessedPrimitiveType = this.GuessPrimitiveType(valueContent); + + if (guessedPrimitiveType != null) + { + entry = guessedPrimitiveType.Value; + } + else + { + entry = EntryType.Invalid; + } + + return; + } + } + else + { + // We allow a node's name to *not* be inside quotation marks + if (this.buffer[0] == '"') + { + name = new string(this.buffer, 1, valueSeparatorIndex - 2); + } + else + { + name = new string(this.buffer, 0, valueSeparatorIndex); + } + + if (string.Equals(name, JsonConfig.REGULAR_ARRAY_CONTENT_SIG, StringComparison.InvariantCulture) && hintEntry == EntryType.StartOfArray) + { + valueContent = null; + entry = EntryType.StartOfArray; + return; + } + + if (string.Equals(name, JsonConfig.PRIMITIVE_ARRAY_CONTENT_SIG, StringComparison.InvariantCulture) && hintEntry == EntryType.StartOfArray) + { + valueContent = null; + entry = EntryType.PrimitiveArray; + return; + } + + if (string.Equals(name, JsonConfig.INTERNAL_REF_SIG, StringComparison.InvariantCulture)) + { + // It's an object reference without a name + // The content is the whole buffer + name = null; + valueContent = new string(this.buffer, 0, this.bufferIndex + 1); + entry = EntryType.InternalReference; + return; + } + + if (string.Equals(name, JsonConfig.EXTERNAL_INDEX_REF_SIG, StringComparison.InvariantCulture)) + { + // It's an external index reference without a name + // The content is the whole buffer + name = null; + valueContent = new string(this.buffer, 0, this.bufferIndex + 1); + entry = EntryType.ExternalReferenceByIndex; + return; + } + + if (string.Equals(name, JsonConfig.EXTERNAL_GUID_REF_SIG, StringComparison.InvariantCulture)) + { + // It's an external guid reference without a name + // The content is the whole buffer + name = null; + valueContent = new string(this.buffer, 0, this.bufferIndex + 1); + entry = EntryType.ExternalReferenceByGuid; + return; + } + + if (string.Equals(name, JsonConfig.EXTERNAL_STRING_REF_SIG, StringComparison.InvariantCulture)) + { + // It's an external guid reference without a name + // The content is the whole buffer + name = null; + valueContent = new string(this.buffer, 0, this.bufferIndex + 1); + entry = EntryType.ExternalReferenceByString; + return; + } + + if (this.bufferIndex >= valueSeparatorIndex) + { + valueContent = new string(this.buffer, valueSeparatorIndex + 1, this.bufferIndex - valueSeparatorIndex); + } + else + { + valueContent = null; + } + + if (valueContent != null) + { + // We can now try to see what the value content actually is, and as such determine the type of the entry + if (string.Equals(name, JsonConfig.REGULAR_ARRAY_LENGTH_SIG, StringComparison.InvariantCulture)) // This is a special case for the length entry that must always come before an array + { + entry = EntryType.StartOfArray; + return; + } + + if (string.Equals(name, JsonConfig.PRIMITIVE_ARRAY_LENGTH_SIG, StringComparison.InvariantCulture)) // This is a special case for the length entry that must always come before an array + { + entry = EntryType.PrimitiveArray; + return; + } + + if (valueContent.Length == 0 && hintEntry.HasValue) + { + entry = hintEntry.Value; + return; + } + + if (string.Equals(valueContent, "null", StringComparison.InvariantCultureIgnoreCase)) + { + entry = EntryType.Null; + return; + } + else if (string.Equals(valueContent, "{", StringComparison.InvariantCulture)) + { + entry = EntryType.StartOfNode; + return; + } + else if (string.Equals(valueContent, "}", StringComparison.InvariantCulture)) + { + entry = EntryType.EndOfNode; + return; + } + else if (string.Equals(valueContent, "[", StringComparison.InvariantCulture)) + { + entry = EntryType.StartOfArray; + return; + } + else if (string.Equals(valueContent, "]", StringComparison.InvariantCulture)) + { + entry = EntryType.EndOfArray; + return; + } + else if (valueContent.StartsWith(JsonConfig.INTERNAL_REF_SIG, StringComparison.InvariantCulture)) + { + entry = EntryType.InternalReference; + return; + } + else if (valueContent.StartsWith(JsonConfig.EXTERNAL_INDEX_REF_SIG, StringComparison.InvariantCulture)) + { + entry = EntryType.ExternalReferenceByIndex; + return; + } + else if (valueContent.StartsWith(JsonConfig.EXTERNAL_GUID_REF_SIG, StringComparison.InvariantCulture)) + { + entry = EntryType.ExternalReferenceByGuid; + return; + } + else if (valueContent.StartsWith(JsonConfig.EXTERNAL_STRING_REF_SIG, StringComparison.InvariantCulture)) + { + entry = EntryType.ExternalReferenceByString; + return; + } + else + { + var guessedPrimitiveType = this.GuessPrimitiveType(valueContent); + + if (guessedPrimitiveType != null) + { + entry = guessedPrimitiveType.Value; + return; + } + } + } + } + } + + if (hintEntry != null) + { + name = null; + valueContent = null; + entry = hintEntry.Value; + return; + } + + // Parsing the entry somehow failed entirely + // This means the JSON was actually invalid + if (this.bufferIndex == -1) + { + this.Context.Config.DebugContext.LogError("Failed to parse empty entry in the stream."); + } + else + { + this.Context.Config.DebugContext.LogError("Tried and failed to parse entry with content '" + new string(this.buffer, 0, this.bufferIndex + 1) + "'."); + } + + if (hintEntry == EntryType.EndOfStream) + { + name = null; + valueContent = null; + entry = EntryType.EndOfStream; + } + else + { + name = null; + valueContent = null; + entry = EntryType.Invalid; + } + } + + private bool IsHex(char c) + { + return (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F'); + } + + private uint ParseSingleChar(char c, uint multiplier) + { + uint p = 0; + + if (c >= '0' && c <= '9') + { + p = (uint)(c - '0') * multiplier; + } + else if (c >= 'A' && c <= 'F') + { + p = (uint)((c - 'A') + 10) * multiplier; + } + else if (c >= 'a' && c <= 'f') + { + p = (uint)((c - 'a') + 10) * multiplier; + } + + return p; + } + + private char ParseHexChar(char c1, char c2, char c3, char c4) + { + uint p1 = this.ParseSingleChar(c1, 0x1000); + uint p2 = this.ParseSingleChar(c2, 0x100); + uint p3 = this.ParseSingleChar(c3, 0x10); + uint p4 = this.ParseSingleChar(c4, 0x1); + + try + { + return (char)(p1 + p2 + p3 + p4); + } + catch (Exception) + { + this.Context.Config.DebugContext.LogError("Could not parse invalid hex values: " + c1 + c2 + c3 + c4); + return ' '; + } + } + + private char ReadCharIntoBuffer() + { + this.bufferIndex++; + + if (this.bufferIndex >= this.buffer.Length - 1) + { + // Ensure there's space in the buffer + var newBuffer = new char[this.buffer.Length * 2]; + Buffer.BlockCopy(this.buffer, 0, newBuffer, 0, this.buffer.Length * sizeof(char)); + this.buffer = newBuffer; + } + + char c = this.ConsumeChar(); + + this.buffer[this.bufferIndex] = c; + this.lastReadChar = c; + + return c; + } + + private EntryType? GuessPrimitiveType(string content) + { + // This method tries to guess what kind of primitive type the current entry is, as cheaply as possible + if (string.Equals(content, "null", StringComparison.InvariantCultureIgnoreCase)) + { + return EntryType.Null; + } + else if (content.Length >= 2 && content[0] == '"' && content[content.Length - 1] == '"') + { + return EntryType.String; + } + else if (content.Length == 36 && content.LastIndexOf('-') > 0) + { + return EntryType.Guid; + } + else if (content.Contains(".") || content.Contains(",")) + { + return EntryType.FloatingPoint; + } + else if (string.Equals(content, "true", StringComparison.InvariantCultureIgnoreCase) || string.Equals(content, "false", StringComparison.InvariantCultureIgnoreCase)) + { + return EntryType.Boolean; + } + else if (content.Length >= 1) + { + return EntryType.Integer; + } + + return null; + } + + private char PeekChar() + { + // Instead of peeking, we read ahead and store the last read character as a peeked character + // this means we don't need seeking support in the stream + if (this.peekedChar == null) + { + if (this.emergencyPlayback != null && this.emergencyPlayback.Count > 0) + { + this.peekedChar = this.emergencyPlayback.Dequeue(); + } + else + { + this.peekedChar = (char)this.reader.Read(); + } + } + + return this.peekedChar.Value; + } + + private void SkipChar() + { + if (this.peekedChar == null) + { + if (this.emergencyPlayback != null && this.emergencyPlayback.Count > 0) + { + this.emergencyPlayback.Dequeue(); + } + else + { + this.reader.Read(); + } + } + else + { + this.peekedChar = null; + } + } + + private char ConsumeChar() + { + if (this.peekedChar == null) + { + if (this.emergencyPlayback != null && this.emergencyPlayback.Count > 0) + { + return this.emergencyPlayback.Dequeue(); + } + else + { + return (char)this.reader.Read(); + } + } + else + { + var c = this.peekedChar; + this.peekedChar = null; + return c.Value; + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonTextReader.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonTextReader.cs.meta new file mode 100644 index 00000000..8d41a77d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/Json/JsonTextReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aad0a34e801ae645b359e4800ef7f636 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes.meta new file mode 100644 index 00000000..7a19797d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a7b0e9eb76b98464a90bdc0fd68ece18 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNode.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNode.cs new file mode 100644 index 00000000..81de5b1c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNode.cs @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializationNode.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// A serialization node as used by the <see cref="DataFormat.Nodes"/> format. + /// </summary> + [Serializable] + public struct SerializationNode + { + /// <summary> + /// The name of the node. + /// </summary> + public string Name; + + /// <summary> + /// The entry type of the node. + /// </summary> + public EntryType Entry; + + /// <summary> + /// The data contained in the node. Depending on the entry type and name, as well as nodes encountered prior to this one, the format can vary wildly. + /// </summary> + public string Data; + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNode.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNode.cs.meta new file mode 100644 index 00000000..a9fe7de2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6a0f5e01b82ae0763f6f907157a2c9c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReader.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReader.cs new file mode 100644 index 00000000..8852a839 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReader.cs @@ -0,0 +1,1028 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializationNodeDataReader.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + + /// <summary> + /// Not yet documented. + /// </summary> + public class SerializationNodeDataReader : BaseDataReader + { + private string peekedEntryName; + private EntryType? peekedEntryType; + private string peekedEntryData; + + private int currentIndex = -1; + private List<SerializationNode> nodes; + private Dictionary<Type, Delegate> primitiveTypeReaders; + + /// <summary> + /// Not yet documented. + /// </summary> + public SerializationNodeDataReader(DeserializationContext context) : base(null, context) + { + this.primitiveTypeReaders = new Dictionary<Type, Delegate>() + { + { typeof(char), (Func<char>)(() => { char v; this.ReadChar(out v); return v; }) }, + { typeof(sbyte), (Func<sbyte>)(() => { sbyte v; this.ReadSByte(out v); return v; }) }, + { typeof(short), (Func<short>)(() => { short v; this.ReadInt16(out v); return v; }) }, + { typeof(int), (Func<int>)(() => { int v; this.ReadInt32(out v); return v; }) }, + { typeof(long), (Func<long>)(() => { long v; this.ReadInt64(out v); return v; }) }, + { typeof(byte), (Func<byte>)(() => { byte v; this.ReadByte(out v); return v; }) }, + { typeof(ushort), (Func<ushort>)(() => { ushort v; this.ReadUInt16(out v); return v; }) }, + { typeof(uint), (Func<uint>)(() => { uint v; this.ReadUInt32(out v); return v; }) }, + { typeof(ulong), (Func<ulong>)(() => { ulong v; this.ReadUInt64(out v); return v; }) }, + { typeof(decimal), (Func<decimal>)(() => { decimal v; this.ReadDecimal(out v); return v; }) }, + { typeof(bool), (Func<bool>)(() => { bool v; this.ReadBoolean(out v); return v; }) }, + { typeof(float), (Func<float>)(() => { float v; this.ReadSingle(out v); return v; }) }, + { typeof(double), (Func<double>)(() => { double v; this.ReadDouble(out v); return v; }) }, + { typeof(Guid), (Func<Guid>)(() => { Guid v; this.ReadGuid(out v); return v; }) } + }; + } + + private bool IndexIsValid { get { return this.nodes != null && this.currentIndex >= 0 && this.currentIndex < this.nodes.Count; } } + + /// <summary> + /// Not yet documented. + /// </summary> + public List<SerializationNode> Nodes + { + get + { + if (this.nodes == null) + { + this.nodes = new List<SerializationNode>(); + } + + return this.nodes; + } + + set + { + if (value == null) + { + throw new ArgumentNullException(); + } + + this.nodes = value; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override Stream Stream + { + get { throw new NotSupportedException("This data reader has no stream."); } + set { throw new NotSupportedException("This data reader has no stream."); } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void Dispose() + { + this.nodes = null; + this.currentIndex = -1; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void PrepareNewSerializationSession() + { + base.PrepareNewSerializationSession(); + this.currentIndex = -1; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override EntryType PeekEntry(out string name) + { + if (this.peekedEntryType != null) + { + name = this.peekedEntryName; + return this.peekedEntryType.Value; + } + + this.currentIndex++; + + if (this.IndexIsValid) + { + var node = this.nodes[this.currentIndex]; + + this.peekedEntryName = node.Name; + this.peekedEntryType = node.Entry; + this.peekedEntryData = node.Data; + } + else + { + this.peekedEntryName = null; + this.peekedEntryType = EntryType.EndOfStream; + this.peekedEntryData = null; + } + + name = this.peekedEntryName; + return this.peekedEntryType.Value; + } + + /// <summary> + /// Tries to enters an array node. This will succeed if the next entry is an <see cref="EntryType.StartOfArray" />. + /// <para /> + /// This call MUST (eventually) be followed by a corresponding call to <see cref="IDataReader.ExitArray(DeserializationContext)" /><para /> + /// This call will change the values of the <see cref="IDataReader.IsInArrayNode" />, <see cref="IDataReader.CurrentNodeName" />, <see cref="IDataReader.CurrentNodeId" /> and <see cref="IDataReader.CurrentNodeDepth" /> properties to the correct values for the current array node. + /// </summary> + /// <param name="length">The length of the array that was entered.</param> + /// <returns> + /// <c>true</c> if an array was entered, otherwise <c>false</c> + /// </returns> + public override bool EnterArray(out long length) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.StartOfArray) + { + this.PushArray(); + + if (!long.TryParse(this.peekedEntryData, NumberStyles.Any, CultureInfo.InvariantCulture, out length)) + { + length = 0; + this.Context.Config.DebugContext.LogError("Failed to parse array length from data '" + this.peekedEntryData + "'."); + } + + this.ConsumeCurrentEntry(); + return true; + } + else + { + this.SkipEntry(); + length = 0; + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool EnterNode(out Type type) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.StartOfNode) + { + string data = this.peekedEntryData; + int id = -1; + type = null; + + if (!string.IsNullOrEmpty(data)) + { + string typeName = null; + int separator = data.IndexOf(SerializationNodeDataReaderWriterConfig.NodeIdSeparator, StringComparison.InvariantCulture); + int parsedId; + + if (separator >= 0) + { + typeName = data.Substring(separator + 1); + + string idStr = data.Substring(0, separator); + + if (int.TryParse(idStr, NumberStyles.Any, CultureInfo.InvariantCulture, out parsedId)) + { + id = parsedId; + } + else + { + this.Context.Config.DebugContext.LogError("Failed to parse id string '" + idStr + "' from data '" + data + "'."); + } + } + else if (int.TryParse(data, out parsedId)) + { + id = parsedId; + } + else + { + typeName = data; + } + + if (typeName != null) + { + type = this.Context.Binder.BindToType(typeName, this.Context.Config.DebugContext); + } + } + + this.ConsumeCurrentEntry(); + this.PushNode(this.peekedEntryName, id, type); + + return true; + } + else + { + this.SkipEntry(); + type = null; + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ExitArray() + { + this.PeekEntry(); + + // Read to next end of array + while (this.peekedEntryType != EntryType.EndOfArray && this.peekedEntryType != EntryType.EndOfStream) + { + if (this.peekedEntryType == EntryType.EndOfNode) + { + this.Context.Config.DebugContext.LogError("Data layout mismatch; skipping past node boundary when exiting array."); + this.ConsumeCurrentEntry(); + } + + this.SkipEntry(); + } + + if (this.peekedEntryType == EntryType.EndOfArray) + { + this.ConsumeCurrentEntry(); + this.PopArray(); + return true; + } + + return false; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ExitNode() + { + this.PeekEntry(); + + // Read to next end of node + while (this.peekedEntryType != EntryType.EndOfNode && this.peekedEntryType != EntryType.EndOfStream) + { + if (this.peekedEntryType == EntryType.EndOfArray) + { + this.Context.Config.DebugContext.LogError("Data layout mismatch; skipping past array boundary when exiting node."); + this.ConsumeCurrentEntry(); + } + + this.SkipEntry(); + } + + if (this.peekedEntryType == EntryType.EndOfNode) + { + this.ConsumeCurrentEntry(); + this.PopNode(this.CurrentNodeName); + return true; + } + + return false; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadBoolean(out bool value) + { + this.PeekEntry(); + + try + { + if (this.peekedEntryType == EntryType.Boolean) + { + value = this.peekedEntryData == "true"; + return true; + } + + value = false; + return false; + } + finally + { + this.ConsumeCurrentEntry(); + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadByte(out byte value) + { + ulong ulongValue; + + if (this.ReadUInt64(out ulongValue)) + { + checked + { + try + { + value = (byte)ulongValue; + } + catch (OverflowException) + { + value = default(byte); + } + } + + return true; + } + + value = default(byte); + return false; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadChar(out char value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.String) + { + try + { + if (this.peekedEntryData.Length == 1) + { + value = this.peekedEntryData[0]; + return true; + } + else + { + this.Context.Config.DebugContext.LogWarning("Expected string of length 1 for char entry."); + value = default(char); + return false; + } + } + finally + { + this.ConsumeCurrentEntry(); + } + } + else + { + this.SkipEntry(); + value = default(char); + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadDecimal(out decimal value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.FloatingPoint || this.peekedEntryType == EntryType.Integer) + { + try + { + if (!decimal.TryParse(this.peekedEntryData, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + this.Context.Config.DebugContext.LogError("Failed to parse decimal value from entry data '" + this.peekedEntryData + "'."); + return false; + } + + return true; + } + finally + { + this.ConsumeCurrentEntry(); + } + } + else + { + this.SkipEntry(); + value = default(decimal); + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadDouble(out double value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.FloatingPoint || this.peekedEntryType == EntryType.Integer) + { + try + { + if (!double.TryParse(this.peekedEntryData, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + this.Context.Config.DebugContext.LogError("Failed to parse double value from entry data '" + this.peekedEntryData + "'."); + return false; + } + + return true; + } + finally + { + this.ConsumeCurrentEntry(); + } + } + else + { + this.SkipEntry(); + value = default(double); + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadExternalReference(out Guid guid) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.ExternalReferenceByGuid) + { + try + { + if ((guid = new Guid(this.peekedEntryData)) != Guid.Empty) + { + return true; + } + + guid = Guid.Empty; + return false; + } + catch (FormatException) + { + guid = Guid.Empty; + return false; + } + catch (OverflowException) + { + guid = Guid.Empty; + return false; + } + finally + { + this.ConsumeCurrentEntry(); + } + } + else + { + this.SkipEntry(); + guid = Guid.Empty; + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadExternalReference(out string id) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.ExternalReferenceByString) + { + id = this.peekedEntryData; + this.ConsumeCurrentEntry(); + return true; + } + else + { + this.SkipEntry(); + id = null; + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadExternalReference(out int index) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.ExternalReferenceByIndex) + { + try + { + if (!int.TryParse(this.peekedEntryData, NumberStyles.Any, CultureInfo.InvariantCulture, out index)) + { + this.Context.Config.DebugContext.LogError("Failed to parse external index reference integer value from entry data '" + this.peekedEntryData + "'."); + return false; + } + + return true; + } + finally + { + this.ConsumeCurrentEntry(); + } + } + else + { + this.SkipEntry(); + index = default(int); + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadGuid(out Guid value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.Guid) + { + try + { + if ((value = new Guid(this.peekedEntryData)) != Guid.Empty) + { + return true; + } + + value = Guid.Empty; + return false; + } + catch (FormatException) + { + value = Guid.Empty; + return false; + } + catch (OverflowException) + { + value = Guid.Empty; + return false; + } + finally + { + this.ConsumeCurrentEntry(); + } + } + else + { + this.SkipEntry(); + value = Guid.Empty; + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadInt16(out short value) + { + long longValue; + + if (this.ReadInt64(out longValue)) + { + checked + { + try + { + value = (short)longValue; + } + catch (OverflowException) + { + value = default(short); + } + } + + return true; + } + + value = default(short); + return false; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadInt32(out int value) + { + long longValue; + + if (this.ReadInt64(out longValue)) + { + checked + { + try + { + value = (int)longValue; + } + catch (OverflowException) + { + value = default(int); + } + } + + return true; + } + + value = default(int); + return false; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadInt64(out long value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.Integer) + { + try + { + if (!long.TryParse(this.peekedEntryData, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + this.Context.Config.DebugContext.LogError("Failed to parse integer value from entry data '" + this.peekedEntryData + "'."); + return false; + } + + return true; + } + finally + { + this.ConsumeCurrentEntry(); + } + } + else + { + this.SkipEntry(); + value = default(long); + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadInternalReference(out int id) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.InternalReference) + { + try + { + if (!int.TryParse(this.peekedEntryData, NumberStyles.Any, CultureInfo.InvariantCulture, out id)) + { + this.Context.Config.DebugContext.LogError("Failed to parse internal reference id integer value from entry data '" + this.peekedEntryData + "'."); + return false; + } + + return true; + } + finally + { + this.ConsumeCurrentEntry(); + } + } + else + { + this.SkipEntry(); + id = default(int); + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadNull() + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.Null) + { + this.ConsumeCurrentEntry(); + return true; + } + else + { + this.SkipEntry(); + return false; + } + } + + /// <summary> + /// Reads a primitive array value. This call will succeed if the next entry is an <see cref="EntryType.PrimitiveArray" />. + /// <para /> + /// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode" /> or an <see cref="EntryType.EndOfArray" />. + /// </summary> + /// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)" />.</typeparam> + /// <param name="array">The resulting primitive array.</param> + /// <returns> + /// <c>true</c> if reading a primitive array succeeded, otherwise <c>false</c> + /// </returns> + /// <exception cref="System.ArgumentException">Type + typeof(T).Name + is not a valid primitive array type.</exception> + public override bool ReadPrimitiveArray<T>(out T[] array) + { + if (FormatterUtilities.IsPrimitiveArrayType(typeof(T)) == false) + { + throw new ArgumentException("Type " + typeof(T).Name + " is not a valid primitive array type."); + } + + if (this.peekedEntryType != EntryType.PrimitiveArray) + { + this.SkipEntry(); + array = null; + return false; + } + + if (typeof(T) == typeof(byte)) + { + array = (T[])(object)ProperBitConverter.HexStringToBytes(this.peekedEntryData); + return true; + } + else + { + this.PeekEntry(); + + long length; + + if (this.peekedEntryType != EntryType.PrimitiveArray) + { + this.Context.Config.DebugContext.LogError("Expected entry of type '" + EntryType.StartOfArray + "' when reading primitive array but got entry of type '" + this.peekedEntryType + "'."); + this.SkipEntry(); + array = new T[0]; + return false; + } + + if (!long.TryParse(this.peekedEntryData, NumberStyles.Any, CultureInfo.InvariantCulture, out length)) + { + this.Context.Config.DebugContext.LogError("Failed to parse primitive array length from entry data '" + this.peekedEntryData + "'."); + this.SkipEntry(); + array = new T[0]; + return false; + } + + this.ConsumeCurrentEntry(); + this.PushArray(); + + array = new T[length]; + + Func<T> reader = (Func<T>)this.primitiveTypeReaders[typeof(T)]; + + for (int i = 0; i < length; i++) + { + array[i] = reader(); + } + + this.ExitArray(); + return true; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadSByte(out sbyte value) + { + long longValue; + + if (this.ReadInt64(out longValue)) + { + checked + { + try + { + value = (sbyte)longValue; + } + catch (OverflowException) + { + value = default(sbyte); + } + } + + return true; + } + + value = default(sbyte); + return false; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadSingle(out float value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.FloatingPoint || this.peekedEntryType == EntryType.Integer) + { + try + { + if (!float.TryParse(this.peekedEntryData, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + this.Context.Config.DebugContext.LogError("Failed to parse float value from entry data '" + this.peekedEntryData + "'."); + return false; + } + + return true; + } + finally + { + this.ConsumeCurrentEntry(); + } + } + else + { + this.SkipEntry(); + value = default(float); + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadString(out string value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.String) + { + value = this.peekedEntryData; + this.ConsumeCurrentEntry(); + return true; + } + else + { + this.SkipEntry(); + value = default(string); + return false; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadUInt16(out ushort value) + { + ulong ulongValue; + + if (this.ReadUInt64(out ulongValue)) + { + checked + { + try + { + value = (ushort)ulongValue; + } + catch (OverflowException) + { + value = default(ushort); + } + } + + return true; + } + + value = default(ushort); + return false; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadUInt32(out uint value) + { + ulong ulongValue; + + if (this.ReadUInt64(out ulongValue)) + { + checked + { + try + { + value = (uint)ulongValue; + } + catch (OverflowException) + { + value = default(uint); + } + } + + return true; + } + + value = default(uint); + return false; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override bool ReadUInt64(out ulong value) + { + this.PeekEntry(); + + if (this.peekedEntryType == EntryType.Integer) + { + try + { + if (!ulong.TryParse(this.peekedEntryData, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + this.Context.Config.DebugContext.LogError("Failed to parse integer value from entry data '" + this.peekedEntryData + "'."); + return false; + } + + return true; + } + finally + { + this.ConsumeCurrentEntry(); + } + } + else + { + this.SkipEntry(); + value = default(ulong); + return false; + } + } + + public override string GetDataDump() + { + var sb = new System.Text.StringBuilder(); + + sb.Append("Nodes: \n\n"); + + for (int i = 0; i < this.nodes.Count; i++) + { + var node = this.nodes[i]; + + sb.Append(" - Name: " + node.Name); + + if (i == this.currentIndex) + { + sb.AppendLine(" <<<< READ POSITION"); + } + else + { + sb.AppendLine(); + } + + sb.AppendLine(" Entry: " + (int)node.Entry); + sb.AppendLine(" Data: " + node.Data); + } + + return sb.ToString(); + } + + private void ConsumeCurrentEntry() + { + if (this.peekedEntryType != null && this.peekedEntryType != EntryType.EndOfStream) + { + this.peekedEntryType = null; + } + } + + /// <summary> + /// Peeks the current entry. + /// </summary> + /// <returns>The peeked entry.</returns> + protected override EntryType PeekEntry() + { + string name; + return this.PeekEntry(out name); + } + + /// <summary> + /// Consumes the current entry, and reads to the next one. + /// </summary> + /// <returns>The next entry.</returns> + protected override EntryType ReadToNextEntry() + { + string name; + this.ConsumeCurrentEntry(); + return this.PeekEntry(out name); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReader.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReader.cs.meta new file mode 100644 index 00000000..c30fcf63 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eab5938e837a8de93ce64c25399edde6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReaderWriterConfig.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReaderWriterConfig.cs new file mode 100644 index 00000000..84b33773 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReaderWriterConfig.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializationNodeDataReaderWriterConfig.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Shared config class for <see cref="SerializationNodeDataReader"/> and <see cref="SerializationNodeDataWriter"/>. + /// </summary> + public static class SerializationNodeDataReaderWriterConfig + { + /// <summary> + /// The string to use to separate node id's from their names. + /// </summary> + public const string NodeIdSeparator = "|"; + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReaderWriterConfig.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReaderWriterConfig.cs.meta new file mode 100644 index 00000000..6438ef3d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataReaderWriterConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9321fb650525f4bed18119d187111569 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataWriter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataWriter.cs new file mode 100644 index 00000000..76222c98 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataWriter.cs @@ -0,0 +1,516 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializationNodeDataWriter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + + /// <summary> + /// Not yet documented. + /// </summary> + public class SerializationNodeDataWriter : BaseDataWriter + { + private List<SerializationNode> nodes; + private Dictionary<Type, Delegate> primitiveTypeWriters; + + /// <summary> + /// Not yet documented. + /// </summary> + public List<SerializationNode> Nodes + { + get + { + if (this.nodes == null) + { + this.nodes = new List<SerializationNode>(); + } + + return this.nodes; + } + + set + { + if (value == null) + { + throw new ArgumentNullException(); + } + + this.nodes = value; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public SerializationNodeDataWriter(SerializationContext context) : base(null, context) + { + this.primitiveTypeWriters = new Dictionary<Type, Delegate>() + { + { typeof(char), (Action<string, char>)this.WriteChar }, + { typeof(sbyte), (Action<string, sbyte>)this.WriteSByte }, + { typeof(short), (Action<string, short>)this.WriteInt16 }, + { typeof(int), (Action<string, int>)this.WriteInt32 }, + { typeof(long), (Action<string, long>)this.WriteInt64 }, + { typeof(byte), (Action<string, byte>)this.WriteByte }, + { typeof(ushort), (Action<string, ushort>)this.WriteUInt16 }, + { typeof(uint), (Action<string, uint>)this.WriteUInt32 }, + { typeof(ulong), (Action<string, ulong>)this.WriteUInt64 }, + { typeof(decimal), (Action<string, decimal>)this.WriteDecimal }, + { typeof(bool), (Action<string, bool>)this.WriteBoolean }, + { typeof(float), (Action<string, float>)this.WriteSingle }, + { typeof(double), (Action<string, double>)this.WriteDouble }, + { typeof(Guid), (Action<string, Guid>)this.WriteGuid } + }; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override Stream Stream + { + get { throw new NotSupportedException("This data writer has no stream."); } + + set { throw new NotSupportedException("This data writer has no stream."); } + } + + /// <summary> + /// Begins an array node of the given length. + /// </summary> + /// <param name="length">The length of the array to come.</param> + /// <exception cref="System.NotImplementedException"></exception> + public override void BeginArrayNode(long length) + { + this.Nodes.Add(new SerializationNode() + { + Name = string.Empty, + Entry = EntryType.StartOfArray, + Data = length.ToString(CultureInfo.InvariantCulture) + }); + + this.PushArray(); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void BeginReferenceNode(string name, Type type, int id) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.StartOfNode, + Data = type != null ? (id.ToString(CultureInfo.InvariantCulture) + SerializationNodeDataReaderWriterConfig.NodeIdSeparator + this.Context.Binder.BindToName(type, this.Context.Config.DebugContext)) : id.ToString(CultureInfo.InvariantCulture) + }); + + this.PushNode(name, id, type); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void BeginStructNode(string name, Type type) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.StartOfNode, + Data = type != null ? this.Context.Binder.BindToName(type, this.Context.Config.DebugContext) : "" + }); + + this.PushNode(name, -1, type); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void Dispose() + { + this.nodes = null; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void EndArrayNode() + { + this.PopArray(); + + this.Nodes.Add(new SerializationNode() + { + Name = string.Empty, + Entry = EntryType.EndOfArray, + Data = string.Empty + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void EndNode(string name) + { + this.PopNode(name); + + this.Nodes.Add(new SerializationNode() + { + Name = string.Empty, + Entry = EntryType.EndOfNode, + Data = string.Empty + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void PrepareNewSerializationSession() + { + base.PrepareNewSerializationSession(); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteBoolean(string name, bool value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.Boolean, + Data = value ? "true" : "false" + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteByte(string name, byte value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.Integer, + Data = value.ToString("D", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteChar(string name, char value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.String, + Data = value.ToString(CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteDecimal(string name, decimal value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.FloatingPoint, + Data = value.ToString("G", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteSingle(string name, float value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.FloatingPoint, + Data = value.ToString("R", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteDouble(string name, double value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.FloatingPoint, + Data = value.ToString("R", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteExternalReference(string name, Guid guid) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.ExternalReferenceByGuid, + Data = guid.ToString("N", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteExternalReference(string name, string id) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.ExternalReferenceByString, + Data = id + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteExternalReference(string name, int index) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.ExternalReferenceByIndex, + Data = index.ToString("D", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteGuid(string name, Guid value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.Guid, + Data = value.ToString("N", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteInt16(string name, short value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.Integer, + Data = value.ToString("D", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteInt32(string name, int value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.Integer, + Data = value.ToString("D", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteInt64(string name, long value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.Integer, + Data = value.ToString("D", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteInternalReference(string name, int id) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.InternalReference, + Data = id.ToString("D", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteNull(string name) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.Null, + Data = string.Empty + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WritePrimitiveArray<T>(T[] array) + { + if (FormatterUtilities.IsPrimitiveArrayType(typeof(T)) == false) + { + throw new ArgumentException("Type " + typeof(T).Name + " is not a valid primitive array type."); + } + + if (typeof(T) == typeof(byte)) + { + string hex = ProperBitConverter.BytesToHexString((byte[])(object)array); + + this.Nodes.Add(new SerializationNode() + { + Name = string.Empty, + Entry = EntryType.PrimitiveArray, + Data = hex + }); + } + else + { + this.Nodes.Add(new SerializationNode() + { + Name = string.Empty, + Entry = EntryType.PrimitiveArray, + Data = array.LongLength.ToString(CultureInfo.InvariantCulture) + }); + + this.PushArray(); + + Action<string, T> writer = (Action<string, T>)this.primitiveTypeWriters[typeof(T)]; + + for (int i = 0; i < array.Length; i++) + { + writer(string.Empty, array[i]); + } + + this.EndArrayNode(); + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteSByte(string name, sbyte value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.Integer, + Data = value.ToString("D", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteString(string name, string value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.String, + Data = value + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteUInt16(string name, ushort value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.Integer, + Data = value.ToString("D", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteUInt32(string name, uint value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.Integer, + Data = value.ToString("D", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void WriteUInt64(string name, ulong value) + { + this.Nodes.Add(new SerializationNode() + { + Name = name, + Entry = EntryType.Integer, + Data = value.ToString("D", CultureInfo.InvariantCulture) + }); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override void FlushToStream() + { + // Do nothing + } + + public override string GetDataDump() + { + var sb = new System.Text.StringBuilder(); + + sb.Append("Nodes: \n\n"); + + for (int i = 0; i < this.nodes.Count; i++) + { + var node = this.nodes[i]; + + sb.AppendLine(" - Name: " + node.Name); + sb.AppendLine(" Entry: " + (int)node.Entry); + sb.AppendLine(" Data: " + node.Data); + } + + return sb.ToString(); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataWriter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataWriter.cs.meta new file mode 100644 index 00000000..5f5feb34 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/DataReaderWriters/SerializationNodes/SerializationNodeDataWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd54f07c359d141095a031192c5ca084 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators.meta new file mode 100644 index 00000000..8d640cbf --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5111dc8c066442e4b8c29dbd18e33192 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ArrayFormatterLocator.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ArrayFormatterLocator.cs new file mode 100644 index 00000000..f3c0733c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ArrayFormatterLocator.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="ArrayFormatterLocator.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatterLocator(typeof(ArrayFormatterLocator), -80)] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + internal class ArrayFormatterLocator : IFormatterLocator + { + public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter) + { + if (!type.IsArray) + { + formatter = null; + return false; + } + + if (type.GetArrayRank() == 1) + { + if (FormatterUtilities.IsPrimitiveArrayType(type.GetElementType())) + { + formatter = (IFormatter)Activator.CreateInstance(typeof(PrimitiveArrayFormatter<>).MakeGenericType(type.GetElementType())); + } + else + { + formatter = (IFormatter)Activator.CreateInstance(typeof(ArrayFormatter<>).MakeGenericType(type.GetElementType())); + } + } + else + { + formatter = (IFormatter)Activator.CreateInstance(typeof(MultiDimensionalArrayFormatter<,>).MakeGenericType(type, type.GetElementType())); + } + + return true; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ArrayFormatterLocator.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ArrayFormatterLocator.cs.meta new file mode 100644 index 00000000..79d3dc8e --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ArrayFormatterLocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0bdecd79f568c8a3252bb5a9f3e2acdc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/DelegateFormatterLocator.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/DelegateFormatterLocator.cs new file mode 100644 index 00000000..bf119ed4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/DelegateFormatterLocator.cs @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------- +// <copyright file="DelegateFormatterLocator.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatterLocator(typeof(DelegateFormatterLocator), -50)] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + internal class DelegateFormatterLocator : IFormatterLocator + { + public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter) + { + if (!typeof(Delegate).IsAssignableFrom(type)) + { + formatter = null; + return false; + } + + formatter = (IFormatter)Activator.CreateInstance(typeof(DelegateFormatter<>).MakeGenericType(type)); + return true; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/DelegateFormatterLocator.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/DelegateFormatterLocator.cs.meta new file mode 100644 index 00000000..e9d96fb3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/DelegateFormatterLocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c4228cdbba89e2a5d52357b998c3387d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/FormatterLocator.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/FormatterLocator.cs new file mode 100644 index 00000000..c9d6b85a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/FormatterLocator.cs @@ -0,0 +1,636 @@ +//----------------------------------------------------------------------- +// <copyright file="FormatterLocator.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using UnityEngine; + using Utilities; + + /// <summary> + /// Utility class for locating and caching formatters for all non-primitive types. + /// </summary> +#if UNITY_EDITOR + + [UnityEditor.InitializeOnLoad] +#endif + + public static class FormatterLocator + { + private static readonly object LOCK = new object(); + + private static readonly Dictionary<Type, IFormatter> FormatterInstances = new Dictionary<Type, IFormatter>(FastTypeComparer.Instance); + private static readonly DoubleLookupDictionary<Type, ISerializationPolicy, IFormatter> TypeFormatterMap = new DoubleLookupDictionary<Type, ISerializationPolicy, IFormatter>(FastTypeComparer.Instance, ReferenceEqualityComparer<ISerializationPolicy>.Default); + + private struct FormatterInfo + { + public Type FormatterType; + public Type TargetType; + public bool AskIfCanFormatTypes; + public int Priority; + } + + private struct FormatterLocatorInfo + { + public IFormatterLocator LocatorInstance; + public int Priority; + } + + private static readonly List<FormatterLocatorInfo> FormatterLocators = new List<FormatterLocatorInfo>(); + private static readonly List<FormatterInfo> FormatterInfos = new List<FormatterInfo>(); + +#if UNITY_EDITOR + + /// <summary> + /// Editor-only event that fires whenever an emittable formatter has been located. + /// This event is used by the AOT formatter pre-emitter to locate types that need to have formatters pre-emitted. + /// </summary> + public static event Action<Type> OnLocatedEmittableFormatterForType; + + /// <summary> + /// Editor-only event that fires whenever a formatter has been located. + /// </summary> + public static event Action<IFormatter> OnLocatedFormatter; + +#endif + + static FormatterLocator() + { + foreach (var ass in AppDomain.CurrentDomain.GetAssemblies()) + { + try + { + var name = ass.GetName().Name; + + if (name.StartsWith("System.") || name.StartsWith("UnityEngine") || name.StartsWith("UnityEditor") || name == "mscorlib") + { + // Filter out various core .NET libraries and Unity engine assemblies + continue; + } + else if (ass.GetName().Name == FormatterEmitter.PRE_EMITTED_ASSEMBLY_NAME || ass.SafeIsDefined(typeof(EmittedAssemblyAttribute), true)) + { + // Only include pre-emitted formatters if we are on an AOT platform. + // Pre-emitted formatters will not work in newer .NET runtimes due to + // lacking private member access privileges, but when compiled via + // IL2CPP they work fine. + +#if UNITY_EDITOR + continue; +#else + if (EmitUtilities.CanEmit) + { + // Never include pre-emitted formatters if we can emit on the current platform + continue; + } +#endif + } + + foreach (var attrUncast in ass.SafeGetCustomAttributes(typeof(RegisterFormatterAttribute), true)) + { + var attr = (RegisterFormatterAttribute)attrUncast; + + if (!attr.FormatterType.IsClass + || attr.FormatterType.IsAbstract + || attr.FormatterType.GetConstructor(Type.EmptyTypes) == null + || !attr.FormatterType.ImplementsOpenGenericInterface(typeof(IFormatter<>))) + { + continue; + } + + FormatterInfos.Add(new FormatterInfo() + { + FormatterType = attr.FormatterType, + TargetType = attr.FormatterType.GetArgumentsOfInheritedOpenGenericInterface(typeof(IFormatter<>))[0], + AskIfCanFormatTypes = typeof(IAskIfCanFormatTypes).IsAssignableFrom(attr.FormatterType), + Priority = attr.Priority + }); + } + + foreach (var attrUncast in ass.SafeGetCustomAttributes(typeof(RegisterFormatterLocatorAttribute), true)) + { + var attr = (RegisterFormatterLocatorAttribute)attrUncast; + + if (!attr.FormatterLocatorType.IsClass + || attr.FormatterLocatorType.IsAbstract + || attr.FormatterLocatorType.GetConstructor(Type.EmptyTypes) == null + || !typeof(IFormatterLocator).IsAssignableFrom(attr.FormatterLocatorType)) + { + continue; + } + + try + { + FormatterLocators.Add(new FormatterLocatorInfo() + { + LocatorInstance = (IFormatterLocator)Activator.CreateInstance(attr.FormatterLocatorType), + Priority = attr.Priority + }); + } + catch (Exception ex) + { + Debug.LogException(new Exception("Exception was thrown while instantiating FormatterLocator of type " + attr.FormatterLocatorType.FullName + ".", ex)); + } + } + } + catch (TypeLoadException) + { + if (ass.GetName().Name == "OdinSerializer") + { + Debug.LogError("A TypeLoadException occurred when FormatterLocator tried to load types from assembly '" + ass.FullName + "'. No serialization formatters in this assembly will be found. Serialization will be utterly broken."); + } + } + catch (ReflectionTypeLoadException) + { + if (ass.GetName().Name == "OdinSerializer") + { + Debug.LogError("A ReflectionTypeLoadException occurred when FormatterLocator tried to load types from assembly '" + ass.FullName + "'. No serialization formatters in this assembly will be found. Serialization will be utterly broken."); + } + } + catch (MissingMemberException) + { + if (ass.GetName().Name == "OdinSerializer") + { + Debug.LogError("A ReflectionTypeLoadException occurred when FormatterLocator tried to load types from assembly '" + ass.FullName + "'. No serialization formatters in this assembly will be found. Serialization will be utterly broken."); + } + } + } + + // Order formatters and formatter locators by priority and then by name, to ensure consistency regardless of the order of loaded types, which is important for cross-platform cases. + + FormatterInfos.Sort((a, b) => + { + int compare = -a.Priority.CompareTo(b.Priority); + + if (compare == 0) + { + compare = a.FormatterType.Name.CompareTo(b.FormatterType.Name); + } + + return compare; + }); + + FormatterLocators.Sort((a, b) => + { + int compare = -a.Priority.CompareTo(b.Priority); + + if (compare == 0) + { + compare = a.LocatorInstance.GetType().Name.CompareTo(b.LocatorInstance.GetType().Name); + } + + return compare; + }); + } + + /// <summary> + /// This event is invoked before everything else when a formatter is being resolved for a given type. If any invoked delegate returns a valid formatter, that formatter is used and the resolve process stops there. + /// <para /> + /// This can be used to hook into and extend the serialization system's formatter resolution logic. + /// </summary> + [Obsolete("Use the new IFormatterLocator interface instead, and register your custom locator with the RegisterFormatterLocator assembly attribute.", true)] + public static event Func<Type, IFormatter> FormatterResolve + { + add { throw new NotSupportedException(); } + remove { throw new NotSupportedException(); } + } + + /// <summary> + /// Gets a formatter for the type <see cref="T" />. + /// </summary> + /// <typeparam name="T">The type to get a formatter for.</typeparam> + /// <param name="policy">The serialization policy to use if a formatter has to be emitted. If null, <see cref="SerializationPolicies.Strict"/> is used.</param> + /// <returns> + /// A formatter for the type <see cref="T" />. + /// </returns> + public static IFormatter<T> GetFormatter<T>(ISerializationPolicy policy) + { + return (IFormatter<T>)GetFormatter(typeof(T), policy); + } + + /// <summary> + /// Gets a formatter for a given type. + /// </summary> + /// <param name="type">The type to get a formatter for.</param> + /// <param name="policy">The serialization policy to use if a formatter has to be emitted. If null, <see cref="SerializationPolicies.Strict"/> is used.</param> + /// <returns> + /// A formatter for the given type. + /// </returns> + /// <exception cref="System.ArgumentNullException">The type argument is null.</exception> + public static IFormatter GetFormatter(Type type, ISerializationPolicy policy) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (policy == null) + { + policy = SerializationPolicies.Strict; + } + + IFormatter result; + + lock (LOCK) + { + if (TypeFormatterMap.TryGetInnerValue(type, policy, out result) == false) + { + // System.ExecutionEngineException is marked obsolete in .NET 4.6. + // That's all very good for .NET, but Unity still uses it, and that means we use it as well! +#pragma warning disable 618 + try + { + result = CreateFormatter(type, policy); + } + catch (TargetInvocationException ex) + { + if (ex.GetBaseException() is ExecutionEngineException) + { + LogAOTError(type, ex.GetBaseException() as ExecutionEngineException); + } + else + { + throw ex; + } + } + catch (TypeInitializationException ex) + { + if (ex.GetBaseException() is ExecutionEngineException) + { + LogAOTError(type, ex.GetBaseException() as ExecutionEngineException); + } + else + { + throw ex; + } + } + catch (ExecutionEngineException ex) + { + LogAOTError(type, ex); + } + + TypeFormatterMap.AddInner(type, policy, result); +#pragma warning restore 618 + } + } + +#if UNITY_EDITOR + if (OnLocatedFormatter != null) + { + OnLocatedFormatter(result); + } + + if (OnLocatedEmittableFormatterForType != null && result.GetType().IsGenericType) + { +#if CAN_EMIT + if (result.GetType().GetGenericTypeDefinition() == typeof(FormatterEmitter.RuntimeEmittedFormatter<>)) + { + OnLocatedEmittableFormatterForType(type); + } + else +#endif + if (result.GetType().GetGenericTypeDefinition() == typeof(ReflectionFormatter<>)) + { + OnLocatedEmittableFormatterForType(type); + } + } +#endif + + return result; + } + + private static void LogAOTError(Type type, Exception ex) + { + var types = new List<string>(GetAllPossibleMissingAOTTypes(type)).ToArray(); + + Debug.LogError("Creating a serialization formatter for the type '" + type.GetNiceFullName() + "' failed due to missing AOT support. \n\n" + + " Please use Odin's AOT generation feature to generate an AOT dll before building, and MAKE SURE that all of the following " + + "types were automatically added to the supported types list after a scan (if they were not, please REPORT AN ISSUE with the details of which exact types the scan is missing " + + "and ADD THEM MANUALLY): \n\n" + string.Join("\n", types) + "\n\nIF ALL THE TYPES ARE IN THE SUPPORT LIST AND YOU STILL GET THIS ERROR, PLEASE REPORT AN ISSUE." + + "The exception contained the following message: \n" + ex.Message); + + throw new SerializationAbortException("AOT formatter support was missing for type '" + type.GetNiceFullName() + "'."); + } + + private static IEnumerable<string> GetAllPossibleMissingAOTTypes(Type type) + { + yield return type.GetNiceFullName() + " (name string: '" + TwoWaySerializationBinder.Default.BindToName(type) + "')"; + + if (!type.IsGenericType) yield break; + + foreach (var arg in type.GetGenericArguments()) + { + yield return arg.GetNiceFullName() + " (name string: '" + TwoWaySerializationBinder.Default.BindToName(arg) + "')"; + + if (arg.IsGenericType) + { + foreach (var subArg in GetAllPossibleMissingAOTTypes(arg)) + { + yield return subArg; + } + } + } + } + + internal static List<IFormatter> GetAllCompatiblePredefinedFormatters(Type type, ISerializationPolicy policy) + { + if (FormatterUtilities.IsPrimitiveType(type)) + { + throw new ArgumentException("Cannot create formatters for a primitive type like " + type.Name); + } + + List<IFormatter> formatters = new List<IFormatter>(); + + // First call formatter locators before checking for registered formatters + for (int i = 0; i < FormatterLocators.Count; i++) + { + try + { + IFormatter result; + if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.BeforeRegisteredFormatters, policy, out result)) + { + formatters.Add(result); + } + } + catch (TargetInvocationException ex) + { + throw ex; + } + catch (TypeInitializationException ex) + { + throw ex; + } +#pragma warning disable CS0618 // Type or member is obsolete + catch (ExecutionEngineException ex) +#pragma warning restore CS0618 // Type or member is obsolete + { + throw ex; + } + catch (Exception ex) + { + Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex)); + } + } + + // Then check for valid registered formatters + for (int i = 0; i < FormatterInfos.Count; i++) + { + var info = FormatterInfos[i]; + + Type formatterType = null; + + if (type == info.TargetType) + { + formatterType = info.FormatterType; + } + else if (info.FormatterType.IsGenericType && info.TargetType.IsGenericParameter) + { + Type[] inferredArgs; + + if (info.FormatterType.TryInferGenericParameters(out inferredArgs, type)) + { + formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(inferredArgs); + } + } + else if (type.IsGenericType && info.FormatterType.IsGenericType && info.TargetType.IsGenericType && type.GetGenericTypeDefinition() == info.TargetType.GetGenericTypeDefinition()) + { + Type[] args = type.GetGenericArguments(); + + if (info.FormatterType.AreGenericConstraintsSatisfiedBy(args)) + { + formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(args); + } + } + + if (formatterType != null) + { + var instance = GetFormatterInstance(formatterType); + + if (instance == null) continue; + + if (info.AskIfCanFormatTypes && !((IAskIfCanFormatTypes)instance).CanFormatType(type)) + { + continue; + } + + formatters.Add(instance); + } + } + + // Then call formatter locators after checking for registered formatters + for (int i = 0; i < FormatterLocators.Count; i++) + { + try + { + IFormatter result; + if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.AfterRegisteredFormatters, policy, out result)) + { + formatters.Add(result); + } + } + catch (TargetInvocationException ex) + { + throw ex; + } + catch (TypeInitializationException ex) + { + throw ex; + } +#pragma warning disable CS0618 // Type or member is obsolete + catch (ExecutionEngineException ex) +#pragma warning restore CS0618 // Type or member is obsolete + { + throw ex; + } + catch (Exception ex) + { + Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex)); + } + } + + formatters.Add((IFormatter)Activator.CreateInstance(typeof(ReflectionFormatter<>).MakeGenericType(type))); + + return formatters; + } + + private static IFormatter CreateFormatter(Type type, ISerializationPolicy policy) + { + if (FormatterUtilities.IsPrimitiveType(type)) + { + throw new ArgumentException("Cannot create formatters for a primitive type like " + type.Name); + } + + // First call formatter locators before checking for registered formatters + for (int i = 0; i < FormatterLocators.Count; i++) + { + try + { + IFormatter result; + if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.BeforeRegisteredFormatters, policy, out result)) + { + return result; + } + } + catch (TargetInvocationException ex) + { + throw ex; + } + catch (TypeInitializationException ex) + { + throw ex; + } +#pragma warning disable CS0618 // Type or member is obsolete + catch (ExecutionEngineException ex) +#pragma warning restore CS0618 // Type or member is obsolete + { + throw ex; + } + catch (Exception ex) + { + Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex)); + } + } + + // Then check for valid registered formatters + for (int i = 0; i < FormatterInfos.Count; i++) + { + var info = FormatterInfos[i]; + + Type formatterType = null; + + if (type == info.TargetType) + { + formatterType = info.FormatterType; + } + else if (info.FormatterType.IsGenericType && info.TargetType.IsGenericParameter) + { + Type[] inferredArgs; + + if (info.FormatterType.TryInferGenericParameters(out inferredArgs, type)) + { + formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(inferredArgs); + } + } + else if (type.IsGenericType && info.FormatterType.IsGenericType && info.TargetType.IsGenericType && type.GetGenericTypeDefinition() == info.TargetType.GetGenericTypeDefinition()) + { + Type[] args = type.GetGenericArguments(); + + if (info.FormatterType.AreGenericConstraintsSatisfiedBy(args)) + { + formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(args); + } + } + + if (formatterType != null) + { + var instance = GetFormatterInstance(formatterType); + + if (instance == null) continue; + + if (info.AskIfCanFormatTypes && !((IAskIfCanFormatTypes)instance).CanFormatType(type)) + { + continue; + } + + return instance; + } + } + + // Then call formatter locators after checking for registered formatters + for (int i = 0; i < FormatterLocators.Count; i++) + { + try + { + IFormatter result; + if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.AfterRegisteredFormatters, policy, out result)) + { + return result; + } + } + catch (TargetInvocationException ex) + { + throw ex; + } + catch (TypeInitializationException ex) + { + throw ex; + } +#pragma warning disable CS0618 // Type or member is obsolete + catch (ExecutionEngineException ex) +#pragma warning restore CS0618 // Type or member is obsolete + { + throw ex; + } + catch (Exception ex) + { + Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex)); + } + } + + // If we can, emit a formatter to handle serialization of this object + { + if (EmitUtilities.CanEmit) + { + var result = FormatterEmitter.GetEmittedFormatter(type, policy); + if (result != null) return result; + } + } + + if (EmitUtilities.CanEmit) + { + Debug.LogWarning("Fallback to reflection for type " + type.Name + " when emit is possible on this platform."); + } + + // Finally, we fall back to a reflection-based formatter if nothing else has been found + return (IFormatter)Activator.CreateInstance(typeof(ReflectionFormatter<>).MakeGenericType(type)); + } + + private static IFormatter GetFormatterInstance(Type type) + { + IFormatter formatter; + if (!FormatterInstances.TryGetValue(type, out formatter)) + { + try + { + formatter = (IFormatter)Activator.CreateInstance(type); + FormatterInstances.Add(type, formatter); + } + catch (TargetInvocationException ex) + { + throw ex; + } + catch (TypeInitializationException ex) + { + throw ex; + } +#pragma warning disable CS0618 // Type or member is obsolete + catch (ExecutionEngineException ex) +#pragma warning restore CS0618 // Type or member is obsolete + { + throw ex; + } + catch (Exception ex) + { + Debug.LogException(new Exception("Exception was thrown while instantiating formatter '" + type.GetNiceFullName() + "'.", ex)); + } + } + return formatter; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/FormatterLocator.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/FormatterLocator.cs.meta new file mode 100644 index 00000000..add7155d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/FormatterLocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf715e98fa96d74c81b4d3f4491d2592 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/GenericCollectionFormatterLocator.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/GenericCollectionFormatterLocator.cs new file mode 100644 index 00000000..d1ff7fac --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/GenericCollectionFormatterLocator.cs @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------- +// <copyright file="GenericCollectionFormatterLocator.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatterLocator(typeof(GenericCollectionFormatterLocator), -100)] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + internal class GenericCollectionFormatterLocator : IFormatterLocator + { + public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter) + { + Type elementType; + if (step != FormatterLocationStep.AfterRegisteredFormatters || !GenericCollectionFormatter.CanFormat(type, out elementType)) + { + formatter = null; + return false; + } + + formatter = (IFormatter)Activator.CreateInstance(typeof(GenericCollectionFormatter<,>).MakeGenericType(type, elementType)); + return true; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/GenericCollectionFormatterLocator.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/GenericCollectionFormatterLocator.cs.meta new file mode 100644 index 00000000..0d8e8674 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/GenericCollectionFormatterLocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d35d0d1eb290f5d00e273d65e5db09d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/IFormatterLocator.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/IFormatterLocator.cs new file mode 100644 index 00000000..cafdcd00 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/IFormatterLocator.cs @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------- +// <copyright file="IFormatterLocator.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + public interface IFormatterLocator + { + bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/IFormatterLocator.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/IFormatterLocator.cs.meta new file mode 100644 index 00000000..964d8638 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/IFormatterLocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2a9beaeecdd6eb929ddb049d7846a14 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ISerializableFormatterLocator.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ISerializableFormatterLocator.cs new file mode 100644 index 00000000..5e590dcf --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ISerializableFormatterLocator.cs @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------- +// <copyright file="ISerializableFormatterLocator.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatterLocator(typeof(ISerializableFormatterLocator), -110)] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using Utilities; + using System; + using System.Collections.Generic; + using System.Runtime.Serialization; + + internal class ISerializableFormatterLocator : IFormatterLocator + { + public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter) + { + if (step != FormatterLocationStep.AfterRegisteredFormatters || !typeof(ISerializable).IsAssignableFrom(type)) + { + formatter = null; + return false; + } + + formatter = (IFormatter)Activator.CreateInstance(typeof(SerializableFormatter<>).MakeGenericType(type)); + return true; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ISerializableFormatterLocator.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ISerializableFormatterLocator.cs.meta new file mode 100644 index 00000000..458bfd33 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/ISerializableFormatterLocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cdd12b278851bfdc68ca0d9e1e4f2d28 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/SelfFormatterLocator.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/SelfFormatterLocator.cs new file mode 100644 index 00000000..c1719a7a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/SelfFormatterLocator.cs @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------- +// <copyright file="SelfFormatterLocator.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatterLocator(typeof(SelfFormatterLocator), -60)] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using Utilities; + + internal class SelfFormatterLocator : IFormatterLocator + { + public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter) + { + formatter = null; + + if (!typeof(ISelfFormatter).IsAssignableFrom(type)) return false; + + if ((step == FormatterLocationStep.BeforeRegisteredFormatters && type.IsDefined<AlwaysFormatsSelfAttribute>()) + || step == FormatterLocationStep.AfterRegisteredFormatters) + { + formatter = (IFormatter)Activator.CreateInstance(typeof(SelfFormatterFormatter<>).MakeGenericType(type)); + return true; + } + + return false; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/SelfFormatterLocator.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/SelfFormatterLocator.cs.meta new file mode 100644 index 00000000..d71900a5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/SelfFormatterLocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 876ae9a404abe412e663fd9bc03d3525 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/TypeFormatterLocator.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/TypeFormatterLocator.cs new file mode 100644 index 00000000..eceb244b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/TypeFormatterLocator.cs @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------- +// <copyright file="TypeFormatterLocator.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatterLocator(typeof(TypeFormatterLocator), -70)] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + internal class TypeFormatterLocator : IFormatterLocator + { + public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter) + { + if (!typeof(Type).IsAssignableFrom(type)) + { + formatter = null; + return false; + } + + formatter = new TypeFormatter(); + return true; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/TypeFormatterLocator.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/TypeFormatterLocator.cs.meta new file mode 100644 index 00000000..f84afc76 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/FormatterLocators/TypeFormatterLocator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 00e10f526d476731ebc596ceb66271a6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters.meta new file mode 100644 index 00000000..af9223bd --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8dfed505173c3b7489a2cc09010cf3e5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayFormatter.cs new file mode 100644 index 00000000..86ce58e7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayFormatter.cs @@ -0,0 +1,109 @@ +//----------------------------------------------------------------------- +// <copyright file="ArrayFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Formatter for all non-primitive one-dimensional arrays. + /// </summary> + /// <typeparam name="T">The element type of the formatted array.</typeparam> + /// <seealso cref="BaseFormatter{T[]}" /> + public sealed class ArrayFormatter<T> : BaseFormatter<T[]> + { + private static Serializer<T> valueReaderWriter = Serializer.Get<T>(); + + /// <summary> + /// Returns null. + /// </summary> + /// <returns> + /// A null value. + /// </returns> + protected override T[] GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref T[] value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.StartOfArray) + { + long length; + reader.EnterArray(out length); + + value = new T[length]; + + // We must remember to register the array reference ourselves, since we return null in GetUninitializedObject + this.RegisterReferenceID(value, reader); + + // There aren't any OnDeserializing callbacks on arrays. + // Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context); + for (int i = 0; i < length; i++) + { + if (reader.PeekEntry(out name) == EntryType.EndOfArray) + { + reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected."); + break; + } + + value[i] = valueReaderWriter.ReadValue(reader); + + if (reader.PeekEntry(out name) == EntryType.EndOfStream) + { + break; + } + } + + reader.ExitArray(); + } + else + { + reader.SkipEntry(); + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref T[] value, IDataWriter writer) + { + try + { + writer.BeginArrayNode(value.Length); + + for (int i = 0; i < value.Length; i++) + { + valueReaderWriter.WriteValue(value[i], writer); + } + } + finally + { + writer.EndArrayNode(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayFormatter.cs.meta new file mode 100644 index 00000000..827f99b0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9aaf14140a26e04b861b027d5ddb8fb6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayListFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayListFormatter.cs new file mode 100644 index 00000000..4b24a452 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayListFormatter.cs @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------- +// <copyright file="ArrayListFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(ArrayListFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections; + + /// <summary> + /// Custom formatter for the type <see cref="ArrayList"/>. + /// </summary> + /// <seealso cref="BaseFormatter{System.Collections.Generic.List{T}}" /> + public class ArrayListFormatter : BaseFormatter<ArrayList> + { + private static readonly Serializer<object> ObjectSerializer = Serializer.Get<object>(); + + /// <summary> + /// Returns null. + /// </summary> + /// <returns> + /// A null value. + /// </returns> + protected override ArrayList GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref ArrayList value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.StartOfArray) + { + try + { + long length; + reader.EnterArray(out length); + value = new ArrayList((int)length); + + // We must remember to register the array reference ourselves, since we return null in GetUninitializedObject + this.RegisterReferenceID(value, reader); + + // There aren't any OnDeserializing callbacks on array lists. + // Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context); + for (int i = 0; i < length; i++) + { + if (reader.PeekEntry(out name) == EntryType.EndOfArray) + { + reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected."); + break; + } + + value.Add(ObjectSerializer.ReadValue(reader)); + + if (reader.IsInArrayNode == false) + { + // Something has gone wrong + reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump()); + break; + } + } + } + finally + { + reader.ExitArray(); + } + } + else + { + reader.SkipEntry(); + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref ArrayList value, IDataWriter writer) + { + try + { + writer.BeginArrayNode(value.Count); + + for (int i = 0; i < value.Count; i++) + { + try + { + ObjectSerializer.WriteValue(value[i], writer); + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + } + } + finally + { + writer.EndArrayNode(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayListFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayListFormatter.cs.meta new file mode 100644 index 00000000..10ea42a1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ArrayListFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3f5dc00eb17e568de42119a7f0f30ee8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/BaseFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/BaseFormatter.cs new file mode 100644 index 00000000..93008be3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/BaseFormatter.cs @@ -0,0 +1,417 @@ +//----------------------------------------------------------------------- +// <copyright file="BaseFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +#if (UNITY_EDITOR || UNITY_STANDALONE) && !ENABLE_IL2CPP +#define CAN_EMIT +#endif + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using VRC.Udon.Serialization.OdinSerializer.Utilities; + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Runtime.Serialization; + + /// <summary> + /// Provides common functionality for serializing and deserializing values of type <see cref="T"/>, and provides automatic support for the following common serialization conventions: + /// <para /> + /// <see cref="IObjectReference"/>, <see cref="ISerializationCallbackReceiver"/>, <see cref="OnSerializingAttribute"/>, <see cref="OnSerializedAttribute"/>, <see cref="OnDeserializingAttribute"/> and <see cref="OnDeserializedAttribute"/>. + /// </summary> + /// <typeparam name="T">The type which can be serialized and deserialized by the formatter.</typeparam> + /// <seealso cref="IFormatter{T}" /> + public abstract class BaseFormatter<T> : IFormatter<T> + { + protected delegate void SerializationCallback(ref T value, StreamingContext context); + + /// <summary> + /// The on serializing callbacks for type <see cref="T"/>. + /// </summary> + protected static readonly SerializationCallback[] OnSerializingCallbacks; + + /// <summary> + /// The on serialized callbacks for type <see cref="T"/>. + /// </summary> + protected static readonly SerializationCallback[] OnSerializedCallbacks; + + /// <summary> + /// The on deserializing callbacks for type <see cref="T"/>. + /// </summary> + protected static readonly SerializationCallback[] OnDeserializingCallbacks; + + /// <summary> + /// The on deserialized callbacks for type <see cref="T"/>. + /// </summary> + protected static readonly SerializationCallback[] OnDeserializedCallbacks; + + /// <summary> + /// Whether the serialized value is a value type. + /// </summary> + protected static readonly bool IsValueType = typeof(T).IsValueType; + + protected static readonly bool ImplementsISerializationCallbackReceiver = typeof(T).ImplementsOrInherits(typeof(UnityEngine.ISerializationCallbackReceiver)); + protected static readonly bool ImplementsIDeserializationCallback = typeof(T).ImplementsOrInherits(typeof(IDeserializationCallback)); + protected static readonly bool ImplementsIObjectReference = typeof(T).ImplementsOrInherits(typeof(IObjectReference)); + + static BaseFormatter() + { + if (typeof(T).ImplementsOrInherits(typeof(UnityEngine.Object))) + { + DefaultLoggers.DefaultLogger.LogWarning("A formatter has been created for the UnityEngine.Object type " + typeof(T).Name + " - this is *strongly* discouraged. Unity should be allowed to handle serialization and deserialization of its own weird objects. Remember to serialize with a UnityReferenceResolver as the external index reference resolver in the serialization context.\n\n Stacktrace: " + new System.Diagnostics.StackTrace().ToString()); + } + + MethodInfo[] methods = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + List<SerializationCallback> callbacks = new List<SerializationCallback>(); + + OnSerializingCallbacks = GetCallbacks(methods, typeof(OnSerializingAttribute), ref callbacks); + OnSerializedCallbacks = GetCallbacks(methods, typeof(OnSerializedAttribute), ref callbacks); + OnDeserializingCallbacks = GetCallbacks(methods, typeof(OnDeserializingAttribute), ref callbacks); + OnDeserializedCallbacks = GetCallbacks(methods, typeof(OnDeserializedAttribute), ref callbacks); + } + + private static SerializationCallback[] GetCallbacks(MethodInfo[] methods, Type callbackAttribute, ref List<SerializationCallback> list) + { + for (int i = 0; i < methods.Length; i++) + { + var method = methods[i]; + + if (method.IsDefined(callbackAttribute, true)) + { + var callback = CreateCallback(method); + + if (callback != null) + { + list.Add(callback); + } + } + } + + var result = list.ToArray(); + list.Clear(); + return result; + } + + private static SerializationCallback CreateCallback(MethodInfo info) + { + var parameters = info.GetParameters(); + if (parameters.Length == 0) + { +#if CAN_EMIT + var action = EmitUtilities.CreateInstanceRefMethodCaller<T>(info); + return (ref T value, StreamingContext context) => action(ref value); +#else + return (ref T value, StreamingContext context) => + { + object obj = value; + info.Invoke(obj, null); + value = (T)obj; + }; +#endif + } + else if (parameters.Length == 1 && parameters[0].ParameterType == typeof(StreamingContext) && parameters[0].ParameterType.IsByRef == false) + { +#if CAN_EMIT + var action = EmitUtilities.CreateInstanceRefMethodCaller<T, StreamingContext>(info); + return (ref T value, StreamingContext context) => action(ref value, context); +#else + return (ref T value, StreamingContext context) => + { + object obj = value; + info.Invoke(obj, new object[] { context }); + value = (T)obj; + }; +#endif + } + else + { + DefaultLoggers.DefaultLogger.LogWarning("The method " + info.GetNiceName() + " has an invalid signature and will be ignored by the serialization system."); + return null; + } + } + + /// <summary> + /// Gets the type that the formatter can serialize. + /// </summary> + /// <value> + /// The type that the formatter can serialize. + /// </value> + public Type SerializedType { get { return typeof(T); } } + + /// <summary> + /// Serializes a value using a specified <see cref="IDataWriter" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to use.</param> + void IFormatter.Serialize(object value, IDataWriter writer) + { + this.Serialize((T)value, writer); + } + + /// <summary> + /// Deserializes a value using a specified <see cref="IDataReader" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The deserialized value. + /// </returns> + object IFormatter.Deserialize(IDataReader reader) + { + return this.Deserialize(reader); + } + + /// <summary> + /// Deserializes a value of type <see cref="T" /> using a specified <see cref="IDataReader" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public T Deserialize(IDataReader reader) + { + var context = reader.Context; + T value = this.GetUninitializedObject(); + + // We allow the above method to return null (for reference types) because of special cases like arrays, + // where the size of the array cannot be known yet, and thus we cannot create an object instance at this time. + // + // Therefore, those who override GetUninitializedObject and return null must call RegisterReferenceID and InvokeOnDeserializingCallbacks manually. + if (BaseFormatter<T>.IsValueType) + { + this.InvokeOnDeserializingCallbacks(ref value, context); + } + else + { + if (object.ReferenceEquals(value, null) == false) + { + this.RegisterReferenceID(value, reader); + this.InvokeOnDeserializingCallbacks(ref value, context); + + if (ImplementsIObjectReference) + { + try + { + value = (T)(value as IObjectReference).GetRealObject(context.StreamingContext); + this.RegisterReferenceID(value, reader); + } + catch (Exception ex) + { + context.Config.DebugContext.LogException(ex); + } + } + } + } + + try + { + this.DeserializeImplementation(ref value, reader); + } + catch (Exception ex) + { + context.Config.DebugContext.LogException(ex); + } + + // The deserialized value might be null, so check for that + if (BaseFormatter<T>.IsValueType || object.ReferenceEquals(value, null) == false) + { + for (int i = 0; i < OnDeserializedCallbacks.Length; i++) + { + try + { + OnDeserializedCallbacks[i](ref value, context.StreamingContext); + } + catch (Exception ex) + { + context.Config.DebugContext.LogException(ex); + } + } + + if (ImplementsIDeserializationCallback) + { + IDeserializationCallback v = value as IDeserializationCallback; + v.OnDeserialization(this); + value = (T)v; + } + + if (ImplementsISerializationCallbackReceiver) + { + try + { + UnityEngine.ISerializationCallbackReceiver v = value as UnityEngine.ISerializationCallbackReceiver; + v.OnAfterDeserialize(); + value = (T)v; + } + catch (Exception ex) + { + context.Config.DebugContext.LogException(ex); + } + } + } + + return value; + } + + /// <summary> + /// Serializes a value of type <see cref="T" /> using a specified <see cref="IDataWriter" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to use.</param> + public void Serialize(T value, IDataWriter writer) + { + var context = writer.Context; + + for (int i = 0; i < OnSerializingCallbacks.Length; i++) + { + try + { + OnSerializingCallbacks[i](ref value, context.StreamingContext); + } + catch (Exception ex) + { + context.Config.DebugContext.LogException(ex); + } + } + + if (ImplementsISerializationCallbackReceiver) + { + try + { + + UnityEngine.ISerializationCallbackReceiver v = value as UnityEngine.ISerializationCallbackReceiver; + v.OnBeforeSerialize(); + value = (T)v; + } + catch (Exception ex) + { + context.Config.DebugContext.LogException(ex); + } + } + + try + { + this.SerializeImplementation(ref value, writer); + } + catch (Exception ex) + { + context.Config.DebugContext.LogException(ex); + } + + for (int i = 0; i < OnSerializedCallbacks.Length; i++) + { + try + { + OnSerializedCallbacks[i](ref value, context.StreamingContext); + } + catch (Exception ex) + { + context.Config.DebugContext.LogException(ex); + } + } + } + + /// <summary> + /// Get an uninitialized object of type <see cref="T"/>. WARNING: If you override this and return null, the object's ID will not be automatically registered and its OnDeserializing callbacks will not be automatically called, before deserialization begins. + /// You will have to call <see cref="BaseFormatter{T}.RegisterReferenceID(T, IDataReader)"/> and <see cref="BaseFormatter{T}.InvokeOnDeserializingCallbacks(ref T, DeserializationContext)"/> immediately after creating the object yourself during deserialization. + /// </summary> + /// <returns>An uninitialized object of type <see cref="T"/>.</returns> + protected virtual T GetUninitializedObject() + { + if (BaseFormatter<T>.IsValueType) + { + return default(T); + } + else + { + return (T)FormatterServices.GetUninitializedObject(typeof(T)); + } + } + + /// <summary> + /// Registers the given object reference in the deserialization context. + /// <para /> + /// NOTE that this method only does anything if <see cref="T"/> is not a value type. + /// </summary> + /// <param name="value">The value to register.</param> + /// <param name="reader">The reader which is currently being used.</param> + protected void RegisterReferenceID(T value, IDataReader reader) + { + if (!BaseFormatter<T>.IsValueType) + { + int id = reader.CurrentNodeId; + + if (id < 0) + { + reader.Context.Config.DebugContext.LogWarning("Reference type node is missing id upon deserialization. Some references may be broken. This tends to happen if a value type has changed to a reference type (IE, struct to class) since serialization took place."); + } + else + { + reader.Context.RegisterInternalReference(id, value); + } + } + } + + /// <summary> + /// Invokes all methods on the object with the [OnDeserializing] attribute. + /// <para /> + /// WARNING: This method will not be called automatically if you override GetUninitializedObject and return null! You will have to call it manually after having created the object instance during deserialization. + /// </summary> + /// <param name="value">The value to invoke the callbacks on.</param> + /// <param name="context">The deserialization context.</param> + [Obsolete("Use the InvokeOnDeserializingCallbacks variant that takes a ref T value instead. This is for struct compatibility reasons.", false)] + protected void InvokeOnDeserializingCallbacks(T value, DeserializationContext context) + { + this.InvokeOnDeserializingCallbacks(ref value, context); + } + + /// <summary> + /// Invokes all methods on the object with the [OnDeserializing] attribute. + /// <para /> + /// WARNING: This method will not be called automatically if you override GetUninitializedObject and return null! You will have to call it manually after having created the object instance during deserialization. + /// </summary> + /// <param name="value">The value to invoke the callbacks on.</param> + /// <param name="context">The deserialization context.</param> + protected void InvokeOnDeserializingCallbacks(ref T value, DeserializationContext context) + { + for (int i = 0; i < OnDeserializingCallbacks.Length; i++) + { + try + { + OnDeserializingCallbacks[i](ref value, context.StreamingContext); + } + catch (Exception ex) + { + context.Config.DebugContext.LogException(ex); + } + } + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected abstract void DeserializeImplementation(ref T value, IDataReader reader); + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected abstract void SerializeImplementation(ref T value, IDataWriter writer); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/BaseFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/BaseFormatter.cs.meta new file mode 100644 index 00000000..55c55b78 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/BaseFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9598679c29f3e3696941746c26f1ccf8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeFormatter.cs new file mode 100644 index 00000000..4380fb3c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeFormatter.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// <copyright file="DateTimeFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(DateTimeFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Custom formatter for the <see cref="DateTime"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{System.DateTime}" /> + public sealed class DateTimeFormatter : MinimalBaseFormatter<DateTime> + { + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref DateTime value, IDataReader reader) + { + string name; + + if (reader.PeekEntry(out name) == EntryType.Integer) + { + long binary; + reader.ReadInt64(out binary); + value = DateTime.FromBinary(binary); + } + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref DateTime value, IDataWriter writer) + { + writer.WriteInt64(null, value.ToBinary()); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeFormatter.cs.meta new file mode 100644 index 00000000..2729f868 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dff51bfb9b4d71aa78b3e5c8fec8c924 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeOffsetFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeOffsetFormatter.cs new file mode 100644 index 00000000..94c44736 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeOffsetFormatter.cs @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------- +// <copyright file="DateTimeOffsetFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(DateTimeOffsetFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System.Globalization; + using System; + + /// <summary> + /// Custom formatter for the <see cref="DateTimeOffset"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{System.DateTimeOffset}" /> + public sealed class DateTimeOffsetFormatter : MinimalBaseFormatter<DateTimeOffset> + { + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref DateTimeOffset value, IDataReader reader) + { + string name; + + if (reader.PeekEntry(out name) == EntryType.String) + { + string str; + reader.ReadString(out str); + DateTimeOffset.TryParse(str, out value); + } + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref DateTimeOffset value, IDataWriter writer) + { + writer.WriteString(null, value.ToString("O", CultureInfo.InvariantCulture)); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeOffsetFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeOffsetFormatter.cs.meta new file mode 100644 index 00000000..d18bb9ec --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DateTimeOffsetFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3480954e7eecdc9745c1d08721b2f8b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DelegateFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DelegateFormatter.cs new file mode 100644 index 00000000..c91b2538 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DelegateFormatter.cs @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------- +// <copyright file="DelegateFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Linq; + using System.Reflection; + using Utilities; + + /// <summary> + /// Formatter for all delegate types. + /// <para /> + /// This formatter can handle anything but delegates for dynamic methods. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <seealso cref="BaseFormatter{T}" /> + public sealed class DelegateFormatter<T> : BaseFormatter<T> where T : class + { + static DelegateFormatter() + { + if (typeof(Delegate).IsAssignableFrom(typeof(T)) == false) + { + throw new ArgumentException("The type " + typeof(T) + " is not a delegate."); + } + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="!:T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="M:OdinSerializer.BaseFormatter`1.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref T value, IDataReader reader) + { + reader.Context.Config.DebugContext.LogWarning("Delegate Deserialization has been removed for security."); + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="!:T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref T value, IDataWriter writer) + { + writer.Context.Config.DebugContext.LogWarning("Delegate Deserialization has been removed for security."); + } + + /// <summary> + /// Get an uninitialized object of type <see cref="!:T" />. WARNING: If you override this and return null, the object's ID will not be automatically registered and its OnDeserializing callbacks will not be automatically called, before deserialization begins. + /// You will have to call <see cref="M:OdinSerializer.BaseFormatter`1.RegisterReferenceID(`0,OdinSerializer.IDataReader)" /> and <see cref="M:OdinSerializer.BaseFormatter`1.InvokeOnDeserializingCallbacks(`0,OdinSerializer.DeserializationContext)" /> immediately after creating the object yourself during deserialization. + /// </summary> + /// <returns> + /// An uninitialized object of type <see cref="!:T" />. + /// </returns> + protected override T GetUninitializedObject() + { + return null; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DelegateFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DelegateFormatter.cs.meta new file mode 100644 index 00000000..f2b37166 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DelegateFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f17b17e986ae6f3be6a2ea1b716fcaf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DerivedDictionaryFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DerivedDictionaryFormatter.cs new file mode 100644 index 00000000..3cba37b9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DerivedDictionaryFormatter.cs @@ -0,0 +1,216 @@ +//----------------------------------------------------------------------- +// <copyright file="DerivedDictionaryFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(DerivedDictionaryFormatter<,,>))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using System.Reflection; + + /// <summary> + /// Emergency hack class to support serialization of types derived from dictionary + /// </summary> + internal sealed class DerivedDictionaryFormatter<TDictionary, TKey, TValue> : BaseFormatter<TDictionary> + where TDictionary : Dictionary<TKey, TValue>, new() + { + private static readonly bool KeyIsValueType = typeof(TKey).IsValueType; + + private static readonly Serializer<IEqualityComparer<TKey>> EqualityComparerSerializer = Serializer.Get<IEqualityComparer<TKey>>(); + private static readonly Serializer<TKey> KeyReaderWriter = Serializer.Get<TKey>(); + private static readonly Serializer<TValue> ValueReaderWriter = Serializer.Get<TValue>(); + + private static readonly ConstructorInfo ComparerConstructor = typeof(TDictionary).GetConstructor(new Type[] { typeof(IEqualityComparer<TKey>) }); + + static DerivedDictionaryFormatter() + { + // This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor + // which it otherwise seems prone to do, regardless of what might be defined in any link.xml file. + + new DerivedDictionaryFormatter<Dictionary<int, string>, int, string>(); + } + + public DerivedDictionaryFormatter() + { + } + + /// <summary> + /// Returns null. + /// </summary> + /// <returns> + /// A value of null. + /// </returns> + protected override TDictionary GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref TDictionary value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + IEqualityComparer<TKey> comparer = null; + + if (name == "comparer" || entry == EntryType.StartOfNode) + { + // There is a comparer serialized + comparer = EqualityComparerSerializer.ReadValue(reader); + entry = reader.PeekEntry(out name); + } + + if (entry == EntryType.StartOfArray) + { + try + { + long length; + reader.EnterArray(out length); + Type type; + + if (!object.ReferenceEquals(comparer, null) && ComparerConstructor != null) + { + value = (TDictionary)ComparerConstructor.Invoke(new object[] { comparer }); + } + else + { + value = new TDictionary(); + } + + // We must remember to register the dictionary reference ourselves, since we returned null in GetUninitializedObject + this.RegisterReferenceID(value, reader); + + // There aren't any OnDeserializing callbacks on dictionaries that we're interested in. + // Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context); + for (int i = 0; i < length; i++) + { + if (reader.PeekEntry(out name) == EntryType.EndOfArray) + { + reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected."); + break; + } + + bool exitNode = true; + + try + { + reader.EnterNode(out type); + TKey key = KeyReaderWriter.ReadValue(reader); + TValue val = ValueReaderWriter.ReadValue(reader); + + if (!KeyIsValueType && object.ReferenceEquals(key, null)) + { + reader.Context.Config.DebugContext.LogWarning("Dictionary key of type '" + typeof(TKey).FullName + "' was null upon deserialization. A key has gone missing."); + continue; + } + + value[key] = val; + } + catch (SerializationAbortException ex) + { + exitNode = false; + throw ex; + } + catch (Exception ex) + { + reader.Context.Config.DebugContext.LogException(ex); + } + finally + { + if (exitNode) + { + reader.ExitNode(); + } + } + + if (reader.IsInArrayNode == false) + { + reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump()); + break; + } + } + } + finally + { + reader.ExitArray(); + } + } + else + { + reader.SkipEntry(); + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref TDictionary value, IDataWriter writer) + { + try + { + if (value.Comparer != null) + { + EqualityComparerSerializer.WriteValue("comparer", value.Comparer, writer); + } + + writer.BeginArrayNode(value.Count); + + foreach (var pair in value) + { + bool endNode = true; + + try + { + writer.BeginStructNode(null, null); + KeyReaderWriter.WriteValue("$k", pair.Key, writer); + ValueReaderWriter.WriteValue("$v", pair.Value, writer); + } + catch (SerializationAbortException ex) + { + endNode = false; + throw ex; + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + finally + { + if (endNode) + { + writer.EndNode(null); + } + } + } + } + finally + { + writer.EndArrayNode(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DerivedDictionaryFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DerivedDictionaryFormatter.cs.meta new file mode 100644 index 00000000..87a2c032 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DerivedDictionaryFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4402da708267c579874c808a813bfe62 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DictionaryFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DictionaryFormatter.cs new file mode 100644 index 00000000..1c33ee70 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DictionaryFormatter.cs @@ -0,0 +1,215 @@ +//----------------------------------------------------------------------- +// <copyright file="DictionaryFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(DictionaryFormatter<,>))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Custom generic formatter for the generic type definition <see cref="Dictionary{TKey, TValue}"/>. + /// </summary> + /// <typeparam name="TKey">The type of the dictionary key.</typeparam> + /// <typeparam name="TValue">The type of the dictionary value.</typeparam> + /// <seealso cref="BaseFormatter{System.Collections.Generic.Dictionary{TKey, TValue}}" /> + public sealed class DictionaryFormatter<TKey, TValue> : BaseFormatter<Dictionary<TKey, TValue>> + { + private static readonly bool KeyIsValueType = typeof(TKey).IsValueType; + + private static readonly Serializer<IEqualityComparer<TKey>> EqualityComparerSerializer = Serializer.Get<IEqualityComparer<TKey>>(); + private static readonly Serializer<TKey> KeyReaderWriter = Serializer.Get<TKey>(); + private static readonly Serializer<TValue> ValueReaderWriter = Serializer.Get<TValue>(); + + static DictionaryFormatter() + { + // This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor + // which it otherwise seems prone to do, regardless of what might be defined in any link.xml file. + + new DictionaryFormatter<int, string>(); + } + + /// <summary> + /// Creates a new instance of <see cref="DictionaryFormatter{TKey, TValue}"/>. + /// </summary> + public DictionaryFormatter() + { + } + + /// <summary> + /// Returns null. + /// </summary> + /// <returns> + /// A value of null. + /// </returns> + protected override Dictionary<TKey, TValue> GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref Dictionary<TKey, TValue> value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + IEqualityComparer<TKey> comparer = null; + + // TODO: Remove this clause in patch 1.1 or later, when it has had time to take effect in people's serialized data + // Clause was introduced in the patch released after 1.0.5.3 + if (name == "comparer" || entry != EntryType.StartOfArray) + { + // There is a comparer serialized + comparer = EqualityComparerSerializer.ReadValue(reader); + entry = reader.PeekEntry(out name); + } + + if (entry == EntryType.StartOfArray) + { + try + { + long length; + reader.EnterArray(out length); + Type type; + + value = object.ReferenceEquals(comparer, null) ? + new Dictionary<TKey, TValue>((int)length) : + new Dictionary<TKey, TValue>((int)length, comparer); + + // We must remember to register the dictionary reference ourselves, since we return null in GetUninitializedObject + this.RegisterReferenceID(value, reader); + + // There aren't any OnDeserializing callbacks on dictionaries that we're interested in. + // Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context); + for (int i = 0; i < length; i++) + { + if (reader.PeekEntry(out name) == EntryType.EndOfArray) + { + reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected."); + break; + } + + bool exitNode = true; + + try + { + reader.EnterNode(out type); + TKey key = KeyReaderWriter.ReadValue(reader); + TValue val = ValueReaderWriter.ReadValue(reader); + + if (!KeyIsValueType && object.ReferenceEquals(key, null)) + { + reader.Context.Config.DebugContext.LogWarning("Dictionary key of type '" + typeof(TKey).FullName + "' was null upon deserialization. A key has gone missing."); + continue; + } + + value[key] = val; + } + catch (SerializationAbortException ex) + { + exitNode = false; + throw ex; + } + catch (Exception ex) + { + reader.Context.Config.DebugContext.LogException(ex); + } + finally + { + if (exitNode) + { + reader.ExitNode(); + } + } + + if (reader.IsInArrayNode == false) + { + reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump()); + break; + } + } + } + finally + { + reader.ExitArray(); + } + } + else + { + reader.SkipEntry(); + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref Dictionary<TKey, TValue> value, IDataWriter writer) + { + try + { + if (value.Comparer != null) + { + EqualityComparerSerializer.WriteValue("comparer", value.Comparer, writer); + } + + writer.BeginArrayNode(value.Count); + + foreach (var pair in value) + { + bool endNode = true; + + try + { + writer.BeginStructNode(null, null); + KeyReaderWriter.WriteValue("$k", pair.Key, writer); + ValueReaderWriter.WriteValue("$v", pair.Value, writer); + } + catch (SerializationAbortException ex) + { + endNode = false; + throw ex; + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + finally + { + if (endNode) + { + writer.EndNode(null); + } + } + } + } + finally + { + writer.EndArrayNode(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DictionaryFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DictionaryFormatter.cs.meta new file mode 100644 index 00000000..0af7c50a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DictionaryFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b80567603fe814a8b4341584f8c3b4a6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DoubleLookupDictionaryFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DoubleLookupDictionaryFormatter.cs new file mode 100644 index 00000000..4218ee7b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DoubleLookupDictionaryFormatter.cs @@ -0,0 +1,179 @@ +//----------------------------------------------------------------------- +// <copyright file="DoubleLookupDictionaryFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(DoubleLookupDictionaryFormatter<,,>))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using VRC.Udon.Serialization.OdinSerializer.Utilities; + using System; + using System.Collections.Generic; + + /// <summary> + /// Custom Odin serialization formatter for <see cref="DoubleLookupDictionary{TFirstKey, TSecondKey, TValue}"/>. + /// </summary> + /// <typeparam name="TPrimary">Type of primary key.</typeparam> + /// <typeparam name="TSecondary">Type of secondary key.</typeparam> + /// <typeparam name="TValue">Type of value.</typeparam> + public sealed class DoubleLookupDictionaryFormatter<TPrimary, TSecondary, TValue> : BaseFormatter<DoubleLookupDictionary<TPrimary, TSecondary, TValue>> + { + private static readonly Serializer<TPrimary> PrimaryReaderWriter = Serializer.Get<TPrimary>(); + private static readonly Serializer<Dictionary<TSecondary, TValue>> InnerReaderWriter = Serializer.Get<Dictionary<TSecondary, TValue>>(); + + static DoubleLookupDictionaryFormatter() + { + new DoubleLookupDictionaryFormatter<int, int, string>(); + } + + /// <summary> + /// Creates a new instance of <see cref="DoubleLookupDictionaryFormatter{TPrimary, TSecondary, TValue}"/>. + /// </summary> + public DoubleLookupDictionaryFormatter() + { + } + + /// <summary> + /// Returns null. + /// </summary> + protected override DoubleLookupDictionary<TPrimary, TSecondary, TValue> GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="!:T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref DoubleLookupDictionary<TPrimary, TSecondary, TValue> value, IDataWriter writer) + { + try + { + writer.BeginArrayNode(value.Count); + + bool endNode = true; + + foreach (var pair in value) + { + try + { + writer.BeginStructNode(null, null); + PrimaryReaderWriter.WriteValue("$k", pair.Key, writer); + InnerReaderWriter.WriteValue("$v", pair.Value, writer); + } + catch (SerializationAbortException ex) + { + endNode = false; + throw ex; + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + finally + { + if (endNode) + { + writer.EndNode(null); + } + } + } + } + finally + { + writer.EndArrayNode(); + } + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="!:T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="M:OdinSerializer.BaseFormatter`1.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref DoubleLookupDictionary<TPrimary, TSecondary, TValue> value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.StartOfArray) + { + try + { + long length; + reader.EnterArray(out length); + Type type; + value = new DoubleLookupDictionary<TPrimary, TSecondary, TValue>(); + + this.RegisterReferenceID(value, reader); + + for (int i = 0; i < length; i++) + { + if (reader.PeekEntry(out name) == EntryType.EndOfArray) + { + reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected."); + break; + } + + bool exitNode = true; + + try + { + reader.EnterNode(out type); + TPrimary key = PrimaryReaderWriter.ReadValue(reader); + Dictionary<TSecondary, TValue> inner = InnerReaderWriter.ReadValue(reader); + + value.Add(key, inner); + } + catch (SerializationAbortException ex) + { + exitNode = false; + throw ex; + } + catch (Exception ex) + { + reader.Context.Config.DebugContext.LogException(ex); + } + finally + { + if (exitNode) + { + reader.ExitNode(); + } + } + + if (reader.IsInArrayNode == false) + { + reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump()); + break; + } + } + } + finally + { + reader.ExitArray(); + } + } + else + { + reader.SkipEntry(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DoubleLookupDictionaryFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DoubleLookupDictionaryFormatter.cs.meta new file mode 100644 index 00000000..3e510606 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/DoubleLookupDictionaryFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5c21ee7e54dff531da57563e2f81fff5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EasyBaseFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EasyBaseFormatter.cs new file mode 100644 index 00000000..e7ccb02f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EasyBaseFormatter.cs @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------- +// <copyright file="EasyBaseFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Provides an easy way of implementing custom formatters. + /// </summary> + /// <typeparam name="T">The type which can be serialized and deserialized by the formatter.</typeparam> + public abstract class EasyBaseFormatter<T> : BaseFormatter<T> + { + /// <summary> + /// Reads through all entries in the current node one at a time, and calls <see cref="EasyBaseFormatter{T}.ReadDataEntry(ref T, string, EntryType, IDataReader, DeserializationContext)" /> for each entry. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected sealed override void DeserializeImplementation(ref T value, IDataReader reader) + { + int count = 0; + string name; + EntryType entry; + + while ((entry = reader.PeekEntry(out name)) != EntryType.EndOfNode && entry != EntryType.EndOfArray && entry != EntryType.EndOfStream) + { + this.ReadDataEntry(ref value, name, entry, reader); + + count++; + + if (count > 1000) + { + reader.Context.Config.DebugContext.LogError("Breaking out of infinite reading loop!"); + break; + } + } + } + + /// <summary> + /// Calls <see cref="EasyBaseFormatter{T}.WriteDataEntries(ref T, IDataWriter)" /> directly. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected sealed override void SerializeImplementation(ref T value, IDataWriter writer) + { + this.WriteDataEntries(ref value, writer); + } + + /// <summary> + /// Reads a data entry into the value denoted by the entry name. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="entryName">The name of the entry.</param> + /// <param name="entryType">The type of the entry.</param> + /// <param name="reader">The reader currently used for deserialization.</param> + protected abstract void ReadDataEntry(ref T value, string entryName, EntryType entryType, IDataReader reader); + + /// <summary> + /// Write the serialized values of a value of type <see cref="t" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer currently used for serialization.</param> + protected abstract void WriteDataEntries(ref T value, IDataWriter writer); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EasyBaseFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EasyBaseFormatter.cs.meta new file mode 100644 index 00000000..a49045ec --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EasyBaseFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54578488936f8484c97ba7c52bfb0563 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmittedFormatterAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmittedFormatterAttribute.cs new file mode 100644 index 00000000..b8da43b2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmittedFormatterAttribute.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// <copyright file="EmittedFormatterAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Indicates that this formatter type has been emitted. Never put this on a type! + /// </summary> + [AttributeUsage(AttributeTargets.Class)] + public class EmittedFormatterAttribute : Attribute + { + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmittedFormatterAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmittedFormatterAttribute.cs.meta new file mode 100644 index 00000000..9c928933 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmittedFormatterAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e226537cbfa910681132da3718f41c34 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmptyTypeFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmptyTypeFormatter.cs new file mode 100644 index 00000000..083f6d7d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmptyTypeFormatter.cs @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------- +// <copyright file="EmptyTypeFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// A formatter for empty types. It writes no data, and skips all data that is to be read, deserializing a "default" value. + /// </summary> + public class EmptyTypeFormatter<T> : EasyBaseFormatter<T> + { + /// <summary> + /// Skips the entry to read. + /// </summary> + protected override void ReadDataEntry(ref T value, string entryName, EntryType entryType, IDataReader reader) + { + // Just skip + reader.SkipEntry(); + } + + /// <summary> + /// Does nothing at all. + /// </summary> + protected override void WriteDataEntries(ref T value, IDataWriter writer) + { + // Do nothing + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmptyTypeFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmptyTypeFormatter.cs.meta new file mode 100644 index 00000000..01454c84 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/EmptyTypeFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 149c482b2ab9c601b8bc2ecc20bbd8d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/FormatterEmitter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/FormatterEmitter.cs new file mode 100644 index 00000000..6a97c768 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/FormatterEmitter.cs @@ -0,0 +1,602 @@ +//----------------------------------------------------------------------- +// <copyright file="FormatterEmitter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +#if (UNITY_EDITOR || UNITY_STANDALONE) && !ENABLE_IL2CPP && NET_4_6 +#define CAN_EMIT +#endif + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using VRC.Udon.Serialization.OdinSerializer.Utilities; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEngine; + +#if CAN_EMIT + + using System.Reflection.Emit; + +#endif + + /// <summary> + /// Utility class for emitting formatters using the <see cref="System.Reflection.Emit"/> namespace. + /// <para /> + /// NOTE: Some platforms do not support emitting. Check whether you can emit on the current platform using <see cref="EmitUtilities.CanEmit"/>. + /// </summary> + public static class FormatterEmitter + { + /// <summary> + /// The name of the pre-generated assembly that contains pre-emitted formatters for use on AOT platforms where emitting is not supported. Note that this assembly is not always present. + /// </summary> + public const string PRE_EMITTED_ASSEMBLY_NAME = "OdinSerializer.AOTGenerated"; + + /// <summary> + /// The name of the runtime-generated assembly that contains runtime-emitted formatters for use on non-AOT platforms where emitting is supported. Note that this assembly is not always present. + /// </summary> + public const string RUNTIME_EMITTED_ASSEMBLY_NAME = "OdinSerializer.RuntimeEmitted"; + + /// <summary> + /// Base type for all AOT-emitted formatters. + /// </summary> + [EmittedFormatter] + public abstract class AOTEmittedFormatter<T> : EasyBaseFormatter<T> + { + } + + /// <summary> + /// Shortcut class that makes it easier to emit empty AOT formatters. + /// </summary> + public abstract class EmptyAOTEmittedFormatter<T> : AOTEmittedFormatter<T> + { + /// <summary> + /// Skips the entry to read. + /// </summary> + protected override void ReadDataEntry(ref T value, string entryName, EntryType entryType, IDataReader reader) + { + reader.SkipEntry(); + } + + /// <summary> + /// Does nothing at all. + /// </summary> + protected override void WriteDataEntries(ref T value, IDataWriter writer) + { + } + } + +#if CAN_EMIT + + private static readonly object LOCK = new object(); + private static readonly DoubleLookupDictionary<ISerializationPolicy, Type, IFormatter> Formatters = new DoubleLookupDictionary<ISerializationPolicy, Type, IFormatter>(); + + private static AssemblyBuilder runtimeEmittedAssembly; + private static ModuleBuilder runtimeEmittedModule; + + public delegate void ReadDataEntryMethodDelegate<T>(ref T value, string entryName, EntryType entryType, IDataReader reader); + + public delegate void WriteDataEntriesMethodDelegate<T>(ref T value, IDataWriter writer); + + [EmittedFormatter] + public sealed class RuntimeEmittedFormatter<T> : EasyBaseFormatter<T> + { + public readonly ReadDataEntryMethodDelegate<T> Read; + public readonly WriteDataEntriesMethodDelegate<T> Write; + + public RuntimeEmittedFormatter(ReadDataEntryMethodDelegate<T> read, WriteDataEntriesMethodDelegate<T> write) + { + this.Read = read; + this.Write = write; + } + + protected override void ReadDataEntry(ref T value, string entryName, EntryType entryType, IDataReader reader) + { + this.Read(ref value, entryName, entryType, reader); + } + + protected override void WriteDataEntries(ref T value, IDataWriter writer) + { + this.Write(ref value, writer); + } + } + +#endif + + /// <summary> + /// Gets an emitted formatter for a given type. + /// <para /> + /// NOTE: Some platforms do not support emitting. On such platforms, this method logs an error and returns null. Check whether you can emit on the current platform using <see cref="EmitUtilities.CanEmit"/>. + /// </summary> + /// <param name="type">The type to emit a formatter for.</param> + /// <param name="policy">The serialization policy to use to determine which members the emitted formatter should serialize. If null, <see cref="SerializationPolicies.Strict"/> is used.</param> + /// <returns>The type of the emitted formatter.</returns> + /// <exception cref="System.ArgumentNullException">The type argument is null.</exception> + public static IFormatter GetEmittedFormatter(Type type, ISerializationPolicy policy) + { +#if !CAN_EMIT + Debug.LogError("Cannot use Reflection.Emit on the current platform. The FormatterEmitter class is currently disabled. Check whether emitting is currently possible with EmitUtilities.CanEmit."); + return null; +#else + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (policy == null) + { + policy = SerializationPolicies.Strict; + } + + IFormatter result = null; + + if (Formatters.TryGetInnerValue(policy, type, out result) == false) + { + lock (LOCK) + { + if (Formatters.TryGetInnerValue(policy, type, out result) == false) + { + EnsureRuntimeAssembly(); + + try + { + result = CreateGenericFormatter(type, runtimeEmittedModule, policy); + } + catch (Exception ex) + { + Debug.LogError("The following error occurred while emitting a formatter for the type " + type.Name); + Debug.LogException(ex); + } + + Formatters.AddInner(policy, type, result); + } + } + } + + return result; +#endif + } + +#if CAN_EMIT + + private static void EnsureRuntimeAssembly() + { + // We always hold the lock in this method + + if (runtimeEmittedAssembly == null) + { + var assemblyName = new AssemblyName(RUNTIME_EMITTED_ASSEMBLY_NAME); + + assemblyName.CultureInfo = System.Globalization.CultureInfo.InvariantCulture; + assemblyName.Flags = AssemblyNameFlags.None; + assemblyName.ProcessorArchitecture = ProcessorArchitecture.MSIL; + assemblyName.VersionCompatibility = System.Configuration.Assemblies.AssemblyVersionCompatibility.SameDomain; + + runtimeEmittedAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + } + + if (runtimeEmittedModule == null) + { + bool emitSymbolInfo; + +#if UNITY_EDITOR + emitSymbolInfo = true; +#else + // Builds cannot emit symbol info + emitSymbolInfo = false; +#endif + + runtimeEmittedModule = runtimeEmittedAssembly.DefineDynamicModule(RUNTIME_EMITTED_ASSEMBLY_NAME, emitSymbolInfo); + } + } + + /// <summary> + /// Emits a formatter for a given type into a given module builder, using a given serialization policy to determine which members to serialize. + /// </summary> + /// <param name="formattedType">Type to create a formatter for.</param> + /// <param name="moduleBuilder">The module builder to emit a formatter into.</param> + /// <param name="policy">The serialization policy to use for creating the formatter.</param> + /// <returns>The fully constructed, emitted formatter type.</returns> + public static Type EmitAOTFormatter(Type formattedType, ModuleBuilder moduleBuilder, ISerializationPolicy policy) + { + Dictionary<string, MemberInfo> serializableMembers = FormatterUtilities.GetSerializableMembersMap(formattedType, policy); + + string formatterName = moduleBuilder.Name + "." + formattedType.GetCompilableNiceFullName() + "__AOTFormatter"; + string formatterHelperName = moduleBuilder.Name + "." + formattedType.GetCompilableNiceFullName() + "__FormatterHelper"; + + if (serializableMembers.Count == 0) + { + return moduleBuilder.DefineType( + formatterName, + TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, + typeof(EmptyAOTEmittedFormatter<>).MakeGenericType(formattedType) + ).CreateType(); + } + + Dictionary<Type, MethodInfo> serializerReadMethods; + Dictionary<Type, MethodInfo> serializerWriteMethods; + Dictionary<Type, FieldBuilder> serializerFields; + FieldBuilder dictField; + Dictionary<MemberInfo, List<string>> memberNames; + + BuildHelperType( + moduleBuilder, + formatterHelperName, + formattedType, + serializableMembers, + out serializerReadMethods, + out serializerWriteMethods, + out serializerFields, + out dictField, + out memberNames + ); + + TypeBuilder formatterType = moduleBuilder.DefineType( + formatterName, + TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, + typeof(AOTEmittedFormatter<>).MakeGenericType(formattedType) + ); + + // Read + { + MethodInfo readBaseMethod = formatterType.BaseType.GetMethod("ReadDataEntry", Flags.InstanceAnyVisibility); + + MethodBuilder readMethod = formatterType.DefineMethod( + readBaseMethod.Name, + MethodAttributes.Family | MethodAttributes.Virtual, + readBaseMethod.ReturnType, + readBaseMethod.GetParameters().Select(n => n.ParameterType).ToArray() + ); + + readBaseMethod.GetParameters().ForEach(n => readMethod.DefineParameter(n.Position, n.Attributes, n.Name)); + EmitReadMethodContents(readMethod.GetILGenerator(), formattedType, dictField, serializerFields, memberNames, serializerReadMethods); + } + + // Write + { + MethodInfo writeBaseMethod = formatterType.BaseType.GetMethod("WriteDataEntries", Flags.InstanceAnyVisibility); + + MethodBuilder dynamicWriteMethod = formatterType.DefineMethod( + writeBaseMethod.Name, + MethodAttributes.Family | MethodAttributes.Virtual, + writeBaseMethod.ReturnType, + writeBaseMethod.GetParameters().Select(n => n.ParameterType).ToArray() + ); + + writeBaseMethod.GetParameters().ForEach(n => dynamicWriteMethod.DefineParameter(n.Position + 1, n.Attributes, n.Name)); + EmitWriteMethodContents(dynamicWriteMethod.GetILGenerator(), formattedType, serializerFields, memberNames, serializerWriteMethods); + } + + var result = formatterType.CreateType(); + + // Register the formatter on the assembly + ((AssemblyBuilder)moduleBuilder.Assembly).SetCustomAttribute(new CustomAttributeBuilder(typeof(RegisterFormatterAttribute).GetConstructor(new Type[] { typeof(Type), typeof(int) }), new object[] { formatterType, -1 })); + + return result; + } + + private static IFormatter CreateGenericFormatter(Type formattedType, ModuleBuilder moduleBuilder, ISerializationPolicy policy) + { + Dictionary<string, MemberInfo> serializableMembers = FormatterUtilities.GetSerializableMembersMap(formattedType, policy); + + if (serializableMembers.Count == 0) + { + return (IFormatter)Activator.CreateInstance(typeof(EmptyTypeFormatter<>).MakeGenericType(formattedType)); + } + + string helperTypeName = moduleBuilder.Name + "." + formattedType.GetCompilableNiceFullName() + "___" + formattedType.Assembly.GetName().Name + "___FormatterHelper___" + Guid.NewGuid().ToString(); + + Dictionary<Type, MethodInfo> serializerReadMethods; + Dictionary<Type, MethodInfo> serializerWriteMethods; + Dictionary<Type, FieldBuilder> serializerFields; + FieldBuilder dictField; + Dictionary<MemberInfo, List<string>> memberNames; + + BuildHelperType( + moduleBuilder, + helperTypeName, + formattedType, + serializableMembers, + out serializerReadMethods, + out serializerWriteMethods, + out serializerFields, + out dictField, + out memberNames + ); + + Type formatterType = typeof(RuntimeEmittedFormatter<>).MakeGenericType(formattedType); + Delegate del1, del2; + + // Read + { + Type readDelegateType = typeof(ReadDataEntryMethodDelegate<>).MakeGenericType(formattedType); + MethodInfo readDataEntryMethod = formatterType.GetMethod("ReadDataEntry", Flags.InstanceAnyVisibility); + DynamicMethod dynamicReadMethod = new DynamicMethod("Dynamic_" + formattedType.GetCompilableNiceFullName(), null, readDataEntryMethod.GetParameters().Select(n => n.ParameterType).ToArray(), true); + readDataEntryMethod.GetParameters().ForEach(n => dynamicReadMethod.DefineParameter(n.Position, n.Attributes, n.Name)); + EmitReadMethodContents(dynamicReadMethod.GetILGenerator(), formattedType, dictField, serializerFields, memberNames, serializerReadMethods); + del1 = dynamicReadMethod.CreateDelegate(readDelegateType); + } + + // Write + { + Type writeDelegateType = typeof(WriteDataEntriesMethodDelegate<>).MakeGenericType(formattedType); + MethodInfo writeDataEntriesMethod = formatterType.GetMethod("WriteDataEntries", Flags.InstanceAnyVisibility); + DynamicMethod dynamicWriteMethod = new DynamicMethod("Dynamic_Write_" + formattedType.GetCompilableNiceFullName(), null, writeDataEntriesMethod.GetParameters().Select(n => n.ParameterType).ToArray(), true); + writeDataEntriesMethod.GetParameters().ForEach(n => dynamicWriteMethod.DefineParameter(n.Position + 1, n.Attributes, n.Name)); + EmitWriteMethodContents(dynamicWriteMethod.GetILGenerator(), formattedType, serializerFields, memberNames, serializerWriteMethods); + del2 = dynamicWriteMethod.CreateDelegate(writeDelegateType); + } + + return (IFormatter)Activator.CreateInstance(formatterType, del1, del2); + } + + private static Type BuildHelperType( + ModuleBuilder moduleBuilder, + string helperTypeName, + Type formattedType, + Dictionary<string, MemberInfo> serializableMembers, + out Dictionary<Type, MethodInfo> serializerReadMethods, + out Dictionary<Type, MethodInfo> serializerWriteMethods, + out Dictionary<Type, FieldBuilder> serializerFields, + out FieldBuilder dictField, + out Dictionary<MemberInfo, List<string>> memberNames) + { + TypeBuilder helperTypeBuilder = moduleBuilder.DefineType(helperTypeName, TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class); + + memberNames = new Dictionary<MemberInfo, List<string>>(); + + foreach (var entry in serializableMembers) + { + List<string> list; + + if (memberNames.TryGetValue(entry.Value, out list) == false) + { + list = new List<string>(); + memberNames.Add(entry.Value, list); + } + + list.Add(entry.Key); + } + + dictField = helperTypeBuilder.DefineField("SwitchLookup", typeof(Dictionary<string, int>), FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly); + + List<Type> neededSerializers = memberNames.Keys.Select(n => FormatterUtilities.GetContainedType(n)).Distinct().ToList(); + + serializerReadMethods = new Dictionary<Type, MethodInfo>(neededSerializers.Count); + serializerWriteMethods = new Dictionary<Type, MethodInfo>(neededSerializers.Count); + serializerFields = new Dictionary<Type, FieldBuilder>(neededSerializers.Count); + + foreach (var t in neededSerializers) + { + string name = t.GetCompilableNiceFullName() + "__Serializer"; + int counter = 1; + + while (serializerFields.Values.Any(n => n.Name == name)) + { + counter++; + name = t.GetCompilableNiceFullName() + "__Serializer" + counter; + } + + Type serializerType = typeof(Serializer<>).MakeGenericType(t); + + serializerReadMethods.Add(t, serializerType.GetMethod("ReadValue", Flags.InstancePublicDeclaredOnly)); + serializerWriteMethods.Add(t, serializerType.GetMethod("WriteValue", Flags.InstancePublicDeclaredOnly, null, new[] { typeof(string), t, typeof(IDataWriter) }, null)); + serializerFields.Add(t, helperTypeBuilder.DefineField(name, serializerType, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly)); + } + + //FieldBuilder readMethodFieldBuilder = helperTypeBuilder.DefineField("ReadMethod", readDelegateType, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly); + //FieldBuilder writeMethodFieldBuilder = helperTypeBuilder.DefineField("WriteMethod", writeDelegateType, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly); + + // We generate a static constructor for our formatter helper type that initializes our switch lookup dictionary and our needed Serializer references + { + var addMethod = typeof(Dictionary<string, int>).GetMethod("Add", Flags.InstancePublic); + var dictionaryConstructor = typeof(Dictionary<string, int>).GetConstructor(Type.EmptyTypes); + var serializerGetMethod = typeof(Serializer).GetMethod("Get", Flags.StaticPublic, null, new[] { typeof(Type) }, null); + var typeOfMethod = typeof(Type).GetMethod("GetTypeFromHandle", Flags.StaticPublic, null, new Type[] { typeof(RuntimeTypeHandle) }, null); + + ConstructorBuilder staticConstructor = helperTypeBuilder.DefineTypeInitializer(); + ILGenerator gen = staticConstructor.GetILGenerator(); + + gen.Emit(OpCodes.Newobj, dictionaryConstructor); // Create new dictionary + + int count = 0; + + foreach (var entry in memberNames) + { + foreach (var name in entry.Value) + { + gen.Emit(OpCodes.Dup); // Load duplicate dictionary value + gen.Emit(OpCodes.Ldstr, name); // Load entry name + gen.Emit(OpCodes.Ldc_I4, count); // Load entry index + gen.Emit(OpCodes.Call, addMethod); // Call dictionary add + } + + count++; + } + + gen.Emit(OpCodes.Stsfld, dictField); // Set static dictionary field to dictionary value + + foreach (var entry in serializerFields) + { + gen.Emit(OpCodes.Ldtoken, entry.Key); // Load type token + gen.Emit(OpCodes.Call, typeOfMethod); // Call typeof method (this pushes a type value onto the stack) + gen.Emit(OpCodes.Call, serializerGetMethod); // Call Serializer.Get(Type type) method + gen.Emit(OpCodes.Stsfld, entry.Value); // Set static serializer field to result of get method + } + + gen.Emit(OpCodes.Ret); // Return + } + + // Now we need to actually create the serializer container type so we can generate the dynamic methods below without getting TypeLoadExceptions up the wazoo + return helperTypeBuilder.CreateType(); + } + + private static void EmitReadMethodContents( + ILGenerator gen, + Type formattedType, + FieldInfo dictField, + Dictionary<Type, FieldBuilder> serializerFields, + Dictionary<MemberInfo, List<string>> memberNames, + Dictionary<Type, MethodInfo> serializerReadMethods) + { + MethodInfo skipMethod = typeof(IDataReader).GetMethod("SkipEntry", Flags.InstancePublic); + MethodInfo tryGetValueMethod = typeof(Dictionary<string, int>).GetMethod("TryGetValue", Flags.InstancePublic); + + //methodBuilder.DefineParameter(5, ParameterAttributes.None, "switchLookup"); + + LocalBuilder lookupResult = gen.DeclareLocal(typeof(int)); + + Label defaultLabel = gen.DefineLabel(); + Label switchLabel = gen.DefineLabel(); + Label endLabel = gen.DefineLabel(); + Label[] switchLabels = memberNames.Select(n => gen.DefineLabel()).ToArray(); + + gen.Emit(OpCodes.Ldarg_1); // Load entryName string + gen.Emit(OpCodes.Ldnull); // Load null + gen.Emit(OpCodes.Ceq); // Equality check + gen.Emit(OpCodes.Brtrue, defaultLabel); // If entryName is null, go to default case + + //gen.Emit(OpCodes.Ldarg, (short)4); // Load lookup dictionary argument (OLD CODE) + + gen.Emit(OpCodes.Ldsfld, dictField); // Load lookup dictionary from static field on helper type + gen.Emit(OpCodes.Ldarg_1); // Load entryName string + gen.Emit(OpCodes.Ldloca, (short)lookupResult.LocalIndex); // Load address of lookupResult + gen.Emit(OpCodes.Callvirt, tryGetValueMethod); // Call TryGetValue on the dictionary + + gen.Emit(OpCodes.Brtrue, switchLabel); // If TryGetValue returned true, go to the switch case + gen.Emit(OpCodes.Br, defaultLabel); // Else, go to the default case + + gen.MarkLabel(switchLabel); // Switch starts here + gen.Emit(OpCodes.Ldloc, lookupResult); // Load lookupResult + gen.Emit(OpCodes.Switch, switchLabels); // Perform switch on switchLabels + + int count = 0; + + foreach (var member in memberNames.Keys) + { + var memberType = FormatterUtilities.GetContainedType(member); + + var propInfo = member as PropertyInfo; + var fieldInfo = member as FieldInfo; + + gen.MarkLabel(switchLabels[count]); // Switch case for [count] starts here + + // Now we load the instance that we have to set the value on + gen.Emit(OpCodes.Ldarg_0); // Load value reference + + if (formattedType.IsValueType == false) + { + gen.Emit(OpCodes.Ldind_Ref); // Indirectly load value of reference + } + + // Now we deserialize the value itself + gen.Emit(OpCodes.Ldsfld, serializerFields[memberType]); // Load serializer from serializer container type + gen.Emit(OpCodes.Ldarg, (short)3); // Load reader argument + gen.Emit(OpCodes.Callvirt, serializerReadMethods[memberType]); // Call Serializer.ReadValue(IDataReader reader) + + // The stack now contains the formatted instance and the deserialized value to set the member to + // Now we set the value + if (fieldInfo != null) + { + gen.Emit(OpCodes.Stfld, fieldInfo.DeAliasField()); // Set field + } + else if (propInfo != null) + { + gen.Emit(OpCodes.Callvirt, propInfo.DeAliasProperty().GetSetMethod(true)); // Call property setter + } + else + { + throw new NotImplementedException(); + } + + gen.Emit(OpCodes.Br, endLabel); // Jump to end of method + + count++; + } + + gen.MarkLabel(defaultLabel); // Default case starts here + gen.Emit(OpCodes.Ldarg, (short)3); // Load reader argument + gen.Emit(OpCodes.Callvirt, skipMethod); // Call IDataReader.SkipEntry + + gen.MarkLabel(endLabel); // Method end starts here + gen.Emit(OpCodes.Ret); // Return method + } + + private static void EmitWriteMethodContents( + ILGenerator gen, + Type formattedType, + Dictionary<Type, FieldBuilder> serializerFields, + Dictionary<MemberInfo, List<string>> memberNames, + Dictionary<Type, MethodInfo> serializerWriteMethods) + { + foreach (var member in memberNames.Keys) + { + var memberType = FormatterUtilities.GetContainedType(member); + + gen.Emit(OpCodes.Ldsfld, serializerFields[memberType]); // Load serializer instance for type + gen.Emit(OpCodes.Ldstr, member.Name); // Load member name string + + // Now we load the value of the actual member + if (member is FieldInfo) + { + var fieldInfo = member as FieldInfo; + + if (formattedType.IsValueType) + { + gen.Emit(OpCodes.Ldarg_0); // Load value argument + gen.Emit(OpCodes.Ldfld, fieldInfo.DeAliasField()); // Load value of field + } + else + { + gen.Emit(OpCodes.Ldarg_0); // Load value argument reference + gen.Emit(OpCodes.Ldind_Ref); // Indirectly load value of reference + gen.Emit(OpCodes.Ldfld, fieldInfo.DeAliasField()); // Load value of field + } + } + else if (member is PropertyInfo) + { + var propInfo = member as PropertyInfo; + + if (formattedType.IsValueType) + { + gen.Emit(OpCodes.Ldarg_0); // Load value argument + gen.Emit(OpCodes.Call, propInfo.DeAliasProperty().GetGetMethod(true)); // Call property getter + } + else + { + gen.Emit(OpCodes.Ldarg_0); // Load value argument reference + gen.Emit(OpCodes.Ldind_Ref); // Indirectly load value of reference + gen.Emit(OpCodes.Callvirt, propInfo.DeAliasProperty().GetGetMethod(true)); // Call property getter + } + } + else + { + throw new NotImplementedException(); + } + + gen.Emit(OpCodes.Ldarg_1); // Load writer argument + gen.Emit(OpCodes.Callvirt, serializerWriteMethods[memberType]); // Call Serializer.WriteValue(string name, T value, IDataWriter writer) + } + + gen.Emit(OpCodes.Ret); // Return method + } + +#endif + + } +} diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/FormatterEmitter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/FormatterEmitter.cs.meta new file mode 100644 index 00000000..5f7209c0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/FormatterEmitter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7da6bf97199e0bb743f7639c17112ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/GenericCollectionFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/GenericCollectionFormatter.cs new file mode 100644 index 00000000..f4682421 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/GenericCollectionFormatter.cs @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------- +// <copyright file="GenericCollectionFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using VRC.Udon.Serialization.OdinSerializer.Utilities; + using System; + using System.Collections.Generic; + + /// <summary> + /// Utility class for the <see cref="GenericCollectionFormatter{TCollection, TElement}"/> class. + /// </summary> + public static class GenericCollectionFormatter + { + /// <summary> + /// Determines whether the specified type can be formatted by a <see cref="GenericCollectionFormatter{TCollection, TElement}"/>. + /// <para /> + /// The following criteria are checked: type implements <see cref="ICollection{T}"/>, type is not abstract, type is not a generic type definition, type is not an interface, type has a public parameterless constructor. + /// </summary> + /// <param name="type">The collection type to check.</param> + /// <param name="elementType">The element type of the collection.</param> + /// <returns><c>true</c> if the type can be formatted by a <see cref="GenericCollectionFormatter{TCollection, TElement}"/>, otherwise <c>false</c></returns> + /// <exception cref="System.ArgumentNullException">The type argument is null.</exception> + public static bool CanFormat(Type type, out Type elementType) + { + if (type == null) + { + throw new ArgumentNullException(); + } + + if (type.IsAbstract || type.IsGenericTypeDefinition || type.IsInterface || type.GetConstructor(Type.EmptyTypes) == null || type.ImplementsOpenGenericInterface(typeof(ICollection<>)) == false) + { + elementType = null; + return false; + } + + elementType = type.GetArgumentsOfInheritedOpenGenericInterface(typeof(ICollection<>))[0]; + return true; + } + } + + /// <summary> + /// Formatter for all eligible types that implement the interface <see cref="ICollection{T}"/>, and which have no other formatters specified. + /// <para /> + /// Eligibility for formatting by this class is determined by the <see cref="GenericCollectionFormatter.CanFormat(Type, out Type)"/> method. + /// </summary> + /// <typeparam name="TCollection">The type of the collection.</typeparam> + /// <typeparam name="TElement">The type of the element.</typeparam> + public sealed class GenericCollectionFormatter<TCollection, TElement> : BaseFormatter<TCollection> where TCollection : ICollection<TElement>, new() + { + private static Serializer<TElement> valueReaderWriter = Serializer.Get<TElement>(); + + static GenericCollectionFormatter() + { + Type e; + + if (GenericCollectionFormatter.CanFormat(typeof(TCollection), out e) == false) + { + throw new ArgumentException("Cannot treat the type " + typeof(TCollection).Name + " as a generic collection."); + } + + if (e != typeof(TElement)) + { + throw new ArgumentException("Type " + typeof(TElement).Name + " is not the element type of the generic collection type " + typeof(TCollection).Name + "."); + } + + // This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor + // which it otherwise seems prone to do, regardless of what might be defined in any link.xml file. + + new GenericCollectionFormatter<List<int>, int>(); + } + + /// <summary> + /// Creates a new instance of <see cref="GenericCollectionFormatter{TCollection, TElement}"/>. + /// </summary> + public GenericCollectionFormatter() + { + } + + /// <summary> + /// Gets a new object of type <see cref="T" />. + /// </summary> + /// <returns> + /// A new object of type <see cref="T" />. + /// </returns> + protected override TCollection GetUninitializedObject() + { + return new TCollection(); + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref TCollection value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.StartOfArray) + { + try + { + long length; + reader.EnterArray(out length); + + for (int i = 0; i < length; i++) + { + if (reader.PeekEntry(out name) == EntryType.EndOfArray) + { + reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected."); + break; + } + + try + { + value.Add(valueReaderWriter.ReadValue(reader)); + } + catch (Exception ex) + { + reader.Context.Config.DebugContext.LogException(ex); + } + + if (reader.IsInArrayNode == false) + { + reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump()); + break; + } + } + } + catch (Exception ex) + { + reader.Context.Config.DebugContext.LogException(ex); + } + finally + { + reader.ExitArray(); + } + } + else + { + reader.SkipEntry(); + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref TCollection value, IDataWriter writer) + { + try + { + writer.BeginArrayNode(value.Count); + + foreach (var element in value) + { + valueReaderWriter.WriteValue(element, writer); + } + } + finally + { + writer.EndArrayNode(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/GenericCollectionFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/GenericCollectionFormatter.cs.meta new file mode 100644 index 00000000..281b756c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/GenericCollectionFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 06ccb8250c692f2695d28bfd6bcd4273 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/HashSetFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/HashSetFormatter.cs new file mode 100644 index 00000000..60b2ffd8 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/HashSetFormatter.cs @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------- +// <copyright file="ListFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(HashSetFormatter<>))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Custom generic formatter for the generic type definition <see cref="HashSet{T}"/>. + /// </summary> + /// <typeparam name="T">The element type of the formatted list.</typeparam> + /// <seealso cref="BaseFormatter{System.Collections.Generic.HashSet{T}}" /> + public class HashSetFormatter<T> : BaseFormatter<HashSet<T>> + { + private static readonly Serializer<T> TSerializer = Serializer.Get<T>(); + + static HashSetFormatter() + { + // This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor + // which it otherwise seems prone to do, regardless of what might be defined in any link.xml file. + + new HashSetFormatter<int>(); + } + + public HashSetFormatter() + { + } + + /// <summary> + /// Returns null. + /// </summary> + /// <returns> + /// A null value. + /// </returns> + protected override HashSet<T> GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref HashSet<T> value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.StartOfArray) + { + try + { + long length; + reader.EnterArray(out length); + value = new HashSet<T>(); + + // We must remember to register the hashset reference ourselves, since we return null in GetUninitializedObject + this.RegisterReferenceID(value, reader); + + // There aren't any relevant OnDeserializing callbacks on hash sets. + // Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context); + for (int i = 0; i < length; i++) + { + if (reader.PeekEntry(out name) == EntryType.EndOfArray) + { + reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected."); + break; + } + + value.Add(TSerializer.ReadValue(reader)); + + if (reader.IsInArrayNode == false) + { + // Something has gone wrong + reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump()); + break; + } + } + } + finally + { + reader.ExitArray(); + } + } + else + { + reader.SkipEntry(); + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref HashSet<T> value, IDataWriter writer) + { + try + { + writer.BeginArrayNode(value.Count); + + foreach (T item in value) + { + try + { + TSerializer.WriteValue(item, writer); + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + } + } + finally + { + writer.EndArrayNode(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/HashSetFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/HashSetFormatter.cs.meta new file mode 100644 index 00000000..2a27f024 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/HashSetFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f1eaa1b43658215b6d81013928eac19e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/IFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/IFormatter.cs new file mode 100644 index 00000000..898d7d46 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/IFormatter.cs @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------- +// <copyright file="IFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Serializes and deserializes a given type. + /// <para /> + /// NOTE that if you are implementing a custom formatter and registering it using the <see cref="CustomFormatterAttribute"/>, it is not enough to implement <see cref="IFormatter"/> - you have to implement <see cref="IFormatter{T}"/>. + /// </summary> + public interface IFormatter + { + /// <summary> + /// Gets the type that the formatter can serialize. + /// </summary> + /// <value> + /// The type that the formatter can serialize. + /// </value> + Type SerializedType { get; } + + /// <summary> + /// Serializes a value using a specified <see cref="IDataWriter" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to use.</param> + void Serialize(object value, IDataWriter writer); + + /// <summary> + /// Deserializes a value using a specified <see cref="IDataReader" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The deserialized value. + /// </returns> + object Deserialize(IDataReader reader); + } + + /// <summary> + /// Serializes and deserializes a given type T. + /// </summary> + /// <typeparam name="T">The type which can be serialized and deserialized by the formatter.</typeparam> + public interface IFormatter<T> : IFormatter + { + /// <summary> + /// Serializes a value of type <see cref="T" /> using a specified <see cref="IDataWriter" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to use.</param> + void Serialize(T value, IDataWriter writer); + + /// <summary> + /// Deserializes a value of type <see cref="T" /> using a specified <see cref="IDataReader" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The deserialized value. + /// </returns> + new T Deserialize(IDataReader reader); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/IFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/IFormatter.cs.meta new file mode 100644 index 00000000..28faf8ec --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/IFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0fcb040f1c493dc2a5224e446be8b3c9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/KeyValuePairFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/KeyValuePairFormatter.cs new file mode 100644 index 00000000..6ec098c0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/KeyValuePairFormatter.cs @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------- +// <copyright file="KeyValuePairFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(KeyValuePairFormatter<,>))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System.Collections.Generic; + + /// <summary> + /// Custom generic formatter for the generic type definition <see cref="KeyValuePair{TKey, TValue}"/>. + /// </summary> + /// <typeparam name="TKey">The type of the key.</typeparam> + /// <typeparam name="TValue">The type of the value.</typeparam> + /// <seealso cref="BaseFormatter{System.Collections.Generic.KeyValuePair{TKey, TValue}}" /> + public sealed class KeyValuePairFormatter<TKey, TValue> : BaseFormatter<KeyValuePair<TKey, TValue>> + { + private static readonly Serializer<TKey> KeySerializer = Serializer.Get<TKey>(); + private static readonly Serializer<TValue> ValueSerializer = Serializer.Get<TValue>(); + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref KeyValuePair<TKey, TValue> value, IDataWriter writer) + { + KeySerializer.WriteValue(value.Key, writer); + ValueSerializer.WriteValue(value.Value, writer); + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + + protected override void DeserializeImplementation(ref KeyValuePair<TKey, TValue> value, IDataReader reader) + { + value = new KeyValuePair<TKey, TValue>( + KeySerializer.ReadValue(reader), + ValueSerializer.ReadValue(reader) + ); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/KeyValuePairFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/KeyValuePairFormatter.cs.meta new file mode 100644 index 00000000..b7ca90b2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/KeyValuePairFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5cae1c5d1116a090d70b6d0289061d21 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ListFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ListFormatter.cs new file mode 100644 index 00000000..eacfdb4a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ListFormatter.cs @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------- +// <copyright file="ListFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(ListFormatter<>))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Custom generic formatter for the generic type definition <see cref="List{T}"/>. + /// </summary> + /// <typeparam name="T">The element type of the formatted list.</typeparam> + /// <seealso cref="BaseFormatter{System.Collections.Generic.List{T}}" /> + public class ListFormatter<T> : BaseFormatter<List<T>> + { + private static readonly Serializer<T> TSerializer = Serializer.Get<T>(); + + static ListFormatter() + { + // This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor + // which it otherwise seems prone to do, regardless of what might be defined in any link.xml file. + + new ListFormatter<int>(); + } + + public ListFormatter() + { + } + + /// <summary> + /// Returns null. + /// </summary> + /// <returns> + /// A null value. + /// </returns> + protected override List<T> GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref List<T> value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.StartOfArray) + { + try + { + long length; + reader.EnterArray(out length); + value = new List<T>((int)length); + + // We must remember to register the list reference ourselves, since we return null in GetUninitializedObject + this.RegisterReferenceID(value, reader); + + // There aren't any OnDeserializing callbacks on lists. + // Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context); + for (int i = 0; i < length; i++) + { + if (reader.PeekEntry(out name) == EntryType.EndOfArray) + { + reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected."); + break; + } + + value.Add(TSerializer.ReadValue(reader)); + + if (reader.IsInArrayNode == false) + { + // Something has gone wrong + reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump()); + break; + } + } + } + finally + { + reader.ExitArray(); + } + } + else + { + reader.SkipEntry(); + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref List<T> value, IDataWriter writer) + { + try + { + writer.BeginArrayNode(value.Count); + + for (int i = 0; i < value.Count; i++) + { + try + { + TSerializer.WriteValue(value[i], writer); + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + } + } + finally + { + writer.EndArrayNode(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ListFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ListFormatter.cs.meta new file mode 100644 index 00000000..a4ddce06 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ListFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba4ee6777a44f6e9a8e2e0a222c0f7db +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MethodInfoFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MethodInfoFormatter.cs new file mode 100644 index 00000000..1ac5210e --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MethodInfoFormatter.cs @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------- +// <copyright file="MethodInfoFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(MethodInfoFormatter<>))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Linq; + using System.Reflection; + using Utilities; + + /// <summary> + /// Custom formatter for MethodInfo, since Unity Mono's MethodInfo ISerializable implementation will often crash if the method no longer exists upon deserialization. + /// </summary> + /// <seealso cref="BaseFormatter{T}" /> + public sealed class MethodInfoFormatter<T> : BaseFormatter<T> + where T : MethodInfo + { + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="!:T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="M:OdinSerializer.BaseFormatter`1.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref T value, IDataReader reader) + { + reader.Context.Config.DebugContext.LogWarning("MethodInfo deserialization has been removed for security."); + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="!:T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref T value, IDataWriter writer) + { + writer.Context.Config.DebugContext.LogWarning("MethodInfo serialization has been removed for security."); + } + + /// <summary> + /// Get an uninitialized object of type <see cref="!:T" />. WARNING: If you override this and return null, the object's ID will not be automatically registered and its OnDeserializing callbacks will not be automatically called, before deserialization begins. + /// You will have to call <see cref="M:OdinSerializer.BaseFormatter`1.RegisterReferenceID(`0,OdinSerializer.IDataReader)" /> and <see cref="M:OdinSerializer.BaseFormatter`1.InvokeOnDeserializingCallbacks(`0,OdinSerializer.DeserializationContext)" /> immediately after creating the object yourself during deserialization. + /// </summary> + /// <returns> + /// An uninitialized object of type <see cref="!:T" />. + /// </returns> + protected override T GetUninitializedObject() + { + return null; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MethodInfoFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MethodInfoFormatter.cs.meta new file mode 100644 index 00000000..abce144e --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MethodInfoFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 21078ce134ce87231526dee77088e7ab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MinimalBaseFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MinimalBaseFormatter.cs new file mode 100644 index 00000000..b1a699f7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MinimalBaseFormatter.cs @@ -0,0 +1,159 @@ +//----------------------------------------------------------------------- +// <copyright file="MinimalBaseFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Runtime.Serialization; + + /// <summary> + /// Minimal baseline formatter. Doesn't come with all the bells and whistles of any of the other BaseFormatter classes. + /// Common serialization conventions aren't automatically supported, and common deserialization callbacks are not automatically invoked. + /// </summary> + /// <typeparam name="T">The type which can be serialized and deserialized by the formatter.</typeparam> + public abstract class MinimalBaseFormatter<T> : IFormatter<T> + { + /// <summary> + /// Whether the serialized value is a value type. + /// </summary> + protected static readonly bool IsValueType = typeof(T).IsValueType; + + /// <summary> + /// Gets the type that the formatter can serialize. + /// </summary> + /// <value> + /// The type that the formatter can serialize. + /// </value> + public Type SerializedType { get { return typeof(T); } } + + /// <summary> + /// Deserializes a value of type <see cref="!:T" /> using a specified <see cref="T:OdinSerializer.IDataReader" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public T Deserialize(IDataReader reader) + { + T result = this.GetUninitializedObject(); + + // We allow the above method to return null (for reference types) because of special cases like arrays, + // where the size of the array cannot be known yet, and thus we cannot create an object instance at this time. + // + // Therefore, those who override GetUninitializedObject and return null must call RegisterReferenceID manually. + if (IsValueType == false && object.ReferenceEquals(result, null) == false) + { + this.RegisterReferenceID(result, reader); + } + + this.Read(ref result, reader); + return result; + } + + /// <summary> + /// Serializes a value of type <see cref="!:T" /> using a specified <see cref="T:OdinSerializer.IDataWriter" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to use.</param> + public void Serialize(T value, IDataWriter writer) + { + this.Write(ref value, writer); + } + + /// <summary> + /// Serializes a value using a specified <see cref="IDataWriter" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to use.</param> + void IFormatter.Serialize(object value, IDataWriter writer) + { + if (value is T) + { + this.Serialize((T)value, writer); + } + } + + /// <summary> + /// Deserializes a value using a specified <see cref="IDataReader" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The deserialized value. + /// </returns> + object IFormatter.Deserialize(IDataReader reader) + { + return this.Deserialize(reader); + } + + /// <summary> + /// Get an uninitialized object of type <see cref="T"/>. WARNING: If you override this and return null, the object's ID will not be automatically registered. + /// You will have to call <see cref="MinimalBaseFormatter{T}{T}.RegisterReferenceID(T, IDataReader, DeserializationContext)"/> immediately after creating the object yourself during deserialization. + /// </summary> + /// <returns>An uninitialized object of type <see cref="T"/>.</returns> + protected virtual T GetUninitializedObject() + { + if (IsValueType) + { + return default(T); + } + else + { + return (T)FormatterServices.GetUninitializedObject(typeof(T)); + } + } + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected abstract void Read(ref T value, IDataReader reader); + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected abstract void Write(ref T value, IDataWriter writer); + + /// <summary> + /// Registers the given object reference in the deserialization context. + /// <para /> + /// NOTE that this method only does anything if <see cref="T"/> is not a value type. + /// </summary> + /// <param name="value">The value to register.</param> + /// <param name="reader">The reader which is currently being used.</param> + protected void RegisterReferenceID(T value, IDataReader reader) + { + if (!IsValueType) + { + // Get ID and register object reference + int id = reader.CurrentNodeId; + + if (id < 0) + { + reader.Context.Config.DebugContext.LogWarning("Reference type node is missing id upon deserialization. Some references may be broken. This tends to happen if a value type has changed to a reference type (IE, struct to class) since serialization took place."); + } + else + { + reader.Context.RegisterInternalReference(id, value); + } + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MinimalBaseFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MinimalBaseFormatter.cs.meta new file mode 100644 index 00000000..9faa40c9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MinimalBaseFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae604bc0ef4ef9938100804f05decb21 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MultiDimensionalArrayFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MultiDimensionalArrayFormatter.cs new file mode 100644 index 00000000..26e623a1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MultiDimensionalArrayFormatter.cs @@ -0,0 +1,283 @@ +//----------------------------------------------------------------------- +// <copyright file="MultiDimensionalArrayFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using System.Globalization; + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Text; + + /// <summary> + /// Formatter for all arrays with more than one dimension. + /// </summary> + /// <typeparam name="TArray">The type of the formatted array.</typeparam> + /// <typeparam name="TElement">The element type of the formatted array.</typeparam> + /// <seealso cref="BaseFormatter{TArray}" /> + public sealed class MultiDimensionalArrayFormatter<TArray, TElement> : BaseFormatter<TArray> where TArray : class + { + private const string RANKS_NAME = "ranks"; + private const char RANKS_SEPARATOR = '|'; + + private static readonly int ArrayRank; + private static readonly Serializer<TElement> ValueReaderWriter = Serializer.Get<TElement>(); + + static MultiDimensionalArrayFormatter() + { + if (typeof(TArray).IsArray == false) + { + throw new ArgumentException("Type " + typeof(TArray).Name + " is not an array."); + } + + if (typeof(TArray).GetElementType() != typeof(TElement)) + { + throw new ArgumentException("Array of type " + typeof(TArray).Name + " does not have the required element type of " + typeof(TElement).Name + "."); + } + + ArrayRank = typeof(TArray).GetArrayRank(); + + if (ArrayRank <= 1) + { + throw new ArgumentException("Array of type " + typeof(TArray).Name + " only has one rank."); + } + } + + /// <summary> + /// Returns null. + /// </summary> + /// <returns> + /// A null value. + /// </returns> + protected override TArray GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref TArray value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.StartOfArray) + { + long length; + reader.EnterArray(out length); + + entry = reader.PeekEntry(out name); + + if (entry != EntryType.String || name != RANKS_NAME) + { + value = default(TArray); + reader.SkipEntry(); + return; + } + + string lengthStr; + reader.ReadString(out lengthStr); + + string[] lengthsStrs = lengthStr.Split(RANKS_SEPARATOR); + + if (lengthsStrs.Length != ArrayRank) + { + value = default(TArray); + reader.SkipEntry(); + return; + } + + int[] lengths = new int[lengthsStrs.Length]; + + for (int i = 0; i < lengthsStrs.Length; i++) + { + int rankVal; + if (int.TryParse(lengthsStrs[i], out rankVal)) + { + lengths[i] = rankVal; + } + else + { + value = default(TArray); + reader.SkipEntry(); + return; + } + } + + long rankTotal = lengths[0]; + + for (int i = 1; i < lengths.Length; i++) + { + rankTotal *= lengths[i]; + } + + if (rankTotal != length) + { + value = default(TArray); + reader.SkipEntry(); + return; + } + + value = (TArray)(object)Array.CreateInstance(typeof(TElement), lengths); + + // We must remember to register the array reference ourselves, since we return null in GetUninitializedObject + this.RegisterReferenceID(value, reader); + + // There aren't any OnDeserializing callbacks on arrays. + // Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context); + int elements = 0; + + try + { + this.IterateArrayWrite( + (Array)(object)value, + () => + { + if (reader.PeekEntry(out name) == EntryType.EndOfArray) + { + reader.Context.Config.DebugContext.LogError("Reached end of array after " + elements + " elements, when " + length + " elements were expected."); + throw new InvalidOperationException(); + } + + var v = ValueReaderWriter.ReadValue(reader); + + if (reader.IsInArrayNode == false) + { + reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump()); + throw new InvalidOperationException(); + } + + elements++; + return v; + }); + } + catch (InvalidOperationException) + { + } + catch (Exception ex) + { + reader.Context.Config.DebugContext.LogException(ex); + } + + reader.ExitArray(); + } + else + { + value = default(TArray); + reader.SkipEntry(); + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref TArray value, IDataWriter writer) + { + var array = value as Array; + + try + { + writer.BeginArrayNode(array.LongLength); + + int[] lengths = new int[ArrayRank]; + + for (int i = 0; i < ArrayRank; i++) + { + lengths[i] = array.GetLength(i); + } + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < ArrayRank; i++) + { + if (i > 0) + { + sb.Append(RANKS_SEPARATOR); + } + + sb.Append(lengths[i].ToString(CultureInfo.InvariantCulture)); + } + + string lengthStr = sb.ToString(); + + writer.WriteString(RANKS_NAME, lengthStr); + + this.IterateArrayRead( + (Array)(object)value, + (v) => + { + ValueReaderWriter.WriteValue(v, writer); + }); + } + finally + { + writer.EndArrayNode(); + } + } + + private void IterateArrayWrite(Array a, Func<TElement> write) + { + int[] indices = new int[ArrayRank]; + this.IterateArrayWrite(a, 0, indices, write); + } + + private void IterateArrayWrite(Array a, int rank, int[] indices, Func<TElement> write) + { + for (int i = 0; i < a.GetLength(rank); i++) + { + indices[rank] = i; + + if (rank + 1 < a.Rank) + { + this.IterateArrayWrite(a, rank + 1, indices, write); + } + else + { + a.SetValue(write(), indices); + } + } + } + + private void IterateArrayRead(Array a, Action<TElement> read) + { + int[] indices = new int[ArrayRank]; + this.IterateArrayRead(a, 0, indices, read); + } + + private void IterateArrayRead(Array a, int rank, int[] indices, Action<TElement> read) + { + for (int i = 0; i < a.GetLength(rank); i++) + { + indices[rank] = i; + + if (rank + 1 < a.Rank) + { + this.IterateArrayRead(a, rank + 1, indices, read); + } + else + { + read((TElement)a.GetValue(indices)); + } + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MultiDimensionalArrayFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MultiDimensionalArrayFormatter.cs.meta new file mode 100644 index 00000000..bb09ef4a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/MultiDimensionalArrayFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc1b5b3148988d0d4fc2dab60a5c146c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/NullableFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/NullableFormatter.cs new file mode 100644 index 00000000..9e40a510 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/NullableFormatter.cs @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------- +// <copyright file="NullableFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(NullableFormatter<>))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Formatter for all <see cref="System.Nullable{T}"/> types. + /// </summary> + /// <typeparam name="T">The type that is nullable.</typeparam> + /// <seealso cref="BaseFormatter{T?}" /> + public sealed class NullableFormatter<T> : BaseFormatter<T?> where T : struct + { + private static readonly Serializer<T> TSerializer = Serializer.Get<T>(); + + static NullableFormatter() + { + // This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor + // which it otherwise seems prone to do, regardless of what might be defined in any link.xml file. + + new NullableFormatter<int>(); + } + + /// <summary> + /// Creates a new instance of <see cref="NullableFormatter{T}"/>. + /// </summary> + public NullableFormatter() + { + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="!:T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="M:OdinSerializer.BaseFormatter`1.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref T? value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Null) + { + value = null; + reader.ReadNull(); + } + else + { + value = TSerializer.ReadValue(reader); + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="!:T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref T? value, IDataWriter writer) + { + if (value.HasValue) + { + TSerializer.WriteValue(value.Value, writer); + } + else + { + writer.WriteNull(null); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/NullableFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/NullableFormatter.cs.meta new file mode 100644 index 00000000..47254342 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/NullableFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9ea00de8051ca957d994e11630671d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/PrimitiveArrayFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/PrimitiveArrayFormatter.cs new file mode 100644 index 00000000..f507047c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/PrimitiveArrayFormatter.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------- +// <copyright file="PrimitiveArrayFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Formatter for all primitive one-dimensional arrays. + /// </summary> + /// <typeparam name="T">The element type of the formatted array. This type must be an eligible primitive array type, as determined by <see cref="FormatterUtilities.IsPrimitiveArrayType(System.Type)"/>.</typeparam> + /// <seealso cref="MinimalBaseFormatter{T[]}" /> + public sealed class PrimitiveArrayFormatter<T> : MinimalBaseFormatter<T[]> where T : struct + { + /// <summary> + /// Returns null. + /// </summary> + /// <returns> + /// A null value. + /// </returns> + protected override T[] GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref T[] value, IDataReader reader) + { + string name; + + if (reader.PeekEntry(out name) == EntryType.PrimitiveArray) + { + reader.ReadPrimitiveArray(out value); + this.RegisterReferenceID(value, reader); + } + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref T[] value, IDataWriter writer) + { + writer.WritePrimitiveArray(value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/PrimitiveArrayFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/PrimitiveArrayFormatter.cs.meta new file mode 100644 index 00000000..be3ef0b3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/PrimitiveArrayFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b6a62ea2fe943a4b261f832e8a1f3dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/QueueFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/QueueFormatter.cs new file mode 100644 index 00000000..6fc040ca --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/QueueFormatter.cs @@ -0,0 +1,151 @@ +//----------------------------------------------------------------------- +// <copyright file="QueueFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(QueueFormatter<,>))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Custom generic formatter for the generic type definition <see cref="Queue{T}"/>. + /// </summary> + /// <typeparam name="T">The element type of the formatted queue.</typeparam> + /// <seealso cref="BaseFormatter{System.Collections.Generic.Queue{T}}" /> + public class QueueFormatter<TQueue, TValue> : BaseFormatter<TQueue> + where TQueue : Queue<TValue>, new() + { + private static readonly Serializer<TValue> TSerializer = Serializer.Get<TValue>(); + private static readonly bool IsPlainQueue = typeof(TQueue) == typeof(Queue<TValue>); + + static QueueFormatter() + { + // This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor + // which it otherwise seems prone to do, regardless of what might be defined in any link.xml file. + + new QueueFormatter<Queue<int>, int>(); + } + + public QueueFormatter() + { + } + + /// <summary> + /// Returns null. + /// </summary> + /// <returns> + /// A null value. + /// </returns> + protected override TQueue GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref TQueue value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.StartOfArray) + { + try + { + long length; + reader.EnterArray(out length); + + if (IsPlainQueue) + { + value = (TQueue)new Queue<TValue>((int)length); + } + else + { + value = new TQueue(); + } + + // We must remember to register the queue reference ourselves, since we return null in GetUninitializedObject + this.RegisterReferenceID(value, reader); + + // There aren't any OnDeserializing callbacks on queues. + // Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context); + for (int i = 0; i < length; i++) + { + if (reader.PeekEntry(out name) == EntryType.EndOfArray) + { + reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected."); + break; + } + + value.Enqueue(TSerializer.ReadValue(reader)); + + if (reader.IsInArrayNode == false) + { + // Something has gone wrong + reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump()); + break; + } + } + } + finally + { + reader.ExitArray(); + } + } + else + { + reader.SkipEntry(); + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref TQueue value, IDataWriter writer) + { + try + { + writer.BeginArrayNode(value.Count); + + foreach (var element in value) + { + try + { + TSerializer.WriteValue(element, writer); + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + } + } + finally + { + writer.EndArrayNode(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/QueueFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/QueueFormatter.cs.meta new file mode 100644 index 00000000..07078ecf --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/QueueFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8045e4edca7c27f5b16bd90d7101c935 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionFormatter.cs new file mode 100644 index 00000000..99696121 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionFormatter.cs @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------- +// <copyright file="ReflectionFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Reflection; + using System.Runtime.Serialization; + + /// <summary> + /// Final fallback formatter for all types which have no other formatters. This formatter relies on reflection to work, and is thus comparatively slow and creates more garbage than a custom formatter. + /// </summary> + /// <typeparam name="T">The type which can be serialized and deserialized by the formatter.</typeparam> + /// <seealso cref="BaseFormatter{T}" /> + public class ReflectionFormatter<T> : BaseFormatter<T> + { + public ReflectionFormatter() + { + } + + public ReflectionFormatter(ISerializationPolicy overridePolicy) + { + this.OverridePolicy = overridePolicy; + } + + public ISerializationPolicy OverridePolicy { get; private set; } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref T value, IDataReader reader) + { + // We sadly *must* box so that complex value types get their values properly set by reflection. + // At least we only box these once. + object boxedValue = value; + + var members = FormatterUtilities.GetSerializableMembersMap(typeof(T), this.OverridePolicy ?? reader.Context.Config.SerializationPolicy); + + EntryType entryType; + string name; + + while ((entryType = reader.PeekEntry(out name)) != EntryType.EndOfNode && entryType != EntryType.EndOfArray && entryType != EntryType.EndOfStream) + { + if (string.IsNullOrEmpty(name)) + { + reader.Context.Config.DebugContext.LogError("Entry of type \"" + entryType + "\" in node \"" + reader.CurrentNodeName + "\" is missing a name."); + reader.SkipEntry(); + continue; + } + + MemberInfo member; + + if (members.TryGetValue(name, out member) == false) + { + reader.Context.Config.DebugContext.LogWarning("Lost serialization data for entry \"" + name + "\" of type \"" + entryType + "\"in node \"" + reader.CurrentNodeName + "\"."); + reader.SkipEntry(); + continue; + } + + Type expectedType = FormatterUtilities.GetContainedType(member); + + try + { + var serializer = Serializer.Get(expectedType); + object entryValue = serializer.ReadValueWeak(reader); + FormatterUtilities.SetMemberValue(member, boxedValue, entryValue); + } + catch (Exception ex) + { + reader.Context.Config.DebugContext.LogException(ex); + } + } + + value = (T)boxedValue; // Unbox + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref T value, IDataWriter writer) + { + var members = FormatterUtilities.GetSerializableMembers(typeof(T), this.OverridePolicy ?? writer.Context.Config.SerializationPolicy); + + for (int i = 0; i < members.Length; i++) + { + var member = members[i]; + Type type; + var memberValue = FormatterUtilities.GetMemberValue(member, value); + + type = FormatterUtilities.GetContainedType(member); + + var serializer = Serializer.Get(type); + + try + { + serializer.WriteValueWeak(member.Name, memberValue, writer); + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionFormatter.cs.meta new file mode 100644 index 00000000..8882efc8 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 15fa864c9e3363220ceac4ec93c7f5b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionOrEmittedBaseFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionOrEmittedBaseFormatter.cs new file mode 100644 index 00000000..23f8b0e3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionOrEmittedBaseFormatter.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------- +// <copyright file="ReflectionOrEmittedBaseFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +#if (UNITY_EDITOR || UNITY_STANDALONE) && !ENABLE_IL2CPP && NET_4_6 +#define CAN_EMIT +#endif + +namespace VRC.Udon.Serialization.OdinSerializer +{ + public abstract class ReflectionOrEmittedBaseFormatter<T> : ReflectionFormatter<T> + { +#if CAN_EMIT + + protected override void DeserializeImplementation(ref T value, IDataReader reader) + { + var formatter = FormatterEmitter.GetEmittedFormatter(typeof(T), reader.Context.Config.SerializationPolicy) as FormatterEmitter.RuntimeEmittedFormatter<T>; + + if (formatter == null) + return; + + int count = 0; + string name; + EntryType entry; + + while ((entry = reader.PeekEntry(out name)) != EntryType.EndOfNode && entry != EntryType.EndOfArray && entry != EntryType.EndOfStream) + { + formatter.Read(ref value, name, entry, reader); + + count++; + + if (count > 1000) + { + reader.Context.Config.DebugContext.LogError("Breaking out of infinite reading loop!"); + break; + } + } + } + + protected override void SerializeImplementation(ref T value, IDataWriter writer) + { + var formatter = FormatterEmitter.GetEmittedFormatter(typeof(T), writer.Context.Config.SerializationPolicy) as FormatterEmitter.RuntimeEmittedFormatter<T>; + + if (formatter == null) + return; + + formatter.Write(ref value, writer); + } +#endif + } +} diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionOrEmittedBaseFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionOrEmittedBaseFormatter.cs.meta new file mode 100644 index 00000000..188f8962 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/ReflectionOrEmittedBaseFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dde0095df6bea6624dfa72a31127bc48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SelfFormatterFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SelfFormatterFormatter.cs new file mode 100644 index 00000000..dd1bd407 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SelfFormatterFormatter.cs @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------- +// <copyright file="SelfFormatterFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Formatter for types that implement the <see cref="ISelfFormatter"/> interface. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <seealso cref="BaseFormatter{T}" /> + public sealed class SelfFormatterFormatter<T> : BaseFormatter<T> where T : ISelfFormatter + { + /// <summary> + /// Calls <see cref="ISelfFormatter.Deserialize" /> on the value to deserialize. + /// </summary> + protected override void DeserializeImplementation(ref T value, IDataReader reader) + { + value.Deserialize(reader); + } + + /// <summary> + /// Calls <see cref="ISelfFormatter.Serialize" /> on the value to deserialize. + /// </summary> + protected override void SerializeImplementation(ref T value, IDataWriter writer) + { + value.Serialize(writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SelfFormatterFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SelfFormatterFormatter.cs.meta new file mode 100644 index 00000000..38c8c2a3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SelfFormatterFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 12a47dd574302b77ba3c5ac05cd04541 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SerializableFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SerializableFormatter.cs new file mode 100644 index 00000000..88d4a45b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SerializableFormatter.cs @@ -0,0 +1,239 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializableFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Reflection; + using System.Runtime.Serialization; + + /// <summary> + /// Formatter for all types that implement the ISerializable interface. + /// </summary> + /// <typeparam name="T">The type which can be serialized and deserialized by the formatter.</typeparam> + /// <seealso cref="BaseFormatter{T}" /> + public sealed class SerializableFormatter<T> : BaseFormatter<T> where T : ISerializable + { + private static readonly Func<SerializationInfo, StreamingContext, T> ISerializableConstructor; + private static readonly ReflectionFormatter<T> ReflectionFormatter; + + static SerializableFormatter() + { + var current = typeof(T); + + ConstructorInfo constructor = null; + + do + { + constructor = current.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext) }, null); + current = current.BaseType; + } + while (constructor == null && current != typeof(object) && current != null); + + if (constructor != null) + { + // TODO: Fancy compiled delegate + ISerializableConstructor = (info, context) => + { + T obj = (T)FormatterServices.GetUninitializedObject(typeof(T)); + constructor.Invoke(obj, new object[] { info, context }); + return obj; + }; + } + else + { + DefaultLoggers.DefaultLogger.LogWarning("Type " + typeof(T).Name + " implements the interface ISerializable but does not implement the required constructor with signature " + typeof(T).Name + "(SerializationInfo info, StreamingContext context). The interface declaration will be ignored, and the formatter fallbacks to reflection."); + ReflectionFormatter = new ReflectionFormatter<T>(); + } + } + + /// <summary> + /// Get an uninitialized object of type <see cref="T" />. WARNING: If you override this and return null, the object's ID will not be automatically registered and its OnDeserializing callbacks will not be automatically called, before deserialization begins. + /// You will have to call <see cref="BaseFormatter{T}.RegisterReferenceID(T, IDataReader, DeserializationContext)" /> and <see cref="BaseFormatter{T}.InvokeOnDeserializingCallbacks(T, IDataReader, DeserializationContext)" /> immediately after creating the object yourself during deserialization. + /// </summary> + /// <returns> + /// An uninitialized object of type <see cref="T" />. + /// </returns> + protected override T GetUninitializedObject() + { + return default(T); + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref T value, IDataReader reader) + { + if (SerializableFormatter<T>.ISerializableConstructor != null) + { + var info = this.ReadSerializationInfo(reader); + + if (info != null) + { + try + { + value = SerializableFormatter<T>.ISerializableConstructor(info, reader.Context.StreamingContext); + + this.InvokeOnDeserializingCallbacks(ref value, reader.Context); + + if (IsValueType == false) + { + this.RegisterReferenceID(value, reader); + } + + return; + } + catch (Exception ex) + { + reader.Context.Config.DebugContext.LogException(ex); + } + } + } + else + { + value = ReflectionFormatter.Deserialize(reader); + + this.InvokeOnDeserializingCallbacks(ref value, reader.Context); + + if (IsValueType == false) + { + this.RegisterReferenceID(value, reader); + } + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref T value, IDataWriter writer) + { + if (SerializableFormatter<T>.ISerializableConstructor != null) + { + var serializable = value as ISerializable; + var info = new SerializationInfo(value.GetType(), writer.Context.FormatterConverter); + + try + { + serializable.GetObjectData(info, writer.Context.StreamingContext); + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + + this.WriteSerializationInfo(info, writer); + } + else + { + ReflectionFormatter.Serialize(value, writer); + } + } + + /// <summary> + /// Creates and reads into a <see cref="SerializationInfo" /> instance using a given reader and context. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The <see cref="SerializationInfo" /> which was read. + /// </returns> + private SerializationInfo ReadSerializationInfo(IDataReader reader) + { + string name; + EntryType entry = reader.PeekEntry(out name); + + if (entry == EntryType.StartOfArray) + { + try + { + long length; + reader.EnterArray(out length); + + SerializationInfo info = new SerializationInfo(typeof(T), reader.Context.FormatterConverter); + + for (int i = 0; i < length; i++) + { + Type type = null; + entry = reader.PeekEntry(out name); + + if (entry == EntryType.String && name == "type") + { + string typeName; + reader.ReadString(out typeName); + type = reader.Context.Binder.BindToType(typeName, reader.Context.Config.DebugContext); + } + + if (type == null) + { + reader.SkipEntry(); + continue; + } + + entry = reader.PeekEntry(out name); + + var readerWriter = Serializer.Get(type); + object value = readerWriter.ReadValueWeak(reader); + info.AddValue(name, value); + } + + return info; + } + finally + { + reader.ExitArray(); + } + } + + return null; + } + + /// <summary> + /// Writes the given <see cref="SerializationInfo" /> using the given writer. + /// </summary> + /// <param name="info">The <see cref="SerializationInfo" /> to write.</param> + /// <param name="writer">The writer to use.</param> + private void WriteSerializationInfo(SerializationInfo info, IDataWriter writer) + { + try + { + writer.BeginArrayNode(info.MemberCount); + + foreach (var entry in info) + { + try + { + writer.WriteString("type", writer.Context.Binder.BindToName(entry.ObjectType, writer.Context.Config.DebugContext)); + var readerWriter = Serializer.Get(entry.ObjectType); + readerWriter.WriteValueWeak(entry.Name, entry.Value, writer); + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + } + } + finally + { + writer.EndArrayNode(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SerializableFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SerializableFormatter.cs.meta new file mode 100644 index 00000000..a3d465d6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/SerializableFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f59404c810d015ed87c7e1557188435 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/StackFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/StackFormatter.cs new file mode 100644 index 00000000..7dbf7fcb --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/StackFormatter.cs @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------- +// <copyright file="StackFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(StackFormatter<,>))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using Utilities; + using System; + using System.Collections.Generic; + + /// <summary> + /// Custom generic formatter for the generic type definition <see cref="Stack{T}"/> and types derived from it. + /// </summary> + /// <typeparam name="T">The element type of the formatted stack.</typeparam> + /// <seealso cref="BaseFormatter{System.Collections.Generic.Stack{T}}" /> + public class StackFormatter<TStack, TValue> : BaseFormatter<TStack> + where TStack : Stack<TValue>, new() + { + private static readonly Serializer<TValue> TSerializer = Serializer.Get<TValue>(); + private static readonly bool IsPlainStack = typeof(TStack) == typeof(Stack<TValue>); + + static StackFormatter() + { + // This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor + // which it otherwise seems prone to do, regardless of what might be defined in any link.xml file. + + new StackFormatter<Stack<int>, int>(); + } + + public StackFormatter() + { + } + + /// <summary> + /// Returns null. + /// </summary> + /// <returns> + /// A null value. + /// </returns> + protected override TStack GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Provides the actual implementation for deserializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param> + /// <param name="reader">The reader to deserialize with.</param> + protected override void DeserializeImplementation(ref TStack value, IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.StartOfArray) + { + try + { + long length; + reader.EnterArray(out length); + + if (IsPlainStack) + { + value = (TStack)new Stack<TValue>((int)length); + } + else + { + value = new TStack(); + } + + // We must remember to register the stack reference ourselves, since we return null in GetUninitializedObject + this.RegisterReferenceID(value, reader); + + // There aren't any OnDeserializing callbacks on stacks. + // Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context); + for (int i = 0; i < length; i++) + { + if (reader.PeekEntry(out name) == EntryType.EndOfArray) + { + reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected."); + break; + } + + value.Push(TSerializer.ReadValue(reader)); + + if (reader.IsInArrayNode == false) + { + // Something has gone wrong + reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump()); + break; + } + } + } + finally + { + reader.ExitArray(); + } + } + else + { + reader.SkipEntry(); + } + } + + /// <summary> + /// Provides the actual implementation for serializing a value of type <see cref="T" />. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to serialize with.</param> + protected override void SerializeImplementation(ref TStack value, IDataWriter writer) + { + try + { + writer.BeginArrayNode(value.Count); + + using (var listCache = Cache<List<TValue>>.Claim()) + { + var list = listCache.Value; + list.Clear(); + + foreach (var element in value) + { + list.Add(element); + } + + for (int i = list.Count - 1; i >= 0; i--) + { + try + { + TSerializer.WriteValue(list[i], writer); + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + } + } + } + finally + { + writer.EndArrayNode(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/StackFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/StackFormatter.cs.meta new file mode 100644 index 00000000..45e1e9ac --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/StackFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 087303d0d43cf7ce5af060a0cc0b5d38 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TimeSpanFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TimeSpanFormatter.cs new file mode 100644 index 00000000..aaf5d1fa --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TimeSpanFormatter.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// <copyright file="TimeSpanFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(TimeSpanFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Custom formatter for the <see cref="TimeSpan"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{System.TimeSpan}" /> + public sealed class TimeSpanFormatter : MinimalBaseFormatter<TimeSpan> + { + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref TimeSpan value, IDataReader reader) + { + string name; + + if (reader.PeekEntry(out name) == EntryType.Integer) + { + long ticks; + reader.ReadInt64(out ticks); + value = new TimeSpan(ticks); + } + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref TimeSpan value, IDataWriter writer) + { + writer.WriteInt64(null, value.Ticks); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TimeSpanFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TimeSpanFormatter.cs.meta new file mode 100644 index 00000000..421e29d1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TimeSpanFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4b0676b49f03cc50a1e532cf23e3988e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TypeFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TypeFormatter.cs new file mode 100644 index 00000000..3564c9b9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TypeFormatter.cs @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------- +// <copyright file="TypeFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + // Registered by TypeFormatterLocator + + /// <summary> + /// Formatter for the <see cref="Type"/> type which uses the reader/writer's <see cref="TwoWaySerializationBinder"/> to bind types. + /// </summary> + /// <seealso cref="Serialization.MinimalBaseFormatter{T}" /> + public sealed class TypeFormatter : MinimalBaseFormatter<Type> + { + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Type value, IDataReader reader) + { + string name; + + if (reader.PeekEntry(out name) == EntryType.String) + { + reader.ReadString(out name); + value = reader.Context.Binder.BindToType(name, reader.Context.Config.DebugContext); + + if (value != null) + { + this.RegisterReferenceID(value, reader); + } + } + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Type value, IDataWriter writer) + { + writer.WriteString(null, writer.Context.Binder.BindToName(value, writer.Context.Config.DebugContext)); + } + + /// <summary> + /// Returns null. + /// </summary> + /// <returns>null.</returns> + protected override Type GetUninitializedObject() + { + return null; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TypeFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TypeFormatter.cs.meta new file mode 100644 index 00000000..ab4e4637 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/TypeFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c6529471b992ba4080a123aa504ef9ea +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/VersionFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/VersionFormatter.cs new file mode 100644 index 00000000..ac9976b5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/VersionFormatter.cs @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------- +// <copyright file="VersionFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(VersionFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Custom formatter for the <see cref="Version"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{System.Version}" /> + public sealed class VersionFormatter : MinimalBaseFormatter<Version> + { + protected override Version GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Version value, IDataReader reader) + { + int major = 0, + minor = 0, + build = 0, + revision = 0; + + reader.ReadInt32(out major); + reader.ReadInt32(out minor); + reader.ReadInt32(out build); + reader.ReadInt32(out revision); + + if (major < 0 || minor < 0) + { + value = new Version(); + } + else if (build < 0) + { + value = new Version(major, minor); + } + else if (revision < 0) + { + value = new Version(major, minor, build); + } + else + { + value = new Version(major, minor, build, revision); + } + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Version value, IDataWriter writer) + { + writer.WriteInt32(null, value.Major); + writer.WriteInt32(null, value.Minor); + writer.WriteInt32(null, value.Build); + writer.WriteInt32(null, value.Revision); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/VersionFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/VersionFormatter.cs.meta new file mode 100644 index 00000000..80b32747 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Formatters/VersionFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a7c8e71a3ef1124db10e72af34e1724 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc.meta new file mode 100644 index 00000000..fe40d6a5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4e7ba823c6aa609449d8e4cd4cf8b589 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AllowDeserializeInvalidDataAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AllowDeserializeInvalidDataAttribute.cs new file mode 100644 index 00000000..dd4da202 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AllowDeserializeInvalidDataAttribute.cs @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------- +// <copyright file="AllowDeserializeInvalidDataAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// <para> + /// Applying this attribute to a type indicates that in the case where, when expecting to deserialize an instance of the type + /// or any of its derived types, but encountering an incompatible, uncastable type in the data being read, the serializer + /// should attempt to deserialize an instance of the expected type using the stored, possibly invalid data. + /// </para> + /// <para> + /// This is equivalent to the <see cref="SerializationConfig.AllowDeserializeInvalidData"/> option, expect type-specific instead + /// of global. + /// </para> + /// </summary> + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] + public class AllowDeserializeInvalidDataAttribute : Attribute + { + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AllowDeserializeInvalidDataAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AllowDeserializeInvalidDataAttribute.cs.meta new file mode 100644 index 00000000..39b2e678 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AllowDeserializeInvalidDataAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 23fa5d3fed3b4b9de502257a594b00de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AlwaysFormatsSelfAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AlwaysFormatsSelfAttribute.cs new file mode 100644 index 00000000..54c3f011 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AlwaysFormatsSelfAttribute.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// <copyright file="AlwaysFormatsSelfAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Use this attribute to specify that a type that implements the <see cref="ISelfFormatter"/> + /// interface should *always* format itself regardless of other formatters being specified. + /// <para /> + /// This means that the interface will be used to format all types derived from the type that + /// is decorated with this attribute, regardless of custom formatters for the derived types. + /// </summary> + /// <seealso cref="System.Attribute" /> + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] + public sealed class AlwaysFormatsSelfAttribute : Attribute + { + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AlwaysFormatsSelfAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AlwaysFormatsSelfAttribute.cs.meta new file mode 100644 index 00000000..0c677c7f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/AlwaysFormatsSelfAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 92726834b08002d525b86fbb012e184f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ArchitectureInfo.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ArchitectureInfo.cs new file mode 100644 index 00000000..2dc5322d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ArchitectureInfo.cs @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------- +// <copyright file="ArchitectureInfo.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using UnityEngine; + + /// <summary> + /// This class gathers info about the current architecture for the purpose of determinining + /// the unaligned read/write capabilities that we have to work with. + /// </summary> + public unsafe static class ArchitectureInfo + { + public static bool Architecture_Supports_Unaligned_Float32_Reads; + + /// <summary> + /// This will be false on some ARM architectures, such as ARMv7. + /// In these cases, we will have to perform slower but safer int-by-int read/writes of data. + /// <para /> + /// Since this value will never change at runtime, performance hits from checking this + /// everywhere should hopefully be negligible, since branch prediction from speculative + /// execution will always predict it correctly. + /// </summary> + public static bool Architecture_Supports_All_Unaligned_ReadWrites; + + static ArchitectureInfo() + { +#if UNITY_EDITOR + Architecture_Supports_Unaligned_Float32_Reads = true; + Architecture_Supports_All_Unaligned_ReadWrites = true; +#else + // At runtime, we are going to be very pessimistic and assume the + // worst until we get more info about the platform we are on. + Architecture_Supports_Unaligned_Float32_Reads = false; + Architecture_Supports_All_Unaligned_ReadWrites = false; + + Debug.Log("Odin Serializer ArchitectureInfo initialization with defaults (all unaligned read/writes disabled)."); +#endif +#pragma warning disable 0162 // Unreachable Code Detected + } + + internal static void SetRuntimePlatform(RuntimePlatform platform) + { + // Experience indicates that unaligned read/write support is pretty spotty and sometimes causes subtle bugs even when it appears to work, + // so to be safe, we only enable it for platforms where we are certain that it will work. + + switch (platform) + { + case RuntimePlatform.LinuxPlayer: + case RuntimePlatform.WindowsPlayer: + case RuntimePlatform.OSXPlayer: + case RuntimePlatform.PS3: + case RuntimePlatform.PS4: + case RuntimePlatform.XBOX360: + case RuntimePlatform.XboxOne: + case RuntimePlatform.WebGLPlayer: + case RuntimePlatform.WSAPlayerX64: + case RuntimePlatform.WSAPlayerX86: + case RuntimePlatform.WiiU: + + try + { + // Try to perform some unaligned float reads. + // If this throws an exception, the current + // architecture does not support doing this. + + // Note that there are cases where this is supported + // but other unaligned read/writes are not, usually + // 64-bit read/writes. However, testing indicates + // that these read/writes cause hard crashes and not + // NullReferenceExceptions, and so we cannot test for + // them but must instead look at the architecture. + + byte[] testArray = new byte[8]; + + fixed (byte* test = testArray) + { + // Even if test is weirdly aligned in the stack, trying four differently aligned + // reads will definitely have an unaligned read or two in there. + + // If all of these reads work, we are safe. We do it this way instead of just having one read, + // because as far as I have been able to determine, there are no guarantees about the alignment + // of local stack memory. + + for (int i = 0; i < 4; i++) + { + float value = *(float*)(test + i); + } + + Architecture_Supports_Unaligned_Float32_Reads = true; + } + } + catch (NullReferenceException) + { + Architecture_Supports_Unaligned_Float32_Reads = false; + } + + if (Architecture_Supports_Unaligned_Float32_Reads) + { + Debug.Log("Odin Serializer detected whitelisted runtime platform " + platform + " and memory read test succeeded; enabling all unaligned memory read/writes."); + Architecture_Supports_All_Unaligned_ReadWrites = true; + } + else + { + Debug.Log("Odin Serializer detected whitelisted runtime platform " + platform + " and memory read test failed; disabling all unaligned memory read/writes."); + } + break; + default: + Architecture_Supports_Unaligned_Float32_Reads = false; + Architecture_Supports_All_Unaligned_ReadWrites = false; + Debug.Log("Odin Serializer detected non-white-listed runtime platform " + platform + "; disabling all unaligned memory read/writes."); + break; + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ArchitectureInfo.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ArchitectureInfo.cs.meta new file mode 100644 index 00000000..e3706aa0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ArchitectureInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 72783638708ea644ba5c3e1b91f827f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/Buffer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/Buffer.cs new file mode 100644 index 00000000..efc21373 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/Buffer.cs @@ -0,0 +1,210 @@ +//----------------------------------------------------------------------- +// <copyright file="Buffer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Provides a way of claiming and releasing cached array buffers. + /// </summary> + /// <typeparam name="T">The element type of the array to buffer.</typeparam> + /// <seealso cref="System.IDisposable" /> + public sealed class Buffer<T> : IDisposable + { + private static readonly object LOCK = new object(); + private static readonly List<Buffer<T>> FreeBuffers = new List<Buffer<T>>(); + + private int count; + private T[] array; + private volatile bool isFree; + + private Buffer(int count) + { + this.array = new T[count]; + this.count = count; + this.isFree = false; // Always start as non-free + } + + /// <summary> + /// Gets the total element count of the buffered array. This will always be a power of two. + /// </summary> + /// <value> + /// The total element count of the buffered array. + /// </value> + /// <exception cref="System.InvalidOperationException">Cannot access a buffer while it is freed.</exception> + public int Count + { + get + { + if (this.isFree) + { + throw new InvalidOperationException("Cannot access a buffer while it is freed."); + } + + return this.count; + } + } + + /// <summary> + /// Gets the buffered array. + /// </summary> + /// <value> + /// The buffered array. + /// </value> + /// <exception cref="System.InvalidOperationException">Cannot access a buffer while it is freed.</exception> + public T[] Array + { + get + { + if (this.isFree) + { + throw new InvalidOperationException("Cannot access a buffer while it is freed."); + } + + return this.array; + } + } + + /// <summary> + /// Gets a value indicating whether this buffer is free. + /// </summary> + /// <value> + /// <c>true</c> if this buffer is free; otherwise, <c>false</c>. + /// </value> + public bool IsFree { get { return this.isFree; } } + + /// <summary> + /// Claims a buffer with the specified minimum capacity. Note: buffers always have a capacity equal to or larger than 256. + /// </summary> + /// <param name="minimumCapacity">The minimum capacity.</param> + /// <returns>A buffer which has a capacity equal to or larger than the specified minimum capacity.</returns> + /// <exception cref="System.ArgumentException">Requested size of buffer must be larger than 0.</exception> + public static Buffer<T> Claim(int minimumCapacity) + { + if (minimumCapacity < 0) + { + throw new ArgumentException("Requested size of buffer must be larger than or equal to 0."); + } + + if (minimumCapacity < 256) + { + minimumCapacity = 256; // Minimum buffer size + } + + Buffer<T> result = null; + + lock (LOCK) + { + // Search for a free buffer of sufficient size + for (int i = 0; i < Buffer<T>.FreeBuffers.Count; i++) + { + var buffer = Buffer<T>.FreeBuffers[i]; + + if (buffer != null && buffer.count >= minimumCapacity) + { + result = buffer; + result.isFree = false; + Buffer<T>.FreeBuffers[i] = null; + break; + } + } + } + + if (result == null) + { + // Allocate new buffer + result = new Buffer<T>(Buffer<T>.NextPowerOfTwo(minimumCapacity)); + } + + return result; + } + + /// <summary> + /// Frees the specified buffer. + /// </summary> + /// <param name="buffer">The buffer to free.</param> + /// <exception cref="System.ArgumentNullException">The buffer argument is null.</exception> + public static void Free(Buffer<T> buffer) + { + if (buffer == null) + { + throw new ArgumentNullException("buffer"); + } + + if (buffer.isFree == false) + { + lock (LOCK) + { + if (buffer.isFree == false) + { + buffer.isFree = true; + + bool added = false; + + for (int i = 0; i < Buffer<T>.FreeBuffers.Count; i++) + { + if (Buffer<T>.FreeBuffers[i] == null) + { + Buffer<T>.FreeBuffers[i] = buffer; + added = true; + break; + } + } + + if (!added) + { + Buffer<T>.FreeBuffers.Add(buffer); + } + } + } + } + } + + /// <summary> + /// Frees this buffer. + /// </summary> + public void Free() + { + Buffer<T>.Free(this); + } + + /// <summary> + /// Frees this buffer. + /// </summary> + public void Dispose() + { + Buffer<T>.Free(this); + } + + private static int NextPowerOfTwo(int v) + { + // Engage bit hax + // http://stackoverflow.com/questions/466204/rounding-up-to-nearest-power-of-2 + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/Buffer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/Buffer.cs.meta new file mode 100644 index 00000000..444d8c4d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/Buffer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ad4e17831e9503c1f11149997c609477 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CachedMemoryStream.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CachedMemoryStream.cs new file mode 100644 index 00000000..d6dbadd3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CachedMemoryStream.cs @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------- +// <copyright file="CachedMemoryStream.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using VRC.Udon.Serialization.OdinSerializer.Utilities; + using System.IO; + + internal sealed class CachedMemoryStream : ICacheNotificationReceiver + { + public static int InitialCapacity = 1024 * 1; // Initial capacity of 1 kb + public static int MaxCapacity = 1024 * 32; // Max of 32 kb cached stream size + + private MemoryStream memoryStream; + + public MemoryStream MemoryStream + { + get + { + if (!this.memoryStream.CanRead) + { + this.memoryStream = new MemoryStream(InitialCapacity); + } + + return this.memoryStream; + } + } + + public CachedMemoryStream() + { + this.memoryStream = new MemoryStream(InitialCapacity); + } + + public void OnFreed() + { + this.memoryStream.SetLength(0); + this.memoryStream.Position = 0; + + if (this.memoryStream.Capacity > MaxCapacity) + { + this.memoryStream.Capacity = MaxCapacity; + } + } + + public void OnClaimed() + { + this.memoryStream.SetLength(0); + this.memoryStream.Position = 0; + } + + public static Cache<CachedMemoryStream> Claim(int minCapacity) + { + var cache = Cache<CachedMemoryStream>.Claim(); + + if (cache.Value.MemoryStream.Capacity < minCapacity) + { + cache.Value.MemoryStream.Capacity = minCapacity; + } + + return cache; + } + + public static Cache<CachedMemoryStream> Claim(byte[] bytes = null) + { + var cache = Cache<CachedMemoryStream>.Claim(); + + if (bytes != null) + { + cache.Value.MemoryStream.Write(bytes, 0, bytes.Length); + cache.Value.MemoryStream.Position = 0; + } + + return cache; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CachedMemoryStream.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CachedMemoryStream.cs.meta new file mode 100644 index 00000000..d370d170 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CachedMemoryStream.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e7e73146f1e861c27c5608bff4142402 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomFormatterAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomFormatterAttribute.cs new file mode 100644 index 00000000..1e7dabde --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomFormatterAttribute.cs @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------- +// <copyright file="CustomFormatterAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Attribute indicating that a class which implements the <see cref="IFormatter{T}" /> interface somewhere in its hierarchy is a custom formatter for the type T. + /// </summary> + /// <seealso cref="System.Attribute" /> + [AttributeUsage(AttributeTargets.Class)] + [Obsolete("Use a RegisterFormatterAttribute applied to the containing assembly instead.", true)] + public class CustomFormatterAttribute : Attribute + { + /// <summary> + /// The priority of the formatter. Of all the available custom formatters, the formatter with the highest priority is always chosen. + /// </summary> + public readonly int Priority; + + /// <summary> + /// Initializes a new instance of the <see cref="CustomFormatterAttribute"/> class with priority 0. + /// </summary> + public CustomFormatterAttribute() + { + this.Priority = 0; + } + + /// <summary> + /// Initializes a new instance of the <see cref="CustomFormatterAttribute"/> class. + /// </summary> + /// <param name="priority">The priority of the formatter. Of all the available custom formatters, the formatter with the highest priority is always chosen.</param> + public CustomFormatterAttribute(int priority = 0) + { + this.Priority = priority; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomFormatterAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomFormatterAttribute.cs.meta new file mode 100644 index 00000000..5d70ef2e --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomFormatterAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4fd6ff4077bbbef9b366d8ffd9236173 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomGenericFormatterAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomGenericFormatterAttribute.cs new file mode 100644 index 00000000..69107fbc --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomGenericFormatterAttribute.cs @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------- +// <copyright file="CustomGenericFormatterAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Attribute indicating that a generic type definition class which implements the <see cref="IFormatter{T}" /> interface somewhere in its hierarchy is a custom formatter for *any variation* of the generic type definition T. + /// <para /> + /// The formatter's generic type parameters are mapped onto the serialized type's generic type parameters. + /// <para /> + /// For example, <see cref="DictionaryFormatter{TKey, TValue}"/> implements <see cref="IFormatter{T}"/>, where T is <see cref="System.Collections.Generic.Dictionary{TKey, TValue}"/>. + /// </summary> + /// <seealso cref="CustomFormatterAttribute" /> + [AttributeUsage(AttributeTargets.Class)] + [Obsolete("Use a RegisterFormatterAttribute applied to the containing assembly instead.", true)] + public class CustomGenericFormatterAttribute : CustomFormatterAttribute + { + /// <summary> + /// The generic type definition of the serialized type. + /// </summary> + public readonly Type SerializedGenericTypeDefinition; + + /// <summary> + /// Initializes a new instance of the <see cref="CustomGenericFormatterAttribute"/> class. + /// </summary> + /// <param name="serializedGenericTypeDefinition">The generic type definition of the serialized type.</param> + /// <param name="priority">The priority of the formatter. Of all the available custom formatters, the formatter with the highest priority is always chosen.</param> + /// <exception cref="System.ArgumentNullException"><paramref name="serializedGenericTypeDefinition"/> was null.</exception> + /// <exception cref="System.ArgumentException">The type given in <paramref name="serializedGenericTypeDefinition"/> is not a generic type definition.</exception> + public CustomGenericFormatterAttribute(Type serializedGenericTypeDefinition, int priority = 0) + : base(priority) + { + if (serializedGenericTypeDefinition == null) + { + throw new ArgumentNullException(); + } + + if (serializedGenericTypeDefinition.IsGenericTypeDefinition == false) + { + throw new ArgumentException("The type " + serializedGenericTypeDefinition.Name + " is not a generic type definition."); + } + + this.SerializedGenericTypeDefinition = serializedGenericTypeDefinition; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomGenericFormatterAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomGenericFormatterAttribute.cs.meta new file mode 100644 index 00000000..94c64a20 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomGenericFormatterAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e02123fad495d06f2a89e5335f00126c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomLogger.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomLogger.cs new file mode 100644 index 00000000..e4261b54 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomLogger.cs @@ -0,0 +1,85 @@ +//----------------------------------------------------------------------- +// <copyright file="ColorFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// A helper class for quickly and easily defining custom loggers. + /// </summary> + /// <seealso cref="ILogger" /> + public class CustomLogger : ILogger + { + private Action<string> logWarningDelegate; + private Action<string> logErrorDelegate; + private Action<Exception> logExceptionDelegate; + + /// <summary> + /// Creates a new custom logger using a set of given delegates. + /// </summary> + public CustomLogger(Action<string> logWarningDelegate, Action<string> logErrorDelegate, Action<Exception> logExceptionDelegate) + { + if (logWarningDelegate == null) + { + throw new ArgumentNullException("logWarningDelegate"); + } + + if (logErrorDelegate == null) + { + throw new ArgumentNullException("logErrorDelegate"); + } + + if (logExceptionDelegate == null) + { + throw new ArgumentNullException("logExceptionDelegate"); + } + + this.logWarningDelegate = logWarningDelegate; + this.logErrorDelegate = logErrorDelegate; + this.logExceptionDelegate = logExceptionDelegate; + } + + /// <summary> + /// Logs a warning. + /// </summary> + /// <param name="warning">The warning to log.</param> + public void LogWarning(string warning) + { + this.logWarningDelegate(warning); + } + + /// <summary> + /// Logs an error. + /// </summary> + /// <param name="error">The error to log.</param> + public void LogError(string error) + { + this.logErrorDelegate(error); + } + + /// <summary> + /// Logs an exception. + /// </summary> + /// <param name="exception">The exception to log.</param> + public void LogException(Exception exception) + { + this.logExceptionDelegate(exception); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomLogger.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomLogger.cs.meta new file mode 100644 index 00000000..38474339 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomLogger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 97e9e01eb36fd43879b166b6b3c2469b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomSerializationPolicy.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomSerializationPolicy.cs new file mode 100644 index 00000000..efd87d09 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomSerializationPolicy.cs @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------- +// <copyright file="CustomSerializationPolicy.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Reflection; + + /// <summary> + /// Helper class for quickly and easily implementing the <see cref="ISerializationPolicy"/> interface. + /// </summary> + public class CustomSerializationPolicy : ISerializationPolicy + { + private string id; + private bool allowNonSerializableTypes; + private Func<MemberInfo, bool> shouldSerializeFunc; + + /// <summary> + /// Initializes a new instance of the <see cref="CustomSerializationPolicy"/> class. + /// </summary> + /// <param name="id">The policy ID.</param> + /// <param name="allowNonSerializableTypes">if set to <c>true</c> non serializable types will be allowed.</param> + /// <param name="shouldSerializeFunc">The delegate to use for determining whether members should be serialized.</param> + /// <exception cref="System.ArgumentNullException"> + /// The id argument or the shouldSerializeFunc argument was null. + /// </exception> + public CustomSerializationPolicy(string id, bool allowNonSerializableTypes, Func<MemberInfo, bool> shouldSerializeFunc) + { + if (id == null) + { + throw new ArgumentNullException("id"); + } + + if (shouldSerializeFunc == null) + { + throw new ArgumentNullException("shouldSerializeFunc"); + } + + this.id = id; + this.allowNonSerializableTypes = allowNonSerializableTypes; + this.shouldSerializeFunc = shouldSerializeFunc; + } + + /// <summary> + /// Gets the identifier of the policy. This can be stored in the serialization metadata, so the policy used to serialize it can be recovered without knowing the policy at runtime. This ID should preferably be unique. + /// </summary> + /// <value> + /// The identifier of the policy. + /// </value> + public string ID { get { return this.id; } } + + /// <summary> + /// Gets a value indicating whether to allow non serializable types. (Types which are not decorated with <see cref="System.SerializableAttribute" />.) + /// </summary> + /// <value> + /// <c>true</c> if serializable types are allowed; otherwise, <c>false</c>. + /// </value> + public bool AllowNonSerializableTypes { get { return this.allowNonSerializableTypes; } } + + /// <summary> + /// Gets a value indicating whether a given <see cref="MemberInfo" /> should be serialized or not. + /// </summary> + /// <param name="member">The member to check.</param> + /// <returns> + /// <c>true</c> if the given member should be serialized, otherwise, <c>false</c>. + /// </returns> + public bool ShouldSerializeMember(MemberInfo member) + { + return this.shouldSerializeFunc(member); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomSerializationPolicy.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomSerializationPolicy.cs.meta new file mode 100644 index 00000000..168bca06 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/CustomSerializationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95bb5531b6c1d1a5eab8400ea1bd6167 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DataFormat.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DataFormat.cs new file mode 100644 index 00000000..d676b95d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DataFormat.cs @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------- +// <copyright file="DataFormat.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Specifies a data format to read and write in. + /// </summary> + public enum DataFormat + { + /// <summary> + /// A custom packed binary format. This format is most efficient and almost allocation-free, + /// but its serialized data is not human-readable. + /// </summary> + Binary = 0, + + /// <summary> + /// A JSON format compliant with the json specification found at "http://www.json.org/". + /// <para /> + /// This format has rather sluggish performance and allocates frightening amounts of string garbage. + /// </summary> + JSON = 1, + + /// <summary> + /// A format that does not serialize to a byte stream, but to a list of data nodes in memory + /// which can then be serialized by Unity. + /// <para /> + /// This format is highly inefficient, and is primarily used for ensuring that Unity assets + /// are mergeable by individual values when saved in Unity's text format. This makes + /// serialized values more robust and data recovery easier in case of issues. + /// <para /> + /// This format is *not* recommended for use in builds. + /// </summary> + Nodes = 2 + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DataFormat.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DataFormat.cs.meta new file mode 100644 index 00000000..8b2523ed --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DataFormat.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c2a40a3e6a114e5a50c0af209b8ae35e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultLoggers.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultLoggers.cs new file mode 100644 index 00000000..43d08983 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultLoggers.cs @@ -0,0 +1,44 @@ +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Defines default loggers for serialization and deserialization. This class and all of its loggers are thread safe. + /// </summary> + public static class DefaultLoggers + { + private static readonly object LOCK = new object(); + private static volatile ILogger unityLogger; + + /// <summary> + /// The default logger - usually this is <see cref="UnityLogger"/>. + /// </summary> + public static ILogger DefaultLogger + { + get + { + return UnityLogger; + } + } + + /// <summary> + /// Logs messages using Unity's <see cref="UnityEngine.Debug"/> class. + /// </summary> + public static ILogger UnityLogger + { + get + { + if (unityLogger == null) + { + lock (LOCK) + { + if (unityLogger == null) + { + unityLogger = new CustomLogger(UnityEngine.Debug.LogWarning, UnityEngine.Debug.LogError, UnityEngine.Debug.LogException); + } + } + } + + return unityLogger; + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultLoggers.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultLoggers.cs.meta new file mode 100644 index 00000000..d1ddbfba --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultLoggers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0bd9ab6cf3bd913588b6652279b7a6ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultSerializationBinder.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultSerializationBinder.cs new file mode 100644 index 00000000..3d012191 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultSerializationBinder.cs @@ -0,0 +1,627 @@ +//----------------------------------------------------------------------- +// <copyright file="DefaultSerializationBinder.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using VRC.Udon.Serialization.OdinSerializer.Utilities; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Runtime.CompilerServices; + + /// <summary> + /// An attribute that lets you help the DefaultSerializationBinder bind type names to types. This is useful if you're renaming a type, + /// that would result in data loss, and what to specify the new type name to avoid loss of data. + /// </summary> + /// <seealso cref="DefaultSerializationBinder" /> + /// <example> + /// <code> + /// [assembly: OdinSerializer.BindTypeNameToType("Namespace.OldTypeName", typeof(Namespace.NewTypeName))] + /// //[assembly: OdinSerializer.BindTypeNameToType("Namespace.OldTypeName, OldFullAssemblyName", typeof(Namespace.NewTypeName))] + /// + /// namespace Namespace + /// { + /// public class SomeComponent : SerializedMonoBehaviour + /// { + /// public IInterface test; // Contains an instance of OldTypeName; + /// } + /// + /// public interface IInterface { } + /// + /// public class NewTypeName : IInterface { } + /// + /// //public class OldTypeName : IInterface { } + /// } + /// </code> + /// </example> + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class BindTypeNameToTypeAttribute : Attribute + { + internal readonly Type NewType; + internal readonly string OldTypeName; + + /// <summary> + /// Initializes a new instance of the <see cref="BindTypeNameToTypeAttribute"/> class. + /// </summary> + /// <param name="oldFullTypeName">Old old full type name. If it's moved to new a new assembly you must specify the old assembly name as well. See example code in the documentation.</param> + /// <param name="newType">The new type.</param> + public BindTypeNameToTypeAttribute(string oldFullTypeName, Type newType) + { + this.OldTypeName = oldFullTypeName; + this.NewType = newType; + } + } + + /// <summary> + /// Provides a default, catch-all <see cref="TwoWaySerializationBinder"/> implementation. This binder only includes assembly names, without versions and tokens, in order to increase compatibility. + /// </summary> + /// <seealso cref="TwoWaySerializationBinder" /> + /// <seealso cref="BindTypeNameToTypeAttribute" /> + public class DefaultSerializationBinder : TwoWaySerializationBinder + { + private static readonly object ASSEMBLY_LOOKUP_LOCK = new object(); + private static readonly Dictionary<string, Assembly> assemblyNameLookUp = new Dictionary<string, Assembly>(); + private static readonly Dictionary<string, Type> customTypeNameToTypeBindings = new Dictionary<string, Type>(); + + private static readonly object TYPETONAME_LOCK = new object(); + private static readonly Dictionary<Type, string> nameMap = new Dictionary<Type, string>(FastTypeComparer.Instance); + + private static readonly object NAMETOTYPE_LOCK = new object(); + private static readonly Dictionary<string, Type> typeMap = new Dictionary<string, Type>(); + + private static readonly List<string> genericArgNamesList = new List<string>(); + private static readonly List<Type> genericArgTypesList = new List<Type>(); + + private static readonly object ASSEMBLY_REGISTER_QUEUE_LOCK = new object(); + private static readonly List<Assembly> assembliesQueuedForRegister = new List<Assembly>(); + private static readonly List<AssemblyLoadEventArgs> assemblyLoadEventsQueuedForRegister = new List<AssemblyLoadEventArgs>(); + + static DefaultSerializationBinder() + { + AppDomain.CurrentDomain.AssemblyLoad += (sender, args) => + { + lock (ASSEMBLY_REGISTER_QUEUE_LOCK) + { + assemblyLoadEventsQueuedForRegister.Add(args); + } + }; + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + lock (ASSEMBLY_REGISTER_QUEUE_LOCK) + { + assembliesQueuedForRegister.Add(assembly); + } + } + } + + private static void RegisterAllQueuedAssembliesRepeating() + { + while (RegisterQueuedAssemblies()) { } + while (RegisterQueuedAssemblyLoadEvents()) { } + } + + private static bool RegisterQueuedAssemblies() + { + Assembly[] toRegister = null; + + lock (ASSEMBLY_REGISTER_QUEUE_LOCK) + { + if (assembliesQueuedForRegister.Count > 0) + { + toRegister = assembliesQueuedForRegister.ToArray(); + assembliesQueuedForRegister.Clear(); + } + } + + if (toRegister == null) return false; + + for (int i = 0; i < toRegister.Length; i++) + { + RegisterAssembly(toRegister[i]); + } + + return true; + } + + private static bool RegisterQueuedAssemblyLoadEvents() + { + AssemblyLoadEventArgs[] toRegister = null; + + lock (ASSEMBLY_REGISTER_QUEUE_LOCK) + { + if (assemblyLoadEventsQueuedForRegister.Count > 0) + { + toRegister = assemblyLoadEventsQueuedForRegister.ToArray(); + assemblyLoadEventsQueuedForRegister.Clear(); + } + } + + if (toRegister == null) return false; + + for (int i = 0; i < toRegister.Length; i++) + { + var args = toRegister[i]; + Assembly assembly; + + try + { + assembly = args.LoadedAssembly; + } + catch { continue; } // Assembly is invalid, likely causing a type load or bad image format exception of some sort + + RegisterAssembly(assembly); + } + + return true; + } + + private static void RegisterAssembly(Assembly assembly) + { + string name; + + try + { + name = assembly.GetName().Name; + } + catch { return; } // Assembly is invalid somehow + + bool wasAdded = false; + + lock (ASSEMBLY_LOOKUP_LOCK) + { + if (!assemblyNameLookUp.ContainsKey(name)) + { + assemblyNameLookUp.Add(name, assembly); + wasAdded = true; + } + } + + if (wasAdded) + { + try + { + var customAttributes = assembly.SafeGetCustomAttributes(typeof(BindTypeNameToTypeAttribute), false); + if (customAttributes != null) + { + for (int i = 0; i < customAttributes.Length; i++) + { + var attr = customAttributes[i] as BindTypeNameToTypeAttribute; + if (attr != null && attr.NewType != null) + { + lock (ASSEMBLY_LOOKUP_LOCK) + { + //if (attr.OldTypeName.Contains(",")) + //{ + customTypeNameToTypeBindings[attr.OldTypeName] = attr.NewType; + //} + //else + //{ + // customTypeNameToTypeBindings[attr.OldTypeName + ", " + assembly.GetName().Name] = attr.NewType; + //} + } + } + } + } + } + catch { } // Assembly is invalid somehow + } + } + + /// <summary> + /// Bind a type to a name. + /// </summary> + /// <param name="type">The type to bind.</param> + /// <param name="debugContext">The debug context to log to.</param> + /// <returns> + /// The name that the type has been bound to. + /// </returns> + /// <exception cref="System.ArgumentNullException">The type argument is null.</exception> + public override string BindToName(Type type, DebugContext debugContext = null) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + string result; + + lock (TYPETONAME_LOCK) + { + if (nameMap.TryGetValue(type, out result) == false) + { + if (type.IsGenericType) + { + // We track down all assemblies in the generic type definition + List<Type> toResolve = type.GetGenericArguments().ToList(); + HashSet<Assembly> assemblies = new HashSet<Assembly>(); + + while (toResolve.Count > 0) + { + var t = toResolve[0]; + + if (t.IsGenericType) + { + toResolve.AddRange(t.GetGenericArguments()); + } + + assemblies.Add(t.Assembly); + toResolve.RemoveAt(0); + } + + result = type.FullName + ", " + type.Assembly.GetName().Name; + + foreach (var ass in assemblies) + { + result = result.Replace(ass.FullName, ass.GetName().Name); + } + } + else if (type.IsDefined(typeof(CompilerGeneratedAttribute), false)) + { + result = type.FullName + ", " + type.Assembly.GetName().Name; + } + else + { + result = type.FullName + ", " + type.Assembly.GetName().Name; + } + + nameMap.Add(type, result); + } + } + + return result; + } + + /// <summary> + /// Determines whether the specified type name is mapped. + /// </summary> + public override bool ContainsType(string typeName) + { + lock (NAMETOTYPE_LOCK) + { + return typeMap.ContainsKey(typeName); + } + } + + /// <summary> + /// Binds a name to type. + /// </summary> + /// <param name="typeName">The name of the type to bind.</param> + /// <param name="debugContext">The debug context to log to.</param> + /// <returns> + /// The type that the name has been bound to, or null if the type could not be resolved. + /// </returns> + /// <exception cref="System.ArgumentNullException">The typeName argument is null.</exception> + public override Type BindToType(string typeName, DebugContext debugContext = null) + { + if (typeName == null) + { + throw new ArgumentNullException("typeName"); + } + + RegisterAllQueuedAssembliesRepeating(); + + Type result; + + lock (NAMETOTYPE_LOCK) + { + if (typeMap.TryGetValue(typeName, out result) == false) + { + result = this.ParseTypeName(typeName, debugContext); + + if (result == null && debugContext != null) + { + debugContext.LogWarning("Failed deserialization type lookup for type name '" + typeName + "'."); + } + + // We allow null values on purpose so we don't have to keep re-performing invalid name lookups + typeMap.Add(typeName, result); + } + } + + return result; + } + + private Type ParseTypeName(string typeName, DebugContext debugContext) + { + Type type; + + lock (ASSEMBLY_LOOKUP_LOCK) + { + // Look for custom defined type name lookups defined with the BindTypeNameToTypeAttribute. + if (customTypeNameToTypeBindings.TryGetValue(typeName, out type)) + { + return type; + } + } + + // Let's try it the traditional .NET way + type = Type.GetType(typeName); + if (type != null) return type; + + // Generic/array type name handling + type = ParseGenericAndOrArrayType(typeName, debugContext); + if (type != null) return type; + + string typeStr, assemblyStr; + + ParseName(typeName, out typeStr, out assemblyStr); + + if (!string.IsNullOrEmpty(typeStr)) + { + lock (ASSEMBLY_LOOKUP_LOCK) + { + // Look for custom defined type name lookups defined with the BindTypeNameToTypeAttribute. + if (customTypeNameToTypeBindings.TryGetValue(typeStr, out type)) + { + return type; + } + } + + Assembly assembly; + + // Try to load from the named assembly + if (assemblyStr != null) + { + lock (ASSEMBLY_LOOKUP_LOCK) + { + assemblyNameLookUp.TryGetValue(assemblyStr, out assembly); + } + + if (assembly == null) + { + try + { + assembly = Assembly.Load(assemblyStr); + } + catch { } + } + + if (assembly != null) + { + try + { + type = assembly.GetType(typeStr); + } + catch { } // Assembly is invalid + + if (type != null) return type; + } + } + + // Try to check all assemblies for the type string + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + for (int i = 0; i < assemblies.Length; i++) + { + assembly = assemblies[i]; + + try + { + type = assembly.GetType(typeStr, false); + } + catch { } // Assembly is invalid + + if (type != null) return type; + } + } + + //type = AssemblyUtilities.GetTypeByCachedFullName(typeStr); + //if (type != null) return type; + + return null; + } + + private static void ParseName(string fullName, out string typeName, out string assemblyName) + { + typeName = null; + assemblyName = null; + + int firstComma = fullName.IndexOf(','); + + if (firstComma < 0 || (firstComma + 1) == fullName.Length) + { + typeName = fullName.Trim(',', ' '); + return; + } + else + { + typeName = fullName.Substring(0, firstComma); + } + + int secondComma = fullName.IndexOf(',', firstComma + 1); + + if (secondComma < 0) + { + assemblyName = fullName.Substring(firstComma).Trim(',', ' '); + } + else + { + assemblyName = fullName.Substring(firstComma, secondComma - firstComma).Trim(',', ' '); + } + } + + private Type ParseGenericAndOrArrayType(string typeName, DebugContext debugContext) + { + string actualTypeName; + List<string> genericArgNames; + + bool isGeneric; + bool isArray; + int arrayRank; + + if (!TryParseGenericAndOrArrayTypeName(typeName, out actualTypeName, out isGeneric, out genericArgNames, out isArray, out arrayRank)) return null; + + Type type = this.BindToType(actualTypeName, debugContext); + + if (type == null) return null; + + if (isGeneric) + { + if (!type.IsGenericType) return null; + + List<Type> args = genericArgTypesList; + args.Clear(); + + for (int i = 0; i < genericArgNames.Count; i++) + { + Type arg = this.BindToType(genericArgNames[i], debugContext); + if (arg == null) return null; + args.Add(arg); + } + + var argsArray = args.ToArray(); + + if (!type.AreGenericConstraintsSatisfiedBy(argsArray)) + { + if (debugContext != null) + { + string argsStr = ""; + + foreach (var arg in args) + { + if (argsStr != "") argsStr += ", "; + argsStr += arg.GetNiceFullName(); + } + + debugContext.LogWarning("Deserialization type lookup failure: The generic type arguments '" + argsStr + "' do not satisfy the generic constraints of generic type definition '" + type.GetNiceFullName() + "'. All this parsed from the full type name string: '" + typeName + "'"); + } + + return null; + } + + type = type.MakeGenericType(argsArray); + } + + if (isArray) + { + type = type.MakeArrayType(arrayRank); + } + + return type; + } + + private static bool TryParseGenericAndOrArrayTypeName(string typeName, out string actualTypeName, out bool isGeneric, out List<string> genericArgNames, out bool isArray, out int arrayRank) + { + isGeneric = false; + isArray = false; + arrayRank = 0; + + bool parsingGenericArguments = false; + + string argName; + genericArgNames = null; + actualTypeName = null; + + for (int i = 0; i < typeName.Length; i++) + { + if (typeName[i] == '[') + { + var next = Peek(typeName, i, 1); + + if (next == ',' || next == ']') + { + if (actualTypeName == null) + { + actualTypeName = typeName.Substring(0, i); + } + + isArray = true; + arrayRank = 1; + i++; + + if (next == ',') + { + while (next == ',') + { + arrayRank++; + next = Peek(typeName, i, 1); + i++; + } + + if (next != ']') + return false; // Malformed type name + } + } + else + { + if (!isGeneric) + { + actualTypeName = typeName.Substring(0, i); + isGeneric = true; + parsingGenericArguments = true; + genericArgNames = genericArgNamesList; + genericArgNames.Clear(); + } + else if (isGeneric && ReadGenericArg(typeName, ref i, out argName)) + { + genericArgNames.Add(argName); + } + else return false; // Malformed type name + } + } + else if (typeName[i] == ']') + { + if (!parsingGenericArguments) return false; // This is not a valid type name, since we're hitting "]" without currently being in the process of parsing the generic arguments or an array thingy + parsingGenericArguments = false; + } + else if (typeName[i] == ',' && !parsingGenericArguments) + { + actualTypeName += typeName.Substring(i); + break; + } + } + + return isArray || isGeneric; + } + + private static char Peek(string str, int i, int ahead) + { + if (i + ahead < str.Length) return str[i + ahead]; + return '\0'; + } + + private static bool ReadGenericArg(string typeName, ref int i, out string argName) + { + argName = null; + if (typeName[i] != '[') return false; + + int start = i + 1; + int genericDepth = 0; + + for (; i < typeName.Length; i++) + { + if (typeName[i] == '[') genericDepth++; + else if (typeName[i] == ']') + { + genericDepth--; + + if (genericDepth == 0) + { + int length = i - start; + argName = typeName.Substring(start, length); + return true; + } + } + } + + return false; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultSerializationBinder.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultSerializationBinder.cs.meta new file mode 100644 index 00000000..03cc3344 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DefaultSerializationBinder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 996e793dcc0920d2590cb61f0761d498 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DeserializationContext.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DeserializationContext.cs new file mode 100644 index 00000000..ae3bd2c3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DeserializationContext.cs @@ -0,0 +1,304 @@ +//----------------------------------------------------------------------- +// <copyright file="DeserializationContext.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using System.Runtime.Serialization; + using Utilities; + + /// <summary> + /// The context of a given deserialization session. This class maintains all internal and external references during deserialization. + /// </summary> + /// <seealso cref="ICacheNotificationReceiver" /> + public sealed class DeserializationContext : ICacheNotificationReceiver + { + private SerializationConfig config; + private Dictionary<int, object> internalIdReferenceMap = new Dictionary<int, object>(128); + private StreamingContext streamingContext; + private IFormatterConverter formatterConverter; + private TwoWaySerializationBinder binder; + + /// <summary> + /// Initializes a new instance of the <see cref="DeserializationContext"/> class. + /// </summary> + public DeserializationContext() + : this(new StreamingContext(), new FormatterConverter()) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="DeserializationContext"/> class. + /// </summary> + /// <param name="context">The streaming context to use.</param> + public DeserializationContext(StreamingContext context) + : this(context, new FormatterConverter()) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="DeserializationContext"/> class. + /// </summary> + /// <param name="formatterConverter">The formatter converter to use.</param> + public DeserializationContext(FormatterConverter formatterConverter) + : this(new StreamingContext(), formatterConverter) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="DeserializationContext"/> class. + /// </summary> + /// <param name="context">The streaming context to use.</param> + /// <param name="formatterConverter">The formatter converter to use.</param> + /// <exception cref="System.ArgumentNullException">The formatterConverter parameter is null.</exception> + public DeserializationContext(StreamingContext context, FormatterConverter formatterConverter) + { + if (formatterConverter == null) + { + throw new ArgumentNullException("formatterConverter"); + } + + this.streamingContext = context; + this.formatterConverter = formatterConverter; + + this.Reset(); + } + + /// <summary> + /// Gets or sets the context's type binder. + /// </summary> + /// <value> + /// The context's serialization binder. + /// </value> + public TwoWaySerializationBinder Binder + { + get + { + if (this.binder == null) + { + this.binder = DefaultSerializationBinder.Default; + } + + return this.binder; + } + + set + { + this.binder = value; + } + } + + /// <summary> + /// Gets or sets the string reference resolver. + /// </summary> + /// <value> + /// The string reference resolver. + /// </value> + public IExternalStringReferenceResolver StringReferenceResolver { get; set; } + + /// <summary> + /// Gets or sets the Guid reference resolver. + /// </summary> + /// <value> + /// The Guid reference resolver. + /// </value> + public IExternalGuidReferenceResolver GuidReferenceResolver { get; set; } + + /// <summary> + /// Gets or sets the index reference resolver. + /// </summary> + /// <value> + /// The index reference resolver. + /// </value> + public IExternalIndexReferenceResolver IndexReferenceResolver { get; set; } + + /// <summary> + /// Gets the streaming context. + /// </summary> + /// <value> + /// The streaming context. + /// </value> + public StreamingContext StreamingContext { get { return this.streamingContext; } } + + /// <summary> + /// Gets the formatter converter. + /// </summary> + /// <value> + /// The formatter converter. + /// </value> + public IFormatterConverter FormatterConverter { get { return this.formatterConverter; } } + + /// <summary> + /// Gets or sets the serialization configuration. + /// </summary> + /// <value> + /// The serialization configuration. + /// </value> + public SerializationConfig Config + { + get + { + if (this.config == null) + { + this.config = new SerializationConfig(); + } + + return this.config; + } + + set + { + this.config = value; + } + } + + /// <summary> + /// Registers an internal reference to a given id. + /// </summary> + /// <param name="id">The id to register the reference with.</param> + /// <param name="reference">The reference to register.</param> + public void RegisterInternalReference(int id, object reference) + { + this.internalIdReferenceMap[id] = reference; + } + + /// <summary> + /// Gets an internal reference from a given id, or null if the id has not been registered. + /// </summary> + /// <param name="id">The id of the reference to get.</param> + /// <returns>An internal reference from a given id, or null if the id has not been registered.</returns> + public object GetInternalReference(int id) + { + object result; + this.internalIdReferenceMap.TryGetValue(id, out result); + return result; + } + + /// <summary> + /// Gets an external object reference by index, or null if the index could not be resolved. + /// </summary> + /// <param name="index">The index to resolve.</param> + /// <returns>An external object reference by the given index, or null if the index could not be resolved.</returns> + public object GetExternalObject(int index) + { + if (this.IndexReferenceResolver == null) + { + this.Config.DebugContext.LogWarning("Tried to resolve external reference by index (" + index + "), but no index reference resolver is assigned to the deserialization context. External reference has been lost."); + return null; + } + + object result; + + if (this.IndexReferenceResolver.TryResolveReference(index, out result)) + { + return result; + } + + this.Config.DebugContext.LogWarning("Failed to resolve external reference by index (" + index + "); the index resolver could not resolve the index. Reference lost."); + return null; + } + + /// <summary> + /// Gets an external object reference by guid, or null if the guid could not be resolved. + /// </summary> + /// <param name="guid">The guid to resolve.</param> + /// <returns>An external object reference by the given guid, or null if the guid could not be resolved.</returns> + public object GetExternalObject(Guid guid) + { + if (this.GuidReferenceResolver == null) + { + this.Config.DebugContext.LogWarning("Tried to resolve external reference by guid (" + guid + "), but no guid reference resolver is assigned to the deserialization context. External reference has been lost."); + return null; + } + + var resolver = this.GuidReferenceResolver; + object result; + + while (resolver != null) + { + if (resolver.TryResolveReference(guid, out result)) + { + return result; + } + + resolver = resolver.NextResolver; + } + + this.Config.DebugContext.LogWarning("Failed to resolve external reference by guid (" + guid + "); no guid resolver could resolve the guid. Reference lost."); + return null; + } + + /// <summary> + /// Gets an external object reference by an id string, or null if the id string could not be resolved. + /// </summary> + /// <param name="id">The id string to resolve.</param> + /// <returns>An external object reference by an id string, or null if the id string could not be resolved.</returns> + public object GetExternalObject(string id) + { + if (this.StringReferenceResolver == null) + { + this.Config.DebugContext.LogWarning("Tried to resolve external reference by string (" + id + "), but no string reference resolver is assigned to the deserialization context. External reference has been lost."); + return null; + } + + var resolver = this.StringReferenceResolver; + object result; + + while (resolver != null) + { + if (resolver.TryResolveReference(id, out result)) + { + return result; + } + + resolver = resolver.NextResolver; + } + + this.Config.DebugContext.LogWarning("Failed to resolve external reference by string (" + id + "); no string resolver could resolve the string. Reference lost."); + return null; + } + + /// <summary> + /// Resets the deserialization context completely to baseline status, as if its constructor has just been called. + /// This allows complete reuse of a deserialization context, with all of its internal reference buffers. + /// </summary> + public void Reset() + { + if (!object.ReferenceEquals(this.config, null)) + { + this.config.ResetToDefault(); + } + + this.internalIdReferenceMap.Clear(); + this.IndexReferenceResolver = null; + this.GuidReferenceResolver = null; + this.StringReferenceResolver = null; + this.binder = null; + } + + void ICacheNotificationReceiver.OnFreed() + { + this.Reset(); + } + + void ICacheNotificationReceiver.OnClaimed() + { + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DeserializationContext.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DeserializationContext.cs.meta new file mode 100644 index 00000000..2092c1c7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/DeserializationContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c79df97337d89089be40beb2e272df0b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EmittedAssemblyAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EmittedAssemblyAttribute.cs new file mode 100644 index 00000000..ae19ce62 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EmittedAssemblyAttribute.cs @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------- +// <copyright file="EmittedAssemblyAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] + public sealed class EmittedAssemblyAttribute : Attribute + { + [Obsolete("This attribute cannot be used in code, and is only meant to be applied to dynamically emitted assemblies.", true)] + public EmittedAssemblyAttribute() { } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EmittedAssemblyAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EmittedAssemblyAttribute.cs.meta new file mode 100644 index 00000000..cc638151 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EmittedAssemblyAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae849a3e6d277006f3b4dd58a5765955 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EntryType.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EntryType.cs new file mode 100644 index 00000000..fedd5554 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EntryType.cs @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------- +// <copyright file="EntryType.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// An entry type which is part of a stream being read by a <see cref="IDataReader"/>. + /// </summary> + public enum EntryType : byte + { + /// <summary> + /// Could not parse entry. + /// </summary> + Invalid = 0, + + /// <summary> + /// Entry is a primitive value of type string or char. + /// </summary> + String = 1, + + /// <summary> + /// Entry is a primitive value of type guid. + /// </summary> + Guid = 2, + + /// <summary> + /// Entry is a primitive value of type sbyte, byte, short, ushort, int, uint, long or ulong. + /// </summary> + Integer = 3, + + /// <summary> + /// Entry is a primitive value of type float, double or decimal. + /// </summary> + FloatingPoint = 4, + + /// <summary> + /// Entry is a primitive boolean value. + /// </summary> + Boolean = 5, + + /// <summary> + /// Entry is a null value. + /// </summary> + Null = 6, + + /// <summary> + /// Entry marks the start of a node, IE, a complex type that contains values of its own. + /// </summary> + StartOfNode = 7, + + /// <summary> + /// Entry marks the end of a node, IE, a complex type that contains values of its own. + /// </summary> + EndOfNode = 8, + + /// <summary> + /// Entry contains an ID that is a reference to a node defined previously in the stream. + /// </summary> + InternalReference = 9, + + /// <summary> + /// Entry contains the index of an external object in the DeserializationContext. + /// </summary> + ExternalReferenceByIndex = 10, + + /// <summary> + /// Entry contains the guid of an external object in the DeserializationContext. + /// </summary> + ExternalReferenceByGuid = 11, + + /// <summary> + /// Entry marks the start of an array. + /// </summary> + StartOfArray = 12, + + /// <summary> + /// Entry marks the end of an array. + /// </summary> + EndOfArray = 13, + + /// <summary> + /// Entry marks a primitive array. + /// </summary> + PrimitiveArray = 14, + + /// <summary> + /// Entry indicating that the reader has reached the end of the data stream. + /// </summary> + EndOfStream = 15, + + /// <summary> + /// Entry contains the string id of an external object in the DeserializationContext. + /// </summary> + ExternalReferenceByString = 16 + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EntryType.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EntryType.cs.meta new file mode 100644 index 00000000..e591702e --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/EntryType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b06b106636f38afbb25ddd11e0c597c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ErrorHandlingPolicy.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ErrorHandlingPolicy.cs new file mode 100644 index 00000000..1ab14408 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ErrorHandlingPolicy.cs @@ -0,0 +1,23 @@ +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// The policy for handling errors during serialization and deserialization. + /// </summary> + public enum ErrorHandlingPolicy + { + /// <summary> + /// Attempts will be made to recover from errors and continue serialization. Data may become invalid. + /// </summary> + Resilient, + + /// <summary> + /// Exceptions will be thrown when errors are logged. + /// </summary> + ThrowOnErrors, + + /// <summary> + /// Exceptions will be thrown when warnings or errors are logged. + /// </summary> + ThrowOnWarningsAndErrors + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ErrorHandlingPolicy.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ErrorHandlingPolicy.cs.meta new file mode 100644 index 00000000..6bef893f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ErrorHandlingPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c73435dff291e72c0d9ce55b59c39145 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ExcludeDataFromInspectorAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ExcludeDataFromInspectorAttribute.cs new file mode 100644 index 00000000..8d7d8747 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ExcludeDataFromInspectorAttribute.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// <copyright file="ExcludeDataFromInspectorAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// <para> + /// Causes Odin's inspector to completely ignore a given member, preventing it from even being included in an Odin PropertyTree, + /// and such will not cause any performance hits in the inspector. + /// </para> + /// <para>Note that Odin can still serialize an excluded member - it is merely ignored in the inspector itself.</para> + /// </summary> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + [Obsolete("Use [HideInInspector] instead - it now also excludes the member completely from becoming a property in the property tree.", false)] + public sealed class ExcludeDataFromInspectorAttribute : Attribute + { + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ExcludeDataFromInspectorAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ExcludeDataFromInspectorAttribute.cs.meta new file mode 100644 index 00000000..466b0bf0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ExcludeDataFromInspectorAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: df06475ac5299f402ca1bdee3cf7e702 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterLocationStep.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterLocationStep.cs new file mode 100644 index 00000000..b9fea33e --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterLocationStep.cs @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------- +// <copyright file="FormatterLocationStep.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + public enum FormatterLocationStep + { + BeforeRegisteredFormatters, + AfterRegisteredFormatters + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterLocationStep.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterLocationStep.cs.meta new file mode 100644 index 00000000..6fabf5be --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterLocationStep.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 08528593c8dd764b6d928dcee6daca9f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterUtilities.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterUtilities.cs new file mode 100644 index 00000000..dbf542e7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterUtilities.cs @@ -0,0 +1,402 @@ +//----------------------------------------------------------------------- +// <copyright file="FormatterUtilities.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System.Globalization; + using VRC.Udon.Serialization.OdinSerializer.Utilities; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Runtime.Serialization; + using UnityEngine; + + /// <summary> + /// Provides an array of utility methods which are commonly used by serialization formatters. + /// </summary> + +#if UNITY_EDITOR + + [UnityEditor.InitializeOnLoad] +#endif + public static class FormatterUtilities + { + private static readonly DoubleLookupDictionary<ISerializationPolicy, Type, MemberInfo[]> MemberArrayCache = new DoubleLookupDictionary<ISerializationPolicy, Type, MemberInfo[]>(); + private static readonly DoubleLookupDictionary<ISerializationPolicy, Type, Dictionary<string, MemberInfo>> MemberMapCache = new DoubleLookupDictionary<ISerializationPolicy, Type, Dictionary<string, MemberInfo>>(); + private static readonly object LOCK = new object(); + + private static readonly HashSet<Type> PrimitiveArrayTypes = new HashSet<Type>(FastTypeComparer.Instance) + { + typeof(char), + typeof(sbyte), + typeof(short), + typeof(int), + typeof(long), + typeof(byte), + typeof(ushort), + typeof(uint), + typeof(ulong), + typeof(decimal), + typeof(bool), + typeof(float), + typeof(double), + typeof(Guid) + }; + + private static readonly FieldInfo UnityObjectRuntimeErrorStringField; + + private const string UnityObjectRuntimeErrorString = +@"The variable nullValue of {0} has not been assigned. +You probably need to assign the nullValue variable of the {0} script in the inspector."; + + static FormatterUtilities() + { + // The required field is missing in Unity builds +#if UNITY_EDITOR + UnityObjectRuntimeErrorStringField = typeof(UnityEngine.Object).GetField("m_UnityRuntimeErrorString", Flags.InstanceAnyVisibility); + + if (UnityObjectRuntimeErrorStringField == null) + { + Debug.LogWarning("A change in Unity has hindered the Serialization system's ability to create proper fake Unity null values; the UnityEngine.Object.m_UnityRuntimeErrorString field has been renamed or removed."); + } +#endif + } + + /// <summary> + /// Gets a map of all serializable members on the given type. This will also properly map names extracted from <see cref="UnityEngine.Serialization.FormerlySerializedAsAttribute"/> and <see cref="PreviouslySerializedAsAttribute"/> to their corresponding members. + /// </summary> + /// <param name="type">The type to get a map for.</param> + /// <param name="policy">The serialization policy to use. If null, <see cref="SerializationPolicies.Strict"/> is used.</param> + /// <returns>A map of all serializable members on the given type.</returns> + public static Dictionary<string, MemberInfo> GetSerializableMembersMap(Type type, ISerializationPolicy policy) + { + Dictionary<string, MemberInfo> result; + + if (policy == null) + { + policy = SerializationPolicies.Strict; + } + + lock (LOCK) + { + if (MemberMapCache.TryGetInnerValue(policy, type, out result) == false) + { + result = FindSerializableMembersMap(type, policy); + MemberMapCache.AddInner(policy, type, result); + } + } + + return result; + } + + /// <summary> + /// Gets an array of all serializable members on the given type. + /// </summary> + /// <param name="type">The type to get serializable members for.</param> + /// <param name="policy">The serialization policy to use. If null, <see cref="SerializationPolicies.Strict"/> is used.</param> + /// <returns>An array of all serializable members on the given type.</returns> + public static MemberInfo[] GetSerializableMembers(Type type, ISerializationPolicy policy) + { + MemberInfo[] result; + + if (policy == null) + { + policy = SerializationPolicies.Strict; + } + + lock (LOCK) + { + if (MemberArrayCache.TryGetInnerValue(policy, type, out result) == false) + { + List<MemberInfo> list = new List<MemberInfo>(); + FindSerializableMembers(type, list, policy); + result = list.ToArray(); + MemberArrayCache.AddInner(policy, type, result); + } + } + + return result; + } + + /// <summary> + /// Creates a fake Unity null value of a given type, for the given <see cref="UnityEngine.Object"/>-derived owning type. + /// <para /> + /// Unity uses these kinds of values to indicate missing object references. + /// </summary> + /// <param name="nullType">Type of the null value.</param> + /// <param name="owningType">Type of the owning value. This is the value which changes the <see cref="MissingReferenceException"/> which you get.</param> + /// <returns>A fake Unity null value of a given type.</returns> + /// <exception cref="System.ArgumentNullException">The nullType or owningType parameter is null.</exception> + /// <exception cref="System.ArgumentException"> + /// The type given in the nullType parameter is not a Unity object. + /// or + /// The type given in the owningType parameter is not a Unity object. + /// </exception> + public static UnityEngine.Object CreateUnityNull(Type nullType, Type owningType) + { + if (nullType == null || owningType == null) + { + throw new ArgumentNullException(); + } + + if (nullType.ImplementsOrInherits(typeof(UnityEngine.Object)) == false) + { + throw new ArgumentException("Type " + nullType.Name + " is not a Unity object."); + } + + if (owningType.ImplementsOrInherits(typeof(UnityEngine.Object)) == false) + { + throw new ArgumentException("Type " + owningType.Name + " is not a Unity object."); + } + + UnityEngine.Object nullValue = (UnityEngine.Object)FormatterServices.GetUninitializedObject(nullType); + + if (UnityObjectRuntimeErrorStringField != null) + { + UnityObjectRuntimeErrorStringField.SetValue(nullValue, string.Format(CultureInfo.InvariantCulture, UnityObjectRuntimeErrorString, owningType.Name)); + } + + return nullValue; + } + + /// <summary> + /// Determines whether a given type is a primitive type to the serialization system. + /// <para /> + /// The following criteria are checked: type.IsPrimitive or type.IsEnum, or type is a <see cref="decimal"/>, <see cref="string"/> or <see cref="Guid"/>. + /// </summary> + /// <param name="type">The type to check.</param> + /// <returns><c>true</c> if the given type is a primitive type; otherwise, <c>false</c>.</returns> + public static bool IsPrimitiveType(Type type) + { + return type.IsPrimitive + || type.IsEnum + || type == typeof(decimal) + || type == typeof(string) + || type == typeof(Guid); + } + + /// <summary> + /// Determines whether a given type is a primitive array type. Namely, arrays with primitive array types as elements are primitive arrays. + /// <para /> + /// The following types are primitive array types: <see cref="char"/>, <see cref="sbyte"/>, <see cref="short"/>, <see cref="int"/>, <see cref="long"/>, <see cref="byte"/>, <see cref="ushort"/>, <see cref="uint"/>, <see cref="ulong"/>, <see cref="decimal"/>, <see cref="bool"/>, <see cref="float"/>, <see cref="double"/> and <see cref="Guid"/>. + /// </summary> + /// <param name="type">The type to check.</param> + /// <returns><c>true</c> if the given type is a primitive array type; otherwise, <c>false</c>.</returns> + public static bool IsPrimitiveArrayType(Type type) + { + return PrimitiveArrayTypes.Contains(type); + } + + /// <summary> + /// Gets the type contained in the given <see cref="MemberInfo"/>. Currently only <see cref="FieldInfo"/> and <see cref="PropertyInfo"/> is supported. + /// </summary> + /// <param name="member">The <see cref="MemberInfo"/> to get the contained type of.</param> + /// <returns>The type contained in the given <see cref="MemberInfo"/>.</returns> + /// <exception cref="System.ArgumentException">Can't get the contained type of the given <see cref="MemberInfo"/> type.</exception> + public static Type GetContainedType(MemberInfo member) + { + if (member is FieldInfo) + { + return (member as FieldInfo).FieldType; + } + else if (member is PropertyInfo) + { + return (member as PropertyInfo).PropertyType; + } + else + { + throw new ArgumentException("Can't get the contained type of a " + member.GetType().Name); + } + } + + /// <summary> + /// Gets the value contained in a given <see cref="MemberInfo"/>. Currently only <see cref="FieldInfo"/> and <see cref="PropertyInfo"/> is supported. + /// </summary> + /// <param name="member">The <see cref="MemberInfo"/> to get the value of.</param> + /// <param name="obj">The instance to get the value from.</param> + /// <returns>The value contained in the given <see cref="MemberInfo"/>.</returns> + /// <exception cref="System.ArgumentException">Can't get the value of the given <see cref="MemberInfo"/> type.</exception> + public static object GetMemberValue(MemberInfo member, object obj) + { + if (member is FieldInfo) + { + return (member as FieldInfo).GetValue(obj); + } + else if (member is PropertyInfo) + { + return (member as PropertyInfo).GetGetMethod(true).Invoke(obj, null); + } + else + { + throw new ArgumentException("Can't get the value of a " + member.GetType().Name); + } + } + + /// <summary> + /// Sets the value of a given MemberInfo. Currently only <see cref="FieldInfo"/> and <see cref="PropertyInfo"/> is supported. + /// </summary> + /// <param name="member">The <see cref="MemberInfo"/> to set the value of.</param> + /// <param name="obj">The object to set the value on.</param> + /// <param name="value">The value to set.</param> + /// <exception cref="System.ArgumentException"> + /// Property has no setter + /// or + /// Can't set the value of the given <see cref="MemberInfo"/> type. + /// </exception> + public static void SetMemberValue(MemberInfo member, object obj, object value) + { + if (member is FieldInfo) + { + (member as FieldInfo).SetValue(obj, value); + } + else if (member is PropertyInfo) + { + var method = (member as PropertyInfo).GetSetMethod(true); + + if (method != null) + { + method.Invoke(obj, new object[] { value }); + } + else + { + throw new ArgumentException("Property " + member.Name + " has no setter"); + } + } + else + { + throw new ArgumentException("Can't set the value of a " + member.GetType().Name); + } + } + + private static Dictionary<string, MemberInfo> FindSerializableMembersMap(Type type, ISerializationPolicy policy) + { + var map = GetSerializableMembers(type, policy).ToDictionary(n => n.Name, n => n); + + foreach (var member in map.Values.ToList()) + { + var serializedAsAttributes = member.GetAttributes<UnityEngine.Serialization.FormerlySerializedAsAttribute>(); + + foreach (var attr in serializedAsAttributes) + { + if (map.ContainsKey(attr.oldName) == false) + { + map.Add(attr.oldName, member); + } + } + } + + return map; + } + + private static void FindSerializableMembers(Type type, List<MemberInfo> members, ISerializationPolicy policy) + { + const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly; + + if (type.BaseType != typeof(object) && type.BaseType != null) + { + FindSerializableMembers(type.BaseType, members, policy); + } + + foreach (var member in type.GetMembers(Flags).Where(n => n is FieldInfo || n is PropertyInfo)) + { + if (policy.ShouldSerializeMember(member)) + { + bool nameAlreadyExists = members.Any(n => n.Name == member.Name); + + if (MemberIsPrivate(member) && nameAlreadyExists) + { + members.Add(GetPrivateMemberAlias(member)); + } + else if (nameAlreadyExists) + { + members.Add(GetPrivateMemberAlias(member)); + } + else + { + members.Add(member); + } + } + } + } + + /// <summary> + /// Gets an aliased version of a member, with the declaring type name included in the member name, so that there are no conflicts with private fields and properties with the same name in different classes in the same inheritance hierarchy. + /// </summary> + public static MemberInfo GetPrivateMemberAlias(MemberInfo member, string prefixString = null, string separatorString = null) + { + if (member is FieldInfo) + { + if (separatorString != null) + { + return new MemberAliasFieldInfo(member as FieldInfo, prefixString ?? member.DeclaringType.Name, separatorString); + } + else + { + return new MemberAliasFieldInfo(member as FieldInfo, prefixString ?? member.DeclaringType.Name); + } + } + else if (member is PropertyInfo) + { + if (separatorString != null) + { + return new MemberAliasPropertyInfo(member as PropertyInfo, prefixString ?? member.DeclaringType.Name, separatorString); + } + else + { + return new MemberAliasPropertyInfo(member as PropertyInfo, prefixString ?? member.DeclaringType.Name); + } + } + else if (member is MethodInfo) + { + if (separatorString != null) + { + return new MemberAliasMethodInfo(member as MethodInfo, prefixString ?? member.DeclaringType.Name, separatorString); + } + else + { + return new MemberAliasMethodInfo(member as MethodInfo, prefixString ?? member.DeclaringType.Name); + } + } + + throw new NotImplementedException(); + } + + private static bool MemberIsPrivate(MemberInfo member) + { + if (member is FieldInfo) + { + return (member as FieldInfo).IsPrivate; + } + else if (member is PropertyInfo) + { + var prop = member as PropertyInfo; + var getter = prop.GetGetMethod(); + var setter = prop.GetSetMethod(); + + return getter != null && setter != null && getter.IsPrivate && setter.IsPrivate; + } + else if (member is MethodInfo) + { + return (member as MethodInfo).IsPrivate; + } + + throw new NotImplementedException(); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterUtilities.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterUtilities.cs.meta new file mode 100644 index 00000000..e51a784d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/FormatterUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 30194d27b77855bf09b9af809a761ca5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IAskIfCanFormatTypes.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IAskIfCanFormatTypes.cs new file mode 100644 index 00000000..7327fcd0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IAskIfCanFormatTypes.cs @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------- +// <copyright file="IAskIfCanFormatTypes.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + public interface IAskIfCanFormatTypes + { + bool CanFormatType(Type type); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IAskIfCanFormatTypes.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IAskIfCanFormatTypes.cs.meta new file mode 100644 index 00000000..441bc56f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IAskIfCanFormatTypes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 32f94aca65b8d09ddd7b3db72e08db3f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalGuidReferenceResolver.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalGuidReferenceResolver.cs new file mode 100644 index 00000000..a0190cca --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalGuidReferenceResolver.cs @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------- +// <copyright file="IExternalGuidReferenceResolver.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Resolves external guid references to reference objects during serialization and deserialization. + /// </summary> + public interface IExternalGuidReferenceResolver + { + /// <summary> + /// Gets or sets the next resolver in the chain. + /// </summary> + /// <value> + /// The next resolver in the chain. + /// </value> + IExternalGuidReferenceResolver NextResolver { get; set; } + + /// <summary> + /// Tries to resolve a reference from a given Guid. + /// </summary> + /// <param name="guid">The Guid to resolve.</param> + /// <param name="value">The resolved value.</param> + /// <returns><c>true</c> if the value was resolved; otherwise, <c>false</c>.</returns> + bool TryResolveReference(Guid guid, out object value); + + /// <summary> + /// Determines whether this resolver can reference the specified value with a Guid. + /// </summary> + /// <param name="value">The value to check.</param> + /// <param name="guid">The Guid which references the value.</param> + /// <returns><c>true</c> if the value can be referenced; otherwise, <c>false</c>.</returns> + bool CanReference(object value, out Guid guid); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalGuidReferenceResolver.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalGuidReferenceResolver.cs.meta new file mode 100644 index 00000000..0756f179 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalGuidReferenceResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7ef6b6dd5e3be66c3a66753cc7e799de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalIndexReferenceResolver.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalIndexReferenceResolver.cs new file mode 100644 index 00000000..be3794a6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalIndexReferenceResolver.cs @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------- +// <copyright file="IExternalIndexReferenceResolver.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Resolves external index references to reference objects during serialization and deserialization. + /// </summary> + public interface IExternalIndexReferenceResolver + { + /// <summary> + /// Tries to resolve the given reference index to a reference value. + /// </summary> + /// <param name="index">The index to resolve.</param> + /// <param name="value">The resolved value.</param> + /// <returns><c>true</c> if the index could be resolved to a value, otherwise <c>false</c>.</returns> + bool TryResolveReference(int index, out object value); + + /// <summary> + /// Determines whether the specified value can be referenced externally via this resolver. + /// </summary> + /// <param name="value">The value to reference.</param> + /// <param name="index">The index of the resolved value, if it can be referenced.</param> + /// <returns><c>true</c> if the reference can be resolved, otherwise <c>false</c>.</returns> + bool CanReference(object value, out int index); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalIndexReferenceResolver.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalIndexReferenceResolver.cs.meta new file mode 100644 index 00000000..c4c2be83 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalIndexReferenceResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1eaa1a505a876bebb9cad40d01989e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalStringReferenceResolver.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalStringReferenceResolver.cs new file mode 100644 index 00000000..52155eb5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalStringReferenceResolver.cs @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------- +// <copyright file="IExternalStringReferenceResolver.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Resolves external strings references to reference objects during serialization and deserialization. + /// </summary> + public interface IExternalStringReferenceResolver + { + /// <summary> + /// Gets or sets the next resolver in the chain. + /// </summary> + /// <value> + /// The next resolver in the chain. + /// </value> + IExternalStringReferenceResolver NextResolver { get; set; } + + /// <summary> + /// Tries to resolve a reference from a given Guid. + /// </summary> + /// <param name="id">The <see cref="string"/> to resolve.</param> + /// <param name="value">The resolved value.</param> + /// <returns><c>true</c> if the value was resolved; otherwise, <c>false</c>.</returns> + bool TryResolveReference(string id, out object value); + + /// <summary> + /// Determines whether this resolver can reference the specified value with a string. + /// </summary> + /// <param name="value">The value to check.</param> + /// <param name="id">The string which references the value.</param> + /// <returns><c>true</c> if the value can be referenced; otherwise, <c>false</c>.</returns> + bool CanReference(object value, out string id); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalStringReferenceResolver.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalStringReferenceResolver.cs.meta new file mode 100644 index 00000000..31d809b5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/IExternalStringReferenceResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9414cf6a3ea9a51afcf648fe9ea02bed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ILogger.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ILogger.cs new file mode 100644 index 00000000..4e340e30 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ILogger.cs @@ -0,0 +1,28 @@ +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Implements methods for logging warnings, errors and exceptions during serialization and deserialization. + /// </summary> + public interface ILogger + { + /// <summary> + /// Logs a warning. + /// </summary> + /// <param name="warning">The warning to log.</param> + void LogWarning(string warning); + + /// <summary> + /// Logs an error. + /// </summary> + /// <param name="error">The error to log.</param> + void LogError(string error); + + /// <summary> + /// Logs an exception. + /// </summary> + /// <param name="exception">The exception to log.</param> + void LogException(Exception exception); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ILogger.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ILogger.cs.meta new file mode 100644 index 00000000..1170dff0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ILogger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8bab352682356b8a2b02842520a68a11 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISelfFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISelfFormatter.cs new file mode 100644 index 00000000..edf44ca6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISelfFormatter.cs @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------- +// <copyright file="ISelfFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Specifies that a type is capable of serializing itself using an <see cref="IDataWriter"/> and an + /// <see cref="IDataReader"/>. + /// <para /> + /// The deserialized type instance will be created without a constructor call using the + /// <see cref="System.Runtime.Serialization.FormatterServices.GetUninitializedObject(System.Type)"/> + /// method if it is a reference type, otherwise it will be created using default(type). + /// <para /> + /// Use <see cref="AlwaysFormatsSelfAttribute"/> to specify that a class which implements this + /// interface should *always* format itself regardless of other formatters being specified. + /// </summary> + public interface ISelfFormatter + { + /// <summary> + /// Serializes the instance's data using the given writer. + /// </summary> + void Serialize(IDataWriter writer); + + /// <summary> + /// Deserializes data into the instance using the given reader. + /// </summary> + void Deserialize(IDataReader reader); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISelfFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISelfFormatter.cs.meta new file mode 100644 index 00000000..8f3b0980 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISelfFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 106ca47adfa52732b129015337a1c8cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISerializationPolicy.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISerializationPolicy.cs new file mode 100644 index 00000000..974a1732 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISerializationPolicy.cs @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------- +// <copyright file="ISerializationPolicy.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System.Reflection; + + /// <summary> + /// Defines which members to serialize and deserialize when there aren't any custom formatters for a type. + /// Usually, it governs the behaviour of the <see cref="FormatterEmitter"/> and <see cref="ReflectionFormatter{T}"/> classes. + /// </summary> + public interface ISerializationPolicy + { + /// <summary> + /// Gets the identifier of the policy. This can be stored in the serialization metadata, so the policy used to serialize can be recovered upon deserialization without knowing the policy ahead of time. This ID should preferably be unique. + /// </summary> + /// <value> + /// The identifier of the policy. + /// </value> + string ID { get; } + + /// <summary> + /// Gets a value indicating whether to allow non serializable types. (Types which are not decorated with <see cref="System.SerializableAttribute"/>.) + /// </summary> + /// <value> + /// <c>true</c> if serializable types are allowed; otherwise, <c>false</c>. + /// </value> + bool AllowNonSerializableTypes { get; } + + /// <summary> + /// Gets a value indicating whether a given <see cref="MemberInfo"/> should be serialized or not. + /// </summary> + /// <param name="member">The member to check.</param> + /// <returns><c>true</c> if the given member should be serialized, otherwise, <c>false</c>.</returns> + bool ShouldSerializeMember(MemberInfo member); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISerializationPolicy.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISerializationPolicy.cs.meta new file mode 100644 index 00000000..f2eea718 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ISerializationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90bcbfdc0286ca48d51fc578a1e15b8f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/LoggingPolicy.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/LoggingPolicy.cs new file mode 100644 index 00000000..63173cca --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/LoggingPolicy.cs @@ -0,0 +1,23 @@ +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// The policy for which level of logging to do during serialization and deserialization. + /// </summary> + public enum LoggingPolicy + { + /// <summary> + /// Log errors. + /// </summary> + LogErrors, + + /// <summary> + /// Log both warnings and errors. + /// </summary> + LogWarningsAndErrors, + + /// <summary> + /// Log nothing at all. Note: Some extremely severe categories of errors are logged regardless of this setting. + /// </summary> + Silent + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/LoggingPolicy.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/LoggingPolicy.cs.meta new file mode 100644 index 00000000..ab0b0e73 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/LoggingPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7de3f23805ad9d4b3d033eef45e3b59b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/NodeInfo.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/NodeInfo.cs new file mode 100644 index 00000000..4f1e3800 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/NodeInfo.cs @@ -0,0 +1,161 @@ +//----------------------------------------------------------------------- +// <copyright file="NodeInfo.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Contains information about a node during deserialization and serialization. + /// </summary> + public struct NodeInfo + { + /// <summary> + /// An empty node. + /// </summary> + public static readonly NodeInfo Empty = new NodeInfo(true); + + /// <summary> + /// The name of the node. + /// </summary> + public readonly string Name; + + /// <summary> + /// The id of the node, or -1 if the node has no id. + /// </summary> + public readonly int Id; + + /// <summary> + /// The type of the node, or null if the node has no type metadata. + /// </summary> + public readonly Type Type; + + /// <summary> + /// Whether the node is an array or not. + /// </summary> + public readonly bool IsArray; + + /// <summary> + /// Whether the node is an empty node. + /// </summary> + public readonly bool IsEmpty; + + /// <summary> + /// Initializes a new instance of the <see cref="NodeInfo"/> struct. + /// </summary> + /// <param name="name">The name of the node.</param> + /// <param name="id">The id of the node.</param> + /// <param name="type">The type of the node.</param> + /// <param name="isArray">If set to <c>true</c> the node is an array node.</param> + public NodeInfo(string name, int id, Type type, bool isArray) + { + this.Name = name; + this.Id = id; + this.Type = type; + this.IsArray = isArray; + this.IsEmpty = false; + } + + private NodeInfo(bool parameter) + { + this.Name = null; + this.Id = -1; + this.Type = null; + this.IsArray = false; + this.IsEmpty = true; + } + + /// <summary> + /// Implements the operator == between <see cref="NodeInfo"/> and <see cref="NodeInfo"/>. + /// </summary> + /// <param name="a">The first <see cref="NodeInfo"/>.</param> + /// <param name="b">The second <see cref="NodeInfo"/>.</param> + /// <returns> + /// <c>true</c> if the nodes were equal; otherwise, <c>false</c>. + /// </returns> + public static bool operator ==(NodeInfo a, NodeInfo b) + { + return a.Name == b.Name + && a.Id == b.Id + && a.Type == b.Type + && a.IsArray == b.IsArray + && a.IsEmpty == b.IsEmpty; + } + + /// <summary> + /// Implements the operator != between <see cref="NodeInfo"/> and <see cref="NodeInfo"/>. + /// </summary> + /// <param name="a">The first <see cref="NodeInfo"/>.</param> + /// <param name="b">The second <see cref="NodeInfo"/>.</param> + /// <returns> + /// <c>true</c> if the nodes were not equal; otherwise, <c>false</c>. + /// </returns> + public static bool operator !=(NodeInfo a, NodeInfo b) + { + return !(a == b); + } + + /// <summary> + /// Determines whether the specified <see cref="System.Object" />, is equal to this instance. + /// </summary> + /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param> + /// <returns> + /// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>. + /// </returns> + public override bool Equals(object obj) + { + if (object.ReferenceEquals(obj, null)) + { + return false; + } + + if (obj is NodeInfo) + { + return (NodeInfo)obj == this; + } + + return false; + } + + /// <summary> + /// Returns a hash code for this instance. + /// </summary> + /// <returns> + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// </returns> + public override int GetHashCode() + { + if (this.IsEmpty) + { + return 0; + } + + const int P = 16777619; + + unchecked + { + return (int)2166136261 + ^ ((this.Name == null ? 12321 : this.Name.GetHashCode()) * P) + ^ (this.Id * P) + ^ ((this.Type == null ? 1423 : this.Type.GetHashCode()) * P) + ^ ((this.IsArray ? 124124 : 43234) * P) + ^ ((this.IsEmpty ? 872934 : 27323) * P); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/NodeInfo.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/NodeInfo.cs.meta new file mode 100644 index 00000000..9cb7819a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/NodeInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10eb7be2b7c363367c46bc5699a361a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/OdinSerializeAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/OdinSerializeAttribute.cs new file mode 100644 index 00000000..58ead1d4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/OdinSerializeAttribute.cs @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------- +// <copyright file="OdinSerializeAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Indicates that an instance field or auto-property should be serialized by Odin. + /// </summary> + /// <seealso cref="System.Attribute" /> + [JetBrains.Annotations.MeansImplicitUse] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class OdinSerializeAttribute : Attribute + { + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/OdinSerializeAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/OdinSerializeAttribute.cs.meta new file mode 100644 index 00000000..98eaabb7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/OdinSerializeAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 766bbafe64ad16f63af4b81eb430e380 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModification.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModification.cs new file mode 100644 index 00000000..72685e90 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModification.cs @@ -0,0 +1,656 @@ +//----------------------------------------------------------------------- +// <copyright file="PrefabModification.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Utilities; + + /// <summary> + /// An Odin-serialized prefab modification, containing all the information necessary to apply the modification. + /// </summary> + public sealed class PrefabModification + { + /// <summary> + /// The type of modification to be made. + /// </summary> + public PrefabModificationType ModificationType; + + /// <summary> + /// The deep reflection path at which to make the modification. + /// </summary> + public string Path; + + /// <summary> + /// A list of all deep reflection paths in the target object where the value referenced by this modification was also located. + /// </summary> + public List<string> ReferencePaths; + + /// <summary> + /// The modified value to set. + /// </summary> + public object ModifiedValue; + + /// <summary> + /// The new list length to set. + /// </summary> + public int NewLength; + + /// <summary> + /// The dictionary keys to add. + /// </summary> + public object[] DictionaryKeysAdded; + + /// <summary> + /// The dictionary keys to remove. + /// </summary> + public object[] DictionaryKeysRemoved; + + /// <summary> + /// Applies the modification to the given Object. + /// </summary> + public void Apply(UnityEngine.Object unityObject) + { + if (this.ModificationType == PrefabModificationType.Value) + { + this.ApplyValue(unityObject); + } + else if (this.ModificationType == PrefabModificationType.ListLength) + { + this.ApplyListLength(unityObject); + } + else if (this.ModificationType == PrefabModificationType.Dictionary) + { + this.ApplyDictionaryModifications(unityObject); + } + else + { + throw new NotImplementedException(this.ModificationType.ToString()); + } + } + + private void ApplyValue(UnityEngine.Object unityObject) + { + Type valueType = null; + + if (!object.ReferenceEquals(this.ModifiedValue, null)) + { + valueType = this.ModifiedValue.GetType(); + } + + if (valueType != null && this.ReferencePaths != null && this.ReferencePaths.Count > 0) + { + for (int i = 0; i < this.ReferencePaths.Count; i++) + { + var path = this.ReferencePaths[i]; + + try + { + var refValue = GetInstanceFromPath(path, unityObject); + + if (!object.ReferenceEquals(refValue, null) && refValue.GetType() == valueType) + { + this.ModifiedValue = refValue; + break; + } + } + catch (Exception) { } + } + } + + SetInstanceToPath(this.Path, unityObject, this.ModifiedValue); + } + + private void ApplyListLength(UnityEngine.Object unityObject) + { + object listObj = GetInstanceFromPath(this.Path, unityObject); + + if (listObj == null) + { + // The list has been deleted on the prefab; + // that supersedes our length change. + return; + } + + Type listType = listObj.GetType(); + + if (listType.IsArray) + { + Array array = (Array)listObj; + + if (this.NewLength == array.Length) + { + // If this happens, for some weird reason, then we can actually just not do anything + return; + } + + // We actually need to replace all references to this array in the entire object graph! + // Ridiculous, we know - but there's no choice... + + // Let's create a new, modified array + Array newArray = Array.CreateInstance(listType.GetElementType(), this.NewLength); + + if (this.NewLength > array.Length) + { + Array.Copy(array, 0, newArray, 0, array.Length); + ReplaceAllReferencesInGraph(unityObject, array, newArray); + } + else + { + Array.Copy(array, 0, newArray, 0, newArray.Length); + ReplaceAllReferencesInGraph(unityObject, array, newArray); + } + } + else if (typeof(IList).IsAssignableFrom(listType)) + { + IList list = (IList)listObj; + Type listElementType = listType.ImplementsOpenGenericInterface(typeof(IList<>)) ? listType.GetArgumentsOfInheritedOpenGenericInterface(typeof(IList<>))[0] : null; + bool elementIsValueType = listElementType != null ? listElementType.IsValueType : false; + + int count = 0; + + while (list.Count < this.NewLength) + { + if (elementIsValueType) + { + list.Add(Activator.CreateInstance(listElementType)); + } + else + { + list.Add(null); + } + + count++; + } + + while (list.Count > this.NewLength) + { + list.RemoveAt(list.Count - 1); + } + } + else if (listType.ImplementsOpenGenericInterface(typeof(IList<>))) + { + Type elementType = listType.GetArgumentsOfInheritedOpenGenericInterface(typeof(IList<>))[0]; + Type collectionType = typeof(ICollection<>).MakeGenericType(elementType); + bool elementIsValueType = elementType.IsValueType; + + PropertyInfo countProp = collectionType.GetProperty("Count"); + + int count = (int)countProp.GetValue(listObj, null); + + if (count < this.NewLength) + { + int add = this.NewLength - count; + + MethodInfo addMethod = collectionType.GetMethod("Add"); + + for (int i = 0; i < add; i++) + { + if (elementIsValueType) + { + addMethod.Invoke(listObj, new object[] { Activator.CreateInstance(elementType) }); + } + else + { + addMethod.Invoke(listObj, new object[] { null }); + } + count++; + } + } + else if (count > this.NewLength) + { + int remove = count - this.NewLength; + + Type listInterfaceType = typeof(IList<>).MakeGenericType(elementType); + MethodInfo removeAtMethod = listInterfaceType.GetMethod("RemoveAt"); + + for (int i = 0; i < remove; i++) + { + removeAtMethod.Invoke(listObj, new object[] { count - (remove + 1) }); + } + } + } + } + + private void ApplyDictionaryModifications(UnityEngine.Object unityObject) + { + object dictionaryObj = GetInstanceFromPath(this.Path, unityObject); + + if (dictionaryObj == null) + { + // The dictionary has been deleted on the prefab; + // that supersedes our dictionary modifications. + return; + } + + var type = dictionaryObj.GetType(); + + if (!type.ImplementsOpenGenericInterface(typeof(IDictionary<,>))) + { + // A value change has changed the target modified value to + // not be a dictionary - that also supersedes this modification. + return; + } + + var typeArgs = type.GetArgumentsOfInheritedOpenGenericInterface(typeof(IDictionary<,>)); + + var iType = typeof(IDictionary<,>).MakeGenericType(typeArgs); + + // + // First, remove keys + // + + if (this.DictionaryKeysRemoved != null && this.DictionaryKeysRemoved.Length > 0) + { + MethodInfo method = iType.GetMethod("Remove", new Type[] { typeArgs[0] }); + object[] parameters = new object[1]; + + for (int i = 0; i < this.DictionaryKeysRemoved.Length; i++) + { + parameters[0] = this.DictionaryKeysRemoved[i]; + + // Ensure the key value is safe to add + if (object.ReferenceEquals(parameters[0], null) || !typeArgs[0].IsAssignableFrom(parameters[0].GetType())) + continue; + + method.Invoke(dictionaryObj, parameters); + } + } + + // + // Then, add keys + // + + if (this.DictionaryKeysAdded != null && this.DictionaryKeysAdded.Length > 0) + { + MethodInfo method = iType.GetMethod("set_Item", typeArgs); + object[] parameters = new object[2]; + + // Get default value to set key to + parameters[1] = typeArgs[1].IsValueType ? Activator.CreateInstance(typeArgs[1]) : null; + + for (int i = 0; i < this.DictionaryKeysAdded.Length; i++) + { + parameters[0] = this.DictionaryKeysAdded[i]; + + // Ensure the key value is safe to add + if (object.ReferenceEquals(parameters[0], null) || !typeArgs[0].IsAssignableFrom(parameters[0].GetType())) + continue; + + method.Invoke(dictionaryObj, parameters); + } + } + } + + private static void ReplaceAllReferencesInGraph(object graph, object oldReference, object newReference, HashSet<object> processedReferences = null) + { + if (processedReferences == null) + { + processedReferences = new HashSet<object>(ReferenceEqualityComparer<object>.Default); + } + + processedReferences.Add(graph); + + if (graph.GetType().IsArray) + { + Array array = (Array)graph; + + for (int i = 0; i < array.Length; i++) + { + var value = array.GetValue(i); + + if (object.ReferenceEquals(value, null)) + { + continue; + } + + if (object.ReferenceEquals(value, oldReference)) + { + array.SetValue(newReference, i); + value = newReference; + } + + if (!processedReferences.Contains(value)) + { + ReplaceAllReferencesInGraph(value, oldReference, newReference, processedReferences); + } + } + } + else + { + var members = FormatterUtilities.GetSerializableMembers(graph.GetType(), SerializationPolicies.Everything); + + for (int i = 0; i < members.Length; i++) + { + FieldInfo field = (FieldInfo)members[i]; + + if (field.FieldType.IsPrimitive || field.FieldType == typeof(SerializationData) || field.FieldType == typeof(string)) + continue; + + object value = field.GetValue(graph); + + if (object.ReferenceEquals(value, null)) + { + continue; + } + + Type valueType = value.GetType(); + + if (valueType.IsPrimitive || valueType == typeof(SerializationData) || valueType == typeof(string)) + continue; + + if (object.ReferenceEquals(value, oldReference)) + { + field.SetValue(graph, newReference); + value = newReference; + } + + if (!processedReferences.Contains(value)) + { + ReplaceAllReferencesInGraph(value, oldReference, newReference, processedReferences); + } + } + } + } + + private static object GetInstanceFromPath(string path, object instance) + { + string[] steps = path.Split('.'); + + object currentInstance = instance; + + for (int i = 0; i < steps.Length; i++) + { + currentInstance = GetInstanceOfStep(steps[i], currentInstance); + + if (object.ReferenceEquals(currentInstance, null)) + { + //Debug.LogWarning("Failed to resolve modification path '" + path + "' at step '" + steps[i] + "'."); + return null; + } + } + + return currentInstance; + } + + private static object GetInstanceOfStep(string step, object instance) + { + Type type = instance.GetType(); + + if (step.StartsWith("[", StringComparison.InvariantCulture) && step.EndsWith("]", StringComparison.InvariantCulture)) + { + int index; + string indexStr = step.Substring(1, step.Length - 2); + + if (!int.TryParse(indexStr, out index)) + { + throw new ArgumentException("Couldn't parse an index from the path step '" + step + "'."); + } + + // We need to check the current type to see if we can treat it as a list + + if (type.IsArray) + { + Array array = (Array)instance; + + if (index < 0 || index >= array.Length) + { + return null; + } + + return array.GetValue(index); + } + else if (typeof(IList).IsAssignableFrom(type)) + { + IList list = (IList)instance; + + if (index < 0 || index >= list.Count) + { + return null; + } + + return list[index]; + } + else if (type.ImplementsOpenGenericInterface(typeof(IList<>))) + { + Type elementType = type.GetArgumentsOfInheritedOpenGenericInterface(typeof(IList<>))[0]; + Type listType = typeof(IList<>).MakeGenericType(elementType); + MethodInfo getItemMethod = listType.GetMethod("get_Item", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + try + { + return getItemMethod.Invoke(instance, new object[] { index }); + } + catch (Exception) + { + return null; + } + } + } + else if (step.StartsWith("{", StringComparison.InvariantCultureIgnoreCase) && step.EndsWith("}", StringComparison.InvariantCultureIgnoreCase)) + { + if (type.ImplementsOpenGenericInterface(typeof(IDictionary<,>))) + { + var dictArgs = type.GetArgumentsOfInheritedOpenGenericInterface(typeof(IDictionary<,>)); + + object key = DictionaryKeyUtility.GetDictionaryKeyValue(step, dictArgs[0]); + + Type dictType = typeof(IDictionary<,>).MakeGenericType(dictArgs); + MethodInfo getItemMethod = dictType.GetMethod("get_Item", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + try + { + return getItemMethod.Invoke(instance, new object[] { key }); + } + catch (Exception) + { + return null; + } + } + } + else + { + string privateTypeName = null; + int plusIndex = step.IndexOf('+'); + + if (plusIndex >= 0) + { + privateTypeName = step.Substring(0, plusIndex); + step = step.Substring(plusIndex + 1); + } + + var possibleMembers = type.GetAllMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(n => n is FieldInfo || n is PropertyInfo); + + foreach (var member in possibleMembers) + { + if (member.Name == step) + { + if (privateTypeName != null && member.DeclaringType.Name != privateTypeName) + { + continue; + } + + return member.GetMemberValue(instance); + } + } + } + + return null; + } + + private static void SetInstanceToPath(string path, object instance, object value) + { + bool setParentInstance; + string[] steps = path.Split('.'); + SetInstanceToPath(path, steps, 0, instance, value, out setParentInstance); + } + + private static void SetInstanceToPath(string path, string[] steps, int index, object instance, object value, out bool setParentInstance) + { + setParentInstance = false; + + if (index < steps.Length - 1) + { + object currentInstance = GetInstanceOfStep(steps[index], instance); + + if (object.ReferenceEquals(currentInstance, null)) + { + //Debug.LogWarning("Failed to resolve prefab modification path '" + path + "' at step '" + steps[index] + "'."); + return; + } + + SetInstanceToPath(path, steps, index + 1, currentInstance, value, out setParentInstance); + + if (setParentInstance) + { + // We need to set the current instance to the parent instance member, + // because the current instance is a value type, and thus it may have + // been boxed. If we don't do this, the value set might be lost. + TrySetInstanceOfStep(steps[index], instance, currentInstance, out setParentInstance); + } + } + else + { + TrySetInstanceOfStep(steps[index], instance, value, out setParentInstance); + } + } + + private static bool TrySetInstanceOfStep(string step, object instance, object value, out bool setParentInstance) + { + setParentInstance = false; + + try + { + Type type = instance.GetType(); + + if (step.StartsWith("[", StringComparison.InvariantCulture) && step.EndsWith("]", StringComparison.InvariantCulture)) + { + int index; + string indexStr = step.Substring(1, step.Length - 2); + + if (!int.TryParse(indexStr, out index)) + { + throw new ArgumentException("Couldn't parse an index from the path step '" + step + "'."); + } + + // We need to check the current type to see if we can treat it as a list + + if (type.IsArray) + { + Array array = (Array)instance; + + if (index < 0 || index >= array.Length) + { + return false; + } + + array.SetValue(value, index); + return true; + } + else if (typeof(IList).IsAssignableFrom(type)) + { + IList list = (IList)instance; + + if (index < 0 || index >= list.Count) + { + return false; + } + + list[index] = value; + return true; + } + else if (type.ImplementsOpenGenericInterface(typeof(IList<>))) + { + Type elementType = type.GetArgumentsOfInheritedOpenGenericInterface(typeof(IList<>))[0]; + Type listType = typeof(IList<>).MakeGenericType(elementType); + MethodInfo setItemMethod = listType.GetMethod("set_Item", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + setItemMethod.Invoke(instance, new object[] { index, value }); + return true; + } + } + else if (step.StartsWith("{", StringComparison.InvariantCulture) && step.EndsWith("}", StringComparison.InvariantCulture)) + { + if (type.ImplementsOpenGenericInterface(typeof(IDictionary<,>))) + { + var dictArgs = type.GetArgumentsOfInheritedOpenGenericInterface(typeof(IDictionary<,>)); + + object key = DictionaryKeyUtility.GetDictionaryKeyValue(step, dictArgs[0]); + + Type dictType = typeof(IDictionary<,>).MakeGenericType(dictArgs); + + MethodInfo containsKeyMethod = dictType.GetMethod("ContainsKey", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + MethodInfo setItemMethod = dictType.GetMethod("set_Item", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + bool containsKey = (bool)containsKeyMethod.Invoke(instance, new object[] { key }); + + if (!containsKey) + { + // We are *not* allowed to add new keys during this step + return false; + } + + setItemMethod.Invoke(instance, new object[] { key, value }); + } + } + else + { + string privateTypeName = null; + int plusIndex = step.IndexOf('+'); + + if (plusIndex >= 0) + { + privateTypeName = step.Substring(0, plusIndex); + step = step.Substring(plusIndex + 1); + } + + var possibleMembers = type.GetAllMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(n => n is FieldInfo || n is PropertyInfo); + + foreach (var member in possibleMembers) + { + if (member.Name == step) + { + if (privateTypeName != null && member.DeclaringType.Name != privateTypeName) + { + continue; + } + + member.SetMemberValue(instance, value); + + if (instance.GetType().IsValueType) + { + setParentInstance = true; + } + + return true; + } + } + } + + return false; + } + catch (Exception) + { + return false; + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModification.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModification.cs.meta new file mode 100644 index 00000000..8aca142c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModification.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3db8c00661ec222984427ab12295940f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModificationType.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModificationType.cs new file mode 100644 index 00000000..0816b494 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModificationType.cs @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------- +// <copyright file="PrefabModificationType.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Types of prefab modification that can be applied. + /// </summary> + public enum PrefabModificationType + { + /// <summary> + /// A value has been changed at a given path. + /// </summary> + Value, + + /// <summary> + /// A list length has been changed at a given path. + /// </summary> + ListLength, + + /// <summary> + /// A dictionary has been changed at a given path. + /// </summary> + Dictionary + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModificationType.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModificationType.cs.meta new file mode 100644 index 00000000..f4a368f7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PrefabModificationType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 23ceed712f987034deb8e92c561a1d3b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PreviouslySerializedAsAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PreviouslySerializedAsAttribute.cs new file mode 100644 index 00000000..7b7e4be7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PreviouslySerializedAsAttribute.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// <copyright file="PreviouslySerializedAsAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Indicates that an instance field or auto-property was previously serialized with a different name, so that values serialized with the old name will be properly deserialized into this member. + /// + /// This does the same as Unity's FormerlySerializedAs attribute, except it can also be applied to properties. + /// </summary> + /// <seealso cref="System.Attribute" /> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class PreviouslySerializedAsAttribute : Attribute + { + /// <summary> + /// The former name. + /// </summary> + public string Name { get; private set; } + + /// <summary> + /// Initializes a new instance of the <see cref="PreviouslySerializedAsAttribute"/> class. + /// </summary> + /// <param name="name">The former name.</param> + public PreviouslySerializedAsAttribute(string name) + { + this.Name = name; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PreviouslySerializedAsAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PreviouslySerializedAsAttribute.cs.meta new file mode 100644 index 00000000..0df4d245 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/PreviouslySerializedAsAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 96fec6c04f13e378def42ea5ad5dc940 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ProperBitConverter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ProperBitConverter.cs new file mode 100644 index 00000000..c5c3b313 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ProperBitConverter.cs @@ -0,0 +1,927 @@ +//----------------------------------------------------------------------- +// <copyright file="ProperBitConverter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +// Tested and verified to work +#pragma warning disable 0675 + +using System.Globalization; + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Runtime.InteropServices; + + /// <summary> + /// Corresponds to the .NET <see cref="BitConverter"/> class, but works only with buffers and so never allocates garbage. + /// <para /> + /// This class always writes and reads bytes in a little endian format, regardless of system architecture. + /// </summary> + public static class ProperBitConverter + { + private static readonly uint[] ByteToHexCharLookupLowerCase = CreateByteToHexLookup(false); + private static readonly uint[] ByteToHexCharLookupUpperCase = CreateByteToHexLookup(true); + + // 16x16 table, set up for direct visual correlation to Unicode table with hex coords + private static readonly byte[] HexToByteLookup = new byte[] { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + private static uint[] CreateByteToHexLookup(bool upperCase) + { + var result = new uint[256]; + + if (upperCase) + { + for (int i = 0; i < 256; i++) + { + string s = i.ToString("X2", CultureInfo.InvariantCulture); + result[i] = ((uint)s[0]) + ((uint)s[1] << 16); + } + } + else + { + for (int i = 0; i < 256; i++) + { + string s = i.ToString("x2", CultureInfo.InvariantCulture); + result[i] = ((uint)s[0]) + ((uint)s[1] << 16); + } + } + + return result; + } + + /// <summary> + /// Converts a byte array into a hexadecimal string. + /// </summary> + public static string BytesToHexString(byte[] bytes, bool lowerCaseHexChars = true) + { + var lookup = lowerCaseHexChars ? ByteToHexCharLookupLowerCase : ByteToHexCharLookupUpperCase; + var result = new char[bytes.Length * 2]; + + for (int i = 0; i < bytes.Length; i++) + { + int offset = i * 2; + var val = lookup[bytes[i]]; + result[offset] = (char)val; + result[offset + 1] = (char)(val >> 16); + } + + return new string(result); + } + + /// <summary> + /// Converts a hexadecimal string into a byte array. + /// </summary> + public static byte[] HexStringToBytes(string hex) + { + int length = hex.Length; + int rLength = length / 2; + + if (length % 2 != 0) + { + throw new ArgumentException("Hex string must have an even length."); + } + + byte[] result = new byte[rLength]; + + for (int i = 0; i < rLength; i++) + { + int offset = i * 2; + + byte b1; + byte b2; + + try + { + b1 = HexToByteLookup[hex[offset]]; + + if (b1 == 0xff) + { + throw new ArgumentException("Expected a hex character, got '" + hex[offset] + "' at string index '" + offset + "'."); + } + } + catch (IndexOutOfRangeException) + { + throw new ArgumentException("Expected a hex character, got '" + hex[offset] + "' at string index '" + offset + "'."); + } + + try + { + b2 = HexToByteLookup[hex[offset + 1]]; + + if (b2 == 0xff) + { + throw new ArgumentException("Expected a hex character, got '" + hex[offset + 1] + "' at string index '" + (offset + 1) + "'."); + } + } + catch (IndexOutOfRangeException) + { + throw new ArgumentException("Expected a hex character, got '" + hex[offset + 1] + "' at string index '" + (offset + 1) + "'."); + } + + result[i] = (byte)(b1 << 4 | b2); + } + + return result; + } + + /// <summary> + /// Reads two bytes from a buffer and converts them into a <see cref="short"/> value. + /// </summary> + /// <param name="buffer">The buffer to read from.</param> + /// <param name="index">The index to start reading at.</param> + /// <returns>The converted value.</returns> + public static short ToInt16(byte[] buffer, int index) + { + short value = default(short); + + value |= buffer[index + 1]; + value <<= 8; + value |= buffer[index]; + + return value; + } + + /// <summary> + /// Reads two bytes from a buffer and converts them into a <see cref="ushort"/> value. + /// </summary> + /// <param name="buffer">The buffer to read from.</param> + /// <param name="index">The index to start reading at.</param> + /// <returns>The converted value.</returns> + public static ushort ToUInt16(byte[] buffer, int index) + { + ushort value = default(ushort); + + value |= buffer[index + 1]; + value <<= 8; + value |= buffer[index]; + + return value; + } + + /// <summary> + /// Reads four bytes from a buffer and converts them into an <see cref="int"/> value. + /// </summary> + /// <param name="buffer">The buffer to read from.</param> + /// <param name="index">The index to start reading at.</param> + /// <returns>The converted value.</returns> + public static int ToInt32(byte[] buffer, int index) + { + int value = default(int); + + value |= buffer[index + 3]; + value <<= 8; + value |= buffer[index + 2]; + value <<= 8; + value |= buffer[index + 1]; + value <<= 8; + value |= buffer[index]; + + return value; + } + + /// <summary> + /// Reads four bytes from a buffer and converts them into an <see cref="uint"/> value. + /// </summary> + /// <param name="buffer">The buffer to read from.</param> + /// <param name="index">The index to start reading at.</param> + /// <returns>The converted value.</returns> + public static uint ToUInt32(byte[] buffer, int index) + { + uint value = default(uint); + + value |= buffer[index + 3]; + value <<= 8; + value |= buffer[index + 2]; + value <<= 8; + value |= buffer[index + 1]; + value <<= 8; + value |= buffer[index]; + + return value; + } + + /// <summary> + /// Reads eight bytes from a buffer and converts them into a <see cref="long"/> value. + /// </summary> + /// <param name="buffer">The buffer to read from.</param> + /// <param name="index">The index to start reading at.</param> + /// <returns>The converted value.</returns> + public static long ToInt64(byte[] buffer, int index) + { + long value = default(long); + + value |= buffer[index + 7]; + value <<= 8; + value |= buffer[index + 6]; + value <<= 8; + value |= buffer[index + 5]; + value <<= 8; + value |= buffer[index + 4]; + value <<= 8; + value |= buffer[index + 3]; + value <<= 8; + value |= buffer[index + 2]; + value <<= 8; + value |= buffer[index + 1]; + value <<= 8; + value |= buffer[index]; + + return value; + } + + /// <summary> + /// Reads eight bytes from a buffer and converts them into an <see cref="ulong"/> value. + /// </summary> + /// <param name="buffer">The buffer to read from.</param> + /// <param name="index">The index to start reading at.</param> + /// <returns>The converted value.</returns> + public static ulong ToUInt64(byte[] buffer, int index) + { + ulong value = default(ulong); + + value |= buffer[index + 7]; + value <<= 8; + value |= buffer[index + 6]; + value <<= 8; + value |= buffer[index + 5]; + value <<= 8; + value |= buffer[index + 4]; + value <<= 8; + value |= buffer[index + 3]; + value <<= 8; + value |= buffer[index + 2]; + value <<= 8; + value |= buffer[index + 1]; + value <<= 8; + value |= buffer[index]; + + return value; + } + + /// <summary> + /// Reads four bytes from a buffer and converts them into an <see cref="float"/> value. + /// </summary> + /// <param name="buffer">The buffer to read from.</param> + /// <param name="index">The index to start reading at.</param> + /// <returns>The converted value.</returns> + public static float ToSingle(byte[] buffer, int index) + { + var union = default(SingleByteUnion); + + if (BitConverter.IsLittleEndian) + { + union.Byte0 = buffer[index]; + union.Byte1 = buffer[index + 1]; + union.Byte2 = buffer[index + 2]; + union.Byte3 = buffer[index + 3]; + } + else + { + union.Byte3 = buffer[index]; + union.Byte2 = buffer[index + 1]; + union.Byte1 = buffer[index + 2]; + union.Byte0 = buffer[index + 3]; + } + + return union.Value; + } + + /// <summary> + /// Reads eight bytes from a buffer and converts them into an <see cref="double"/> value. + /// </summary> + /// <param name="buffer">The buffer to read from.</param> + /// <param name="index">The index to start reading at.</param> + /// <returns>The converted value.</returns> + public static double ToDouble(byte[] buffer, int index) + { + var union = default(DoubleByteUnion); + + if (BitConverter.IsLittleEndian) + { + union.Byte0 = buffer[index]; + union.Byte1 = buffer[index + 1]; + union.Byte2 = buffer[index + 2]; + union.Byte3 = buffer[index + 3]; + union.Byte4 = buffer[index + 4]; + union.Byte5 = buffer[index + 5]; + union.Byte6 = buffer[index + 6]; + union.Byte7 = buffer[index + 7]; + } + else + { + union.Byte7 = buffer[index]; + union.Byte6 = buffer[index + 1]; + union.Byte5 = buffer[index + 2]; + union.Byte4 = buffer[index + 3]; + union.Byte3 = buffer[index + 4]; + union.Byte2 = buffer[index + 5]; + union.Byte1 = buffer[index + 6]; + union.Byte0 = buffer[index + 7]; + } + + return union.Value; + } + + /// <summary> + /// Reads sixteen bytes from a buffer and converts them into a <see cref="decimal"/> value. + /// </summary> + /// <param name="buffer">The buffer to read from.</param> + /// <param name="index">The index to start reading at.</param> + /// <returns>The converted value.</returns> + public static decimal ToDecimal(byte[] buffer, int index) + { + var union = default(DecimalByteUnion); + + if (BitConverter.IsLittleEndian) + { + union.Byte0 = buffer[index]; + union.Byte1 = buffer[index + 1]; + union.Byte2 = buffer[index + 2]; + union.Byte3 = buffer[index + 3]; + union.Byte4 = buffer[index + 4]; + union.Byte5 = buffer[index + 5]; + union.Byte6 = buffer[index + 6]; + union.Byte7 = buffer[index + 7]; + union.Byte8 = buffer[index + 8]; + union.Byte9 = buffer[index + 9]; + union.Byte10 = buffer[index + 10]; + union.Byte11 = buffer[index + 11]; + union.Byte12 = buffer[index + 12]; + union.Byte13 = buffer[index + 13]; + union.Byte14 = buffer[index + 14]; + union.Byte15 = buffer[index + 15]; + } + else + { + union.Byte15 = buffer[index]; + union.Byte14 = buffer[index + 1]; + union.Byte13 = buffer[index + 2]; + union.Byte12 = buffer[index + 3]; + union.Byte11 = buffer[index + 4]; + union.Byte10 = buffer[index + 5]; + union.Byte9 = buffer[index + 6]; + union.Byte8 = buffer[index + 7]; + union.Byte7 = buffer[index + 8]; + union.Byte6 = buffer[index + 9]; + union.Byte5 = buffer[index + 10]; + union.Byte4 = buffer[index + 11]; + union.Byte3 = buffer[index + 12]; + union.Byte2 = buffer[index + 13]; + union.Byte1 = buffer[index + 14]; + union.Byte0 = buffer[index + 15]; + } + + return union.Value; + } + + /// <summary> + /// Reads sixteen bytes from a buffer and converts them into a <see cref="Guid"/> value. + /// </summary> + /// <param name="buffer">The buffer to read from.</param> + /// <param name="index">The index to start reading at.</param> + /// <returns>The converted value.</returns> + public static Guid ToGuid(byte[] buffer, int index) + { + var union = default(GuidByteUnion); + + // First 10 bytes of a guid are always little endian + // Last 6 bytes depend on architecture endianness + // See http://stackoverflow.com/questions/10190817/guid-byte-order-in- + union.Byte0 = buffer[index]; + union.Byte1 = buffer[index + 1]; + union.Byte2 = buffer[index + 2]; + union.Byte3 = buffer[index + 3]; + union.Byte4 = buffer[index + 4]; + union.Byte5 = buffer[index + 5]; + union.Byte6 = buffer[index + 6]; + union.Byte7 = buffer[index + 7]; + union.Byte8 = buffer[index + 8]; + union.Byte9 = buffer[index + 9]; + + if (BitConverter.IsLittleEndian) + { + union.Byte10 = buffer[index + 10]; + union.Byte11 = buffer[index + 11]; + union.Byte12 = buffer[index + 12]; + union.Byte13 = buffer[index + 13]; + union.Byte14 = buffer[index + 14]; + union.Byte15 = buffer[index + 15]; + } + else + { + union.Byte15 = buffer[index + 10]; + union.Byte14 = buffer[index + 11]; + union.Byte13 = buffer[index + 12]; + union.Byte12 = buffer[index + 13]; + union.Byte11 = buffer[index + 14]; + union.Byte10 = buffer[index + 15]; + } + + return union.Value; + } + + /// <summary> + /// Turns a <see cref="short"/> value into two bytes and writes those bytes to a given buffer. + /// </summary> + /// <param name="buffer">The buffer to write to.</param> + /// <param name="index">The index to start writing at.</param> + /// <param name="value">The value to write.</param> + public static void GetBytes(byte[] buffer, int index, short value) + { + if (BitConverter.IsLittleEndian) + { + buffer[index] = (byte)value; + buffer[index + 1] = (byte)(value >> 8); + } + else + { + buffer[index] = (byte)(value >> 8); + buffer[index + 1] = (byte)value; + } + } + + /// <summary> + /// Turns an <see cref="ushort"/> value into two bytes and writes those bytes to a given buffer. + /// </summary> + /// <param name="buffer">The buffer to write to.</param> + /// <param name="index">The index to start writing at.</param> + /// <param name="value">The value to write.</param> + public static void GetBytes(byte[] buffer, int index, ushort value) + { + if (BitConverter.IsLittleEndian) + { + buffer[index] = (byte)value; + buffer[index + 1] = (byte)(value >> 8); + } + else + { + buffer[index] = (byte)(value >> 8); + buffer[index + 1] = (byte)value; + } + } + + /// <summary> + /// Turns an <see cref="int"/> value into four bytes and writes those bytes to a given buffer. + /// </summary> + /// <param name="buffer">The buffer to write to.</param> + /// <param name="index">The index to start writing at.</param> + /// <param name="value">The value to write.</param> + public static void GetBytes(byte[] buffer, int index, int value) + { + if (BitConverter.IsLittleEndian) + { + buffer[index] = (byte)value; + buffer[index + 1] = (byte)(value >> 8); + buffer[index + 2] = (byte)(value >> 16); + buffer[index + 3] = (byte)(value >> 24); + } + else + { + buffer[index] = (byte)(value >> 24); + buffer[index + 1] = (byte)(value >> 16); + buffer[index + 2] = (byte)(value >> 8); + buffer[index + 3] = (byte)value; + } + } + + /// <summary> + /// Turns an <see cref="uint"/> value into four bytes and writes those bytes to a given buffer. + /// </summary> + /// <param name="buffer">The buffer to write to.</param> + /// <param name="index">The index to start writing at.</param> + /// <param name="value">The value to write.</param> + public static void GetBytes(byte[] buffer, int index, uint value) + { + if (BitConverter.IsLittleEndian) + { + buffer[index] = (byte)value; + buffer[index + 1] = (byte)(value >> 8); + buffer[index + 2] = (byte)(value >> 16); + buffer[index + 3] = (byte)(value >> 24); + } + else + { + buffer[index] = (byte)(value >> 24); + buffer[index + 1] = (byte)(value >> 16); + buffer[index + 2] = (byte)(value >> 8); + buffer[index + 3] = (byte)value; + } + } + + /// <summary> + /// Turns a <see cref="long"/> value into eight bytes and writes those bytes to a given buffer. + /// </summary> + /// <param name="buffer">The buffer to write to.</param> + /// <param name="index">The index to start writing at.</param> + /// <param name="value">The value to write.</param> + public static void GetBytes(byte[] buffer, int index, long value) + { + if (BitConverter.IsLittleEndian) + { + buffer[index] = (byte)value; + buffer[index + 1] = (byte)(value >> 8); + buffer[index + 2] = (byte)(value >> 16); + buffer[index + 3] = (byte)(value >> 24); + buffer[index + 4] = (byte)(value >> 32); + buffer[index + 5] = (byte)(value >> 40); + buffer[index + 6] = (byte)(value >> 48); + buffer[index + 7] = (byte)(value >> 56); + } + else + { + buffer[index] = (byte)(value >> 56); + buffer[index + 1] = (byte)(value >> 48); + buffer[index + 2] = (byte)(value >> 40); + buffer[index + 3] = (byte)(value >> 32); + buffer[index + 4] = (byte)(value >> 24); + buffer[index + 5] = (byte)(value >> 16); + buffer[index + 6] = (byte)(value >> 8); + buffer[index + 7] = (byte)value; + } + } + + /// <summary> + /// Turns an <see cref="ulong"/> value into eight bytes and writes those bytes to a given buffer. + /// </summary> + /// <param name="buffer">The buffer to write to.</param> + /// <param name="index">The index to start writing at.</param> + /// <param name="value">The value to write.</param> + public static void GetBytes(byte[] buffer, int index, ulong value) + { + if (BitConverter.IsLittleEndian) + { + buffer[index] = (byte)value; + buffer[index + 1] = (byte)(value >> 8); + buffer[index + 2] = (byte)(value >> 16); + buffer[index + 3] = (byte)(value >> 24); + buffer[index + 4] = (byte)(value >> 32); + buffer[index + 5] = (byte)(value >> 40); + buffer[index + 6] = (byte)(value >> 48); + buffer[index + 7] = (byte)(value >> 56); + } + else + { + buffer[index] = (byte)(value >> 56); + buffer[index + 1] = (byte)(value >> 48); + buffer[index + 2] = (byte)(value >> 40); + buffer[index + 3] = (byte)(value >> 32); + buffer[index + 4] = (byte)(value >> 24); + buffer[index + 5] = (byte)(value >> 16); + buffer[index + 6] = (byte)(value >> 8); + buffer[index + 7] = (byte)value; + } + } + + /// <summary> + /// Turns a <see cref="float"/> value into four bytes and writes those bytes to a given buffer. + /// </summary> + /// <param name="buffer">The buffer to write to.</param> + /// <param name="index">The index to start writing at.</param> + /// <param name="value">The value to write.</param> + public static void GetBytes(byte[] buffer, int index, float value) + { + var union = default(SingleByteUnion); + union.Value = value; + + if (BitConverter.IsLittleEndian) + { + buffer[index] = union.Byte0; + buffer[index + 1] = union.Byte1; + buffer[index + 2] = union.Byte2; + buffer[index + 3] = union.Byte3; + } + else + { + buffer[index] = union.Byte3; + buffer[index + 1] = union.Byte2; + buffer[index + 2] = union.Byte1; + buffer[index + 3] = union.Byte0; + } + } + + /// <summary> + /// Turns a <see cref="double"/> value into eight bytes and writes those bytes to a given buffer. + /// </summary> + /// <param name="buffer">The buffer to write to.</param> + /// <param name="index">The index to start writing at.</param> + /// <param name="value">The value to write.</param> + public static void GetBytes(byte[] buffer, int index, double value) + { + var union = default(DoubleByteUnion); + union.Value = value; + + if (BitConverter.IsLittleEndian) + { + buffer[index] = union.Byte0; + buffer[index + 1] = union.Byte1; + buffer[index + 2] = union.Byte2; + buffer[index + 3] = union.Byte3; + buffer[index + 4] = union.Byte4; + buffer[index + 5] = union.Byte5; + buffer[index + 6] = union.Byte6; + buffer[index + 7] = union.Byte7; + } + else + { + buffer[index] = union.Byte7; + buffer[index + 1] = union.Byte6; + buffer[index + 2] = union.Byte5; + buffer[index + 3] = union.Byte4; + buffer[index + 4] = union.Byte3; + buffer[index + 5] = union.Byte2; + buffer[index + 6] = union.Byte1; + buffer[index + 7] = union.Byte0; + } + } + + /// <summary> + /// Turns a <see cref="decimal"/> value into sixteen bytes and writes those bytes to a given buffer. + /// </summary> + /// <param name="buffer">The buffer to write to.</param> + /// <param name="index">The index to start writing at.</param> + /// <param name="value">The value to write.</param> + public static void GetBytes(byte[] buffer, int index, decimal value) + { + var union = default(DecimalByteUnion); + union.Value = value; + + if (BitConverter.IsLittleEndian) + { + buffer[index] = union.Byte0; + buffer[index + 1] = union.Byte1; + buffer[index + 2] = union.Byte2; + buffer[index + 3] = union.Byte3; + buffer[index + 4] = union.Byte4; + buffer[index + 5] = union.Byte5; + buffer[index + 6] = union.Byte6; + buffer[index + 7] = union.Byte7; + buffer[index + 8] = union.Byte8; + buffer[index + 9] = union.Byte9; + buffer[index + 10] = union.Byte10; + buffer[index + 11] = union.Byte11; + buffer[index + 12] = union.Byte12; + buffer[index + 13] = union.Byte13; + buffer[index + 14] = union.Byte14; + buffer[index + 15] = union.Byte15; + } + else + { + buffer[index] = union.Byte15; + buffer[index + 1] = union.Byte14; + buffer[index + 2] = union.Byte13; + buffer[index + 3] = union.Byte12; + buffer[index + 4] = union.Byte11; + buffer[index + 5] = union.Byte10; + buffer[index + 6] = union.Byte9; + buffer[index + 7] = union.Byte8; + buffer[index + 8] = union.Byte7; + buffer[index + 9] = union.Byte6; + buffer[index + 10] = union.Byte5; + buffer[index + 11] = union.Byte4; + buffer[index + 12] = union.Byte3; + buffer[index + 13] = union.Byte2; + buffer[index + 14] = union.Byte1; + buffer[index + 15] = union.Byte0; + } + } + + /// <summary> + /// Turns a <see cref="Guid"/> value into sixteen bytes and writes those bytes to a given buffer. + /// </summary> + /// <param name="buffer">The buffer to write to.</param> + /// <param name="index">The index to start writing at.</param> + /// <param name="value">The value to write.</param> + public static void GetBytes(byte[] buffer, int index, Guid value) + { + var union = default(GuidByteUnion); + union.Value = value; + + // First 10 bytes of a guid are always little endian + // Last 6 bytes depend on architecture endianness + // See http://stackoverflow.com/questions/10190817/guid-byte-order-in-net + + // TODO: Test if this actually works on big-endian architecture. Where the hell do we find that? + + buffer[index] = union.Byte0; + buffer[index + 1] = union.Byte1; + buffer[index + 2] = union.Byte2; + buffer[index + 3] = union.Byte3; + buffer[index + 4] = union.Byte4; + buffer[index + 5] = union.Byte5; + buffer[index + 6] = union.Byte6; + buffer[index + 7] = union.Byte7; + buffer[index + 8] = union.Byte8; + buffer[index + 9] = union.Byte9; + + if (BitConverter.IsLittleEndian) + { + buffer[index + 10] = union.Byte10; + buffer[index + 11] = union.Byte11; + buffer[index + 12] = union.Byte12; + buffer[index + 13] = union.Byte13; + buffer[index + 14] = union.Byte14; + buffer[index + 15] = union.Byte15; + } + else + { + buffer[index + 10] = union.Byte15; + buffer[index + 11] = union.Byte14; + buffer[index + 12] = union.Byte13; + buffer[index + 13] = union.Byte12; + buffer[index + 14] = union.Byte11; + buffer[index + 15] = union.Byte10; + } + } + + [StructLayout(LayoutKind.Explicit)] + private struct SingleByteUnion + { + [FieldOffset(0)] + public byte Byte0; + + [FieldOffset(1)] + public byte Byte1; + + [FieldOffset(2)] + public byte Byte2; + + [FieldOffset(3)] + public byte Byte3; + + [FieldOffset(0)] + public float Value; + } + + [StructLayout(LayoutKind.Explicit)] + private struct DoubleByteUnion + { + [FieldOffset(0)] + public byte Byte0; + + [FieldOffset(1)] + public byte Byte1; + + [FieldOffset(2)] + public byte Byte2; + + [FieldOffset(3)] + public byte Byte3; + + [FieldOffset(4)] + public byte Byte4; + + [FieldOffset(5)] + public byte Byte5; + + [FieldOffset(6)] + public byte Byte6; + + [FieldOffset(7)] + public byte Byte7; + + [FieldOffset(0)] + public double Value; + } + + [StructLayout(LayoutKind.Explicit)] + private struct DecimalByteUnion + { + [FieldOffset(0)] + public byte Byte0; + + [FieldOffset(1)] + public byte Byte1; + + [FieldOffset(2)] + public byte Byte2; + + [FieldOffset(3)] + public byte Byte3; + + [FieldOffset(4)] + public byte Byte4; + + [FieldOffset(5)] + public byte Byte5; + + [FieldOffset(6)] + public byte Byte6; + + [FieldOffset(7)] + public byte Byte7; + + [FieldOffset(8)] + public byte Byte8; + + [FieldOffset(9)] + public byte Byte9; + + [FieldOffset(10)] + public byte Byte10; + + [FieldOffset(11)] + public byte Byte11; + + [FieldOffset(12)] + public byte Byte12; + + [FieldOffset(13)] + public byte Byte13; + + [FieldOffset(14)] + public byte Byte14; + + [FieldOffset(15)] + public byte Byte15; + + [FieldOffset(0)] + public decimal Value; + } + + [StructLayout(LayoutKind.Explicit)] + private struct GuidByteUnion + { + [FieldOffset(0)] + public byte Byte0; + + [FieldOffset(1)] + public byte Byte1; + + [FieldOffset(2)] + public byte Byte2; + + [FieldOffset(3)] + public byte Byte3; + + [FieldOffset(4)] + public byte Byte4; + + [FieldOffset(5)] + public byte Byte5; + + [FieldOffset(6)] + public byte Byte6; + + [FieldOffset(7)] + public byte Byte7; + + [FieldOffset(8)] + public byte Byte8; + + [FieldOffset(9)] + public byte Byte9; + + [FieldOffset(10)] + public byte Byte10; + + [FieldOffset(11)] + public byte Byte11; + + [FieldOffset(12)] + public byte Byte12; + + [FieldOffset(13)] + public byte Byte13; + + [FieldOffset(14)] + public byte Byte14; + + [FieldOffset(15)] + public byte Byte15; + + [FieldOffset(0)] + public Guid Value; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ProperBitConverter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ProperBitConverter.cs.meta new file mode 100644 index 00000000..79a0f760 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/ProperBitConverter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 989e99cd5b8f922edc1b13bbc22f4289 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterAttribute.cs new file mode 100644 index 00000000..d0b703b2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterAttribute.cs @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------- +// <copyright file="RegisterFormatterAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class RegisterFormatterAttribute : Attribute + { + public Type FormatterType { get; private set; } + public int Priority { get; private set; } + + public RegisterFormatterAttribute(Type formatterType, int priority = 0) + { + this.FormatterType = formatterType; + this.Priority = priority; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterAttribute.cs.meta new file mode 100644 index 00000000..100cf134 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 82702718797409c332f9174bdad57bbc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterLocatorAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterLocatorAttribute.cs new file mode 100644 index 00000000..aea5b1fc --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterLocatorAttribute.cs @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------- +// <copyright file="RegisterFormatterLocatorAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class RegisterFormatterLocatorAttribute : Attribute + { + public Type FormatterLocatorType { get; private set; } + public int Priority { get; private set; } + + public RegisterFormatterLocatorAttribute(Type formatterLocatorType, int priority = 0) + { + this.FormatterLocatorType = formatterLocatorType; + this.Priority = priority; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterLocatorAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterLocatorAttribute.cs.meta new file mode 100644 index 00000000..9e85f275 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/RegisterFormatterLocatorAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a000ffc63858a974eb63d9ef6f91adac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationAbortException.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationAbortException.cs new file mode 100644 index 00000000..c84e5f08 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationAbortException.cs @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializationOptions.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using System; + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// An exception thrown when the serialization system has encountered an issue so severe that serialization is being aborted. If this exception is caught in the serialization system somewhere, it should be rethrown. + /// </summary> + public class SerializationAbortException : Exception + { + /// <summary> + /// Initializes a new instance of the <see cref="SerializationException"/> class. + /// </summary> + /// <param name="message">The message.</param> + public SerializationAbortException(string message) + : base(message) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="SerializationException"/> class. + /// </summary> + /// <param name="message">The message.</param> + /// <param name="innerException">The inner exception.</param> + public SerializationAbortException(string message, Exception innerException) + : base(message, innerException) + { + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationAbortException.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationAbortException.cs.meta new file mode 100644 index 00000000..216ba8cb --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationAbortException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dca124a461001ad1494664ed95539612 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationConfig.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationConfig.cs new file mode 100644 index 00000000..58bc260d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationConfig.cs @@ -0,0 +1,266 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializationOptions.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using System; + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Defines the configuration during serialization and deserialization. This class is thread-safe. + /// </summary> + public class SerializationConfig + { + private readonly object LOCK = new object(); + private volatile ISerializationPolicy serializationPolicy; + private volatile DebugContext debugContext; + + /// <summary> + /// Initializes a new instance of the <see cref="SerializationConfig"/> class. + /// </summary> + public SerializationConfig() + { + this.ResetToDefault(); + } + + /// <summary> + /// <para> + /// Setting this member to true indicates that in the case where, when expecting to deserialize an instance of a certain type, + /// but encountering an incompatible, uncastable type in the data being read, the serializer should attempt to deserialize an + /// instance of the expected type using the stored, possibly invalid data. + /// </para> + /// <para> + /// This is equivalent to applying the <see cref="SerializationConfig.AllowDeserializeInvalidData"/> attribute, except global + /// instead of specific to a single type. Note that if this member is set to false, individual types may still be deserialized + /// with invalid data if they are decorated with the <see cref="SerializationConfig.AllowDeserializeInvalidData"/> attribute. + /// </para> + /// </summary> + public bool AllowDeserializeInvalidData = false; + + /// <summary> + /// Gets or sets the serialization policy. This value is never null; if set to null, it will default to <see cref="SerializationPolicies.Unity"/>. + /// </summary> + /// <value> + /// The serialization policy. + /// </value> + public ISerializationPolicy SerializationPolicy + { + get + { + if (this.serializationPolicy == null) + { + lock (this.LOCK) + { + if (this.serializationPolicy == null) + { + this.serializationPolicy = SerializationPolicies.Unity; + } + } + } + + return this.serializationPolicy; + } + + set + { + lock (this.LOCK) + { + this.serializationPolicy = value; + } + } + } + + /// <summary> + /// Gets or sets the debug context. This value is never null; if set to null, a new default instance of <see cref="DebugContext"/> will be created upon the next get. + /// </summary> + /// <value> + /// The debug context. + /// </value> + public DebugContext DebugContext + { + get + { + if (this.debugContext == null) + { + lock (this.LOCK) + { + if (this.debugContext == null) + { + this.debugContext = new DebugContext(); + } + } + } + + return this.debugContext; + } + + set + { + lock (this.LOCK) + { + this.debugContext = value; + } + } + } + + /// <summary> + /// Resets the configuration to a default configuration, as if the constructor had just been called. + /// </summary> + public void ResetToDefault() + { + lock (this.LOCK) + { + this.AllowDeserializeInvalidData = false; + this.serializationPolicy = null; + if (!object.ReferenceEquals(this.debugContext, null)) + { + this.debugContext.ResetToDefault(); + } + } + } + } + + /// <summary> + /// Defines a context for debugging and logging during serialization and deserialization. This class is thread-safe. + /// </summary> + public sealed class DebugContext + { + private readonly object LOCK = new object(); + + private volatile ILogger logger; + private volatile LoggingPolicy loggingPolicy; + private volatile ErrorHandlingPolicy errorHandlingPolicy; + + /// <summary> + /// The logger to use for logging messages. + /// </summary> + public ILogger Logger + { + get + { + if (this.logger == null) + { + lock (this.LOCK) + { + if (this.logger == null) + { + this.logger = DefaultLoggers.UnityLogger; + } + } + } + + return this.logger; + } + set + { + lock (this.LOCK) + { + this.logger = value; + } + } + } + + /// <summary> + /// The logging policy to use. + /// </summary> + public LoggingPolicy LoggingPolicy + { + get { return this.loggingPolicy; } + set { this.loggingPolicy = value; } + } + + /// <summary> + /// The error handling policy to use. + /// </summary> + public ErrorHandlingPolicy ErrorHandlingPolicy + { + get { return this.errorHandlingPolicy; } + set { this.errorHandlingPolicy = value; } + } + + /// <summary> + /// Log a warning. Depending on the logging policy and error handling policy, this message may be suppressed or result in an exception being thrown. + /// </summary> + public void LogWarning(string message) + { + if (this.errorHandlingPolicy == ErrorHandlingPolicy.ThrowOnWarningsAndErrors) + { + throw new SerializationAbortException("The following warning was logged during serialization or deserialization: " + (message ?? "EMPTY EXCEPTION MESSAGE")); + } + + if (this.loggingPolicy == LoggingPolicy.LogWarningsAndErrors) + { + this.Logger.LogWarning(message); + } + } + + /// <summary> + /// Log an error. Depending on the logging policy and error handling policy, this message may be suppressed or result in an exception being thrown. + /// </summary> + public void LogError(string message) + { + if (this.errorHandlingPolicy != ErrorHandlingPolicy.Resilient) + { + throw new SerializationAbortException("The following error was logged during serialization or deserialization: " + (message ?? "EMPTY EXCEPTION MESSAGE")); + } + + if (this.loggingPolicy != LoggingPolicy.Silent) + { + this.Logger.LogError(message); + } + } + + /// <summary> + /// Log an exception. Depending on the logging policy and error handling policy, this message may be suppressed or result in an exception being thrown. + /// </summary> + public void LogException(Exception exception) + { + if (exception == null) + { + throw new ArgumentNullException("exception"); + } + + // We must always rethrow abort exceptions + if (exception is SerializationAbortException) + { + throw exception; + } + + var policy = this.errorHandlingPolicy; + + if (policy != ErrorHandlingPolicy.Resilient) + { + throw new SerializationAbortException("An exception of type " + exception.GetType().Name + " occurred during serialization or deserialization.", exception); + } + + if (this.loggingPolicy != LoggingPolicy.Silent) + { + this.Logger.LogException(exception); + } + } + + public void ResetToDefault() + { + lock (LOCK) + { + this.logger = null; + this.loggingPolicy = default(LoggingPolicy); + this.errorHandlingPolicy = default(ErrorHandlingPolicy); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationConfig.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationConfig.cs.meta new file mode 100644 index 00000000..950a0c8b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eba33c8e77e2084c660af46be1b547dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationContext.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationContext.cs new file mode 100644 index 00000000..9e9f2967 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationContext.cs @@ -0,0 +1,318 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializationContext.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using System.Runtime.Serialization; + using Utilities; + + /// <summary> + /// The context of a given serialization session. This class maintains all internal and external references during serialization. + /// </summary> + /// <seealso cref="ICacheNotificationReceiver" /> + public sealed class SerializationContext : ICacheNotificationReceiver + { + private SerializationConfig config; + private Dictionary<object, int> internalReferenceIdMap = new Dictionary<object, int>(128, ReferenceEqualityComparer<object>.Default); + private StreamingContext streamingContext; + private IFormatterConverter formatterConverter; + private TwoWaySerializationBinder binder; + + /// <summary> + /// Initializes a new instance of the <see cref="SerializationContext"/> class. + /// </summary> + public SerializationContext() + : this(new StreamingContext(), new FormatterConverter()) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="SerializationContext"/> class. + /// </summary> + /// <param name="context">The streaming context to use.</param> + public SerializationContext(StreamingContext context) + : this(context, new FormatterConverter()) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="SerializationContext"/> class. + /// </summary> + /// <param name="formatterConverter">The formatter converter to use.</param> + public SerializationContext(FormatterConverter formatterConverter) + : this(new StreamingContext(), formatterConverter) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="SerializationContext"/> class. + /// </summary> + /// <param name="context">The streaming context to use.</param> + /// <param name="formatterConverter">The formatter converter to use.</param> + /// <exception cref="System.ArgumentNullException">The formatterConverter parameter is null.</exception> + public SerializationContext(StreamingContext context, FormatterConverter formatterConverter) + { + if (formatterConverter == null) + { + throw new ArgumentNullException("formatterConverter"); + } + + this.streamingContext = context; + this.formatterConverter = formatterConverter; + + this.ResetToDefault(); + } + + /// <summary> + /// Gets or sets the context's type binder. + /// </summary> + /// <value> + /// The context's serialization binder. + /// </value> + public TwoWaySerializationBinder Binder + { + get + { + if (this.binder == null) + { + this.binder = DefaultSerializationBinder.Default; + } + + return this.binder; + } + + set + { + this.binder = value; + } + } + + /// <summary> + /// Gets the streaming context. + /// </summary> + /// <value> + /// The streaming context. + /// </value> + public StreamingContext StreamingContext { get { return this.streamingContext; } } + + /// <summary> + /// Gets the formatter converter. + /// </summary> + /// <value> + /// The formatter converter. + /// </value> + public IFormatterConverter FormatterConverter { get { return this.formatterConverter; } } + + /// <summary> + /// Gets or sets the index reference resolver. + /// </summary> + /// <value> + /// The index reference resolver. + /// </value> + public IExternalIndexReferenceResolver IndexReferenceResolver { get; set; } + + /// <summary> + /// Gets or sets the string reference resolver. + /// </summary> + /// <value> + /// The string reference resolver. + /// </value> + public IExternalStringReferenceResolver StringReferenceResolver { get; set; } + + /// <summary> + /// Gets or sets the Guid reference resolver. + /// </summary> + /// <value> + /// The Guid reference resolver. + /// </value> + public IExternalGuidReferenceResolver GuidReferenceResolver { get; set; } + + /// <summary> + /// Gets or sets the serialization configuration. + /// </summary> + /// <value> + /// The serialization configuration. + /// </value> + public SerializationConfig Config + { + get + { + if (this.config == null) + { + this.config = new SerializationConfig(); + } + + return this.config; + } + + set + { + this.config = value; + } + } + + /// <summary> + /// Tries to get the id of an internally referenced object. + /// </summary> + /// <param name="reference">The reference to get the id of.</param> + /// <param name="id">The id that was found, or -1 if no id was found.</param> + /// <returns><c>true</c> if a reference was found, otherwise <c>false</c>.</returns> + public bool TryGetInternalReferenceId(object reference, out int id) + { + return this.internalReferenceIdMap.TryGetValue(reference, out id); + } + + /// <summary> + /// Tries to register an internal reference. Returns <c>true</c> if the reference was registered, otherwise, <c>false</c> when the reference has already been registered. + /// </summary> + /// <param name="reference">The reference to register.</param> + /// <param name="id">The id of the registered reference.</param> + /// <returns><c>true</c> if the reference was registered, otherwise, <c>false</c> when the reference has already been registered.</returns> + public bool TryRegisterInternalReference(object reference, out int id) + { + if (this.internalReferenceIdMap.TryGetValue(reference, out id) == false) + { + id = this.internalReferenceIdMap.Count; + this.internalReferenceIdMap.Add(reference, id); + return true; + } + + return false; + } + + /// <summary> + /// Tries to register an external index reference. + /// </summary> + /// <param name="obj">The object to reference.</param> + /// <param name="index">The index of the referenced object.</param> + /// <returns><c>true</c> if the object could be referenced by index; otherwise, <c>false</c>.</returns> + public bool TryRegisterExternalReference(object obj, out int index) + { + if (this.IndexReferenceResolver == null) + { + index = -1; + return false; + } + + if (this.IndexReferenceResolver.CanReference(obj, out index)) + { + return true; + } + + index = -1; + return false; + } + + /// <summary> + /// Tries to register an external guid reference. + /// </summary> + /// <param name="obj">The object to reference.</param> + /// <param name="guid">The guid of the referenced object.</param> + /// <returns><c>true</c> if the object could be referenced by guid; otherwise, <c>false</c>.</returns> + public bool TryRegisterExternalReference(object obj, out Guid guid) + { + if (this.GuidReferenceResolver == null) + { + guid = Guid.Empty; + return false; + } + + var resolver = this.GuidReferenceResolver; + + while (resolver != null) + { + if (resolver.CanReference(obj, out guid)) + { + return true; + } + + resolver = resolver.NextResolver; + } + + guid = Guid.Empty; + return false; + } + + /// <summary> + /// Tries to register an external string reference. + /// </summary> + /// <param name="obj">The object to reference.</param> + /// <param name="id">The id string of the referenced object.</param> + /// <returns><c>true</c> if the object could be referenced by string; otherwise, <c>false</c>.</returns> + public bool TryRegisterExternalReference(object obj, out string id) + { + if (this.StringReferenceResolver == null) + { + id = null; + return false; + } + + var resolver = this.StringReferenceResolver; + + while (resolver != null) + { + if (resolver.CanReference(obj, out id)) + { + return true; + } + + resolver = resolver.NextResolver; + } + + id = null; + return false; + } + + /// <summary> + /// Resets the context's internal reference map. + /// </summary> + public void ResetInternalReferences() + { + this.internalReferenceIdMap.Clear(); + } + + /// <summary> + /// Resets the serialization context completely to baseline status, as if its constructor has just been called. + /// This allows complete reuse of a serialization context, with all of its internal reference buffers. + /// </summary> + public void ResetToDefault() + { + if (!object.ReferenceEquals(this.config, null)) + { + this.config.ResetToDefault(); + } + + this.internalReferenceIdMap.Clear(); + this.IndexReferenceResolver = null; + this.GuidReferenceResolver = null; + this.StringReferenceResolver = null; + this.binder = null; + } + + void ICacheNotificationReceiver.OnFreed() + { + this.ResetToDefault(); + } + + void ICacheNotificationReceiver.OnClaimed() + { + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationContext.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationContext.cs.meta new file mode 100644 index 00000000..07890f87 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e93880e733f9a6a084cf4061634e7fb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationPolicies.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationPolicies.cs new file mode 100644 index 00000000..bfd4c9a8 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationPolicies.cs @@ -0,0 +1,204 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializationPolicies.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using Utilities; + using System; + using System.Reflection; + using System.Runtime.CompilerServices; + using UnityEngine; + + /// <summary> + /// Contains a set of default implementations of the <see cref="ISerializationPolicy"/> interface. + /// <para /> + /// NOTE: Policies are not necessarily compatible with each other in intuitive ways. + /// Data serialized with the <see cref="SerializationPolicies.Everything"/> policy + /// will for example fail to deserialize auto-properties with <see cref="SerializationPolicies.Strict"/>, + /// even if only strict data is needed. + /// It is best to ensure that you always use the same policy for serialization and deserialization. + /// <para /> + /// This class and all of its policies are thread-safe. + /// </summary> + public static class SerializationPolicies + { + private static readonly object LOCK = new object(); + + private static volatile ISerializationPolicy everythingPolicy; + private static volatile ISerializationPolicy unityPolicy; + private static volatile ISerializationPolicy strictPolicy; + + /// <summary> + /// Tries to get a serialization policy by its id, in case a serialization graph has the policy used for serialization stored by name. + /// </summary> + public static bool TryGetByID(string name, out ISerializationPolicy policy) + { + switch (name) + { + case "OdinSerializerPolicies.Everything": + policy = SerializationPolicies.Everything; + break; + + case "OdinSerializerPolicies.Unity": + policy = SerializationPolicies.Unity; + break; + + case "OdinSerializerPolicies.Strict": + policy = SerializationPolicies.Strict; + break; + + default: + policy = null; + break; + } + + return policy != null; + } + + /// <summary> + /// All fields not marked with <see cref="NonSerializedAttribute"/> are serialized. If a field is marked with both <see cref="NonSerializedAttribute"/> and <see cref="OdinSerializeAttribute"/>, then the field will be serialized. + /// </summary> + public static ISerializationPolicy Everything + { + get + { + if (everythingPolicy == null) + { + lock (LOCK) + { + if (everythingPolicy == null) + { + everythingPolicy = new CustomSerializationPolicy("OdinSerializerPolicies.Everything", true, (member) => + { + if (!(member is FieldInfo)) + { + return false; + } + + if (member.IsDefined<OdinSerializeAttribute>(true)) + { + return true; + } + + return !member.IsDefined<NonSerializedAttribute>(true); + }); + } + } + } + + return everythingPolicy; + } + } + + /// <summary> + /// Public fields, as well as fields or auto-properties marked with <see cref="SerializeField"/> or <see cref="OdinSerializeAttribute"/> and not marked with <see cref="NonSerializedAttribute"/>, are serialized. + /// <para /> + /// There are two exceptions: + /// <para/>1) All fields in tuples, as well as in private nested types marked as compiler generated (e.g. lambda capture classes) are also serialized. + /// <para/>2) Virtual auto-properties are never serialized. Note that properties specified by an implemented interface are automatically marked virtual by the compiler. + /// </summary> + public static ISerializationPolicy Unity + { + get + { + if (unityPolicy == null) + { + lock (LOCK) + { + if (unityPolicy == null) + { + // In Unity 2017.1's .NET 4.6 profile, Tuples implement System.ITuple. In Unity 2017.2 and up, tuples implement System.ITupleInternal instead for some reason. + Type tupleInterface = typeof(string).Assembly.GetType("System.ITuple") ?? typeof(string).Assembly.GetType("System.ITupleInternal"); + + unityPolicy = new CustomSerializationPolicy("OdinSerializerPolicies.Unity", true, (member) => + { + // As of Odin 3.0, we now allow non-auto properties and virtual properties. + // However, properties still need a getter and a setter. + if (member is PropertyInfo) + { + var propInfo = member as PropertyInfo; + if (propInfo.GetGetMethod(true) == null || propInfo.GetSetMethod(true) == null) return false; + } + + // If OdinSerializeAttribute is defined, NonSerializedAttribute is ignored. + // This enables users to ignore Unity's infinite serialization depth warnings. + if (member.IsDefined<NonSerializedAttribute>(true) && !member.IsDefined<OdinSerializeAttribute>()) + { + return false; + } + + if (member is FieldInfo && ((member as FieldInfo).IsPublic || (member.DeclaringType.IsNestedPrivate && member.DeclaringType.IsDefined<CompilerGeneratedAttribute>()) || (tupleInterface != null && tupleInterface.IsAssignableFrom(member.DeclaringType)))) + { + return true; + } + + return member.IsDefined<SerializeField>(false) || member.IsDefined<OdinSerializeAttribute>(false) || (UnitySerializationUtility.SerializeReferenceAttributeType != null && member.IsDefined(UnitySerializationUtility.SerializeReferenceAttributeType, false)); + }); + } + } + } + + return unityPolicy; + } + } + + /// <summary> + /// Only fields and auto-properties marked with <see cref="SerializeField"/> or <see cref="OdinSerializeAttribute"/> and not marked with <see cref="NonSerializedAttribute"/> are serialized. + /// <para /> + /// There are two exceptions: + /// <para/>1) All fields in private nested types marked as compiler generated (e.g. lambda capture classes) are also serialized. + /// <para/>2) Virtual auto-properties are never serialized. Note that properties specified by an implemented interface are automatically marked virtual by the compiler. + /// </summary> + public static ISerializationPolicy Strict + { + get + { + if (strictPolicy == null) + { + lock (LOCK) + { + if (strictPolicy == null) + { + strictPolicy = new CustomSerializationPolicy("OdinSerializerPolicies.Strict", true, (member) => + { + // Non-auto properties are never supported. + if (member is PropertyInfo && ((PropertyInfo)member).IsAutoProperty() == false) + { + return false; + } + + if (member.IsDefined<NonSerializedAttribute>()) + { + return false; + } + + if (member is FieldInfo && member.DeclaringType.IsNestedPrivate && member.DeclaringType.IsDefined<CompilerGeneratedAttribute>()) + { + return true; + } + + return member.IsDefined<SerializeField>(false) || member.IsDefined<OdinSerializeAttribute>(false) || (UnitySerializationUtility.SerializeReferenceAttributeType != null && member.IsDefined(UnitySerializationUtility.SerializeReferenceAttributeType, false)); + }); + } + } + } + + return strictPolicy; + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationPolicies.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationPolicies.cs.meta new file mode 100644 index 00000000..1b66aa92 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationPolicies.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 67a19021ff9e6b27d8e9257ad075055a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationUtility.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationUtility.cs new file mode 100644 index 00000000..464edf73 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationUtility.cs @@ -0,0 +1,747 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializationUtility.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using System.IO; + using UnityEngine; + using Utilities; + + /// <summary> + /// Provides an array of utility wrapper methods for easy serialization and deserialization of objects of any type. + /// </summary> + public static class SerializationUtility + { + /// <summary> + /// Creates an <see cref="IDataWriter" /> for a given format. + /// </summary> + /// <param name="stream">The stream to write to.</param> + /// <param name="context">The serialization context to use.</param> + /// <param name="format">The format to write.</param> + /// <returns> + /// An <see cref="IDataWriter" /> for a given format. + /// </returns> + /// <exception cref="System.NotImplementedException"></exception> + public static IDataWriter CreateWriter(Stream stream, SerializationContext context, DataFormat format) + { + switch (format) + { + case DataFormat.Binary: + return new BinaryDataWriter(stream, context); + + case DataFormat.JSON: + return new JsonDataWriter(stream, context); + + case DataFormat.Nodes: + Debug.LogError("Cannot automatically create a writer for the format '" + DataFormat.Nodes + "', because it does not use a stream."); + return null; + + default: + throw new NotImplementedException(format.ToString()); + } + } + + /// <summary> + /// Creates an <see cref="IDataReader" /> for a given format. + /// </summary> + /// <param name="stream">The stream to read from.</param> + /// <param name="context">The deserialization context to use.</param> + /// <param name="format">The format to read.</param> + /// <returns> + /// An <see cref="IDataReader" /> for a given format. + /// </returns> + /// <exception cref="System.NotImplementedException"></exception> + public static IDataReader CreateReader(Stream stream, DeserializationContext context, DataFormat format) + { + switch (format) + { + case DataFormat.Binary: + return new BinaryDataReader(stream, context); + + case DataFormat.JSON: + return new JsonDataReader(stream, context); + + case DataFormat.Nodes: + Debug.LogError("Cannot automatically create a reader for the format '" + DataFormat.Nodes + "', because it does not use a stream."); + return null; + + default: + throw new NotImplementedException(format.ToString()); + } + } + + private static IDataWriter GetCachedWriter(out IDisposable cache, DataFormat format, Stream stream, SerializationContext context) + { + IDataWriter writer; + + if (format == DataFormat.Binary) + { + var binaryCache = Cache<BinaryDataWriter>.Claim(); + var binaryWriter = binaryCache.Value; + + binaryWriter.Stream = stream; + binaryWriter.Context = context; + binaryWriter.PrepareNewSerializationSession(); + + writer = binaryWriter; + cache = binaryCache; + } + else if (format == DataFormat.JSON) + { + var jsonCache = Cache<JsonDataWriter>.Claim(); + var jsonWriter = jsonCache.Value; + + jsonWriter.Stream = stream; + jsonWriter.Context = context; + jsonWriter.PrepareNewSerializationSession(); + + writer = jsonWriter; + cache = jsonCache; + } + else if (format == DataFormat.Nodes) + { + throw new InvalidOperationException("Cannot automatically create a writer for the format '" + DataFormat.Nodes + "', because it does not use a stream."); + } + else + { + throw new NotImplementedException(format.ToString()); + } + + return writer; + } + + private static IDataReader GetCachedReader(out IDisposable cache, DataFormat format, Stream stream, DeserializationContext context) + { + IDataReader reader; + + if (format == DataFormat.Binary) + { + var binaryCache = Cache<BinaryDataReader>.Claim(); + var binaryReader = binaryCache.Value; + + binaryReader.Stream = stream; + binaryReader.Context = context; + binaryReader.PrepareNewSerializationSession(); + + reader = binaryReader; + cache = binaryCache; + } + else if (format == DataFormat.JSON) + { + var jsonCache = Cache<JsonDataReader>.Claim(); + var jsonReader = jsonCache.Value; + + jsonReader.Stream = stream; + jsonReader.Context = context; + jsonReader.PrepareNewSerializationSession(); + + reader = jsonReader; + cache = jsonCache; + } + else if (format == DataFormat.Nodes) + { + throw new InvalidOperationException("Cannot automatically create a reader for the format '" + DataFormat.Nodes + "', because it does not use a stream."); + } + else + { + throw new NotImplementedException(format.ToString()); + } + + return reader; + } + + /// <summary> + /// Serializes the given value using the given writer. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to use.</param> + public static void SerializeValueWeak(object value, IDataWriter writer) + { + Serializer.GetForValue(value).WriteValueWeak(value, writer); + writer.FlushToStream(); + } + + /// <summary> + /// Serializes the given value, using the given writer. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to use.</param> + /// <param name="unityObjects">A list of the Unity objects which were referenced during serialization.</param> + public static void SerializeValueWeak(object value, IDataWriter writer, out List<UnityEngine.Object> unityObjects) + { + using (var unityResolver = Cache<UnityReferenceResolver>.Claim()) + { + writer.Context.IndexReferenceResolver = unityResolver.Value; + Serializer.GetForValue(value).WriteValueWeak(value, writer); + writer.FlushToStream(); + unityObjects = unityResolver.Value.GetReferencedUnityObjects(); + } + } + + /// <summary> + /// Serializes the given value using the given writer. + /// </summary> + /// <typeparam name="T">The type of the value to serialize.</typeparam> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to use.</param> + public static void SerializeValue<T>(T value, IDataWriter writer) + { + Serializer.Get<T>().WriteValue(value, writer); + writer.FlushToStream(); + } + + /// <summary> + /// Serializes the given value, using the given writer. + /// </summary> + /// <typeparam name="T">The type of the value to serialize.</typeparam> + /// <param name="value">The value to serialize.</param> + /// <param name="writer">The writer to use.</param> + /// <param name="unityObjects">A list of the Unity objects which were referenced during serialization.</param> + public static void SerializeValue<T>(T value, IDataWriter writer, out List<UnityEngine.Object> unityObjects) + { + using (var unityResolver = Cache<UnityReferenceResolver>.Claim()) + { + writer.Context.IndexReferenceResolver = unityResolver.Value; + Serializer.Get<T>().WriteValue(value, writer); + writer.FlushToStream(); + unityObjects = unityResolver.Value.GetReferencedUnityObjects(); + } + } + + + + /// <summary> + /// Serializes the given value to a given stream in the specified format. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="stream">The stream to serialize to.</param> + /// <param name="format">The format to serialize in.</param> + /// <param name="context">The context.</param> + public static void SerializeValueWeak(object value, Stream stream, DataFormat format, SerializationContext context = null) + { + IDisposable cache; + var writer = GetCachedWriter(out cache, format, stream, context); + + try + { + if (context != null) + { + SerializeValueWeak(value, writer); + } + else + { + using (var con = Cache<SerializationContext>.Claim()) + { + writer.Context = con; + SerializeValueWeak(value, writer); + } + } + } + finally + { + cache.Dispose(); + } + } + + /// <summary> + /// Serializes the given value to a given stream in the specified format. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="stream">The stream to serialize to.</param> + /// <param name="format">The format to serialize in.</param> + /// <param name="unityObjects">A list of the Unity objects which were referenced during serialization.</param> + /// <param name="context">The context.</param> + public static void SerializeValueWeak(object value, Stream stream, DataFormat format, out List<UnityEngine.Object> unityObjects, SerializationContext context = null) + { + IDisposable cache; + var writer = GetCachedWriter(out cache, format, stream, context); + + try + { + if (context != null) + { + SerializeValueWeak(value, writer, out unityObjects); + } + else + { + using (var con = Cache<SerializationContext>.Claim()) + { + writer.Context = con; + SerializeValueWeak(value, writer, out unityObjects); + } + } + } + finally + { + cache.Dispose(); + } + } + + /// <summary> + /// Serializes the given value to a given stream in the specified format. + /// </summary> + /// <typeparam name="T">The type of the value to serialize.</typeparam> + /// <param name="value">The value to serialize.</param> + /// <param name="stream">The stream to serialize to.</param> + /// <param name="format">The format to serialize in.</param> + /// <param name="context">The context.</param> + public static void SerializeValue<T>(T value, Stream stream, DataFormat format, SerializationContext context = null) + { + IDisposable cache; + var writer = GetCachedWriter(out cache, format, stream, context); + + try + { + if (context != null) + { + SerializeValue(value, writer); + } + else + { + using (var con = Cache<SerializationContext>.Claim()) + { + writer.Context = con; + SerializeValue(value, writer); + } + } + } + finally + { + cache.Dispose(); + } + } + + /// <summary> + /// Serializes the given value to a given stream in the specified format. + /// </summary> + /// <typeparam name="T">The type of the value to serialize.</typeparam> + /// <param name="value">The value to serialize.</param> + /// <param name="stream">The stream to serialize to.</param> + /// <param name="format">The format to serialize in.</param> + /// <param name="unityObjects">A list of the Unity objects which were referenced during serialization.</param> + /// <param name="context">The context.</param> + public static void SerializeValue<T>(T value, Stream stream, DataFormat format, out List<UnityEngine.Object> unityObjects, SerializationContext context = null) + { + IDisposable cache; + var writer = GetCachedWriter(out cache, format, stream, context); + + try + { + if (context != null) + { + SerializeValue(value, writer, out unityObjects); + } + else + { + using (var con = Cache<SerializationContext>.Claim()) + { + writer.Context = con; + SerializeValue(value, writer, out unityObjects); + } + } + } + finally + { + cache.Dispose(); + } + } + + /// <summary> + /// Serializes the given value using the specified format, and returns the result as a byte array. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="format">The format to use.</param> + /// <param name="context">The context.</param> + /// <returns>A byte array containing the serialized value.</returns> + public static byte[] SerializeValueWeak(object value, DataFormat format, SerializationContext context = null) + { + using (var stream = CachedMemoryStream.Claim()) + { + SerializeValueWeak(value, stream.Value.MemoryStream, format, context); + return stream.Value.MemoryStream.ToArray(); + } + } + + /// <summary> + /// Serializes the given value using the specified format, and returns the result as a byte array. + /// </summary> + /// <param name="value">The value to serialize.</param> + /// <param name="format">The format to use.</param> + /// <param name="unityObjects">A list of the Unity objects which were referenced during serialization.</param> + /// <returns>A byte array containing the serialized value.</returns> + public static byte[] SerializeValueWeak(object value, DataFormat format, out List<UnityEngine.Object> unityObjects) + { + using (var stream = CachedMemoryStream.Claim()) + { + SerializeValueWeak(value, stream.Value.MemoryStream, format, out unityObjects); + return stream.Value.MemoryStream.ToArray(); + } + } + + /// <summary> + /// Serializes the given value using the specified format, and returns the result as a byte array. + /// </summary> + /// <typeparam name="T">The type of the value to serialize.</typeparam> + /// <param name="value">The value to serialize.</param> + /// <param name="format">The format to use.</param> + /// <param name="context">The context to use.</param> + /// <returns>A byte array containing the serialized value.</returns> + public static byte[] SerializeValue<T>(T value, DataFormat format, SerializationContext context = null) + { + using (var stream = CachedMemoryStream.Claim()) + { + SerializeValue(value, stream.Value.MemoryStream, format, context); + return stream.Value.MemoryStream.ToArray(); + } + } + + /// <summary> + /// Serializes the given value using the specified format and returns the result as a byte array. + /// </summary> + /// <typeparam name="T">The type of the value to serialize.</typeparam> + /// <param name="value">The value to serialize.</param> + /// <param name="format">The format to use.</param> + /// <param name="unityObjects">A list of the Unity objects which were referenced during serialization.</param> + /// <param name="context">The context to use.</param> + /// <returns>A byte array containing the serialized value.</returns> + public static byte[] SerializeValue<T>(T value, DataFormat format, out List<UnityEngine.Object> unityObjects, SerializationContext context = null) + { + using (var stream = CachedMemoryStream.Claim()) + { + SerializeValue(value, stream.Value.MemoryStream, format, out unityObjects, context); + return stream.Value.MemoryStream.ToArray(); + } + } + + /// <summary> + /// Deserializes a value from the given reader. This might fail with primitive values, as they don't come with metadata. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns>The deserialized value.</returns> + public static object DeserializeValueWeak(IDataReader reader) + { + return Serializer.Get<object>().ReadValueWeak(reader); + } + + /// <summary> + /// Deserializes a value from the given reader, using the given list of Unity objects for external index reference resolution. This might fail with primitive values, as they don't come with type metadata. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <param name="referencedUnityObjects">The list of Unity objects to use for external index reference resolution.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public static object DeserializeValueWeak(IDataReader reader, List<UnityEngine.Object> referencedUnityObjects) + { + using (var unityResolver = Cache<UnityReferenceResolver>.Claim()) + { + unityResolver.Value.SetReferencedUnityObjects(referencedUnityObjects); + reader.Context.IndexReferenceResolver = unityResolver.Value; + return Serializer.Get<object>().ReadValueWeak(reader); + } + } + + /// <summary> + /// Deserializes a value from the given reader. + /// </summary> + /// <typeparam name="T">The type to deserialize.</typeparam> + /// <param name="reader">The reader to use.</param> + /// <returns>The deserialized value.</returns> + public static T DeserializeValue<T>(IDataReader reader) + { + return Serializer.Get<T>().ReadValue(reader); + } + + /// <summary> + /// Deserializes a value of a given type from the given reader, using the given list of Unity objects for external index reference resolution. + /// </summary> + /// <typeparam name="T">The type to deserialize.</typeparam> + /// <param name="reader">The reader to use.</param> + /// <param name="referencedUnityObjects">The list of Unity objects to use for external index reference resolution.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public static T DeserializeValue<T>(IDataReader reader, List<UnityEngine.Object> referencedUnityObjects) + { + using (var unityResolver = Cache<UnityReferenceResolver>.Claim()) + { + unityResolver.Value.SetReferencedUnityObjects(referencedUnityObjects); + reader.Context.IndexReferenceResolver = unityResolver.Value; + return Serializer.Get<T>().ReadValue(reader); + } + } + + /// <summary> + /// Deserializes a value from the given stream in the given format. This might fail with primitive values, as they don't come with type metadata. + /// </summary> + /// <param name="stream">The reader to use.</param> + /// <param name="format">The format to read.</param> + /// <param name="context">The context.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public static object DeserializeValueWeak(Stream stream, DataFormat format, DeserializationContext context = null) + { + IDisposable cache; + var reader = GetCachedReader(out cache, format, stream, context); + + try + { + if (context != null) + { + return DeserializeValueWeak(reader); + } + else + { + using (var con = Cache<DeserializationContext>.Claim()) + { + reader.Context = con; + return DeserializeValueWeak(reader); + } + } + } + finally + { + cache.Dispose(); + } + } + + /// <summary> + /// Deserializes a value from the given stream in the given format, using the given list of Unity objects for external index reference resolution. This might fail with primitive values, as they don't come with type metadata. + /// </summary> + /// <param name="stream">The stream to read from.</param> + /// <param name="format">The format to read.</param> + /// <param name="referencedUnityObjects">The list of Unity objects to use for external index reference resolution.</param> + /// <param name="context">The context.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public static object DeserializeValueWeak(Stream stream, DataFormat format, List<UnityEngine.Object> referencedUnityObjects, DeserializationContext context = null) + { + IDisposable cache; + var reader = GetCachedReader(out cache, format, stream, context); + + try + { + if (context != null) + { + return DeserializeValueWeak(reader, referencedUnityObjects); + } + else + { + using (var con = Cache<DeserializationContext>.Claim()) + { + reader.Context = con; + return DeserializeValueWeak(reader, referencedUnityObjects); + } + } + } + finally + { + cache.Dispose(); + } + } + + /// <summary> + /// Deserializes a value of a given type from the given stream in the given format. + /// </summary> + /// <typeparam name="T">The type to deserialize.</typeparam> + /// <param name="stream">The stream to read from.</param> + /// <param name="format">The format to read.</param> + /// <param name="context">The context.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public static T DeserializeValue<T>(Stream stream, DataFormat format, DeserializationContext context = null) + { + IDisposable cache; + var reader = GetCachedReader(out cache, format, stream, context); + + try + { + if (context != null) + { + return DeserializeValue<T>(reader); + } + else + { + using (var con = Cache<DeserializationContext>.Claim()) + { + reader.Context = con; + return DeserializeValue<T>(reader); + } + } + } + finally + { + cache.Dispose(); + } + } + + /// <summary> + /// Deserializes a value of a given type from the given stream in the given format, using the given list of Unity objects for external index reference resolution. + /// </summary> + /// <typeparam name="T">The type to deserialize.</typeparam> + /// <param name="stream">The stream to read from.</param> + /// <param name="format">The format to read.</param> + /// <param name="referencedUnityObjects">The list of Unity objects to use for external index reference resolution.</param> + /// <param name="context">The context.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public static T DeserializeValue<T>(Stream stream, DataFormat format, List<UnityEngine.Object> referencedUnityObjects, DeserializationContext context = null) + { + IDisposable cache; + var reader = GetCachedReader(out cache, format, stream, context); + + try + { + if (context != null) + { + return DeserializeValue<T>(reader, referencedUnityObjects); + } + else + { + using (var con = Cache<DeserializationContext>.Claim()) + { + reader.Context = con; + return DeserializeValue<T>(reader, referencedUnityObjects); + } + } + } + finally + { + cache.Dispose(); + } + } + + /// <summary> + /// Deserializes a value from the given byte array in the given format. This might fail with primitive values, as they don't come with type metadata. + /// </summary> + /// <param name="bytes">The bytes to deserialize from.</param> + /// <param name="format">The format to read.</param> + /// <param name="context">The context.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public static object DeserializeValueWeak(byte[] bytes, DataFormat format, DeserializationContext context = null) + { + using (var stream = CachedMemoryStream.Claim(bytes)) + { + return DeserializeValueWeak(stream.Value.MemoryStream, format, context); + } + } + + /// <summary> + /// Deserializes a value from the given byte array in the given format, using the given list of Unity objects for external index reference resolution. This might fail with primitive values, as they don't come with type metadata. + /// </summary> + /// <param name="bytes">The bytes to deserialize from.</param> + /// <param name="format">The format to read.</param> + /// <param name="referencedUnityObjects">The list of Unity objects to use for external index reference resolution.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public static object DeserializeValueWeak(byte[] bytes, DataFormat format, List<UnityEngine.Object> referencedUnityObjects) + { + using (var stream = CachedMemoryStream.Claim(bytes)) + { + return DeserializeValueWeak(stream.Value.MemoryStream, format, referencedUnityObjects); + } + } + + /// <summary> + /// Deserializes a value of a given type from the given byte array in the given format. + /// </summary> + /// <typeparam name="T">The type to deserialize.</typeparam> + /// <param name="bytes">The bytes to deserialize from.</param> + /// <param name="format">The format to read.</param> + /// <param name="context">The context to use.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public static T DeserializeValue<T>(byte[] bytes, DataFormat format, DeserializationContext context = null) + { + using (var stream = CachedMemoryStream.Claim(bytes)) + { + return DeserializeValue<T>(stream.Value.MemoryStream, format, context); + } + } + + /// <summary> + /// Deserializes a value of a given type from the given byte array in the given format, using the given list of Unity objects for external index reference resolution. + /// </summary> + /// <typeparam name="T">The type to deserialize.</typeparam> + /// <param name="bytes">The bytes to deserialize from.</param> + /// <param name="format">The format to read.</param> + /// <param name="referencedUnityObjects">The list of Unity objects to use for external index reference resolution.</param> + /// <param name="context">The context to use.</param> + /// <returns> + /// The deserialized value. + /// </returns> + public static T DeserializeValue<T>(byte[] bytes, DataFormat format, List<UnityEngine.Object> referencedUnityObjects, DeserializationContext context = null) + { + using (var stream = CachedMemoryStream.Claim(bytes)) + { + return DeserializeValue<T>(stream.Value.MemoryStream, format, referencedUnityObjects, context); + } + } + + /// <summary> + /// Creates a deep copy of an object. Returns null if null. All Unity objects references will remain the same - they will not get copied. + /// </summary> + public static object CreateCopy(object obj) + { + if (obj == null) + { + return null; + } + + if (obj as string != null) + { + return obj; + } + + var type = obj.GetType(); + + if (type.IsValueType) + { + return obj; + } + + if (type.InheritsFrom(typeof(UnityEngine.Object))) + { + return obj; + } + + using (var stream = CachedMemoryStream.Claim()) + using (var serContext = Cache<SerializationContext>.Claim()) + using (var deserContext = Cache<DeserializationContext>.Claim()) + { + serContext.Value.Config.SerializationPolicy = SerializationPolicies.Everything; + deserContext.Value.Config.SerializationPolicy = SerializationPolicies.Everything; + + List<UnityEngine.Object> unityReferences; + SerializeValue(obj, stream.Value.MemoryStream, DataFormat.Binary, out unityReferences, serContext); + stream.Value.MemoryStream.Position = 0; + return DeserializeValue<object>(stream.Value.MemoryStream, DataFormat.Binary, unityReferences, deserContext); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationUtility.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationUtility.cs.meta new file mode 100644 index 00000000..dc3614de --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/SerializationUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 08607b6e9c39ec19c1b61341583c2f3e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/TwoWaySerializationBinder.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/TwoWaySerializationBinder.cs new file mode 100644 index 00000000..76c42479 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/TwoWaySerializationBinder.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="TwoWaySerializationBinder.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Binds types to strings during serialization, and strings to types during deserialization. + /// </summary> + public abstract class TwoWaySerializationBinder + { + /// <summary> + /// Provides a default, catch-all <see cref="TwoWaySerializationBinder"/> implementation. This binder only includes assembly names, without versions and tokens, in order to increase compatibility. + /// </summary> + public static readonly TwoWaySerializationBinder Default = new DefaultSerializationBinder(); + + /// <summary> + /// Bind a type to a name. + /// </summary> + /// <param name="type">The type to bind.</param> + /// <param name="debugContext">The debug context to log to.</param> + /// <returns>The name that the type has been bound to.</returns> + public abstract string BindToName(Type type, DebugContext debugContext = null); + + /// <summary> + /// Binds a name to a type. + /// </summary> + /// <param name="typeName">The name of the type to bind.</param> + /// <param name="debugContext">The debug context to log to.</param> + /// <returns>The type that the name has been bound to, or null if the type could not be resolved.</returns> + public abstract Type BindToType(string typeName, DebugContext debugContext = null); + + /// <summary> + /// Determines whether the specified type name is mapped. + /// </summary> + public abstract bool ContainsType(string typeName); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/TwoWaySerializationBinder.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/TwoWaySerializationBinder.cs.meta new file mode 100644 index 00000000..73b5d412 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Misc/TwoWaySerializationBinder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc69d8fd9d15913a491a45d1e040faf6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers.meta new file mode 100644 index 00000000..31db22a9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 76fca96687985434f81c6cd7d3397dd6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/BooleanSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/BooleanSerializer.cs new file mode 100644 index 00000000..b185c215 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/BooleanSerializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="BooleanSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="bool"/> type. + /// </summary> + /// <seealso cref="Serializer{System.Boolean}" /> + public sealed class BooleanSerializer : Serializer<bool> + { + /// <summary> + /// Reads a value of type <see cref="bool" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override bool ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Boolean) + { + bool value; + if (reader.ReadBoolean(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Boolean.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(bool); + } + } + + /// <summary> + /// Writes a value of type <see cref="bool" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, bool value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteBoolean(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/BooleanSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/BooleanSerializer.cs.meta new file mode 100644 index 00000000..17089744 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/BooleanSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e8d8c5a97fdc322a8b8471aaf02f469 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ByteSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ByteSerializer.cs new file mode 100644 index 00000000..1f9172c4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ByteSerializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="ByteSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="byte"/> type. + /// </summary> + /// <seealso cref="Serializer{System.Byte}" /> + public sealed class ByteSerializer : Serializer<byte> + { + /// <summary> + /// Reads a value of type <see cref="byte" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override byte ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Integer) + { + byte value; + if (reader.ReadByte(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(byte); + } + } + + /// <summary> + /// Writes a value of type <see cref="byte" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, byte value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteByte(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ByteSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ByteSerializer.cs.meta new file mode 100644 index 00000000..09080028 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ByteSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8aa9f52771b0e4e6f8f0c438a4f0430b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/CharSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/CharSerializer.cs new file mode 100644 index 00000000..902bd649 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/CharSerializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="CharSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="char"/> type. + /// </summary> + /// <seealso cref="Serializer{System.Char}" /> + public sealed class CharSerializer : Serializer<char> + { + /// <summary> + /// Reads a value of type <see cref="char" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override char ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.String) + { + char value; + if (reader.ReadChar(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.String.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(char); + } + } + + /// <summary> + /// Writes a value of type <see cref="char" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, char value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteChar(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/CharSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/CharSerializer.cs.meta new file mode 100644 index 00000000..92db21b0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/CharSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d44d1ae83013328d7b855275fa1cfad7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ComplexTypeSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ComplexTypeSerializer.cs new file mode 100644 index 00000000..e8ed5a1f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ComplexTypeSerializer.cs @@ -0,0 +1,663 @@ +//----------------------------------------------------------------------- +// <copyright file="ComplexTypeSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using Utilities; + + /// <summary> + /// Serializer for all complex types; IE, types which are not primitives as determined by the <see cref="FormatterUtilities.IsPrimitiveType(Type)" /> method. + /// </summary> + /// <typeparam name="T">The type which the <see cref="ComplexTypeSerializer{T}" /> can serialize and deserialize.</typeparam> + /// <seealso cref="Serializer{T}" /> + public sealed class ComplexTypeSerializer<T> : Serializer<T> + { + private static readonly bool ComplexTypeMayBeBoxedValueType = typeof(T).IsInterface || typeof(T) == typeof(object) || typeof(T) == typeof(ValueType) || typeof(T) == typeof(Enum); + private static readonly bool ComplexTypeIsAbstract = typeof(T).IsAbstract || typeof(T).IsInterface; + private static readonly bool ComplexTypeIsNullable = typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>); + private static readonly bool ComplexTypeIsValueType = typeof(T).IsValueType; + private static readonly Type TypeOf_T = typeof(T); + + private static readonly bool AllowDeserializeInvalidDataForT = typeof(T).IsDefined(typeof(AllowDeserializeInvalidDataAttribute), true); + + private static readonly Dictionary<ISerializationPolicy, IFormatter<T>> FormattersByPolicy = new Dictionary<ISerializationPolicy, IFormatter<T>>(ReferenceEqualityComparer<ISerializationPolicy>.Default); + private static readonly object FormattersByPolicy_LOCK = new object(); + + private static readonly ISerializationPolicy UnityPolicy = SerializationPolicies.Unity; + private static readonly ISerializationPolicy StrictPolicy = SerializationPolicies.Strict; + private static readonly ISerializationPolicy EverythingPolicy = SerializationPolicies.Everything; + + private static IFormatter<T> UnityPolicyFormatter; + private static IFormatter<T> StrictPolicyFormatter; + private static IFormatter<T> EverythingPolicyFormatter; + + /// <summary> + /// Reads a value of type <see cref="T" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override T ReadValue(IDataReader reader) + { + var context = reader.Context; + + if (context.Config.SerializationPolicy.AllowNonSerializableTypes == false && TypeOf_T.IsSerializable == false) + { + context.Config.DebugContext.LogError("The type " + TypeOf_T.Name + " is not marked as serializable."); + return default(T); + } + + bool exitNode = true; + + string name; + var entry = reader.PeekEntry(out name); + + if (ComplexTypeIsValueType) + { + if (entry == EntryType.Null) + { + context.Config.DebugContext.LogWarning("Expecting complex struct of type " + TypeOf_T.GetNiceFullName() + " but got null value."); + reader.ReadNull(); + return default(T); + } + else if (entry != EntryType.StartOfNode) + { + context.Config.DebugContext.LogWarning("Unexpected entry '" + name + "' of type " + entry.ToString() + ", when " + EntryType.StartOfNode + " was expected. A value has likely been lost."); + reader.SkipEntry(); + return default(T); + } + + try + { + Type expectedType = TypeOf_T; + Type serializedType; + + if (reader.EnterNode(out serializedType)) + { + if (serializedType != expectedType) + { + if (serializedType != null) + { + context.Config.DebugContext.LogWarning("Expected complex struct value " + expectedType.Name + " but the serialized value is of type " + serializedType.Name + "."); + + if (serializedType.IsCastableTo(expectedType)) + { + object value = FormatterLocator.GetFormatter(serializedType, context.Config.SerializationPolicy).Deserialize(reader); + + bool serializedTypeIsNullable = serializedType.IsGenericType && serializedType.GetGenericTypeDefinition() == typeof(Nullable<>); + bool allowCastMethod = !ComplexTypeIsNullable && !serializedTypeIsNullable; + + var castMethod = allowCastMethod ? serializedType.GetCastMethodDelegate(expectedType) : null; + + if (castMethod != null) + { + return (T)castMethod(value); + } + else + { + return (T)value; + } + } + else if (AllowDeserializeInvalidDataForT || reader.Context.Config.AllowDeserializeInvalidData) + { + context.Config.DebugContext.LogWarning("Can't cast serialized type " + serializedType.Name + " into expected type " + expectedType.Name + ". Attempting to deserialize with possibly invalid data. Value may be lost or corrupted for node '" + name + "'."); + return GetBaseFormatter(context.Config.SerializationPolicy).Deserialize(reader); + } + else + { + context.Config.DebugContext.LogWarning("Can't cast serialized type " + serializedType.Name + " into expected type " + expectedType.Name + ". Value lost for node '" + name + "'."); + return default(T); + } + } + else if (AllowDeserializeInvalidDataForT || reader.Context.Config.AllowDeserializeInvalidData) + { + context.Config.DebugContext.LogWarning("Expected complex struct value " + expectedType.Name + " but the serialized type could not be resolved. Attempting to deserialize with possibly invalid data. Value may be lost or corrupted for node '" + name + "'."); + return GetBaseFormatter(context.Config.SerializationPolicy).Deserialize(reader); + } + else + { + context.Config.DebugContext.LogWarning("Expected complex struct value " + expectedType.Name + " but the serialized type could not be resolved. Value lost for node '" + name + "'."); + return default(T); + } + } + else + { + return GetBaseFormatter(context.Config.SerializationPolicy).Deserialize(reader); + } + } + else + { + context.Config.DebugContext.LogError("Failed to enter node '" + name + "'."); + return default(T); + } + } + catch (SerializationAbortException ex) + { + exitNode = false; + throw ex; + } + catch (Exception ex) + { + context.Config.DebugContext.LogException(ex); + return default(T); + } + finally + { + if (exitNode) + { + reader.ExitNode(); + } + } + } + else + { + switch (entry) + { + case EntryType.Null: + { + reader.ReadNull(); + return default(T); + } + + case EntryType.ExternalReferenceByIndex: + { + int index; + reader.ReadExternalReference(out index); + + object value = context.GetExternalObject(index); + + try + { + return (T)value; + } + catch (InvalidCastException) + { + context.Config.DebugContext.LogWarning("Can't cast external reference type " + value.GetType().Name + " into expected type " + TypeOf_T.Name + ". Value lost for node '" + name + "'."); + return default(T); + } + } + + case EntryType.ExternalReferenceByGuid: + { + Guid guid; + reader.ReadExternalReference(out guid); + + object value = context.GetExternalObject(guid); + + try + { + return (T)value; + } + catch (InvalidCastException) + { + context.Config.DebugContext.LogWarning("Can't cast external reference type " + value.GetType().Name + " into expected type " + TypeOf_T.Name + ". Value lost for node '" + name + "'."); + return default(T); + } + } + + case EntryType.ExternalReferenceByString: + { + string id; + reader.ReadExternalReference(out id); + + object value = context.GetExternalObject(id); + + try + { + return (T)value; + } + catch (InvalidCastException) + { + context.Config.DebugContext.LogWarning("Can't cast external reference type " + value.GetType().Name + " into expected type " + TypeOf_T.Name + ". Value lost for node '" + name + "'."); + return default(T); + } + } + + case EntryType.InternalReference: + { + int id; + reader.ReadInternalReference(out id); + + object value = context.GetInternalReference(id); + + try + { + return (T)value; + } + catch (InvalidCastException) + { + context.Config.DebugContext.LogWarning("Can't cast internal reference type " + value.GetType().Name + " into expected type " + TypeOf_T.Name + ". Value lost for node '" + name + "'."); + return default(T); + } + } + + case EntryType.StartOfNode: + { + try + { + Type expectedType = TypeOf_T; + Type serializedType; + int id; + + if (reader.EnterNode(out serializedType)) + { + id = reader.CurrentNodeId; + + T result; + + if (serializedType != null && expectedType != serializedType) // We have type metadata different from the expected type + { + bool success = false; + var isPrimitive = FormatterUtilities.IsPrimitiveType(serializedType); + + bool assignableCast; + + if (ComplexTypeMayBeBoxedValueType && isPrimitive) + { + // It's a boxed primitive type, so simply read that straight and register success + var serializer = Serializer.Get(serializedType); + result = (T)serializer.ReadValueWeak(reader); + success = true; + } + else if ((assignableCast = expectedType.IsAssignableFrom(serializedType)) || serializedType.HasCastDefined(expectedType, false)) + { + try + { + object value; + + if (isPrimitive) + { + var serializer = Serializer.Get(serializedType); + value = serializer.ReadValueWeak(reader); + } + else + { + var alternateFormatter = FormatterLocator.GetFormatter(serializedType, context.Config.SerializationPolicy); + value = alternateFormatter.Deserialize(reader); + } + + if (assignableCast) + { + result = (T)value; + } + else + { + var castMethod = serializedType.GetCastMethodDelegate(expectedType); + + if (castMethod != null) + { + result = (T)castMethod(value); + } + else + { + // Let's just give it a go anyways + result = (T)value; + } + } + + success = true; + } + catch (SerializationAbortException ex) + { + exitNode = false; + throw ex; + } + catch (InvalidCastException) + { + success = false; + result = default(T); + } + } + else if (!ComplexTypeIsAbstract && (AllowDeserializeInvalidDataForT || reader.Context.Config.AllowDeserializeInvalidData)) + { + // We will try to deserialize an instance of T with the invalid data. + context.Config.DebugContext.LogWarning("Can't cast serialized type " + serializedType.Name + " into expected type " + expectedType.Name + ". Attempting to deserialize with invalid data. Value may be lost or corrupted for node '" + name + "'."); + result = GetBaseFormatter(context.Config.SerializationPolicy).Deserialize(reader); + success = true; + } + else + { + // We couldn't cast or use the type, but we still have to deserialize it and register + // the reference so the reference isn't lost if it is referred to further down + // the data stream. + + var alternateFormatter = FormatterLocator.GetFormatter(serializedType, context.Config.SerializationPolicy); + object value = alternateFormatter.Deserialize(reader); + + if (id >= 0) + { + context.RegisterInternalReference(id, value); + } + + result = default(T); + } + + if (!success) + { + // We can't use this + context.Config.DebugContext.LogWarning("Can't cast serialized type " + serializedType.Name + " into expected type " + expectedType.Name + ". Value lost for node '" + name + "'."); + result = default(T); + } + } + else if (ComplexTypeIsAbstract) + { + result = default(T); + } + else + { + result = GetBaseFormatter(context.Config.SerializationPolicy).Deserialize(reader); + } + + if (id >= 0) + { + context.RegisterInternalReference(id, result); + } + + return result; + } + else + { + context.Config.DebugContext.LogError("Failed to enter node '" + name + "'."); + return default(T); + } + } + catch (SerializationAbortException ex) + { + exitNode = false; + throw ex; + } + catch (Exception ex) + { + context.Config.DebugContext.LogException(ex); + return default(T); + } + finally + { + if (exitNode) + { + reader.ExitNode(); + } + } + } + + // + // The below cases are for when we expect an object, but have + // serialized a straight primitive type. In such cases, we can + // often box the primitive type as an object. + // + // Sadly, the exact primitive type might be lost in case of + // integer and floating points numbers, as we don't know what + // type to expect. + // + // To be safe, we read and box the most precise type available. + // + + case EntryType.Boolean: + { + if (!ComplexTypeMayBeBoxedValueType) + { + goto default; + } + + bool value; + reader.ReadBoolean(out value); + return (T)(object)value; + } + + case EntryType.FloatingPoint: + { + if (!ComplexTypeMayBeBoxedValueType) + { + goto default; + } + + double value; + reader.ReadDouble(out value); + return (T)(object)value; + } + + case EntryType.Integer: + { + if (!ComplexTypeMayBeBoxedValueType) + { + goto default; + } + + long value; + reader.ReadInt64(out value); + return (T)(object)value; + } + + case EntryType.String: + { + if (!ComplexTypeMayBeBoxedValueType) + { + goto default; + } + + string value; + reader.ReadString(out value); + return (T)(object)value; + } + + case EntryType.Guid: + { + if (!ComplexTypeMayBeBoxedValueType) + { + goto default; + } + + Guid value; + reader.ReadGuid(out value); + return (T)(object)value; + } + + default: + + // Lost value somehow + context.Config.DebugContext.LogWarning("Unexpected entry of type " + entry.ToString() + ", when a reference or node start was expected. A value has been lost."); + reader.SkipEntry(); + return default(T); + } + } + } + + private static IFormatter<T> GetBaseFormatter(ISerializationPolicy serializationPolicy) + { + // This is an optimization - it's a lot cheaper to compare three references and do a null check, + // than it is to look something up in a dictionary. By far most of the time, we will be using + // one of these three policies. + + if (object.ReferenceEquals(serializationPolicy, UnityPolicy)) + { + if (UnityPolicyFormatter == null) + { + UnityPolicyFormatter = FormatterLocator.GetFormatter<T>(UnityPolicy); + } + + return UnityPolicyFormatter; + } + else if (object.ReferenceEquals(serializationPolicy, EverythingPolicy)) + { + if (EverythingPolicyFormatter == null) + { + EverythingPolicyFormatter = FormatterLocator.GetFormatter<T>(EverythingPolicy); + } + + return EverythingPolicyFormatter; + } + else if (object.ReferenceEquals(serializationPolicy, StrictPolicy)) + { + if (StrictPolicyFormatter == null) + { + StrictPolicyFormatter = FormatterLocator.GetFormatter<T>(StrictPolicy); + } + + return StrictPolicyFormatter; + } + + IFormatter<T> formatter; + + lock (FormattersByPolicy_LOCK) + { + if (!FormattersByPolicy.TryGetValue(serializationPolicy, out formatter)) + { + formatter = FormatterLocator.GetFormatter<T>(serializationPolicy); + FormattersByPolicy.Add(serializationPolicy, formatter); + } + } + + return formatter; + } + + /// <summary> + /// Writes a value of type <see cref="T" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, T value, IDataWriter writer) + { + var context = writer.Context; + var policy = context.Config.SerializationPolicy; + + if (policy.AllowNonSerializableTypes == false && TypeOf_T.IsSerializable == false) + { + context.Config.DebugContext.LogError("The type " + TypeOf_T.Name + " is not marked as serializable."); + return; + } + + FireOnSerializedType(); + + if (ComplexTypeIsValueType) + { + bool endNode = true; + + try + { + writer.BeginStructNode(name, TypeOf_T); + GetBaseFormatter(policy).Serialize(value, writer); + } + catch (SerializationAbortException ex) + { + endNode = false; + throw ex; + } + finally + { + if (endNode) + { + writer.EndNode(name); + } + } + } + else + { + int id; + int index; + string strId; + Guid guid; + + bool endNode = true; + + if (object.ReferenceEquals(value, null)) + { + writer.WriteNull(name); + } + else if (context.TryRegisterExternalReference(value, out index)) + { + writer.WriteExternalReference(name, index); + } + else if (context.TryRegisterExternalReference(value, out guid)) + { + writer.WriteExternalReference(name, guid); + } + else if (context.TryRegisterExternalReference(value, out strId)) + { + writer.WriteExternalReference(name, strId); + } + else if (context.TryRegisterInternalReference(value, out id)) + { + Type type = value.GetType(); // Get type of actual stored object + + if (ComplexTypeMayBeBoxedValueType && FormatterUtilities.IsPrimitiveType(type)) + // It's a boxed primitive type + { + try + { + writer.BeginReferenceNode(name, type, id); + + var serializer = Serializer.Get(type); + serializer.WriteValueWeak(value, writer); + } + catch (SerializationAbortException ex) + { + endNode = false; + throw ex; + } + finally + { + if (endNode) + { + writer.EndNode(name); + } + } + } + else + { + IFormatter formatter; + + if (object.ReferenceEquals(type, TypeOf_T)) + { + formatter = GetBaseFormatter(policy); + } + else + { + formatter = FormatterLocator.GetFormatter(type, policy); + } + + try + { + writer.BeginReferenceNode(name, type, id); + formatter.Serialize(value, writer); + } + catch (SerializationAbortException ex) + { + endNode = false; + throw ex; + } + finally + { + if (endNode) + { + writer.EndNode(name); + } + } + } + } + else + { + writer.WriteInternalReference(name, id); + } + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ComplexTypeSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ComplexTypeSerializer.cs.meta new file mode 100644 index 00000000..4d9d4b05 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/ComplexTypeSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a2a43b51cef79fd0e85028650394b55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DecimalSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DecimalSerializer.cs new file mode 100644 index 00000000..8e2f7540 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DecimalSerializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="DecimalSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="decimal"/> type. + /// </summary> + /// <seealso cref="Serializer{System.Decimal}" /> + public sealed class DecimalSerializer : Serializer<decimal> + { + /// <summary> + /// Reads a value of type <see cref="decimal" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override decimal ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.FloatingPoint || entry == EntryType.Integer) + { + decimal value; + if (reader.ReadDecimal(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.FloatingPoint.ToString() + " or " + EntryType.Integer.ToString() + ", but got entry of type " + entry.ToString()); + reader.SkipEntry(); + return default(decimal); + } + } + + /// <summary> + /// Writes a value of type <see cref="decimal" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, decimal value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteDecimal(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DecimalSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DecimalSerializer.cs.meta new file mode 100644 index 00000000..9037506c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DecimalSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 50c67937d611e4749188b838e4cff5dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DoubleSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DoubleSerializer.cs new file mode 100644 index 00000000..d2b24f6f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DoubleSerializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="DoubleSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="double"/> type. + /// </summary> + /// <seealso cref="Serializer{System.Double}" /> + public sealed class DoubleSerializer : Serializer<double> + { + /// <summary> + /// Reads a value of type <see cref="double" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override double ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.FloatingPoint || entry == EntryType.Integer) + { + double value; + if (reader.ReadDouble(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.FloatingPoint.ToString() + " or " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(double); + } + } + + /// <summary> + /// Writes a value of type <see cref="double" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, double value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteDouble(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DoubleSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DoubleSerializer.cs.meta new file mode 100644 index 00000000..0b83aa6d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/DoubleSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9fc4716f683bc313c24bfa537cdd97f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/EnumSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/EnumSerializer.cs new file mode 100644 index 00000000..31356eb2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/EnumSerializer.cs @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------- +// <copyright file="EnumSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Serializer for all enums. + /// </summary> + /// <typeparam name="T">The type of the enum to serialize and deserialize.</typeparam> + /// <seealso cref="Serializer{T}" /> +#if CSHARP_7_3_OR_NEWER + public unsafe sealed class EnumSerializer<T> : Serializer<T> where T : unmanaged, Enum + { + private static readonly int SizeOf_T = sizeof(T); +#else + public sealed class EnumSerializer<T> : Serializer<T> + { + static EnumSerializer() + { + if (typeof(T).IsEnum == false) + { + throw new Exception("Type " + typeof(T).Name + " is not an enum."); + } + } +#endif + + /// <summary> + /// Reads an enum value of type <see cref="T" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override T ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Integer) + { + ulong value; + if (reader.ReadUInt64(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + +#if CSHARP_7_3_OR_NEWER + return *(T*)&value; +#else + return (T)Enum.ToObject(typeof(T), value); +#endif + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(T); + } + } + + /// <summary> + /// Writes an enum value of type <see cref="T" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, T value, IDataWriter writer) + { + ulong ul; + + FireOnSerializedType(); + +#if CSHARP_7_3_OR_NEWER + byte* toPtr = (byte*)&ul; + byte* fromPtr = (byte*)&value; + + for (int i = 0; i < SizeOf_T; i++) + { + *toPtr++ = *fromPtr++; + } +#else + try + { + ul = Convert.ToUInt64(value as Enum); + } + catch (OverflowException) + { + unchecked + { + ul = (ulong)Convert.ToInt64(value as Enum); + } + } +#endif + + writer.WriteUInt64(name, ul); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/EnumSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/EnumSerializer.cs.meta new file mode 100644 index 00000000..9ad82525 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/EnumSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a5d23b139cd8e692702aa431b071d07 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/GuidSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/GuidSerializer.cs new file mode 100644 index 00000000..ab6d15b1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/GuidSerializer.cs @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------- +// <copyright file="GuidSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using System.Globalization; + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Serializer for the <see cref="Guid"/> type. + /// </summary> + /// <seealso cref="Serializer{System.Guid}" /> + public sealed class GuidSerializer : Serializer<Guid> + { + /// <summary> + /// Reads a value of type <see cref="Guid" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override Guid ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Guid) + { + Guid value; + if (reader.ReadGuid(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Guid.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(Guid); + } + } + + /// <summary> + /// Writes a value of type <see cref="Guid" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, Guid value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteGuid(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/GuidSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/GuidSerializer.cs.meta new file mode 100644 index 00000000..c0f9714b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/GuidSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 19dcfa9f6a40979fc2b6c3ae0f24b67c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int16Serializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int16Serializer.cs new file mode 100644 index 00000000..fe2aef72 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int16Serializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="Int16Serializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="short"/> type. + /// </summary> + /// <seealso cref="Serializer{System.Int16}" /> + public sealed class Int16Serializer : Serializer<short> + { + /// <summary> + /// Reads a value of type <see cref="short" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override short ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Integer) + { + short value; + if (reader.ReadInt16(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(short); + } + } + + /// <summary> + /// Writes a value of type <see cref="short" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, short value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteInt16(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int16Serializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int16Serializer.cs.meta new file mode 100644 index 00000000..d6a68035 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int16Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d280b44f7c75a9a18484a84745998130 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int32Serializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int32Serializer.cs new file mode 100644 index 00000000..276512a2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int32Serializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="Int32Serializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="int"/> type. + /// </summary> + /// <seealso cref="Serializer{System.Int32}" /> + public sealed class Int32Serializer : Serializer<int> + { + /// <summary> + /// Reads a value of type <see cref="int" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override int ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Integer) + { + int value; + if (reader.ReadInt32(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(int); + } + } + + /// <summary> + /// Writes a value of type <see cref="int" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, int value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteInt32(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int32Serializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int32Serializer.cs.meta new file mode 100644 index 00000000..470eae82 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int32Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eafebb70813195e03b1ba467931eb686 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int64Serializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int64Serializer.cs new file mode 100644 index 00000000..18bcc95e --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int64Serializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="Int64Serializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="long"/> type. + /// </summary> + /// <seealso cref="Serializer{System.Int64}" /> + public sealed class Int64Serializer : Serializer<long> + { + /// <summary> + /// Reads a value of type <see cref="long" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override long ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Integer) + { + long value; + if (reader.ReadInt64(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(long); + } + } + + /// <summary> + /// Writes a value of type <see cref="long" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, long value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteInt64(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int64Serializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int64Serializer.cs.meta new file mode 100644 index 00000000..a3471b4e --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Int64Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afe45c48508431a62aba886d943d8501 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/IntPtrSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/IntPtrSerializer.cs new file mode 100644 index 00000000..959a2c0c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/IntPtrSerializer.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// <copyright file="IntPtrSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Serializer for the <see cref="IntPtr"/> type. + /// </summary> + /// <seealso cref="Serializer{System.IntPtr}" /> + public sealed class IntPtrSerializer : Serializer<IntPtr> + { + /// <summary> + /// Reads a value of type <see cref="IntPtr" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override IntPtr ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Integer) + { + long value; + if (reader.ReadInt64(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return new IntPtr(value); + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(IntPtr); + } + } + + /// <summary> + /// Writes a value of type <see cref="IntPtr" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, IntPtr value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteInt64(name, (long)value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/IntPtrSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/IntPtrSerializer.cs.meta new file mode 100644 index 00000000..eaff995b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/IntPtrSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ccaffe3090611c2ada67d49cf834771 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SByteSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SByteSerializer.cs new file mode 100644 index 00000000..7c7b2cb6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SByteSerializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="SByteSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="sbyte"/> type. + /// </summary> + /// <seealso cref="Serializer{System.SByte}" /> + public sealed class SByteSerializer : Serializer<sbyte> + { + /// <summary> + /// Reads a value of type <see cref="sbyte" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override sbyte ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Integer) + { + sbyte value; + if (reader.ReadSByte(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(sbyte); + } + } + + /// <summary> + /// Writes a value of type <see cref="sbyte" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, sbyte value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteSByte(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SByteSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SByteSerializer.cs.meta new file mode 100644 index 00000000..21374495 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SByteSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88f3ec418fdfdd7eabd6134f1de91991 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Serializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Serializer.cs new file mode 100644 index 00000000..727d37f6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Serializer.cs @@ -0,0 +1,311 @@ +//----------------------------------------------------------------------- +// <copyright file="Serializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using VRC.Udon.Serialization.OdinSerializer.Utilities; + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Reflection; + + /// <summary> + /// Serializes and deserializes a given type, and wraps serialization and deserialization with all the proper calls to free formatters from tedious boilerplate. + /// <para /> + /// Whenever serializing or deserializing anything, it is *strongly recommended* to use <see cref="Serializer.Get{T}"/> to get a proper wrapping serializer for that type. + /// <para /> + /// NOTE: This class should NOT be inherited from; it is hard-coded into the system. + /// <para /> + /// To extend the serialization system, instead create custom formatters, which are used by the <see cref="ComplexTypeSerializer{T}"/> class. + /// </summary> + public abstract class Serializer + { + private static readonly Dictionary<Type, Type> PrimitiveReaderWriterTypes = new Dictionary<Type, Type>() + { + { typeof(char), typeof(CharSerializer) }, + { typeof(string), typeof(StringSerializer) }, + { typeof(sbyte), typeof(SByteSerializer) }, + { typeof(short), typeof(Int16Serializer) }, + { typeof(int), typeof(Int32Serializer) }, + { typeof(long), typeof(Int64Serializer) }, + { typeof(byte), typeof(ByteSerializer) }, + { typeof(ushort), typeof(UInt16Serializer) }, + { typeof(uint), typeof(UInt32Serializer) }, + { typeof(ulong), typeof(UInt64Serializer) }, + { typeof(decimal), typeof(DecimalSerializer) }, + { typeof(bool), typeof(BooleanSerializer) }, + { typeof(float), typeof(SingleSerializer) }, + { typeof(double), typeof(DoubleSerializer) }, + { typeof(IntPtr), typeof(IntPtrSerializer) }, + { typeof(UIntPtr), typeof(UIntPtrSerializer) }, + { typeof(Guid), typeof(GuidSerializer) } + }; + + private static readonly object LOCK = new object(); + + private static readonly Dictionary<Type, Serializer> ReaderWriterCache = new Dictionary<Type, Serializer>(FastTypeComparer.Instance); + +#if UNITY_EDITOR + + /// <summary> + /// Editor-only event that fires whenever a serializer serializes a type. + /// </summary> + public static event Action<Type> OnSerializedType; + +#endif + + /// <summary> + /// Fires the <see cref="OnSerializedType"/> event. + /// </summary> + [Conditional("UNITY_EDITOR")] + protected static void FireOnSerializedType(Type type) + { +#if UNITY_EDITOR + if (OnSerializedType != null) + { + OnSerializedType(type); + } +#endif + } + + /// <summary> + /// Gets a <see cref="Serializer"/> for the given value. If the value is null, it will be treated as a value of type <see cref="object"/>. + /// </summary> + /// <param name="value">The value to get a <see cref="Serializer"/> for.</param> + /// <returns>A <see cref="Serializer"/> for the given value.</returns> + public static Serializer GetForValue(object value) + { + if (object.ReferenceEquals(value, null)) + { + return Get(typeof(object)); + } + else + { + return Get(value.GetType()); + } + } + + /// <summary> + /// Gets a <see cref="Serializer"/> for type T. + /// </summary> + /// <typeparam name="T">The type to get a <see cref="Serializer"/> for.</typeparam> + /// <returns>A <see cref="Serializer"/> for type T.</returns> + public static Serializer<T> Get<T>() + { + return (Serializer<T>)Serializer.Get(typeof(T)); + } + + /// <summary> + /// Gets a <see cref="Serializer"/> for the given type. + /// </summary> + /// <param name="type">The type to get a <see cref="Serializer"/> for.</param> + /// <returns>A <see cref="Serializer"/> for the given type.</returns> + /// <exception cref="System.ArgumentNullException">The type argument is null.</exception> + public static Serializer Get(Type type) + { + if (type == null) + { + throw new ArgumentNullException(); + } + + Serializer result; + + lock (LOCK) + { + if (ReaderWriterCache.TryGetValue(type, out result) == false) + { + result = Create(type); + ReaderWriterCache.Add(type, result); + } + } + + return result; + } + + /// <summary> + /// Reads a value weakly, casting it into object. Use this method if you don't know what type you're going to be working with at compile time. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns>The value which has been read.</returns> + public abstract object ReadValueWeak(IDataReader reader); + + /// <summary> + /// Writes a weakly typed value. Use this method if you don't know what type you're going to be working with at compile time. + /// </summary> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public void WriteValueWeak(object value, IDataWriter writer) + { + this.WriteValueWeak(null, value, writer); + } + + /// <summary> + /// Writes a weakly typed value with a given name. Use this method if you don't know what type you're going to be working with at compile time. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public abstract void WriteValueWeak(string name, object value, IDataWriter writer); + + private static Serializer Create(Type type) + { + try + { + Type resultType = null; + + if (type.IsEnum) + { + resultType = typeof(EnumSerializer<>).MakeGenericType(type); + } + else if (FormatterUtilities.IsPrimitiveType(type)) + { + try + { + resultType = PrimitiveReaderWriterTypes[type]; + } + catch (KeyNotFoundException) + { + UnityEngine.Debug.LogError("Failed to find primitive serializer for " + type.Name); + } + } + else + { + resultType = typeof(ComplexTypeSerializer<>).MakeGenericType(type); + } + + return (Serializer)Activator.CreateInstance(resultType); + } + // System.ExecutionEngineException is marked obsolete in .NET 4.6 + // That's all very good for .NET, but Unity still uses it! +#pragma warning disable 618 + catch (TargetInvocationException ex) + { + if (ex.GetBaseException() is ExecutionEngineException) + { + LogAOTError(type, ex.GetBaseException() as ExecutionEngineException); + return null; + } + else + { + throw ex; + } + } + catch (TypeInitializationException ex) + { + if (ex.GetBaseException() is ExecutionEngineException) + { + LogAOTError(type, ex.GetBaseException() as ExecutionEngineException); + return null; + } + else + { + throw ex; + } + } + catch (ExecutionEngineException ex) + { + LogAOTError(type, ex); + return null; + } + } + + private static void LogAOTError(Type type, ExecutionEngineException ex) + { + UnityEngine.Debug.LogError("No AOT serializer was pre-generated for the type '" + type.GetNiceFullName() + "'. " + + "Please use Odin's AOT generation feature to generate an AOT dll before building, and ensure that '" + + type.GetNiceFullName() + "' is in the list of supported types after a scan. If it is not, please " + + "report an issue and add it to the list manually."); + + throw new SerializationAbortException("AOT serializer was missing for type '" + type.GetNiceFullName() + "'."); + } + } + +#pragma warning restore 618 + + /// <summary> + /// Serializes and deserializes the type <see cref="T"/>, and wraps serialization and deserialization with all the proper calls to free formatters from tedious boilerplate. + /// <para /> + /// Whenever serializing or deserializing anything, it is *strongly recommended* to use <see cref="Serializer.Get{T}"/> to get a proper wrapping serializer for that type. + /// <para /> + /// NOTE: This class should NOT be inherited from; it is hard-coded into the system. + /// <para /> + /// To extend the serialization system, instead create custom formatters, which are used by the <see cref="ComplexTypeSerializer{T}"/> class. + /// </summary> + /// <typeparam name="T">The type which the <see cref="Serializer{T}"/> can serialize and deserialize.</typeparam> + public abstract class Serializer<T> : Serializer + { + /// <summary> + /// Reads a value of type <see cref="T"/> weakly, casting it into object. Use this method if you don't know what type you're going to be working with at compile time. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override object ReadValueWeak(IDataReader reader) + { + return this.ReadValue(reader); + } + + /// <summary> + /// Writes a weakly typed value of type <see cref="T"/> with a given name. Use this method if you don't know what type you're going to be working with at compile time. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValueWeak(string name, object value, IDataWriter writer) + { + this.WriteValue(name, (T)value, writer); + } + + /// <summary> + /// Reads a value of type <see cref="T"/>. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public abstract T ReadValue(IDataReader reader); + + /// <summary> + /// Writes a value of type <see cref="T"/>. + /// </summary> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public void WriteValue(T value, IDataWriter writer) + { + this.WriteValue(null, value, writer); + } + + /// <summary> + /// Writes a value of type <see cref="T"/> with a given name. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public abstract void WriteValue(string name, T value, IDataWriter writer); + + /// <summary> + /// Fires the <see cref="OnSerializedType"/> event with the T generic argument of the serializer. + /// </summary> + [Conditional("UNITY_EDITOR")] + protected static void FireOnSerializedType() + { + FireOnSerializedType(typeof(T)); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Serializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Serializer.cs.meta new file mode 100644 index 00000000..4d8f540e --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 29261eaea99f2d34c42cdc0b04f95daa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SingleSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SingleSerializer.cs new file mode 100644 index 00000000..b6195764 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SingleSerializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="SingleSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="float"/> type. + /// </summary> + /// <seealso cref="Serializer{System.Single}" /> + public sealed class SingleSerializer : Serializer<float> + { + /// <summary> + /// Reads a value of type <see cref="float" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override float ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.FloatingPoint || entry == EntryType.Integer) + { + float value; + if (reader.ReadSingle(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.FloatingPoint.ToString() + " or " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(float); + } + } + + /// <summary> + /// Writes a value of type <see cref="float" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, float value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteSingle(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SingleSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SingleSerializer.cs.meta new file mode 100644 index 00000000..f94a3e31 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/SingleSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7aa356971fd0b66eb59875b278fa7f03 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/StringSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/StringSerializer.cs new file mode 100644 index 00000000..9a048ffe --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/StringSerializer.cs @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------- +// <copyright file="StringSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="string"/> type. + /// </summary> + /// <seealso cref="Serializer{System.String}" /> + public sealed class StringSerializer : Serializer<string> + { + /// <summary> + /// Reads a value of type <see cref="string" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override string ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.String) + { + string value; + if (reader.ReadString(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else if (entry == EntryType.Null) + { + if (reader.ReadNull() == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return null; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.String.ToString() + " or " + EntryType.Null.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(string); + } + } + + /// <summary> + /// Writes a value of type <see cref="string" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, string value, IDataWriter writer) + { + FireOnSerializedType(); + + if (value == null) + { + writer.WriteNull(name); + } + else + { + writer.WriteString(name, value); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/StringSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/StringSerializer.cs.meta new file mode 100644 index 00000000..e2ce7c64 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/StringSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 85996580a8691185d06ec342c5c43747 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt16Serializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt16Serializer.cs new file mode 100644 index 00000000..f35cb6e5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt16Serializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="UInt16Serializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="ushort"/> type. + /// </summary> + /// <seealso cref="Serializer{System.UInt16}" /> + public sealed class UInt16Serializer : Serializer<ushort> + { + /// <summary> + /// Reads a value of type <see cref="ushort" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override ushort ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Integer) + { + ushort value; + if (reader.ReadUInt16(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(ushort); + } + } + + /// <summary> + /// Writes a value of type <see cref="ulong" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, ushort value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteUInt16(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt16Serializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt16Serializer.cs.meta new file mode 100644 index 00000000..0e7d391d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt16Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3936194ea64890e11a7db8474eb0bbcf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt32Serializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt32Serializer.cs new file mode 100644 index 00000000..9b222e9d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt32Serializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="UInt32Serializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="uint"/> type. + /// </summary> + /// <seealso cref="Serializer{System.UInt32}" /> + public sealed class UInt32Serializer : Serializer<uint> + { + /// <summary> + /// Reads a value of type <see cref="uint" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override uint ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Integer) + { + uint value; + if (reader.ReadUInt32(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(uint); + } + } + + /// <summary> + /// Writes a value of type <see cref="uint" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, uint value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteUInt32(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt32Serializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt32Serializer.cs.meta new file mode 100644 index 00000000..0825ae2d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt32Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f30e426f88b471e498dd1853b7bbaee6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt64Serializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt64Serializer.cs new file mode 100644 index 00000000..5ac77266 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt64Serializer.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="UInt64Serializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Serializer for the <see cref="ulong"/> type. + /// </summary> + /// <seealso cref="Serializer{System.UInt64}" /> + public sealed class UInt64Serializer : Serializer<ulong> + { + /// <summary> + /// Reads a value of type <see cref="ulong" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override ulong ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Integer) + { + ulong value; + if (reader.ReadUInt64(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return value; + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(ulong); + } + } + + /// <summary> + /// Writes a value of type <see cref="ulong" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, ulong value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteUInt64(name, value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt64Serializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt64Serializer.cs.meta new file mode 100644 index 00000000..71e0ca5a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UInt64Serializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f55c085325e12800428d01e3535cb297 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UIntPtrSerializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UIntPtrSerializer.cs new file mode 100644 index 00000000..31fab0cd --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UIntPtrSerializer.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// <copyright file="IntPtrSerializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + /// <summary> + /// Serializer for the <see cref="UIntPtr"/> type. + /// </summary> + /// <seealso cref="Serializer{System.UIntPtr}" /> + public sealed class UIntPtrSerializer : Serializer<UIntPtr> + { + /// <summary> + /// Reads a value of type <see cref="UIntPtr" />. + /// </summary> + /// <param name="reader">The reader to use.</param> + /// <returns> + /// The value which has been read. + /// </returns> + public override UIntPtr ReadValue(IDataReader reader) + { + string name; + var entry = reader.PeekEntry(out name); + + if (entry == EntryType.Integer) + { + ulong value; + if (reader.ReadUInt64(out value) == false) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read entry '" + name + "' of type " + entry.ToString()); + } + return new UIntPtr(value); + } + else + { + reader.Context.Config.DebugContext.LogWarning("Expected entry of type " + EntryType.Integer.ToString() + ", but got entry '" + name + "' of type " + entry.ToString()); + reader.SkipEntry(); + return default(UIntPtr); + } + } + + /// <summary> + /// Writes a value of type <see cref="UIntPtr" />. + /// </summary> + /// <param name="name">The name of the value to write.</param> + /// <param name="value">The value to write.</param> + /// <param name="writer">The writer to use.</param> + public override void WriteValue(string name, UIntPtr value, IDataWriter writer) + { + FireOnSerializedType(); + writer.WriteUInt64(name, (ulong)value); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UIntPtrSerializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UIntPtrSerializer.cs.meta new file mode 100644 index 00000000..fcf7cef6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Core/Serializers/UIntPtrSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0ee9dd19c234e4b16c835b9188459e36 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/LICENSE b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/LICENSE new file mode 100644 index 00000000..2bb9ad24 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/LICENSE.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/LICENSE.meta new file mode 100644 index 00000000..082f70ad --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/LICENSE.meta @@ -0,0 +1,7 @@ + +fileFormatVersion: 2 +guid: ec4e6da38017fe7df076afceb30fa17c +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration.meta new file mode 100644 index 00000000..6c23a637 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a85a221e3caaeb248bffd11766d00c61 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportScanner.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportScanner.cs new file mode 100644 index 00000000..672d524f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportScanner.cs @@ -0,0 +1,738 @@ +//----------------------------------------------------------------------- +// <copyright file="AOTSupportScanner.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +#if UNITY_EDITOR + +namespace VRC.Udon.Serialization.OdinSerializer.Editor +{ + using Utilities; + using System; + using System.Collections.Generic; + using System.Linq; + using UnityEditor; + using UnityEditor.SceneManagement; + using UnityEngine; + using System.Reflection; + using UnityEngine.SceneManagement; + using System.Collections; + + public sealed class AOTSupportScanner : IDisposable + { + private bool scanning; + private bool allowRegisteringScannedTypes; + private HashSet<Type> seenSerializedTypes = new HashSet<Type>(); + + private static System.Diagnostics.Stopwatch smartProgressBarWatch = System.Diagnostics.Stopwatch.StartNew(); + private static int smartProgressBarDisplaysSinceLastUpdate = 0; + + private static readonly MethodInfo PlayerSettings_GetPreloadedAssets_Method = typeof(PlayerSettings).GetMethod("GetPreloadedAssets", BindingFlags.Public | BindingFlags.Static, null, Type.EmptyTypes, null); + private static readonly PropertyInfo Debug_Logger_Property = typeof(Debug).GetProperty("unityLogger") ?? typeof(Debug).GetProperty("logger"); + + public void BeginScan() + { + this.scanning = true; + allowRegisteringScannedTypes = false; + + this.seenSerializedTypes.Clear(); + + FormatterLocator.OnLocatedEmittableFormatterForType += this.OnLocatedEmitType; + FormatterLocator.OnLocatedFormatter += this.OnLocatedFormatter; + Serializer.OnSerializedType += this.OnSerializedType; + } + + public bool ScanPreloadedAssets(bool showProgressBar) + { + // The API does not exist in this version of Unity + if (PlayerSettings_GetPreloadedAssets_Method == null) return true; + + UnityEngine.Object[] assets = (UnityEngine.Object[])PlayerSettings_GetPreloadedAssets_Method.Invoke(null, null); + + if (assets == null) return true; + + try + { + for (int i = 0; i < assets.Length; i++) + { + if (showProgressBar && DisplaySmartUpdatingCancellableProgressBar("Scanning preloaded assets for AOT support", (i + 1) + " / " + assets.Length, (float)i / assets.Length)) + { + return false; + } + + var asset = assets[i]; + + if (asset == null) continue; + + if (AssetDatabase.Contains(asset)) + { + // Scan the asset and all its dependencies + var path = AssetDatabase.GetAssetPath(asset); + this.ScanAsset(path, true); + } + else + { + // Just scan the asset + this.ScanObject(asset); + } + } + } + finally + { + if (showProgressBar) + { + EditorUtility.ClearProgressBar(); + } + } + + return true; + } + + public bool ScanAssetBundle(string bundle) + { + string[] assets = AssetDatabase.GetAssetPathsFromAssetBundle(bundle); + + foreach (var asset in assets) + { + this.ScanAsset(asset, true); + } + + return true; + } + + public bool ScanAllAssetBundles(bool showProgressBar) + { + try + { + string[] bundles = AssetDatabase.GetAllAssetBundleNames(); + + for (int i = 0; i < bundles.Length; i++) + { + var bundle = bundles[i]; + + if (showProgressBar && DisplaySmartUpdatingCancellableProgressBar("Scanning asset bundles for AOT support", bundle, (float)i / bundles.Length)) + { + return false; + } + + this.ScanAssetBundle(bundle); + } + } + finally + { + if (showProgressBar) + { + EditorUtility.ClearProgressBar(); + } + } + + return true; + } + + public bool ScanAllAddressables(bool includeAssetDependencies, bool showProgressBar) + { + // We don't know whether the addressables package is installed or not. So... needs must. + // Our only real choice is to utilize reflection that's stocked to the brim with failsafes + // and error logging. + // + // Truly, the code below should not have needed to be written. + + // The following section is the code as it would be without reflection. Please modify this + // code reference to be accurate if the reflection code is changed. + + /* + + var settings = UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings; + + if (settings != null && settings.groups != null) + { + foreach (AddressableAssetGroup group in settings.groups) + { + if (group.HasSchema(typeof(PlayerDataGroupSchema))) continue; + + List<AddressableAssetEntry> results = new List<AddressableAssetEntry>(); + + group.GatherAllAssets(results, true, true, true, null); + + foreach (var result in results) + { + this.ScanAsset(result.AssetPath, includeAssetDependencies); + } + } + } + + */ + + bool progressBarWasDisplayed = false; + + try + { + Type AddressableAssetSettingsDefaultObject_Type = TwoWaySerializationBinder.Default.BindToType("UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject"); + if (AddressableAssetSettingsDefaultObject_Type == null) return true; + PropertyInfo AddressableAssetSettingsDefaultObject_Settings = AddressableAssetSettingsDefaultObject_Type.GetProperty("Settings"); + if (AddressableAssetSettingsDefaultObject_Settings == null) throw new NotSupportedException("AddressableAssetSettingsDefaultObject.Settings property not found"); + ScriptableObject settings = (ScriptableObject)AddressableAssetSettingsDefaultObject_Settings.GetValue(null, null); + + if (settings == null) return true; + + Type AddressableAssetSettings_Type = settings.GetType(); + PropertyInfo AddressableAssetSettings_groups = AddressableAssetSettings_Type.GetProperty("groups"); + if (AddressableAssetSettings_groups == null) throw new NotSupportedException("AddressableAssetSettings.groups property not found"); + + IList groups = (IList)AddressableAssetSettings_groups.GetValue(settings, null); + + if (groups == null) return true; + + Type PlayerDataGroupSchema_Type = TwoWaySerializationBinder.Default.BindToType("UnityEditor.AddressableAssets.Settings.GroupSchemas.PlayerDataGroupSchema"); + if (PlayerDataGroupSchema_Type == null) throw new NotSupportedException("PlayerDataGroupSchema type not found"); + + Type AddressableAssetGroup_Type = null; + MethodInfo AddressableAssetGroup_HasSchema = null; + MethodInfo AddressableAssetGroup_GatherAllAssets = null; + + Type AddressableAssetEntry_Type = TwoWaySerializationBinder.Default.BindToType("UnityEditor.AddressableAssets.Settings.AddressableAssetEntry"); + if (AddressableAssetEntry_Type == null) throw new NotSupportedException("AddressableAssetEntry type not found"); + Type List_AddressableAssetEntry_Type = typeof(List<>).MakeGenericType(AddressableAssetEntry_Type); + Type Func_AddressableAssetEntry_bool_Type = typeof(Func<,>).MakeGenericType(AddressableAssetEntry_Type, typeof(bool)); + PropertyInfo AddressableAssetEntry_AssetPath = AddressableAssetEntry_Type.GetProperty("AssetPath"); + if (AddressableAssetEntry_AssetPath == null) throw new NotSupportedException("AddressableAssetEntry.AssetPath property not found"); + + foreach (object groupObj in groups) + { + ScriptableObject group = (ScriptableObject)groupObj; + if (group == null) continue; + + string groupName = group.name; + + if (AddressableAssetGroup_Type == null) + { + AddressableAssetGroup_Type = group.GetType(); + AddressableAssetGroup_HasSchema = AddressableAssetGroup_Type.GetMethod("HasSchema", Flags.InstancePublic, null, new Type[] { typeof(Type) }, null); + if (AddressableAssetGroup_HasSchema == null) throw new NotSupportedException("AddressableAssetGroup.HasSchema(Type type) method not found"); + AddressableAssetGroup_GatherAllAssets = AddressableAssetGroup_Type.GetMethod("GatherAllAssets", Flags.InstancePublic, null, new Type[] { List_AddressableAssetEntry_Type, typeof(bool), typeof(bool), typeof(bool), Func_AddressableAssetEntry_bool_Type }, null); + if (AddressableAssetGroup_GatherAllAssets == null) throw new NotSupportedException("AddressableAssetGroup.GatherAllAssets(List<AddressableAssetEntry> results, bool includeSelf, bool recurseAll, bool includeSubObjects, Func<AddressableAssetEntry, bool> entryFilter) method not found"); + } + + bool hasPlayerDataGroupSchema = (bool)AddressableAssetGroup_HasSchema.Invoke(group, new object[] { PlayerDataGroupSchema_Type }); + if (hasPlayerDataGroupSchema) continue; // Skip this group, since it contains all the player data such as resources and build scenes, and we're scanning that separately + + IList results = (IList)Activator.CreateInstance(List_AddressableAssetEntry_Type); + + AddressableAssetGroup_GatherAllAssets.Invoke(group, new object[] { results, true, true, true, null }); + + for (int i = 0; i < results.Count; i++) + { + object entry = (object)results[i]; + if (entry == null) continue; + string assetPath = (string)AddressableAssetEntry_AssetPath.GetValue(entry, null); + + if (showProgressBar) + { + progressBarWasDisplayed = true; + + if (DisplaySmartUpdatingCancellableProgressBar("Scanning addressables for AOT support", groupName + ": " + assetPath, (float)i / results.Count)) + { + return false; + } + } + + // Finally! + this.ScanAsset(assetPath, includeAssetDependencies); + } + } + } + catch (NotSupportedException ex) + { + Debug.LogWarning("Could not AOT scan Addressables assets due to missing APIs: " + ex.Message); + } + catch (Exception ex) + { + Debug.LogError("Scanning addressables failed with the following exception..."); + Debug.LogException(ex); + } + finally + { + if (progressBarWasDisplayed) + { + EditorUtility.ClearProgressBar(); + } + } + + return true; + } + + public bool ScanAllResources(bool includeResourceDependencies, bool showProgressBar, List<string> resourcesPaths = null) + { + if (resourcesPaths == null) + { + resourcesPaths = new List<string>() {""}; + } + + try + { + if (showProgressBar && DisplaySmartUpdatingCancellableProgressBar("Scanning resources for AOT support", "Loading resource assets", 0f)) + { + return false; + } + + var resourcesPathsSet = new HashSet<string>(); + for (int i = 0; i < resourcesPaths.Count; i++) + { + var resourcesPath = resourcesPaths[i]; + + if (showProgressBar && DisplaySmartUpdatingCancellableProgressBar("Listing resources for AOT support", resourcesPath, (float)i / resourcesPaths.Count)) + { + return false; + } + + var resources = Resources.LoadAll(resourcesPath); + + foreach (var resource in resources) + { + try + { + var assetPath = AssetDatabase.GetAssetPath(resource); + + if (assetPath != null) + { + resourcesPathsSet.Add(assetPath); + } + } + catch (MissingReferenceException ex) + { + Debug.LogError("A resource threw a missing reference exception when scanning. Skipping resource and continuing scan.", resource); + Debug.LogException(ex, resource); + continue; + } + } + } + + string[] resourcePaths = resourcesPathsSet.ToArray(); + + for (int i = 0; i < resourcePaths.Length; i++) + { + if (resourcePaths[i] == null) continue; + + try + { + if (showProgressBar && DisplaySmartUpdatingCancellableProgressBar("Scanning resource " + i + " for AOT support", resourcePaths[i], (float)i / resourcePaths.Length)) + { + return false; + } + + var assetPath = resourcePaths[i]; + + // Exclude editor-only resources + if (assetPath.ToLower().Contains("/editor/")) continue; + + this.ScanAsset(assetPath, includeAssetDependencies: includeResourceDependencies); + } + catch (MissingReferenceException ex) + { + Debug.LogError("A resource '" + resourcePaths[i] + "' threw a missing reference exception when scanning. Skipping resource and continuing scan."); + Debug.LogException(ex); + continue; + } + } + + return true; + } + finally + { + if (showProgressBar) + { + EditorUtility.ClearProgressBar(); + } + } + } + + public bool ScanBuildScenes(bool includeSceneDependencies, bool showProgressBar) + { + var scenePaths = EditorBuildSettings.scenes + .Where(n => n.enabled) + .Select(n => n.path) + .ToArray(); + + return this.ScanScenes(scenePaths, includeSceneDependencies, showProgressBar); + } + + public bool ScanScenes(string[] scenePaths, bool includeSceneDependencies, bool showProgressBar) + { + if (scenePaths.Length == 0) return true; + + bool formerForceEditorModeSerialization = UnitySerializationUtility.ForceEditorModeSerialization; + + try + { + UnitySerializationUtility.ForceEditorModeSerialization = true; + + bool hasDirtyScenes = false; + + for (int i = 0; i < EditorSceneManager.sceneCount; i++) + { + if (EditorSceneManager.GetSceneAt(i).isDirty) + { + hasDirtyScenes = true; + break; + } + } + + if (hasDirtyScenes && !EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) + { + return false; + } + + var oldSceneSetup = EditorSceneManager.GetSceneManagerSetup(); + + try + { + for (int i = 0; i < scenePaths.Length; i++) + { + var scenePath = scenePaths[i]; + + if (showProgressBar && DisplaySmartUpdatingCancellableProgressBar("Scanning scenes for AOT support", "Scene " + (i + 1) + "/" + scenePaths.Length + " - " + scenePath, (float)i / scenePaths.Length)) + { + return false; + } + + if (!System.IO.File.Exists(scenePath)) + { + Debug.LogWarning("Skipped AOT scanning scene '" + scenePath + "' for a file not existing at the scene path."); + continue; + } + + Scene openScene = default(Scene); + + try + { + openScene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); + } + catch + { + Debug.LogWarning("Skipped AOT scanning scene '" + scenePath + "' for throwing exceptions when trying to load it."); + continue; + } + + var sceneGOs = Resources.FindObjectsOfTypeAll<GameObject>(); + + foreach (var go in sceneGOs) + { + if (go.scene != openScene) continue; + + if ((go.hideFlags & HideFlags.DontSaveInBuild) == 0) + { + foreach (var component in go.GetComponents<ISerializationCallbackReceiver>()) + { + try + { + this.allowRegisteringScannedTypes = true; + component.OnBeforeSerialize(); + + var prefabSupporter = component as ISupportsPrefabSerialization; + + if (prefabSupporter != null) + { + // Also force a serialization of the object's prefab modifications, in case there are unknown types in there + + List<UnityEngine.Object> objs = null; + var mods = UnitySerializationUtility.DeserializePrefabModifications(prefabSupporter.SerializationData.PrefabModifications, prefabSupporter.SerializationData.PrefabModificationsReferencedUnityObjects); + UnitySerializationUtility.SerializePrefabModifications(mods, ref objs); + } + } + finally + { + this.allowRegisteringScannedTypes = false; + } + } + } + } + } + + // Load a new empty scene that will be unloaded immediately, just to be sure we completely clear all changes made by the scan + // Sometimes this fails for unknown reasons. In that case, swallow any exceptions, and just soldier on and hope for the best! + // Additionally, also eat any debug logs that happen here, because logged errors can stop the build process, and we don't want + // that to happen. + + UnityEngine.ILogger logger = null; + + if (Debug_Logger_Property != null) + { + logger = (UnityEngine.ILogger)Debug_Logger_Property.GetValue(null, null); + } + + bool previous = true; + + try + { + if (logger != null) + { + previous = logger.logEnabled; + logger.logEnabled = false; + } + + EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); + } + catch { } + finally + { + if (logger != null) + { + logger.logEnabled = previous; + } + } + + } + finally + { + if (oldSceneSetup != null && oldSceneSetup.Length > 0) + { + if (showProgressBar) + { + EditorUtility.DisplayProgressBar("Restoring scene setup", "", 1.0f); + } + EditorSceneManager.RestoreSceneManagerSetup(oldSceneSetup); + } + } + + if (includeSceneDependencies) + { + for (int i = 0; i < scenePaths.Length; i++) + { + var scenePath = scenePaths[i]; + if (showProgressBar && DisplaySmartUpdatingCancellableProgressBar("Scanning scene dependencies for AOT support", "Scene " + (i + 1) + "/" + scenePaths.Length + " - " + scenePath, (float)i / scenePaths.Length)) + { + return false; + } + + string[] dependencies = AssetDatabase.GetDependencies(scenePath, recursive: true); + + foreach (var dependency in dependencies) + { + this.ScanAsset(dependency, includeAssetDependencies: false); // All dependencies of this asset were already included recursively by Unity + } + } + } + + return true; + } + finally + { + if (showProgressBar) + { + EditorUtility.ClearProgressBar(); + } + + UnitySerializationUtility.ForceEditorModeSerialization = formerForceEditorModeSerialization; + } + } + + public bool ScanAsset(string assetPath, bool includeAssetDependencies) + { + if (assetPath.EndsWith(".unity")) + { + return this.ScanScenes(new string[] { assetPath }, includeAssetDependencies, false); + } + + if (!(assetPath.EndsWith(".asset") || assetPath.EndsWith(".prefab"))) + { + // ScanAsset can only scan .asset and .prefab assets. + return false; + } + + bool formerForceEditorModeSerialization = UnitySerializationUtility.ForceEditorModeSerialization; + + try + { + UnitySerializationUtility.ForceEditorModeSerialization = true; + + var assets = AssetDatabase.LoadAllAssetsAtPath(assetPath); + + if (assets == null || assets.Length == 0) + { + return false; + } + + foreach (var asset in assets) + { + if (asset == null) continue; + + this.ScanObject(asset); + } + + if (includeAssetDependencies) + { + string[] dependencies = AssetDatabase.GetDependencies(assetPath, recursive: true); + + foreach (var dependency in dependencies) + { + this.ScanAsset(dependency, includeAssetDependencies: false); // All dependencies were already included recursively by Unity + } + } + + return true; + } + finally + { + UnitySerializationUtility.ForceEditorModeSerialization = formerForceEditorModeSerialization; + } + } + + public void ScanObject(UnityEngine.Object obj) + { + if (obj is ISerializationCallbackReceiver) + { + bool formerForceEditorModeSerialization = UnitySerializationUtility.ForceEditorModeSerialization; + + try + { + UnitySerializationUtility.ForceEditorModeSerialization = true; + this.allowRegisteringScannedTypes = true; + (obj as ISerializationCallbackReceiver).OnBeforeSerialize(); + } + finally + { + this.allowRegisteringScannedTypes = false; + UnitySerializationUtility.ForceEditorModeSerialization = formerForceEditorModeSerialization; + } + } + } + + public List<Type> EndScan() + { + if (!this.scanning) throw new InvalidOperationException("Cannot end a scan when scanning has not begun."); + + var result = this.seenSerializedTypes.ToList(); + this.Dispose(); + return result; + } + + public void Dispose() + { + if (this.scanning) + { + FormatterLocator.OnLocatedEmittableFormatterForType -= this.OnLocatedEmitType; + FormatterLocator.OnLocatedFormatter -= this.OnLocatedFormatter; + Serializer.OnSerializedType -= this.OnSerializedType; + + this.scanning = false; + this.seenSerializedTypes.Clear(); + this.allowRegisteringScannedTypes = false; + } + } + + private void OnLocatedEmitType(Type type) + { + if (!AllowRegisterType(type)) return; + + this.RegisterType(type); + } + + private void OnSerializedType(Type type) + { + if (!AllowRegisterType(type)) return; + + this.RegisterType(type); + } + + private void OnLocatedFormatter(IFormatter formatter) + { + var type = formatter.SerializedType; + + if (type == null) return; + if (!AllowRegisterType(type)) return; + this.RegisterType(type); + } + + private static bool AllowRegisterType(Type type) + { + if (IsEditorOnlyAssembly(type.Assembly)) + return false; + + if (type.IsGenericType) + { + foreach (var parameter in type.GetGenericArguments()) + { + if (!AllowRegisterType(parameter)) return false; + } + } + + return true; + } + + private static bool IsEditorOnlyAssembly(Assembly assembly) + { + return EditorAssemblyNames.Contains(assembly.GetName().Name); + } + + private static HashSet<string> EditorAssemblyNames = new HashSet<string>() + { + "Assembly-CSharp-Editor", + "Assembly-UnityScript-Editor", + "Assembly-Boo-Editor", + "Assembly-CSharp-Editor-firstpass", + "Assembly-UnityScript-Editor-firstpass", + "Assembly-Boo-Editor-firstpass", + typeof(Editor).Assembly.GetName().Name + }; + + private void RegisterType(Type type) + { + if (!this.allowRegisteringScannedTypes) return; + //if (type.IsAbstract || type.IsInterface) return; + if (type.IsGenericType && (type.IsGenericTypeDefinition || !type.IsFullyConstructedGenericType())) return; + + //if (this.seenSerializedTypes.Add(type)) + //{ + // Debug.Log("Added " + type.GetNiceFullName()); + //} + + this.seenSerializedTypes.Add(type); + + if (type.IsGenericType) + { + foreach (var arg in type.GetGenericArguments()) + { + this.RegisterType(arg); + } + } + } + + private static bool DisplaySmartUpdatingCancellableProgressBar(string title, string details, float progress, int updateIntervalByMS = 200, int updateIntervalByCall = 50) + { + bool updateProgressBar = + smartProgressBarWatch.ElapsedMilliseconds >= updateIntervalByMS + || ++smartProgressBarDisplaysSinceLastUpdate >= updateIntervalByCall; + + if (updateProgressBar) + { + smartProgressBarWatch.Stop(); + smartProgressBarWatch.Reset(); + smartProgressBarWatch.Start(); + + smartProgressBarDisplaysSinceLastUpdate = 0; + + if (EditorUtility.DisplayCancelableProgressBar(title, details, progress)) + { + return true; + } + } + + return false; + } + } +} + +#endif
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportScanner.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportScanner.cs.meta new file mode 100644 index 00000000..5a975681 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportScanner.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 94a6cc2044fcd2cb317b1cdb1e8fcdaf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportUtilities.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportUtilities.cs new file mode 100644 index 00000000..4f02012f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportUtilities.cs @@ -0,0 +1,400 @@ +//----------------------------------------------------------------------- +// <copyright file="AOTSupportUtilities.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +#if UNITY_EDITOR + +namespace VRC.Udon.Serialization.OdinSerializer.Editor +{ + using VRC.Udon.Serialization.OdinSerializer.Utilities; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Reflection.Emit; + using UnityEditor; + using UnityEditor.SceneManagement; + using UnityEngine; + using UnityEngine.Scripting; + + public static class AOTSupportUtilities + { + /// <summary> + /// Scans the project's build scenes and resources, plus their dependencies, for serialized types to support. Progress bars are shown during the scan. + /// </summary> + /// <param name="serializedTypes">The serialized types to support.</param> + /// <param name="scanBuildScenes">Whether to scan the project's build scenes.</param> + /// <param name="scanAllAssetBundles">Whether to scan all the project's asset bundles.</param> + /// <param name="scanPreloadedAssets">Whether to scan the project's preloaded assets.</param> + /// <param name="scanResources">Whether to scan the project's resources.</param> + /// <param name="resourcesToScan">An optional list of the resource paths to scan. Only has an effect if the scanResources argument is true. All the resources will be scanned if null.</param> + /// <returns>true if the scan succeeded, false if the scan failed or was cancelled</returns> + public static bool ScanProjectForSerializedTypes(out List<Type> serializedTypes, bool scanBuildScenes = true, bool scanAllAssetBundles = true, bool scanPreloadedAssets = true, bool scanResources = true, List<string> resourcesToScan = null, bool scanAddressables = true) + { + using (var scanner = new AOTSupportScanner()) + { + scanner.BeginScan(); + + if (scanBuildScenes && !scanner.ScanBuildScenes(includeSceneDependencies: true, showProgressBar: true)) + { + Debug.Log("Project scan canceled while scanning scenes and their dependencies."); + serializedTypes = null; + return false; + } + + if (scanResources && !scanner.ScanAllResources(includeResourceDependencies: true, showProgressBar: true, resourcesPaths: resourcesToScan)) + { + Debug.Log("Project scan canceled while scanning resources and their dependencies."); + serializedTypes = null; + return false; + } + + if (scanAllAssetBundles && !scanner.ScanAllAssetBundles(showProgressBar: true)) + { + Debug.Log("Project scan canceled while scanning asset bundles and their dependencies."); + serializedTypes = null; + return false; + } + + if (scanPreloadedAssets && !scanner.ScanPreloadedAssets(showProgressBar: true)) + { + Debug.Log("Project scan canceled while scanning preloaded assets and their dependencies."); + serializedTypes = null; + return false; + } + + if (scanAddressables && !scanner.ScanAllAddressables(includeAssetDependencies: true, showProgressBar: true)) + { + Debug.Log("Project scan canceled while scanning addressable assets and their dependencies."); + serializedTypes = null; + return false; + } + + serializedTypes = scanner.EndScan(); + } + return true; + } + + /// <summary> + /// Generates an AOT DLL, using the given parameters. + /// </summary> + public static void GenerateDLL(string dirPath, string assemblyName, List<Type> supportSerializedTypes, bool generateLinkXml = true) + { + #if UNITY_EDITOR && NET_4_6 + if (!dirPath.EndsWith("/")) dirPath += "/"; + + var newDllPath = dirPath + assemblyName; + var fullDllPath = newDllPath + ".dll"; + + var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName() { Name = assemblyName }, AssemblyBuilderAccess.Save, dirPath); + var module = assembly.DefineDynamicModule(assemblyName); + + assembly.SetCustomAttribute(new CustomAttributeBuilder(typeof(EmittedAssemblyAttribute).GetConstructor(new Type[0]), new object[0])); + + // VRChat Edit: Add the UnityAPICompatibilityVersion Attribute for the current version of Unity to skip API Updating. + #if UNITY_2019 + assembly.SetCustomAttribute(new CustomAttributeBuilder( + typeof(UnityAPICompatibilityVersionAttribute).GetConstructor(new[]{typeof(string), typeof(bool)}), + new object[]{Application.unityVersion, true}) + ); + #else + assembly.SetCustomAttribute(new CustomAttributeBuilder( + typeof(UnityAPICompatibilityVersionAttribute).GetConstructor(new[]{typeof(string)}), + new object[]{Application.unityVersion}) + ); + #endif + + // The following is a fix for Unity's crappy Mono runtime that doesn't know how to do this sort + // of stuff properly + // + // We must manually remove the "Default Dynamic Assembly" module that is automatically defined, + // otherwise a reference to that non-existent module will be saved into the assembly's IL, and + // that will cause a multitude of issues. + // + // We do this by forcing there to be only one module - the one we just defined, and we set the + // manifest module to be that module as well. + { + var modulesField = assembly.GetType().GetField("modules", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + var manifestModuleField = assembly.GetType().GetField("manifest_module", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + if (modulesField != null) + { + modulesField.SetValue(assembly, new ModuleBuilder[] { module }); + } + + if (manifestModuleField != null) + { + manifestModuleField.SetValue(assembly, module); + } + } + + var type = module.DefineType(assemblyName + ".PreventCodeStrippingViaReferences", TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.NotPublic); + + CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder(typeof(PreserveAttribute).GetConstructor(Type.EmptyTypes), new object[0]); + type.SetCustomAttribute(attributeBuilder); + + var staticConstructor = type.DefineTypeInitializer(); + var il = staticConstructor.GetILGenerator(); + + var falseLocal = il.DeclareLocal(typeof(bool)); + + il.Emit(OpCodes.Ldc_I4_0); // Load false + il.Emit(OpCodes.Stloc, falseLocal); // Set to falseLocal + + HashSet<Type> seenTypes = new HashSet<Type>(); + + if (UnityVersion.Major == 2019 && UnityVersion.Minor == 2) + { + // This here is a hack that fixes Unity's assembly updater triggering faultily in Unity 2019.2 + // (and in early 2019.3 alphas/betas, but we're not handling those). When it triggers, it edits + // the generated AOT assembly such that it immediately causes Unity to hard crash. Having this + // type reference present in the assembly prevents that from happening. (Any concrete type in + // the serialization assembly would work, this one is just a random pick.) + // + // Unity should have fixed this in 2019.3, but said that a backport to 2019.2 is not guaranteed + // to occur, though it might. + + supportSerializedTypes.Add(typeof(DateTimeFormatter)); + } + + //var endPoint = il.DefineLabel(); + //il.Emit(OpCodes.Br, endPoint); + + foreach (var serializedType in supportSerializedTypes) + { + if (serializedType == null) continue; + + bool isAbstract = serializedType.IsAbstract || serializedType.IsInterface; + + if (serializedType.IsGenericType && (serializedType.IsGenericTypeDefinition || !serializedType.IsFullyConstructedGenericType())) + { + Debug.LogError("Skipping type '" + serializedType.GetNiceFullName() + "'! Type is a generic type definition, or its arguments contain generic parameters; type must be a fully constructed generic type."); + continue; + } + + if (seenTypes.Contains(serializedType)) continue; + + seenTypes.Add(serializedType); + + // Reference serialized type + { + if (serializedType.IsValueType) + { + var local = il.DeclareLocal(serializedType); + + il.Emit(OpCodes.Ldloca, local); + il.Emit(OpCodes.Initobj, serializedType); + } + else if (!isAbstract) + { + var constructor = serializedType.GetConstructor(Type.EmptyTypes); + + if (constructor != null) + { + il.Emit(OpCodes.Newobj, constructor); + il.Emit(OpCodes.Pop); + } + } + } + + // Reference and/or create formatter type + if (!FormatterUtilities.IsPrimitiveType(serializedType) && !typeof(UnityEngine.Object).IsAssignableFrom(serializedType) && !isAbstract) + { + var actualFormatter = FormatterLocator.GetFormatter(serializedType, SerializationPolicies.Unity); + + if (actualFormatter.GetType().IsDefined<EmittedFormatterAttribute>()) + { + //TODO: Make emitted formatter code compatible with IL2CPP + //// Emit an actual AOT formatter into the generated assembly + + //if (this.emitAOTFormatters) + //{ + // var emittedFormatter = FormatterEmitter.EmitAOTFormatter(typeEntry.Type, module, SerializationPolicies.Unity); + // var emittedFormatterConstructor = emittedFormatter.GetConstructor(Type.EmptyTypes); + + // il.Emit(OpCodes.Newobj, emittedFormatterConstructor); + // il.Emit(OpCodes.Pop); + //} + } + + var formatters = FormatterLocator.GetAllCompatiblePredefinedFormatters(serializedType, SerializationPolicies.Unity); + + foreach (var formatter in formatters) + { + // Reference the pre-existing formatter + + var formatterConstructor = formatter.GetType().GetConstructor(Type.EmptyTypes); + + if (formatterConstructor != null) + { + il.Emit(OpCodes.Newobj, formatterConstructor); + il.Emit(OpCodes.Pop); + } + } + + //// Make sure we have a proper reflection formatter variant if all else goes wrong + //il.Emit(OpCodes.Newobj, typeof(ReflectionFormatter<>).MakeGenericType(serializedType).GetConstructor(Type.EmptyTypes)); + //il.Emit(OpCodes.Pop); + } + + ConstructorInfo serializerConstructor; + + // Reference serializer variant + if (serializedType.IsValueType) + { + serializerConstructor = Serializer.Get(serializedType).GetType().GetConstructor(Type.EmptyTypes); + + il.Emit(OpCodes.Newobj, serializerConstructor); + + // The following section is a fix for an issue on IL2CPP for PS4, where sometimes bytecode isn't + // generated for methods in base types of needed types - FX, Serializer<T>.ReadValueWeak() + // may be missing. This only seems to happen in a relevant way for value types. + { + var endLabel = il.DefineLabel(); + + // Load a false local value, then jump to the end of this segment of code due to that + // false value. This is an attempt to trick any potential code flow analysis made + // by IL2CPP that checks whether this segment of code is actually run. + // + // We don't run the code because if we did, that would actually throw a bunch of + // exceptions from invalid calls to ReadValueWeak and WriteValueWeak. + il.Emit(OpCodes.Ldloc, falseLocal); + il.Emit(OpCodes.Brfalse, endLabel); + + var baseSerializerType = typeof(Serializer<>).MakeGenericType(serializedType); + + var readValueWeakMethod = baseSerializerType.GetMethod("ReadValueWeak", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, new Type[] { typeof(IDataReader) }, null); + var writeValueWeakMethod = baseSerializerType.GetMethod("WriteValueWeak", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, new Type[] { typeof(string), typeof(object), typeof(IDataWriter) }, null); + + il.Emit(OpCodes.Dup); // Duplicate serializer instance + il.Emit(OpCodes.Ldnull); // Load null argument for IDataReader reader + il.Emit(OpCodes.Callvirt, readValueWeakMethod); // Call ReadValueWeak on serializer instance + il.Emit(OpCodes.Pop); // Pop result of ReadValueWeak + + il.Emit(OpCodes.Dup); // Duplicate serializer instance + il.Emit(OpCodes.Ldnull); // Load null argument for string name + il.Emit(OpCodes.Ldnull); // Load null argument for object value + il.Emit(OpCodes.Ldnull); // Load null argument for IDataWriter writer + il.Emit(OpCodes.Callvirt, writeValueWeakMethod); // Call WriteValueWeak on serializer instance + + il.MarkLabel(endLabel); // This is where the code always jumps to, skipping the above + } + + il.Emit(OpCodes.Pop); // Pop the serializer instance + } + else + { + serializerConstructor = typeof(ComplexTypeSerializer<>).MakeGenericType(serializedType).GetConstructor(Type.EmptyTypes); + il.Emit(OpCodes.Newobj, serializerConstructor); + il.Emit(OpCodes.Pop); + } + + } + + //il.MarkLabel(endPoint); + il.Emit(OpCodes.Ret); + + type.CreateType(); + + if (!Directory.Exists(dirPath)) + { + Directory.CreateDirectory(dirPath); + } + + if (File.Exists(fullDllPath)) + { + File.Delete(fullDllPath); + } + + if (File.Exists(fullDllPath + ".meta")) + { + File.Delete(fullDllPath + ".meta"); + } + + try + { + AssetDatabase.Refresh(); + } + catch (Exception) + { + // Sigh, Unity 5.3.0 + } + + assembly.Save(assemblyName); + + File.Move(newDllPath, fullDllPath); + + if (generateLinkXml) + { + File.WriteAllText(dirPath + "link.xml", + @"<linker> + <assembly fullname=""" + assemblyName + @""" preserve=""all""/> +</linker>"); + } + + try + { + AssetDatabase.Refresh(); + } + catch (Exception) + { + // Sigh, Unity 5.3.0 + } + + var pluginImporter = PluginImporter.GetAtPath(fullDllPath) as PluginImporter; + + if (pluginImporter != null) + { + //pluginImporter.ClearSettings(); + + pluginImporter.SetCompatibleWithEditor(false); + pluginImporter.SetCompatibleWithAnyPlatform(true); + + // Disable for all standalones + pluginImporter.SetCompatibleWithPlatform(BuildTarget.StandaloneLinux64, false); + + if (!UnityVersion.IsVersionOrGreater(2019, 2)) + { + pluginImporter.SetCompatibleWithPlatform((BuildTarget)17, false); // StandaloneLinux + pluginImporter.SetCompatibleWithPlatform((BuildTarget)25, false); // StandaloneLinuxUniversal + } + + // StandaloneOSXUniversal (<= 2017.2) / StandaloneOSX (>= 2017.3) + pluginImporter.SetCompatibleWithPlatform((BuildTarget)2, false); + + if (!UnityVersion.IsVersionOrGreater(2017, 3)) + { + pluginImporter.SetCompatibleWithPlatform((BuildTarget)4, false); // StandaloneOSXIntel + pluginImporter.SetCompatibleWithPlatform((BuildTarget)27, false); // StandaloneOSXIntel64 + } + + pluginImporter.SetCompatibleWithPlatform(BuildTarget.StandaloneWindows, false); + //pluginImporter.SetCompatibleWithPlatform(BuildTarget.StandaloneWindows64, false); + + //pluginImporter.SetCompatibleWithPlatform(BuildTarget.Android, false); + + pluginImporter.SaveAndReimport(); + } + + AssetDatabase.SaveAssets(); + #endif + } + } +} + +#endif diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportUtilities.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportUtilities.cs.meta new file mode 100644 index 00000000..0fa39b53 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/AOTSupportUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f5fe153775edbadfa2b659e0e35dc881 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport.meta new file mode 100644 index 00000000..00b643bb --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 766e16676b7508647a3ab84d0cae8fa7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/BaseDictionaryKeyPathProvider.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/BaseDictionaryKeyPathProvider.cs new file mode 100644 index 00000000..f6d1e678 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/BaseDictionaryKeyPathProvider.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// <copyright file="BaseDictionaryKeyPathProvider.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System.Collections.Generic; + + /// <summary> + /// Not yet documented. + /// </summary> + /// <typeparam name="T">Not yet documented.</typeparam> + public abstract class BaseDictionaryKeyPathProvider<T> : IDictionaryKeyPathProvider<T>, IComparer<T> + { + /// <summary> + /// Not yet documented. + /// </summary> + public abstract string ProviderID { get; } + + /// <summary> + /// Not yet documented. + /// </summary> + /// <param name="pathStr">Not yet documented.</param> + /// <returns>Not yet documented.</returns> + public abstract T GetKeyFromPathString(string pathStr); + + /// <summary> + /// Not yet documented. + /// </summary> + /// <param name="key">Not yet documented.</param> + /// <returns>Not yet documented.</returns> + public abstract string GetPathStringFromKey(T key); + + /// <summary> + /// Not yet documented. + /// </summary> + /// <param name="x">Not yet documented.</param> + /// <param name="y">Not yet documented.</param> + /// <returns>Not yet documented.</returns> + public abstract int Compare(T x, T y); + + int IDictionaryKeyPathProvider.Compare(object x, object y) + { + return this.Compare((T)x, (T)y); + } + + object IDictionaryKeyPathProvider.GetKeyFromPathString(string pathStr) + { + return this.GetKeyFromPathString(pathStr); + } + + string IDictionaryKeyPathProvider.GetPathStringFromKey(object key) + { + return this.GetPathStringFromKey((T)key); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/BaseDictionaryKeyPathProvider.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/BaseDictionaryKeyPathProvider.cs.meta new file mode 100644 index 00000000..9e6fcbb6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/BaseDictionaryKeyPathProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 864fb1c011715f9df2998d71ac8716f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/DictionaryKeyUtility.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/DictionaryKeyUtility.cs new file mode 100644 index 00000000..78dbb94c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/DictionaryKeyUtility.cs @@ -0,0 +1,467 @@ +//----------------------------------------------------------------------- +// <copyright file="DictionaryKeyUtility.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System.Globalization; + using System; + using System.Collections.Generic; + using Utilities; + using System.Linq; + using UnityEngine; + using System.Reflection; + + /// <summary> + /// Provides utility methods for handling dictionary keys in the prefab modification system. + /// </summary> + public static class DictionaryKeyUtility + { + private static readonly Dictionary<Type, bool> GetSupportedDictionaryKeyTypesResults = new Dictionary<Type, bool>(); + + private static readonly HashSet<Type> BaseSupportedDictionaryKeyTypes = new HashSet<Type>() + { + typeof(string), + typeof(char), + typeof(byte), + typeof(sbyte), + typeof(ushort), + typeof(short), + typeof(uint), + typeof(int), + typeof(ulong), + typeof(long), + typeof(float), + typeof(double), + typeof(decimal), + typeof(Guid) + }; + + private static readonly HashSet<char> AllowedSpecialKeyStrChars = new HashSet<char>() + { + ',', '(', ')', '\\', '|', '-', '+' + }; + + private static readonly Dictionary<Type, IDictionaryKeyPathProvider> TypeToKeyPathProviders = new Dictionary<Type, IDictionaryKeyPathProvider>(); + private static readonly Dictionary<string, IDictionaryKeyPathProvider> IDToKeyPathProviders = new Dictionary<string, IDictionaryKeyPathProvider>(); + private static readonly Dictionary<IDictionaryKeyPathProvider, string> ProviderToID = new Dictionary<IDictionaryKeyPathProvider, string>(); + + private static readonly Dictionary<object, string> ObjectsToTempKeys = new Dictionary<object, string>(); + private static readonly Dictionary<string, object> TempKeysToObjects = new Dictionary<string, object>(); + private static long tempKeyCounter = 0; + + private class UnityObjectKeyComparer<T> : IComparer<T> + { + public int Compare(T x, T y) + { + var a = (UnityEngine.Object)(object)x; + var b = (UnityEngine.Object)(object)y; + + if (a == null && b == null) return 0; + + if (a == null) return 1; + if (b == null) return -1; + + return a.name.CompareTo(b.name); + } + } + + private class FallbackKeyComparer<T> : IComparer<T> + { + public int Compare(T x, T y) + { + return GetDictionaryKeyString(x).CompareTo(GetDictionaryKeyString(y)); + } + } + + /// <summary> + /// A smart comparer for dictionary keys, that uses the most appropriate available comparison method for the given key types. + /// </summary> + public class KeyComparer<T> : IComparer<T> + { + public readonly static KeyComparer<T> Default = new KeyComparer<T>(); + + private readonly IComparer<T> actualComparer; + + public KeyComparer() + { + IDictionaryKeyPathProvider provider; + + if (TypeToKeyPathProviders.TryGetValue(typeof(T), out provider)) + { + this.actualComparer = (IComparer<T>)provider; + } + else if (typeof(IComparable).IsAssignableFrom(typeof(T)) || typeof(IComparable<T>).IsAssignableFrom(typeof(T))) + { + this.actualComparer = Comparer<T>.Default; + } + else if (typeof(UnityEngine.Object).IsAssignableFrom(typeof(T))) + { + this.actualComparer = new UnityObjectKeyComparer<T>(); + } + else + { + this.actualComparer = new FallbackKeyComparer<T>(); + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + /// <param name="x">Not yet documented.</param> + /// <param name="y">Not yet documented.</param> + /// <returns>Not yet documented.</returns> + public int Compare(T x, T y) + { + return this.actualComparer.Compare(x, y); + } + } + + static DictionaryKeyUtility() + { + var attributes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(ass => + { + return ass.SafeGetCustomAttributes(typeof(RegisterDictionaryKeyPathProviderAttribute), false) + .Select(attr => new { Assembly = ass, Attribute = (RegisterDictionaryKeyPathProviderAttribute)attr }); + }) + .Where(n => n.Attribute.ProviderType != null); + + foreach (var entry in attributes) + { + var assembly = entry.Assembly; + var providerType = entry.Attribute.ProviderType; + + if (providerType.IsAbstract) + { + LogInvalidKeyPathProvider(providerType, assembly, "Type cannot be abstract"); + continue; + } + + if (providerType.IsInterface) + { + LogInvalidKeyPathProvider(providerType, assembly, "Type cannot be an interface"); + continue; + } + + if (!providerType.ImplementsOpenGenericInterface(typeof(IDictionaryKeyPathProvider<>))) + { + LogInvalidKeyPathProvider(providerType, assembly, "Type must implement the " + typeof(IDictionaryKeyPathProvider<>).GetNiceName() + " interface"); + continue; + } + + if (providerType.IsGenericType) + { + LogInvalidKeyPathProvider(providerType, assembly, "Type cannot be generic"); + continue; + } + + if (providerType.GetConstructor(Type.EmptyTypes) == null) + { + LogInvalidKeyPathProvider(providerType, assembly, "Type must have a public parameterless constructor"); + continue; + } + + var keyType = providerType.GetArgumentsOfInheritedOpenGenericInterface(typeof(IDictionaryKeyPathProvider<>))[0]; + + if (!keyType.IsValueType) + { + LogInvalidKeyPathProvider(providerType, assembly, "Key type to support '" + keyType.GetNiceFullName() + "' must be a value type - support for extending dictionaries with reference type keys may come at a later time"); + continue; + } + + if (TypeToKeyPathProviders.ContainsKey(keyType)) + { + Debug.LogWarning("Ignoring dictionary key path provider '" + providerType.GetNiceFullName() + "' registered on assembly '" + assembly.GetName().Name + "': A previous provider '" + TypeToKeyPathProviders[keyType].GetType().GetNiceFullName() + "' was already registered for the key type '" + keyType.GetNiceFullName() + "'."); + continue; + } + + IDictionaryKeyPathProvider provider; + string id; + + try + { + provider = (IDictionaryKeyPathProvider)Activator.CreateInstance(providerType); + } + catch (Exception ex) + { + Debug.LogException(ex); + Debug.LogWarning("Ignoring dictionary key path provider '" + providerType.GetNiceFullName() + "' registered on assembly '" + assembly.GetName().Name + "': An exception of type '" + ex.GetType() + "' was thrown when trying to instantiate a provider instance."); + continue; + } + + try + { + id = provider.ProviderID; + } + catch (Exception ex) + { + Debug.LogException(ex); + Debug.LogWarning("Ignoring dictionary key path provider '" + providerType.GetNiceFullName() + "' registered on assembly '" + assembly.GetName().Name + "': An exception of type '" + ex.GetType() + "' was thrown when trying to get the provider ID string."); + continue; + } + + if (id == null) + { + LogInvalidKeyPathProvider(providerType, assembly, "Provider ID is null"); + continue; + } + + if (id.Length == 0) + { + LogInvalidKeyPathProvider(providerType, assembly, "Provider ID is an empty string"); + continue; + } + + for (int i = 0; i < id.Length; i++) + { + if (!char.IsLetterOrDigit(id[i])) + { + LogInvalidKeyPathProvider(providerType, assembly, "Provider ID '" + id + "' cannot contain characters which are not letters or digits"); + continue; + } + } + + if (IDToKeyPathProviders.ContainsKey(id)) + { + LogInvalidKeyPathProvider(providerType, assembly, "Provider ID '" + id + "' is already in use for the provider '" + IDToKeyPathProviders[id].GetType().GetNiceFullName() + "'"); + continue; + } + + TypeToKeyPathProviders[keyType] = provider; + IDToKeyPathProviders[id] = provider; + ProviderToID[provider] = id; + } + } + + private static void LogInvalidKeyPathProvider(Type type, Assembly assembly, string reason) + { + Debug.LogError("Invalid dictionary key path provider '" + type.GetNiceFullName() + "' registered on assembly '" + assembly.GetName().Name + "': " + reason); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static IEnumerable<Type> GetPersistentPathKeyTypes() + { + foreach (var type in BaseSupportedDictionaryKeyTypes) + { + yield return type; + } + + foreach (var type in TypeToKeyPathProviders.Keys) + { + yield return type; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static bool KeyTypeSupportsPersistentPaths(Type type) + { + bool result; + if (!GetSupportedDictionaryKeyTypesResults.TryGetValue(type, out result)) + { + result = PrivateIsSupportedDictionaryKeyType(type); + GetSupportedDictionaryKeyTypesResults.Add(type, result); + } + return result; + } + + private static bool PrivateIsSupportedDictionaryKeyType(Type type) + { + return type.IsEnum || BaseSupportedDictionaryKeyTypes.Contains(type) || TypeToKeyPathProviders.ContainsKey(type); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static string GetDictionaryKeyString(object key) + { + if (key == null) + { + throw new ArgumentNullException("key"); + } + + Type type = key.GetType(); + + if (!KeyTypeSupportsPersistentPaths(type)) + { + string keyString; + + if (!ObjectsToTempKeys.TryGetValue(key, out keyString)) + { + keyString = (tempKeyCounter++).ToString(); + var str = "{temp:" + keyString + "}"; + ObjectsToTempKeys[key] = str; + TempKeysToObjects[str] = key; + } + + return keyString; + } + + IDictionaryKeyPathProvider keyPathProvider; + + if (TypeToKeyPathProviders.TryGetValue(type, out keyPathProvider)) + { + var keyStr = keyPathProvider.GetPathStringFromKey(key); + string error = null; + + bool validPath = true; + + if (keyStr == null || keyStr.Length == 0) + { + validPath = false; + error = "String is null or empty"; + } + + if (validPath) + { + for (int i = 0; i < keyStr.Length; i++) + { + var c = keyStr[i]; + + if (char.IsLetterOrDigit(c) || AllowedSpecialKeyStrChars.Contains(c)) continue; + + validPath = false; + error = "Invalid character '" + c + "' at index " + i; + break; + } + } + + if (!validPath) + { + throw new ArgumentException("Invalid key path '" + keyStr + "' given by provider '" + keyPathProvider.GetType().GetNiceFullName() + "': " + error); + } + + return "{id:" + ProviderToID[keyPathProvider] + ":" + keyStr + "}"; + } + + if (type.IsEnum) + { + Type backingType = Enum.GetUnderlyingType(type); + + if (backingType == typeof(ulong)) + { + ulong value = Convert.ToUInt64(key); + return "{" + value.ToString("D", CultureInfo.InvariantCulture) + "eu}"; + } + else + { + long value = Convert.ToInt64(key); + return "{" + value.ToString("D", CultureInfo.InvariantCulture) + "es}"; + } + } + + if (type == typeof(string)) return "{\"" + key + "\"}"; + if (type == typeof(char)) return "{'" + ((char)key).ToString(CultureInfo.InvariantCulture) + "'}"; + if (type == typeof(byte)) return "{" + ((byte)key).ToString("D", CultureInfo.InvariantCulture) + "ub}"; + if (type == typeof(sbyte)) return "{" + ((sbyte)key).ToString("D", CultureInfo.InvariantCulture) + "sb}"; + if (type == typeof(ushort)) return "{" + ((ushort)key).ToString("D", CultureInfo.InvariantCulture) + "us}"; + if (type == typeof(short)) return "{" + ((short)key).ToString("D", CultureInfo.InvariantCulture) + "ss}"; + if (type == typeof(uint)) return "{" + ((uint)key).ToString("D", CultureInfo.InvariantCulture) + "ui}"; + if (type == typeof(int)) return "{" + ((int)key).ToString("D", CultureInfo.InvariantCulture) + "si}"; + if (type == typeof(ulong)) return "{" + ((ulong)key).ToString("D", CultureInfo.InvariantCulture) + "ul}"; + if (type == typeof(long)) return "{" + ((long)key).ToString("D", CultureInfo.InvariantCulture) + "sl}"; + if (type == typeof(float)) return "{" + ((float)key).ToString("R", CultureInfo.InvariantCulture) + "fl}"; + if (type == typeof(double)) return "{" + ((double)key).ToString("R", CultureInfo.InvariantCulture) + "dl}"; + if (type == typeof(decimal)) return "{" + ((decimal)key).ToString("G", CultureInfo.InvariantCulture) + "dc}"; + if (type == typeof(Guid)) return "{" + ((Guid)key).ToString("N", CultureInfo.InvariantCulture) + "gu}"; + + throw new NotImplementedException("Support has not been implemented for the supported dictionary key type '" + type.GetNiceName() + "'."); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static object GetDictionaryKeyValue(string keyStr, Type expectedType) + { + const string InvalidKeyString = "Invalid key string: "; + + if (keyStr == null) throw new ArgumentNullException("keyStr"); + if (keyStr.Length < 4 || keyStr[0] != '{' || keyStr[keyStr.Length - 1] != '}') throw new ArgumentException(InvalidKeyString + keyStr); + + if (keyStr[1] == '"') + { + if (keyStr[keyStr.Length - 2] != '"') throw new ArgumentException(InvalidKeyString + keyStr); + return keyStr.Substring(2, keyStr.Length - 4); + } + + if (keyStr[1] == '\'') + { + if (keyStr.Length != 5 || keyStr[keyStr.Length - 2] != '\'') throw new ArgumentException(InvalidKeyString + keyStr); + return keyStr[2]; + } + + if (keyStr.StartsWith("{temp:")) + { + object key; + + if (!TempKeysToObjects.TryGetValue(keyStr, out key)) + { + throw new ArgumentException("The temp dictionary key '" + keyStr + "' has not been allocated yet."); + } + + return key; + } + + if (keyStr.StartsWith("{id:")) + { + int secondColon = keyStr.IndexOf(':', 4); + + if (secondColon == -1 || secondColon > keyStr.Length - 3) throw new ArgumentException(InvalidKeyString + keyStr); + + string id = keyStr.FromTo(4, secondColon); + string key = keyStr.FromTo(secondColon + 1, keyStr.Length - 1); + + IDictionaryKeyPathProvider provider; + + if (!IDToKeyPathProviders.TryGetValue(id, out provider)) + { + throw new ArgumentException("No provider found for provider ID '" + id + "' in key string '" + keyStr + "'."); + } + + return provider.GetKeyFromPathString(key); + } + + // Handle enums + + if (keyStr.EndsWith("ub}")) return byte.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any); + if (keyStr.EndsWith("sb}")) return sbyte.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any); + if (keyStr.EndsWith("us}")) return ushort.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any); + if (keyStr.EndsWith("ss}")) return short.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any); + if (keyStr.EndsWith("ui}")) return uint.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any); + if (keyStr.EndsWith("si}")) return int.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any); + if (keyStr.EndsWith("ul}")) return ulong.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any); + if (keyStr.EndsWith("sl}")) return long.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any); + if (keyStr.EndsWith("fl}")) return float.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any); + if (keyStr.EndsWith("dl}")) return double.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any); + if (keyStr.EndsWith("dc}")) return decimal.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any); + if (keyStr.EndsWith("gu}")) return new Guid(keyStr.Substring(1, keyStr.Length - 4)); + if (keyStr.EndsWith("es}")) return Enum.ToObject(expectedType, long.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any)); + if (keyStr.EndsWith("eu}")) return Enum.ToObject(expectedType, ulong.Parse(keyStr.Substring(1, keyStr.Length - 4), NumberStyles.Any)); + + throw new ArgumentException(InvalidKeyString + keyStr); + } + + private static string FromTo(this string str, int from, int to) + { + return str.Substring(from, to - from); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/DictionaryKeyUtility.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/DictionaryKeyUtility.cs.meta new file mode 100644 index 00000000..fcf54e24 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/DictionaryKeyUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef6f699f176c2dfdea788982526f989a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/IDictionaryKeyPathProvider.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/IDictionaryKeyPathProvider.cs new file mode 100644 index 00000000..ae9ab609 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/IDictionaryKeyPathProvider.cs @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------- +// <copyright file="IDictionaryKeyPathProvider.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Not yet documented. + /// </summary> + public interface IDictionaryKeyPathProvider + { + /// <summary> + /// Gets the provider identifier. + /// </summary> + string ProviderID { get; } + + /// <summary> + /// Gets the path string from key. + /// </summary> + /// <param name="key">The key.</param> + string GetPathStringFromKey(object key); + + /// <summary> + /// Gets the key from path string. + /// </summary> + /// <param name="pathStr">The path string.</param> + object GetKeyFromPathString(string pathStr); + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + int Compare(object x, object y); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public interface IDictionaryKeyPathProvider<T> : IDictionaryKeyPathProvider + { + /// <summary> + /// Gets the path string from key. + /// </summary> + /// <param name="key">The key.</param> + string GetPathStringFromKey(T key); + + /// <summary> + /// Gets the key from path string. + /// </summary> + /// <param name="pathStr">The path string.</param> + new T GetKeyFromPathString(string pathStr); + + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + int Compare(T x, T y); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/IDictionaryKeyPathProvider.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/IDictionaryKeyPathProvider.cs.meta new file mode 100644 index 00000000..dcb5d6eb --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/IDictionaryKeyPathProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b513c156933d8b833ccd40d717bf7e2b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/RegisterDictionaryKeyPathProviderAttribute.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/RegisterDictionaryKeyPathProviderAttribute.cs new file mode 100644 index 00000000..c3b04f6a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/RegisterDictionaryKeyPathProviderAttribute.cs @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------- +// <copyright file="RegisterDictionaryKeyPathProviderAttribute.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RegisterDictionaryKeyPathProviderAttribute : Attribute + { + public readonly Type ProviderType; + + public RegisterDictionaryKeyPathProviderAttribute(Type providerType) + { + this.ProviderType = providerType; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/RegisterDictionaryKeyPathProviderAttribute.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/RegisterDictionaryKeyPathProviderAttribute.cs.meta new file mode 100644 index 00000000..adbddde2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/RegisterDictionaryKeyPathProviderAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54f653ed4a4e15c07057283c11dce4d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector2DictionaryKeyPathProvider.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector2DictionaryKeyPathProvider.cs new file mode 100644 index 00000000..972edfdd --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector2DictionaryKeyPathProvider.cs @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------- +// <copyright file="Vector2DictionaryKeyPathProvider.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterDictionaryKeyPathProvider(typeof(Vector2DictionaryKeyPathProvider))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System.Globalization; + using UnityEngine; + + /// <summary> + /// Not yet documented. + /// </summary> + public sealed class Vector2DictionaryKeyPathProvider : BaseDictionaryKeyPathProvider<Vector2> + { + /// <summary> + /// Not yet documented. + /// </summary> + public override string ProviderID { get { return "v2"; } } + + /// <summary> + /// Not yet documented. + /// </summary> + public override int Compare(Vector2 x, Vector2 y) + { + int result = x.x.CompareTo(y.x); + + if (result == 0) + { + result = x.y.CompareTo(y.y); + } + + return result; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override Vector2 GetKeyFromPathString(string pathStr) + { + int sep = pathStr.IndexOf('|'); + + string x = pathStr.Substring(1, sep - 1).Trim(); + string y = pathStr.Substring(sep + 1, pathStr.Length - (sep + 2)).Trim(); + + return new Vector2(float.Parse(x), float.Parse(y)); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public override string GetPathStringFromKey(Vector2 key) + { + var x = key.x.ToString("R", CultureInfo.InvariantCulture); + var y = key.y.ToString("R", CultureInfo.InvariantCulture); + return ("(" + x + "|" + y + ")").Replace('.', ','); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector2DictionaryKeyPathProvider.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector2DictionaryKeyPathProvider.cs.meta new file mode 100644 index 00000000..afcdc7bc --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector2DictionaryKeyPathProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0405ef103432161dff609e75f71f3f55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector3DictionaryKeyPathProvider.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector3DictionaryKeyPathProvider.cs new file mode 100644 index 00000000..20616ad2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector3DictionaryKeyPathProvider.cs @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------- +// <copyright file="Vector3DictionaryKeyPathProvider.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterDictionaryKeyPathProvider(typeof(Vector3DictionaryKeyPathProvider))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System.Globalization; + using UnityEngine; + + /// <summary> + /// Dictionary key path provider for <see cref="UnityEngine.Vector3"/> + /// </summary> + public sealed class Vector3DictionaryKeyPathProvider : BaseDictionaryKeyPathProvider<Vector3> + { + public override string ProviderID { get { return "v3"; } } + + public override int Compare(Vector3 x, Vector3 y) + { + int result = x.x.CompareTo(y.x); + + if (result == 0) + { + result = x.y.CompareTo(y.y); + } + + if (result == 0) + { + result = x.z.CompareTo(y.z); + } + + return result; + } + + public override Vector3 GetKeyFromPathString(string pathStr) + { + int sep1 = pathStr.IndexOf('|'); + int sep2 = pathStr.IndexOf('|', sep1 + 1); + + string x = pathStr.Substring(1, sep1 - 1).Trim(); + string y = pathStr.Substring(sep1 + 1, sep2 - (sep1 + 1)).Trim(); + string z = pathStr.Substring(sep2 + 1, pathStr.Length - (sep2 + 2)).Trim(); + + return new Vector3(float.Parse(x), float.Parse(y), float.Parse(z)); + } + + public override string GetPathStringFromKey(Vector3 key) + { + var x = key.x.ToString("R", CultureInfo.InvariantCulture); + var y = key.y.ToString("R", CultureInfo.InvariantCulture); + var z = key.z.ToString("R", CultureInfo.InvariantCulture); + return ("(" + x + "|" + y + "|" + z + ")").Replace('.', ','); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector3DictionaryKeyPathProvider.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector3DictionaryKeyPathProvider.cs.meta new file mode 100644 index 00000000..04a831b9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector3DictionaryKeyPathProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d61e235c606c1c9d7269f7e68471e38 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector4DictionaryKeyPathProvider.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector4DictionaryKeyPathProvider.cs new file mode 100644 index 00000000..ea499259 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector4DictionaryKeyPathProvider.cs @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------- +// <copyright file="Vector4DictionaryKeyPathProvider.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterDictionaryKeyPathProvider(typeof(Vector4DictionaryKeyPathProvider))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System.Globalization; + using UnityEngine; + + public sealed class Vector4DictionaryKeyPathProvider : BaseDictionaryKeyPathProvider<Vector4> + { + public override string ProviderID { get { return "v4"; } } + + public override int Compare(Vector4 x, Vector4 y) + { + int result = x.x.CompareTo(y.x); + + if (result == 0) + { + result = x.y.CompareTo(y.y); + } + + if (result == 0) + { + result = x.z.CompareTo(y.z); + } + + if (result == 0) + { + result = x.w.CompareTo(y.w); + } + + return result; + } + + public override Vector4 GetKeyFromPathString(string pathStr) + { + int sep1 = pathStr.IndexOf('|'); + int sep2 = pathStr.IndexOf('|', sep1 + 1); + int sep3 = pathStr.IndexOf('|', sep2 + 1); + + string x = pathStr.Substring(1, sep1 - 1).Trim(); + string y = pathStr.Substring(sep1 + 1, sep2 - (sep1 + 1)).Trim(); + string z = pathStr.Substring(sep2 + 1, sep3 - (sep2 + 1)).Trim(); + string w = pathStr.Substring(sep3 + 1, pathStr.Length - (sep3 + 2)).Trim(); + + return new Vector4(float.Parse(x), float.Parse(y), float.Parse(z), float.Parse(w)); + } + + public override string GetPathStringFromKey(Vector4 key) + { + var x = key.x.ToString("R", CultureInfo.InvariantCulture); + var y = key.y.ToString("R", CultureInfo.InvariantCulture); + var z = key.z.ToString("R", CultureInfo.InvariantCulture); + var w = key.w.ToString("R", CultureInfo.InvariantCulture); + + return ("(" + x + "|" + y + "|" + z + "|" + w + ")").Replace('.', ','); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector4DictionaryKeyPathProvider.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector4DictionaryKeyPathProvider.cs.meta new file mode 100644 index 00000000..fb88e42b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/DictionaryKeySupport/Vector4DictionaryKeyPathProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 51bb2cf369b5ea90948a20e4f2ebae48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters.meta new file mode 100644 index 00000000..84864a7a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: aee2797df5ac0f24d97dc6ce96c65ca4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/AnimationCurveFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/AnimationCurveFormatter.cs new file mode 100644 index 00000000..4e798c3a --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/AnimationCurveFormatter.cs @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------- +// <copyright file="AnimationCurveFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(AnimationCurveFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="AnimationCurve"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.AnimationCurve}" /> + public class AnimationCurveFormatter : MinimalBaseFormatter<AnimationCurve> + { + private static readonly Serializer<Keyframe[]> KeyframeSerializer = Serializer.Get<Keyframe[]>(); + private static readonly Serializer<WrapMode> WrapModeSerializer = Serializer.Get<WrapMode>(); + + /// <summary> + /// Returns null. + /// </summary> + /// <returns> + /// A null value. + /// </returns> + protected override AnimationCurve GetUninitializedObject() + { + return null; + } + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref AnimationCurve value, IDataReader reader) + { + var keys = KeyframeSerializer.ReadValue(reader); + + value = new AnimationCurve(keys); + value.preWrapMode = WrapModeSerializer.ReadValue(reader); + value.postWrapMode = WrapModeSerializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref AnimationCurve value, IDataWriter writer) + { + KeyframeSerializer.WriteValue(value.keys, writer); + WrapModeSerializer.WriteValue(value.preWrapMode, writer); + WrapModeSerializer.WriteValue(value.postWrapMode, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/AnimationCurveFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/AnimationCurveFormatter.cs.meta new file mode 100644 index 00000000..9d72bfaa --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/AnimationCurveFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3d2976bd61cccf62b11b4d3f02762465 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/BoundsFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/BoundsFormatter.cs new file mode 100644 index 00000000..ab175f31 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/BoundsFormatter.cs @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------- +// <copyright file="BoundsFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(BoundsFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="Bounds"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.Bounds}" /> + public class BoundsFormatter : MinimalBaseFormatter<Bounds> + { + private static readonly Serializer<Vector3> Vector3Serializer = Serializer.Get<Vector3>(); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Bounds value, IDataReader reader) + { + value.center = Vector3Serializer.ReadValue(reader); + value.size = Vector3Serializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Bounds value, IDataWriter writer) + { + Vector3Serializer.WriteValue(value.center, writer); + Vector3Serializer.WriteValue(value.size, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/BoundsFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/BoundsFormatter.cs.meta new file mode 100644 index 00000000..39e28ff9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/BoundsFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ff1b29d64402a15d020739becd8661e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Color32Formatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Color32Formatter.cs new file mode 100644 index 00000000..42d744aa --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Color32Formatter.cs @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------- +// <copyright file="Color32Formatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(Color32Formatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="Color32"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.Color32}" /> + public class Color32Formatter : MinimalBaseFormatter<Color32> + { + private static readonly Serializer<byte> ByteSerializer = Serializer.Get<byte>(); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Color32 value, IDataReader reader) + { + value.r = Color32Formatter.ByteSerializer.ReadValue(reader); + value.g = Color32Formatter.ByteSerializer.ReadValue(reader); + value.b = Color32Formatter.ByteSerializer.ReadValue(reader); + value.a = Color32Formatter.ByteSerializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Color32 value, IDataWriter writer) + { + Color32Formatter.ByteSerializer.WriteValue(value.r, writer); + Color32Formatter.ByteSerializer.WriteValue(value.g, writer); + Color32Formatter.ByteSerializer.WriteValue(value.b, writer); + Color32Formatter.ByteSerializer.WriteValue(value.a, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Color32Formatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Color32Formatter.cs.meta new file mode 100644 index 00000000..0ede86ff --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Color32Formatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0878ec68b6ab3c9ebc365b6d139e4840 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorBlockFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorBlockFormatter.cs new file mode 100644 index 00000000..3528161b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorBlockFormatter.cs @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------- +// <copyright file="ColorBlockFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatterLocator(typeof(ColorBlockFormatterLocator))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Reflection; + using UnityEngine; + + public class ColorBlockFormatterLocator : IFormatterLocator + { + public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter) + { + if (step == FormatterLocationStep.BeforeRegisteredFormatters && type.FullName == "UnityEngine.UI.ColorBlock") + { + var formatterType = typeof(ColorBlockFormatter<>).MakeGenericType(type); + formatter = (IFormatter)Activator.CreateInstance(formatterType); + return true; + } + + formatter = null; + return false; + } + } + + /// <summary> + /// Custom formatter for the <see cref="ColorBlock"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.UI.ColorBlock}" /> + public class ColorBlockFormatter<T> : MinimalBaseFormatter<T> + { + private static readonly Serializer<float> FloatSerializer = Serializer.Get<float>(); + private static readonly Serializer<Color> ColorSerializer = Serializer.Get<Color>(); + + private static readonly PropertyInfo normalColor = typeof(T).GetProperty("normalColor"); + private static readonly PropertyInfo highlightedColor = typeof(T).GetProperty("highlightedColor"); + private static readonly PropertyInfo pressedColor = typeof(T).GetProperty("pressedColor"); + private static readonly PropertyInfo disabledColor = typeof(T).GetProperty("disabledColor"); + private static readonly PropertyInfo colorMultiplier = typeof(T).GetProperty("colorMultiplier"); + private static readonly PropertyInfo fadeDuration = typeof(T).GetProperty("fadeDuration"); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref T value, IDataReader reader) + { + object boxed = value; + + normalColor.SetValue(boxed, ColorSerializer.ReadValue(reader), null); + highlightedColor.SetValue(boxed, ColorSerializer.ReadValue(reader), null); + pressedColor.SetValue(boxed, ColorSerializer.ReadValue(reader), null); + disabledColor.SetValue(boxed, ColorSerializer.ReadValue(reader), null); + colorMultiplier.SetValue(boxed, FloatSerializer.ReadValue(reader), null); + fadeDuration.SetValue(boxed, FloatSerializer.ReadValue(reader), null); + + value = (T)boxed; + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref T value, IDataWriter writer) + { + ColorSerializer.WriteValue((Color)normalColor.GetValue(value, null), writer); + ColorSerializer.WriteValue((Color)highlightedColor.GetValue(value, null), writer); + ColorSerializer.WriteValue((Color)pressedColor.GetValue(value, null), writer); + ColorSerializer.WriteValue((Color)disabledColor.GetValue(value, null), writer); + FloatSerializer.WriteValue((float)colorMultiplier.GetValue(value, null), writer); + FloatSerializer.WriteValue((float)fadeDuration.GetValue(value, null), writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorBlockFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorBlockFormatter.cs.meta new file mode 100644 index 00000000..c63edfd6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorBlockFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 25e35581ce6d1febd9ac41864a76ecdb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorFormatter.cs new file mode 100644 index 00000000..d5bd2621 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorFormatter.cs @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------- +// <copyright file="ColorFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(ColorFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="Color"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.Color}" /> + public class ColorFormatter : MinimalBaseFormatter<Color> + { + private static readonly Serializer<float> FloatSerializer = Serializer.Get<float>(); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Color value, IDataReader reader) + { + value.r = ColorFormatter.FloatSerializer.ReadValue(reader); + value.g = ColorFormatter.FloatSerializer.ReadValue(reader); + value.b = ColorFormatter.FloatSerializer.ReadValue(reader); + value.a = ColorFormatter.FloatSerializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Color value, IDataWriter writer) + { + ColorFormatter.FloatSerializer.WriteValue(value.r, writer); + ColorFormatter.FloatSerializer.WriteValue(value.g, writer); + ColorFormatter.FloatSerializer.WriteValue(value.b, writer); + ColorFormatter.FloatSerializer.WriteValue(value.a, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorFormatter.cs.meta new file mode 100644 index 00000000..4963c973 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/ColorFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 484768ba343a6a05522c29d81a4ce61d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/CoroutineFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/CoroutineFormatter.cs new file mode 100644 index 00000000..7201c6b2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/CoroutineFormatter.cs @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------- +// <copyright file="CoroutineFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(CoroutineFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using UnityEngine; + + /// <summary> + /// <para> + /// Custom formatter for the <see cref="Coroutine"/> type. + /// This serializes nothing and always deserializes null, + /// and only exists to ensure that no coroutine instances + /// are ever created by the serialization system, since they + /// will in almost all cases be invalid instances. + /// </para> + /// <para> + /// Invalid coroutine instances crash Unity instantly when + /// they are garbage collected. + /// </para> + /// </summary> + public sealed class CoroutineFormatter : IFormatter<Coroutine> + { + /// <summary> + /// Gets the type that the formatter can serialize. + /// </summary> + /// <value> + /// The type that the formatter can serialize. + /// </value> + public Type SerializedType { get { return typeof(Coroutine); } } + + /// <summary> + /// Returns null. + /// </summary> + object IFormatter.Deserialize(IDataReader reader) + { + return null; + } + + /// <summary> + /// Returns null. + /// </summary> + public Coroutine Deserialize(IDataReader reader) + { + return null; + } + + /// <summary> + /// Does nothing. + /// </summary> + public void Serialize(object value, IDataWriter writer) + { + } + + /// <summary> + /// Does nothing. + /// </summary> + public void Serialize(Coroutine value, IDataWriter writer) + { + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/CoroutineFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/CoroutineFormatter.cs.meta new file mode 100644 index 00000000..cb1282bf --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/CoroutineFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3968bef792c5668478ac01be7645b30 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientAlphaKeyFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientAlphaKeyFormatter.cs new file mode 100644 index 00000000..01ca811f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientAlphaKeyFormatter.cs @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------- +// <copyright file="GradientAlphaKeyFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(GradientAlphaKeyFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="GradientAlphaKey"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.GradientAlphaKey}" /> + public class GradientAlphaKeyFormatter : MinimalBaseFormatter<GradientAlphaKey> + { + private static readonly Serializer<float> FloatSerializer = Serializer.Get<float>(); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref GradientAlphaKey value, IDataReader reader) + { + value.alpha = GradientAlphaKeyFormatter.FloatSerializer.ReadValue(reader); + value.time = GradientAlphaKeyFormatter.FloatSerializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref GradientAlphaKey value, IDataWriter writer) + { + GradientAlphaKeyFormatter.FloatSerializer.WriteValue(value.alpha, writer); + GradientAlphaKeyFormatter.FloatSerializer.WriteValue(value.time, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientAlphaKeyFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientAlphaKeyFormatter.cs.meta new file mode 100644 index 00000000..1b0a78e1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientAlphaKeyFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b5b415c00da8157ac50b8f5543f0b1d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientColorKeyFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientColorKeyFormatter.cs new file mode 100644 index 00000000..324cbb78 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientColorKeyFormatter.cs @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------- +// <copyright file="GradientColorKeyFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(GradientColorKeyFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="GradientColorKey"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.GradientColorKey}" /> + public class GradientColorKeyFormatter : MinimalBaseFormatter<GradientColorKey> + { + private static readonly Serializer<Color> ColorSerializer = Serializer.Get<Color>(); + private static readonly Serializer<float> FloatSerializer = Serializer.Get<float>(); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref GradientColorKey value, IDataReader reader) + { + value.color = GradientColorKeyFormatter.ColorSerializer.ReadValue(reader); + value.time = GradientColorKeyFormatter.FloatSerializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref GradientColorKey value, IDataWriter writer) + { + GradientColorKeyFormatter.ColorSerializer.WriteValue(value.color, writer); + GradientColorKeyFormatter.FloatSerializer.WriteValue(value.time, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientColorKeyFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientColorKeyFormatter.cs.meta new file mode 100644 index 00000000..7f99b928 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientColorKeyFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8936a3e43078251682f18923139f7aee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientFormatter.cs new file mode 100644 index 00000000..e0fe9c84 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientFormatter.cs @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------- +// <copyright file="GradientFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(GradientFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Reflection; + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="Gradient"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.Gradient}" /> + public class GradientFormatter : MinimalBaseFormatter<Gradient> + { + private static readonly Serializer<GradientAlphaKey[]> AlphaKeysSerializer = Serializer.Get<GradientAlphaKey[]>(); + private static readonly Serializer<GradientColorKey[]> ColorKeysSerializer = Serializer.Get<GradientColorKey[]>(); + + // The Gradient.mode member of type UnityEngine.GradientMode was added in a later version of Unity + // Therefore we need to handle it using reflection, as it might not be there if Odin is running in an early version + + private static readonly PropertyInfo ModeProperty = typeof(Gradient).GetProperty("mode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + private static readonly Serializer<object> EnumSerializer = ModeProperty != null ? Serializer.Get<object>() : null; + + protected override Gradient GetUninitializedObject() + { + return new Gradient(); + } + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Gradient value, IDataReader reader) + { + value.alphaKeys = GradientFormatter.AlphaKeysSerializer.ReadValue(reader); + value.colorKeys = GradientFormatter.ColorKeysSerializer.ReadValue(reader); + + string name; + reader.PeekEntry(out name); + + if (name == "mode") + { + try + { + if (ModeProperty != null) + { + ModeProperty.SetValue(value, EnumSerializer.ReadValue(reader), null); + } + else + { + reader.SkipEntry(); + } + } + catch (Exception) + { + reader.Context.Config.DebugContext.LogWarning("Failed to read Gradient.mode, due to Unity's API disallowing setting of this member on other threads than the main thread. Gradient.mode value will have been lost."); + } + } + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Gradient value, IDataWriter writer) + { + GradientFormatter.AlphaKeysSerializer.WriteValue(value.alphaKeys, writer); + GradientFormatter.ColorKeysSerializer.WriteValue(value.colorKeys, writer); + + if (ModeProperty != null) + { + try + { + EnumSerializer.WriteValue("mode", ModeProperty.GetValue(value, null), writer); + } + catch (Exception) + { + writer.Context.Config.DebugContext.LogWarning("Failed to write Gradient.mode, due to Unity's API disallowing setting of this member on other threads than the main thread. Gradient.mode will have been lost upon deserialization."); + } + + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientFormatter.cs.meta new file mode 100644 index 00000000..8c4ad104 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/GradientFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d5b54660d5342fd45e2e43775538879d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/KeyframeFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/KeyframeFormatter.cs new file mode 100644 index 00000000..c2aa73e1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/KeyframeFormatter.cs @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------- +// <copyright file="KeyframeFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(KeyframeFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using VRC.Udon.Serialization.OdinSerializer.Utilities; + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="Keyframe"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.Keyframe}" /> + public class KeyframeFormatter : MinimalBaseFormatter<Keyframe> + { + private static readonly Serializer<float> FloatSerializer = Serializer.Get<float>(); + private static readonly Serializer<int> IntSerializer = Serializer.Get<int>(); + + private static readonly bool Is_In_2018_1_Or_Above; + private static IFormatter<Keyframe> Formatter; + + static KeyframeFormatter() + { + Is_In_2018_1_Or_Above = typeof(Keyframe).GetProperty("weightedMode") != null; + + if (Is_In_2018_1_Or_Above) + { + if (EmitUtilities.CanEmit) + { + Formatter = (IFormatter<Keyframe>)FormatterEmitter.GetEmittedFormatter(typeof(Keyframe), SerializationPolicies.Everything); + } + else + { + Formatter = new ReflectionFormatter<Keyframe>(SerializationPolicies.Everything); + } + } + } + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Keyframe value, IDataReader reader) + { + string name; + EntryType first = reader.PeekEntry(out name); + + if (first == EntryType.Integer && name == "ver") + { + if (Formatter == null) + { + // We're deserializing 2018.1+ data in a lower version of Unity - so just give it a go + Formatter = new ReflectionFormatter<Keyframe>(SerializationPolicies.Everything); + } + + int version; + reader.ReadInt32(out version); + + // Only one version so far, so ignore it for now + value = Formatter.Deserialize(reader); + } + else + { + // Legacy Keyframe deserialization code + value.inTangent = KeyframeFormatter.FloatSerializer.ReadValue(reader); + value.outTangent = KeyframeFormatter.FloatSerializer.ReadValue(reader); + value.time = KeyframeFormatter.FloatSerializer.ReadValue(reader); + value.value = KeyframeFormatter.FloatSerializer.ReadValue(reader); + +#pragma warning disable 0618 + value.tangentMode = KeyframeFormatter.IntSerializer.ReadValue(reader); +#pragma warning restore 0618 + } + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Keyframe value, IDataWriter writer) + { + if (Is_In_2018_1_Or_Above) + { + writer.WriteInt32("ver", 1); + Formatter.Serialize(value, writer); + } + else + { + // Legacy Keyframe serialization code + KeyframeFormatter.FloatSerializer.WriteValue(value.inTangent, writer); + KeyframeFormatter.FloatSerializer.WriteValue(value.outTangent, writer); + KeyframeFormatter.FloatSerializer.WriteValue(value.time, writer); + KeyframeFormatter.FloatSerializer.WriteValue(value.value, writer); + +#pragma warning disable 0618 + KeyframeFormatter.IntSerializer.WriteValue(value.tangentMode, writer); +#pragma warning restore 0618 + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/KeyframeFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/KeyframeFormatter.cs.meta new file mode 100644 index 00000000..785081be --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/KeyframeFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 68ac0b27f571616d3ed26c23eef40c8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/LayerMaskFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/LayerMaskFormatter.cs new file mode 100644 index 00000000..4231e03c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/LayerMaskFormatter.cs @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------- +// <copyright file="LayerMaskFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(LayerMaskFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="LayerMask"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.LayerMask}" /> + public class LayerMaskFormatter : MinimalBaseFormatter<LayerMask> + { + private static readonly Serializer<int> IntSerializer = Serializer.Get<int>(); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref LayerMask value, IDataReader reader) + { + value.value = LayerMaskFormatter.IntSerializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref LayerMask value, IDataWriter writer) + { + LayerMaskFormatter.IntSerializer.WriteValue(value.value, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/LayerMaskFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/LayerMaskFormatter.cs.meta new file mode 100644 index 00000000..d24fe609 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/LayerMaskFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afc596cd95a1ac316024d16f6fec6536 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/QuaternionFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/QuaternionFormatter.cs new file mode 100644 index 00000000..230fcc8f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/QuaternionFormatter.cs @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------- +// <copyright file="QuaternionFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(QuaternionFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="Quaternion"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.Quaternion}" /> + public class QuaternionFormatter : MinimalBaseFormatter<Quaternion> + { + private static readonly Serializer<float> FloatSerializer = Serializer.Get<float>(); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Quaternion value, IDataReader reader) + { + value.x = QuaternionFormatter.FloatSerializer.ReadValue(reader); + value.y = QuaternionFormatter.FloatSerializer.ReadValue(reader); + value.z = QuaternionFormatter.FloatSerializer.ReadValue(reader); + value.w = QuaternionFormatter.FloatSerializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Quaternion value, IDataWriter writer) + { + QuaternionFormatter.FloatSerializer.WriteValue(value.x, writer); + QuaternionFormatter.FloatSerializer.WriteValue(value.y, writer); + QuaternionFormatter.FloatSerializer.WriteValue(value.z, writer); + QuaternionFormatter.FloatSerializer.WriteValue(value.w, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/QuaternionFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/QuaternionFormatter.cs.meta new file mode 100644 index 00000000..ec7ce1cb --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/QuaternionFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 558323987bf9b75943382a5faa093ee3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/RectFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/RectFormatter.cs new file mode 100644 index 00000000..bd309e5b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/RectFormatter.cs @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------- +// <copyright file="RectFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(RectFormatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="Rect"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.Rect}" /> + public class RectFormatter : MinimalBaseFormatter<Rect> + { + private static readonly Serializer<float> FloatSerializer = Serializer.Get<float>(); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Rect value, IDataReader reader) + { + value.x = RectFormatter.FloatSerializer.ReadValue(reader); + value.y = RectFormatter.FloatSerializer.ReadValue(reader); + value.width = RectFormatter.FloatSerializer.ReadValue(reader); + value.height = RectFormatter.FloatSerializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Rect value, IDataWriter writer) + { + RectFormatter.FloatSerializer.WriteValue(value.x, writer); + RectFormatter.FloatSerializer.WriteValue(value.y, writer); + RectFormatter.FloatSerializer.WriteValue(value.width, writer); + RectFormatter.FloatSerializer.WriteValue(value.height, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/RectFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/RectFormatter.cs.meta new file mode 100644 index 00000000..3461fb01 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/RectFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 196809b991e565a48e3d4ad08cb30b5e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/UnityEventFormatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/UnityEventFormatter.cs new file mode 100644 index 00000000..a2568444 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/UnityEventFormatter.cs @@ -0,0 +1,47 @@ +//VRC TCL: Disabled for security + +//----------------------------------------------------------------------- +// <copyright file="UnityEventFormatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +// using VRC.Udon.Serialization.OdinSerializer; +// +// [assembly: RegisterFormatter(typeof(UnityEventFormatter<>))] +// +// namespace VRC.Udon.Serialization.OdinSerializer +// { +// using UnityEngine.Events; +// +// /// <summary> +// /// Custom generic formatter for the <see cref="UnityEvent{T0}"/>, <see cref="UnityEvent{T0, T1}"/>, <see cref="UnityEvent{T0, T1, T2}"/> and <see cref="UnityEvent{T0, T1, T2, T3}"/> types. +// /// </summary> +// /// <typeparam name="T">The type of UnityEvent that this formatter can serialize and deserialize.</typeparam> +// /// <seealso cref="ReflectionFormatter{UnityEngine.Events.UnityEvent}" /> +// public class UnityEventFormatter<T> : ReflectionFormatter<T> where T : UnityEventBase, new() +// { +// /// <summary> +// /// Get an uninitialized object of type <see cref="T" />. +// /// </summary> +// /// <returns> +// /// An uninitialized object of type <see cref="T" />. +// /// </returns> +// protected override T GetUninitializedObject() +// { +// return new T(); +// } +// } +// } diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/UnityEventFormatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/UnityEventFormatter.cs.meta new file mode 100644 index 00000000..cfc50f2b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/UnityEventFormatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c934302874ac3315ed322feefefa1f9c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector2Formatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector2Formatter.cs new file mode 100644 index 00000000..eb6a8488 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector2Formatter.cs @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------- +// <copyright file="Vector2Formatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(Vector2Formatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="Vector2"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.Vector2}" /> + public class Vector2Formatter : MinimalBaseFormatter<Vector2> + { + private static readonly Serializer<float> FloatSerializer = Serializer.Get<float>(); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Vector2 value, IDataReader reader) + { + value.x = Vector2Formatter.FloatSerializer.ReadValue(reader); + value.y = Vector2Formatter.FloatSerializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Vector2 value, IDataWriter writer) + { + Vector2Formatter.FloatSerializer.WriteValue(value.x, writer); + Vector2Formatter.FloatSerializer.WriteValue(value.y, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector2Formatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector2Formatter.cs.meta new file mode 100644 index 00000000..bc1ed4e4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector2Formatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 70c675a7b4c71c685ee39d745ccb058b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector3Formatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector3Formatter.cs new file mode 100644 index 00000000..139ad212 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector3Formatter.cs @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------- +// <copyright file="Vector3Formatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(Vector3Formatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="Vector3"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.Vector3}" /> + public class Vector3Formatter : MinimalBaseFormatter<Vector3> + { + private static readonly Serializer<float> FloatSerializer = Serializer.Get<float>(); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Vector3 value, IDataReader reader) + { + value.x = Vector3Formatter.FloatSerializer.ReadValue(reader); + value.y = Vector3Formatter.FloatSerializer.ReadValue(reader); + value.z = Vector3Formatter.FloatSerializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Vector3 value, IDataWriter writer) + { + Vector3Formatter.FloatSerializer.WriteValue(value.x, writer); + Vector3Formatter.FloatSerializer.WriteValue(value.y, writer); + Vector3Formatter.FloatSerializer.WriteValue(value.z, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector3Formatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector3Formatter.cs.meta new file mode 100644 index 00000000..d0280f99 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector3Formatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da2644647af1368176103aa87de1dbaf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector4Formatter.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector4Formatter.cs new file mode 100644 index 00000000..1beec915 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector4Formatter.cs @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------- +// <copyright file="Vector4Formatter.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +using VRC.Udon.Serialization.OdinSerializer; + +[assembly: RegisterFormatter(typeof(Vector4Formatter))] + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Custom formatter for the <see cref="Vector4"/> type. + /// </summary> + /// <seealso cref="MinimalBaseFormatter{UnityEngine.Vector4}" /> + public class Vector4Formatter : MinimalBaseFormatter<Vector4> + { + private static readonly Serializer<float> FloatSerializer = Serializer.Get<float>(); + + /// <summary> + /// Reads into the specified value using the specified reader. + /// </summary> + /// <param name="value">The value to read into.</param> + /// <param name="reader">The reader to use.</param> + protected override void Read(ref Vector4 value, IDataReader reader) + { + value.x = Vector4Formatter.FloatSerializer.ReadValue(reader); + value.y = Vector4Formatter.FloatSerializer.ReadValue(reader); + value.z = Vector4Formatter.FloatSerializer.ReadValue(reader); + value.w = Vector4Formatter.FloatSerializer.ReadValue(reader); + } + + /// <summary> + /// Writes from the specified value using the specified writer. + /// </summary> + /// <param name="value">The value to write from.</param> + /// <param name="writer">The writer to use.</param> + protected override void Write(ref Vector4 value, IDataWriter writer) + { + Vector4Formatter.FloatSerializer.WriteValue(value.x, writer); + Vector4Formatter.FloatSerializer.WriteValue(value.y, writer); + Vector4Formatter.FloatSerializer.WriteValue(value.z, writer); + Vector4Formatter.FloatSerializer.WriteValue(value.w, writer); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector4Formatter.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector4Formatter.cs.meta new file mode 100644 index 00000000..7e912d48 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/Formatters/Vector4Formatter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 60afa8ede3981c383782a01ddc55e943 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/OdinPrefabSerializationEditorUtility.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/OdinPrefabSerializationEditorUtility.cs new file mode 100644 index 00000000..4a870b1c --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/OdinPrefabSerializationEditorUtility.cs @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------- +// <copyright file="OdinPrefabSerializationEditorUtility.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +//#define PREFAB_DEBUG +#if UNITY_EDITOR +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + public static class OdinPrefabSerializationEditorUtility + { + private static bool? hasNewPrefabWorkflow; + private static MethodInfo PrefabUtility_GetPrefabAssetType_Method = typeof(PrefabUtility).GetMethod("GetPrefabAssetType", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(UnityEngine.Object) }, null); + private static MethodInfo PrefabUtility_GetPrefabParent_Method = typeof(PrefabUtility).GetMethod("GetPrefabParent", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(UnityEngine.Object) }, null); + private static MethodInfo PrefabUtility_GetCorrespondingObjectFromSource_Method = typeof(PrefabUtility).GetMethod("GetCorrespondingObjectFromSource", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(UnityEngine.Object) }, null); + private static MethodInfo PrefabUtility_GetPrefabType_Method = typeof(PrefabUtility).GetMethod("GetPrefabType", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(UnityEngine.Object) }, null); + private static MethodInfo PrefabUtility_ApplyPropertyOverride_Method; + + static OdinPrefabSerializationEditorUtility() + { + Type interactionModeEnum = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.InteractionMode"); + + if (interactionModeEnum != null) + { + PrefabUtility_ApplyPropertyOverride_Method = typeof(PrefabUtility).GetMethod("ApplyPropertyOverride", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(SerializedProperty), typeof(string), interactionModeEnum }, null); + } + } + + public static bool HasNewPrefabWorkflow + { + get + { + if (hasNewPrefabWorkflow == null) + { + hasNewPrefabWorkflow = DetectNewPrefabWorkflow(); + } + + return hasNewPrefabWorkflow.Value; + } + } + + public static bool HasApplyPropertyOverride + { + get + { + return PrefabUtility_ApplyPropertyOverride_Method != null; + } + } + + private static bool DetectNewPrefabWorkflow() + { + try + { + var method = typeof(PrefabUtility).GetMethod("GetPrefabType", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(UnityEngine.Object) }, null); + + if (method == null) return true; + + if (method.IsDefined(typeof(ObsoleteAttribute), false)) + { + return true; + } + + return false; + } + catch + { + return false; + } + } + + public static void ApplyPropertyOverride(SerializedProperty instanceProperty, string assetPath) + { + //PrefabUtility.ApplyPropertyOverride(instanceProperty, assetPath, InteractionMode.AutomatedAction); + if (!HasApplyPropertyOverride) throw new NotSupportedException("PrefabUtility.ApplyPropertyOverride doesn't exist in this version of Unity"); + PrefabUtility_ApplyPropertyOverride_Method.Invoke(null, new object[] { instanceProperty, assetPath, 0 }); + } + + public static bool ObjectIsPrefabInstance(UnityEngine.Object unityObject) + { + if (PrefabUtility_GetPrefabType_Method != null) + { + try + { + int prefabType = Convert.ToInt32((Enum)PrefabUtility_GetPrefabType_Method.Invoke(null, new object[] { unityObject })); + // PrefabType.PrefabInstance == 3 + if (prefabType == 3) return true; + } + catch (Exception) { } + } + + if (PrefabUtility_GetPrefabAssetType_Method != null) + { + int prefabAssetType = Convert.ToInt32((Enum)PrefabUtility_GetPrefabAssetType_Method.Invoke(null, new object[] { unityObject })); + // 1 = PrefabAssetType.Regular + // 3 = PrefabAssetType.Variant + return prefabAssetType == 1 || prefabAssetType == 3; + } + + if (PrefabUtility_GetPrefabType_Method == null && PrefabUtility_GetPrefabAssetType_Method == null) + { + Debug.LogError("Neither PrefabUtility.GetPrefabType or PrefabUtility.GetPrefabAssetType methods could be located. Prefab functionality will likely be broken in this build of Odin."); + } + + return GetCorrespondingObjectFromSource(unityObject) != null; + } + + public static bool ObjectHasNestedOdinPrefabData(UnityEngine.Object unityObject) + { + if (!HasNewPrefabWorkflow) return false; + if (!(unityObject is ISupportsPrefabSerialization)) return false; + var prefab = GetCorrespondingObjectFromSource(unityObject); + return IsOdinSerializedPrefabInstance(prefab); + } + + private static bool IsOdinSerializedPrefabInstance(UnityEngine.Object unityObject) + { + if (!(unityObject is ISupportsPrefabSerialization)) return false; + return GetCorrespondingObjectFromSource(unityObject) != null; + } + + public static UnityEngine.Object GetCorrespondingObjectFromSource(UnityEngine.Object unityObject) + { + if (PrefabUtility_GetCorrespondingObjectFromSource_Method != null) + { + return (UnityEngine.Object)PrefabUtility_GetCorrespondingObjectFromSource_Method.Invoke(null, new object[] { unityObject }); + } + + if (PrefabUtility_GetPrefabParent_Method != null) + { + return (UnityEngine.Object)PrefabUtility_GetPrefabParent_Method.Invoke(null, new object[] { unityObject }); + } + + Debug.LogError("Neither PrefabUtility.GetCorrespondingObjectFromSource or PrefabUtility.GetPrefabParent methods could be located. Prefab functionality will be broken in this build of Odin."); + return null; + } + } +} +#endif
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/OdinPrefabSerializationEditorUtility.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/OdinPrefabSerializationEditorUtility.cs.meta new file mode 100644 index 00000000..4e44bfb1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/OdinPrefabSerializationEditorUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aaf2f90207414827b53b85dae0eae82e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects.meta new file mode 100644 index 00000000..81f64b31 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2fafac9a46c188b409d374352cf956c3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationFormat.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationFormat.cs new file mode 100644 index 00000000..4771de1b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationFormat.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// <copyright file="IOverridesSerializationFormat.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Indicates that an Odin-serialized Unity object controls its own serialization format. Every time it is serialized, it will be asked which format to use. + /// </summary> + public interface IOverridesSerializationFormat + { + /// <summary> + /// Gets the format to use for serialization. + /// </summary> + DataFormat GetFormatToSerializeAs(bool isPlayer); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationFormat.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationFormat.cs.meta new file mode 100644 index 00000000..9ec81ef0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationFormat.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff1ca109149d83b03b39644f8045275e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationPolicy.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationPolicy.cs new file mode 100644 index 00000000..6f46532f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationPolicy.cs @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------- +// <copyright file="IOverridesSerializationPolicy.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Indicates that an Odin-serialized Unity object provides its own serialization policy rather than using the default policy. + /// <para/> + /// Note that THE VALUES RETURNED BY THIS INTERFACE WILL OVERRIDE THE PARAMETERS PASSED TO <see cref="UnitySerializationUtility.SerializeUnityObject(UnityEngine.Object, ref SerializationData, bool, SerializationContext)"/> and <see cref="UnitySerializationUtility.DeserializeUnityObject(UnityEngine.Object, ref SerializationData, DeserializationContext)"/>. + /// </summary> + public interface IOverridesSerializationPolicy + { + ISerializationPolicy SerializationPolicy { get; } + bool OdinSerializesUnityFields { get; } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationPolicy.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationPolicy.cs.meta new file mode 100644 index 00000000..57a0c8a0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/IOverridesSerializationPolicy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8942002e9ac41c2bfd27c4fbedf93f09 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/ISupportsPrefabSerialization.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/ISupportsPrefabSerialization.cs new file mode 100644 index 00000000..507eaa70 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/ISupportsPrefabSerialization.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// <copyright file="ISupportsPrefabSerialization.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + /// <summary> + /// Indicates that an Odin-serialized Unity object supports prefab serialization. + /// </summary> + public interface ISupportsPrefabSerialization + { + /// <summary> + /// Gets or sets the serialization data of the object. + /// </summary> + SerializationData SerializationData { get; set; } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/ISupportsPrefabSerialization.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/ISupportsPrefabSerialization.cs.meta new file mode 100644 index 00000000..1023fe57 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/ISupportsPrefabSerialization.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7279ec8ad7837f13ec833193ab4282cc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializationData.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializationData.cs new file mode 100644 index 00000000..8b441751 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializationData.cs @@ -0,0 +1,175 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializationData.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System; + using System.Collections.Generic; + using UnityEngine; + using Utilities; + + /// <summary> + /// Unity serialized data struct that contains all data needed by Odin serialization. + /// </summary> + [Serializable] + public struct SerializationData + { + /// <summary> + /// The name of the <see cref="PrefabModificationsReferencedUnityObjects"/> field. + /// </summary> + public const string PrefabModificationsReferencedUnityObjectsFieldName = "PrefabModificationsReferencedUnityObjects"; + + /// <summary> + /// The name of the <see cref="PrefabModifications"/> field. + /// </summary> + public const string PrefabModificationsFieldName = "PrefabModifications"; + + /// <summary> + /// The name of the <see cref="Prefab"/> field. + /// </summary> + public const string PrefabFieldName = "Prefab"; + + /// <summary> + /// The data format used by the serializer. This field will be automatically set to the format specified in the global serialization config + /// when the Unity object gets serialized, unless the Unity object implements the <see cref="IOverridesSerializationFormat"/> interface. + /// </summary> + [SerializeField] + public DataFormat SerializedFormat; + + /// <summary> + /// The serialized data when serializing with the Binray format. + /// </summary> + [SerializeField] + public byte[] SerializedBytes; + + /// <summary> + /// All serialized Unity references. + /// </summary> + [SerializeField] + public List<UnityEngine.Object> ReferencedUnityObjects; + + /// <summary> + /// Whether the object contains any serialized data. + /// </summary> + [Obsolete("Use ContainsData instead")] + public bool HasEditorData + { + get + { + switch (this.SerializedFormat) + { + case DataFormat.Binary: + case DataFormat.JSON: + return !(this.SerializedBytesString.IsNullOrWhitespace() && (this.SerializedBytes == null || this.SerializedBytes.Length == 0)); + + case DataFormat.Nodes: + return !(this.SerializationNodes == null || this.SerializationNodes.Count == 0); + + default: + throw new NotImplementedException(this.SerializedFormat.ToString()); + } + } + } + + /// <summary> + /// Gets a value indicating whether the struct contains any data. + /// If this is false, then it could mean that Unity has not yet deserialized the struct. + /// </summary> + public bool ContainsData + { + get + { + return + // this.SerializedBytesString != null && // Unity serialized strings remains null when an object is created until it's deserialized. + this.SerializedBytes != null && + this.SerializationNodes != null && + this.PrefabModifications != null && + this.ReferencedUnityObjects != null; + } + } + + /// <summary> + /// The serialized data when serializing with the JSON format. + /// </summary> + [SerializeField] + public string SerializedBytesString; + + /// <summary> + /// The reference to the prefab this is only populated in prefab scene instances. + /// </summary> + [SerializeField] + public UnityEngine.Object Prefab; + + /// <summary> + /// All serialized Unity references. + /// </summary> + [SerializeField] + public List<UnityEngine.Object> PrefabModificationsReferencedUnityObjects; + + /// <summary> + /// All Odin serialized prefab modifications. + /// </summary> + [SerializeField] + public List<string> PrefabModifications; + + /// <summary> + /// The serialized data when serializing with the Nodes format. + /// </summary> + [SerializeField] + public List<SerializationNode> SerializationNodes; + + /// <summary> + /// Resets all data. + /// </summary> + public void Reset() + { + this.SerializedFormat = DataFormat.Binary; + + if (this.SerializedBytes != null && this.SerializedBytes.Length > 0) + { + this.SerializedBytes = new byte[0]; + } + + if (this.ReferencedUnityObjects != null && this.ReferencedUnityObjects.Count > 0) + { + this.ReferencedUnityObjects.Clear(); + } + + this.Prefab = null; + + if (this.SerializationNodes != null && this.SerializationNodes.Count > 0) + { + this.SerializationNodes.Clear(); + } + + if (this.SerializedBytesString != null && this.SerializedBytesString.Length > 0) + { + this.SerializedBytesString = string.Empty; + } + + if (this.PrefabModificationsReferencedUnityObjects != null && this.PrefabModificationsReferencedUnityObjects.Count > 0) + { + this.PrefabModificationsReferencedUnityObjects.Clear(); + } + + if (this.PrefabModifications != null && this.PrefabModifications.Count > 0) + { + this.PrefabModifications.Clear(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializationData.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializationData.cs.meta new file mode 100644 index 00000000..95b776f7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializationData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ea095023abd05a7af0da4166dcefdee8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedBehaviour.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedBehaviour.cs new file mode 100644 index 00000000..f31b8b4d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedBehaviour.cs @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializedBehaviour.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// A Unity Behaviour which is serialized by the Sirenix serialization system. + /// </summary> +#if ODIN_INSPECTOR + [Sirenix.OdinInspector.ShowOdinSerializedPropertiesInInspector] +#endif + + public abstract class SerializedBehaviour : Behaviour, ISerializationCallbackReceiver, ISupportsPrefabSerialization + { + [SerializeField, HideInInspector] + private SerializationData serializationData; + + SerializationData ISupportsPrefabSerialization.SerializationData { get { return this.serializationData; } set { this.serializationData = value; } } + + void ISerializationCallbackReceiver.OnAfterDeserialize() + { + UnitySerializationUtility.DeserializeUnityObject(this, ref this.serializationData); + this.OnAfterDeserialize(); + } + + void ISerializationCallbackReceiver.OnBeforeSerialize() + { + this.OnBeforeSerialize(); + UnitySerializationUtility.SerializeUnityObject(this, ref this.serializationData); + } + + /// <summary> + /// Invoked after deserialization has taken place. + /// </summary> + protected virtual void OnAfterDeserialize() + { + } + + /// <summary> + /// Invoked before serialization has taken place. + /// </summary> + protected virtual void OnBeforeSerialize() + { + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedBehaviour.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedBehaviour.cs.meta new file mode 100644 index 00000000..605a78ee --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedBehaviour.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3cecb461cebbc940ede3b5ddb72382e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedComponent.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedComponent.cs new file mode 100644 index 00000000..47ba4f3b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedComponent.cs @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializedComponent.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// A Unity Component which is serialized by the Sirenix serialization system. + /// </summary> +#if ODIN_INSPECTOR + [Sirenix.OdinInspector.ShowOdinSerializedPropertiesInInspector] +#endif + + public abstract class SerializedComponent : Component, ISerializationCallbackReceiver, ISupportsPrefabSerialization + { + [SerializeField, HideInInspector] + private SerializationData serializationData; + + SerializationData ISupportsPrefabSerialization.SerializationData { get { return this.serializationData; } set { this.serializationData = value; } } + + void ISerializationCallbackReceiver.OnAfterDeserialize() + { + UnitySerializationUtility.DeserializeUnityObject(this, ref this.serializationData); + this.OnAfterDeserialize(); + } + + void ISerializationCallbackReceiver.OnBeforeSerialize() + { + this.OnBeforeSerialize(); + UnitySerializationUtility.SerializeUnityObject(this, ref this.serializationData); + } + + /// <summary> + /// Invoked after deserialization has taken place. + /// </summary> + protected virtual void OnAfterDeserialize() + { + } + + /// <summary> + /// Invoked before serialization has taken place. + /// </summary> + protected virtual void OnBeforeSerialize() + { + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedComponent.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedComponent.cs.meta new file mode 100644 index 00000000..903c320b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 56b88cfe9935184fe250bda018144f26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedMonoBehaviour.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedMonoBehaviour.cs new file mode 100644 index 00000000..8d5ea413 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedMonoBehaviour.cs @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializedMonoBehaviour.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// A Unity MonoBehaviour which is serialized by the Sirenix serialization system. + /// </summary> +#if ODIN_INSPECTOR + [Sirenix.OdinInspector.ShowOdinSerializedPropertiesInInspector] +#endif + + public abstract class SerializedMonoBehaviour : MonoBehaviour, ISerializationCallbackReceiver, ISupportsPrefabSerialization + { + [SerializeField, HideInInspector] + private SerializationData serializationData; + + SerializationData ISupportsPrefabSerialization.SerializationData { get { return this.serializationData; } set { this.serializationData = value; } } + + void ISerializationCallbackReceiver.OnAfterDeserialize() + { + UnitySerializationUtility.DeserializeUnityObject(this, ref this.serializationData); + this.OnAfterDeserialize(); + } + + void ISerializationCallbackReceiver.OnBeforeSerialize() + { + this.OnBeforeSerialize(); + UnitySerializationUtility.SerializeUnityObject(this, ref this.serializationData); + } + + /// <summary> + /// Invoked after deserialization has taken place. + /// </summary> + protected virtual void OnAfterDeserialize() + { + } + + /// <summary> + /// Invoked before serialization has taken place. + /// </summary> + protected virtual void OnBeforeSerialize() + { + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedMonoBehaviour.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedMonoBehaviour.cs.meta new file mode 100644 index 00000000..8a926e47 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedMonoBehaviour.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1b9fa6342beb9fdfc2c4bc1d8e5e971 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedScriptableObject.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedScriptableObject.cs new file mode 100644 index 00000000..f0af64ee --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedScriptableObject.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializedScriptableObject.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// A Unity ScriptableObject which is serialized by the Sirenix serialization system. + /// </summary> +#if ODIN_INSPECTOR + [Sirenix.OdinInspector.ShowOdinSerializedPropertiesInInspector] +#endif + + public abstract class SerializedScriptableObject : ScriptableObject, ISerializationCallbackReceiver + { + [SerializeField, HideInInspector] + private SerializationData serializationData; + + void ISerializationCallbackReceiver.OnAfterDeserialize() + { + UnitySerializationUtility.DeserializeUnityObject(this, ref this.serializationData); + this.OnAfterDeserialize(); + } + + void ISerializationCallbackReceiver.OnBeforeSerialize() + { + this.OnBeforeSerialize(); + UnitySerializationUtility.SerializeUnityObject(this, ref this.serializationData); + } + + /// <summary> + /// Invoked after deserialization has taken place. + /// </summary> + protected virtual void OnAfterDeserialize() + { + } + + /// <summary> + /// Invoked before serialization has taken place. + /// </summary> + protected virtual void OnBeforeSerialize() + { + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedScriptableObject.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedScriptableObject.cs.meta new file mode 100644 index 00000000..72be5810 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedScriptableObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6cb9325ffffee5d6ed94d71590b4049a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedStateMachineBehaviour.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedStateMachineBehaviour.cs new file mode 100644 index 00000000..85a01e97 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedStateMachineBehaviour.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializedStateMachineBehaviour.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// A Unity StateMachineBehaviour which is serialized by the Sirenix serialization system. + /// </summary> +#if ODIN_INSPECTOR + [Sirenix.OdinInspector.ShowOdinSerializedPropertiesInInspector] +#endif + + public abstract class SerializedStateMachineBehaviour : StateMachineBehaviour, ISerializationCallbackReceiver + { + [SerializeField, HideInInspector] + private SerializationData serializationData; + + void ISerializationCallbackReceiver.OnAfterDeserialize() + { + UnitySerializationUtility.DeserializeUnityObject(this, ref this.serializationData); + this.OnAfterDeserialize(); + } + + void ISerializationCallbackReceiver.OnBeforeSerialize() + { + this.OnBeforeSerialize(); + UnitySerializationUtility.SerializeUnityObject(this, ref this.serializationData); + } + + /// <summary> + /// Invoked after deserialization has taken place. + /// </summary> + protected virtual void OnAfterDeserialize() + { + } + + /// <summary> + /// Invoked before serialization has taken place. + /// </summary> + protected virtual void OnBeforeSerialize() + { + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedStateMachineBehaviour.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedStateMachineBehaviour.cs.meta new file mode 100644 index 00000000..442e5312 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedStateMachineBehaviour.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eefcd68a84ee7903b08c6254c17fafe3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedUnityObject.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedUnityObject.cs new file mode 100644 index 00000000..86a75a75 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedUnityObject.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// <copyright file="SerializedUnityObject.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// A Unity ScriptableObject which is serialized by the Sirenix serialization system. + /// </summary> +#if ODIN_INSPECTOR + [Sirenix.OdinInspector.ShowOdinSerializedPropertiesInInspector] +#endif + + public abstract class SerializedUnityObject : UnityEngine.Object, ISerializationCallbackReceiver + { + [SerializeField, HideInInspector] + private SerializationData serializationData; + + void ISerializationCallbackReceiver.OnAfterDeserialize() + { + UnitySerializationUtility.DeserializeUnityObject(this, ref this.serializationData); + this.OnAfterDeserialize(); + } + + void ISerializationCallbackReceiver.OnBeforeSerialize() + { + this.OnBeforeSerialize(); + UnitySerializationUtility.SerializeUnityObject(this, ref this.serializationData); + } + + /// <summary> + /// Invoked after deserialization has taken place. + /// </summary> + protected virtual void OnAfterDeserialize() + { + } + + /// <summary> + /// Invoked before serialization has taken place. + /// </summary> + protected virtual void OnBeforeSerialize() + { + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedUnityObject.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedUnityObject.cs.meta new file mode 100644 index 00000000..738d24b4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/SerializedUnityObjects/SerializedUnityObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d62f7ab4e5aa075b819d6c71e929686b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnityReferenceResolver.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnityReferenceResolver.cs new file mode 100644 index 00000000..a7cff020 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnityReferenceResolver.cs @@ -0,0 +1,160 @@ +//----------------------------------------------------------------------- +// <copyright file="UnityReferenceResolver.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System.Collections.Generic; + using Utilities; + + /// <summary> + /// Resolves external index references to Unity objects. + /// </summary> + /// <seealso cref="IExternalIndexReferenceResolver" /> + /// <seealso cref="ICacheNotificationReceiver" /> + public sealed class UnityReferenceResolver : IExternalIndexReferenceResolver, ICacheNotificationReceiver + { + private Dictionary<UnityEngine.Object, int> referenceIndexMapping = new Dictionary<UnityEngine.Object, int>(32, ReferenceEqualityComparer<UnityEngine.Object>.Default); + private List<UnityEngine.Object> referencedUnityObjects; + + /// <summary> + /// Initializes a new instance of the <see cref="UnityReferenceResolver"/> class. + /// </summary> + public UnityReferenceResolver() + { + this.referencedUnityObjects = new List<UnityEngine.Object>(); + } + + /// <summary> + /// Initializes a new instance of the <see cref="UnityReferenceResolver"/> class with a list of Unity objects. + /// </summary> + /// <param name="referencedUnityObjects">The referenced Unity objects.</param> + public UnityReferenceResolver(List<UnityEngine.Object> referencedUnityObjects) + { + this.SetReferencedUnityObjects(referencedUnityObjects); + } + + /// <summary> + /// Gets the currently referenced Unity objects. + /// </summary> + /// <returns>A list of the currently referenced Unity objects.</returns> + public List<UnityEngine.Object> GetReferencedUnityObjects() + { + return this.referencedUnityObjects; + } + + /// <summary> + /// Sets the referenced Unity objects of the resolver to a given list, or a new list if the value is null. + /// </summary> + /// <param name="referencedUnityObjects">The referenced Unity objects to set, or null if a new list is required.</param> + public void SetReferencedUnityObjects(List<UnityEngine.Object> referencedUnityObjects) + { + if (referencedUnityObjects == null) + { + referencedUnityObjects = new List<UnityEngine.Object>(); + } + + this.referencedUnityObjects = referencedUnityObjects; + this.referenceIndexMapping.Clear(); + + for (int i = 0; i < this.referencedUnityObjects.Count; i++) + { + if (object.ReferenceEquals(this.referencedUnityObjects[i], null) == false) + { + if (!this.referenceIndexMapping.ContainsKey(this.referencedUnityObjects[i])) + { + this.referenceIndexMapping.Add(this.referencedUnityObjects[i], i); + } + } + } + } + + /// <summary> + /// Determines whether the specified value can be referenced externally via this resolver. + /// </summary> + /// <param name="value">The value to reference.</param> + /// <param name="index">The index of the resolved value, if it can be referenced.</param> + /// <returns> + /// <c>true</c> if the reference can be resolved, otherwise <c>false</c>. + /// </returns> + public bool CanReference(object value, out int index) + { + if (this.referencedUnityObjects == null) + { + this.referencedUnityObjects = new List<UnityEngine.Object>(32); + } + + var obj = value as UnityEngine.Object; + + if (object.ReferenceEquals(null, obj) == false) + { + if (this.referenceIndexMapping.TryGetValue(obj, out index) == false) + { + index = this.referencedUnityObjects.Count; + this.referenceIndexMapping.Add(obj, index); + this.referencedUnityObjects.Add(obj); + } + + return true; + } + + index = -1; + return false; + } + + /// <summary> + /// Tries to resolve the given reference index to a reference value. + /// </summary> + /// <param name="index">The index to resolve.</param> + /// <param name="value">The resolved value.</param> + /// <returns> + /// <c>true</c> if the index could be resolved to a value, otherwise <c>false</c>. + /// </returns> + public bool TryResolveReference(int index, out object value) + { + if (this.referencedUnityObjects == null || index < 0 || index >= this.referencedUnityObjects.Count) + { + // Sometimes something has destroyed the list of references in between serialization and deserialization + // (Unity prefab instances are especially bad at preserving such data), and in these cases we still don't + // want the system to fall back to a formatter, so we give out a null value. + value = null; + return true; + } + + value = this.referencedUnityObjects[index]; + return true; + } + + /// <summary> + /// Resets this instance. + /// </summary> + public void Reset() + { + this.referencedUnityObjects = null; + this.referenceIndexMapping.Clear(); + } + + void ICacheNotificationReceiver.OnFreed() + { + this.Reset(); + } + + void ICacheNotificationReceiver.OnClaimed() + { + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnityReferenceResolver.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnityReferenceResolver.cs.meta new file mode 100644 index 00000000..2a0284bb --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnityReferenceResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de5584f66ccc5e072681a310c5987b8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationInitializer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationInitializer.cs new file mode 100644 index 00000000..74cf6983 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationInitializer.cs @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------- +// <copyright file="UnitySerializationInitializer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using UnityEngine; + + /// <summary> + /// Utility class which initializes the Sirenix serialization system to be compatible with Unity. + /// </summary> + public static class UnitySerializationInitializer + { + private static readonly object LOCK = new object(); + private static bool initialized = false; + + public static bool Initialized { get { return initialized; } } + + public static RuntimePlatform CurrentPlatform { get; private set; } + + /// <summary> + /// Initializes the Sirenix serialization system to be compatible with Unity. + /// </summary> + public static void Initialize() + { + if (!initialized) + { + lock (LOCK) + { + if (!initialized) + { + try + { + // Ensure that the config instance is loaded before deserialization of anything occurs. + // If we try to load it during deserialization, Unity will throw exceptions, as a lot of + // the Unity API is disallowed during serialization and deserialization. + GlobalSerializationConfig.LoadInstanceIfAssetExists(); + + CurrentPlatform = Application.platform; + + if (Application.isEditor) return; + + ArchitectureInfo.SetRuntimePlatform(CurrentPlatform); + + //if (CurrentPlatform == RuntimePlatform.Android) + //{ + // //using (var system = new AndroidJavaClass("java.lang.System")) + // //{ + // // string architecture = system.CallStatic<string>("getProperty", "os.arch"); + // // ArchitectureInfo.SetIsOnAndroid(architecture); + // //} + //} + //else if (CurrentPlatform == RuntimePlatform.IPhonePlayer) + //{ + // ArchitectureInfo.SetIsOnIPhone(); + //} + //else + //{ + // ArchitectureInfo.SetIsNotOnMobile(); + //} + } + finally + { + initialized = true; + } + } + } + } + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void InitializeRuntime() + { + Initialize(); + } + +#if UNITY_EDITOR + + [UnityEditor.InitializeOnLoadMethod] + private static void InitializeEditor() + { + Initialize(); + } +#endif + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationInitializer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationInitializer.cs.meta new file mode 100644 index 00000000..59e74aa5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationInitializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f670c1f9aa9ab0c5988e81802c005767 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationUtility.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationUtility.cs new file mode 100644 index 00000000..d7b77731 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationUtility.cs @@ -0,0 +1,2788 @@ +//----------------------------------------------------------------------- +// <copyright file="UnitySerializationUtility.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +//#define PREFAB_DEBUG + +namespace VRC.Udon.Serialization.OdinSerializer +{ + using System.Globalization; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Text; + using Utilities; + using UnityEngine; + using UnityEngine.Events; + using System.Runtime.CompilerServices; + using UnityEngine.Assertions; + using System.Runtime.Serialization; + +#pragma warning disable 0618 // Obsolete Methods // VRC + +#if PREFAB_DEBUG && !SIRENIX_INTERNAL +#warning "Prefab serialization debugging is enabled outside of Sirenix internal. Are you sure this is right?" +#endif + + /// <summary> + /// Provides an array of utility wrapper methods for easy serialization and deserialization of Unity objects of any type. + /// Note that, during serialization, it is always assumed that we are running on Unity's main thread. Deserialization can + /// happen on any thread, and all API's interacting with deserialization are thread-safe. + /// <para /> + /// Note that setting the IndexReferenceResolver on contexts passed into methods on this class will have no effect, as it will always + /// be set to a UnityReferenceResolver. + /// </summary> + public static class UnitySerializationUtility + { + public static readonly Type SerializeReferenceAttributeType = typeof(SerializeField).Assembly.GetType("UnityEngine.SerializeReference"); + + private static readonly Assembly String_Assembly = typeof(string).Assembly; + private static readonly Assembly HashSet_Assembly = typeof(HashSet<>).Assembly; + private static readonly Assembly LinkedList_Assembly = typeof(LinkedList<>).Assembly; + +#if UNITY_EDITOR + /// <summary> + /// From the new scriptable build pipeline package + /// </summary> + [NonSerialized] + private static readonly Type SBP_ContentPipelineType = TwoWaySerializationBinder.Default.BindToType("UnityEditor.Build.Pipeline.ContentPipeline"); + + [NonSerialized] + private static readonly MethodInfo PrefabUtility_IsComponentAddedToPrefabInstance_MethodInfo = typeof(UnityEditor.PrefabUtility).GetMethod("IsComponentAddedToPrefabInstance"); + + [NonSerialized] + private static readonly HashSet<UnityEngine.Object> UnityObjectsWaitingForDelayedModificationApply = new HashSet<UnityEngine.Object>(ReferenceEqualityComparer<UnityEngine.Object>.Default); + + [NonSerialized] + private static readonly Dictionary<UnityEngine.Object, List<PrefabModification>> RegisteredPrefabModifications = new Dictionary<UnityEngine.Object, List<PrefabModification>>(ReferenceEqualityComparer<UnityEngine.Object>.Default); + + private static class PrefabDeserializeUtility + { + private static int updateCount = 0; + + [NonSerialized] + public static readonly HashSet<UnityEngine.Object> PrefabsWithValuesApplied = new HashSet<UnityEngine.Object>(ReferenceEqualityComparer<UnityEngine.Object>.Default); + + [NonSerialized] + private static readonly Dictionary<UnityEngine.Object, HashSet<object>> SceneObjectsToKeepOnApply = new Dictionary<UnityEngine.Object, HashSet<object>>(ReferenceEqualityComparer<UnityEngine.Object>.Default); + + [NonSerialized] + public static readonly object DeserializePrefabs_LOCK = new object(); + + private static readonly List<UnityEngine.Object> toRemove = new List<UnityEngine.Object>(); + + static PrefabDeserializeUtility() + { + UnityEditor.EditorApplication.update += OnEditorUpdate; + } + + /// <summary> + /// Note: it is assumed that code calling this is holding the DeserializePrefabCaches_LOCK lock, and will continue to hold it while the returned hashset is being modified + /// </summary> + public static HashSet<object> GetSceneObjectsToKeepSet(UnityEngine.Object unityObject, bool createIfDoesntExist) + { + HashSet<object> keep; + + if (!SceneObjectsToKeepOnApply.TryGetValue(unityObject, out keep)) + { + keep = new HashSet<object>(ReferenceEqualityComparer<object>.Default); + SceneObjectsToKeepOnApply.Add(unityObject, keep); + } + + return keep; + } + + public static void CleanSceneObjectToKeepOnApply() + { + lock (DeserializePrefabs_LOCK) + { + foreach (var obj in SceneObjectsToKeepOnApply.Keys) + { + if (obj == null) + { + toRemove.Add(obj); + } + } + + for (int i = 0; i < toRemove.Count; i++) + { + SceneObjectsToKeepOnApply.Remove(toRemove[i]); + } + + toRemove.Clear(); + } + } + + private static void OnEditorUpdate() + { + lock (DeserializePrefabs_LOCK) + { + updateCount++; + + if (updateCount >= 1000) + { + SceneObjectsToKeepOnApply.Clear(); + updateCount = 0; + } + } + } + } + +#endif + + // Note: Code that accesses any of these four caches should lock on the cache data structure itself + private static readonly Dictionary<MemberInfo, WeakValueGetter> UnityMemberGetters = new Dictionary<MemberInfo, WeakValueGetter>(); + private static readonly Dictionary<MemberInfo, WeakValueSetter> UnityMemberSetters = new Dictionary<MemberInfo, WeakValueSetter>(); + + private static readonly Dictionary<MemberInfo, bool> UnityWillSerializeMembersCache = new Dictionary<MemberInfo, bool>(); + private static readonly Dictionary<Type, bool> UnityWillSerializeTypesCache = new Dictionary<Type, bool>(); + + private static readonly HashSet<Type> UnityNeverSerializesTypes = new HashSet<Type>() + { + typeof(Coroutine) + }; + + private static readonly HashSet<string> UnityNeverSerializesTypeNames = new HashSet<string>() + { + "UnityEngine.AnimationState" + }; + +#if UNITY_EDITOR + + /// <summary> + /// Whether to always force editor mode serialization. This member only exists in the editor. + /// </summary> + public static bool ForceEditorModeSerialization { get; set; } + + /// <summary> + /// Not yet documented. + /// </summary> + public static List<PrefabModification> GetRegisteredPrefabModifications(UnityEngine.Object obj) + { + List<PrefabModification> result; + RegisteredPrefabModifications.TryGetValue(obj, out result); + return result; + } + + public static bool HasModificationsWaitingForDelayedApply(UnityEngine.Object obj) + { + return UnityObjectsWaitingForDelayedModificationApply.Contains(obj); + } + +#endif + + /// <summary> + /// Checks whether Odin will serialize a given member. + /// </summary> + /// <param name="member">The member to check.</param> + /// <param name="serializeUnityFields">Whether to allow serialization of members that will also be serialized by Unity.</param> + /// <param name="policy">The policy that Odin should be using for serialization of the given member. If this parameter is null, it defaults to <see cref="SerializationPolicies.Unity"/>.</param> + /// <returns>True if Odin will serialize the member, otherwise false.</returns> + public static bool OdinWillSerialize(MemberInfo member, bool serializeUnityFields, ISerializationPolicy policy = null) + { + Dictionary<MemberInfo, CachedSerializationBackendResult> cacheForPolicy; + + if (policy == null || object.ReferenceEquals(policy, UnityPolicy)) + { + cacheForPolicy = OdinWillSerializeCache_UnityPolicy; + } + else if (object.ReferenceEquals(policy, EverythingPolicy)) + { + cacheForPolicy = OdinWillSerializeCache_EverythingPolicy; + } + else if (object.ReferenceEquals(policy, StrictPolicy)) + { + cacheForPolicy = OdinWillSerializeCache_StrictPolicy; + } + else + { + lock (OdinWillSerializeCache_CustomPolicies) + { + if (!OdinWillSerializeCache_CustomPolicies.TryGetValue(policy, out cacheForPolicy)) + { + cacheForPolicy = new Dictionary<MemberInfo, CachedSerializationBackendResult>(ReferenceEqualityComparer<MemberInfo>.Default); + OdinWillSerializeCache_CustomPolicies.Add(policy, cacheForPolicy); + } + } + } + + CachedSerializationBackendResult result; + + lock (cacheForPolicy) + { + if (!cacheForPolicy.TryGetValue(member, out result)) + { + result = default(CachedSerializationBackendResult); + + if (serializeUnityFields) + { + result.SerializeUnityFieldsTrueResult = CalculateOdinWillSerialize(member, serializeUnityFields, policy ?? UnityPolicy); + result.HasCalculatedSerializeUnityFieldsTrueResult = true; + } + else + { + result.SerializeUnityFieldsFalseResult = CalculateOdinWillSerialize(member, serializeUnityFields, policy ?? UnityPolicy); + result.HasCalculatedSerializeUnityFieldsFalseResult = true; + } + + cacheForPolicy.Add(member, result); + } + else + { + if (serializeUnityFields && !result.HasCalculatedSerializeUnityFieldsTrueResult) + { + result.SerializeUnityFieldsTrueResult = CalculateOdinWillSerialize(member, serializeUnityFields, policy ?? UnityPolicy); + result.HasCalculatedSerializeUnityFieldsTrueResult = true; + + cacheForPolicy[member] = result; + } + else if (!serializeUnityFields && !result.HasCalculatedSerializeUnityFieldsFalseResult) + { + result.SerializeUnityFieldsFalseResult = CalculateOdinWillSerialize(member, serializeUnityFields, policy ?? UnityPolicy); + result.HasCalculatedSerializeUnityFieldsFalseResult = true; + + cacheForPolicy[member] = result; + } + } + + return serializeUnityFields ? result.SerializeUnityFieldsTrueResult : result.SerializeUnityFieldsFalseResult; + } + } + + private static bool CalculateOdinWillSerialize(MemberInfo member, bool serializeUnityFields, ISerializationPolicy policy) + { + if (member.DeclaringType == typeof(UnityEngine.Object)) return false; + if (!policy.ShouldSerializeMember(member)) return false; + + // Allow serialization of fields with [OdinSerialize], regardless of whether Unity + // serializes the field or not + if (member is FieldInfo && member.IsDefined(typeof(OdinSerializeAttribute), true)) + { + return true; + } + + // No need to check whether Unity serializes it or not, our answer will always be the same + if (serializeUnityFields) return true; + + try + { + if (SerializeReferenceAttributeType != null && member.IsDefined(SerializeReferenceAttributeType, true)) + { + // Unity is serializing it as a polymorphic value + return false; + } + } + catch { } + + if (GuessIfUnityWillSerialize(member)) return false; + + return true; + } + + private struct CachedSerializationBackendResult + { + public bool HasCalculatedSerializeUnityFieldsTrueResult; + public bool HasCalculatedSerializeUnityFieldsFalseResult; + + public bool SerializeUnityFieldsTrueResult; + public bool SerializeUnityFieldsFalseResult; + } + + private static readonly ISerializationPolicy UnityPolicy = SerializationPolicies.Unity; + private static readonly ISerializationPolicy EverythingPolicy = SerializationPolicies.Everything; + private static readonly ISerializationPolicy StrictPolicy = SerializationPolicies.Strict; + private static readonly Dictionary<MemberInfo, CachedSerializationBackendResult> OdinWillSerializeCache_UnityPolicy = new Dictionary<MemberInfo, CachedSerializationBackendResult>(ReferenceEqualityComparer<MemberInfo>.Default); + private static readonly Dictionary<MemberInfo, CachedSerializationBackendResult> OdinWillSerializeCache_EverythingPolicy = new Dictionary<MemberInfo, CachedSerializationBackendResult>(ReferenceEqualityComparer<MemberInfo>.Default); + private static readonly Dictionary<MemberInfo, CachedSerializationBackendResult> OdinWillSerializeCache_StrictPolicy = new Dictionary<MemberInfo, CachedSerializationBackendResult>(ReferenceEqualityComparer<MemberInfo>.Default); + private static readonly Dictionary<ISerializationPolicy, Dictionary<MemberInfo, CachedSerializationBackendResult>> OdinWillSerializeCache_CustomPolicies = new Dictionary<ISerializationPolicy, Dictionary<MemberInfo, CachedSerializationBackendResult>>(ReferenceEqualityComparer<ISerializationPolicy>.Default); + + /// <summary> + /// Guesses whether or not Unity will serialize a given member. This is not completely accurate. + /// </summary> + /// <param name="member">The member to check.</param> + /// <returns>True if it is guessed that Unity will serialize the member, otherwise false.</returns> + /// <exception cref="System.ArgumentNullException">The parameter <paramref name="member"/> is null.</exception> + public static bool GuessIfUnityWillSerialize(MemberInfo member) + { + if (member == null) + { + throw new ArgumentNullException("member"); + } + + bool result; + + lock (UnityWillSerializeMembersCache) + { + if (!UnityWillSerializeMembersCache.TryGetValue(member, out result)) + { + result = GuessIfUnityWillSerializePrivate(member); + UnityWillSerializeMembersCache[member] = result; + } + } + + return result; + } + + private static bool GuessIfUnityWillSerializePrivate(MemberInfo member) + { + FieldInfo fieldInfo = member as FieldInfo; + + if (fieldInfo == null || fieldInfo.IsStatic || fieldInfo.IsInitOnly) + { + return false; + } + + if (fieldInfo.IsDefined<NonSerializedAttribute>()) + { + return false; + } + + if (SerializeReferenceAttributeType != null && fieldInfo.IsDefined(SerializeReferenceAttributeType, true)) + { + return true; + } + + if (!typeof(UnityEngine.Object).IsAssignableFrom(fieldInfo.FieldType) && fieldInfo.FieldType == fieldInfo.DeclaringType) + { + // Unity will not serialize references that are obviously cyclical + return false; + } + + if (!(fieldInfo.IsPublic || fieldInfo.IsDefined<SerializeField>())) + { + return false; + } + + if (fieldInfo.IsDefined<FixedBufferAttribute>()) + { + return UnityVersion.IsVersionOrGreater(2017, 1); + } + + return GuessIfUnityWillSerialize(fieldInfo.FieldType); + } + + /// <summary> + /// Guesses whether or not Unity will serialize a given type. This is not completely accurate. + /// </summary> + /// <param name="type">The type to check.</param> + /// <returns>True if it is guessed that Unity will serialize the type, otherwise false.</returns> + /// <exception cref="System.ArgumentNullException">The parameter <paramref name="type"/> is null.</exception> + public static bool GuessIfUnityWillSerialize(Type type) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + bool result; + + lock (UnityWillSerializeTypesCache) + { + if (!UnityWillSerializeTypesCache.TryGetValue(type, out result)) + { + result = GuessIfUnityWillSerializePrivate(type); + UnityWillSerializeTypesCache[type] = result; + } + } + + return result; + } + + private static bool GuessIfUnityWillSerializePrivate(Type type) + { + if (UnityNeverSerializesTypes.Contains(type) || UnityNeverSerializesTypeNames.Contains(type.FullName)) + { + return false; + } + + if (typeof(UnityEngine.Object).IsAssignableFrom(type)) + { + if (type.IsGenericType) + { + return UnityVersion.IsVersionOrGreater(2020, 1); + } + + // Unity will always serialize all of its own special objects + // Except when they have generic type arguments. + return true; + } + + if (type.IsAbstract || type.IsInterface || type == typeof(object)) + { + return false; + } + + if (type.IsEnum) + { + var underlyingType = Enum.GetUnderlyingType(type); + + if (UnityVersion.IsVersionOrGreater(5, 6)) + { + return underlyingType != typeof(long) && underlyingType != typeof(ulong); + } + else + { + return underlyingType == typeof(int) || underlyingType == typeof(byte); + } + } + + if (type.IsPrimitive || type == typeof(string)) + { + return true; + } + + if (typeof(Delegate).IsAssignableFrom(type)) + { + return false; + } + + if (typeof(UnityEventBase).IsAssignableFrom(type)) + { + if (type.IsGenericType && !UnityVersion.IsVersionOrGreater(2020, 1)) + { + return false; + } + + return (type == typeof(UnityEvent) || type.IsDefined<SerializableAttribute>(false)); + } + + if (type.IsArray) + { + // Unity does not support multidim arrays, or arrays of lists or arrays. + var elementType = type.GetElementType(); + + return type.GetArrayRank() == 1 + && !elementType.IsArray + && !elementType.ImplementsOpenGenericClass(typeof(List<>)) + && GuessIfUnityWillSerialize(elementType); + } + + if (type.IsGenericType && !type.IsGenericTypeDefinition && type.GetGenericTypeDefinition() == typeof(List<>)) + { + // Unity does not support lists or arrays in lists. + var elementType = type.GetArgumentsOfInheritedOpenGenericClass(typeof(List<>))[0]; + if (elementType.IsArray || elementType.ImplementsOpenGenericClass(typeof(List<>))) + { + return false; + } + return GuessIfUnityWillSerialize(elementType); + } + + if (type.Assembly.FullName.StartsWith("UnityEngine", StringComparison.InvariantCulture) || type.Assembly.FullName.StartsWith("UnityEditor", StringComparison.InvariantCulture)) + { + // We assume Unity will serialize all of their own structs and classes (many of them are not marked serializable). + // If not, well, too bad - the user can use the [OdinSerialize] attribute on their field/property in that case to trigger custom serialization. + return true; + } + + // Unity 2020.1 added support for serializing arbitrary generic types directly + if (type.IsGenericType && !UnityVersion.IsVersionOrGreater(2020, 1)) + { + return false; + } + + // Unity does not serialize [Serializable] structs and classes if they are defined in mscorlib, System.dll or System.Core.dll if those are present + // Checking against the assemblies that declare System.String, HashSet<T> and LinkedList<T> is a simple way to do this. + if (type.Assembly == String_Assembly || type.Assembly == HashSet_Assembly || type.Assembly == LinkedList_Assembly) + { + return false; + } + + if (type.IsDefined<SerializableAttribute>(false)) + { + // Before Unity 4.5, Unity did not support serialization of custom structs, only custom classes + if (UnityVersion.IsVersionOrGreater(4, 5)) + { + return true; + } + else + { + return type.IsClass; + } + } + + // Check for synclists if legacy networking is present + // it was removed in 2018.2 + if (!UnityVersion.IsVersionOrGreater(2018, 2)) + { + Type current = type.BaseType; + + while (current != null && current != typeof(object)) + { + if (current.IsGenericType && current.GetGenericTypeDefinition().FullName == "UnityEngine.Networking.SyncListStruct`1") + { + return true; + } + + current = current.BaseType; + }; + } + + return false; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static void SerializeUnityObject(UnityEngine.Object unityObject, ref SerializationData data, bool serializeUnityFields = false, SerializationContext context = null) + { + if (unityObject == null) + { + throw new ArgumentNullException("unityObject"); + } + +#if UNITY_EDITOR + + if (OdinPrefabSerializationEditorUtility.HasNewPrefabWorkflow) + { + ISupportsPrefabSerialization supporter = unityObject as ISupportsPrefabSerialization; + + if (supporter != null) + { + var sData = supporter.SerializationData; + + //if (!sData.ContainsData) + //{ + // return; + //} + + sData.Prefab = null; + supporter.SerializationData = sData; + } + } + + { + bool pretendIsPlayer = Application.isPlaying && !UnityEditor.AssetDatabase.Contains(unityObject); + + // + // Look through a stack trace to determine some things about the current serialization context. + // For example, we check if we are currently building a player, or if we are currently recording prefab instance property modifications. + // This is pretty hacky, but as far as we can tell it's the only way to do it. + // + + { + var stackFrames = new System.Diagnostics.StackTrace().GetFrames(); + Type buildPipelineType = typeof(UnityEditor.BuildPipeline); + Type prefabUtilityType = typeof(UnityEditor.PrefabUtility); + + + for (int i = 0; i < stackFrames.Length; i++) + { + var frame = stackFrames[i]; + var method = frame.GetMethod(); + + if (method.DeclaringType == buildPipelineType || method.DeclaringType == SBP_ContentPipelineType) + { + // We are currently building a player + pretendIsPlayer = true; + break; + } + + if (method.DeclaringType == prefabUtilityType && method.Name == "RecordPrefabInstancePropertyModifications") + { + // Do nothing whatsoever and return immediately, lest we break Unity's "smart" modification recording + return; + } + } + } + + if (ForceEditorModeSerialization) + { + pretendIsPlayer = false; + } + + // + // Prefab handling + // + // If we're not building a player and the Unity object is a prefab instance + // that supports special prefab serialization, we enter a special bail-out case. + // + + if (!pretendIsPlayer) + { + UnityEngine.Object prefab = null; + SerializationData prefabData = default(SerializationData); + + bool prefabDataIsFromSelf = false; + + if (OdinPrefabSerializationEditorUtility.ObjectIsPrefabInstance(unityObject)) + { + prefab = OdinPrefabSerializationEditorUtility.GetCorrespondingObjectFromSource(unityObject); + + if (prefab.SafeIsUnityNull() && !object.ReferenceEquals(data.Prefab, null)) + { + // Sometimes, GetPrefabParent does not return the prefab, + // because Unity is just completely unreliable. + // + // In these cases, we sometimes have a reference to the + // prefab in the data. If so, we can use that instead. + // + // Even though that reference is "fake null". + + prefab = data.Prefab; + } + + if (!object.ReferenceEquals(prefab, null)) + { + if (prefab is ISupportsPrefabSerialization) + { + var pData = (prefab as ISupportsPrefabSerialization).SerializationData; + + if (pData.ContainsData) + { + prefabData = pData; + } + else + { + prefabData = data; + prefabData.Prefab = null; + prefabDataIsFromSelf = true; + } + } + else if (prefab.GetType() != typeof(UnityEngine.Object)) + { + //Debug.LogWarning(unityObject.name + " is a prefab instance, but the prefab reference type " + prefab.GetType().GetNiceName() + " does not implement the interface " + typeof(ISupportsPrefabSerialization).GetNiceName() + "; non-Unity-serialized data will most likely not be updated properly from the prefab any more."); + //prefab = null; + + prefabData = data; + prefabData.Prefab = null; + prefabDataIsFromSelf = true; + } + } + } + + if (!object.ReferenceEquals(prefab, null)) + { + // We will bail out. But first... + + if (!prefabDataIsFromSelf && prefabData.PrefabModifications != null && prefabData.PrefabModifications.Count > 0) + { + // + // This is a special case that can happen after changes to a prefab instance + // have been applied to the source prefab using "Apply Changes", thus copying + // the instances' applied changes over to the source prefab. + // + // We re-serialize the prefab, to make sure its data is properly saved. + // Though data saved this way will still work, it is quite inefficient. + // + + try + { + (prefab as ISerializationCallbackReceiver).OnBeforeSerialize(); + } + catch (Exception ex) + { + // This can sometimes throw null reference exceptions in the new prefab workflow, + // if people are doing nested stuff despite the fac that they really, really shouldn't. + // + // Just ignore it. + if (!OdinPrefabSerializationEditorUtility.HasNewPrefabWorkflow) + { + throw ex; + } + } + + EditorApplication_delayCall_Alias += () => + { + if (prefab) + { + UnityEditor.EditorUtility.SetDirty(prefab); + //UnityEditor.AssetDatabase.SaveAssets(); // Has a tendency to cause infinite serialization loops + } + }; + + prefabData = (prefab as ISupportsPrefabSerialization).SerializationData; + } + + // Now we determine the modifications string to keep + + bool newModifications = false; + List<string> modificationsToKeep; + List<PrefabModification> modificationsList; + List<UnityEngine.Object> modificationsReferencedUnityObjects = data.PrefabModificationsReferencedUnityObjects; + + if (RegisteredPrefabModifications.TryGetValue(unityObject, out modificationsList)) + { + RegisteredPrefabModifications.Remove(unityObject); + + // We have to generate a new prefab modification string from the registered changes + modificationsToKeep = SerializePrefabModifications(modificationsList, ref modificationsReferencedUnityObjects); + +#if PREFAB_DEBUG + Debug.Log("Setting new modifications: ", unityObject); + + foreach (var mod in modificationsToKeep) + { + Debug.Log(" " + mod); + } +#endif + + newModifications = true; + } + else + { + // Keep the old ones + modificationsToKeep = data.PrefabModifications; + } + + // Make sure we have the same base data as the prefab (except UnityObject references), then change the rest + var unityObjects = data.ReferencedUnityObjects; + + data = prefabData; + data.ReferencedUnityObjects = unityObjects; + + //if (unityObjects.Count == prefabData.ReferencedUnityObjects.Count) + //{ + //} + //else + //{ + // var stackTrace = new System.Diagnostics.StackTrace(); + + // //DefaultLoggers.DefaultLogger.LogError( + // // "Prefab instance serialization error on object'" + unityObject.name + "': Unity object reference count mismatch " + + // // "between prefab and prefab instance in the core umodified data! This should never, ever happen! Prefab instance " + + // // "references have been replaced with the prefab's references! Unity object references may have been changed from " + + // // "expected values! Please report this error and how to reproduce it at 'http://bitbucket.org/sirenix/odin-inspector/issues'."); + //} + + data.Prefab = prefab; + data.PrefabModifications = modificationsToKeep; + data.PrefabModificationsReferencedUnityObjects = modificationsReferencedUnityObjects; + + if (newModifications) + { + SetUnityObjectModifications(unityObject, ref data, prefab); + } + + // Now we determine the Unity object references to keep if this prefab instance is ever applied + if (data.Prefab != null) // It can still be "fake null", in which case, never mind + { + PrefabDeserializeUtility.CleanSceneObjectToKeepOnApply(); + + lock (PrefabDeserializeUtility.DeserializePrefabs_LOCK) + { + HashSet<object> keep = PrefabDeserializeUtility.GetSceneObjectsToKeepSet(unityObject, true); + keep.Clear(); + + if (data.PrefabModificationsReferencedUnityObjects != null && data.PrefabModificationsReferencedUnityObjects.Count > 0) + { + //var prefabRoot = UnityEditor.PrefabUtility.FindPrefabRoot(((Component)data.Prefab).gameObject); + var instanceRoot = UnityEditor.PrefabUtility.FindPrefabRoot(((Component)unityObject).gameObject); + + foreach (var reference in data.PrefabModificationsReferencedUnityObjects) + { + if (reference == null) continue; + if (!(reference is GameObject || reference is Component)) continue; + if (UnityEditor.AssetDatabase.Contains(reference)) continue; + + var referencePrefabType = UnityEditor.PrefabUtility.GetPrefabType(reference); + + bool mightBeInPrefab = referencePrefabType == UnityEditor.PrefabType.Prefab + || referencePrefabType == UnityEditor.PrefabType.PrefabInstance + || referencePrefabType == UnityEditor.PrefabType.ModelPrefab + || referencePrefabType == UnityEditor.PrefabType.ModelPrefabInstance; + + if (!mightBeInPrefab) + { + if (PrefabUtility_IsComponentAddedToPrefabInstance_MethodInfo != null) + { + if (reference is Component && (bool)PrefabUtility_IsComponentAddedToPrefabInstance_MethodInfo.Invoke(null, new object[] { reference })) + { + mightBeInPrefab = true; + } + } + } + + if (!mightBeInPrefab) + { + keep.Add(reference); + continue; + } + + var gameObject = (GameObject)(reference is GameObject ? reference : (reference as Component).gameObject); + var referenceRoot = UnityEditor.PrefabUtility.FindPrefabRoot(gameObject); + + if (referenceRoot != instanceRoot) + { + keep.Add(reference); + } + } + } + } + } + + return; // Buh bye + } + } + + // + // We are not dealing with a properly supported prefab instance if we get this far. + // Serialize as if it isn't a prefab instance. + // + + // Ensure there is no superfluous data left over after serialization + // (We will reassign all necessary data.) + data.Reset(); + + DataFormat format; + + // Get the format to serialize as + { + IOverridesSerializationFormat formatOverride = unityObject as IOverridesSerializationFormat; + + if (formatOverride != null) + { + format = formatOverride.GetFormatToSerializeAs(pretendIsPlayer); + } + else if (GlobalSerializationConfig.HasInstanceLoaded) + { + if (pretendIsPlayer) + { + format = GlobalSerializationConfig.Instance.BuildSerializationFormat; + } + else + { + format = GlobalSerializationConfig.Instance.EditorSerializationFormat; + } + } + else if (pretendIsPlayer) + { + format = DataFormat.Binary; + } + else + { + format = DataFormat.Nodes; + } + } + + ISerializationPolicy serializationPolicy = SerializationPolicies.Unity; + + // Get the policy to serialize with + { + IOverridesSerializationPolicy policyOverride = unityObject as IOverridesSerializationPolicy; + + if (policyOverride != null) + { + serializationPolicy = policyOverride.SerializationPolicy ?? SerializationPolicies.Unity; + + if (context != null) + { + context.Config.SerializationPolicy = serializationPolicy; + } + + serializeUnityFields = policyOverride.OdinSerializesUnityFields; + } + + } + + if (pretendIsPlayer) + { + // We pretend as though we're serializing outside of the editor + if (format == DataFormat.Nodes) + { + Debug.LogWarning("The serialization format '" + format.ToString() + "' is disabled in play mode, and when building a player. Defaulting to the format '" + DataFormat.Binary.ToString() + "' instead."); + format = DataFormat.Binary; + } + + UnitySerializationUtility.SerializeUnityObject(unityObject, ref data.SerializedBytes, ref data.ReferencedUnityObjects, format, serializeUnityFields, context); + data.SerializedFormat = format; + } + else + { + if (format == DataFormat.Nodes) + { + // Special case for node format + if (context == null) + { + using (var newContext = Cache<SerializationContext>.Claim()) + using (var writer = new SerializationNodeDataWriter(newContext)) + using (var resolver = Cache<UnityReferenceResolver>.Claim()) + { + if (data.SerializationNodes != null) + { + // Reuse pre-expanded list to keep GC down + data.SerializationNodes.Clear(); + writer.Nodes = data.SerializationNodes; + } + + resolver.Value.SetReferencedUnityObjects(data.ReferencedUnityObjects); + + newContext.Value.Config.SerializationPolicy = serializationPolicy; + newContext.Value.IndexReferenceResolver = resolver.Value; + + writer.Context = newContext; + + UnitySerializationUtility.SerializeUnityObject(unityObject, writer, serializeUnityFields); + data.SerializationNodes = writer.Nodes; + data.ReferencedUnityObjects = resolver.Value.GetReferencedUnityObjects(); + } + } + else + { + using (var writer = new SerializationNodeDataWriter(context)) + using (var resolver = Cache<UnityReferenceResolver>.Claim()) + { + if (data.SerializationNodes != null) + { + // Reuse pre-expanded list to keep GC down + data.SerializationNodes.Clear(); + writer.Nodes = data.SerializationNodes; + } + + resolver.Value.SetReferencedUnityObjects(data.ReferencedUnityObjects); + context.IndexReferenceResolver = resolver.Value; + + UnitySerializationUtility.SerializeUnityObject(unityObject, writer, serializeUnityFields); + data.SerializationNodes = writer.Nodes; + data.ReferencedUnityObjects = resolver.Value.GetReferencedUnityObjects(); + } + } + } + else + { + UnitySerializationUtility.SerializeUnityObject(unityObject, ref data.SerializedBytesString, ref data.ReferencedUnityObjects, format, serializeUnityFields, context); + } + + data.SerializedFormat = format; + } + } +#else + { + DataFormat format; + IOverridesSerializationFormat formatOverride = unityObject as IOverridesSerializationFormat; + + if (formatOverride != null) + { + format = formatOverride.GetFormatToSerializeAs(true); + } + else if (GlobalSerializationConfig.HasInstanceLoaded) + { + format = GlobalSerializationConfig.Instance.BuildSerializationFormat; + } + else + { + format = DataFormat.Binary; + } + + if (format == DataFormat.Nodes) + { + Debug.LogWarning("The serialization format '" + format.ToString() + "' is disabled outside of the editor. Defaulting to the format '" + DataFormat.Binary.ToString() + "' instead."); + format = DataFormat.Binary; + } + + UnitySerializationUtility.SerializeUnityObject(unityObject, ref data.SerializedBytes, ref data.ReferencedUnityObjects, format); + data.SerializedFormat = format; + } +#endif + } + +#if UNITY_EDITOR + + private static void SetUnityObjectModifications(UnityEngine.Object unityObject, ref SerializationData data, UnityEngine.Object prefab) + { + // + // We need to set the modifications to the prefab instance manually, + // to ensure that Unity gets it right and doesn't mess with them. + // + + Type unityObjectType = unityObject.GetType(); + var serializedDataField = unityObjectType.GetAllMembers<FieldInfo>(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(field => field.FieldType == typeof(SerializationData) && UnitySerializationUtility.GuessIfUnityWillSerialize(field)) + .LastOrDefault(); + + if (serializedDataField == null) + { + Debug.LogError("Could not find a field of type " + typeof(SerializationData).Name + " on the serializing type " + unityObjectType.GetNiceName() + " when trying to manually set prefab modifications. It is possible that prefab instances of this type will be corrupted if changes are ever applied to prefab.", prefab); + } + else + { + string serializedDataPath = serializedDataField.Name + "."; + string referencedUnityObjectsPath = serializedDataPath + SerializationData.PrefabModificationsReferencedUnityObjectsFieldName + ".Array."; + string modificationsPath = serializedDataPath + SerializationData.PrefabModificationsFieldName + ".Array."; + string prefabPath = serializedDataPath + SerializationData.PrefabFieldName; + + var mods = UnityEditor.PrefabUtility.GetPropertyModifications(unityObject).ToList(); + + // + // Clear all old modifications to serialized data out + // + + for (int i = 0; i < mods.Count; i++) + { + var mod = mods[i]; + + if (mod.propertyPath.StartsWith(serializedDataPath, StringComparison.InvariantCulture) && object.ReferenceEquals(mod.target, prefab)) + { + mods.RemoveAt(i); + i--; + } + } + + // + // Add the new modifications + // + + // Array length changes seem to always come first? Let's do that to be sure... + mods.Insert(0, new UnityEditor.PropertyModification() + { + target = prefab, + propertyPath = referencedUnityObjectsPath + "size", + value = data.PrefabModificationsReferencedUnityObjects.Count.ToString("D", CultureInfo.InvariantCulture) + }); + + mods.Insert(0, new UnityEditor.PropertyModification() + { + target = prefab, + propertyPath = modificationsPath + "size", + value = data.PrefabModifications.Count.ToString("D", CultureInfo.InvariantCulture) + }); + + // Then the prefab object reference + mods.Add(new UnityEditor.PropertyModification() + { + target = prefab, + propertyPath = prefabPath, + objectReference = prefab + }); + + // Then the actual array values + for (int i = 0; i < data.PrefabModificationsReferencedUnityObjects.Count; i++) + { + mods.Add(new UnityEditor.PropertyModification() + { + target = prefab, + propertyPath = referencedUnityObjectsPath + "data[" + i.ToString("D", CultureInfo.InvariantCulture) + "]", + objectReference = data.PrefabModificationsReferencedUnityObjects[i] + }); + } + + for (int i = 0; i < data.PrefabModifications.Count; i++) + { + mods.Add(new UnityEditor.PropertyModification() + { + target = prefab, + propertyPath = modificationsPath + "data[" + i.ToString("D", CultureInfo.InvariantCulture) + "]", + value = data.PrefabModifications[i] + }); + } + + // Set the Unity property modifications + + // This won't always stick; there is code in the PropertyTree class + // that keeps checking if the number of custom modifications is correct + // and, if not, it keeps registering the change until Unity gets it. + + // Setting the prefab modifications here directly has a tendency to crash the Unity Editor, so we use a delayed call + // so the modifications are set during a time that's better for Unity. + EditorApplication_delayCall_Alias += () => + { +#if PREFAB_DEBUG + Debug.Log("DELAYED: Actually setting prefab modifications:"); + foreach (var mod in mods) + { + if (!mod.propertyPath.StartsWith("serializationData")) continue; + + int index = -1; + + if (mod.target is Component) + { + Component com = mod.target as Component; + + var coms = com.gameObject.GetComponents(com.GetType()); + + for (int j = 0; j < coms.Length; j++) + { + if (object.ReferenceEquals(coms[j], mod.target)) + { + index = j; + break; + } + } + } + + Debug.Log(" " + mod.target.name + " (" + index + ") " + mod.propertyPath + ": " + mod.value); + } +#endif + + UnityObjectsWaitingForDelayedModificationApply.Remove(unityObject); + UnityEditor.PrefabUtility.SetPropertyModifications(unityObject, mods.ToArray()); + }; + + UnityObjectsWaitingForDelayedModificationApply.Add(unityObject); + } + } + +#endif + + /// <summary> + /// Not yet documented. + /// </summary> + public static void SerializeUnityObject(UnityEngine.Object unityObject, ref string base64Bytes, ref List<UnityEngine.Object> referencedUnityObjects, DataFormat format, bool serializeUnityFields = false, SerializationContext context = null) + { + byte[] bytes = null; + SerializeUnityObject(unityObject, ref bytes, ref referencedUnityObjects, format, serializeUnityFields, context); + base64Bytes = Convert.ToBase64String(bytes); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static void SerializeUnityObject(UnityEngine.Object unityObject, ref byte[] bytes, ref List<UnityEngine.Object> referencedUnityObjects, DataFormat format, bool serializeUnityFields = false, SerializationContext context = null) + { + if (unityObject == null) + { + throw new ArgumentNullException("unityObject"); + } + + if (format == DataFormat.Nodes) + { + Debug.LogError("The serialization data format '" + format.ToString() + "' is not supported by this method. You must create your own writer."); + return; + } + + if (referencedUnityObjects == null) + { + referencedUnityObjects = new List<UnityEngine.Object>(); + } + else + { + referencedUnityObjects.Clear(); + } + + using (var stream = Cache<CachedMemoryStream>.Claim()) + using (var resolver = Cache<UnityReferenceResolver>.Claim()) + { + resolver.Value.SetReferencedUnityObjects(referencedUnityObjects); + + if (context != null) + { + context.IndexReferenceResolver = resolver.Value; + using (var writerCache = GetCachedUnityWriter(format, stream.Value.MemoryStream, context)) + { + SerializeUnityObject(unityObject, writerCache.Value as IDataWriter, serializeUnityFields); + } + } + else + { + using (var con = Cache<SerializationContext>.Claim()) + { + con.Value.Config.SerializationPolicy = SerializationPolicies.Unity; + + /* If the config instance is not loaded (it should usually be, but in rare cases + * it's not), we must not ask for it, as we are not allowed to load from resources + * or the asset database during some serialization callbacks. + * + * (Trying to do that causes internal Unity errors and potentially even crashes.) + * + * If it's not loaded, we fall back to default values, since there's no other choice. + */ + if (GlobalSerializationConfig.HasInstanceLoaded) + { + //Debug.Log("Serializing " + unityObject.GetType().Name + " WITH loaded!"); + con.Value.Config.DebugContext.ErrorHandlingPolicy = GlobalSerializationConfig.Instance.ErrorHandlingPolicy; + con.Value.Config.DebugContext.LoggingPolicy = GlobalSerializationConfig.Instance.LoggingPolicy; + con.Value.Config.DebugContext.Logger = GlobalSerializationConfig.Instance.Logger; + } + else + { + //Debug.Log("Serializing " + unityObject.GetType().Name + " WITHOUT loaded!"); + con.Value.Config.DebugContext.ErrorHandlingPolicy = ErrorHandlingPolicy.Resilient; + con.Value.Config.DebugContext.LoggingPolicy = LoggingPolicy.LogErrors; + con.Value.Config.DebugContext.Logger = DefaultLoggers.UnityLogger; + } + + con.Value.IndexReferenceResolver = resolver.Value; + + using (var writerCache = GetCachedUnityWriter(format, stream.Value.MemoryStream, con)) + { + SerializeUnityObject(unityObject, writerCache.Value as IDataWriter, serializeUnityFields); + } + } + } + + bytes = stream.Value.MemoryStream.ToArray(); + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static void SerializeUnityObject(UnityEngine.Object unityObject, IDataWriter writer, bool serializeUnityFields = false) + { + if (unityObject == null) + { + throw new ArgumentNullException("unityObject"); + } + + if (writer == null) + { + throw new ArgumentNullException("writer"); + } + + //if (unityObject is Component) + //{ + // Component com = (Component)unityObject; + + // if (com.gameObject.scene.IsValid()) + // { + // Debug.Log("Serializing scene " + com.gameObject.scene.name + ": " + com, com); + // } + // else + // { + // var path = UnityEditor.AssetDatabase.GetAssetPath(com); + + // Debug.Log("Serializing prefab '" + path + "': " + com, com); + // } + //} + //else + //{ + // Debug.Log("Serializing " + unityObject, unityObject); + //} + + try + { + writer.PrepareNewSerializationSession(); + + var members = FormatterUtilities.GetSerializableMembers(unityObject.GetType(), writer.Context.Config.SerializationPolicy); + object unityObjectInstance = unityObject; + + for (int i = 0; i < members.Length; i++) + { + var member = members[i]; + WeakValueGetter getter = null; + + if (!OdinWillSerialize(member, serializeUnityFields, writer.Context.Config.SerializationPolicy) || (getter = GetCachedUnityMemberGetter(member)) == null) + { + continue; + } + + var value = getter(ref unityObjectInstance); + + bool isNull = object.ReferenceEquals(value, null); + + // Never serialize serialization data. That way lies madness. + if (!isNull && value.GetType() == typeof(SerializationData)) + { + continue; + } + + Serializer serializer = Serializer.Get(FormatterUtilities.GetContainedType(member)); + + try + { + serializer.WriteValueWeak(member.Name, value, writer); + } + catch (Exception ex) + { + writer.Context.Config.DebugContext.LogException(ex); + } + } + + writer.FlushToStream(); + } + catch (SerializationAbortException ex) + { + throw new SerializationAbortException("Serialization of type '" + unityObject.GetType().GetNiceFullName() + "' aborted.", ex); + } + catch (Exception ex) + { + Debug.LogException(new Exception("Exception thrown while serializing type '" + unityObject.GetType().GetNiceFullName() + "': " + ex.Message, ex)); + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static void DeserializeUnityObject(UnityEngine.Object unityObject, ref SerializationData data, DeserializationContext context = null) + { + //#if UNITY_EDITOR + DeserializeUnityObject(unityObject, ref data, context, isPrefabData: false, prefabInstanceUnityObjects: null); + //#else + // UnitySerializationUtility.DeserializeUnityObject(unityObject, ref data.SerializedBytes, ref data.ReferencedUnityObjects, data.SerializedFormat, context); + // data = default(SerializationData); // Free all data for GC + //#endif + } + + private static void DeserializeUnityObject(UnityEngine.Object unityObject, ref SerializationData data, DeserializationContext context, bool isPrefabData, List<UnityEngine.Object> prefabInstanceUnityObjects) + { + if (unityObject == null) + { + throw new ArgumentNullException("unityObject"); + } + + if (isPrefabData && prefabInstanceUnityObjects == null) + { + prefabInstanceUnityObjects = new List<UnityEngine.Object>(); // There's likely no data at all + } + +#if UNITY_EDITOR + if (OdinPrefabSerializationEditorUtility.HasNewPrefabWorkflow) + { + ISupportsPrefabSerialization supporter = unityObject as ISupportsPrefabSerialization; + + if (supporter != null) + { + var sData = supporter.SerializationData; + + if (!sData.ContainsData) + { + return; + } + + sData.Prefab = null; + supporter.SerializationData = sData; + } + } + + // TODO: This fix needs to be applied for edge-cases! But we also need a way to only do it while in the Editor! and if UNITY_EDITOR is not enough. + //Debug.Log("Deserializing" + new System.Diagnostics.StackTrace().ToString(), unityObject); + //var prefabDataObject = data.Prefab as ISupportsPrefabSerialization; + //if (prefabDataObject != null && data.PrefabModifications != null && UnityEditor.AssetDatabase.Contains(data.Prefab)) + //{ + // // In some rare cases, prefab instances can become corrupted if there somehow end up being applied bad prefab modifications + // // for the JSON, Nodes or Binary data fields. This will of course result in a corrupted data-stream, and if the resilient serialization mode fails + // // to be resilient, scenes will also give an error when opnened, and the object won't fix itself. + // // How these bad Unity prefab modifications ends up there in the first place, is a mystery. + + // // ---------------- + // // But it's easy enought to replicate: + // // 1. Create a game object with a SerializedMonoBehaviour containing a dictionary. + // // 2. Make it a prefab + // // 3. Instantiate the dictionary and add an entry to it in the prefab instance and hit apply (ONCE) + // // 4. Now totally random prefab modifications are applied to the SerializedNodes data member: https://jumpshare.com/v/eiZv39CUJKOuNCVhMu83 + // // - These are harmless in many situations, as the values are exactly the same as in the prefab: https://jumpshare.com/v/aMlA0dD7gsA2uvO5ot48 + // // - Furthermore, when hitting apply again, all of the bad modifications go away. + // // 5. But if we instead of hitting apply again, save the scene, and remove the dictionary member from the c# class, + // // we're now left with permanent modifications to the data.SerializaitionNodes, which will result + // // in a corrupted data stream, as we continue to modify the prefab it self. + // // 6. Set the serialization mode to log warnings and errors, remove the dictioanry from the c# class and open the scene again. + // // ---------------- + + // // Here we make sure that we deserialize the object using the data from the prefab, and override any potentially corrupted data. + // // In prefab instances, the only thing we care about are the actual prefab-modifications (data.PrefabModifications and data.PrefabModificationsReferencedUnityObjects) + // // This fix will also remove any corruped data, and trigger Unity to remove all bad prefab modifications. + // var prefabData = prefabDataObject.SerializationData; + // if (prefabData.ContainsData) + // { + // data.SerializedFormat = prefabData.SerializedFormat; + // data.SerializationNodes = prefabData.SerializationNodes ?? new List<SerializationNode>(); + // data.ReferencedUnityObjects = prefabData.ReferencedUnityObjects ?? new List<UnityEngine.Object>(); + // data.SerializedBytesString = prefabData.SerializedBytesString ?? ""; + // data.SerializedBytes = prefabData.SerializedBytes ?? new byte[0]; + // } + //} +#endif + + if ((data.SerializedBytes != null && data.SerializedBytes.Length > 0) && (data.SerializationNodes == null || data.SerializationNodes.Count == 0)) + { + // If it happens that we have bytes in the serialized bytes array + // then we deserialize from that instead. + + // This happens often in play mode, when instantiating, since we + // are emulating build behaviour. + + if (data.SerializedFormat == DataFormat.Nodes) + { + // The stored format says nodes, but there is no serialized node data. + // Figure out what format the serialized bytes are in, and deserialize that format instead + + DataFormat formatGuess = data.SerializedBytes[0] == '{' ? DataFormat.JSON : DataFormat.Binary; + + try + { + var bytesStr = ProperBitConverter.BytesToHexString(data.SerializedBytes); + Debug.LogWarning("Serialization data has only bytes stored, but the serialized format is marked as being 'Nodes', which is incompatible with data stored as a byte array. Based on the appearance of the serialized bytes, Odin has guessed that the data format is '" + formatGuess + "', and will attempt to deserialize the bytes using that format. The serialized bytes follow, converted to a hex string: " + bytesStr); + } + catch { } + + UnitySerializationUtility.DeserializeUnityObject(unityObject, ref data.SerializedBytes, ref data.ReferencedUnityObjects, formatGuess, context); + } + else + { + UnitySerializationUtility.DeserializeUnityObject(unityObject, ref data.SerializedBytes, ref data.ReferencedUnityObjects, data.SerializedFormat, context); + } + + // If there are any prefab modifications, we should *always* apply those + ApplyPrefabModifications(unityObject, data.PrefabModifications, data.PrefabModificationsReferencedUnityObjects); + } + else + { + Cache<DeserializationContext> cachedContext = null; + + try + { + if (context == null) + { + cachedContext = Cache<DeserializationContext>.Claim(); + context = cachedContext; + + context.Config.SerializationPolicy = SerializationPolicies.Unity; + + /* If the config instance is not loaded (it should usually be, but in rare cases + * it's not), we must not ask for it, as we are not allowed to load from resources + * or the asset database during some serialization callbacks. + * + * (Trying to do that causes internal Unity errors and potentially even crashes.) + * + * If it's not loaded, we fall back to default values, since there's no other choice. + */ + if (GlobalSerializationConfig.HasInstanceLoaded) + { + //Debug.Log("Deserializing " + unityObject.GetType().Name + " WITH loaded!"); + context.Config.DebugContext.ErrorHandlingPolicy = GlobalSerializationConfig.Instance.ErrorHandlingPolicy; + context.Config.DebugContext.LoggingPolicy = GlobalSerializationConfig.Instance.LoggingPolicy; + context.Config.DebugContext.Logger = GlobalSerializationConfig.Instance.Logger; + } + else + { + //Debug.Log("Deserializing " + unityObject.GetType().Name + " WITHOUT loaded!"); + context.Config.DebugContext.ErrorHandlingPolicy = ErrorHandlingPolicy.Resilient; + context.Config.DebugContext.LoggingPolicy = LoggingPolicy.LogErrors; + context.Config.DebugContext.Logger = DefaultLoggers.UnityLogger; + } + } + + // If we have a policy override, use that + { + IOverridesSerializationPolicy policyOverride = unityObject as IOverridesSerializationPolicy; + + if (policyOverride != null) + { + var serializationPolicy = policyOverride.SerializationPolicy; + + if (serializationPolicy != null) + { + context.Config.SerializationPolicy = serializationPolicy; + } + } + + } + + if (!isPrefabData && !data.Prefab.SafeIsUnityNull()) + { + if (data.Prefab is ISupportsPrefabSerialization) + { + if (object.ReferenceEquals(data.Prefab, unityObject) && data.PrefabModifications != null && data.PrefabModifications.Count > 0) + { + // We are deserializing a prefab, which has *just* had changes applied + // from an instance of itself. + // + // This is the only place, anywhere, where we can detect this happening + // so we need to register it, so the prefab instance that just applied + // its values knows to wipe all of its modifications clean. + + // However, we only do this in the editor. If it happens outside of the + // editor it would be deeply strange, but we shouldn't correct anything + // in that case as it makes no sense. + +#if UNITY_EDITOR + lock (PrefabDeserializeUtility.DeserializePrefabs_LOCK) + { + PrefabDeserializeUtility.PrefabsWithValuesApplied.Add(unityObject); + } +#endif + } + else + { + // We are dealing with a prefab instance, which is a special bail-out case + SerializationData prefabData = (data.Prefab as ISupportsPrefabSerialization).SerializationData; + +#if UNITY_EDITOR + lock (PrefabDeserializeUtility.DeserializePrefabs_LOCK) + { + // Only perform this check in the editor, as we are never dealing with a prefab + // instance outside of the editor - even if the serialized data is weird + if (PrefabDeserializeUtility.PrefabsWithValuesApplied.Contains(data.Prefab)) + { + // Our prefab has had values applied; now to check if the object we're + // deserializing was the one to apply those values. If it is, then we + // have to wipe all of this object's prefab modifications clean. + // + // So far, the only way we know how to do that, is checking whether this + // object is currently selected. + + if (PrefabSelectionTracker.IsCurrentlySelectedPrefabRoot(unityObject)) + { + PrefabDeserializeUtility.PrefabsWithValuesApplied.Remove(data.Prefab); + + List<PrefabModification> newModifications = null; + HashSet<object> keep = PrefabDeserializeUtility.GetSceneObjectsToKeepSet(unityObject, false); + + if (data.PrefabModificationsReferencedUnityObjects.Count > 0 && keep != null && keep.Count > 0) + { + newModifications = DeserializePrefabModifications(data.PrefabModifications, data.PrefabModificationsReferencedUnityObjects); + newModifications.RemoveAll(n => object.ReferenceEquals(n.ModifiedValue, null) || !keep.Contains(n.ModifiedValue)); + } + else + { + if (data.PrefabModifications != null) + { + data.PrefabModifications.Clear(); + } + + if (data.PrefabModificationsReferencedUnityObjects != null) + { + data.PrefabModificationsReferencedUnityObjects.Clear(); + } + } + + newModifications = newModifications ?? new List<PrefabModification>(); + PrefabModificationCache.CachePrefabModifications(unityObject, newModifications); + + RegisterPrefabModificationsChange(unityObject, newModifications); + } + } + } +#endif + + if (!prefabData.ContainsData) + { + // Sometimes, the prefab hasn't actually been deserialized yet, because + // Unity doesn't do anything in a sensible way at all. + // + // In this case, we have to deserialize from our own data, and just + // pretend it's the prefab's data. We can just hope Unity hasn't messed + // with the serialized data; it *should* be the same on this instance as + // it is on the prefab itself. + // + // This case occurs often during editor recompile reloads. + + DeserializeUnityObject(unityObject, ref data, context, isPrefabData: true, prefabInstanceUnityObjects: data.ReferencedUnityObjects); + } + else + { + // Deserialize the current object with the prefab's data + DeserializeUnityObject(unityObject, ref prefabData, context, isPrefabData: true, prefabInstanceUnityObjects: data.ReferencedUnityObjects); + } + + // Then apply the prefab modifications using the deserialization context + ApplyPrefabModifications(unityObject, data.PrefabModifications, data.PrefabModificationsReferencedUnityObjects); + + return; // Buh bye + } + } + // A straight UnityEngine.Object instance means that the type has been lost to Unity due to a deleted or renamed script + // We shouldn't complain in this case, as Unity itself will make it clear to the user that there is something wrong. + else if (data.Prefab.GetType() != typeof(UnityEngine.Object)) + { + Debug.LogWarning("The type " + data.Prefab.GetType().GetNiceName() + " no longer supports special prefab serialization (the interface " + typeof(ISupportsPrefabSerialization).GetNiceName() + ") upon deserialization of an instance of a prefab; prefab data may be lost. Has a type been lost?"); + } + } + + var unityObjects = isPrefabData ? prefabInstanceUnityObjects : data.ReferencedUnityObjects; + + if (data.SerializedFormat == DataFormat.Nodes) + { + // Special case for node format + using (var reader = new SerializationNodeDataReader(context)) + using (var resolver = Cache<UnityReferenceResolver>.Claim()) + { + resolver.Value.SetReferencedUnityObjects(unityObjects); + context.IndexReferenceResolver = resolver.Value; + + reader.Nodes = data.SerializationNodes; + + UnitySerializationUtility.DeserializeUnityObject(unityObject, reader); + } + } + else if (data.SerializedBytes != null && data.SerializedBytes.Length > 0) + { + UnitySerializationUtility.DeserializeUnityObject(unityObject, ref data.SerializedBytes, ref unityObjects, data.SerializedFormat, context); + } + else + { + UnitySerializationUtility.DeserializeUnityObject(unityObject, ref data.SerializedBytesString, ref unityObjects, data.SerializedFormat, context); + } + + // We may have a prefab that has had changes applied to it; either way, apply the stored modifications. + ApplyPrefabModifications(unityObject, data.PrefabModifications, data.PrefabModificationsReferencedUnityObjects); + } + finally + { + if (cachedContext != null) + { + Cache<DeserializationContext>.Release(cachedContext); + } + } + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static void DeserializeUnityObject(UnityEngine.Object unityObject, ref string base64Bytes, ref List<UnityEngine.Object> referencedUnityObjects, DataFormat format, DeserializationContext context = null) + { + if (string.IsNullOrEmpty(base64Bytes)) + { + return; + } + + byte[] bytes = null; + + try + { + bytes = Convert.FromBase64String(base64Bytes); + } + catch (FormatException) + { + Debug.LogError("Invalid base64 string when deserializing data: " + base64Bytes); + } + + if (bytes != null) + { + DeserializeUnityObject(unityObject, ref bytes, ref referencedUnityObjects, format, context); + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static void DeserializeUnityObject(UnityEngine.Object unityObject, ref byte[] bytes, ref List<UnityEngine.Object> referencedUnityObjects, DataFormat format, DeserializationContext context = null) + { + if (unityObject == null) + { + throw new ArgumentNullException("unityObject"); + } + + if (bytes == null || bytes.Length == 0) + { + return; + } + + if (format == DataFormat.Nodes) + { + try + { + Debug.LogError("The serialization data format '" + format.ToString() + "' is not supported by this method. You must create your own reader."); + } + catch { } + return; + } + + if (referencedUnityObjects == null) + { + referencedUnityObjects = new List<UnityEngine.Object>(); + } + + using (var stream = Cache<CachedMemoryStream>.Claim()) + using (var resolver = Cache<UnityReferenceResolver>.Claim()) + { + stream.Value.MemoryStream.Write(bytes, 0, bytes.Length); + stream.Value.MemoryStream.Position = 0; + + resolver.Value.SetReferencedUnityObjects(referencedUnityObjects); + + if (context != null) + { + context.IndexReferenceResolver = resolver.Value; + + using (var readerCache = GetCachedUnityReader(format, stream.Value.MemoryStream, context)) + { + DeserializeUnityObject(unityObject, readerCache.Value as IDataReader); + } + } + else + { + using (var con = Cache<DeserializationContext>.Claim()) + { + con.Value.Config.SerializationPolicy = SerializationPolicies.Unity; + + /* If the config instance is not loaded (it should usually be, but in rare cases + * it's not), we must not ask for it, as we are not allowed to load from resources + * or the asset database during some serialization callbacks. + * + * (Trying to do that causes internal Unity errors and potentially even crashes.) + * + * If it's not loaded, we fall back to default values, since there's no other choice. + */ + if (GlobalSerializationConfig.HasInstanceLoaded) + { + //Debug.Log("Deserializing " + unityObject.GetType().Name + " WITH loaded!"); + con.Value.Config.DebugContext.ErrorHandlingPolicy = GlobalSerializationConfig.Instance.ErrorHandlingPolicy; + con.Value.Config.DebugContext.LoggingPolicy = GlobalSerializationConfig.Instance.LoggingPolicy; + con.Value.Config.DebugContext.Logger = GlobalSerializationConfig.Instance.Logger; + } + else + { + //Debug.Log("Deserializing " + unityObject.GetType().Name + " WITHOUT loaded!"); + con.Value.Config.DebugContext.ErrorHandlingPolicy = ErrorHandlingPolicy.Resilient; + con.Value.Config.DebugContext.LoggingPolicy = LoggingPolicy.LogErrors; + con.Value.Config.DebugContext.Logger = DefaultLoggers.UnityLogger; + } + + con.Value.IndexReferenceResolver = resolver.Value; + + using (var readerCache = GetCachedUnityReader(format, stream.Value.MemoryStream, con)) + { + DeserializeUnityObject(unityObject, readerCache.Value as IDataReader); + } + } + } + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static void DeserializeUnityObject(UnityEngine.Object unityObject, IDataReader reader) + { + if (unityObject == null) + { + throw new ArgumentNullException("unityObject"); + } + + if (reader == null) + { + throw new ArgumentNullException("reader"); + } + + var policyOverride = unityObject as IOverridesSerializationPolicy; + + if (policyOverride != null) + { + var policy = policyOverride.SerializationPolicy; + + if (policy != null) + { + reader.Context.Config.SerializationPolicy = policy; + } + } + + try + { + reader.PrepareNewSerializationSession(); + + var members = FormatterUtilities.GetSerializableMembersMap(unityObject.GetType(), reader.Context.Config.SerializationPolicy); + + int count = 0; + string name; + EntryType entryType; + object unityObjectInstance = unityObject; + + while ((entryType = reader.PeekEntry(out name)) != EntryType.EndOfNode && entryType != EntryType.EndOfArray && entryType != EntryType.EndOfStream) + { + MemberInfo member = null; + WeakValueSetter setter = null; + + bool skip = false; + + if (entryType == EntryType.Invalid) + { + // Oh boy. We have a lot of logging to do! + + var message = "Encountered invalid entry while reading serialization data for Unity object of type '" + unityObject.GetType().GetNiceFullName() + "'. " + + "This likely means that Unity has filled Odin's stored serialization data with garbage, which can randomly happen after upgrading the Unity version of the project, or when otherwise doing things that have a lot of fragile interactions with the asset database. " + + "Locating the asset which causes this error log and causing it to reserialize (IE, modifying it and then causing it to be saved to disk) is likely to 'fix' the issue and make this message go away. " + + "Even so, DATA MAY HAVE BEEN LOST, and you should verify with your version control system (you're using one, right?!) that everything is alright, and if not, use it to rollback the asset to recover your data.\n\n\n"; + +#if UNITY_EDITOR + // Schedule a delayed log: + try + { + message += "A delayed warning message containing the originating object's name, type and scene/asset path (if applicable) will be scheduled for logging on Unity's main thread. Search for \"DELAYED SERIALIZATION LOG\". " + + "This logging callback will also mark the object dirty if it is an asset, hopefully making the issue 'fix' itself. HOWEVER, THERE MAY STILL BE DATA LOSS.\n\n\n"; + + EditorApplication_delayCall_Alias += () => + { + var log = "DELAYED SERIALIZATION LOG: Name = " + unityObject.name + ", Type = " + unityObject.GetType().GetNiceFullName(); + + UnityEngine.Object toPing = unityObject; + + var component = unityObject as Component; + + if (component != null && component.gameObject.scene.IsValid()) + { + log += ", ScenePath = " + component.gameObject.scene.path; + } + + if (UnityEditor.AssetDatabase.Contains(unityObject)) + { + var path = UnityEditor.AssetDatabase.GetAssetPath(unityObject); + log += ", AssetPath = " + path; + + toPing = UnityEditor.AssetDatabase.LoadMainAssetAtPath(path); + + if (toPing == null) toPing = unityObject; + + UnityEditor.EditorUtility.SetDirty(unityObject); + UnityEditor.AssetDatabase.SaveAssets(); + } + + Debug.LogWarning(log, toPing); + }; + } + catch + { + Debug.LogWarning("DELAYED SERIALIZATION LOG: Delaying log to main thread failed, likely due to a race condition when subscribing to EditorApplication.delayCall; this cannot be guarded against from our code. Try to provoke the error again and hope to get luckier next time!"); + } +#endif + + message += + "IF YOU HAVE CONSISTENT REPRODUCTION STEPS THAT MAKE THIS ISSUE REOCCUR, please report it at this issue at 'https://bitbucket.org/sirenix/odin-inspector/issues/526', and copy paste this debug message into your comment, along with any potential actions or recent changes in the project that might have happened to cause this message to occur. " + + "If the data dump in this message is cut off, please find the editor's log file (see https://docs.unity3d.com/Manual/LogFiles.html) and copy paste the full version of this message from there.\n\n\n" + + "Data dump:\n\n" + + " Reader type: " + reader.GetType().Name + "\n"; + + try + { + message += " Data dump: " + reader.GetDataDump(); + //if (reader is SerializationNodeDataReader) + //{ + // var nodes = (reader as SerializationNodeDataReader).Nodes; + // message += " Nodes dump: \n\n" + string.Join("\n", nodes.Select(node => " - Name: " + node.Name + "\n Entry: " + node.Entry + "\n Data: " + node.Data).ToArray()); + //} + //else if (reader.Stream is MemoryStream) + //{ + // message += " Data stream dump (base64): " + ProperBitConverter.BytesToHexString((reader.Stream as MemoryStream).ToArray()); + //} + } + finally + { + reader.Context.Config.DebugContext.LogError(message); + skip = true; + } + } + else if (string.IsNullOrEmpty(name)) + { + reader.Context.Config.DebugContext.LogError("Entry of type \"" + entryType + "\" in node \"" + reader.CurrentNodeName + "\" is missing a name."); + skip = true; + } + else if (members.TryGetValue(name, out member) == false || (setter = GetCachedUnityMemberSetter(member)) == null) + { + skip = true; + } + + if (skip) + { + reader.SkipEntry(); + continue; + } + + { + Type expectedType = FormatterUtilities.GetContainedType(member); + Serializer serializer = Serializer.Get(expectedType); + + try + { + object value = serializer.ReadValueWeak(reader); + setter(ref unityObjectInstance, value); + } + catch (Exception ex) + { + reader.Context.Config.DebugContext.LogException(ex); + } + } + + count++; + + if (count > 1000) + { + reader.Context.Config.DebugContext.LogError("Breaking out of infinite reading loop! (Read more than a thousand entries for one type!)"); + break; + } + } + } + catch (SerializationAbortException ex) + { + throw new SerializationAbortException("Deserialization of type '" + unityObject.GetType().GetNiceFullName() + "' aborted.", ex); + } + catch (Exception ex) + { + Debug.LogException(new Exception("Exception thrown while deserializing type '" + unityObject.GetType().GetNiceFullName() + "': " + ex.Message, ex)); + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static List<string> SerializePrefabModifications(List<PrefabModification> modifications, ref List<UnityEngine.Object> referencedUnityObjects) + { + if (referencedUnityObjects == null) + { + referencedUnityObjects = new List<UnityEngine.Object>(); + } + else if (referencedUnityObjects.Count > 0) + { + referencedUnityObjects.Clear(); + } + + if (modifications == null || modifications.Count == 0) + { + return new List<string>(); + } + + // Sort modifications alphabetically by path; this will ensure that modifications + // to child paths are always applied after modifications to the parent paths + modifications.Sort((a, b) => + { + int compared = a.Path.CompareTo(b.Path); + + if (compared == 0) + { + if ((a.ModificationType == PrefabModificationType.ListLength || a.ModificationType == PrefabModificationType.Dictionary) && b.ModificationType == PrefabModificationType.Value) + { + return 1; + } + else if (a.ModificationType == PrefabModificationType.Value && (b.ModificationType == PrefabModificationType.ListLength || b.ModificationType == PrefabModificationType.Dictionary)) + { + return -1; + } + } + + return compared; + }); + + List<string> result = new List<string>(); + + using (var context = Cache<SerializationContext>.Claim()) + using (var stream = CachedMemoryStream.Claim()) + using (var writerCache = Cache<JsonDataWriter>.Claim()) + using (var resolver = Cache<UnityReferenceResolver>.Claim()) + { + var writer = writerCache.Value; + + writer.Context = context; + writer.Stream = stream.Value.MemoryStream; + writer.PrepareNewSerializationSession(); + writer.FormatAsReadable = false; + writer.EnableTypeOptimization = false; + + resolver.Value.SetReferencedUnityObjects(referencedUnityObjects); + writer.Context.IndexReferenceResolver = resolver.Value; + + for (int i = 0; i < modifications.Count; i++) + { + var mod = modifications[i]; + + if (mod.ModificationType == PrefabModificationType.ListLength) + { + writer.MarkJustStarted(); + writer.WriteString("path", mod.Path); + writer.WriteInt32("length", mod.NewLength); + + writer.FlushToStream(); + result.Add(GetStringFromStreamAndReset(stream.Value.MemoryStream)); + } + else if (mod.ModificationType == PrefabModificationType.Value) + { + writer.MarkJustStarted(); + writer.WriteString("path", mod.Path); + + if (mod.ReferencePaths != null && mod.ReferencePaths.Count > 0) + { + writer.BeginStructNode("references", null); + { + for (int j = 0; j < mod.ReferencePaths.Count; j++) + { + writer.WriteString(null, mod.ReferencePaths[j]); + } + } + writer.EndNode("references"); + } + + var serializer = Serializer.Get<object>(); + serializer.WriteValueWeak("value", mod.ModifiedValue, writer); + + writer.FlushToStream(); + result.Add(GetStringFromStreamAndReset(stream.Value.MemoryStream)); + } + else if (mod.ModificationType == PrefabModificationType.Dictionary) + { + writer.MarkJustStarted(); + writer.WriteString("path", mod.Path); + + Serializer.Get<object[]>().WriteValue("add_keys", mod.DictionaryKeysAdded, writer); + Serializer.Get<object[]>().WriteValue("remove_keys", mod.DictionaryKeysRemoved, writer); + + writer.FlushToStream(); + result.Add(GetStringFromStreamAndReset(stream.Value.MemoryStream)); + } + + // We don't want modifications to be able to reference each other + writer.Context.ResetInternalReferences(); + } + } + + return result; + } + + private static string GetStringFromStreamAndReset(Stream stream) + { + byte[] bytes = new byte[stream.Position]; + stream.Position = 0; + stream.Read(bytes, 0, bytes.Length); + stream.Position = 0; + + return Encoding.UTF8.GetString(bytes); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static List<PrefabModification> DeserializePrefabModifications(List<string> modifications, List<UnityEngine.Object> referencedUnityObjects) + { + if (modifications == null || modifications.Count == 0) + { + // Nothing to apply + return new List<PrefabModification>(); + } + + List<PrefabModification> result = new List<PrefabModification>(); + + int longestByteCount = 0; + + for (int i = 0; i < modifications.Count; i++) + { + int count = modifications[i].Length * 2; + + if (count > longestByteCount) + { + longestByteCount = count; + } + } + + using (var context = Cache<DeserializationContext>.Claim()) + using (var streamCache = CachedMemoryStream.Claim(longestByteCount)) + using (var readerCache = Cache<JsonDataReader>.Claim())// GetCachedUnityReader(DataFormat.JSON, streamCache.Value.MemoryStream, context)) + using (var resolver = Cache<UnityReferenceResolver>.Claim()) + { + var stream = streamCache.Value.MemoryStream; + var reader = readerCache.Value; + + reader.Context = context; + reader.Stream = stream; + + resolver.Value.SetReferencedUnityObjects(referencedUnityObjects); + reader.Context.IndexReferenceResolver = resolver.Value; + + for (int i = 0; i < modifications.Count; i++) + { + string modStr = modifications[i]; + byte[] bytes = Encoding.UTF8.GetBytes(modStr); + + stream.SetLength(bytes.Length); + stream.Position = 0; + stream.Write(bytes, 0, bytes.Length); + stream.Position = 0; + + PrefabModification modification = new PrefabModification(); + + string entryName; + EntryType entryType; + + reader.PrepareNewSerializationSession(); + + entryType = reader.PeekEntry(out entryName); + + if (entryType == EntryType.EndOfStream) + { + // We might have reached the end of stream from a prior modification string + // If we have, force our way into the first entry in the new string + reader.SkipEntry(); + } + + while ((entryType = reader.PeekEntry(out entryName)) != EntryType.EndOfNode && entryType != EntryType.EndOfArray && entryType != EntryType.EndOfStream) + { + if (entryName == null) + { + Debug.LogError("Unexpected entry of type " + entryType + " without a name."); + reader.SkipEntry(); + continue; + } + + if (entryName.Equals("path", StringComparison.InvariantCultureIgnoreCase)) + { + reader.ReadString(out modification.Path); + } + else if (entryName.Equals("length", StringComparison.InvariantCultureIgnoreCase)) + { + reader.ReadInt32(out modification.NewLength); + modification.ModificationType = PrefabModificationType.ListLength; + } + else if (entryName.Equals("references", StringComparison.InvariantCultureIgnoreCase)) + { + modification.ReferencePaths = new List<string>(); + + Type dummy; + reader.EnterNode(out dummy); + { + while (reader.PeekEntry(out entryName) == EntryType.String) + { + string path; + reader.ReadString(out path); + modification.ReferencePaths.Add(path); + } + } + reader.ExitNode(); + } + else if (entryName.Equals("value", StringComparison.InvariantCultureIgnoreCase)) + { + modification.ModifiedValue = Serializer.Get<object>().ReadValue(reader); + modification.ModificationType = PrefabModificationType.Value; + } + else if (entryName.Equals("add_keys", StringComparison.InvariantCultureIgnoreCase)) + { + modification.DictionaryKeysAdded = Serializer.Get<object[]>().ReadValue(reader); + modification.ModificationType = PrefabModificationType.Dictionary; + } + else if (entryName.Equals("remove_keys", StringComparison.InvariantCultureIgnoreCase)) + { + modification.DictionaryKeysRemoved = Serializer.Get<object[]>().ReadValue(reader); + modification.ModificationType = PrefabModificationType.Dictionary; + } + else + { + Debug.LogError("Unexpected entry name '" + entryName + "' while deserializing prefab modifications."); + reader.SkipEntry(); + } + } + + if (modification.Path == null) + { + // This happens quite often if you change the structure of a class which cointains prefab modifications made to it. + // Those invalid prefab modifications should be removed, which they seem to be in most cases, but apparently not some. + // + // And debugging a warning here makes give errors because of the bug in VS Bridge. And prevents people from bulding. + // Debug.LogWarning("Error when deserializing prefab modification; no path found. Modification lost; string was: '" + modStr + "'."); + continue; + } + + result.Add(modification); + } + } + + return result; + } + +#if UNITY_EDITOR + + /// <summary> + /// Not yet documented. + /// </summary> + public static void RegisterPrefabModificationsChange(UnityEngine.Object unityObject, List<PrefabModification> modifications) + { + if (unityObject == null) + { + throw new ArgumentNullException("unityObject"); + } + +#if PREFAB_DEBUG + //Debug.Log((Event.current == null ? "NO EVENT" : Event.current.type.ToString()) + ": Registering " + (modifications == null ? 0 : modifications.Count) + " modifications to " + unityObject.name + ":"); + Debug.Log("Registering " + (modifications == null ? 0 : modifications.Count) + " prefab modifications: "); + + for (int i = 0; i < modifications.Count; i++) + { + var mod = modifications[i]; + + if (mod.ModificationType == PrefabModificationType.ListLength) + { + Debug.Log(" LENGTH@" + mod.Path + ": " + mod.NewLength); + } + else if (mod.ModificationType == PrefabModificationType.Dictionary) + { + Debug.Log(" DICT@" + mod.Path + ": (add: " + (mod.DictionaryKeysAdded != null ? mod.DictionaryKeysAdded.Length : 0) + ") -- (remove: " + (mod.DictionaryKeysRemoved != null ? mod.DictionaryKeysRemoved.Length : 0) + ")"); + } + else + { + string str; + + if (mod.ModifiedValue == null) + { + str = "null"; + } + else if (typeof(UnityEngine.Object).IsAssignableFrom(mod.ModificationType.GetType())) + { + str = "Unity object"; + } + else + { + str = mod.ModificationType.ToString(); + } + + Debug.Log(" VALUE@" + mod.Path + ": " + str); + } + } +#endif + + PrefabModificationCache.CachePrefabModifications(unityObject, modifications); + RegisteredPrefabModifications[unityObject] = modifications; + } + +#endif + + /// <summary> + /// Creates an object with default values initialized in the style of Unity; strings will be "", classes will be instantiated recursively with default values, and so on. + /// </summary> + public static object CreateDefaultUnityInitializedObject(Type type) + { + Assert.IsNotNull(type); + Assert.IsFalse(type.IsAbstract); + Assert.IsFalse(type.IsInterface); + + return CreateDefaultUnityInitializedObject(type, 0); + } + + private static object CreateDefaultUnityInitializedObject(Type type, int depth) + { + if (depth > 5) + { + return null; + } + + if (!UnitySerializationUtility.GuessIfUnityWillSerialize(type)) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + + if (type == typeof(string)) + { + return ""; + } + else if (type.IsEnum) + { + var values = Enum.GetValues(type); + return values.Length > 0 ? values.GetValue(0) : Enum.ToObject(type, 0); + } + else if (type.IsPrimitive) + { + return Activator.CreateInstance(type); + } + else if (type.IsArray) + { + Assert.IsTrue(type.GetArrayRank() == 1); + return Array.CreateInstance(type.GetElementType(), 0); + } + else if (type.ImplementsOpenGenericClass(typeof(List<>)) || typeof(UnityEventBase).IsAssignableFrom(type)) + { + try + { + return Activator.CreateInstance(type); + } + catch + { + return null; + } + } + else if (typeof(UnityEngine.Object).IsAssignableFrom(type)) + { + return null; + } + else if ((type.Assembly.GetName().Name.StartsWith("UnityEngine") || type.Assembly.GetName().Name.StartsWith("UnityEditor")) && type.GetConstructor(Type.EmptyTypes) != null) + { + try + { + return Activator.CreateInstance(type); + } + catch (Exception ex) + { + Debug.LogException(ex); + return null; + } + } + + object value; + + if (type.GetConstructor(Type.EmptyTypes) != null) + { + return Activator.CreateInstance(type); + } + + value = FormatterServices.GetUninitializedObject(type); + + var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + for (int i = 0; i < fields.Length; i++) + { + var field = fields[i]; + if (GuessIfUnityWillSerialize(field)) + { + field.SetValue(value, CreateDefaultUnityInitializedObject(field.FieldType, depth + 1)); + } + } + + return value; + } + + private static void ApplyPrefabModifications(UnityEngine.Object unityObject, List<string> modificationData, List<UnityEngine.Object> referencedUnityObjects) + { + if (unityObject == null) + { + throw new ArgumentNullException("unityObject"); + } + + if (modificationData == null || modificationData.Count == 0) + { +#if UNITY_EDITOR + PrefabModificationCache.CachePrefabModifications(unityObject, new List<PrefabModification>()); +#endif + + // Nothing to apply. + return; + } + + var modifications = DeserializePrefabModifications(modificationData, referencedUnityObjects); + +#if UNITY_EDITOR + PrefabModificationCache.CachePrefabModifications(unityObject, modifications); +#endif + + for (int i = 0; i < modifications.Count; i++) + { + var mod = modifications[i]; + + try + { + mod.Apply(unityObject); + } + catch (Exception ex) + { + Debug.Log("The following exception was thrown when trying to apply a prefab modification for path '" + mod.Path + "':"); + Debug.LogException(ex); + } + } + } + + private static WeakValueGetter GetCachedUnityMemberGetter(MemberInfo member) + { + lock (UnityMemberGetters) + { + WeakValueGetter result; + + if (UnityMemberGetters.TryGetValue(member, out result) == false) + { + if (member is FieldInfo) + { + result = EmitUtilities.CreateWeakInstanceFieldGetter(member.DeclaringType, member as FieldInfo); + } + else if (member is PropertyInfo) + { + result = EmitUtilities.CreateWeakInstancePropertyGetter(member.DeclaringType, member as PropertyInfo); + } + else + { + result = delegate (ref object instance) + { + return FormatterUtilities.GetMemberValue(member, instance); + }; + } + + UnityMemberGetters.Add(member, result); + } + + return result; + } + } + + private static WeakValueSetter GetCachedUnityMemberSetter(MemberInfo member) + { + lock (UnityMemberSetters) + { + WeakValueSetter result; + + if (UnityMemberSetters.TryGetValue(member, out result) == false) + { + if (member is FieldInfo) + { + result = EmitUtilities.CreateWeakInstanceFieldSetter(member.DeclaringType, member as FieldInfo); + } + else if (member is PropertyInfo) + { + result = EmitUtilities.CreateWeakInstancePropertySetter(member.DeclaringType, member as PropertyInfo); + } + else + { + result = delegate (ref object instance, object value) + { + FormatterUtilities.SetMemberValue(member, instance, value); + }; + } + + UnityMemberSetters.Add(member, result); + } + + return result; + } + } + + private static ICache GetCachedUnityWriter(DataFormat format, Stream stream, SerializationContext context) + { + ICache cache; + + switch (format) + { + case DataFormat.Binary: + { + var c = Cache<BinaryDataWriter>.Claim(); + c.Value.Stream = stream; + cache = c; + } + break; + case DataFormat.JSON: + { + var c = Cache<JsonDataWriter>.Claim(); + c.Value.Stream = stream; + cache = c; + } + break; + case DataFormat.Nodes: + throw new InvalidOperationException("Don't do this for nodes!"); + default: + throw new NotImplementedException(format.ToString()); + } + + (cache.Value as IDataWriter).Context = context; + + return cache; + + //IDataWriter writer; + + //if (UnityWriters.TryGetValue(format, out writer) == false) + //{ + // writer = SerializationUtility.CreateWriter(stream, context, format); + // UnityWriters.Add(format, writer); + //} + //else + //{ + // writer.Context = context; + + // if (writer is BinaryDataWriter) + // { + // (writer as BinaryDataWriter).Stream = stream; + // } + // else if (writer is JsonDataWriter) + // { + // (writer as JsonDataWriter).Stream = stream; + // } + //} + + //return writer; + } + + private static ICache GetCachedUnityReader(DataFormat format, Stream stream, DeserializationContext context) + { + ICache cache; + + switch (format) + { + case DataFormat.Binary: + { + var c = Cache<BinaryDataReader>.Claim(); + c.Value.Stream = stream; + cache = c; + } + break; + case DataFormat.JSON: + { + var c = Cache<JsonDataReader>.Claim(); + c.Value.Stream = stream; + cache = c; + } + break; + case DataFormat.Nodes: + throw new InvalidOperationException("Don't do this for nodes!"); + default: + throw new NotImplementedException(format.ToString()); + } + + (cache.Value as IDataReader).Context = context; + + return cache; + + //if (UnityReaders.TryGetValue(format, out reader) == false) + //{ + // reader = SerializationUtility.CreateReader(stream, context, format); + // UnityReaders.Add(format, reader); + //} + //else + //{ + // reader.Context = context; + + // if (reader is BinaryDataReader) + // { + // (reader as BinaryDataReader).Stream = stream; + // } + // else if (reader is JsonDataReader) + // { + // (reader as JsonDataReader).Stream = stream; + // } + //} + + //return reader; + } + +#if UNITY_EDITOR + + [UnityEditor.InitializeOnLoad] + private static class PrefabSelectionTracker + { + private static readonly object LOCK = new object(); + private static readonly HashSet<UnityEngine.Object> selectedPrefabObjects; + + static PrefabSelectionTracker() + { + selectedPrefabObjects = new HashSet<UnityEngine.Object>(ReferenceEqualityComparer<UnityEngine.Object>.Default); + UnityEditor.Selection.selectionChanged += OnSelectionChanged; + OnSelectionChanged(); + } + + public static bool IsCurrentlySelectedPrefabRoot(UnityEngine.Object obj) + { + lock (LOCK) + { + return selectedPrefabObjects.Contains(obj); + } + + //var component = obj as Component; + + //if (object.ReferenceEquals(component, null)) + //{ + // Debug.LogError("A non-component type Unity object (type '" + obj.GetType() + "') is acting like a prefab. What?", obj); + // return false; + //} + + //var prefabRoot = UnityEditor.PrefabUtility.FindPrefabRoot(component.gameObject); + + //return prefabRoot != null && PrefabSelectionTracker.SelectedPrefabRoots.Contains(prefabRoot); + + //var selectedObjects = PrefabSelectionTracker.SelectedPrefabRoots; + + //for (int i = 0; i < selectedObjects.Count; i++) + //{ + // if (object.ReferenceEquals(obj, selectedObjects[i])) + // { + // return true; + // } + //} + + //return false; + } + + private static void OnSelectionChanged() + { + lock (LOCK) + { + selectedPrefabObjects.Clear(); + + var rootPrefabs = UnityEditor.Selection.objects + .Where(n => + { + if (!(n is GameObject)) return false; + + var prefabType = UnityEditor.PrefabUtility.GetPrefabType(n); + return prefabType == UnityEditor.PrefabType.Prefab + || prefabType == UnityEditor.PrefabType.ModelPrefab + || prefabType == UnityEditor.PrefabType.PrefabInstance + || prefabType == UnityEditor.PrefabType.ModelPrefabInstance; + }) + .Select(n => UnityEditor.PrefabUtility.FindPrefabRoot((GameObject)n)) + .Distinct(); + + foreach (var root in rootPrefabs) + { + RegisterRecursive(root); + } + } + //SelectedPrefabRoots.AddRange(selection); + + //for (int i = 0; i < selection.Length; i++) + //{ + // var obj = selection[i]; + + // GameObject gameObject = obj as GameObject; + + // if (!gameObject.SafeIsUnityNull()) + // { + // SelectedPrefabRoots.AddRange(gameObject.GetComponents(typeof(Component))); + // } + //} + } + + private static void RegisterRecursive(GameObject go) + { + selectedPrefabObjects.Add(go); + + var components = go.GetComponents<Component>(); + + for (int i = 0; i < components.Length; i++) + { + selectedPrefabObjects.Add(components[i]); + } + + var transform = go.transform; + + for (int i = 0; i < transform.childCount; i++) + { + var child = transform.GetChild(i); + RegisterRecursive(child.gameObject); + } + } + } + + + public static class PrefabModificationCache + { + private static readonly Dictionary<object, List<PrefabModification>> CachedDeserializedModifications = new Dictionary<object, List<PrefabModification>>(ReferenceEqualityComparer<object>.Default); + private static readonly Dictionary<object, int> CachedDeserializedModificationTimes = new Dictionary<object, int>(ReferenceEqualityComparer<object>.Default); + + private static readonly object Caches_LOCK = new object(); + + private static int counter = 0; + + public static List<PrefabModification> DeserializePrefabModificationsCached(UnityEngine.Object obj, List<string> modifications, List<UnityEngine.Object> referencedUnityObjects) + { + lock (Caches_LOCK) + { + List<PrefabModification> result; + + if (!CachedDeserializedModifications.TryGetValue(obj, out result)) + { + result = DeserializePrefabModifications(modifications, referencedUnityObjects); + CachedDeserializedModifications.Add(obj, result); + } + + CachedDeserializedModificationTimes[obj] = ++counter; + PrunePrefabModificationsCache(); + + return result; + } + } + + public static void CachePrefabModifications(UnityEngine.Object obj, List<PrefabModification> modifications) + { + lock (Caches_LOCK) + { + CachedDeserializedModifications[obj] = modifications; + CachedDeserializedModificationTimes[obj] = ++counter; + PrunePrefabModificationsCache(); + } + } + + private static void PrunePrefabModificationsCache() + { + const int CACHE_SIZE = 10; + + if (CachedDeserializedModifications.Count != CachedDeserializedModificationTimes.Count) + { + CachedDeserializedModifications.Clear(); + CachedDeserializedModificationTimes.Clear(); + } + + // Once, this was a 'while count > CACHE_SIZE' loop, but in certain cases that can infinite loop + // so now it's a simpler and harder-to-break for loop with extra debugging clauses in the body. + int removeCount = CachedDeserializedModificationTimes.Count - CACHE_SIZE; + + for (int i = 0; i < removeCount; i++) + { + object lowestObj = null; + int lowestTime = int.MaxValue; + + foreach (var pair in CachedDeserializedModificationTimes) + { + if (pair.Value < lowestTime) + { + lowestObj = pair.Key; + lowestTime = pair.Value; + } + } + + CachedDeserializedModifications.Remove(lowestObj); + if (!CachedDeserializedModificationTimes.Remove(lowestObj)) + { + Debug.LogError("A Unity object instance of type '" + lowestObj.GetType().GetNiceName() + "' has likely become corrupt or destroyed somehow, yet deserialization has been invoked for it. If you're in the editor, you can click this log message to attempt to highlight the object. (It probably won't work, but there's a chance. If the highlighting doesn't work, the object instance is so broken that Odin cannot give you any more info about it than this message contains. Good luck!)", lowestObj as UnityEngine.Object); + + // There are bad keys in the dictionaries; we have to clear them and just rebuild the cache. + // This theory isn't confirmed, but it's probably because UnityEngine.Object.GetHashCode() + // returns inconsistent results/changes for destroyed objects. + // + // If we don't clear the dictionaries, we will never be able to remove the bad keys. In olden + // days, this was the cause of infinite looping. + + CachedDeserializedModifications.Clear(); + CachedDeserializedModificationTimes.Clear(); + } + } + } + } + + private static readonly MemberInfo EditorApplication_delayCall_Member = typeof(UnityEditor.EditorApplication).GetMember("delayCall", Flags.StaticAnyVisibility).FirstOrDefault(); + + /// <summary> + /// In 2020.1, Unity changed EditorApplication.delayCall from a field to an event, meaning + /// we now have to use reflection to access it consistently across all versions of Unity. + /// </summary> + private static event Action EditorApplication_delayCall_Alias + { + add + { + if (EditorApplication_delayCall_Member == null) throw new InvalidOperationException("EditorApplication.delayCall field or event could not be found. Odin will be broken."); + + if (EditorApplication_delayCall_Member is FieldInfo) + { + UnityEditor.EditorApplication.CallbackFunction val = (UnityEditor.EditorApplication.CallbackFunction)(EditorApplication_delayCall_Member as FieldInfo).GetValue(null); + val += value.ConvertDelegate<UnityEditor.EditorApplication.CallbackFunction>(); + (EditorApplication_delayCall_Member as FieldInfo).SetValue(null, val); + } + else if (EditorApplication_delayCall_Member is EventInfo) + { + (EditorApplication_delayCall_Member as EventInfo).AddEventHandler(null, value); + } + else + { + if (EditorApplication_delayCall_Member == null) throw new InvalidOperationException("EditorApplication.delayCall was not a field or an event. Odin will be broken."); + } + } + remove + { + if (EditorApplication_delayCall_Member == null) throw new InvalidOperationException("EditorApplication.delayCall field or event could not be found. Odin will be broken."); + + if (EditorApplication_delayCall_Member is FieldInfo) + { + UnityEditor.EditorApplication.CallbackFunction val = (UnityEditor.EditorApplication.CallbackFunction)(EditorApplication_delayCall_Member as FieldInfo).GetValue(null); + val -= value.ConvertDelegate<UnityEditor.EditorApplication.CallbackFunction>(); + (EditorApplication_delayCall_Member as FieldInfo).SetValue(null, val); + } + else if (EditorApplication_delayCall_Member is EventInfo) + { + (EditorApplication_delayCall_Member as EventInfo).RemoveEventHandler(null, value); + } + else + { + if (EditorApplication_delayCall_Member == null) throw new InvalidOperationException("EditorApplication.delayCall was not a field or an event. Odin will be broken."); + } + } + } + + private static T ConvertDelegate<T>(this Delegate src) + { + if (src == null || src.GetType() == typeof(T)) + return (T)(object)src; + + if (src.GetInvocationList().Count() == 1) + { + return (T)(object)Delegate.CreateDelegate(typeof(T), src.Target, src.Method); + } + else + { + return (T)(object)src.GetInvocationList().Aggregate<Delegate, Delegate>(null, (current, d) => Delegate.Combine(current, (Delegate)(object)ConvertDelegate<T>(d))); + } + } + +#endif + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationUtility.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationUtility.cs.meta new file mode 100644 index 00000000..53e93183 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Unity Integration/UnitySerializationUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9eb15f2339819bb651c7872d73c89776 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities.meta new file mode 100644 index 00000000..cfae30c1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 64d47b8f1da297b46b2ef39ac68b40ad +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions.meta new file mode 100644 index 00000000..a6221dea --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 56c5d7ad3a7da184bb15ac88016146d8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/FieldInfoExtensions.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/FieldInfoExtensions.cs new file mode 100644 index 00000000..1787771f --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/FieldInfoExtensions.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// <copyright file="FieldInfoExtensions.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Reflection; + + /// <summary> + /// FieldInfo method extensions. + /// </summary> + public static class FieldInfoExtensions + { + /// <summary> + /// Determines whether the specified field is an alias. + /// </summary> + /// <param name="fieldInfo">The field to check.</param> + /// <returns> + /// <c>true</c> if the specified field is an alias; otherwise, <c>false</c>. + /// </returns> + public static bool IsAliasField(this FieldInfo fieldInfo) + { + return fieldInfo is MemberAliasFieldInfo; + } + + /// <summary> + /// Returns the original, backing field of an alias field if the field is an alias. + /// </summary> + /// <param name="fieldInfo">The field to check.</param> + /// /// <param name="throwOnNotAliased">if set to <c>true</c> an exception will be thrown if the field is not aliased.</param> + /// <returns></returns> + /// <exception cref="System.ArgumentException">The field was not aliased; this only occurs if throwOnNotAliased is true.</exception> + public static FieldInfo DeAliasField(this FieldInfo fieldInfo, bool throwOnNotAliased = false) + { + MemberAliasFieldInfo aliasFieldInfo = fieldInfo as MemberAliasFieldInfo; + + if (aliasFieldInfo != null) + { + while (aliasFieldInfo.AliasedField is MemberAliasFieldInfo) + { + aliasFieldInfo = aliasFieldInfo.AliasedField as MemberAliasFieldInfo; + } + + return aliasFieldInfo.AliasedField; + } + + if (throwOnNotAliased) + { + throw new ArgumentException("The field " + fieldInfo.GetNiceName() + " was not aliased."); + } + + return fieldInfo; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/FieldInfoExtensions.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/FieldInfoExtensions.cs.meta new file mode 100644 index 00000000..f3556584 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/FieldInfoExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 78ce67c0b3c1975c520a08d1ff9fd24e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/GarbageFreeIterators.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/GarbageFreeIterators.cs new file mode 100644 index 00000000..9c3653cb --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/GarbageFreeIterators.cs @@ -0,0 +1,330 @@ +//----------------------------------------------------------------------- +// <copyright file="GarbageFreeIterators.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Garbage free enumerator methods. + /// </summary> + public static class GarbageFreeIterators + { + /// <summary> + /// Garbage free enumerator for lists. + /// </summary> + public static ListIterator<T> GFIterator<T>(this List<T> list) + { + return new ListIterator<T>(list); + } + + /// <summary> + /// Garbage free enumerator for dictionaries. + /// </summary> + public static DictionaryIterator<T1, T2> GFIterator<T1, T2>(this Dictionary<T1, T2> dictionary) + { + return new DictionaryIterator<T1, T2>(dictionary); + } + + /// <summary> + /// Garbage free enumator for dictionary values. + /// </summary> + public static DictionaryValueIterator<T1, T2> GFValueIterator<T1, T2>(this Dictionary<T1, T2> dictionary) + { + return new DictionaryValueIterator<T1, T2>(dictionary); + } + + /// <summary> + /// Garbage free enumerator for hashsets. + /// </summary> + public static HashsetIterator<T> GFIterator<T>(this HashSet<T> hashset) + { + return new HashsetIterator<T>(hashset); + } + + /// <summary> + /// List iterator. + /// </summary> + public struct ListIterator<T> : IDisposable + { + private bool isNull; + private List<T> list; + private List<T>.Enumerator enumerator; + + /// <summary> + /// Creates a list iterator. + /// </summary> + public ListIterator(List<T> list) + { + this.isNull = list == null; + if (this.isNull) + { + this.list = null; + this.enumerator = new List<T>.Enumerator(); + } + else + { + this.list = list; + this.enumerator = this.list.GetEnumerator(); + } + } + + /// <summary> + /// Gets the enumerator. + /// </summary> + public ListIterator<T> GetEnumerator() + { + return this; + } + + /// <summary> + /// Gets the current value. + /// </summary> + public T Current + { + get + { + return this.enumerator.Current; + } + } + + /// <summary> + /// Moves to the next value. + /// </summary> + public bool MoveNext() + { + if (this.isNull) + { + return false; + } + return this.enumerator.MoveNext(); + } + + /// <summary> + /// Disposes the iterator. + /// </summary> + public void Dispose() + { + this.enumerator.Dispose(); + } + } + + /// <summary> + /// Hashset iterator. + /// </summary> + public struct HashsetIterator<T> : IDisposable + { + private bool isNull; + private HashSet<T> hashset; + private HashSet<T>.Enumerator enumerator; + + /// <summary> + /// Creates a hashset iterator. + /// </summary> + public HashsetIterator(HashSet<T> hashset) + { + this.isNull = hashset == null; + if (this.isNull) + { + this.hashset = null; + this.enumerator = new HashSet<T>.Enumerator(); + } + else + { + this.hashset = hashset; + this.enumerator = this.hashset.GetEnumerator(); + } + } + + /// <summary> + /// Gets the enumerator. + /// </summary> + public HashsetIterator<T> GetEnumerator() + { + return this; + } + + /// <summary> + /// Gets the current value. + /// </summary> + public T Current + { + get + { + return this.enumerator.Current; + } + } + + /// <summary> + /// Moves to the next value. + /// </summary> + public bool MoveNext() + { + if (this.isNull) + { + return false; + } + return this.enumerator.MoveNext(); + } + + /// <summary> + /// Disposes the iterator. + /// </summary> + public void Dispose() + { + this.enumerator.Dispose(); + } + } + + /// <summary> + /// Dictionary iterator. + /// </summary> + public struct DictionaryIterator<T1, T2> : IDisposable + { + private Dictionary<T1, T2> dictionary; + private Dictionary<T1, T2>.Enumerator enumerator; + private bool isNull; + + /// <summary> + /// Creates a dictionary iterator. + /// </summary> + public DictionaryIterator(Dictionary<T1, T2> dictionary) + { + this.isNull = dictionary == null; + + if (this.isNull) + { + this.dictionary = null; + this.enumerator = new Dictionary<T1, T2>.Enumerator(); + } + else + { + this.dictionary = dictionary; + this.enumerator = this.dictionary.GetEnumerator(); + } + } + + /// <summary> + /// Gets the enumerator. + /// </summary> + public DictionaryIterator<T1, T2> GetEnumerator() + { + return this; + } + + /// <summary> + /// Gets the current value. + /// </summary> + public KeyValuePair<T1, T2> Current + { + get + { + return this.enumerator.Current; + } + } + + /// <summary> + /// Moves to the next value. + /// </summary> + public bool MoveNext() + { + if (this.isNull) + { + return false; + } + return this.enumerator.MoveNext(); + } + + /// <summary> + /// Disposes the iterator. + /// </summary> + public void Dispose() + { + this.enumerator.Dispose(); + } + } + + /// <summary> + /// Dictionary value iterator. + /// </summary> + public struct DictionaryValueIterator<T1, T2> : IDisposable + { + private Dictionary<T1, T2> dictionary; + private Dictionary<T1, T2>.Enumerator enumerator; + private bool isNull; + + /// <summary> + /// Creates a dictionary value iterator. + /// </summary> + public DictionaryValueIterator(Dictionary<T1, T2> dictionary) + { + this.isNull = dictionary == null; + + if (this.isNull) + { + this.dictionary = null; + this.enumerator = new Dictionary<T1, T2>.Enumerator(); + } + else + { + this.dictionary = dictionary; + this.enumerator = this.dictionary.GetEnumerator(); + } + } + + /// <summary> + /// Gets the enumerator. + /// </summary> + public DictionaryValueIterator<T1, T2> GetEnumerator() + { + return this; + } + + /// <summary> + /// Gets the current value. + /// </summary> + public T2 Current + { + get + { + return this.enumerator.Current.Value; + } + } + + /// <summary> + /// Moves to the next value. + /// </summary> + public bool MoveNext() + { + if (this.isNull) + { + return false; + } + return this.enumerator.MoveNext(); + } + + /// <summary> + /// Disposes the iterator. + /// </summary> + public void Dispose() + { + this.enumerator.Dispose(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/GarbageFreeIterators.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/GarbageFreeIterators.cs.meta new file mode 100644 index 00000000..d47adba1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/GarbageFreeIterators.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 068f5645a5c3f9ce36a580ec57e775d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/LinqExtensions.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/LinqExtensions.cs new file mode 100644 index 00000000..29c8d1a5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/LinqExtensions.cs @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------- +// <copyright file="LinqExtensions.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Various LinQ extensions. + /// </summary> + public static class LinqExtensions + { + /// <summary> + /// Perform an action on each item. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="action">The action to perform.</param> + public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action) + { + foreach (var item in source) + { + action(item); + } + + return source; + } + + /// <summary> + /// Perform an action on each item. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="action">The action to perform.</param> + public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T, int> action) + { + int counter = 0; + + foreach (var item in source) + { + action(item, counter++); + } + + return source; + } + + /// <summary> + /// Add a collection to the end of another collection. + /// </summary> + /// <param name="source">The collection.</param> + /// <param name="append">The collection to append.</param> + public static IEnumerable<T> Append<T>(this IEnumerable<T> source, IEnumerable<T> append) + { + foreach (var item in source) + { + yield return item; + } + + foreach (var item in append) + { + yield return item; + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/LinqExtensions.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/LinqExtensions.cs.meta new file mode 100644 index 00000000..1afa9a89 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/LinqExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f84614827ff91701149564447a3932b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MemberInfoExtensions.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MemberInfoExtensions.cs new file mode 100644 index 00000000..eea7bef9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MemberInfoExtensions.cs @@ -0,0 +1,254 @@ +//----------------------------------------------------------------------- +// <copyright file="MemberInfoExtensions.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Globalization; + + /// <summary> + /// MemberInfo method extensions. + /// </summary> + public static class MemberInfoExtensions + { + /// <summary> + /// Returns true if the attribute whose type is specified by the generic argument is defined on this member + /// </summary> + public static bool IsDefined<T>(this ICustomAttributeProvider member, bool inherit) where T : Attribute + { + try + { + return member.IsDefined(typeof(T), inherit); + } + catch + { + return false; + } + } + + /// <summary> + /// Returns true if the attribute whose type is specified by the generic argument is defined on this member + /// </summary> + public static bool IsDefined<T>(this ICustomAttributeProvider member) where T : Attribute + { + return IsDefined<T>(member, false); + } + + /// <summary> + /// Returns the first found custom attribute of type T on this member + /// Returns null if none was found + /// </summary> + public static T GetAttribute<T>(this ICustomAttributeProvider member, bool inherit) where T : Attribute + { + var all = GetAttributes<T>(member, inherit).ToArray(); + return (all == null || all.Length == 0) ? null : all[0]; + } + + /// <summary> + /// Returns the first found non-inherited custom attribute of type T on this member + /// Returns null if none was found + /// </summary> + public static T GetAttribute<T>(this ICustomAttributeProvider member) where T : Attribute + { + return GetAttribute<T>(member, false); + } + + /// <summary> + /// Gets all attributes of the specified generic type. + /// </summary> + /// <param name="member">The member.</param> + public static IEnumerable<T> GetAttributes<T>(this ICustomAttributeProvider member) where T : Attribute + { + return GetAttributes<T>(member, false); + } + + /// <summary> + /// Gets all attributes of the specified generic type. + /// </summary> + /// <param name="member">The member.</param> + /// <param name="inherit">If true, specifies to also search the ancestors of element for custom attributes.</param> + public static IEnumerable<T> GetAttributes<T>(this ICustomAttributeProvider member, bool inherit) where T : Attribute + { + try + { + return member.GetCustomAttributes(typeof(T), inherit).Cast<T>(); + } + catch + { + return new T[0]; + } + } + + /// <summary> + /// Gets all attribute instances defined on a MemeberInfo. + /// </summary> + /// <param name="member">The member.</param> + public static Attribute[] GetAttributes(this ICustomAttributeProvider member) + { + try + { + return member.GetAttributes<Attribute>().ToArray(); + } + catch + { + return new Attribute[0]; + } + } + + /// <summary> + /// Gets all attribute instances on a MemberInfo. + /// </summary> + /// <param name="member">The member.</param> + /// <param name="inherit">If true, specifies to also search the ancestors of element for custom attributes.</param> + public static Attribute[] GetAttributes(this ICustomAttributeProvider member, bool inherit) + { + try + { + return member.GetAttributes<Attribute>(inherit).ToArray(); + } + catch + { + return new Attribute[0]; + } + } + + /// <summary> + /// If this member is a method, returns the full method name (name + params) otherwise the member name paskal splitted + /// </summary> + public static string GetNiceName(this MemberInfo member) + { + var method = member as MethodBase; + string result; + if (method != null) + { + result = method.GetFullName(); + } + else + { + result = member.Name; + } + + return result.ToTitleCase(); + } + + /// <summary> + /// Determines whether a FieldInfo, PropertyInfo or MethodInfo is static. + /// </summary> + /// <param name="member">The member.</param> + /// <returns> + /// <c>true</c> if the specified member is static; otherwise, <c>false</c>. + /// </returns> + /// <exception cref="System.NotSupportedException"></exception> + public static bool IsStatic(this MemberInfo member) + { + var field = member as FieldInfo; + if (field != null) + { + return field.IsStatic; + } + + var property = member as PropertyInfo; + if (property != null) + { + return property.CanRead ? property.GetGetMethod(true).IsStatic : property.GetSetMethod(true).IsStatic; + } + + var method = member as MethodBase; + if (method != null) + { + return method.IsStatic; + } + + var @event = member as EventInfo; + if (@event != null) + { + return @event.GetRaiseMethod(true).IsStatic; + } + + var type = member as Type; + if (type != null) + { + return type.IsSealed && type.IsAbstract; + } + + string message = string.Format( + CultureInfo.InvariantCulture, + "Unable to determine IsStatic for member {0}.{1}" + + "MemberType was {2} but only fields, properties and methods are supported.", + member.DeclaringType.FullName, + member.Name, + member.GetType().FullName); + + throw new NotSupportedException(message); + } + + /// <summary> + /// Determines whether the specified member is an alias. + /// </summary> + /// <param name="memberInfo">The member to check.</param> + /// <returns> + /// <c>true</c> if the specified member is an alias; otherwise, <c>false</c>. + /// </returns> + public static bool IsAlias(this MemberInfo memberInfo) + { + return memberInfo is MemberAliasFieldInfo + || memberInfo is MemberAliasPropertyInfo + || memberInfo is MemberAliasMethodInfo; + } + + /// <summary> + /// Returns the original, backing member of an alias member if the member is an alias. + /// </summary> + /// <param name="memberInfo">The member to check.</param> + /// /// <param name="throwOnNotAliased">if set to <c>true</c> an exception will be thrown if the member is not aliased.</param> + /// <returns></returns> + /// <exception cref="System.ArgumentException">The member was not aliased; this only occurs if throwOnNotAliased is true.</exception> + public static MemberInfo DeAlias(this MemberInfo memberInfo, bool throwOnNotAliased = false) + { + MemberAliasFieldInfo aliasFieldInfo = memberInfo as MemberAliasFieldInfo; + + if (aliasFieldInfo != null) + { + return aliasFieldInfo.AliasedField; + } + + MemberAliasPropertyInfo aliasPropertyInfo = memberInfo as MemberAliasPropertyInfo; + + if (aliasPropertyInfo != null) + { + return aliasPropertyInfo.AliasedProperty; + } + + MemberAliasMethodInfo aliasMethodInfo = memberInfo as MemberAliasMethodInfo; + + if (aliasMethodInfo != null) + { + return aliasMethodInfo.AliasedMethod; + } + + if (throwOnNotAliased) + { + throw new ArgumentException("The member " + memberInfo.GetNiceName() + " was not aliased."); + } + + return memberInfo; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MemberInfoExtensions.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MemberInfoExtensions.cs.meta new file mode 100644 index 00000000..683ed7a3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MemberInfoExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62088a9c188c943eb4035de16eb6ec32 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MethodInfoExtensions.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MethodInfoExtensions.cs new file mode 100644 index 00000000..91eba202 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MethodInfoExtensions.cs @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------- +// <copyright file="MethodInfoExtensions.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Linq; + using System.Reflection; + using System.Runtime.CompilerServices; + using System.Text; + + /// <summary> + /// Various extensions for MethodInfo. + /// </summary> + public static class MethodInfoExtensions + { + /// <summary> + /// Returns the specified method's full name "methodName(argType1 arg1, argType2 arg2, etc)" + /// Uses the specified gauntlet to replaces type names, ex: "int" instead of "Int32" + /// </summary> + public static string GetFullName(this MethodBase method, string extensionMethodPrefix) + { + var builder = new StringBuilder(); + bool isExtensionMethod = method.IsExtensionMethod(); + + if (isExtensionMethod) + { + builder.Append(extensionMethodPrefix); + } + + builder.Append(method.Name); + builder.Append("("); + builder.Append(method.GetParamsNames()); + builder.Append(")"); + return builder.ToString(); + } + + /// <summary> + /// Returns a string representing the passed method parameters names. Ex "int num, float damage, Transform target" + /// </summary> + public static string GetParamsNames(this MethodBase method) + { + ParameterInfo[] pinfos = method.IsExtensionMethod() ? method.GetParameters().Skip(1).ToArray() : method.GetParameters(); + var builder = new StringBuilder(); + + for (int i = 0, len = pinfos.Length; i < len; i++) + { + var param = pinfos[i]; + var paramTypeName = param.ParameterType.GetNiceName(); + builder.Append(paramTypeName); + builder.Append(" "); + builder.Append(param.Name); + + if (i < len - 1) + { + builder.Append(", "); + } + } + + return builder.ToString(); + } + + /// <summary> + /// Returns the specified method's full name. + /// </summary> + public static string GetFullName(this MethodBase method) + { + return GetFullName(method, "[ext] "); + } + + /// <summary> + /// Tests if a method is an extension method. + /// </summary> + public static bool IsExtensionMethod(this MethodBase method) + { + var type = method.DeclaringType; + return type.IsSealed && + !type.IsGenericType && + !type.IsNested && + method.IsDefined(typeof(ExtensionAttribute), false); + } + + /// <summary> + /// Determines whether the specified method is an alias. + /// </summary> + /// <param name="methodInfo">The method to check.</param> + /// <returns> + /// <c>true</c> if the specified method is an alias; otherwise, <c>false</c>. + /// </returns> + public static bool IsAliasMethod(this MethodInfo methodInfo) + { + return methodInfo is MemberAliasMethodInfo; + } + + /// <summary> + /// Returns the original, backing method of an alias method if the method is an alias. + /// </summary> + /// <param name="methodInfo">The method to check.</param> + /// /// <param name="throwOnNotAliased">if set to <c>true</c> an exception will be thrown if the method is not aliased.</param> + /// <returns></returns> + /// <exception cref="System.ArgumentException">The method was not aliased; this only occurs if throwOnNotAliased is true.</exception> + public static MethodInfo DeAliasMethod(this MethodInfo methodInfo, bool throwOnNotAliased = false) + { + MemberAliasMethodInfo aliasMethodInfo = methodInfo as MemberAliasMethodInfo; + + if (aliasMethodInfo != null) + { + while (aliasMethodInfo.AliasedMethod is MemberAliasMethodInfo) + { + aliasMethodInfo = aliasMethodInfo.AliasedMethod as MemberAliasMethodInfo; + } + + return aliasMethodInfo.AliasedMethod; + } + + if (throwOnNotAliased) + { + throw new ArgumentException("The method " + methodInfo.GetNiceName() + " was not aliased."); + } + + return methodInfo; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MethodInfoExtensions.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MethodInfoExtensions.cs.meta new file mode 100644 index 00000000..0c7f3001 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/MethodInfoExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 63a9a0384a6fe66fb04f82f325895b30 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/Operator.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/Operator.cs new file mode 100644 index 00000000..1d9dd605 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/Operator.cs @@ -0,0 +1,126 @@ +//----------------------------------------------------------------------- +// <copyright file="Operator.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + /// <summary> + /// Determines the type of operator. + /// </summary> + /// <seealso cref="TypeExtensions" /> + public enum Operator + { + /// <summary> + /// The == operator. + /// </summary> + Equality, + + /// <summary> + /// The != operator. + /// </summary> + Inequality, + + /// <summary> + /// The + operator. + /// </summary> + Addition, + + /// <summary> + /// The - operator. + /// </summary> + Subtraction, + + /// <summary> + /// The * operator. + /// </summary> + Multiply, + + /// <summary> + /// The / operator. + /// </summary> + Division, + + /// <summary> + /// The < operator. + /// </summary> + LessThan, + + /// <summary> + /// The > operator. + /// </summary> + GreaterThan, + + /// <summary> + /// The <= operator. + /// </summary> + LessThanOrEqual, + + /// <summary> + /// The >= operator. + /// </summary> + GreaterThanOrEqual, + + /// <summary> + /// The % operator. + /// </summary> + Modulus, + + /// <summary> + /// The >> operator. + /// </summary> + RightShift, + + /// <summary> + /// The << operator. + /// </summary> + LeftShift, + + /// <summary> + /// The & operator. + /// </summary> + BitwiseAnd, + + /// <summary> + /// The | operator. + /// </summary> + BitwiseOr, + + /// <summary> + /// The ^ operator. + /// </summary> + ExclusiveOr, + + /// <summary> + /// The ~ operator. + /// </summary> + BitwiseComplement, + + /// <summary> + /// The && operator. + /// </summary> + LogicalAnd, + + /// <summary> + /// The || operator. + /// </summary> + LogicalOr, + + /// <summary> + /// The ! operator. + /// </summary> + LogicalNot, + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/Operator.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/Operator.cs.meta new file mode 100644 index 00000000..b3e287b3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/Operator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1df9513f03131466eecad22d1b19c4d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PathUtilities.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PathUtilities.cs new file mode 100644 index 00000000..7bcdfecf --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PathUtilities.cs @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------- +// <copyright file="PathUtilities.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.IO; + using System.Text; + + /// <summary> + /// DirectoryInfo method extensions. + /// </summary> + public static class PathUtilities + { + /// <summary> + /// Determines whether the directory has a given directory in its hierarchy of children. + /// </summary> + /// <param name="parentDir">The parent directory.</param> + /// <param name="subDir">The sub directory.</param> + public static bool HasSubDirectory(this DirectoryInfo parentDir, DirectoryInfo subDir) + { + var parentDirName = parentDir.FullName.TrimEnd('\\', '/'); + + while (subDir != null) + { + if (subDir.FullName.TrimEnd('\\', '/') == parentDirName) + { + return true; + } + else + { + subDir = subDir.Parent; + } + } + + return false; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PathUtilities.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PathUtilities.cs.meta new file mode 100644 index 00000000..c50e1780 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PathUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da8aea12015a2df5402c9e2d4f1cec5c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PropertyInfoExtensions.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PropertyInfoExtensions.cs new file mode 100644 index 00000000..09b175c7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PropertyInfoExtensions.cs @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------- +// <copyright file="PropertyInfoExtensions.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Reflection; + + /// <summary> + /// PropertyInfo method extensions. + /// </summary> + public static class PropertyInfoExtensions + { + /// <summary> + /// Determines whether a property is an auto property with a usable getter and setter. + /// </summary> + public static bool IsAutoProperty(this PropertyInfo propInfo, bool allowVirtual = false) + { + if (!(propInfo.CanWrite && propInfo.CanRead)) + { + return false; + } + + if (!allowVirtual) + { + var getter = propInfo.GetGetMethod(true); + var setter = propInfo.GetSetMethod(true); + + if ((getter != null && (getter.IsAbstract || getter.IsVirtual)) || (setter != null && (setter.IsAbstract || setter.IsVirtual))) + { + return false; + } + } + + var flag = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; + string compilerGeneratedName = "<" + propInfo.Name + ">"; + var fields = propInfo.DeclaringType.GetFields(flag); + + for (int i = 0; i < fields.Length; i++) + { + if (fields[i].Name.Contains(compilerGeneratedName)) + { + return true; + } + } + + return false; + } + + /// <summary> + /// Determines whether the specified property is an alias. + /// </summary> + /// <param name="propertyInfo">The property to check.</param> + /// <returns> + /// <c>true</c> if the specified property is an alias; otherwise, <c>false</c>. + /// </returns> + public static bool IsAliasProperty(this PropertyInfo propertyInfo) + { + return propertyInfo is MemberAliasPropertyInfo; + } + + /// <summary> + /// Returns the original, backing property of an alias property if the property is an alias. + /// </summary> + /// <param name="propertyInfo">The property to check.</param> + /// /// <param name="throwOnNotAliased">if set to <c>true</c> an exception will be thrown if the property is not aliased.</param> + /// <returns></returns> + /// <exception cref="System.ArgumentException">The property was not aliased; this only occurs if throwOnNotAliased is true.</exception> + public static PropertyInfo DeAliasProperty(this PropertyInfo propertyInfo, bool throwOnNotAliased = false) + { + MemberAliasPropertyInfo aliasPropertyInfo = propertyInfo as MemberAliasPropertyInfo; + + if (aliasPropertyInfo != null) + { + while (aliasPropertyInfo.AliasedProperty is MemberAliasPropertyInfo) + { + aliasPropertyInfo = aliasPropertyInfo.AliasedProperty as MemberAliasPropertyInfo; + } + + return aliasPropertyInfo.AliasedProperty; + } + + if (throwOnNotAliased) + { + throw new ArgumentException("The property " + propertyInfo.GetNiceName() + " was not aliased."); + } + + return propertyInfo; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PropertyInfoExtensions.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PropertyInfoExtensions.cs.meta new file mode 100644 index 00000000..1c704a93 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/PropertyInfoExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f13450d6fd82372ffa7ee075a8eb4c9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/StringExtensions.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/StringExtensions.cs new file mode 100644 index 00000000..0bb70b20 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/StringExtensions.cs @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------- +// <copyright file="StringExtensions.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Globalization; + using System.Text; + + /// <summary> + /// String method extensions. + /// </summary> + public static class StringExtensions + { + /// <summary> + /// Eg MY_INT_VALUE => MyIntValue + /// </summary> + public static string ToTitleCase(this string input) + { + var builder = new StringBuilder(); + for (int i = 0; i < input.Length; i++) + { + var current = input[i]; + if (current == '_' && i + 1 < input.Length) + { + var next = input[i + 1]; + if (char.IsLower(next)) + { + next = char.ToUpper(next, CultureInfo.InvariantCulture); + } + + builder.Append(next); + i++; + } + else + { + builder.Append(current); + } + } + + return builder.ToString(); + } + + /// <summary> + /// Returns true if this string is null, empty, or contains only whitespace. + /// </summary> + /// <param name="str">The string to check.</param> + /// <returns><c>true</c> if this string is null, empty, or contains only whitespace; otherwise, <c>false</c>.</returns> + public static bool IsNullOrWhitespace(this string str) + { + if (!string.IsNullOrEmpty(str)) + { + for (int i = 0; i < str.Length; i++) + { + if (char.IsWhiteSpace(str[i]) == false) + { + return false; + } + } + } + + return true; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/StringExtensions.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/StringExtensions.cs.meta new file mode 100644 index 00000000..53d6b4a6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/StringExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b554cbd9469011b544a2d92ae85a3b60 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/TypeExtensions.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/TypeExtensions.cs new file mode 100644 index 00000000..b797a1f4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/TypeExtensions.cs @@ -0,0 +1,2368 @@ +//----------------------------------------------------------------------- +// <copyright file="TypeExtensions.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Reflection; + using System.Text; + using UnityEngine; + + /// <summary> + /// Type method extensions. + /// </summary> + public static class TypeExtensions + { + private static readonly Func<float, float, bool> FloatEqualityComparerFunc = FloatEqualityComparer; + private static readonly Func<double, double, bool> DoubleEqualityComparerFunc = DoubleEqualityComparer; + private static readonly Func<Quaternion, Quaternion, bool> QuaternionEqualityComparerFunc = QuaternionEqualityComparer; + + private static readonly object GenericConstraintsSatisfaction_LOCK = new object(); + private static readonly Dictionary<Type, Type> GenericConstraintsSatisfactionInferredParameters = new Dictionary<Type, Type>(); + private static readonly Dictionary<Type, Type> GenericConstraintsSatisfactionResolvedMap = new Dictionary<Type, Type>(); + private static readonly HashSet<Type> GenericConstraintsSatisfactionProcessedParams = new HashSet<Type>(); + + private static readonly Type GenericListInterface = typeof(IList<>); + private static readonly Type GenericCollectionInterface = typeof(ICollection<>); + + private static readonly object WeaklyTypedTypeCastDelegates_LOCK = new object(); + private static readonly object StronglyTypedTypeCastDelegates_LOCK = new object(); + private static readonly DoubleLookupDictionary<Type, Type, Func<object, object>> WeaklyTypedTypeCastDelegates = new DoubleLookupDictionary<Type, Type, Func<object, object>>(); + private static readonly DoubleLookupDictionary<Type, Type, Delegate> StronglyTypedTypeCastDelegates = new DoubleLookupDictionary<Type, Type, Delegate>(); + + private static readonly Type[] TwoLengthTypeArray_Cached = new Type[2]; + + private static readonly Stack<Type> GenericArgumentsContainsTypes_ArgsToCheckCached = new Stack<Type>(); + + private static HashSet<string> ReservedCSharpKeywords = new HashSet<string>() + { + "abstract", + "as", + "base", + "bool", + "break", + "byte", + "case", + "catch", + "char", + "checked", + "class", + "const", + "continue", + "decimal", + "default", + "delegate", + "do", + "double", + "else", + "enum", + "event", + "explicit", + "extern", + "false", + "finally", + "fixed", + "float", + "for", + "foreach", + "goto", + "if", + "implicit", + "in", + "int", + "interface", + "internal", + "is", + "lock", + "long", + "namespace", + "new", + "null", + "object", + "operator", + "out", + "override", + "params", + "private", + "protected", + "public", + "readonly", + "ref", + "return", + "sbyte", + "sealed", + "short", + "sizeof", + "stackalloc", + "static", + "string", + "struct", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "uint", + "ulong", + "unchecked", + "unsafe", + "ushort", + "using", + "static", + "void", + "volatile", + "while", + "in", + "get", + "set", + "var", + //"async", // Identifiers can be named async and await + //"await", + }; + + /// <summary> + /// Type name alias lookup. + /// TypeNameAlternatives["Single"] will give you "float", "UInt16" will give you "ushort", "Boolean[]" will give you "bool[]" etc.. + /// </summary> + public static readonly Dictionary<string, string> TypeNameAlternatives = new Dictionary<string, string>() + { + { "Single", "float" }, + { "Double", "double" }, + { "SByte", "sbyte" }, + { "Int16", "short" }, + { "Int32", "int" }, + { "Int64", "long" }, + { "Byte", "byte" }, + { "UInt16", "ushort" }, + { "UInt32", "uint" }, + { "UInt64", "ulong" }, + { "Decimal", "decimal" }, + { "String", "string" }, + { "Char", "char" }, + { "Boolean", "bool" }, + { "Single[]", "float[]" }, + { "Double[]", "double[]" }, + { "SByte[]", "sbyte[]" }, + { "Int16[]", "short[]" }, + { "Int32[]", "int[]" }, + { "Int64[]", "long[]" }, + { "Byte[]", "byte[]" }, + { "UInt16[]", "ushort[]" }, + { "UInt32[]", "uint[]" }, + { "UInt64[]", "ulong[]" }, + { "Decimal[]", "decimal[]" }, + { "String[]", "string[]" }, + { "Char[]", "char[]" }, + { "Boolean[]", "bool[]" }, + }; + + private static readonly object CachedNiceNames_LOCK = new object(); + private static readonly Dictionary<Type, string> CachedNiceNames = new Dictionary<Type, string>(); + + private static string GetCachedNiceName(Type type) + { + string result; + lock (CachedNiceNames_LOCK) + { + if (!CachedNiceNames.TryGetValue(type, out result)) + { + result = CreateNiceName(type); + CachedNiceNames.Add(type, result); + } + } + return result; + } + + private static string CreateNiceName(Type type) + { + if (type.IsArray) + { + int rank = type.GetArrayRank(); + return type.GetElementType().GetNiceName() + (rank == 1 ? "[]" : "[,]"); + } + + if (type.InheritsFrom(typeof(Nullable<>))) + { + return type.GetGenericArguments()[0].GetNiceName() + "?"; + } + + if (type.IsByRef) + { + return "ref " + type.GetElementType().GetNiceName(); + } + + if (type.IsGenericParameter || !type.IsGenericType) + { + return TypeNameGauntlet(type); + } + + var builder = new StringBuilder(); + var name = type.Name; + var index = name.IndexOf("`"); + + if (index != -1) + { + builder.Append(name.Substring(0, index)); + } + else + { + builder.Append(name); + } + + builder.Append('<'); + var args = type.GetGenericArguments(); + + for (int i = 0; i < args.Length; i++) + { + var arg = args[i]; + + if (i != 0) + { + builder.Append(", "); + } + + builder.Append(GetNiceName(arg)); + } + + builder.Append('>'); + return builder.ToString(); + } + + private static readonly Type VoidPointerType = typeof(void).MakePointerType(); + + private static readonly Dictionary<Type, HashSet<Type>> PrimitiveImplicitCasts = new Dictionary<Type, HashSet<Type>>() + { + { typeof(Int64), new HashSet<Type>() { typeof(Single), typeof(Double), typeof(Decimal) } }, + { typeof(Int32), new HashSet<Type>() { typeof(Int64), typeof(Single), typeof(Double), typeof(Decimal) } }, + { typeof(Int16), new HashSet<Type>() { typeof(Int32), typeof(Int64), typeof(Single), typeof(Double), typeof(Decimal) } }, + { typeof(SByte), new HashSet<Type>() { typeof(Int16), typeof(Int32), typeof(Int64), typeof(Single), typeof(Double), typeof(Decimal) } }, + { typeof(UInt64), new HashSet<Type>() { typeof(Single), typeof(Double), typeof(Decimal) } }, + { typeof(UInt32), new HashSet<Type>() { typeof(Int64), typeof(UInt64), typeof(Single), typeof(Double), typeof(Decimal) } }, + { typeof(UInt16), new HashSet<Type>() { typeof(Int32), typeof(UInt32), typeof(Int64), typeof(UInt64), typeof(Single), typeof(Double), typeof(Decimal) } }, + { typeof(Byte), new HashSet<Type>() { typeof(Int16), typeof(UInt16), typeof(Int32), typeof(UInt32), typeof(Int64), typeof(UInt64), typeof(Single), typeof(Double), typeof(Decimal) } }, + { typeof(Char), new HashSet<Type>() { typeof(UInt16), typeof(Int32), typeof(UInt32), typeof(Int64), typeof(UInt64), typeof(Single), typeof(Double), typeof(Decimal) } }, + { typeof(Boolean), new HashSet<Type>() { } }, + { typeof(Decimal), new HashSet<Type>() { } }, + { typeof(Single), new HashSet<Type>() { typeof(Double) } }, + { typeof(Double), new HashSet<Type>() { } }, + { typeof(IntPtr), new HashSet<Type>() { } }, + { typeof(UIntPtr), new HashSet<Type>() { } }, + { VoidPointerType, new HashSet<Type>() { } }, + }; + + private static readonly HashSet<Type> ExplicitCastIntegrals = new HashSet<Type>() + { + { typeof(Int64) }, + { typeof(Int32) }, + { typeof(Int16) }, + { typeof(SByte) }, + { typeof(UInt64) }, + { typeof(UInt32) }, + { typeof(UInt16) }, + { typeof(Byte) }, + { typeof(Char) }, + { typeof(Decimal) }, + { typeof(Single) }, + { typeof(Double) }, + { typeof(IntPtr) }, + { typeof(UIntPtr) } + }; + + internal static bool HasCastDefined(this Type from, Type to, bool requireImplicitCast) + { + if (from.IsEnum) + { + return Enum.GetUnderlyingType(from).IsCastableTo(to); + } + + if (to.IsEnum) + { + return Enum.GetUnderlyingType(to).IsCastableTo(from); + } + + if ((from.IsPrimitive || from == VoidPointerType) && (to.IsPrimitive || to == VoidPointerType)) + { + if (requireImplicitCast) + { + return PrimitiveImplicitCasts[from].Contains(to); + } + else + { + if (from == typeof(IntPtr)) + { + if (to == typeof(UIntPtr)) + { + return false; + } + else if (to == VoidPointerType) + { + return true; + } + } + else if (from == typeof(UIntPtr)) + { + if (to == typeof(IntPtr)) + { + return false; + } + else if (to == VoidPointerType) + { + return true; + } + } + + return ExplicitCastIntegrals.Contains(from) && ExplicitCastIntegrals.Contains(to); + } + } + + return from.GetCastMethod(to, requireImplicitCast) != null; + } + + /// <summary> + /// Checks whether a given string is a valid CSharp identifier name. This also checks full type names including namespaces. + /// </summary> + /// <param name="identifier">The identifier to check.</param> + public static bool IsValidIdentifier(string identifier) + { + if (identifier == null || identifier.Length == 0) + { + return false; + } + + int dotIndex = identifier.IndexOf('.'); + + if (dotIndex >= 0) + { + string[] identifiers = identifier.Split('.'); + + for (int i = 0; i < identifiers.Length; i++) + { + if (!IsValidIdentifier(identifiers[i])) + { + return false; + } + } + + return true; + } + + if (ReservedCSharpKeywords.Contains(identifier)) + { + return false; + } + + if (!IsValidIdentifierStartCharacter(identifier[0])) + { + return false; + } + + for (int i = 1; i < identifier.Length; i++) + { + if (!IsValidIdentifierPartCharacter(identifier[i])) + { + return false; + } + } + + return true; + } + + private static bool IsValidIdentifierStartCharacter(char c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '@' || char.IsLetter(c); + } + + private static bool IsValidIdentifierPartCharacter(char c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') || char.IsLetter(c); + } + + /// <summary> + /// Determines whether a type can be casted to another type. + /// </summary> + /// <param name="from">From.</param> + /// <param name="to">To.</param> + /// <param name="requireImplicitCast">if set to <c>true</c> an implicit or explicit operator must be defined on the given type.</param> + public static bool IsCastableTo(this Type from, Type to, bool requireImplicitCast = false) + { + if (from == null) + { + throw new ArgumentNullException("from"); + } + + if (to == null) + { + throw new ArgumentNullException("to"); + } + + if (from == to) + { + return true; + } + + return to.IsAssignableFrom(from) || from.HasCastDefined(to, requireImplicitCast); + } + + /// <summary> + /// If a type can be casted to another type, this provides a function to manually convert the type. + /// </summary> + /// <param name="from">From.</param> + /// <param name="to">To.</param> + /// <param name="requireImplicitCast">if set to <c>true</c> an implicit or explicit operator must be defined on the given type.</param> + public static Func<object, object> GetCastMethodDelegate(this Type from, Type to, bool requireImplicitCast = false) + { + Func<object, object> result; + + lock (WeaklyTypedTypeCastDelegates_LOCK) + { + if (WeaklyTypedTypeCastDelegates.TryGetInnerValue(from, to, out result) == false) + { + var method = GetCastMethod(from, to, requireImplicitCast); + + if (method != null) + { + result = (obj) => method.Invoke(null, new object[] { obj }); + } + + WeaklyTypedTypeCastDelegates.AddInner(from, to, result); + } + } + + return result; + } + + /// <summary> + /// If a type can be casted to another type, this provides a function to manually convert the type. + /// </summary> + /// <param name="requireImplicitCast">if set to <c>true</c> an implicit or explicit operator must be defined on the given type.</param> + public static Func<TFrom, TTo> GetCastMethodDelegate<TFrom, TTo>(bool requireImplicitCast = false) + { + Delegate del; + + lock (StronglyTypedTypeCastDelegates_LOCK) + { + if (StronglyTypedTypeCastDelegates.TryGetInnerValue(typeof(TFrom), typeof(TTo), out del) == false) + { + var method = GetCastMethod(typeof(TFrom), typeof(TTo), requireImplicitCast); + + if (method != null) + { + del = Delegate.CreateDelegate(typeof(Func<TFrom, TTo>), method); + } + + StronglyTypedTypeCastDelegates.AddInner(typeof(TFrom), typeof(TTo), del); + } + } + + return (Func<TFrom, TTo>)del; + } + + /// <summary> + /// If a type can be casted to another type, this provides the method info of the method in charge of converting the type. + /// </summary> + /// <param name="from">From.</param> + /// <param name="to">To.</param> + /// <param name="requireImplicitCast">if set to <c>true</c> an implicit or explicit operator must be defined on the given type.</param> + public static MethodInfo GetCastMethod(this Type from, Type to, bool requireImplicitCast = false) + { + var fromMethods = from.GetAllMembers<MethodInfo>(BindingFlags.Public | BindingFlags.Static); + + foreach (var method in fromMethods) + { + if ((method.Name == "op_Implicit" || (requireImplicitCast == false && method.Name == "op_Explicit")) && to.IsAssignableFrom(method.ReturnType)) + { + return method; + } + } + + var toMethods = to.GetAllMembers<MethodInfo>(BindingFlags.Public | BindingFlags.Static); + + foreach (var method in toMethods) + { + if ((method.Name == "op_Implicit" || (requireImplicitCast == false && method.Name == "op_Explicit")) && method.GetParameters()[0].ParameterType.IsAssignableFrom(from)) + { + return method; + } + } + + return null; + } + + private static bool FloatEqualityComparer(float a, float b) + { + if (float.IsNaN(a) && float.IsNaN(b)) return true; + return a == b; + } + + private static bool DoubleEqualityComparer(double a, double b) + { + if (double.IsNaN(a) && double.IsNaN(b)) return true; + return a == b; + } + + private static bool QuaternionEqualityComparer(Quaternion a, Quaternion b) + { + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; + } + + /// <summary> + /// Gets an equality comparer delegate used to compare the equality of values of a given type. In order, this will be: + /// + /// 1. The == operator, if one is defined on the type. + /// 2. A delegate that uses <see cref="IEquatable{T}"/>, if the type implements that interface. + /// 3. .NET's own <see cref="EqualityComparer{T}.Default"/> + /// </summary> + /// <remarks> + /// <para>Note that in the special case of the type <see cref="UnityEngine.Quaternion"/>, a special equality comparer is returned that only checks whether all the Quaternion components are equal.</para> + /// <para>This is because, by default, Quaternion's equality operator is broken when operating on invalid quaternions; "default(Quaternion) == default(Quaternion)" evaluates to false, and this causes a multitude of problems.</para> + /// <para>Special delegates are also returned for float and double, that consider float.NaN to be equal to float.NaN, and double.NaN to be equal to double.NaN.</para> + /// </remarks> + public static Func<T, T, bool> GetEqualityComparerDelegate<T>() + { + if (typeof(T) == typeof(float)) + return (Func<T, T, bool>)(object)FloatEqualityComparerFunc; + else if (typeof(T) == typeof(double)) + return (Func<T, T, bool>)(object)DoubleEqualityComparerFunc; + else if (typeof(T) == typeof(Quaternion)) + return (Func<T, T, bool>)(object)QuaternionEqualityComparerFunc; + + Func<T, T, bool> result = null; + MethodInfo equalityMethod; + + if (typeof(IEquatable<T>).IsAssignableFrom(typeof(T))) + { + if (typeof(T).IsValueType) + { + result = (a, b) => + { + return ((IEquatable<T>)a).Equals(b); + }; + } + else + { + result = (a, b) => + { + if (object.ReferenceEquals(a, b)) + { + return true; + } + else if (object.ReferenceEquals(a, null)) + { + return false; + } + else + { + return ((IEquatable<T>)a).Equals(b); + } + }; + } + } + else + { + Type currentType = typeof(T); + + while (currentType != null && currentType != typeof(object)) + { + equalityMethod = currentType.GetOperatorMethod(Operator.Equality, currentType, currentType); + + if (equalityMethod != null) + { + result = (Func<T, T, bool>)Delegate.CreateDelegate(typeof(Func<T, T, bool>), equalityMethod, true); + break; + } + + currentType = currentType.BaseType; + } + } + + if (result == null) + { + var comparer = EqualityComparer<T>.Default; + result = comparer.Equals; + } + + return result; + } + + /// <summary> + /// Gets the first attribute of type T. Returns null in the no attribute of type T was found. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="inherit">If true, specifies to also search the ancestors of element for custom attributes.</param> + public static T GetAttribute<T>(this Type type, bool inherit) where T : Attribute + { + var attrs = type.GetCustomAttributes(typeof(T), inherit); + + if (attrs.Length == 0) + { + return null; + } + else + { + return (T)attrs[0]; + } + } + + /// <summary> + /// Determines whether a type implements or inherits from another type. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="to">To.</param> + public static bool ImplementsOrInherits(this Type type, Type to) + { + return to.IsAssignableFrom(type); + } + + /// <summary> + /// Determines whether a type implements an open generic interface or class such as IList<> or List<>. + /// </summary> + /// <param name="candidateType">Type of the candidate.</param> + /// <param name="openGenericType">Type of the open generic type.</param> + /// <returns></returns> + public static bool ImplementsOpenGenericType(this Type candidateType, Type openGenericType) + { + if (openGenericType.IsInterface) return candidateType.ImplementsOpenGenericInterface(openGenericType); + else return candidateType.ImplementsOpenGenericClass(openGenericType); + } + + /// <summary> + /// Determines whether a type implements an open generic interface such as IList<>. + /// </summary> + /// <param name="candidateType">Type of the candidate.</param> + /// <param name="openGenericInterfaceType">Type of the open generic interface.</param> + /// <exception cref="System.ArgumentNullException"></exception> + /// <exception cref="System.ArgumentException">Type " + openGenericInterfaceType.Name + " is not a generic type definition and an interface.</exception> + public static bool ImplementsOpenGenericInterface(this Type candidateType, Type openGenericInterfaceType) + { + if (candidateType == openGenericInterfaceType) + return true; + + if (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition() == openGenericInterfaceType) + return true; + + var interfaces = candidateType.GetInterfaces(); + + for (int i = 0; i < interfaces.Length; i++) + { + if (interfaces[i].ImplementsOpenGenericInterface(openGenericInterfaceType)) + return true; + } + + return false; + } + + /// <summary> + /// Determines whether a type implements an open generic class such as List<>. + /// </summary> + /// <param name="candidateType">Type of the candidate.</param> + /// <param name="openGenericType">Type of the open generic interface.</param> + public static bool ImplementsOpenGenericClass(this Type candidateType, Type openGenericType) + { + if (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition() == openGenericType) + return true; + + var baseType = candidateType.BaseType; + + if (baseType != null && baseType.ImplementsOpenGenericClass(openGenericType)) + return true; + + return false; + } + + /// <summary> + /// Gets the generic arguments of an inherited open generic class or interface. + /// </summary> + /// <param name="candidateType">Type of the candidate.</param> + /// <param name="openGenericType">The open generic type to get the arguments of.</param> + public static Type[] GetArgumentsOfInheritedOpenGenericType(this Type candidateType, Type openGenericType) + { + if (openGenericType.IsInterface) return candidateType.GetArgumentsOfInheritedOpenGenericInterface(openGenericType); + else return candidateType.GetArgumentsOfInheritedOpenGenericClass(openGenericType); + } + + /// <summary> + /// Gets the generic arguments of an inherited open generic class. + /// </summary> + /// <param name="candidateType">Type of the candidate.</param> + /// <param name="openGenericType">Type of the open generic class.</param> + public static Type[] GetArgumentsOfInheritedOpenGenericClass(this Type candidateType, Type openGenericType) + { + if (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition() == openGenericType) + return candidateType.GetGenericArguments(); + + var baseType = candidateType.BaseType; + + if (baseType != null) + return baseType.GetArgumentsOfInheritedOpenGenericClass(openGenericType); + + return null; + } + + /// <summary> + /// Gets the generic arguments of an inherited open generic interface. + /// </summary> + /// <param name="candidateType">Type of the candidate.</param> + /// <param name="openGenericInterfaceType">Type of the open generic interface.</param> + public static Type[] GetArgumentsOfInheritedOpenGenericInterface(this Type candidateType, Type openGenericInterfaceType) + { + // This if clause fixes an "error" in newer .NET Runtimes where enum arrays + // implement interfaces like IList<int>, which will be matched on by Odin + // before the IList<TheEnum> interface and cause a lot of issues because + // you can't actually use an enum array as if it was an IList<int>. + if ((openGenericInterfaceType == GenericListInterface || openGenericInterfaceType == GenericCollectionInterface) && candidateType.IsArray) + { + return new Type[] { candidateType.GetElementType() }; + } + + if (candidateType == openGenericInterfaceType) + return candidateType.GetGenericArguments(); + + if (candidateType.IsGenericType && candidateType.GetGenericTypeDefinition() == openGenericInterfaceType) + return candidateType.GetGenericArguments(); + + var interfaces = candidateType.GetInterfaces(); + + for (int i = 0; i < interfaces.Length; i++) + { + var @interface = interfaces[i]; + if (!@interface.IsGenericType) continue; + + var result = @interface.GetArgumentsOfInheritedOpenGenericInterface(openGenericInterfaceType); + + if (result != null) + return result; + } + + return null; + } + + /// <summary> + /// Gets the MethodInfo of a specific operator kind, with the given left and right operands. This overload is *far* faster than any of the other GetOperatorMethod implementations, and should be used whenever possible. + /// </summary> + public static MethodInfo GetOperatorMethod(this Type type, Operator op, Type leftOperand, Type rightOperand) + { + string methodName; + + switch (op) + { + case Operator.Equality: + methodName = "op_Equality"; + break; + + case Operator.Inequality: + methodName = "op_Inequality"; + break; + + case Operator.Addition: + methodName = "op_Addition"; + break; + + case Operator.Subtraction: + methodName = "op_Subtraction"; + break; + + case Operator.Multiply: + methodName = "op_Multiply"; + break; + + case Operator.Division: + methodName = "op_Division"; + break; + + case Operator.LessThan: + methodName = "op_LessThan"; + break; + + case Operator.GreaterThan: + methodName = "op_GreaterThan"; + break; + + case Operator.LessThanOrEqual: + methodName = "op_LessThanOrEqual"; + break; + + case Operator.GreaterThanOrEqual: + methodName = "op_GreaterThanOrEqual"; + break; + + case Operator.Modulus: + methodName = "op_Modulus"; + break; + + case Operator.RightShift: + methodName = "op_RightShift"; + break; + + case Operator.LeftShift: + methodName = "op_LeftShift"; + break; + + case Operator.BitwiseAnd: + methodName = "op_BitwiseAnd"; + break; + + case Operator.BitwiseOr: + methodName = "op_BitwiseOr"; + break; + + case Operator.ExclusiveOr: + methodName = "op_ExclusiveOr"; + break; + + case Operator.BitwiseComplement: + methodName = "op_OnesComplement"; + break; + + case Operator.LogicalNot: + methodName = "op_LogicalNot"; + break; + + case Operator.LogicalAnd: + case Operator.LogicalOr: + return null; // Not overridable + + default: + throw new NotImplementedException(); + } + + var types = TwoLengthTypeArray_Cached; + + lock (types) + { + types[0] = leftOperand; + types[1] = rightOperand; + + try + { + var result = type.GetMethod(methodName, Flags.StaticAnyVisibility, null, types, null); + + if (result != null && result.ReturnType != typeof(bool)) return null; + + return result; + } + catch (AmbiguousMatchException) + { + // We fallback to manual resolution + var methods = type.GetMethods(Flags.StaticAnyVisibility); + + for (int i = 0; i < methods.Length; i++) + { + var method = methods[i]; + if (method.Name != methodName) continue; + if (method.ReturnType != typeof(bool)) continue; + var parameters = method.GetParameters(); + if (parameters.Length != 2) continue; + if (!parameters[0].ParameterType.IsAssignableFrom(leftOperand)) continue; + if (!parameters[1].ParameterType.IsAssignableFrom(rightOperand)) continue; + + return method; + } + + return null; + } + } + } + + /// <summary> + /// Gets the MethodInfo of a specific operator type. + /// </summary> + public static MethodInfo GetOperatorMethod(this Type type, Operator op) + { + string methodName; + + switch (op) + { + case Operator.Equality: + methodName = "op_Equality"; + break; + + case Operator.Inequality: + methodName = "op_Inequality"; + break; + + case Operator.Addition: + methodName = "op_Addition"; + break; + + case Operator.Subtraction: + methodName = "op_Subtraction"; + break; + + case Operator.Multiply: + methodName = "op_Multiply"; + break; + + case Operator.Division: + methodName = "op_Division"; + break; + + case Operator.LessThan: + methodName = "op_LessThan"; + break; + + case Operator.GreaterThan: + methodName = "op_GreaterThan"; + break; + + case Operator.LessThanOrEqual: + methodName = "op_LessThanOrEqual"; + break; + + case Operator.GreaterThanOrEqual: + methodName = "op_GreaterThanOrEqual"; + break; + + case Operator.Modulus: + methodName = "op_Modulus"; + break; + + case Operator.RightShift: + methodName = "op_RightShift"; + break; + + case Operator.LeftShift: + methodName = "op_LeftShift"; + break; + + case Operator.BitwiseAnd: + methodName = "op_BitwiseAnd"; + break; + + case Operator.BitwiseOr: + methodName = "op_BitwiseOr"; + break; + + case Operator.ExclusiveOr: + methodName = "op_ExclusiveOr"; + break; + + case Operator.BitwiseComplement: + methodName = "op_OnesComplement"; + break; + + case Operator.LogicalNot: + methodName = "op_LogicalNot"; + break; + + case Operator.LogicalAnd: + case Operator.LogicalOr: + return null; // Not overridable + + default: + throw new NotImplementedException(); + } + + return type.GetAllMembers<MethodInfo>(Flags.StaticAnyVisibility).FirstOrDefault(m => m.Name == methodName); + } + + /// <summary> + /// Gets the MethodInfo of a specific operator type. + /// </summary> + public static MethodInfo[] GetOperatorMethods(this Type type, Operator op) + { + string methodName; + + switch (op) + { + // TODO: Add Divide and other names for other .Net versions + case Operator.Equality: + methodName = "op_Equality"; + break; + + case Operator.Inequality: + methodName = "op_Inequality"; + break; + + case Operator.Addition: + methodName = "op_Addition"; + break; + + case Operator.Subtraction: + methodName = "op_Subtraction"; + break; + + case Operator.Multiply: + methodName = "op_Multiply"; + break; + + case Operator.Division: + methodName = "op_Division"; + break; + + case Operator.LessThan: + methodName = "op_LessThan"; + break; + + case Operator.GreaterThan: + methodName = "op_GreaterThan"; + break; + + case Operator.LessThanOrEqual: + methodName = "op_LessThanOrEqual"; + break; + + case Operator.GreaterThanOrEqual: + methodName = "op_GreaterThanOrEqual"; + break; + + case Operator.Modulus: + methodName = "op_Modulus"; + break; + + case Operator.RightShift: + methodName = "op_RightShift"; + break; + + case Operator.LeftShift: + methodName = "op_LeftShift"; + break; + + case Operator.BitwiseAnd: + methodName = "op_BitwiseAnd"; + break; + + case Operator.BitwiseOr: + methodName = "op_BitwiseOr"; + break; + + case Operator.ExclusiveOr: + methodName = "op_ExclusiveOr"; + break; + + case Operator.BitwiseComplement: + methodName = "op_OnesComplement"; + break; + + case Operator.LogicalNot: + methodName = "op_LogicalNot"; + break; + + case Operator.LogicalAnd: + case Operator.LogicalOr: + return null; // Not overridable + + default: + throw new NotImplementedException(); + } + + return type.GetAllMembers<MethodInfo>(Flags.StaticAnyVisibility).Where(x => x.Name == methodName).ToArray(); + } + + /// <summary> + /// Gets all members from a given type, including members from all base types if the <see cref="BindingFlags.DeclaredOnly"/> flag isn't set. + /// </summary> + public static IEnumerable<MemberInfo> GetAllMembers(this Type type, BindingFlags flags = BindingFlags.Default) + { + Type currentType = type; + + if ((flags & BindingFlags.DeclaredOnly) == BindingFlags.DeclaredOnly) + { + foreach (var member in currentType.GetMembers(flags)) + { + yield return member; + } + } + else + { + flags |= BindingFlags.DeclaredOnly; + + do + { + foreach (var member in currentType.GetMembers(flags)) + { + yield return member; + } + + currentType = currentType.BaseType; + } + while (currentType != null); + } + } + + /// <summary> + /// Gets all members from a given type, including members from all base types. + /// </summary> + public static IEnumerable<MemberInfo> GetAllMembers(this Type type, string name, BindingFlags flags = BindingFlags.Default) + { + foreach (var member in type.GetAllMembers(flags)) + { + if (member.Name != name) continue; + yield return member; + } + } + + /// <summary> + /// Gets all members of a specific type from a type, including members from all base types, if the <see cref="BindingFlags.DeclaredOnly"/> flag isn't set. + /// </summary> + public static IEnumerable<T> GetAllMembers<T>(this Type type, BindingFlags flags = BindingFlags.Default) where T : MemberInfo + { + if (type == null) throw new ArgumentNullException("type"); + if (type == typeof(object)) yield break; + + Type currentType = type; + + if ((flags & BindingFlags.DeclaredOnly) == BindingFlags.DeclaredOnly) + { + foreach (var member in currentType.GetMembers(flags)) + { + var found = member as T; + + if (found != null) + { + yield return found; + } + } + } + else + { + flags |= BindingFlags.DeclaredOnly; + + do + { + foreach (var member in currentType.GetMembers(flags)) + { + var found = member as T; + + if (found != null) + { + yield return found; + } + } + + currentType = currentType.BaseType; + } + while (currentType != null); + } + } + + /// <summary> + /// Gets the generic type definition of an open generic base type. + /// </summary> + public static Type GetGenericBaseType(this Type type, Type baseType) + { + int count; + return GetGenericBaseType(type, baseType, out count); + } + + /// <summary> + /// Gets the generic type definition of an open generic base type. + /// </summary> + public static Type GetGenericBaseType(this Type type, Type baseType, out int depthCount) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (baseType == null) + { + throw new ArgumentNullException("baseType"); + } + + if (baseType.IsGenericType == false) + { + throw new ArgumentException("Type " + baseType.Name + " is not a generic type."); + } + + if (type.InheritsFrom(baseType) == false) + { + throw new ArgumentException("Type " + type.Name + " does not inherit from " + baseType.Name + "."); + } + + var t = type; + + depthCount = 0; + while (t != null && (t.IsGenericType == false || t.GetGenericTypeDefinition() != baseType)) + { + depthCount++; + t = t.BaseType; + } + + if (t == null) + { + throw new ArgumentException(type.Name + " is assignable from " + baseType.Name + ", but base type was not found?"); + } + + return t; + } + + /// <summary> + /// Returns a lazy enumerable of all the base types of this type including interfaces and classes + /// </summary> + public static IEnumerable<Type> GetBaseTypes(this Type type, bool includeSelf = false) + { + var result = GetBaseClasses(type, includeSelf).Concat(type.GetInterfaces()); + if (includeSelf && type.IsInterface) + { + result.Concat(new Type[] { type }); + } + return result; + } + + /// <summary> + /// Returns a lazy enumerable of all the base classes of this type + /// </summary> + public static IEnumerable<Type> GetBaseClasses(this Type type, bool includeSelf = false) + { + if (type == null || type.BaseType == null) + { + yield break; + } + + if (includeSelf) + { + yield return type; + } + + var current = type.BaseType; + + while (current != null) + { + yield return current; + current = current.BaseType; + } + } + + /// <summary> + /// Used to filter out unwanted type names. Ex "int" instead of "Int32" + /// </summary> + private static string TypeNameGauntlet(this Type type) + { + string typeName = type.Name; + + string altTypeName = string.Empty; + + if (TypeNameAlternatives.TryGetValue(typeName, out altTypeName)) + { + typeName = altTypeName; + } + + return typeName; + } + + /// <summary> + /// Returns a nicely formatted name of a type. + /// </summary> + public static string GetNiceName(this Type type) + { + if (type.IsNested && type.IsGenericParameter == false) + { + return type.DeclaringType.GetNiceName() + "." + GetCachedNiceName(type); + } + + return GetCachedNiceName(type); + } + + /// <summary> + /// Returns a nicely formatted full name of a type. + /// </summary> + public static string GetNiceFullName(this Type type) + { + string result; + + if (type.IsNested && type.IsGenericParameter == false) + { + return type.DeclaringType.GetNiceFullName() + "." + GetCachedNiceName(type); + } + + result = GetCachedNiceName(type); + + if (type.Namespace != null) + { + result = type.Namespace + "." + result; + } + + return result; + } + + /// <summary> + /// Gets the name of the compilable nice. + /// </summary> + /// <param name="type">The type.</param> + public static string GetCompilableNiceName(this Type type) + { + return type.GetNiceName().Replace('<', '_').Replace('>', '_').TrimEnd('_'); + } + + /// <summary> + /// Gets the full name of the compilable nice. + /// </summary> + /// <param name="type">The type.</param> + public static string GetCompilableNiceFullName(this Type type) + { + return type.GetNiceFullName().Replace('<', '_').Replace('>', '_').TrimEnd('_'); + } + + /// <summary> + /// Returns the first found custom attribute of type T on this type + /// Returns null if none was found + /// </summary> + public static T GetCustomAttribute<T>(this Type type, bool inherit) where T : Attribute + { + var attrs = type.GetCustomAttributes(typeof(T), inherit); + if (attrs.Length == 0) return null; + return attrs[0] as T; + } + + /// <summary> + /// Returns the first found non-inherited custom attribute of type T on this type + /// Returns null if none was found + /// </summary> + public static T GetCustomAttribute<T>(this Type type) where T : Attribute + { + return GetCustomAttribute<T>(type, false); + } + + /// <summary> + /// Gets all attributes of type T. + /// </summary> + /// <param name="type">The type.</param> + public static IEnumerable<T> GetCustomAttributes<T>(this Type type) where T : Attribute + { + return GetCustomAttributes<T>(type, false); + } + + /// <summary> + /// Gets all attributes of type T. + /// </summary> + /// <param name="type">The type</param> + /// <param name="inherit">If true, specifies to also search the ancestors of element for custom attributes.</param> + public static IEnumerable<T> GetCustomAttributes<T>(this Type type, bool inherit) where T : Attribute + { + var attrs = type.GetCustomAttributes(typeof(T), inherit); + + for (int i = 0; i < attrs.Length; i++) + { + yield return attrs[i] as T; + } + } + + /// <summary> + /// Returns true if the attribute whose type is specified by the generic argument is defined on this type + /// </summary> + public static bool IsDefined<T>(this Type type) where T : Attribute + { + return type.IsDefined(typeof(T), false); + } + + /// <summary> + /// Returns true if the attribute whose type is specified by the generic argument is defined on this type + /// </summary> + public static bool IsDefined<T>(this Type type, bool inherit) where T : Attribute + { + return type.IsDefined(typeof(T), inherit); + } + + /// <summary> + /// Determines whether a type inherits or implements another type. Also include support for open generic base types such as List<>. + /// </summary> + /// <param name="type"></param> + public static bool InheritsFrom<TBase>(this Type type) + { + return type.InheritsFrom(typeof(TBase)); + } + + /// <summary> + /// Determines whether a type inherits or implements another type. Also include support for open generic base types such as List<>. + /// </summary> + /// <param name="type"></param> + /// <param name="baseType"></param> + public static bool InheritsFrom(this Type type, Type baseType) + { + if (baseType.IsAssignableFrom(type)) + { + return true; + } + + if (type.IsInterface && baseType.IsInterface == false) + { + return false; + } + + if (baseType.IsInterface) + { + return type.GetInterfaces().Contains(baseType); + } + + var t = type; + while (t != null) + { + if (t == baseType) + { + return true; + } + + if (baseType.IsGenericTypeDefinition && t.IsGenericType && t.GetGenericTypeDefinition() == baseType) + { + return true; + } + + t = t.BaseType; + } + + return false; + } + + /// <summary> + /// Gets the number of base types between given type and baseType. + /// </summary> + public static int GetInheritanceDistance(this Type type, Type baseType) + { + Type lowerType; + Type higherType; + + if (type.IsAssignableFrom(baseType)) + { + higherType = type; + lowerType = baseType; + } + else if (baseType.IsAssignableFrom(type)) + { + higherType = baseType; + lowerType = type; + } + else + { + throw new ArgumentException("Cannot assign types '" + type.GetNiceName() + "' and '" + baseType.GetNiceName() + "' to each other."); + } + + Type currentType = lowerType; + int count = 0; + + if (higherType.IsInterface) + { + while (currentType != null && currentType != typeof(object)) + { + count++; + currentType = currentType.BaseType; + + var interfaces = currentType.GetInterfaces(); + + for (int i = 0; i < interfaces.Length; i++) + { + if (interfaces[i] == higherType) + { + currentType = null; + break; + } + } + } + } + else + { + while (currentType != higherType && currentType != null && currentType != typeof(object)) + { + count++; + currentType = currentType.BaseType; + } + } + + return count; + } + + /// <summary> + /// Determines whether a method has the specified parameter types. + /// </summary> + public static bool HasParamaters(this MethodInfo methodInfo, IList<Type> paramTypes, bool inherit = true) + { + var methodParams = methodInfo.GetParameters(); + if (methodParams.Length == paramTypes.Count) + { + for (int i = 0; i < methodParams.Length; i++) + { + if (inherit && paramTypes[i].InheritsFrom(methodParams[i].ParameterType) == false) + { + return false; + } + else if (methodParams[i].ParameterType != paramTypes[i]) + { + return false; + } + } + return true; + } + return false; + } + + /// <summary> + /// FieldInfo will return the fieldType, propertyInfo the PropertyType, MethodInfo the return type and EventInfo will return the EventHandlerType. + /// </summary> + /// <param name="memberInfo">The MemberInfo.</param> + public static Type GetReturnType(this MemberInfo memberInfo) + { + var fieldInfo = memberInfo as FieldInfo; + if (fieldInfo != null) + { + return fieldInfo.FieldType; + } + + var propertyInfo = memberInfo as PropertyInfo; + if (propertyInfo != null) + { + return propertyInfo.PropertyType; + } + + var methodInfo = memberInfo as MethodInfo; + if (methodInfo != null) + { + return methodInfo.ReturnType; + } + + var eventInfo = memberInfo as EventInfo; + if (eventInfo != null) + { + return eventInfo.EventHandlerType; + } + return null; + } + + /// <summary> + /// Gets the value contained in a given <see cref="MemberInfo"/>. Currently only <see cref="FieldInfo"/> and <see cref="PropertyInfo"/> is supported. + /// </summary> + /// <param name="member">The <see cref="MemberInfo"/> to get the value of.</param> + /// <param name="obj">The instance to get the value from.</param> + /// <returns>The value contained in the given <see cref="MemberInfo"/>.</returns> + /// <exception cref="System.ArgumentException">Can't get the value of the given <see cref="MemberInfo"/> type.</exception> + public static object GetMemberValue(this MemberInfo member, object obj) + { + if (member is FieldInfo) + { + return (member as FieldInfo).GetValue(obj); + } + else if (member is PropertyInfo) + { + return (member as PropertyInfo).GetGetMethod(true).Invoke(obj, null); + } + else + { + throw new ArgumentException("Can't get the value of a " + member.GetType().Name); + } + } + + /// <summary> + /// Sets the value of a given MemberInfo. Currently only <see cref="FieldInfo"/> and <see cref="PropertyInfo"/> is supported. + /// </summary> + /// <param name="member">The <see cref="MemberInfo"/> to set the value of.</param> + /// <param name="obj">The object to set the value on.</param> + /// <param name="value">The value to set.</param> + /// <exception cref="System.ArgumentException"> + /// Property has no setter + /// or + /// Can't set the value of the given <see cref="MemberInfo"/> type. + /// </exception> + public static void SetMemberValue(this MemberInfo member, object obj, object value) + { + if (member is FieldInfo) + { + (member as FieldInfo).SetValue(obj, value); + } + else if (member is PropertyInfo) + { + var method = (member as PropertyInfo).GetSetMethod(true); + + if (method != null) + { + method.Invoke(obj, new object[] { value }); + } + else + { + throw new ArgumentException("Property " + member.Name + " has no setter"); + } + } + else + { + throw new ArgumentException("Can't set the value of a " + member.GetType().Name); + } + } + + /// <summary> + /// Tries to infer a set of valid generic parameters for a generic type definition, given a subset of known parameters. + /// </summary> + /// <param name="genericTypeDefinition">The generic type definition to attempt to infer parameters for.</param> + /// <param name="inferredParams">The inferred parameters, if inferral was successful.</param> + /// <param name="knownParameters">The known parameters to infer from.</param> + /// <returns>True if the parameters could be inferred, otherwise, false.</returns> + /// <exception cref="System.ArgumentNullException"> + /// genericTypeDefinition is null + /// or + /// knownParameters is null + /// </exception> + /// <exception cref="System.ArgumentException">The genericTypeDefinition parameter must be a generic type definition.</exception> + public static bool TryInferGenericParameters(this Type genericTypeDefinition, out Type[] inferredParams, params Type[] knownParameters) + { + if (genericTypeDefinition == null) + { + throw new ArgumentNullException("genericTypeDefinition"); + } + + if (knownParameters == null) + { + throw new ArgumentNullException("knownParameters"); + } + + if (!genericTypeDefinition.IsGenericType) + { + throw new ArgumentException("The genericTypeDefinition parameter must be a generic type."); + } + + lock (GenericConstraintsSatisfaction_LOCK) + { + Dictionary<Type, Type> matches = GenericConstraintsSatisfactionInferredParameters; + matches.Clear(); + + Type[] definitions = genericTypeDefinition.GetGenericArguments(); + + if (!genericTypeDefinition.IsGenericTypeDefinition) + { + Type[] constructedParameters = definitions; + genericTypeDefinition = genericTypeDefinition.GetGenericTypeDefinition(); + definitions = genericTypeDefinition.GetGenericArguments(); + + int unknownCount = 0; + + for (int i = 0; i < constructedParameters.Length; i++) + { + if (!constructedParameters[i].IsGenericParameter && (!constructedParameters[i].IsGenericType || constructedParameters[i].IsFullyConstructedGenericType())) + { + matches[definitions[i]] = constructedParameters[i]; + } + else + { + unknownCount++; + } + } + + if (unknownCount == knownParameters.Length) + { + int count = 0; + + for (int i = 0; i < constructedParameters.Length; i++) + { + if (constructedParameters[i].IsGenericParameter) + { + constructedParameters[i] = knownParameters[count++]; + } + } + + if (genericTypeDefinition.AreGenericConstraintsSatisfiedBy(constructedParameters)) + { + inferredParams = constructedParameters; + return true; + } + } + } + + if (definitions.Length == knownParameters.Length && genericTypeDefinition.AreGenericConstraintsSatisfiedBy(knownParameters)) + { + inferredParams = knownParameters; + return true; + } + + foreach (var type in definitions) + { + if (matches.ContainsKey(type)) continue; + + var constraints = type.GetGenericParameterConstraints(); + + foreach (var constraint in constraints) + { + foreach (var parameter in knownParameters) + { + if (!constraint.IsGenericType) + { + continue; + } + + Type constraintDefinition = constraint.GetGenericTypeDefinition(); + + var constraintParams = constraint.GetGenericArguments(); + Type[] paramParams; + + if (parameter.IsGenericType && constraintDefinition == parameter.GetGenericTypeDefinition()) + { + paramParams = parameter.GetGenericArguments(); + } + else if (constraintDefinition.IsInterface && parameter.ImplementsOpenGenericInterface(constraintDefinition)) + { + paramParams = parameter.GetArgumentsOfInheritedOpenGenericInterface(constraintDefinition); + } + else if (constraintDefinition.IsClass && parameter.ImplementsOpenGenericClass(constraintDefinition)) + { + paramParams = parameter.GetArgumentsOfInheritedOpenGenericClass(constraintDefinition); + } + else + { + continue; + } + + matches[type] = parameter; + + for (int i = 0; i < constraintParams.Length; i++) + { + if (constraintParams[i].IsGenericParameter) + { + matches[constraintParams[i]] = paramParams[i]; + } + } + } + } + } + + if (matches.Count == definitions.Length) + { + inferredParams = new Type[matches.Count]; + + for (int i = 0; i < definitions.Length; i++) + { + inferredParams[i] = matches[definitions[i]]; + } + + if (AreGenericConstraintsSatisfiedBy(genericTypeDefinition, inferredParams)) + { + return true; + } + } + + inferredParams = null; + return false; + } + } + /// <summary> + /// <para>Checks whether an array of types satisfy the constraints of a given generic type definition.</para> + /// <para>If this method returns true, the given parameters can be safely used with <see cref="Type.MakeGenericType(Type[])"/> with the given generic type definition.</para> + /// </summary> + /// <param name="genericType">The generic type definition to check.</param> + /// <param name="parameters">The parameters to check validity for.</param> + /// <exception cref="System.ArgumentNullException"> + /// genericType is null + /// or + /// types is null + /// </exception> + /// <exception cref="System.ArgumentException">The genericType parameter must be a generic type definition.</exception> + public static bool AreGenericConstraintsSatisfiedBy(this Type genericType, params Type[] parameters) + { + if (genericType == null) + { + throw new ArgumentNullException("genericType"); + } + + if (parameters == null) + { + throw new ArgumentNullException("parameters"); + } + + if (!genericType.IsGenericType) + { + throw new ArgumentException("The genericTypeDefinition parameter must be a generic type."); + } + + return AreGenericConstraintsSatisfiedBy(genericType.GetGenericArguments(), parameters); + } + + /// <summary> + /// <para>Checks whether an array of types satisfy the constraints of a given generic method definition.</para> + /// <para>If this method returns true, the given parameters can be safely used with <see cref="MethodInfo.MakeGenericMethod(Type[])"/> with the given generic method definition.</para> + /// </summary> + /// <param name="genericType">The generic method definition to check.</param> + /// <param name="parameters">The parameters to check validity for.</param> + /// <exception cref="System.ArgumentNullException"> + /// genericType is null + /// or + /// types is null + /// </exception> + /// <exception cref="System.ArgumentException">The genericMethod parameter must be a generic method definition.</exception> + public static bool AreGenericConstraintsSatisfiedBy(this MethodBase genericMethod, params Type[] parameters) + { + if (genericMethod == null) + { + throw new ArgumentNullException("genericMethod"); + } + + if (parameters == null) + { + throw new ArgumentNullException("parameters"); + } + + if (!genericMethod.IsGenericMethod) + { + throw new ArgumentException("The genericMethod parameter must be a generic method."); + } + + return AreGenericConstraintsSatisfiedBy(genericMethod.GetGenericArguments(), parameters); + } + + public static bool AreGenericConstraintsSatisfiedBy(Type[] definitions, Type[] parameters) + { + if (definitions.Length != parameters.Length) + { + return false; + } + + lock (GenericConstraintsSatisfaction_LOCK) + { + Dictionary<Type, Type> resolvedMap = GenericConstraintsSatisfactionResolvedMap; + resolvedMap.Clear(); + + for (int i = 0; i < definitions.Length; i++) + { + Type definition = definitions[i]; + Type parameter = parameters[i]; + + if (!definition.GenericParameterIsFulfilledBy(parameter, resolvedMap)) + { + return false; + } + } + + return true; + } + } + + public static bool GenericParameterIsFulfilledBy(this Type genericParameterDefinition, Type parameterType) + { + lock (GenericConstraintsSatisfaction_LOCK) + { + GenericConstraintsSatisfactionResolvedMap.Clear(); + return genericParameterDefinition.GenericParameterIsFulfilledBy(parameterType, GenericConstraintsSatisfactionResolvedMap); + } + } + + /// <summary> + /// Before calling this method we must ALWAYS hold a lock on the GenericConstraintsSatisfaction_LOCK object, as that is an implicit assumption it works with. + /// </summary> + private static bool GenericParameterIsFulfilledBy(this Type genericParameterDefinition, Type parameterType, Dictionary<Type, Type> resolvedMap, HashSet<Type> processedParams = null) + { + if (genericParameterDefinition == null) + { + throw new ArgumentNullException("genericParameterDefinition"); + } + + if (parameterType == null) + { + throw new ArgumentNullException("parameterType"); + } + + if (resolvedMap == null) + { + throw new ArgumentNullException("resolvedMap"); + } + + if (genericParameterDefinition.IsGenericParameter == false && genericParameterDefinition == parameterType) + { + return true; + } + + if (genericParameterDefinition.IsGenericParameter == false) + { + return false; + } + + if (processedParams == null) + { + processedParams = GenericConstraintsSatisfactionProcessedParams; // This is safe because we are currently holding the lock + processedParams.Clear(); + } + + processedParams.Add(genericParameterDefinition); + + // First, check up on the special constraint flags + GenericParameterAttributes specialConstraints = genericParameterDefinition.GenericParameterAttributes; + + if (specialConstraints != GenericParameterAttributes.None) + { + // Struct constraint (must not be nullable) + if ((specialConstraints & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint) + { + if (!parameterType.IsValueType || (parameterType.IsGenericType && parameterType.GetGenericTypeDefinition() == typeof(Nullable<>))) + { + return false; + } + } + // Class constraint + else if ((specialConstraints & GenericParameterAttributes.ReferenceTypeConstraint) == GenericParameterAttributes.ReferenceTypeConstraint) + { + if (parameterType.IsValueType) + { + return false; + } + } + + // Must have a public parameterless constructor + if ((specialConstraints & GenericParameterAttributes.DefaultConstructorConstraint) == GenericParameterAttributes.DefaultConstructorConstraint) + { + if (parameterType.IsAbstract || (!parameterType.IsValueType && parameterType.GetConstructor(Type.EmptyTypes) == null)) + { + return false; + } + } + } + + // If this parameter has already been resolved to a type, check if that resolved type is assignable with the argument type + if (resolvedMap.ContainsKey(genericParameterDefinition)) + { + if (!parameterType.IsAssignableFrom(resolvedMap[genericParameterDefinition])) + { + return false; + } + } + + // Then, check up on the actual type constraints, of which there can be three kinds: + // Type inheritance, Interface implementation and fulfillment of another generic parameter. + Type[] constraints = genericParameterDefinition.GetGenericParameterConstraints(); + + for (int i = 0; i < constraints.Length; i++) + { + Type constraint = constraints[i]; + + // Replace resolved constraint parameters with their resolved types + if (constraint.IsGenericParameter && resolvedMap.ContainsKey(constraint)) + { + constraint = resolvedMap[constraint]; + } + + if (constraint.IsGenericParameter) + { + if (!constraint.GenericParameterIsFulfilledBy(parameterType, resolvedMap, processedParams)) + { + return false; + } + } + else if (constraint.IsClass || constraint.IsInterface || constraint.IsValueType) + { + if (constraint.IsGenericType) + { + Type constraintDefinition = constraint.GetGenericTypeDefinition(); + + Type[] constraintParams = constraint.GetGenericArguments(); + Type[] paramParams; + + if (parameterType.IsGenericType && constraintDefinition == parameterType.GetGenericTypeDefinition()) + { + paramParams = parameterType.GetGenericArguments(); + } + else + { + if (constraintDefinition.IsClass) + { + if (parameterType.ImplementsOpenGenericClass(constraintDefinition)) + { + paramParams = parameterType.GetArgumentsOfInheritedOpenGenericClass(constraintDefinition); + } + else + { + return false; + } + } + else + { + if (parameterType.ImplementsOpenGenericInterface(constraintDefinition)) + { + paramParams = parameterType.GetArgumentsOfInheritedOpenGenericInterface(constraintDefinition); + } + else + { + return false; + } + } + } + + for (int j = 0; j < constraintParams.Length; j++) + { + var c = constraintParams[j]; + var p = paramParams[j]; + + // Replace resolved constraint parameters with their resolved types + if (c.IsGenericParameter && resolvedMap.ContainsKey(c)) + { + c = resolvedMap[c]; + } + + if (c.IsGenericParameter) + { + if (!processedParams.Contains(c) && !GenericParameterIsFulfilledBy(c, p, resolvedMap, processedParams)) + { + return false; + } + } + else if (c != p && !c.IsAssignableFrom(p)) + { + return false; + } + } + } + else if (!constraint.IsAssignableFrom(parameterType)) + { + return false; + } + } + else + { + throw new Exception("Unknown parameter constraint type! " + constraint.GetNiceName()); + } + } + + resolvedMap[genericParameterDefinition] = parameterType; + return true; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static string GetGenericConstraintsString(this Type type, bool useFullTypeNames = false) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (!type.IsGenericTypeDefinition) + { + throw new ArgumentException("Type '" + type.GetNiceName() + "' is not a generic type definition!"); + } + + var parameters = type.GetGenericArguments(); + var strings = new string[parameters.Length]; + + for (int i = 0; i < parameters.Length; i++) + { + strings[i] = parameters[i].GetGenericParameterConstraintsString(useFullTypeNames); + } + + return string.Join(" ", strings); + } + + /// <summary> + /// Formats a string with the specified generic parameter constraints on any given type. Example output: <c>where T : class</c> + /// </summary> + public static string GetGenericParameterConstraintsString(this Type type, bool useFullTypeNames = false) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (!type.IsGenericParameter) + { + throw new ArgumentException("Type '" + type.GetNiceName() + "' is not a generic parameter!"); + } + + StringBuilder sb = new StringBuilder(); + + bool started = false; + + var specialConstraints = type.GenericParameterAttributes; + + // Struct constraint (must not be nullable) + if ((specialConstraints & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint) + { + sb.Append("where ") + .Append(type.Name) + .Append(" : struct"); + + started = true; + } + // Class constraint + else if ((specialConstraints & GenericParameterAttributes.ReferenceTypeConstraint) == GenericParameterAttributes.ReferenceTypeConstraint) + { + sb.Append("where ") + .Append(type.Name) + .Append(" : class"); + + started = true; + } + + // Must have a public parameterless constructor + if ((specialConstraints & GenericParameterAttributes.DefaultConstructorConstraint) == GenericParameterAttributes.DefaultConstructorConstraint) + { + if (started) + { + sb.Append(", new()"); + } + else + { + sb.Append("where ") + .Append(type.Name) + .Append(" : new()"); + + started = true; + } + } + + // Then add type constraints + var constraints = type.GetGenericParameterConstraints(); + + if (constraints.Length > 0) + { + for (int j = 0; j < constraints.Length; j++) + { + var constraint = constraints[j]; + + if (started) + { + sb.Append(", "); + + if (useFullTypeNames) + sb.Append(constraint.GetNiceFullName()); + else + sb.Append(constraint.GetNiceName()); + } + else + { + sb.Append("where ") + .Append(type.Name) + .Append(" : "); + + if (useFullTypeNames) + sb.Append(constraint.GetNiceFullName()); + else + sb.Append(constraint.GetNiceName()); + + started = true; + } + } + } + + return sb.ToString(); + } + + /// <summary> + /// Determines whether a generic type contains the specified generic argument constraints. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="types">The generic argument types.</param> + public static bool GenericArgumentsContainsTypes(this Type type, params Type[] types) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (type.IsGenericType == false) + { + return false; + } + + bool[] typesSeen = new bool[types.Length]; + var args = type.GetGenericArguments(); + + var argsToCheck = GenericArgumentsContainsTypes_ArgsToCheckCached; + + lock (argsToCheck) + { + argsToCheck.Clear(); + + for (int i = 0; i < args.Length; i++) + { + argsToCheck.Push(args[i]); + } + + while (argsToCheck.Count > 0) + { + var arg = argsToCheck.Pop(); + + // Check if it's one of the types we're looking for, and if so, mark that as seen + for (int i = 0; i < types.Length; i++) + { + Type lookingForType = types[i]; + + if (lookingForType == arg) + { + typesSeen[i] = true; + } + else if (lookingForType.IsGenericTypeDefinition && arg.IsGenericType && !arg.IsGenericTypeDefinition && arg.GetGenericTypeDefinition() == lookingForType) + { + typesSeen[i] = true; + } + } + + // Check if all types we're looking for have been seen + { + bool allSeen = true; + + for (int i = 0; i < typesSeen.Length; i++) + { + if (typesSeen[i] == false) + { + allSeen = false; + break; + } + } + + if (allSeen) + { + return true; + } + } + + // If argument is a generic type, we have to also check its arguments + if (arg.IsGenericType) + { + foreach (var innerArg in arg.GetGenericArguments()) + { + argsToCheck.Push(innerArg); + } + } + } + } + + return false; + } + + /// <summary> + /// Determines whether a type is a fully constructed generic type. + /// </summary> + public static bool IsFullyConstructedGenericType(this Type type) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (type.IsGenericTypeDefinition) + { + return false; + } + + if (type.HasElementType) + { + var element = type.GetElementType(); + if (element.IsGenericParameter || element.IsFullyConstructedGenericType() == false) + { + return false; + } + } + + var args = type.GetGenericArguments(); + + for (int i = 0; i < args.Length; i++) + { + var arg = args[i]; + + if (arg.IsGenericParameter) + { + return false; + } + else if (!arg.IsFullyConstructedGenericType()) + { + return false; + } + } + + return !type.IsGenericTypeDefinition; + + //if (type.IsGenericType == false || type.IsGenericTypeDefinition) + //{ + // return false; + //} + + //var args = type.GetGenericArguments(); + + //for (int i = 0; i < args.Length; i++) + //{ + // var arg = args[i]; + + // if (arg.IsGenericParameter) + // { + // return false; + // } + // else if (arg.IsGenericType && !arg.IsFullyConstructedGenericType()) + // { + // return false; + // } + //} + + //return true; + } + + /// <summary> + /// Determines whether a type is nullable by ensuring the type is neither a PrimitiveType, ValueType or an Enum. + /// </summary> + public static bool IsNullableType(this Type type) + { + return !(type.IsPrimitive || type.IsValueType || type.IsEnum); + } + + /// <summary> + /// Gets the enum bitmask in a ulong. + /// </summary> + /// <exception cref="System.ArgumentException">enumType</exception> + public static ulong GetEnumBitmask(object value, Type enumType) + { + if (!enumType.IsEnum) + { + throw new ArgumentException("enumType"); + } + + ulong selectedValue; + + try + { + selectedValue = Convert.ToUInt64(value, CultureInfo.InvariantCulture); + } + catch (OverflowException) + { + unchecked + { + selectedValue = (ulong)Convert.ToInt64(value, CultureInfo.InvariantCulture); + } + } + + return selectedValue; + } + + public static Type[] SafeGetTypes(this Assembly assembly) + { + try + { + return assembly.GetTypes(); + } + catch + { + return Type.EmptyTypes; + } + } + + public static bool SafeIsDefined(this Assembly assembly, Type attribute, bool inherit) + { + try + { + return assembly.IsDefined(attribute, inherit); + } + catch + { + return false; + } + } + + public static object[] SafeGetCustomAttributes(this Assembly assembly, Type type, bool inherit) + { + try + { + return assembly.GetCustomAttributes(type, inherit); + } + catch + { + return new object[0]; + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/TypeExtensions.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/TypeExtensions.cs.meta new file mode 100644 index 00000000..b7222210 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/TypeExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6a172cef14a88c7fb714df37bbecedb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/UnityExtensions.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/UnityExtensions.cs new file mode 100644 index 00000000..bfca1b53 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/UnityExtensions.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// <copyright file="UnityExtensions.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Reflection; + + /// <summary> + /// Extends various Unity classes. + /// </summary> + public static class UnityExtensions + { + private static readonly ValueGetter<UnityEngine.Object, IntPtr> UnityObjectCachedPtrFieldGetter; + + static UnityExtensions() + { + var field = typeof(UnityEngine.Object).GetField("m_CachedPtr", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + if (field != null) + { + UnityObjectCachedPtrFieldGetter = EmitUtilities.CreateInstanceFieldGetter<UnityEngine.Object, IntPtr>(field); + } + } + + /// <summary> + /// Determines whether a Unity object is null or "fake null", + /// without ever calling Unity's own equality operators. + /// This method is useful for checking if a Unity object is + /// null, destroyed or missing at times when it is not allowed + /// to call Unity's own equality operators, for example when + /// not running on the main thread. + /// </summary> + /// <param name="obj">The Unity object to check.</param> + /// <returns>True if the object is null, missing or destroyed; otherwise false.</returns> + public static bool SafeIsUnityNull(this UnityEngine.Object obj) + { + if (object.ReferenceEquals(obj, null)) + { + return true; + } + + if (UnityObjectCachedPtrFieldGetter == null) + { + throw new NotSupportedException("Could not find the field 'm_CachedPtr' in the class UnityEngine.Object; cannot perform a special null check."); + } + + IntPtr ptr = UnityObjectCachedPtrFieldGetter(ref obj); + return ptr == IntPtr.Zero; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/UnityExtensions.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/UnityExtensions.cs.meta new file mode 100644 index 00000000..ad7fea6b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Extensions/UnityExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb77f5278e425e91b71e186df29a5f16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc.meta new file mode 100644 index 00000000..983f77cf --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9633fa76046a40746ad43ef055712a32 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/AssemblyImportSettingsUtilities.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/AssemblyImportSettingsUtilities.cs new file mode 100644 index 00000000..08a22475 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/AssemblyImportSettingsUtilities.cs @@ -0,0 +1,289 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyImportSettingsUtilities.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +#if UNITY_EDITOR + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities.Editor +{ + using System; + using System.IO; + using System.Linq; + using System.Reflection; + using UnityEditor; + + /// <summary> + /// Defines how an assembly's import settings should be configured. + /// </summary> + public enum OdinAssemblyImportSettings + { + /// <summary> + /// Include the assembly in the build, but not in the editor. + /// </summary> + IncludeInBuildOnly, + /// <summary> + /// Include the assembly in the editor, but not in the build. + /// </summary> + IncludeInEditorOnly, + /// <summary> + /// Include the assembly in both the build and in the editor. + /// </summary> + IncludeInAll, + /// <summary> + /// Exclude the assembly from both the build and from the editor. + /// </summary> + ExcludeFromAll, + } + + /// <summary> + /// Utility for correctly setting import on OdinSerializer assemblies based on platform and scripting backend. + /// </summary> + public static class AssemblyImportSettingsUtilities + { + private static MethodInfo getPropertyIntMethod; + private static MethodInfo getScriptingBackendMethod; + private static MethodInfo getApiCompatibilityLevelMethod; + private static MethodInfo apiCompatibilityLevelProperty; + + /// <summary> + /// All valid Unity BuildTarget platforms. + /// </summary> + public static readonly ImmutableList<BuildTarget> Platforms; + + /// <summary> + /// All valid Unity BuildTarget platforms that support Just In Time compilation. + /// </summary> + public static readonly ImmutableList<BuildTarget> JITPlatforms; + + /// <summary> + /// All scripting backends that support JIT. + /// </summary> + public static readonly ImmutableList<ScriptingImplementation> JITScriptingBackends; + + /// <summary> + /// All API compatibility levels that support JIT. + /// </summary> + public static readonly ImmutableList<ApiCompatibilityLevel> JITApiCompatibilityLevels; + + static AssemblyImportSettingsUtilities() + { + // Different methods required for getting the current scripting backend from different versions of the Unity Editor. + getPropertyIntMethod = typeof(PlayerSettings).GetMethod("GetPropertyInt", Flags.StaticPublic, null, new Type[] { typeof(string), typeof(BuildTargetGroup) }, null); + getScriptingBackendMethod = typeof(PlayerSettings).GetMethod("GetScriptingBackend", Flags.StaticPublic, null, new Type[] { typeof(BuildTargetGroup) }, null); + + // Diffferent methods required for getting the current api level from different versions of the Unity Editor. + getApiCompatibilityLevelMethod = typeof(PlayerSettings).GetMethod("GetApiCompatibilityLevel", Flags.StaticPublic, null, new Type[] { typeof(BuildTargetGroup) }, null); + var apiLevelProperty = typeof(PlayerSettings).GetProperty("apiCompatibilityLevel", Flags.StaticPublic); + apiCompatibilityLevelProperty = apiLevelProperty != null ? apiLevelProperty.GetGetMethod() : null; + + // All valid BuildTarget values. + Platforms = new ImmutableList<BuildTarget>(Enum.GetValues(typeof(BuildTarget)) + .Cast<BuildTarget>() + .Where(t => t >= 0 && typeof(BuildTarget).GetMember(t.ToString())[0].IsDefined(typeof(ObsoleteAttribute), false) == false) + .ToArray()); + + // All BuildTarget values that support JIT. + JITPlatforms = new ImmutableList<BuildTarget>(Platforms + .Where(i => i.ToString().StartsWith("StandaloneOSX")) // Unity 2017.3 replaced StandaloneOSXIntel, StandaloneOSXIntel64 and StandaloneOSXUniversal with StandaloneOSX. + .Append(new BuildTarget[] + { + BuildTarget.StandaloneWindows, + BuildTarget.StandaloneWindows64, + BuildTarget.StandaloneLinux, + BuildTarget.StandaloneLinux64, + BuildTarget.StandaloneLinuxUniversal, + BuildTarget.Android + }) + .ToArray()); + + // All scripting backends that support JIT. + JITScriptingBackends = new ImmutableList<ScriptingImplementation>(new ScriptingImplementation[] + { + ScriptingImplementation.Mono2x, + }); + + // Names of all api levels that support JIT. + string[] jitApiNames = new string[] + { + "NET_2_0", + "NET_2_0_Subset", + "NET_4_6", + "NET_Web", // TODO: Does NET_Web support JIT stuff? + "NET_Micro" // TODO: Does NET_Micro support JIT stuff? + }; + + var apiLevelNames = Enum.GetNames(typeof(ApiCompatibilityLevel)); + + JITApiCompatibilityLevels = new ImmutableList<ApiCompatibilityLevel>(jitApiNames + .Where(x => apiLevelNames.Contains(x)) + .Select(x => (ApiCompatibilityLevel)Enum.Parse(typeof(ApiCompatibilityLevel), x)) + .ToArray()); + } + + /// <summary> + /// Set the import settings on the assembly. + /// </summary> + /// <param name="assemblyFilePath">The path to the assembly to configure import settings from.</param> + /// <param name="importSettings">The import settings to configure for the assembly at the path.</param> + public static void SetAssemblyImportSettings(BuildTarget platform, string assemblyFilePath, OdinAssemblyImportSettings importSettings) + { + bool includeInBuild = false; + bool includeInEditor = false; + + switch (importSettings) + { + case OdinAssemblyImportSettings.IncludeInAll: + includeInBuild = true; + includeInEditor = true; + break; + + case OdinAssemblyImportSettings.IncludeInBuildOnly: + includeInBuild = true; + break; + + case OdinAssemblyImportSettings.IncludeInEditorOnly: + includeInEditor = true; + break; + + case OdinAssemblyImportSettings.ExcludeFromAll: + break; + } + + SetAssemblyImportSettings(platform, assemblyFilePath, includeInBuild, includeInEditor); + } + + /// <summary> + /// Set the import settings on the assembly. + /// </summary> + /// <param name="assemblyFilePath">The path to the assembly to configure import settings from.</param> + /// <param name="includeInBuild">Indicates if the assembly should be included in the build.</param> + /// <param name="includeInEditor">Indicates if the assembly should be included in the Unity editor.</param> + public static void SetAssemblyImportSettings(BuildTarget platform, string assemblyFilePath, bool includeInBuild, bool includeInEditor) + { + if (File.Exists(assemblyFilePath) == false) + { + throw new FileNotFoundException(assemblyFilePath); + } + + var importer = (PluginImporter)AssetImporter.GetAtPath(assemblyFilePath); + if (importer == null) + { + throw new InvalidOperationException("Failed to get PluginImporter for " + assemblyFilePath); + } + + bool updateImportSettings = + importer.GetCompatibleWithAnyPlatform() // If the 'any platform' flag is true, then reapply settings no matter what to ensure that everything is correct. + //|| Platforms.Any(p => importer.GetCompatibleWithPlatform(p) != includeInBuild) + || importer.GetCompatibleWithPlatform(platform) != includeInBuild + || importer.GetCompatibleWithEditor() != includeInEditor; + + // Apply new import settings if necessary. + if (updateImportSettings) + { + importer.SetCompatibleWithAnyPlatform(false); + //Platforms.ForEach(p => importer.SetCompatibleWithPlatform(p, includeInBuild)); + importer.SetCompatibleWithPlatform(platform, includeInBuild); + importer.SetCompatibleWithEditor(includeInEditor); + + importer.SaveAndReimport(); + } + } + + /// <summary> + /// Gets the current scripting backend for the build from the Unity editor. This method is Unity version independent. + /// </summary> + /// <returns></returns> + public static ScriptingImplementation GetCurrentScriptingBackend() + { + var buildGroup = EditorUserBuildSettings.selectedBuildTargetGroup; + + if (getScriptingBackendMethod != null) + { + return (ScriptingImplementation)getScriptingBackendMethod.Invoke(null, new object[] { buildGroup }); + } + else if (getPropertyIntMethod != null) + { + return (ScriptingImplementation)getPropertyIntMethod.Invoke(null, new object[] { "ScriptingBackend", buildGroup }); + } + + throw new InvalidOperationException("Was unable to get the current scripting backend!"); + } + + /// <summary> + /// Gets the current API compatibility level from the Unity Editor. This method is Unity version independent. + /// </summary> + /// <returns></returns> + public static ApiCompatibilityLevel GetCurrentApiCompatibilityLevel() + { + if (getApiCompatibilityLevelMethod != null) + { + var buildGroup = EditorUserBuildSettings.selectedBuildTargetGroup; + return (ApiCompatibilityLevel)getApiCompatibilityLevelMethod.Invoke(null, new object[] { buildGroup }); + } + else if (apiCompatibilityLevelProperty != null) + { + return (ApiCompatibilityLevel)apiCompatibilityLevelProperty.Invoke(null, null); + } + + throw new InvalidOperationException("Was unable to get the current api compatibility level!"); + } + + /// <summary> + /// Gets a value that indicates if the specified platform supports JIT. + /// </summary> + /// <param name="platform">The platform to test.</param> + /// <returns><c>true</c> if the platform supports JIT; otherwise <c>false</c>.</returns> + public static bool PlatformSupportsJIT(BuildTarget platform) + { + return JITPlatforms.Contains(platform); + } + + /// <summary> + /// Gets a value that indicates if the specified scripting backend supports JIT. + /// </summary> + /// <param name="backend">The backend to test.</param> + /// <returns><c>true</c> if the backend supports JIT; otherwise <c>false</c>.</returns> + public static bool ScriptingBackendSupportsJIT(ScriptingImplementation backend) + { + return JITScriptingBackends.Contains(backend); + } + + /// <summary> + /// Gets a value that indicates if the specified api level supports JIT. + /// </summary> + /// <param name="apiLevel">The api level to test.</param> + /// <returns><c>true</c> if the api level supports JIT; otherwise <c>false</c>.</returns> + public static bool ApiCompatibilityLevelSupportsJIT(ApiCompatibilityLevel apiLevel) + { + return JITApiCompatibilityLevels.Contains(apiLevel); + } + + /// <summary> + /// Gets a value that indicates if the specified build settings supports JIT. + /// </summary> + /// <param name="platform">The platform build setting.</param> + /// <param name="backend">The scripting backend build settting.</param> + /// <param name="apiLevel">The api level build setting.</param> + /// <returns><c>true</c> if the build settings supports JIT; otherwise <c>false</c>.</returns> + public static bool IsJITSupported(BuildTarget platform, ScriptingImplementation backend, ApiCompatibilityLevel apiLevel) + { + return PlatformSupportsJIT(platform) && ScriptingBackendSupportsJIT(backend) && ApiCompatibilityLevelSupportsJIT(apiLevel); + } + } +} + +#endif
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/AssemblyImportSettingsUtilities.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/AssemblyImportSettingsUtilities.cs.meta new file mode 100644 index 00000000..623f5053 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/AssemblyImportSettingsUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 787c97af872124f748a4a9b366f325d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Cache.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Cache.cs new file mode 100644 index 00000000..f99535a4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Cache.cs @@ -0,0 +1,254 @@ +//----------------------------------------------------------------------- +// <copyright file="Cache.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Threading; + + public interface ICache : IDisposable + { + object Value { get; } + } + + /// <summary> + /// Provides an easy way of claiming and freeing cached values of any non-abstract reference type with a public parameterless constructor. + /// <para /> + /// Cached types which implement the <see cref="ICacheNotificationReceiver"/> interface will receive notifications when they are claimed and freed. + /// <para /> + /// Only one thread should be holding a given cache instance at a time if <see cref="ICacheNotificationReceiver"/> is implemented, since the invocation of + /// <see cref="ICacheNotificationReceiver.OnFreed()"/> is not thread safe, IE, weird stuff might happen if multiple different threads are trying to free + /// the same cache instance at the same time. This will practically never happen unless you're doing really strange stuff, but the case is documented here. + /// </summary> + /// <typeparam name="T">The type which is cached.</typeparam> + /// <seealso cref="System.IDisposable" /> + public sealed class Cache<T> : ICache where T : class, new() + { + private static readonly bool IsNotificationReceiver = typeof(ICacheNotificationReceiver).IsAssignableFrom(typeof(T)); + private static object[] FreeValues = new object[4]; + + private bool isFree; + + private static volatile int THREAD_LOCK_TOKEN = 0; + + private static int maxCacheSize = 5; + + /// <summary> + /// Gets or sets the maximum size of the cache. This value can never go beneath 1. + /// </summary> + /// <value> + /// The maximum size of the cache. + /// </value> + public static int MaxCacheSize + { + get + { + return Cache<T>.maxCacheSize; + } + + set + { + Cache<T>.maxCacheSize = Math.Max(1, value); + } + } + + private Cache() + { + this.Value = new T(); + this.isFree = false; + } + + /// <summary> + /// The cached value. + /// </summary> + public T Value; + + /// <summary> + /// Gets a value indicating whether this cached value is free. + /// </summary> + /// <value> + /// <c>true</c> if this cached value is free; otherwise, <c>false</c>. + /// </value> + public bool IsFree { get { return this.isFree; } } + + object ICache.Value { get { return this.Value; } } + + /// <summary> + /// Claims a cached value of type <see cref="T"/>. + /// </summary> + /// <returns>A cached value of type <see cref="T"/>.</returns> + public static Cache<T> Claim() + { + Cache<T> result = null; + + // Very, very simple spinlock implementation + // this lock will almost never be contested + // and it will never be held for more than + // an instant; therefore, we want to avoid paying + // the lock(object) statement's semaphore + // overhead. + while (true) + { + if (Interlocked.CompareExchange(ref THREAD_LOCK_TOKEN, 1, 0) == 0) + { + break; + } + } + + // We now hold the lock + var freeValues = FreeValues; + var length = freeValues.Length; + + for (int i = 0; i < length; i++) + { + result = (Cache<T>)freeValues[i]; + if (!object.ReferenceEquals(result, null)) + { + freeValues[i] = null; + result.isFree = false; + break; + } + } + + // Release the lock + THREAD_LOCK_TOKEN = 0; + + if (result == null) + { + result = new Cache<T>(); + } + + if (IsNotificationReceiver) + { + (result.Value as ICacheNotificationReceiver).OnClaimed(); + } + + return result; + } + + /// <summary> + /// Releases a cached value. + /// </summary> + /// <param name="cache">The cached value to release.</param> + /// <exception cref="System.ArgumentNullException">The cached value to release is null.</exception> + public static void Release(Cache<T> cache) + { + if (cache == null) + { + throw new ArgumentNullException("cache"); + } + + if (cache.isFree) return; + + // No need to call this method inside the lock, which might do heavy work + // there is a thread safety hole here, actually - if several different threads + // are trying to free the same cache instance, OnFreed might be called several + // times concurrently for the same cached value. + if (IsNotificationReceiver) + { + (cache.Value as ICacheNotificationReceiver).OnFreed(); + } + + while (true) + { + if (Interlocked.CompareExchange(ref THREAD_LOCK_TOKEN, 1, 0) == 0) + { + break; + } + } + + // We now hold the lock + + if (cache.isFree) + { + // Release the lock and leave - job's done already + THREAD_LOCK_TOKEN = 0; + return; + } + + + cache.isFree = true; + + var freeValues = FreeValues; + var length = freeValues.Length; + + bool added = false; + + for (int i = 0; i < length; i++) + { + if (object.ReferenceEquals(freeValues[i], null)) + { + freeValues[i] = cache; + added = true; + break; + } + } + + if (!added && length < MaxCacheSize) + { + var newArr = new object[length * 2]; + + for (int i = 0; i < length; i++) + { + newArr[i] = freeValues[i]; + } + + newArr[length] = cache; + + FreeValues = newArr; + } + + // Release the lock + THREAD_LOCK_TOKEN = 0; + + } + + /// <summary> + /// Performs an implicit conversion from <see cref="Cache{T}"/> to <see cref="T"/>. + /// </summary> + /// <param name="cache">The cache to convert.</param> + /// <returns> + /// The result of the conversion. + /// </returns> + public static implicit operator T(Cache<T> cache) + { + if (cache == null) + { + return default(T); + } + + return cache.Value; + } + + /// <summary> + /// Releases this cached value. + /// </summary> + public void Release() + { + Release(this); + } + + /// <summary> + /// Releases this cached value. + /// </summary> + void IDisposable.Dispose() + { + Cache<T>.Release(this); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Cache.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Cache.cs.meta new file mode 100644 index 00000000..bffcf76b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Cache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 146b6bd1e3b0f0926205abf839ec9e6f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/DoubleLookupDictionary.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/DoubleLookupDictionary.cs new file mode 100644 index 00000000..6706be1d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/DoubleLookupDictionary.cs @@ -0,0 +1,181 @@ +//----------------------------------------------------------------------- +// <copyright file="DoubleLookupDictionary.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Not yet documented. + /// </summary> + [Serializable] + public class DoubleLookupDictionary<TFirstKey, TSecondKey, TValue> : Dictionary<TFirstKey, Dictionary<TSecondKey, TValue>> + { + private readonly IEqualityComparer<TSecondKey> secondKeyComparer; + + public DoubleLookupDictionary() + { + this.secondKeyComparer = EqualityComparer<TSecondKey>.Default; + } + + public DoubleLookupDictionary(IEqualityComparer<TFirstKey> firstKeyComparer, IEqualityComparer<TSecondKey> secondKeyComparer) + : base(firstKeyComparer) + { + this.secondKeyComparer = secondKeyComparer; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public new Dictionary<TSecondKey, TValue> this[TFirstKey firstKey] + { + get + { + Dictionary<TSecondKey, TValue> innerDict; + + if (!this.TryGetValue(firstKey, out innerDict)) + { + innerDict = new Dictionary<TSecondKey, TValue>(this.secondKeyComparer); + this.Add(firstKey, innerDict); + } + + return innerDict; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public int InnerCount(TFirstKey firstKey) + { + Dictionary<TSecondKey, TValue> innerDict; + + if (this.TryGetValue(firstKey, out innerDict)) + { + return innerDict.Count; + } + + return 0; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public int TotalInnerCount() + { + int count = 0; + + if (this.Count > 0) + { + foreach (var innerDict in this.Values) + { + count += innerDict.Count; + } + } + + return count; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public bool ContainsKeys(TFirstKey firstKey, TSecondKey secondKey) + { + Dictionary<TSecondKey, TValue> innerDict; + + return this.TryGetValue(firstKey, out innerDict) && innerDict.ContainsKey(secondKey); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public bool TryGetInnerValue(TFirstKey firstKey, TSecondKey secondKey, out TValue value) + { + Dictionary<TSecondKey, TValue> innerDict; + + if (this.TryGetValue(firstKey, out innerDict) && innerDict.TryGetValue(secondKey, out value)) + { + return true; + } + + value = default(TValue); + return false; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public TValue AddInner(TFirstKey firstKey, TSecondKey secondKey, TValue value) + { + if (this.ContainsKeys(firstKey, secondKey)) + { + throw new ArgumentException("An element with the same keys already exists in the " + this.GetType().GetNiceName() + "."); + } + + return this[firstKey][secondKey] = value; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public bool RemoveInner(TFirstKey firstKey, TSecondKey secondKey) + { + Dictionary<TSecondKey, TValue> innerDict; + + if (this.TryGetValue(firstKey, out innerDict)) + { + bool removed = innerDict.Remove(secondKey); + + if (innerDict.Count == 0) + { + this.Remove(firstKey); + } + + return removed; + } + + return false; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public void RemoveWhere(Func<TValue, bool> predicate) + { + List<TFirstKey> toRemoveBufferFirstKey = new List<TFirstKey>(); + List<TSecondKey> toRemoveBufferSecondKey = new List<TSecondKey>(); + + foreach (var outerDictionary in this.GFIterator()) + { + foreach (var innerKeyPair in outerDictionary.Value.GFIterator()) + { + if (predicate(innerKeyPair.Value)) + { + toRemoveBufferFirstKey.Add(outerDictionary.Key); + toRemoveBufferSecondKey.Add(innerKeyPair.Key); + } + } + } + + for (int i = 0; i < toRemoveBufferFirstKey.Count; i++) + { + this.RemoveInner(toRemoveBufferFirstKey[i], toRemoveBufferSecondKey[i]); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/DoubleLookupDictionary.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/DoubleLookupDictionary.cs.meta new file mode 100644 index 00000000..b2fe8e13 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/DoubleLookupDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1bd625694c606aab0cb7895da4911c6a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/EmitUtilities.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/EmitUtilities.cs new file mode 100644 index 00000000..8ddc781b --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/EmitUtilities.cs @@ -0,0 +1,1772 @@ +//----------------------------------------------------------------------- +// <copyright file="EmitUtilities.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +// #if NET_STANDARD_2_0 +// #error Odin Inspector is incapable of compiling source code against the .NET Standard 2.0 API surface. You can change the API Compatibility Level in the Player settings. +// #endif + +#if (UNITY_EDITOR || UNITY_STANDALONE) && !ENABLE_IL2CPP && NET_4_6 +#define CAN_EMIT +#endif + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Reflection; + +#if CAN_EMIT + + using System.Reflection.Emit; + +#endif + + /// <summary> + /// Not yet documented. + /// </summary> + public delegate object WeakValueGetter(ref object instance); + + /// <summary> + /// Not yet documented. + /// </summary> + public delegate void WeakValueSetter(ref object instance, object value); + + /// <summary> + /// Not yet documented. + /// </summary> + public delegate FieldType WeakValueGetter<FieldType>(ref object instance); + + /// <summary> + /// Not yet documented. + /// </summary> + public delegate void WeakValueSetter<FieldType>(ref object instance, FieldType value); + + /// <summary> + /// Not yet documented. + /// </summary> + public delegate FieldType ValueGetter<InstanceType, FieldType>(ref InstanceType instance); + + /// <summary> + /// Not yet documented. + /// </summary> + public delegate void ValueSetter<InstanceType, FieldType>(ref InstanceType instance, FieldType value); + + /// <summary> + /// Provides utilities for using the <see cref="System.Reflection.Emit"/> namespace. + /// <para /> + /// This class is due for refactoring. Use at your own peril. + /// </summary> + public static class EmitUtilities + { + /// <summary> + /// Gets a value indicating whether emitting is supported on the current platform. + /// </summary> + /// <value> + /// <c>true</c> if the current platform can emit; otherwise, <c>false</c>. + /// </value> + public static bool CanEmit + { + get + { +#if CAN_EMIT + return true; +#else + return false; +#endif + } + } + + /// <summary> + /// Creates a delegate which gets the value of a field. If emitting is not supported on the current platform, the delegate will use reflection to get the value. + /// </summary> + /// <typeparam name="FieldType">The type of the field to get a value from.</typeparam> + /// <param name="fieldInfo">The <see cref="FieldInfo"/> instance describing the field to create a getter for.</param> + /// <returns>A delegate which gets the value of the given field.</returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + public static Func<FieldType> CreateStaticFieldGetter<FieldType>(FieldInfo fieldInfo) + { + if (fieldInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + if (!fieldInfo.IsStatic) + { + throw new ArgumentException("Field must be static."); + } + + fieldInfo = fieldInfo.DeAliasField(); + + if (fieldInfo.IsLiteral) + { + FieldType value = (FieldType)fieldInfo.GetValue(null); + return () => value; + } + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate () + { + return (FieldType)fieldInfo.GetValue(null); + }; +#else + string methodName = fieldInfo.ReflectedType.FullName + ".get_" + fieldInfo.Name; + + DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(FieldType), new Type[0], true); + ILGenerator gen = getterMethod.GetILGenerator(); + + gen.Emit(OpCodes.Ldsfld, fieldInfo); + gen.Emit(OpCodes.Ret); + + return (Func<FieldType>)getterMethod.CreateDelegate(typeof(Func<FieldType>)); +#endif + } + + /// <summary> + /// Creates a delegate which gets the value of a field. If emitting is not supported on the current platform, the delegate will use reflection to get the value. + /// </summary> + /// <param name="fieldInfo">The <see cref="FieldInfo"/> instance describing the field to create a getter for.</param> + /// <returns>A delegate which gets the value of the given field.</returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + public static Func<object> CreateWeakStaticFieldGetter(FieldInfo fieldInfo) + { + if (fieldInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + if (!fieldInfo.IsStatic) + { + throw new ArgumentException("Field must be static."); + } + + fieldInfo = fieldInfo.DeAliasField(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate () + { + return fieldInfo.GetValue(null); + }; +#else + string methodName = fieldInfo.ReflectedType.FullName + ".get_" + fieldInfo.Name; + + DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(object), new Type[0], true); + ILGenerator gen = getterMethod.GetILGenerator(); + + gen.Emit(OpCodes.Ldsfld, fieldInfo); + + if (fieldInfo.FieldType.IsValueType) + { + gen.Emit(OpCodes.Box, fieldInfo.FieldType); + } + + gen.Emit(OpCodes.Ret); + + return (Func<object>)getterMethod.CreateDelegate(typeof(Func<object>)); +#endif + } + + /// <summary> + /// Creates a delegate which sets the value of a field. If emitting is not supported on the current platform, the delegate will use reflection to set the value. + /// </summary> + /// <typeparam name="FieldType">The type of the field to set a value to.</typeparam> + /// <param name="fieldInfo">The <see cref="FieldInfo"/> instance describing the field to create a setter for.</param> + /// <returns>A delegate which sets the value of the given field.</returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + public static Action<FieldType> CreateStaticFieldSetter<FieldType>(FieldInfo fieldInfo) + { + if (fieldInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + if (!fieldInfo.IsStatic) + { + throw new ArgumentException("Field must be static."); + } + + fieldInfo = fieldInfo.DeAliasField(); + + if (fieldInfo.IsLiteral) + { + throw new ArgumentException("Field cannot be constant."); + } + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (FieldType value) + { + fieldInfo.SetValue(null, value); + }; +#else + string methodName = fieldInfo.ReflectedType.FullName + ".set_" + fieldInfo.Name; + + DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[] { typeof(FieldType) }, true); + ILGenerator gen = setterMethod.GetILGenerator(); + + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Stsfld, fieldInfo); + gen.Emit(OpCodes.Ret); + + return (Action<FieldType>)setterMethod.CreateDelegate(typeof(Action<FieldType>)); +#endif + } + + /// <summary> + /// Creates a delegate which sets the value of a field. If emitting is not supported on the current platform, the delegate will use reflection to set the value. + /// </summary> + /// <param name="fieldInfo">The <see cref="FieldInfo"/> instance describing the field to create a setter for.</param> + /// <returns>A delegate which sets the value of the given field.</returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + public static Action<object> CreateWeakStaticFieldSetter(FieldInfo fieldInfo) + { + if (fieldInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + if (!fieldInfo.IsStatic) + { + throw new ArgumentException("Field must be static."); + } + + fieldInfo = fieldInfo.DeAliasField(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (object value) + { + fieldInfo.SetValue(null, value); + }; +#else + string methodName = fieldInfo.ReflectedType.FullName + ".set_" + fieldInfo.Name; + + DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[] { typeof(object) }, true); + ILGenerator gen = setterMethod.GetILGenerator(); + + gen.Emit(OpCodes.Ldarg_0); + + if (fieldInfo.FieldType.IsValueType) + { + gen.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType); + } + else + { + gen.Emit(OpCodes.Castclass, fieldInfo.FieldType); + } + + gen.Emit(OpCodes.Stsfld, fieldInfo); + gen.Emit(OpCodes.Ret); + + return (Action<object>)setterMethod.CreateDelegate(typeof(Action<object>)); +#endif + } + + /// <summary> + /// Creates a delegate which gets the value of a field. If emitting is not supported on the current platform, the delegate will use reflection to get the value. + /// </summary> + /// <typeparam name="InstanceType">The type of the instance to get a value from.</typeparam> + /// <typeparam name="FieldType">The type of the field to get a value from.</typeparam> + /// <param name="fieldInfo">The <see cref="FieldInfo"/> instance describing the field to create a getter for.</param> + /// <returns>A delegate which gets the value of the given field.</returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + public static ValueGetter<InstanceType, FieldType> CreateInstanceFieldGetter<InstanceType, FieldType>(FieldInfo fieldInfo) + { + if (fieldInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + if (fieldInfo.IsStatic) + { + throw new ArgumentException("Field cannot be static."); + } + + fieldInfo = fieldInfo.DeAliasField(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (ref InstanceType classInstance) + { + return (FieldType)fieldInfo.GetValue(classInstance); + }; +#else + string methodName = fieldInfo.ReflectedType.FullName + ".get_" + fieldInfo.Name; + + DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(FieldType), new Type[1] { typeof(InstanceType).MakeByRefType() }, true); + ILGenerator gen = getterMethod.GetILGenerator(); + + if (typeof(InstanceType).IsValueType) + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldfld, fieldInfo); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Ldfld, fieldInfo); + } + + gen.Emit(OpCodes.Ret); + + return (ValueGetter<InstanceType, FieldType>)getterMethod.CreateDelegate(typeof(ValueGetter<InstanceType, FieldType>)); +#endif + } + + /// <summary> + /// Creates a delegate which gets the value of a field from a weakly typed instance of a given type. If emitting is not supported on the current platform, the delegate will use reflection to get the value. + /// </summary> + /// <typeparam name="FieldType">The type of the field to get a value from.</typeparam> + /// <param name="instanceType">The <see cref="Type"/> of the instance to get a value from.</param> + /// <param name="fieldInfo">The <see cref="FieldInfo"/> instance describing the field to create a getter for.</param> + /// <returns>A delegate which gets the value of the given field.</returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + public static WeakValueGetter<FieldType> CreateWeakInstanceFieldGetter<FieldType>(Type instanceType, FieldInfo fieldInfo) + { + if (fieldInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + if (instanceType == null) + { + throw new ArgumentNullException("instanceType"); + } + + if (fieldInfo.IsStatic) + { + throw new ArgumentException("Field cannot be static."); + } + + fieldInfo = fieldInfo.DeAliasField(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (ref object classInstance) + { + return (FieldType)fieldInfo.GetValue(classInstance); + }; +#else + string methodName = fieldInfo.ReflectedType.FullName + ".get_" + fieldInfo.Name; + + DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(FieldType), new Type[1] { typeof(object).MakeByRefType() }, true); + ILGenerator gen = getterMethod.GetILGenerator(); + + if (instanceType.IsValueType) + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Unbox_Any, instanceType); + gen.Emit(OpCodes.Ldfld, fieldInfo); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Castclass, instanceType); + gen.Emit(OpCodes.Ldfld, fieldInfo); + } + + gen.Emit(OpCodes.Ret); + + return (WeakValueGetter<FieldType>)getterMethod.CreateDelegate(typeof(WeakValueGetter<FieldType>)); +#endif + } + + /// <summary> + /// Creates a delegate which gets the weakly typed value of a field from a weakly typed instance of a given type. If emitting is not supported on the current platform, the delegate will use reflection to get the value. + /// </summary> + /// <param name="instanceType">The <see cref="Type"/> of the instance to get a value from.</param> + /// <param name="fieldInfo">The <see cref="FieldInfo"/> instance describing the field to create a getter for.</param> + /// <returns>A delegate which gets the value of the given field.</returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + public static WeakValueGetter CreateWeakInstanceFieldGetter(Type instanceType, FieldInfo fieldInfo) + { + if (fieldInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + if (instanceType == null) + { + throw new ArgumentNullException("instanceType"); + } + + if (fieldInfo.IsStatic) + { + throw new ArgumentException("Field cannot be static."); + } + + fieldInfo = fieldInfo.DeAliasField(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (ref object classInstance) + { + return fieldInfo.GetValue(classInstance); + }; +#else + string methodName = fieldInfo.ReflectedType.FullName + ".get_" + fieldInfo.Name; + + DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(object), new Type[1] { typeof(object).MakeByRefType() }, true); + ILGenerator gen = getterMethod.GetILGenerator(); + + if (instanceType.IsValueType) + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Unbox_Any, instanceType); + gen.Emit(OpCodes.Ldfld, fieldInfo); + + if (fieldInfo.FieldType.IsValueType) + { + gen.Emit(OpCodes.Box, fieldInfo.FieldType); + } + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Castclass, instanceType); + gen.Emit(OpCodes.Ldfld, fieldInfo); + + if (fieldInfo.FieldType.IsValueType) + { + gen.Emit(OpCodes.Box, fieldInfo.FieldType); + } + } + + gen.Emit(OpCodes.Ret); + + return (WeakValueGetter)getterMethod.CreateDelegate(typeof(WeakValueGetter)); +#endif + } + + /// <summary> + /// Creates a delegate which sets the value of a field. If emitting is not supported on the current platform, the delegate will use reflection to set the value. + /// </summary> + /// <typeparam name="InstanceType">The type of the instance to set a value on.</typeparam> + /// <typeparam name="FieldType">The type of the field to set a value to.</typeparam> + /// <param name="fieldInfo">The <see cref="FieldInfo"/> instance describing the field to create a setter for.</param> + /// <returns>A delegate which sets the value of the given field.</returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + public static ValueSetter<InstanceType, FieldType> CreateInstanceFieldSetter<InstanceType, FieldType>(FieldInfo fieldInfo) + { + if (fieldInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + if (fieldInfo.IsStatic) + { + throw new ArgumentException("Field cannot be static."); + } + + fieldInfo = fieldInfo.DeAliasField(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (ref InstanceType classInstance, FieldType value) + { + if (typeof(InstanceType).IsValueType) + { + // Box value type so that the value will be properly set via reflection + object obj = classInstance; + fieldInfo.SetValue(obj, value); + // Unbox the boxed value type that was changed + classInstance = (InstanceType)obj; + } + else + { + fieldInfo.SetValue(classInstance, value); + } + }; +#else + string methodName = fieldInfo.ReflectedType.FullName + ".set_" + fieldInfo.Name; + + DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[2] { typeof(InstanceType).MakeByRefType(), typeof(FieldType) }, true); + ILGenerator gen = setterMethod.GetILGenerator(); + + if (typeof(InstanceType).IsValueType) + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Stfld, fieldInfo); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Stfld, fieldInfo); + } + + gen.Emit(OpCodes.Ret); + + return (ValueSetter<InstanceType, FieldType>)setterMethod.CreateDelegate(typeof(ValueSetter<InstanceType, FieldType>)); +#endif + } + + /// <summary> + /// Creates a delegate which sets the value of a field on a weakly typed instance of a given type. If emitting is not supported on the current platform, the delegate will use reflection to set the value. + /// </summary> + /// <typeparam name="FieldType">The type of the field to set a value to.</typeparam> + /// <param name="instanceType">Type of the instance.</param> + /// <param name="fieldInfo">The <see cref="FieldInfo" /> instance describing the field to create a setter for.</param> + /// <returns> + /// A delegate which sets the value of the given field. + /// </returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + /// <exception cref="System.ArgumentException">Field cannot be static.</exception> + public static WeakValueSetter<FieldType> CreateWeakInstanceFieldSetter<FieldType>(Type instanceType, FieldInfo fieldInfo) + { + if (fieldInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + if (instanceType == null) + { + throw new ArgumentNullException("instanceType"); + } + + if (fieldInfo.IsStatic) + { + throw new ArgumentException("Field cannot be static."); + } + + fieldInfo = fieldInfo.DeAliasField(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (ref object classInstance, FieldType value) + { + fieldInfo.SetValue(classInstance, value); + }; +#else + + string methodName = fieldInfo.ReflectedType.FullName + ".set_" + fieldInfo.Name; + + DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[2] { typeof(object).MakeByRefType(), typeof(FieldType) }, true); + ILGenerator gen = setterMethod.GetILGenerator(); + + if (instanceType.IsValueType) + { + var local = gen.DeclareLocal(instanceType); + + gen.Emit(OpCodes.Ldarg_0); // Load object reference argument + gen.Emit(OpCodes.Ldind_Ref); // Load reference + gen.Emit(OpCodes.Unbox_Any, instanceType); // Unbox to struct + gen.Emit(OpCodes.Stloc, local); // Set local to struct value + gen.Emit(OpCodes.Ldloca_S, local); // Load address to local value + gen.Emit(OpCodes.Ldarg_1); // Load FieldType value + gen.Emit(OpCodes.Stfld, fieldInfo); // Set field on local struct value + gen.Emit(OpCodes.Ldarg_0); // Load object reference argument + gen.Emit(OpCodes.Ldloc, local); // Load local struct value + gen.Emit(OpCodes.Box, instanceType); // Box local struct + gen.Emit(OpCodes.Stind_Ref); // Set object reference argument + } + else + { + gen.Emit(OpCodes.Ldarg_0); // Load object reference argument + gen.Emit(OpCodes.Ldind_Ref); // Load reference + gen.Emit(OpCodes.Castclass, instanceType); // Cast to instance type + gen.Emit(OpCodes.Ldarg_1); // Load value argument + gen.Emit(OpCodes.Stfld, fieldInfo); // Set field + } + + gen.Emit(OpCodes.Ret); + + return (WeakValueSetter<FieldType>)setterMethod.CreateDelegate(typeof(WeakValueSetter<FieldType>)); +#endif + } + + /// <summary> + /// Creates a delegate which sets the weakly typed value of a field on a weakly typed instance of a given type. If emitting is not supported on the current platform, the delegate will use reflection to set the value. + /// </summary> + /// <param name="instanceType">Type of the instance.</param> + /// <param name="fieldInfo">The <see cref="FieldInfo" /> instance describing the field to create a setter for.</param> + /// <returns> + /// A delegate which sets the value of the given field. + /// </returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + /// <exception cref="System.ArgumentException">Field cannot be static.</exception> + public static WeakValueSetter CreateWeakInstanceFieldSetter(Type instanceType, FieldInfo fieldInfo) + { + if (fieldInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + if (instanceType == null) + { + throw new ArgumentNullException("instanceType"); + } + + if (fieldInfo.IsStatic) + { + throw new ArgumentException("Field cannot be static."); + } + + fieldInfo = fieldInfo.DeAliasField(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (ref object classInstance, object value) + { + fieldInfo.SetValue(classInstance, value); + }; +#else + + string methodName = fieldInfo.ReflectedType.FullName + ".set_" + fieldInfo.Name; + + DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[2] { typeof(object).MakeByRefType(), typeof(object) }, true); + ILGenerator gen = setterMethod.GetILGenerator(); + + if (instanceType.IsValueType) + { + var local = gen.DeclareLocal(instanceType); + + gen.Emit(OpCodes.Ldarg_0); // Load object reference argument + gen.Emit(OpCodes.Ldind_Ref); // Load reference + gen.Emit(OpCodes.Unbox_Any, instanceType); // Unbox to struct + gen.Emit(OpCodes.Stloc, local); // Set local to struct value + gen.Emit(OpCodes.Ldloca_S, local); // Load address to local value + gen.Emit(OpCodes.Ldarg_1); // Load FieldType value + + if (fieldInfo.FieldType.IsValueType) + { + gen.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType); + } + else + { + gen.Emit(OpCodes.Castclass, fieldInfo.FieldType); + } + + gen.Emit(OpCodes.Stfld, fieldInfo); // Set field on local struct value + gen.Emit(OpCodes.Ldarg_0); // Load object reference argument + gen.Emit(OpCodes.Ldloc, local); // Load local struct value + gen.Emit(OpCodes.Box, instanceType); // Box local struct + gen.Emit(OpCodes.Stind_Ref); // Set object reference argument + } + else + { + gen.Emit(OpCodes.Ldarg_0); // Load object reference argument + gen.Emit(OpCodes.Ldind_Ref); // Load reference + gen.Emit(OpCodes.Castclass, instanceType); // Cast to instance type + gen.Emit(OpCodes.Ldarg_1); // Load value argument + + if (fieldInfo.FieldType.IsValueType) + { + gen.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType); + } + else + { + gen.Emit(OpCodes.Castclass, fieldInfo.FieldType); + } + + gen.Emit(OpCodes.Stfld, fieldInfo); // Set field + } + + gen.Emit(OpCodes.Ret); + + return (WeakValueSetter)setterMethod.CreateDelegate(typeof(WeakValueSetter)); +#endif + } + + /// <summary> + /// Creates a delegate which gets the weakly typed value of a field from a weakly typed instance of a given type. If emitting is not supported on the current platform, the delegate will use reflection to get the value. + /// </summary> + /// <param name="instanceType">The <see cref="Type"/> of the instance to get a value from.</param> + /// <param name="propertyInfo">The <see cref="FieldInfo"/> instance describing the field to create a getter for.</param> + /// <returns>A delegate which gets the value of the given field.</returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + public static WeakValueGetter CreateWeakInstancePropertyGetter(Type instanceType, PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentNullException("propertyInfo"); + } + + if (instanceType == null) + { + throw new ArgumentNullException("instanceType"); + } + + propertyInfo = propertyInfo.DeAliasProperty(); + + if (propertyInfo.GetIndexParameters().Length > 0) + { + throw new ArgumentException("Property must not have any index parameters"); + } + + var getMethod = propertyInfo.GetGetMethod(true); + + if (getMethod == null) + { + throw new ArgumentException("Property must have a getter."); + } + + if (getMethod.IsStatic) + { + throw new ArgumentException("Property cannot be static."); + } + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (ref object classInstance) + { + return propertyInfo.GetValue(classInstance, null); + }; +#else + + string methodName = propertyInfo.ReflectedType.FullName + ".get_" + propertyInfo.Name; + + DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(object), new Type[1] { typeof(object).MakeByRefType() }, true); + ILGenerator gen = getterMethod.GetILGenerator(); + + if (instanceType.IsValueType) + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Unbox_Any, instanceType); + + if (getMethod.IsVirtual || getMethod.IsAbstract) + { + gen.Emit(OpCodes.Callvirt, getMethod); + } + else + { + gen.Emit(OpCodes.Call, getMethod); + } + + if (propertyInfo.PropertyType.IsValueType) + { + gen.Emit(OpCodes.Box, propertyInfo.PropertyType); + } + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Castclass, instanceType); + + if (getMethod.IsVirtual || getMethod.IsAbstract) + { + gen.Emit(OpCodes.Callvirt, getMethod); + } + else + { + gen.Emit(OpCodes.Call, getMethod); + } + + if (propertyInfo.PropertyType.IsValueType) + { + gen.Emit(OpCodes.Box, propertyInfo.PropertyType); + } + } + + gen.Emit(OpCodes.Ret); + + return (WeakValueGetter)getterMethod.CreateDelegate(typeof(WeakValueGetter)); +#endif + } + + /// <summary> + /// Creates a delegate which sets the weakly typed value of a property on a weakly typed instance of a given type. If emitting is not supported on the current platform, the delegate will use reflection to set the value. + /// </summary> + /// <param name="instanceType">Type of the instance.</param> + /// <param name="propertyInfo">The <see cref="PropertyInfo" /> instance describing the property to create a setter for.</param> + /// <returns> + /// A delegate which sets the value of the given field. + /// </returns> + /// <exception cref="System.ArgumentNullException">The fieldInfo parameter is null.</exception> + /// <exception cref="System.ArgumentException">Property cannot be static.</exception> + public static WeakValueSetter CreateWeakInstancePropertySetter(Type instanceType, PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentNullException("propertyInfo"); + } + + if (instanceType == null) + { + throw new ArgumentNullException("instanceType"); + } + + propertyInfo = propertyInfo.DeAliasProperty(); + + if (propertyInfo.GetIndexParameters().Length > 0) + { + throw new ArgumentException("Property must not have any index parameters"); + } + + var setMethod = propertyInfo.GetSetMethod(true); + + if (setMethod.IsStatic) + { + throw new ArgumentException("Property cannot be static."); + } + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (ref object classInstance, object value) + { + propertyInfo.SetValue(classInstance, value, null); + }; +#else + + string methodName = propertyInfo.ReflectedType.FullName + ".set_" + propertyInfo.Name; + + DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[2] { typeof(object).MakeByRefType(), typeof(object) }, true); + ILGenerator gen = setterMethod.GetILGenerator(); + + if (instanceType.IsValueType) + { + var local = gen.DeclareLocal(instanceType); + + gen.Emit(OpCodes.Ldarg_0); // Load object reference argument + gen.Emit(OpCodes.Ldind_Ref); // Load reference + gen.Emit(OpCodes.Unbox_Any, instanceType); // Unbox to struct + gen.Emit(OpCodes.Stloc, local); // Set local to struct value + gen.Emit(OpCodes.Ldloca_S, local); // Load address to local value + gen.Emit(OpCodes.Ldarg_1); // Load PropertyInfo value + + if (propertyInfo.PropertyType.IsValueType) + { + gen.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType); + } + else + { + gen.Emit(OpCodes.Castclass, propertyInfo.PropertyType); + } + + if (setMethod.IsVirtual || setMethod.IsAbstract) + { + gen.Emit(OpCodes.Callvirt, setMethod); // Set property on local struct value + } + else + { + gen.Emit(OpCodes.Call, setMethod); // Set property on local struct value + } + + gen.Emit(OpCodes.Ldarg_0); // Load object reference argument + gen.Emit(OpCodes.Ldloc, local); // Load local struct value + gen.Emit(OpCodes.Box, instanceType); // Box local struct + gen.Emit(OpCodes.Stind_Ref); // Set object reference argument + } + else + { + gen.Emit(OpCodes.Ldarg_0); // Load object reference argument + gen.Emit(OpCodes.Ldind_Ref); // Load reference + gen.Emit(OpCodes.Castclass, instanceType); // Cast to instance type + gen.Emit(OpCodes.Ldarg_1); // Load value argument + + if (propertyInfo.PropertyType.IsValueType) + { + gen.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType); + } + else + { + gen.Emit(OpCodes.Castclass, propertyInfo.PropertyType); + } + + if (setMethod.IsVirtual || setMethod.IsAbstract) + { + gen.Emit(OpCodes.Callvirt, setMethod); // Set property on local struct value + } + else + { + gen.Emit(OpCodes.Call, setMethod); // Set property on local struct value + } + } + + gen.Emit(OpCodes.Ret); + + return (WeakValueSetter)setterMethod.CreateDelegate(typeof(WeakValueSetter)); +#endif + } + + /// <summary> + /// Creates a delegate which sets the value of a property. If emitting is not supported on the current platform, the delegate will use reflection to set the value. + /// </summary> + /// <typeparam name="PropType">The type of the property to set a value to.</typeparam> + /// <param name="propertyInfo">The <see cref="PropertyInfo"/> instance describing the property to create a setter for.</param> + /// <returns>A delegate which sets the value of the given property.</returns> + /// <exception cref="System.ArgumentNullException">The propertyInfo parameter is null.</exception> + public static Action<PropType> CreateStaticPropertySetter<PropType>(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + propertyInfo = propertyInfo.DeAliasProperty(); + + if (propertyInfo.GetIndexParameters().Length > 0) + { + throw new ArgumentException("Property must not have any index parameters"); + } + + MethodInfo setMethod = propertyInfo.GetSetMethod(true); + + if (setMethod == null) + { + throw new ArgumentException("Property must have a set method."); + } + + if (!setMethod.IsStatic) + { + throw new ArgumentException("Property must be static."); + } + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (PropType value) + { + propertyInfo.SetValue(null, value, null); + }; +#else + string methodName = propertyInfo.ReflectedType.FullName + ".set_" + propertyInfo.Name; + + DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[] { typeof(PropType) }, true); + ILGenerator gen = setterMethod.GetILGenerator(); + + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Call, setMethod); + gen.Emit(OpCodes.Ret); + + return (Action<PropType>)setterMethod.CreateDelegate(typeof(Action<PropType>)); +#endif + } + + /// <summary> + /// Creates a delegate which gets the value of a property. If emitting is not supported on the current platform, the delegate will use reflection to get the value. + /// </summary> + /// <typeparam name="PropType">The type of the property to get a value from.</typeparam> + /// <param name="propertyInfo">The <see cref="PropertyInfo"/> instance describing the property to create a getter for.</param> + /// <returns>A delegate which gets the value of the given property.</returns> + /// <exception cref="System.ArgumentNullException">The propertyInfo parameter is null.</exception> + public static Func<PropType> CreateStaticPropertyGetter<PropType>(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentNullException("propertyInfo"); + } + + propertyInfo = propertyInfo.DeAliasProperty(); + + if (propertyInfo.GetIndexParameters().Length > 0) + { + throw new ArgumentException("Property must not have any index parameters"); + } + + MethodInfo getMethod = propertyInfo.GetGetMethod(true); + + if (getMethod == null) + { + throw new ArgumentException("Property must have a get method."); + } + + if (!getMethod.IsStatic) + { + throw new ArgumentException("Property must be static."); + } + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate () + { + return (PropType)propertyInfo.GetValue(null, null); + }; +#else + + string methodName = propertyInfo.ReflectedType.FullName + ".get_" + propertyInfo.Name; + + DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(PropType), new Type[0], true); + ILGenerator gen = getterMethod.GetILGenerator(); + + gen.Emit(OpCodes.Call, getMethod); + + var returnType = propertyInfo.GetReturnType(); + if (returnType.IsValueType && !typeof(PropType).IsValueType) + { + gen.Emit(OpCodes.Box, returnType); + } + + gen.Emit(OpCodes.Ret); + + return (Func<PropType>)getterMethod.CreateDelegate(typeof(Func<PropType>)); +#endif + } + + /// <summary> + /// Creates a delegate which sets the value of a property. If emitting is not supported on the current platform, the delegate will use reflection to set the value. + /// </summary> + /// <typeparam name="InstanceType">The type of the instance to set a value on.</typeparam> + /// <typeparam name="PropType">The type of the property to set a value to.</typeparam> + /// <param name="propertyInfo">The <see cref="PropertyInfo"/> instance describing the property to create a setter for.</param> + /// <returns>A delegate which sets the value of the given property.</returns> + /// <exception cref="System.ArgumentNullException">The propertyInfo parameter is null.</exception> + public static ValueSetter<InstanceType, PropType> CreateInstancePropertySetter<InstanceType, PropType>(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentNullException("fieldInfo"); + } + + propertyInfo = propertyInfo.DeAliasProperty(); + + if (propertyInfo.GetIndexParameters().Length > 0) + { + throw new ArgumentException("Property must not have any index parameters"); + } + + MethodInfo setMethod = propertyInfo.GetSetMethod(true); + + if (setMethod == null) + { + throw new ArgumentException("Property must have a set method."); + } + + if (setMethod.IsStatic) + { + throw new ArgumentException("Property cannot be static."); + } + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (ref InstanceType classInstance, PropType value) + { + if (typeof(InstanceType).IsValueType) + { + // Box value type so that the value will be properly set via reflection + object obj = classInstance; + propertyInfo.SetValue(obj, value, null); + // Unbox the boxed value type that was changed + classInstance = (InstanceType)obj; + } + else + { + propertyInfo.SetValue(classInstance, value, null); + } + }; +#else + + string methodName = propertyInfo.ReflectedType.FullName + ".set_" + propertyInfo.Name; + + DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[2] { typeof(InstanceType).MakeByRefType(), typeof(PropType) }, true); + ILGenerator gen = setterMethod.GetILGenerator(); + + if (typeof(InstanceType).IsValueType) + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Callvirt, setMethod); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Callvirt, setMethod); + } + + gen.Emit(OpCodes.Ret); + + return (ValueSetter<InstanceType, PropType>)setterMethod.CreateDelegate(typeof(ValueSetter<InstanceType, PropType>)); +#endif + } + + /// <summary> + /// Creates a delegate which gets the value of a property. If emitting is not supported on the current platform, the delegate will use reflection to get the value. + /// </summary> + /// <typeparam name="InstanceType">The type of the instance to get a value from.</typeparam> + /// <typeparam name="PropType">The type of the property to get a value from.</typeparam> + /// <param name="propertyInfo">The <see cref="PropertyInfo"/> instance describing the property to create a getter for.</param> + /// <returns>A delegate which gets the value of the given property.</returns> + /// <exception cref="System.ArgumentNullException">The propertyInfo parameter is null.</exception> + public static ValueGetter<InstanceType, PropType> CreateInstancePropertyGetter<InstanceType, PropType>(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentNullException("propertyInfo"); + } + + propertyInfo = propertyInfo.DeAliasProperty(); + + if (propertyInfo.GetIndexParameters().Length > 0) + { + throw new ArgumentException("Property must not have any index parameters"); + } + + MethodInfo getMethod = propertyInfo.GetGetMethod(true); + + if (getMethod == null) + { + throw new ArgumentException("Property must have a get method."); + } + + if (getMethod.IsStatic) + { + throw new ArgumentException("Property cannot be static."); + } + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (ref InstanceType classInstance) + { + return (PropType)propertyInfo.GetValue(classInstance, null); + }; +#else + + string methodName = propertyInfo.ReflectedType.FullName + ".get_" + propertyInfo.Name; + + DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(PropType), new Type[] { typeof(InstanceType).MakeByRefType() }, true); + ILGenerator gen = getterMethod.GetILGenerator(); + + if (typeof(InstanceType).IsValueType) + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Callvirt, getMethod); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Callvirt, getMethod); + } + + gen.Emit(OpCodes.Ret); + + return (ValueGetter<InstanceType, PropType>)getterMethod.CreateDelegate(typeof(ValueGetter<InstanceType, PropType>)); +#endif + } + + /// <summary> + /// Creates a fast delegate method which calls a given parameterless instance method and returns the result. + /// </summary> + /// <typeparam name="InstanceType">The type of the class which the method is on.</typeparam> + /// <typeparam name="ReturnType">The type which is returned by the given method info.</typeparam> + /// <param name="methodInfo">The method info instance which is used.</param> + /// <returns>A delegate which calls the method and returns the result, except it's hundreds of times faster than MethodInfo.Invoke.</returns> + public static Func<InstanceType, ReturnType> CreateMethodReturner<InstanceType, ReturnType>(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException("methodInfo"); + } + + if (methodInfo.IsStatic) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' is static when it has to be an instance method."); + } + + methodInfo = methodInfo.DeAliasMethod(); + + // Luckily there's no need to emit this - we can just create a delegate and it's only ~10% slower than calling the method directly + // from normal compiled/emitted code. As opposed to using MethodInfo.Invoke, which is on average 600 (!!!) times slower. + return (Func<InstanceType, ReturnType>)Delegate.CreateDelegate(typeof(Func<InstanceType, ReturnType>), methodInfo); + } + + /// <summary> + /// Creates a fast delegate method which calls a given parameterless static method. + /// </summary> + /// <param name="methodInfo">The method info instance which is used.</param> + /// <returns>A delegate which calls the method and returns the result, except it's hundreds of times faster than MethodInfo.Invoke.</returns> + public static Action CreateStaticMethodCaller(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException("methodInfo"); + } + + if (!methodInfo.IsStatic) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' is an instance method when it has to be static."); + } + + if (methodInfo.GetParameters().Length > 0) + { + throw new ArgumentException("Given method cannot have any parameters."); + } + + methodInfo = methodInfo.DeAliasMethod(); + + // Luckily there's no need to emit this - we can just create a delegate and it's only ~10% slower than calling the method directly + // from normal compiled/emitted code. As opposed to using MethodInfo.Invoke, which is on average 600 (!!!) times slower. + return (Action)Delegate.CreateDelegate(typeof(Action), methodInfo); + } + + /// <summary> + /// Creates a fast delegate method which calls a given parameterless weakly typed instance method. + /// </summary> + /// <param name="methodInfo">The method info instance which is used.</param> + /// <returns>A delegate which calls the method and returns the result, except it's hundreds of times faster than MethodInfo.Invoke.</returns> + public static Action<object, TArg1> CreateWeakInstanceMethodCaller<TArg1>(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException("methodInfo"); + } + + if (methodInfo.IsStatic) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' is static when it has to be an instance method."); + } + + var parameters = methodInfo.GetParameters(); + + if (parameters.Length != 1) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' must have exactly one parameter."); + } + + if (parameters[0].ParameterType != typeof(TArg1)) + { + throw new ArgumentException("The first parameter of the method '" + methodInfo.Name + "' must be of type " + typeof(TArg1) + "."); + } + + methodInfo = methodInfo.DeAliasMethod(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return (object classInstance, TArg1 arg) => + { + methodInfo.Invoke(classInstance, new object[] { arg }); + }; +#else + + Type declaringType = methodInfo.DeclaringType; + string methodName = methodInfo.ReflectedType.FullName + ".call_" + methodInfo.Name; + + DynamicMethod method = new DynamicMethod(methodName, null, new Type[] { typeof(object), typeof(TArg1) }, true); + ILGenerator gen = method.GetILGenerator(); + + if (declaringType.IsValueType) + { + var loc = gen.DeclareLocal(declaringType); + + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Unbox_Any, declaringType); + gen.Emit(OpCodes.Stloc, loc); + gen.Emit(OpCodes.Ldloca_S, loc); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Call, methodInfo); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Castclass, declaringType); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Callvirt, methodInfo); + } + + gen.Emit(OpCodes.Ret); + + return (Action<object, TArg1>)method.CreateDelegate(typeof(Action<object, TArg1>)); +#endif + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static Action<object> CreateWeakInstanceMethodCaller(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException("methodInfo"); + } + + if (methodInfo.IsStatic) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' is static when it has to be an instance method."); + } + + if (methodInfo.GetParameters().Length > 0) + { + throw new ArgumentException("Given method cannot have any parameters."); + } + + methodInfo = methodInfo.DeAliasMethod(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return delegate (object classInstance) + { + methodInfo.Invoke(classInstance, null); + }; +#else + + Type declaringType = methodInfo.DeclaringType; + string methodName = methodInfo.ReflectedType.FullName + ".call_" + methodInfo.Name; + + DynamicMethod method = new DynamicMethod(methodName, null, new Type[] { typeof(object) }, true); + ILGenerator gen = method.GetILGenerator(); + + if (declaringType.IsValueType) + { + var loc = gen.DeclareLocal(declaringType); + + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Unbox_Any, declaringType); + gen.Emit(OpCodes.Stloc, loc); + gen.Emit(OpCodes.Ldloca_S, loc); + gen.Emit(OpCodes.Call, methodInfo); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Castclass, declaringType); + gen.Emit(OpCodes.Callvirt, methodInfo); + } + + if (methodInfo.ReturnType != null && methodInfo.ReturnType != typeof(void)) + { + // If there is a return type, pop the returned value off the stack, because we're not returning anything + gen.Emit(OpCodes.Pop); + } + + gen.Emit(OpCodes.Ret); + + return (Action<object>)method.CreateDelegate(typeof(Action<object>)); +#endif + } + + /// <summary> + /// Creates a fast delegate method which calls a given weakly typed instance method with one argument and returns a value. + /// </summary> + /// <typeparam name="TResult">The type of the result.</typeparam> + /// <typeparam name="TArg1">The type of the first argument.</typeparam> + /// <param name="methodInfo">The method info instance which is used.</param> + /// <returns> + /// A delegate which calls the method and returns the result, except it's hundreds of times faster than MethodInfo.Invoke. + /// </returns> + /// <exception cref="System.ArgumentNullException">methodInfo</exception> + /// <exception cref="System.ArgumentException"> + /// Given method ' + methodInfo.Name + ' is static when it has to be an instance method. + /// or + /// Given method ' + methodInfo.Name + ' must return type + typeof(TResult) + . + /// or + /// Given method ' + methodInfo.Name + ' must have exactly one parameter. + /// or + /// The first parameter of the method ' + methodInfo.Name + ' must be of type + typeof(TArg1) + . + /// </exception> + public static Func<object, TArg1, TResult> CreateWeakInstanceMethodCaller<TResult, TArg1>(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException("methodInfo"); + } + + if (methodInfo.IsStatic) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' is static when it has to be an instance method."); + } + + if (methodInfo.ReturnType != typeof(TResult)) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' must return type " + typeof(TResult) + "."); + } + + var parameters = methodInfo.GetParameters(); + + if (parameters.Length != 1) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' must have exactly one parameter."); + } + + if (typeof(TArg1).InheritsFrom(parameters[0].ParameterType) == false) + { + throw new ArgumentException("The first parameter of the method '" + methodInfo.Name + "' must be of type " + typeof(TArg1) + "."); + } + + methodInfo = methodInfo.DeAliasMethod(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return (object classInstance, TArg1 arg1) => + { + return (TResult)methodInfo.Invoke(classInstance, new object[] { arg1 }); + }; +#else + + Type declaringType = methodInfo.DeclaringType; + string methodName = methodInfo.ReflectedType.FullName + ".call_" + methodInfo.Name; + + DynamicMethod method = new DynamicMethod(methodName, typeof(TResult), new Type[] { typeof(object), typeof(TArg1) }, true); + ILGenerator gen = method.GetILGenerator(); + + if (declaringType.IsValueType) + { + var loc = gen.DeclareLocal(declaringType); + + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Unbox_Any, declaringType); + gen.Emit(OpCodes.Stloc, loc); + gen.Emit(OpCodes.Ldloca_S, loc); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Call, methodInfo); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Castclass, declaringType); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Callvirt, methodInfo); + } + + gen.Emit(OpCodes.Ret); + + return (Func<object, TArg1, TResult>)method.CreateDelegate(typeof(Func<object, TArg1, TResult>)); +#endif + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static Func<object, TResult> CreateWeakInstanceMethodCallerFunc<TResult>(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException("methodInfo"); + } + + if (methodInfo.IsStatic) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' is static when it has to be an instance method."); + } + + if (methodInfo.ReturnType != typeof(TResult)) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' must return type " + typeof(TResult) + "."); + } + + var parameters = methodInfo.GetParameters(); + + if (parameters.Length != 0) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' must have no parameter."); + } + + methodInfo = methodInfo.DeAliasMethod(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return (object classInstance) => + { + return (TResult)methodInfo.Invoke(classInstance, null); + }; +#else + + Type declaringType = methodInfo.DeclaringType; + string methodName = methodInfo.ReflectedType.FullName + ".call_" + methodInfo.Name; + + DynamicMethod method = new DynamicMethod(methodName, typeof(TResult), new Type[] { typeof(object) }, true); + ILGenerator gen = method.GetILGenerator(); + + if (declaringType.IsValueType) + { + var loc = gen.DeclareLocal(declaringType); + + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Unbox_Any, declaringType); + gen.Emit(OpCodes.Stloc, loc); + gen.Emit(OpCodes.Ldloca_S, loc); + gen.Emit(OpCodes.Call, methodInfo); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Castclass, declaringType); + gen.Emit(OpCodes.Callvirt, methodInfo); + } + + gen.Emit(OpCodes.Ret); + + return (Func<object, TResult>)method.CreateDelegate(typeof(Func<object, TResult>)); +#endif + } + + /// <summary> + /// Not yet documented. + /// </summary> + public static Func<object, TArg, TResult> CreateWeakInstanceMethodCallerFunc<TArg, TResult>(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException("methodInfo"); + } + + if (methodInfo.IsStatic) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' is static when it has to be an instance method."); + } + + if (methodInfo.ReturnType != typeof(TResult)) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' must return type " + typeof(TResult) + "."); + } + + var parameters = methodInfo.GetParameters(); + + if (parameters.Length != 1) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' must have one parameter."); + } + + if (!parameters[0].ParameterType.IsAssignableFrom(typeof(TArg))) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' has an invalid parameter type."); + } + + methodInfo = methodInfo.DeAliasMethod(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return (object classInstance, TArg arg) => + { + return (TResult)methodInfo.Invoke(classInstance, new object[] { arg }); + }; +#else + Type declaringType = methodInfo.DeclaringType; + string methodName = methodInfo.ReflectedType.FullName + ".call_" + methodInfo.Name; + + DynamicMethod method = new DynamicMethod(methodName, typeof(TResult), new Type[] { typeof(object), typeof(TArg) }, true); + ILGenerator gen = method.GetILGenerator(); + + if (declaringType.IsValueType) + { + var loc = gen.DeclareLocal(declaringType); + + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Unbox_Any, declaringType); + gen.Emit(OpCodes.Stloc, loc); + gen.Emit(OpCodes.Ldloca_S, loc); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Call, methodInfo); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Castclass, declaringType); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Callvirt, methodInfo); + } + + gen.Emit(OpCodes.Ret); + + return (Func<object, TArg, TResult>)method.CreateDelegate(typeof(Func<object, TArg, TResult>)); +#endif + } + + /// <summary> + /// Creates a fast delegate method which calls a given parameterless instance method on a reference type. + /// </summary> + /// <typeparam name="InstanceType">The type of the class which the method is on.</typeparam> + /// <param name="methodInfo">The method info instance which is used.</param> + /// <returns>A delegate which calls the method and returns the result, except it's hundreds of times faster than MethodInfo.Invoke.</returns> + public static Action<InstanceType> CreateInstanceMethodCaller<InstanceType>(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException("methodInfo"); + } + + if (methodInfo.IsStatic) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' is static when it has to be an instance method."); + } + + if (methodInfo.GetParameters().Length > 0) + { + throw new ArgumentException("Given method cannot have any parameters."); + } + + if (typeof(InstanceType).IsValueType) + { + throw new ArgumentException("This method does not work with struct instances; please use CreateInstanceRefMethodCaller instead."); + } + + methodInfo = methodInfo.DeAliasMethod(); + + // Luckily there's no need to emit this - we can just create a delegate and it's only ~10% slower than calling the method directly + // from normal compiled/emitted code. As opposed to using MethodInfo.Invoke, which is on average 600 (!!!) times slower. + return (Action<InstanceType>)Delegate.CreateDelegate(typeof(Action<InstanceType>), methodInfo); + } + + /// <summary> + /// Creates a fast delegate method which calls a given instance method with a given argument on a reference type. + /// </summary> + /// <typeparam name="InstanceType">The type of the class which the method is on.</typeparam> + /// <typeparam name="Arg1">The type of the argument with which to call the method.</typeparam> + /// <param name="methodInfo">The method info instance which is used.</param> + /// <returns>A delegate which calls the method and returns the result, except it's hundreds of times faster than MethodInfo.Invoke.</returns> + public static Action<InstanceType, Arg1> CreateInstanceMethodCaller<InstanceType, Arg1>(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException("methodInfo"); + } + + if (methodInfo.IsStatic) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' is static when it has to be an instance method."); + } + + if (methodInfo.GetParameters().Length != 1) + { + throw new ArgumentException("Given method must have only one parameter."); + } + + if (typeof(InstanceType).IsValueType) + { + throw new ArgumentException("This method does not work with struct instances; please use CreateInstanceRefMethodCaller instead."); + } + + methodInfo = methodInfo.DeAliasMethod(); + + // Luckily there's no need to emit this - we can just create a delegate and it's only ~10% slower than calling the method directly + // from normal compiled/emitted code. As opposed to using MethodInfo.Invoke, which is on average 600 (!!!) times slower. + return (Action<InstanceType, Arg1>)Delegate.CreateDelegate(typeof(Action<InstanceType, Arg1>), methodInfo); + } + + public delegate void InstanceRefMethodCaller<InstanceType>(ref InstanceType instance); + public delegate void InstanceRefMethodCaller<InstanceType, TArg1>(ref InstanceType instance, TArg1 arg1); + + /// <summary> + /// Creates a fast delegate method which calls a given parameterless instance method. + /// </summary> + /// <typeparam name="InstanceType">The type of the class which the method is on.</typeparam> + /// <param name="methodInfo">The method info instance which is used.</param> + /// <returns>A delegate which calls the method and returns the result, except it's hundreds of times faster than MethodInfo.Invoke.</returns> + public static InstanceRefMethodCaller<InstanceType> CreateInstanceRefMethodCaller<InstanceType>(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException("methodInfo"); + } + + if (methodInfo.IsStatic) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' is static when it has to be an instance method."); + } + + if (methodInfo.GetParameters().Length > 0) + { + throw new ArgumentException("Given method cannot have any parameters."); + } + + methodInfo = methodInfo.DeAliasMethod(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return (ref InstanceType instance) => + { + object obj = instance; + methodInfo.Invoke(obj, null); + instance = (InstanceType)obj; + }; +#else + Type declaringType = methodInfo.DeclaringType; + string methodName = methodInfo.ReflectedType.FullName + ".call_" + methodInfo.Name; + + DynamicMethod method = new DynamicMethod(methodName, typeof(void), new Type[] { typeof(InstanceType).MakeByRefType() }, true); + ILGenerator gen = method.GetILGenerator(); + + if (declaringType.IsValueType) + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Call, methodInfo); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Callvirt, methodInfo); + } + + gen.Emit(OpCodes.Ret); + + return (InstanceRefMethodCaller<InstanceType>)method.CreateDelegate(typeof(InstanceRefMethodCaller<InstanceType>)); +#endif + } + + /// <summary> + /// Creates a fast delegate method which calls a given instance method with a given argument on a struct type. + /// </summary> + /// <typeparam name="InstanceType">The type of the class which the method is on.</typeparam> + /// <typeparam name="Arg1">The type of the argument with which to call the method.</typeparam> + /// <param name="methodInfo">The method info instance which is used.</param> + /// <returns>A delegate which calls the method and returns the result, except it's hundreds of times faster than MethodInfo.Invoke.</returns> + public static InstanceRefMethodCaller<InstanceType, Arg1> CreateInstanceRefMethodCaller<InstanceType, Arg1>(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException("methodInfo"); + } + + if (methodInfo.IsStatic) + { + throw new ArgumentException("Given method '" + methodInfo.Name + "' is static when it has to be an instance method."); + } + + if (methodInfo.GetParameters().Length != 1) + { + throw new ArgumentException("Given method must have only one parameter."); + } + + methodInfo = methodInfo.DeAliasMethod(); + +#if !CAN_EMIT + // Platform does not support emitting dynamic code + return (ref InstanceType instance, Arg1 arg1) => + { + object obj = instance; + methodInfo.Invoke(obj, new object[] { arg1 }); + instance = (InstanceType)obj; + }; +#else + Type declaringType = methodInfo.DeclaringType; + string methodName = methodInfo.ReflectedType.FullName + ".call_" + methodInfo.Name; + + DynamicMethod method = new DynamicMethod(methodName, typeof(void), new Type[] { typeof(InstanceType).MakeByRefType(), typeof(Arg1) }, true); + ILGenerator gen = method.GetILGenerator(); + + if (declaringType.IsValueType) + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Call, methodInfo); + } + else + { + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldind_Ref); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Callvirt, methodInfo); + } + + gen.Emit(OpCodes.Ret); + + return (InstanceRefMethodCaller<InstanceType, Arg1>)method.CreateDelegate(typeof(InstanceRefMethodCaller<InstanceType, Arg1>)); +#endif + } + } +} diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/EmitUtilities.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/EmitUtilities.cs.meta new file mode 100644 index 00000000..fb5d69a9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/EmitUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bda92ec6156282448e883bf8f6a781fd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/FastTypeComparer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/FastTypeComparer.cs new file mode 100644 index 00000000..e53098b5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/FastTypeComparer.cs @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------- +// <copyright file="FastTypeComparer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Compares types by reference before comparing them using the default type equality operator. + /// This can constitute a *significant* speedup when used as the comparer for dictionaries. + /// </summary> + /// <seealso cref="System.Collections.Generic.IEqualityComparer{System.Type}" /> + public class FastTypeComparer : IEqualityComparer<Type> + { + public static readonly FastTypeComparer Instance = new FastTypeComparer(); + + public bool Equals(Type x, Type y) + { + if (object.ReferenceEquals(x, y)) return true; // Oft-used fast path over regular Type.Equals makes this much faster + return x == y; + } + + public int GetHashCode(Type obj) + { + return obj.GetHashCode(); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/FastTypeComparer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/FastTypeComparer.cs.meta new file mode 100644 index 00000000..ce4a4497 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/FastTypeComparer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 570028979953bd2c60b7e89ff7cef92e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Flags.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Flags.cs new file mode 100644 index 00000000..7876a6ba --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Flags.cs @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------- +// <copyright file="Flags.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Text; + + /// <summary> + /// This class encapsulates common <see cref="BindingFlags"/> combinations. + /// </summary> + public static class Flags + { + /// <summary> + /// Search criteria encompassing all public and non-public members, including base members. + /// Note that you also need to specify either the Instance or Static flag. + /// </summary> + public const BindingFlags AnyVisibility = BindingFlags.Public | BindingFlags.NonPublic; + + /// <summary> + /// Search criteria encompassing all public instance members, including base members. + /// </summary> + public const BindingFlags InstancePublic = BindingFlags.Public | BindingFlags.Instance; + + /// <summary> + /// Search criteria encompassing all non-public instance members, including base members. + /// </summary> + public const BindingFlags InstancePrivate = BindingFlags.NonPublic | BindingFlags.Instance; + + /// <summary> + /// Search criteria encompassing all public and non-public instance members, including base members. + /// </summary> + public const BindingFlags InstanceAnyVisibility = AnyVisibility | BindingFlags.Instance; + + /// <summary> + /// Search criteria encompassing all public static members, including base members. + /// </summary> + public const BindingFlags StaticPublic = BindingFlags.Public | BindingFlags.Static; + + /// <summary> + /// Search criteria encompassing all non-public static members, including base members. + /// </summary> + public const BindingFlags StaticPrivate = BindingFlags.NonPublic | BindingFlags.Static; + + /// <summary> + /// Search criteria encompassing all public and non-public static members, including base members. + /// </summary> + public const BindingFlags StaticAnyVisibility = AnyVisibility | BindingFlags.Static; + + /// <summary> + /// Search criteria encompassing all public instance members, excluding base members. + /// </summary> + public const BindingFlags InstancePublicDeclaredOnly = InstancePublic | BindingFlags.DeclaredOnly; + + /// <summary> + /// Search criteria encompassing all non-public instance members, excluding base members. + /// </summary> + public const BindingFlags InstancePrivateDeclaredOnly = InstancePrivate | BindingFlags.DeclaredOnly; + + /// <summary> + /// Search criteria encompassing all public and non-public instance members, excluding base members. + /// </summary> + public const BindingFlags InstanceAnyDeclaredOnly = InstanceAnyVisibility | BindingFlags.DeclaredOnly; + + /// <summary> + /// Search criteria encompassing all public static members, excluding base members. + /// </summary> + public const BindingFlags StaticPublicDeclaredOnly = StaticPublic | BindingFlags.DeclaredOnly; + + /// <summary> + /// Search criteria encompassing all non-public static members, excluding base members. + /// </summary> + public const BindingFlags StaticPrivateDeclaredOnly = StaticPrivate | BindingFlags.DeclaredOnly; + + /// <summary> + /// Search criteria encompassing all public and non-public static members, excluding base members. + /// </summary> + public const BindingFlags StaticAnyDeclaredOnly = StaticAnyVisibility | BindingFlags.DeclaredOnly; + + /// <summary> + /// Search criteria encompassing all members, including base and static members. + /// </summary> + public const BindingFlags StaticInstanceAnyVisibility = InstanceAnyVisibility | BindingFlags.Static; + + /// <summary> + /// Search criteria encompassing all members (public and non-public, instance and static), including base members. + /// </summary> + public const BindingFlags AllMembers = StaticInstanceAnyVisibility | BindingFlags.FlattenHierarchy; + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Flags.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Flags.cs.meta new file mode 100644 index 00000000..e7b1d3c6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/Flags.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 42e5d977e21c7a6524213a8a7dbee24a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ICacheNotificationReceiver.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ICacheNotificationReceiver.cs new file mode 100644 index 00000000..5d5d3eca --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ICacheNotificationReceiver.cs @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------- +// <copyright file="ICacheNotificationReceiver.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + /// <summary> + /// Provides notification callbacks for values that are cached using the <see cref="Cache{T}"/> class. + /// </summary> + public interface ICacheNotificationReceiver + { + /// <summary> + /// Called when the cached value is freed. + /// </summary> + void OnFreed(); + + /// <summary> + /// Called when the cached value is claimed. + /// </summary> + void OnClaimed(); + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ICacheNotificationReceiver.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ICacheNotificationReceiver.cs.meta new file mode 100644 index 00000000..18129377 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ICacheNotificationReceiver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 783316da32d87acfae14953e341732a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ImmutableList.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ImmutableList.cs new file mode 100644 index 00000000..60eae3c5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ImmutableList.cs @@ -0,0 +1,605 @@ +//----------------------------------------------------------------------- +// <copyright file="ImmutableList.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Collections; + using System.Collections.Generic; + using UnityEngine; + + /// <summary> + /// Interface for immutable list. + /// </summary> + public interface IImmutableList : IList + { + } + + /// <summary> + /// Interface for generic immutable list. + /// </summary> + public interface IImmutableList<T> : IImmutableList, IList<T> + { + /// <summary> + /// Index accessor. + /// </summary> + new T this[int index] { get; } + } + + /// <summary> + /// Immutable list wraps another list, and allows for reading the inner list, without the ability to change it. + /// </summary> + [Serializable] + public sealed class ImmutableList : IImmutableList<object> + { + [SerializeField] + private IList innerList; + + /// <summary> + /// Creates an immutable list around another list. + /// </summary> + public ImmutableList(IList innerList) + { + if (innerList == null) + { + throw new ArgumentNullException("innerList"); + } + + this.innerList = innerList; + } + + /// <summary> + /// Number of items in the list. + /// </summary> + public int Count { get { return this.innerList.Count; } } + + /// <summary> + /// Immutable list cannot be changed directly, so it's size is always fixed. + /// </summary> + public bool IsFixedSize { get { return true; } } + + /// <summary> + /// Immutable list are always readonly. + /// </summary> + public bool IsReadOnly { get { return true; } } + + /// <summary> + /// Returns <c>true</c> if the inner list is synchronized. + /// </summary> + public bool IsSynchronized { get { return this.innerList.IsSynchronized; } } + + /// <summary> + /// Gets the sync root object. + /// </summary> + public object SyncRoot { get { return this.innerList.SyncRoot; } } + + object IList.this[int index] + { + get + { + return this.innerList[index]; + } + + set + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + } + + object IList<object>.this[int index] + { + get + { + return this.innerList[index]; + } + + set + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + } + + /// <summary> + /// Index accessor. + /// </summary> + /// <param name="index">Index.</param> + public object this[int index] { get { return this.innerList[index]; } } + + /// <summary> + /// Returns <c>true</c> if the item is contained in the list. + /// </summary> + /// <param name="value">The item's value.</param> + public bool Contains(object value) + { + return this.innerList.Contains(value); + } + + /// <summary> + /// Copy the list to an array, + /// </summary> + /// <param name="array">Target array.</param> + /// <param name="arrayIndex">Index.</param> + public void CopyTo(object[] array, int arrayIndex) + { + this.innerList.CopyTo(array, arrayIndex); + } + + /// <summary> + /// Copy the list to an array, + /// </summary> + /// <param name="array">Target array.</param> + /// <param name="index">Index.</param> + public void CopyTo(Array array, int index) + { + this.innerList.CopyTo(array, index); + } + + /// <summary> + /// Gets an enumerator. + /// </summary> + public IEnumerator GetEnumerator() + { + return this.innerList.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + IEnumerator<object> IEnumerable<object>.GetEnumerator() + { + foreach (var obj in this.innerList) + { + yield return obj; + } + } + + int IList.Add(object value) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList.Clear() + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList.Insert(int index, object value) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList.Remove(object value) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + /// <summary> + /// Get the index of a value. + /// </summary> + /// <param name="value">The item's value.</param> + public int IndexOf(object value) + { + return this.innerList.IndexOf(value); + } + + /// <summary> + /// Immutable list cannot be edited. + /// </summary> + /// <param name="index">Index.</param> + void IList<object>.RemoveAt(int index) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + /// <summary> + /// Immutable list cannot be edited. + /// </summary> + /// <param name="index">Index.</param> + /// <param name="item">Item.</param> + void IList<object>.Insert(int index, object item) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + /// <summary> + /// Immutable list cannot be edited. + /// </summary> + /// <param name="item">Item.</param> + void ICollection<object>.Add(object item) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + /// <summary> + /// Immutable list cannot be edited. + /// </summary> + void ICollection<object>.Clear() + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + /// <summary> + /// Immutable list cannot be edited. + /// </summary> + /// <param name="item">Item.</param> + bool ICollection<object>.Remove(object item) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + [Serializable] + public sealed class ImmutableList<T> : IImmutableList<T> + { + [SerializeField] + private IList<T> innerList; + + /// <summary> + /// Not yet documented. + /// </summary> + public ImmutableList(IList<T> innerList) + { + if (innerList == null) + { + throw new ArgumentNullException("innerList"); + } + + this.innerList = innerList; + } + + /// <summary> + /// Not yet documented. + /// </summary> + public int Count { get { return this.innerList.Count; } } + + bool ICollection.IsSynchronized { get { return false; } } + object ICollection.SyncRoot { get { return null; } } + bool IList.IsFixedSize { get { return true; } } + bool IList.IsReadOnly { get { return true; } } + + /// <summary> + /// Not yet documented. + /// </summary> + public bool IsReadOnly { get { return true; } } + + object IList.this[int index] + { + get + { + return this[index]; + } + + set + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + } + + T IList<T>.this[int index] + { + get + { + return this.innerList[index]; + } + + set + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public T this[int index] + { + get + { + return this.innerList[index]; + } + } + + /// <summary> + /// Not yet documented. + /// </summary> + public bool Contains(T item) + { + return this.innerList.Contains(item); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public void CopyTo(T[] array, int arrayIndex) + { + this.innerList.CopyTo(array, arrayIndex); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public IEnumerator<T> GetEnumerator() + { + return this.innerList.GetEnumerator(); + } + + void ICollection.CopyTo(Array array, int index) + { + this.innerList.CopyTo((T[])array, index); + } + + void ICollection<T>.Add(T item) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void ICollection<T>.Clear() + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + bool ICollection<T>.Remove(T item) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + int IList.Add(object value) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList.Clear() + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + bool IList.Contains(object value) + { + return this.innerList.Contains((T)value); + } + + int IList.IndexOf(object value) + { + return this.innerList.IndexOf((T)value); + } + + void IList.Insert(int index, object value) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList.Remove(object value) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList<T>.Insert(int index, T item) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + /// <summary> + /// Not yet documented. + /// </summary> + public int IndexOf(T item) + { + return this.innerList.IndexOf(item); + } + + void IList<T>.RemoveAt(int index) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + } + + /// <summary> + /// Immutable list wraps another list, and allows for reading the inner list, without the ability to change it. + /// </summary> + [Serializable] + public sealed class ImmutableList<TList, TElement> : IImmutableList<TElement> where TList : IList<TElement> + { + private TList innerList; + + /// <summary> + /// Creates an immutable list around another list. + /// </summary> + public ImmutableList(TList innerList) + { + if (innerList == null) + { + throw new ArgumentNullException("innerList"); + } + + this.innerList = innerList; + } + + /// <summary> + /// Number of items in the list. + /// </summary> + public int Count { get { return this.innerList.Count; } } + + bool ICollection.IsSynchronized { get { return false; } } + object ICollection.SyncRoot { get { return null; } } + bool IList.IsFixedSize { get { return true; } } + bool IList.IsReadOnly { get { return true; } } + + /// <summary> + /// Immutable list are always readonly. + /// </summary> + public bool IsReadOnly { get { return true; } } + + object IList.this[int index] + { + get + { + return this[index]; + } + + set + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + } + + TElement IList<TElement>.this[int index] + { + get + { + return this.innerList[index]; + } + + set + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + } + + /// <summary> + /// Index accessor. + /// </summary> + /// <param name="index">Index.</param> + public TElement this[int index] + { + get + { + return this.innerList[index]; + } + } + + /// <summary> + /// Returns <c>true</c> if the item is contained in the list. + /// </summary> + public bool Contains(TElement item) + { + return this.innerList.Contains(item); + } + + /// <summary> + /// Copies the list to an array. + /// </summary> + public void CopyTo(TElement[] array, int arrayIndex) + { + this.innerList.CopyTo(array, arrayIndex); + } + + /// <summary> + /// Gets an enumerator. + /// </summary> + public IEnumerator<TElement> GetEnumerator() + { + return this.innerList.GetEnumerator(); + } + + void ICollection.CopyTo(Array array, int index) + { + this.innerList.CopyTo((TElement[])array, index); + } + + void ICollection<TElement>.Add(TElement item) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void ICollection<TElement>.Clear() + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + bool ICollection<TElement>.Remove(TElement item) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + int IList.Add(object value) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList.Clear() + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + bool IList.Contains(object value) + { + return this.innerList.Contains((TElement)value); + } + + int IList.IndexOf(object value) + { + return this.innerList.IndexOf((TElement)value); + } + + void IList.Insert(int index, object value) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList.Remove(object value) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList<TElement>.Insert(int index, TElement item) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + + /// <summary> + /// Gets the index of an item. + /// </summary> + public int IndexOf(TElement item) + { + return this.innerList.IndexOf(item); + } + + void IList<TElement>.RemoveAt(int index) + { + throw new NotSupportedException("Immutable Lists cannot be edited."); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ImmutableList.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ImmutableList.cs.meta new file mode 100644 index 00000000..cfcee8c9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ImmutableList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1bc635f3755c60fe69f1895dd53974e2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasFieldInfo.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasFieldInfo.cs new file mode 100644 index 00000000..a1230b37 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasFieldInfo.cs @@ -0,0 +1,179 @@ +//----------------------------------------------------------------------- +// <copyright file="MemberAliasFieldInfo.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Globalization; + using System.Reflection; + + /// <summary> + /// Provides a methods of representing imaginary fields which are unique to serialization. + /// <para /> + /// We aggregate the FieldInfo associated with this member and return a mangled form of the name. + /// </summary> + /// <seealso cref="System.Reflection.FieldInfo" /> + public sealed class MemberAliasFieldInfo : FieldInfo + { + /// <summary> + /// The default fake name separator string. + /// </summary> + private const string FAKE_NAME_SEPARATOR_STRING = "+"; + + private FieldInfo aliasedField; + private string mangledName; + + /// <summary> + /// Initializes a new instance of the <see cref="MemberAliasFieldInfo"/> class. + /// </summary> + /// <param name="field">The field to alias.</param> + /// <param name="namePrefix">The name prefix to use.</param> + public MemberAliasFieldInfo(FieldInfo field, string namePrefix) + { + this.aliasedField = field; + this.mangledName = string.Concat(namePrefix, FAKE_NAME_SEPARATOR_STRING, this.aliasedField.Name); + } + + /// <summary> + /// Initializes a new instance of the <see cref="MemberAliasFieldInfo"/> class. + /// </summary> + /// <param name="field">The field to alias.</param> + /// <param name="namePrefix">The name prefix to use.</param> + /// <param name="separatorString">The separator string to use.</param> + public MemberAliasFieldInfo(FieldInfo field, string namePrefix, string separatorString) + { + this.aliasedField = field; + this.mangledName = string.Concat(namePrefix, separatorString, this.aliasedField.Name); + } + + /// <summary> + /// Gets the aliased field. + /// </summary> + /// <value> + /// The aliased field. + /// </value> + public FieldInfo AliasedField { get { return this.aliasedField; } } + + /// <summary> + /// Gets the module in which the type that declares the member represented by the current <see cref="T:System.Reflection.MemberInfo" /> is defined. + /// </summary> + public override Module Module { get { return this.aliasedField.Module; } } + + /// <summary> + /// Gets a value that identifies a metadata element. + /// </summary> + public override int MetadataToken { get { return this.aliasedField.MetadataToken; } } + + /// <summary> + /// Gets the name of the current member. + /// </summary> + public override string Name { get { return this.mangledName; } } + + /// <summary> + /// Gets the class that declares this member. + /// </summary> + public override Type DeclaringType { get { return this.aliasedField.DeclaringType; } } + + /// <summary> + /// Gets the class object that was used to obtain this instance of MemberInfo. + /// </summary> + public override Type ReflectedType { get { return this.aliasedField.ReflectedType; } } + + /// <summary> + /// Gets the type of the field. + /// </summary> + /// <value> + /// The type of the field. + /// </value> + public override Type FieldType { get { return this.aliasedField.FieldType; } } + + /// <summary> + /// Gets a RuntimeFieldHandle, which is a handle to the internal metadata representation of a field. + /// </summary> + public override RuntimeFieldHandle FieldHandle { get { return this.aliasedField.FieldHandle; } } + + /// <summary> + /// Gets the attributes. + /// </summary> + /// <value> + /// The attributes. + /// </value> + public override FieldAttributes Attributes { get { return this.aliasedField.Attributes; } } + + /// <summary> + /// When overridden in a derived class, returns an array of all custom attributes applied to this member. + /// </summary> + /// <param name="inherit">True to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events; see Remarks.</param> + /// <returns> + /// An array that contains all the custom attributes applied to this member, or an array with zero elements if no attributes are defined. + /// </returns> + public override object[] GetCustomAttributes(bool inherit) + { + return this.aliasedField.GetCustomAttributes(inherit); + } + + /// <summary> + /// When overridden in a derived class, returns an array of custom attributes applied to this member and identified by <see cref="T:System.Type" />. + /// </summary> + /// <param name="attributeType">The type of attribute to search for. Only attributes that are assignable to this type are returned.</param> + /// <param name="inherit">True to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events; see Remarks.</param> + /// <returns> + /// An array of custom attributes applied to this member, or an array with zero elements if no attributes assignable to <paramref name="attributeType" /> have been applied. + /// </returns> + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + return this.aliasedField.GetCustomAttributes(attributeType, inherit); + } + + /// <summary> + /// When overridden in a derived class, indicates whether one or more attributes of the specified type or of its derived types is applied to this member. + /// </summary> + /// <param name="attributeType">The type of custom attribute to search for. The search includes derived types.</param> + /// <param name="inherit">True to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events; see Remarks.</param> + /// <returns> + /// True if one or more instances of <paramref name="attributeType" /> or any of its derived types is applied to this member; otherwise, false. + /// </returns> + public override bool IsDefined(Type attributeType, bool inherit) + { + return this.aliasedField.IsDefined(attributeType, inherit); + } + + /// <summary> + /// Gets the value of the field. + /// </summary> + /// <param name="obj">The object instance to get the value from.</param> + /// <returns>The value of the field.</returns> + public override object GetValue(object obj) + { + return this.aliasedField.GetValue(obj); + } + + /// <summary> + /// When overridden in a derived class, sets the value of the field supported by the given object. + /// </summary> + /// <param name="obj">The object whose field value will be set.</param> + /// <param name="value">The value to assign to the field.</param> + /// <param name="invokeAttr">A field of Binder that specifies the type of binding that is desired (for example, Binder.CreateInstance or Binder.ExactBinding).</param> + /// <param name="binder">A set of properties that enables the binding, coercion of argument types, and invocation of members through reflection. If <paramref name="binder" /> is null, then Binder.DefaultBinding is used.</param> + /// <param name="culture">The software preferences of a particular culture.</param> + public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, CultureInfo culture) + { + this.aliasedField.SetValue(obj, value, invokeAttr, binder, culture); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasFieldInfo.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasFieldInfo.cs.meta new file mode 100644 index 00000000..591dfd06 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasFieldInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 000592e93b119574207ea3bf59f659e4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasMethodInfo.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasMethodInfo.cs new file mode 100644 index 00000000..80253fb1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasMethodInfo.cs @@ -0,0 +1,194 @@ +//----------------------------------------------------------------------- +// <copyright file="MemberAliasMethodInfo.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Globalization; + using System.Reflection; + + /// <summary> + /// Provides a methods of representing aliased methods. + /// <para /> + /// In this case, what we're representing is a method on a parent class with the same name. + /// <para /> + /// We aggregate the MethodInfo associated with this member and return a mangled form of the name. + /// The name that we return is "parentname+methodName". + /// </summary> + /// <seealso cref="System.Reflection.FieldInfo" /> + public sealed class MemberAliasMethodInfo : MethodInfo + { + /// <summary> + /// The default fake name separator string. + /// </summary> + private const string FAKE_NAME_SEPARATOR_STRING = "+"; + + private MethodInfo aliasedMethod; + private string mangledName; + + /// <summary> + /// Initializes a new instance of the <see cref="MemberAliasMethodInfo"/> class. + /// </summary> + /// <param name="method">The method to alias.</param> + /// <param name="namePrefix">The name prefix to use.</param> + public MemberAliasMethodInfo(MethodInfo method, string namePrefix) + { + this.aliasedMethod = method; + this.mangledName = string.Concat(namePrefix, FAKE_NAME_SEPARATOR_STRING, this.aliasedMethod.Name); + } + + /// <summary> + /// Initializes a new instance of the <see cref="MemberAliasMethodInfo"/> class. + /// </summary> + /// <param name="method">The method to alias.</param> + /// <param name="namePrefix">The name prefix to use.</param> + /// <param name="separatorString">The separator string to use.</param> + public MemberAliasMethodInfo(MethodInfo method, string namePrefix, string separatorString) + { + this.aliasedMethod = method; + this.mangledName = string.Concat(namePrefix, separatorString, this.aliasedMethod.Name); + } + + /// <summary> + /// Gets the aliased method. + /// </summary> + /// <value> + /// The aliased method. + /// </value> + public MethodInfo AliasedMethod { get { return this.aliasedMethod; } } + + /// <summary> + /// Gets the custom attributes for the return type. + /// </summary> + public override ICustomAttributeProvider ReturnTypeCustomAttributes { get { return this.aliasedMethod.ReturnTypeCustomAttributes; } } + + /// <summary> + /// Gets a handle to the internal metadata representation of a method. + /// </summary> + public override RuntimeMethodHandle MethodHandle { get { return this.aliasedMethod.MethodHandle; } } + + /// <summary> + /// Gets the attributes associated with this method. + /// </summary> + public override MethodAttributes Attributes { get { return this.aliasedMethod.Attributes; } } + + public override Type ReturnType { get { return this.aliasedMethod.ReturnType; } } + + /// <summary> + /// Gets the class that declares this member. + /// </summary> + public override Type DeclaringType { get { return this.aliasedMethod.DeclaringType; } } + + /// <summary> + /// Gets the name of the current member. + /// </summary> + public override string Name { get { return this.mangledName; } } + + /// <summary> + /// Gets the class object that was used to obtain this instance of MemberInfo. + /// </summary> + public override Type ReflectedType { get { return this.aliasedMethod.ReflectedType; } } + + /// <summary> + /// When overridden in a derived class, returns the MethodInfo object for the method on the direct or indirect base class in which the method represented by this instance was first declared. + /// </summary> + /// <returns> + /// A MethodInfo object for the first implementation of this method. + /// </returns> + public override MethodInfo GetBaseDefinition() + { + return this.aliasedMethod.GetBaseDefinition(); + } + + /// <summary> + /// When overridden in a derived class, returns an array of all custom attributes applied to this member. + /// </summary> + /// <param name="inherit">true to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events; see Remarks.</param> + /// <returns> + /// An array that contains all the custom attributes applied to this member, or an array with zero elements if no attributes are defined. + /// </returns> + public override object[] GetCustomAttributes(bool inherit) + { + return this.aliasedMethod.GetCustomAttributes(inherit); + } + + /// <summary> + /// When overridden in a derived class, returns an array of custom attributes applied to this member and identified by <see cref="T:System.Type" />. + /// </summary> + /// <param name="attributeType">The type of attribute to search for. Only attributes that are assignable to this type are returned.</param> + /// <param name="inherit">true to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events; see Remarks.</param> + /// <returns> + /// An array of custom attributes applied to this member, or an array with zero elements if no attributes assignable to <paramref name="attributeType" /> have been applied. + /// </returns> + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + return this.aliasedMethod.GetCustomAttributes(attributeType, inherit); + } + + /// <summary> + /// When overridden in a derived class, returns the <see cref="T:System.Reflection.MethodImplAttributes" /> flags. + /// </summary> + /// <returns> + /// The MethodImplAttributes flags. + /// </returns> + public override MethodImplAttributes GetMethodImplementationFlags() + { + return this.aliasedMethod.GetMethodImplementationFlags(); + } + + /// <summary> + /// When overridden in a derived class, gets the parameters of the specified method or constructor. + /// </summary> + /// <returns> + /// An array of type ParameterInfo containing information that matches the signature of the method (or constructor) reflected by this MethodBase instance. + /// </returns> + public override ParameterInfo[] GetParameters() + { + return this.aliasedMethod.GetParameters(); + } + + /// <summary> + /// When overridden in a derived class, invokes the reflected method or constructor with the given parameters. + /// </summary> + /// <param name="obj">The object on which to invoke the method or constructor. If a method is static, this argument is ignored. If a constructor is static, this argument must be null or an instance of the class that defines the constructor.</param> + /// <param name="invokeAttr">A bitmask that is a combination of 0 or more bit flags from <see cref="T:System.Reflection.BindingFlags" />. If <paramref name="binder" /> is null, this parameter is assigned the value <see cref="F:System.Reflection.BindingFlags.Default" />; thus, whatever you pass in is ignored.</param> + /// <param name="binder">An object that enables the binding, coercion of argument types, invocation of members, and retrieval of MemberInfo objects via reflection. If <paramref name="binder" /> is null, the default binder is used.</param> + /// <param name="parameters">An argument list for the invoked method or constructor. This is an array of objects with the same number, order, and type as the parameters of the method or constructor to be invoked. If there are no parameters, this should be null.If the method or constructor represented by this instance takes a ByRef parameter, there is no special attribute required for that parameter in order to invoke the method or constructor using this function. Any object in this array that is not explicitly initialized with a value will contain the default value for that object type. For reference-type elements, this value is null. For value-type elements, this value is 0, 0.0, or false, depending on the specific element type.</param> + /// <param name="culture">An instance of CultureInfo used to govern the coercion of types. If this is null, the CultureInfo for the current thread is used. (This is necessary to convert a String that represents 1000 to a Double value, for example, since 1000 is represented differently by different cultures.)</param> + /// <returns> + /// An Object containing the return value of the invoked method, or null in the case of a constructor, or null if the method's return type is void. Before calling the method or constructor, Invoke checks to see if the user has access permission and verifies that the parameters are valid.CautionElements of the <paramref name="parameters" /> array that represent parameters declared with the ref or out keyword may also be modified. + /// </returns> + public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture) + { + return this.aliasedMethod.Invoke(obj, invokeAttr, binder, parameters, culture); + } + + /// <summary> + /// When overridden in a derived class, indicates whether one or more attributes of the specified type or of its derived types is applied to this member. + /// </summary> + /// <param name="attributeType">The type of custom attribute to search for. The search includes derived types.</param> + /// <param name="inherit">true to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events; see Remarks.</param> + /// <returns> + /// true if one or more instances of <paramref name="attributeType" /> or any of its derived types is applied to this member; otherwise, false. + /// </returns> + public override bool IsDefined(Type attributeType, bool inherit) + { + return this.aliasedMethod.IsDefined(attributeType, inherit); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasMethodInfo.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasMethodInfo.cs.meta new file mode 100644 index 00000000..1ea42af1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasMethodInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c1e85c1ef449ccb40e05f0afd3dd717f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasPropertyInfo.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasPropertyInfo.cs new file mode 100644 index 00000000..6e6db9c0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasPropertyInfo.cs @@ -0,0 +1,237 @@ +//----------------------------------------------------------------------- +// <copyright file="MemberAliasPropertyInfo.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Globalization; + using System.Reflection; + + /// <summary> + /// Provides a methods of representing imaginary properties which are unique to serialization. + /// <para /> + /// We aggregate the PropertyInfo associated with this member and return a mangled form of the name. + /// </summary> + /// <seealso cref="System.Reflection.FieldInfo" /> + public sealed class MemberAliasPropertyInfo : PropertyInfo + { + /// <summary> + /// The default fake name separator string. + /// </summary> + private const string FakeNameSeparatorString = "+"; + + private PropertyInfo aliasedProperty; + private string mangledName; + + /// <summary> + /// Initializes a new instance of the <see cref="MemberAliasPropertyInfo"/> class. + /// </summary> + /// <param name="prop">The property to alias.</param> + /// <param name="namePrefix">The name prefix to use.</param> + public MemberAliasPropertyInfo(PropertyInfo prop, string namePrefix) + { + this.aliasedProperty = prop; + this.mangledName = string.Concat(namePrefix, FakeNameSeparatorString, this.aliasedProperty.Name); + } + + /// <summary> + /// Initializes a new instance of the <see cref="MemberAliasPropertyInfo"/> class. + /// </summary> + /// <param name="prop">The property to alias.</param> + /// <param name="namePrefix">The name prefix to use.</param> + /// <param name="separatorString">The separator string to use.</param> + public MemberAliasPropertyInfo(PropertyInfo prop, string namePrefix, string separatorString) + { + this.aliasedProperty = prop; + this.mangledName = string.Concat(namePrefix, separatorString, this.aliasedProperty.Name); + } + + /// <summary> + /// The backing PropertyInfo that is being aliased. + /// </summary> + public PropertyInfo AliasedProperty { get { return this.aliasedProperty; } } + + /// <summary> + /// Gets the module in which the type that declares the member represented by the current <see cref="T:System.Reflection.MemberInfo" /> is defined. + /// </summary> + public override Module Module { get { return this.aliasedProperty.Module; } } + + /// <summary> + /// Gets a value that identifies a metadata element. + /// </summary> + public override int MetadataToken { get { return this.aliasedProperty.MetadataToken; } } + + /// <summary> + /// Gets the name of the current member. + /// </summary> + public override string Name { get { return this.mangledName; } } + + /// <summary> + /// Gets the class that declares this member. + /// </summary> + public override Type DeclaringType { get { return this.aliasedProperty.DeclaringType; } } + + /// <summary> + /// Gets the class object that was used to obtain this instance of MemberInfo. + /// </summary> + public override Type ReflectedType { get { return this.aliasedProperty.ReflectedType; } } + + /// <summary> + /// Gets the type of the property. + /// </summary> + /// <value> + /// The type of the property. + /// </value> + public override Type PropertyType { get { return this.aliasedProperty.PropertyType; } } + + /// <summary> + /// Gets the attributes. + /// </summary> + /// <value> + /// The attributes. + /// </value> + public override PropertyAttributes Attributes { get { return this.aliasedProperty.Attributes; } } + + /// <summary> + /// Gets a value indicating whether this instance can read. + /// </summary> + /// <value> + /// <c>true</c> if this instance can read; otherwise, <c>false</c>. + /// </value> + public override bool CanRead { get { return this.aliasedProperty.CanRead; } } + + /// <summary> + /// Gets a value indicating whether this instance can write. + /// </summary> + /// <value> + /// <c>true</c> if this instance can write; otherwise, <c>false</c>. + /// </value> + public override bool CanWrite { get { return this.aliasedProperty.CanWrite; } } + + /// <summary> + /// When overridden in a derived class, returns an array of all custom attributes applied to this member. + /// </summary> + /// <param name="inherit">True to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events; see Remarks.</param> + /// <returns> + /// An array that contains all the custom attributes applied to this member, or an array with zero elements if no attributes are defined. + /// </returns> + public override object[] GetCustomAttributes(bool inherit) + { + return this.aliasedProperty.GetCustomAttributes(inherit); + } + + /// <summary> + /// When overridden in a derived class, returns an array of custom attributes applied to this member and identified by <see cref="T:System.Type" />. + /// </summary> + /// <param name="attributeType">The type of attribute to search for. Only attributes that are assignable to this type are returned.</param> + /// <param name="inherit">True to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events; see Remarks.</param> + /// <returns> + /// An array of custom attributes applied to this member, or an array with zero elements if no attributes assignable to <paramref name="attributeType" /> have been applied. + /// </returns> + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + return this.aliasedProperty.GetCustomAttributes(attributeType, inherit); + } + + /// <summary> + /// When overridden in a derived class, indicates whether one or more attributes of the specified type or of its derived types is applied to this member. + /// </summary> + /// <param name="attributeType">The type of custom attribute to search for. The search includes derived types.</param> + /// <param name="inherit">True to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events; see Remarks.</param> + /// <returns> + /// True if one or more instances of <paramref name="attributeType" /> or any of its derived types is applied to this member; otherwise, false. + /// </returns> + public override bool IsDefined(Type attributeType, bool inherit) + { + return this.aliasedProperty.IsDefined(attributeType, inherit); + } + + /// <summary> + /// Returns an array whose elements reflect the public and, if specified, non-public get, set, and other accessors of the property reflected by the current instance. + /// </summary> + /// <param name="nonPublic">Indicates whether non-public methods should be returned in the MethodInfo array. true if non-public methods are to be included; otherwise, false.</param> + /// <returns> + /// An array of <see cref="T:System.Reflection.MethodInfo" /> objects whose elements reflect the get, set, and other accessors of the property reflected by the current instance. If <paramref name="nonPublic" /> is true, this array contains public and non-public get, set, and other accessors. If <paramref name="nonPublic" /> is false, this array contains only public get, set, and other accessors. If no accessors with the specified visibility are found, this method returns an array with zero (0) elements. + /// </returns> + public override MethodInfo[] GetAccessors(bool nonPublic) + { + return this.aliasedProperty.GetAccessors(nonPublic); + } + + /// <summary> + /// When overridden in a derived class, returns the public or non-public get accessor for this property. + /// </summary> + /// <param name="nonPublic">Indicates whether a non-public get accessor should be returned. true if a non-public accessor is to be returned; otherwise, false.</param> + /// <returns> + /// A MethodInfo object representing the get accessor for this property, if <paramref name="nonPublic" /> is true. Returns null if <paramref name="nonPublic" /> is false and the get accessor is non-public, or if <paramref name="nonPublic" /> is true but no get accessors exist. + /// </returns> + public override MethodInfo GetGetMethod(bool nonPublic) + { + return this.aliasedProperty.GetGetMethod(nonPublic); + } + + /// <summary> + /// Gets the index parameters of the property. + /// </summary> + /// <returns>The index parameters of the property.</returns> + public override ParameterInfo[] GetIndexParameters() + { + return this.aliasedProperty.GetIndexParameters(); + } + + /// <summary> + /// When overridden in a derived class, returns the set accessor for this property. + /// </summary> + /// <param name="nonPublic">Indicates whether the accessor should be returned if it is non-public. true if a non-public accessor is to be returned; otherwise, false.</param> + /// <returns> + /// Value Condition A <see cref="T:System.Reflection.MethodInfo" /> object representing the Set method for this property. The set accessor is public.-or- <paramref name="nonPublic" /> is true and the set accessor is non-public. null<paramref name="nonPublic" /> is true, but the property is read-only.-or- <paramref name="nonPublic" /> is false and the set accessor is non-public.-or- There is no set accessor. + /// </returns> + public override MethodInfo GetSetMethod(bool nonPublic) + { + return this.aliasedProperty.GetSetMethod(nonPublic); + } + + /// <summary> + /// Gets the value of the property on the given instance. + /// </summary> + /// <param name="obj">The object to invoke the getter on.</param> + /// <param name="invokeAttr">The <see cref="BindingFlags"/> to invoke with.</param> + /// <param name="binder">The binder to use.</param> + /// <param name="index">The indices to use.</param> + /// <param name="culture">The culture to use.</param> + /// <returns>The value of the property on the given instance.</returns> + public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) + { + return this.aliasedProperty.GetValue(obj, invokeAttr, binder, index, culture); + } + + /// <summary> + /// Sets the value of the property on the given instance. + /// </summary> + /// <param name="obj">The object to set the value on.</param> + /// <param name="value">The value to set.</param> + /// <param name="invokeAttr">The <see cref="BindingFlags"/> to invoke with.</param> + /// <param name="binder">The binder to use.</param> + /// <param name="index">The indices to use.</param> + /// <param name="culture">The culture to use.</param> + public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) + { + this.aliasedProperty.SetValue(obj, value, invokeAttr, binder, index, culture); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasPropertyInfo.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasPropertyInfo.cs.meta new file mode 100644 index 00000000..c6b15163 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/MemberAliasPropertyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 00bf47593f2a330c1bb41552bdc1233f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ReferenceEqualityComparer.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ReferenceEqualityComparer.cs new file mode 100644 index 00000000..1b1984c9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ReferenceEqualityComparer.cs @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------- +// <copyright file="ReferenceEqualityComparer.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- + +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using System; + using System.Collections.Generic; + + /// <summary> + /// Compares objects by reference only, ignoring equality operators completely. This is used by the property tree reference dictionaries to keep track of references. + /// </summary> + public class ReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class + { + /// <summary> + /// A default, cached instance of this generic variant of the reference equality comparer. + /// </summary> + public static readonly ReferenceEqualityComparer<T> Default = new ReferenceEqualityComparer<T>(); + + /// <summary> + /// Returns true if the object references are equal. + /// </summary> + public bool Equals(T x, T y) + { + return object.ReferenceEquals(x, y); + } + + /// <summary> + /// Returns the result of the object's own GetHashCode method. + /// </summary> + public int GetHashCode(T obj) + { + try + { + return obj.GetHashCode(); + } + catch (NullReferenceException) + { + return -1; + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ReferenceEqualityComparer.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ReferenceEqualityComparer.cs.meta new file mode 100644 index 00000000..9f4efc31 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/ReferenceEqualityComparer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5ad884ed6013d621a310ceb4c954f62a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnityVersion.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnityVersion.cs new file mode 100644 index 00000000..c4bbe5d5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnityVersion.cs @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------- +// <copyright file="UnityVersion.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer.Utilities +{ + using UnityEngine; + + /// <summary> + /// Utility class indicating current Unity version. + /// </summary> +#if UNITY_EDITOR + + [UnityEditor.InitializeOnLoad] +#endif + public static class UnityVersion + { + static UnityVersion() + { + string[] version = Application.unityVersion.Split('.'); + + if (version.Length < 2) + { + Debug.LogError("Could not parse current Unity version '" + Application.unityVersion + "'; not enough version elements."); + return; + } + + if (int.TryParse(version[0], out Major) == false) + { + Debug.LogError("Could not parse major part '" + version[0] + "' of Unity version '" + Application.unityVersion + "'."); + } + + if (int.TryParse(version[1], out Minor) == false) + { + Debug.LogError("Could not parse minor part '" + version[1] + "' of Unity version '" + Application.unityVersion + "'."); + } + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void EnsureLoaded() + { + // This method ensures that this type has been initialized before any loading of objects occurs. + // If this isn't done, the static constructor may be invoked at an illegal time that is not + // allowed by Unity. During scene deserialization, off the main thread, is an example. + } + + /// <summary> + /// Tests current Unity version is equal or greater. + /// </summary> + /// <param name="major">Minimum major version.</param> + /// <param name="minor">Minimum minor version.</param> + /// <returns><c>true</c> if the current Unity version is greater. Otherwise <c>false</c>.</returns> + public static bool IsVersionOrGreater(int major, int minor) + { + return UnityVersion.Major > major || (UnityVersion.Major == major && UnityVersion.Minor >= minor); + } + + /// <summary> + /// The current Unity version major. + /// </summary> + public static readonly int Major; + + /// <summary> + /// The current Unity version minor. + /// </summary> + public static readonly int Minor; + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnityVersion.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnityVersion.cs.meta new file mode 100644 index 00000000..5f93dea1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnityVersion.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0fe3820fb4651e200f17905245ec8be2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnsafeUtilities.cs b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnsafeUtilities.cs new file mode 100644 index 00000000..e6058115 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnsafeUtilities.cs @@ -0,0 +1,408 @@ +//----------------------------------------------------------------------- +// <copyright file="UnsafeUtilities.cs" company="Sirenix IVS"> +// Copyright (c) 2018 Sirenix IVS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// </copyright> +//----------------------------------------------------------------------- +namespace VRC.Udon.Serialization.OdinSerializer.Utilities.Unsafe +{ + using System; + using System.Runtime.InteropServices; + +#pragma warning disable 0649 // Field is never assigned to and will have its default value // VRC + + /// <summary> + /// Contains utilities for performing common unsafe operations. + /// </summary> + public static class UnsafeUtilities + { + /// <summary> + /// Blindly creates an array of structs from an array of bytes via direct memory copy/blit. + /// </summary> + public static unsafe T[] StructArrayFromBytes<T>(byte[] bytes, int byteLength) where T : struct + { + return StructArrayFromBytes<T>(bytes, 0, 0); + } + + /// <summary> + /// Blindly creates an array of structs from an array of bytes via direct memory copy/blit. + /// </summary> + public static unsafe T[] StructArrayFromBytes<T>(byte[] bytes, int byteLength, int byteOffset) where T : struct + { + if (bytes == null) + { + throw new ArgumentNullException("bytes"); + } + + if (byteLength <= 0) + { + throw new ArgumentException("Byte length must be larger than zero."); + } + + if (byteOffset < 0) + { + throw new ArgumentException("Byte offset must be larger than or equal to zero."); + } + + int typeSize = Marshal.SizeOf(typeof(T)); + + if (byteOffset % sizeof(ulong) != 0) + { + throw new ArgumentException("Byte offset must be divisible by " + sizeof(ulong) + " (IE, sizeof(ulong))"); + } + + if (byteLength + byteOffset >= bytes.Length) + { + throw new ArgumentException("Given byte array of size " + bytes.Length + " is not large enough to copy requested number of bytes " + byteLength + "."); + } + + if ((byteLength - byteOffset) % typeSize != 0) + { + throw new ArgumentException("The length in the given byte array (" + bytes.Length + ", and " + (bytes.Length - byteOffset) + " minus byteOffset " + byteOffset + ") to convert to type " + typeof(T).Name + " is not divisible by the size of " + typeof(T).Name + " (" + typeSize + ")."); + } + + int elementCount = (bytes.Length - byteOffset) / typeSize; + T[] array = new T[elementCount]; + MemoryCopy(bytes, array, byteLength, byteOffset, 0); + return array; + } + + /// <summary> + /// Blindly copies an array of structs into an array of bytes via direct memory copy/blit. + /// </summary> + public static unsafe byte[] StructArrayToBytes<T>(T[] array) where T : struct + { + byte[] bytes = null; + return StructArrayToBytes(array, ref bytes, 0); + } + + /// <summary> + /// Blindly copies an array of structs into an array of bytes via direct memory copy/blit. + /// </summary> + public static unsafe byte[] StructArrayToBytes<T>(T[] array, ref byte[] bytes, int byteOffset) where T : struct + { + if (array == null) + { + throw new ArgumentNullException("array"); + } + + if (byteOffset < 0) + { + throw new ArgumentException("Byte offset must be larger than or equal to zero."); + } + + int typeSize = Marshal.SizeOf(typeof(T)); + int byteCount = typeSize * array.Length; + + if (bytes == null) + { + bytes = new byte[byteCount + byteOffset]; + } + else if (bytes.Length + byteOffset > byteCount) + { + throw new ArgumentException("Byte array must be at least " + (bytes.Length + byteOffset) + " long with the given byteOffset."); + } + + MemoryCopy(array, bytes, byteCount, 0, byteOffset); + return bytes; + } + + /// <summary> + /// Creates a new string from the contents of a given byte buffer. + /// </summary> + public static unsafe string StringFromBytes(byte[] buffer, int charLength, bool needs16BitSupport) + { + int byteCount = needs16BitSupport ? charLength * 2 : charLength; + + if (buffer.Length < byteCount) + { + throw new ArgumentException("Buffer is not large enough to contain the given string; a size of at least " + byteCount + " is required."); + } + + GCHandle toHandle = default(GCHandle); + string result = new string(default(char), charLength); // Creaty empty string of required length + + try + { + toHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + + if (needs16BitSupport) + { + if (BitConverter.IsLittleEndian) + { + fixed (char* charPtr1 = result) + { + ushort* fromPtr1 = (ushort*)toHandle.AddrOfPinnedObject().ToPointer(); + ushort* toPtr1 = (ushort*)charPtr1; + + for (int i = 0; i < byteCount; i += sizeof(ushort)) + { + *toPtr1++ = *fromPtr1++; + } + } + } + else + { + fixed (char* charPtr2 = result) + { + byte* fromPtr2 = (byte*)toHandle.AddrOfPinnedObject().ToPointer(); + byte* toPtr2 = (byte*)charPtr2; + + for (int i = 0; i < byteCount; i += sizeof(ushort)) + { + *toPtr2 = *(fromPtr2 + 1); + *(toPtr2 + 1) = *fromPtr2; + + fromPtr2 += 2; + toPtr2 += 2; + } + } + } + } + else + { + if (BitConverter.IsLittleEndian) + { + fixed (char* charPtr3 = result) + { + byte* fromPtr3 = (byte*)toHandle.AddrOfPinnedObject().ToPointer(); + byte* toPtr3 = (byte*)charPtr3; + + for (int i = 0; i < byteCount; i += sizeof(byte)) + { + *toPtr3++ = *fromPtr3++; + toPtr3++; // Skip every other string byte + } + } + } + else + { + fixed (char* charPtr4 = result) + { + byte* fromPtr4 = (byte*)toHandle.AddrOfPinnedObject().ToPointer(); + byte* toPtr4 = (byte*)charPtr4; + + for (int i = 0; i < byteCount; i += sizeof(byte)) + { + toPtr4++; // Skip every other string byte + *toPtr4++ = *fromPtr4++; + } + } + } + } + } + finally + { + if (toHandle.IsAllocated) + { + toHandle.Free(); + } + } + + // Retrieve proper string reference from the intern pool. + // This code removed for now, as the slight decrease in memory use is not considered worth the performance cost of the intern lookup and the potential extra garbage to be collected. + // Might eventually become a global config option, if this is considered necessary. + //result = string.Intern(result); + + return result; + } + + /// <summary> + /// Writes the contents of a string into a given byte buffer. + /// </summary> + public static unsafe int StringToBytes(byte[] buffer, string value, bool needs16BitSupport) + { + int byteCount = needs16BitSupport ? value.Length * 2 : value.Length; + + if (buffer.Length < byteCount) + { + throw new ArgumentException("Buffer is not large enough to contain the given string; a size of at least " + byteCount + " is required."); + } + + GCHandle toHandle = default(GCHandle); + + try + { + toHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + + if (needs16BitSupport) + { + if (BitConverter.IsLittleEndian) + { + fixed (char* charPtr1 = value) + { + ushort* fromPtr1 = (ushort*)charPtr1; + ushort* toPtr1 = (ushort*)toHandle.AddrOfPinnedObject().ToPointer(); + + for (int i = 0; i < byteCount; i += sizeof(ushort)) + { + *toPtr1++ = *fromPtr1++; + } + } + } + else + { + fixed (char* charPtr2 = value) + { + byte* fromPtr2 = (byte*)charPtr2; + byte* toPtr2 = (byte*)toHandle.AddrOfPinnedObject().ToPointer(); + + for (int i = 0; i < byteCount; i += sizeof(ushort)) + { + *toPtr2 = *(fromPtr2 + 1); + *(toPtr2 + 1) = *fromPtr2; + + fromPtr2 += 2; + toPtr2 += 2; + } + } + } + } + else + { + if (BitConverter.IsLittleEndian) + { + fixed (char* charPtr3 = value) + { + byte* fromPtr3 = (byte*)charPtr3; + byte* toPtr3 = (byte*)toHandle.AddrOfPinnedObject().ToPointer(); + + for (int i = 0; i < byteCount; i += sizeof(byte)) + { + fromPtr3++; // Skip every other string byte + *toPtr3++ = *fromPtr3++; + } + } + } + else + { + fixed (char* charPtr4 = value) + { + byte* fromPtr4 = (byte*)charPtr4; + byte* toPtr4 = (byte*)toHandle.AddrOfPinnedObject().ToPointer(); + + for (int i = 0; i < byteCount; i += sizeof(byte)) + { + *toPtr4++ = *fromPtr4++; + fromPtr4++; // Skip every other string byte + } + } + } + } + } + finally + { + if (toHandle.IsAllocated) + { + toHandle.Free(); + } + } + + return byteCount; + } + + private struct Struct256Bit + { + public decimal d1; + public decimal d2; + } + + public static unsafe void MemoryCopy(void* from, void* to, int bytes) + { + byte* end = (byte*)to + bytes; + + Struct256Bit* fromBigPtr = (Struct256Bit*)from; + Struct256Bit* toBigPtr = (Struct256Bit*)to; + + while ((toBigPtr + 1) <= end) + { + *toBigPtr++ = *fromBigPtr++; + } + + byte* fromSmallPtr = (byte*)fromBigPtr; + byte* toSmallPtr = (byte*)toBigPtr; + + while (toSmallPtr < end) + { + *toSmallPtr++ = *fromSmallPtr++; + } + } + + /// <summary> + /// Blindly mem-copies a given number of bytes from the memory location of one object to another. WARNING: This method is ridiculously dangerous. Only use if you know what you're doing. + /// </summary> + public static unsafe void MemoryCopy(object from, object to, int byteCount, int fromByteOffset, int toByteOffset) + { + GCHandle fromHandle = default(GCHandle); + GCHandle toHandle = default(GCHandle); + + if (fromByteOffset % sizeof(ulong) != 0 || toByteOffset % sizeof(ulong) != 0) + { + throw new ArgumentException("Byte offset must be divisible by " + sizeof(ulong) + " (IE, sizeof(ulong))"); + } + + try + { + int restBytes = byteCount % sizeof(ulong); + int ulongCount = (byteCount - restBytes) / sizeof(ulong); + int fromOffsetCount = fromByteOffset / sizeof(ulong); + int toOffsetCount = toByteOffset / sizeof(ulong); + + fromHandle = GCHandle.Alloc(from, GCHandleType.Pinned); + toHandle = GCHandle.Alloc(to, GCHandleType.Pinned); + + ulong* fromUlongPtr = (ulong*)fromHandle.AddrOfPinnedObject().ToPointer(); + ulong* toUlongPtr = (ulong*)toHandle.AddrOfPinnedObject().ToPointer(); + + if (fromOffsetCount > 0) + { + fromUlongPtr += fromOffsetCount; + } + + if (toOffsetCount > 0) + { + toUlongPtr += toOffsetCount; + } + + for (int i = 0; i < ulongCount; i++) + { + *toUlongPtr++ = *fromUlongPtr++; + } + + if (restBytes > 0) + { + byte* fromBytePtr = (byte*)fromUlongPtr; + byte* toBytePtr = (byte*)toUlongPtr; + + for (int i = 0; i < restBytes; i++) + { + *toBytePtr++ = *fromBytePtr++; + } + } + } + finally + { + if (fromHandle.IsAllocated) + { + fromHandle.Free(); + } + + if (toHandle.IsAllocated) + { + toHandle.Free(); + } + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnsafeUtilities.cs.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnsafeUtilities.cs.meta new file mode 100644 index 00000000..2198f9c2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Utilities/Misc/UnsafeUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93b4d01199b118896c756b09a9206fc0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/VRC.Udon.Serialization.OdinSerializer.asmdef b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/VRC.Udon.Serialization.OdinSerializer.asmdef new file mode 100644 index 00000000..4e218bef --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/VRC.Udon.Serialization.OdinSerializer.asmdef @@ -0,0 +1,12 @@ +{ + "name": "VRC.Udon.Serialization.OdinSerializer", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [] +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/VRC.Udon.Serialization.OdinSerializer.asmdef.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/VRC.Udon.Serialization.OdinSerializer.asmdef.meta new file mode 100644 index 00000000..34cbdf85 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/VRC.Udon.Serialization.OdinSerializer.asmdef.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f70a94b0bedfa8ec50ed757f72032810 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Version.txt b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Version.txt new file mode 100644 index 00000000..ca930343 --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Version.txt @@ -0,0 +1 @@ +2019.11.4 - d0ad4a37
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Version.txt.meta b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Version.txt.meta new file mode 100644 index 00000000..1934c44d --- /dev/null +++ b/VRCSDK3Worlds/Assets/Udon/Serialization/OdinSerializer/Version.txt.meta @@ -0,0 +1,7 @@ + +fileFormatVersion: 2 +guid: 2105a6c0e5c0955d2d4a704c5e9c9b8f +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: |