Added Unity project files

This commit is contained in:
2026-06-07 16:58:24 +01:00
parent 3cc05d260b
commit 23bbcab156
3942 changed files with 453676 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bdfe5e856bdc1964b98db849426fd028
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,202 @@
using VRC.SDK3.Data;
using VRC.Udon.Serialization.OdinSerializer;
[assembly: RegisterFormatter(typeof(DataTokenFormatter))]
public class DataTokenFormatter : BaseFormatter<DataToken>
{
//DataTokenFormatter allows for Udon to serialize Data containers in field initializers and embed inside Udon assembly.
//This class exists in both the SDK project and the client project. Both versions should be identical and any changes to one needs to be duplicated to the other.
private static readonly Serializer<object> _referenceReaderWriter = Serializer.Get<object>();
protected override void DeserializeImplementation(ref DataToken value, IDataReader reader)
{
reader.ReadByte(out byte _type);
TokenType Type = (TokenType)_type;
switch (Type)
{
case TokenType.Boolean:
{
reader.ReadBoolean(out bool innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.SByte:
{
reader.ReadSByte(out sbyte innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.Byte:
{
reader.ReadByte(out byte innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.Short:
{
reader.ReadInt16(out short innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.UShort:
{
reader.ReadUInt16(out ushort innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.Int:
{
reader.ReadInt32(out int innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.UInt:
{
reader.ReadUInt32(out uint innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.Long:
{
reader.ReadInt64(out long innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.ULong:
{
reader.ReadUInt64(out ulong innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.Float:
{
reader.ReadSingle(out float innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.Double:
{
reader.ReadDouble(out double innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.String:
{
reader.ReadString(out string innerValue);
value = new DataToken(innerValue);
break;
}
case TokenType.DataDictionary:
{
value = new DataToken((DataDictionary)_referenceReaderWriter.ReadValue(reader));
break;
}
case TokenType.DataList:
{
value = new DataToken((DataList)_referenceReaderWriter.ReadValue(reader));
break;
}
case TokenType.Reference:
{
value = new DataToken(_referenceReaderWriter.ReadValue(reader));
break;
}
case TokenType.Error:
{
reader.ReadByte(out byte innerValue);
value = new DataToken((DataError)innerValue);
break;
}
}
}
protected override void SerializeImplementation(ref DataToken value, IDataWriter writer)
{
writer.WriteByte("_type", (byte)value.TokenType);
switch (value.TokenType)
{
case TokenType.Boolean:
{
writer.WriteBoolean("_boolean", value.Boolean);
break;
}
case TokenType.SByte:
{
writer.WriteSByte("_sbyte", value.SByte);
break;
}
case TokenType.Byte:
{
writer.WriteByte("_byte", value.Byte);
break;
}
case TokenType.Short:
{
writer.WriteInt16("_short", value.Short);
break;
}
case TokenType.UShort:
{
writer.WriteUInt16("_ushort", value.UShort);
break;
}
case TokenType.Int:
{
writer.WriteInt32("_int", value.Int);
break;
}
case TokenType.UInt:
{
writer.WriteUInt32("_uint", value.UInt);
break;
}
case TokenType.Long:
{
writer.WriteInt64("_long", value.Long);
break;
}
case TokenType.ULong:
{
writer.WriteUInt64("_ulong", value.ULong);
break;
}
case TokenType.Float:
{
writer.WriteSingle("_float", value.Float);
break;
}
case TokenType.Double:
{
writer.WriteDouble("_double", value.Double);
break;
}
case TokenType.String:
{
writer.WriteString("_string", value.String);
break;
}
case TokenType.DataDictionary:
{
_referenceReaderWriter.WriteValue(value.DataDictionary, writer);
break;
}
case TokenType.DataList:
{
_referenceReaderWriter.WriteValue(value.DataList, writer);
break;
}
case TokenType.Reference:
{
_referenceReaderWriter.WriteValue(value.Reference, writer);
break;
}
case TokenType.Error:
{
writer.WriteByte("_error", (byte)value.Error);
break;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e297f55d72e99c94abb5722781971931
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b1d0b8aa8084bcd44a572d524d7a31bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,59 @@
using VRC.Udon.Common;
using VRC.Udon.Common.Interfaces;
using VRC.Udon.Serialization.OdinSerializer;
using VRC.Udon.Serialization.Formatters;
[assembly: RegisterFormatter(typeof(UdonProgramFormatter))]
#if UNITY_6000_0_OR_NEWER
[assembly: BindTypeNameToType("UnityEngine.PhysicMaterialCombine, UnityEngine.PhysicsModule", typeof(UnityEngine.PhysicsMaterialCombine))]
[assembly: BindTypeNameToType("UnityEngine.PhysicMaterial, UnityEngine.PhysicsModule", typeof(UnityEngine.PhysicsMaterial))]
#endif
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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f2626352b2a60eb41adc3580ae44c750
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b4297afd7f7aed84393e952440959fea
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 807997f9a489fef46b3bbc241aaeb2f9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,213 @@
#if false
-----------------------------------------------------------------------
<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>
-----------------------------------------------------------------------
#if UNITY_EDITOR
namespace VRC.Udon.Serialization.OdinSerializer.Utilities.Editor
{
using System;
using System.Linq;
using UnityEditor;
public enum AssemblyImportSettings
{
BuildOnly,
EditorOnly,
BuildAndEditor,
Exclude,
}
public static class OdinSerializationBuiltSettingsConfig
{
public static readonly OdinSerializationBuiltSettings AOT = new AOTImportSettingsConfig();
public static readonly OdinSerializationBuiltSettings JIT = new JITImportSettingsConfig();
public static readonly OdinSerializationBuiltSettings EditorOnly = new EditorOnlyImportSettingsConfig();
public static OdinSerializationBuiltSettings Current
{
get
{
var buildGroup = EditorUserBuildSettings.selectedBuildTargetGroup;
#if UNITY_2022_3_OR_NEWER
NamedBuildTarget namedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(buildGroup);
var backend = PlayerSettings.GetScriptingBackend(namedBuildTarget);
#elif UNITY_5_6_OR_NEWER
var backend = PlayerSettings.GetScriptingBackend(buildGroup);
#else
var backend = (ScriptingImplementation)PlayerSettings.GetPropertyInt("ScriptingBackend", buildGroup);
#endif
if (backend != ScriptingImplementation.Mono2x)
{
return AOT;
}
var target = EditorUserBuildSettings.activeBuildTarget;
if (OdinAssemblyImportSettingsUtility.JITPlatforms.Any(p => p == target))
{
return JIT;
}
else
{
return AOT;
}
}
}
[MenuItem("Tools/Odin Serializer/Refresh Assembly Import Settings")]
public static void RefreshAssemblyImportSettings()
{
Current.Apply();
}
}
public static class OdinAssemblyImportSettingsUtility
{
public static readonly BuildTarget[] Platforms = Enum.GetValues(typeof(BuildTarget))
.Cast<BuildTarget>()
.Where(t => t >= 0 && typeof(BuildTarget).GetMember(t.ToString())[0].IsDefined(typeof(ObsoleteAttribute), false) == false)
.ToArray();
public static readonly BuildTarget[] JITPlatforms = new BuildTarget[]
{
BuildTarget.StandaloneOSX,
BuildTarget.StandaloneWindows,
BuildTarget.StandaloneWindows64,
BuildTarget.StandaloneLinux64,
BuildTarget.Android,
};
public static void ApplyImportSettings(string assemblyFilePath, AssemblyImportSettings importSettings)
{
bool includeInBuild = false;
bool includeInEditor = false;
switch (importSettings)
{
case AssemblyImportSettings.BuildAndEditor:
includeInBuild = true;
includeInEditor = true;
break;
case AssemblyImportSettings.BuildOnly:
includeInBuild = true;
break;
case AssemblyImportSettings.EditorOnly:
includeInEditor = true;
break;
case AssemblyImportSettings.Exclude:
break;
}
ApplyImportSettings(assemblyFilePath, includeInBuild, includeInEditor);
}
private static void ApplyImportSettings(string assemblyFilePath, bool includeInBuild, bool includeInEditor)
{
var importer = (PluginImporter)AssetImporter.GetAtPath(assemblyFilePath);
#if UNITY_5_6_OR_NEWER
if (importer.GetCompatibleWithAnyPlatform() != includeInBuild
|| Platforms.Any(p => importer.GetCompatibleWithPlatform(p) != includeInBuild)
|| (includeInBuild && importer.GetExcludeEditorFromAnyPlatform() != !includeInEditor || importer.GetCompatibleWithEditor()))
{
importer.ClearSettings();
importer.SetCompatibleWithAnyPlatform(includeInBuild);
Array.ForEach(Platforms,p => importer.SetCompatibleWithPlatform(p, includeInBuild));
if (includeInBuild)
{
importer.SetExcludeEditorFromAnyPlatform(!includeInEditor);
}
else
{
importer.SetCompatibleWithEditor(includeInEditor);
}
importer.SaveAndReimport();
}
#else
if (importer.GetCompatibleWithAnyPlatform() != includeInBuild
|| Platforms.Any(p => importer.GetCompatibleWithPlatform(p) != includeInBuild)
|| importer.GetCompatibleWithEditor() != includeInEditor)
{
importer.SetCompatibleWithAnyPlatform(includeInBuild);
Platforms.ForEach(p => importer.SetCompatibleWithPlatform(p, includeInBuild));
importer.SetCompatibleWithEditor(includeInEditor);
importer.SaveAndReimport();
}
#endif
}
}
public abstract class OdinSerializationBuiltSettings
{
protected const string NoEditorSerializationMeta = "Assets/Plugins/Sirenix/Assemblies/NoEditor/Sirenix.Serialization.dll";
protected const string NoEditorUtilityMeta = "Assets/Plugins/Sirenix/Assemblies/NoEditor/Sirenix.Utilities.dll";
protected const string NoEmitNoEditorSerializationMeta = "Assets/Plugins/Sirenix/Assemblies/NoEmitAndNoEditor/Sirenix.Serialization.dll";
protected const string NoEmitNoEditorUtilityMeta = "Assets/Plugins/Sirenix/Assemblies/NoEmitAndNoEditor/Sirenix.Utilities.dll";
protected const string SerializationConfigMeta = "Assets/Plugins/Sirenix/Assemblies/Sirenix.Serialization.Config.dll";
public abstract void Apply();
}
public class AOTImportSettingsConfig : OdinSerializationBuiltSettings
{
public override void Apply()
{
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEmitNoEditorSerializationMeta, AssemblyImportSettings.BuildOnly);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEmitNoEditorUtilityMeta, AssemblyImportSettings.BuildOnly);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEditorSerializationMeta, AssemblyImportSettings.Exclude);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEditorUtilityMeta, AssemblyImportSettings.Exclude);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(SerializationConfigMeta, AssemblyImportSettings.BuildAndEditor);
}
}
public class JITImportSettingsConfig : OdinSerializationBuiltSettings
{
public override void Apply()
{
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEditorSerializationMeta, AssemblyImportSettings.BuildOnly);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEditorUtilityMeta, AssemblyImportSettings.BuildOnly);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEmitNoEditorSerializationMeta, AssemblyImportSettings.Exclude);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEmitNoEditorUtilityMeta, AssemblyImportSettings.Exclude);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(SerializationConfigMeta, AssemblyImportSettings.BuildAndEditor);
}
}
public class EditorOnlyImportSettingsConfig : OdinSerializationBuiltSettings
{
public override void Apply()
{
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEditorSerializationMeta, AssemblyImportSettings.Exclude);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEditorUtilityMeta, AssemblyImportSettings.Exclude);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEmitNoEditorSerializationMeta, AssemblyImportSettings.Exclude);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(NoEmitNoEditorUtilityMeta, AssemblyImportSettings.Exclude);
OdinAssemblyImportSettingsUtility.ApplyImportSettings(SerializationConfigMeta, AssemblyImportSettings.EditorOnly);
}
}
}
#endif
#endif

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c57d4a1577bbb34c88b7a1190c26097
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bfaf18dca1f68cc99ebeb0b862179265
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 611bdb2531ad4264da1bf57af6963b7a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c9b8b0ed146a4f459d08999c54a9f8a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4ac1e1612275111bd50db8a3de8ba9c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,221 @@
//-----------------------------------------------------------------------
// <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)]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
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 nodes array. The amount of nodes contained in it is stored in the <see cref="NodeDepth"/> property. The remainder of the array's length is buffer space.
/// </summary>
/// <value>
/// The current node array.
/// </value>
protected NodeInfo[] NodesArray { get { return this.nodes; } }
/// <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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 501a7e1356f5fdc8e9bbefcd61a56490
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9638b18c6b6b6532b3b3cd3a73fefc2a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5c9619f514cdff2428036e02b47f6fa8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee0465a1838833eb878447b34339d4f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1bc9e2503afdd0290574ebc14cf4a16d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1361420bc2b384389a065fd2fe59fb22
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,370 @@
//-----------------------------------------------------------------------
// <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)]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
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();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc1fe25e670cf981ed66b3e85c3e4249
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,266 @@
//-----------------------------------------------------------------------
// <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)]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
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();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: af6696e41807b3c3f9a1d73667f76701
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5af9fb1a12e46e34989948edd205430c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,81 @@
//-----------------------------------------------------------------------
// <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. This is an old entry using an invalid data format where the ref string is dumped inline without escaping.
/// </summary>
public const string EXTERNAL_STRING_REF_SIG_OLD = "$strref";
/// <summary>
/// The beginning of the content of an external reference by string entry. This is a new entry using the valid format where the ref string is written as an escaped string.
/// </summary>
public const string EXTERNAL_STRING_REF_SIG_FIXED = "$fstrref";
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a3a6dce9e0b8317b3804b35f48f6a97
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ecc39ef0dc55ec10f83bb7eefd4f1db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,754 @@
//-----------------------------------------------------------------------
// <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_FIXED);
this.EnsureBufferSpace(id.Length + 3);
this.buffer[this.bufferIndex++] = (byte)':';
this.buffer[this.bufferIndex++] = (byte)'"';
this.Buffer_WriteString_WithEscape(id);
this.buffer[this.bufferIndex++] = (byte)'"';
}
/// <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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3e05b98a26be61fa9203d4a45bfc1e95
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,756 @@
//-----------------------------------------------------------------------
// <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_OLD, StringComparison.InvariantCulture))
{
// It's an external guid reference without a name, of the old broken kind
// The content is the whole buffer
name = null;
valueContent = new string(this.buffer, 0, this.bufferIndex + 1);
entry = EntryType.ExternalReferenceByString;
return;
}
if (string.Equals(name, JsonConfig.EXTERNAL_STRING_REF_SIG_FIXED, StringComparison.InvariantCulture))
{
// It's an external guid reference without a name, of the new non-broken kind
// The content is the buffer, unquoted and unescaped
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_OLD, StringComparison.InvariantCulture))
{
entry = EntryType.ExternalReferenceByString;
return;
}
else if (valueContent.StartsWith(JsonConfig.EXTERNAL_STRING_REF_SIG_FIXED, 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;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aad0a34e801ae645b359e4800ef7f636
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 44eda941bcc959347ae5b5185914213b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6a0f5e01b82ae0763f6f907157a2c9c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eab5938e837a8de93ce64c25399edde6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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 = "|";
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9321fb650525f4bed18119d187111569
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dd54f07c359d141095a031192c5ca084
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 232299f7185c9834a9afde7a9c808c07
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,126 @@
//-----------------------------------------------------------------------
// <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 System.Collections.Generic;
using VRC.Udon.Serialization.OdinSerializer;
using VRC.Udon.Serialization.OdinSerializer.Utilities;
[assembly: RegisterFormatterLocator(typeof(ArrayFormatterLocator), -80)]
namespace VRC.Udon.Serialization.OdinSerializer
{
using System;
internal class ArrayFormatterLocator : IFormatterLocator
{
// VRC Unity John: PER-818 - Introduce a type-keyed cache of created formatters to avoid creating duplicates (which was happening previously).
private static readonly Dictionary<Type, IFormatter> FormatterInstances = new(FastTypeComparer.Instance);
// VRC Unity John: PER-818 - end
public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter)
{
if (!type.IsArray)
{
formatter = null;
return false;
}
// VRC Unity John: PER-818 - Introduce a type-keyed cache of created formatters to avoid creating duplicates (which was happening previously).
// If we've serialized this type before, we'll have a cached formatter
if (FormatterInstances.TryGetValue(type, out formatter))
{
return true;
}
// VRC Unity John: PER-818 - end
var elementType = type.GetElementType();
if (type.GetArrayRank() == 1)
{
if (FormatterUtilities.IsPrimitiveArrayType(elementType))
{
#if false //vrc security patch
try
{
formatter = (IFormatter)Activator.CreateInstance(typeof(PrimitiveArrayFormatter<>).MakeGenericType(elementType));
}
catch (Exception ex)
{
#pragma warning disable CS0618 // Type or member is obsolete
if (allowWeakFallbackFormatters && (ex is ExecutionEngineException || ex.GetBaseException() is ExecutionEngineException))
#pragma warning restore CS0618 // Type or member is obsolete
{
formatter = new WeakPrimitiveArrayFormatter(type, elementType);
}
else throw;
}
#endif
formatter = (IFormatter)Activator.CreateInstance(typeof(PrimitiveArrayFormatter<>).MakeGenericType(elementType));
}
else
{
#if false //vrc security patch
try
{
formatter = (IFormatter)Activator.CreateInstance(typeof(ArrayFormatter<>).MakeGenericType(elementType));
}
catch (Exception ex)
{
#pragma warning disable CS0618 // Type or member is obsolete
if (allowWeakFallbackFormatters && (ex is ExecutionEngineException || ex.GetBaseException() is ExecutionEngineException))
#pragma warning restore CS0618 // Type or member is obsolete
{
formatter = new WeakArrayFormatter(type, elementType);
}
else throw;
}
#endif
formatter = (IFormatter)Activator.CreateInstance(typeof(ArrayFormatter<>).MakeGenericType(elementType));
}
}
else
{
#if false //vrc security patch
try
{
formatter = (IFormatter)Activator.CreateInstance(typeof(MultiDimensionalArrayFormatter<,>).MakeGenericType(type, type.GetElementType()));
}
catch (Exception ex)
{
#pragma warning disable CS0618 // Type or member is obsolete
if (allowWeakFallbackFormatters && (ex is ExecutionEngineException || ex.GetBaseException() is ExecutionEngineException))
#pragma warning restore CS0618 // Type or member is obsolete
{
formatter = new WeakMultiDimensionalArrayFormatter(type, elementType);
}
else throw;
}
#endif
formatter = (IFormatter)Activator.CreateInstance(typeof(MultiDimensionalArrayFormatter<,>).MakeGenericType(type, type.GetElementType()));
}
// VRC Unity John: PER-818 - Introduce a type-keyed cache of created formatters to avoid creating duplicates (which was happening previously).
FormatterInstances.Add(type, formatter);
// VRC Unity John: PER-818 - end
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0bdecd79f568c8a3252bb5a9f3e2acdc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,59 @@
//-----------------------------------------------------------------------
// <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;
}
#if false //vrc security patch
try
{
formatter = (IFormatter)Activator.CreateInstance(typeof(DelegateFormatter<>).MakeGenericType(type));
}
catch (Exception ex)
{
#pragma warning disable CS0618 // Type or member is obsolete
if (allowWeakFallbackFormatters && (ex is ExecutionEngineException || ex.GetBaseException() is ExecutionEngineException))
#pragma warning restore CS0618 // Type or member is obsolete
{
formatter = new WeakDelegateFormatter(type);
}
else throw;
}
#endif
formatter = (IFormatter)Activator.CreateInstance(typeof(DelegateFormatter<>).MakeGenericType(type));
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c4228cdbba89e2a5d52357b998c3387d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,710 @@
//-----------------------------------------------------------------------
// <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 StrongFormatters_LOCK = new object();
private static readonly object WeakFormatters_LOCK = new object();
private static readonly Dictionary<Type, IFormatter> FormatterInstances = new Dictionary<Type, IFormatter>(FastTypeComparer.Instance);
private static readonly DoubleLookupDictionary<Type, ISerializationPolicy, IFormatter> StrongTypeFormatterMap = new DoubleLookupDictionary<Type, ISerializationPolicy, IFormatter>(FastTypeComparer.Instance, ReferenceEqualityComparer<ISerializationPolicy>.Default);
private static readonly DoubleLookupDictionary<Type, ISerializationPolicy, IFormatter> WeakTypeFormatterMap = new DoubleLookupDictionary<Type, ISerializationPolicy, IFormatter>(FastTypeComparer.Instance, ReferenceEqualityComparer<ISerializationPolicy>.Default);
private struct FormatterInfo
{
public Type FormatterType;
public Type TargetType;
public Type WeakFallbackType;
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)]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
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)
{
IFormatter result;
if (type == null)
{
throw new ArgumentNullException("type");
}
if (policy == null)
{
policy = SerializationPolicies.Strict;
}
var lockObj = StrongFormatters_LOCK;
var formatterMap = StrongTypeFormatterMap;
lock (lockObj)
{
if (formatterMap.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);
}
formatterMap.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() + "'.", ex);
}
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;
Type weakFallbackType = null;
Type[] genericFormatterArgs = 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))
{
genericFormatterArgs = 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))
{
genericFormatterArgs = args;
}
}
if (formatterType == null && genericFormatterArgs != null)
{
formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(genericFormatterArgs);
weakFallbackType = info.WeakFallbackType;
}
if (formatterType != null)
{
#if false //vrc security patch
IFormatter instance = null;
bool aotError = false;
Exception aotEx = null;
try
{
instance = GetFormatterInstance(formatterType);
}
#pragma warning disable 618
catch (TargetInvocationException ex)
{
aotError = true;
aotEx = ex;
}
catch (TypeInitializationException ex)
{
aotError = true;
aotEx = ex;
}
catch (ExecutionEngineException ex)
{
aotError = true;
aotEx = ex;
}
#pragma warning restore 618
if (aotError && !EmitUtilities.CanEmit && allowWeakFormatters)
{
if (weakFallbackType != null)
{
instance = (IFormatter)Activator.CreateInstance(weakFallbackType, type);
}
if (instance == null)
{
string argsStr = "";
for (int j = 0; j < genericFormatterArgs.Length; j++)
{
if (j > 0) argsStr = argsStr + ", ";
argsStr = argsStr + genericFormatterArgs[j].GetNiceFullName();
}
Debug.LogError("No AOT support was generated for serialization formatter type '" + info.FormatterType.GetNiceFullName() + "' for the generic arguments <" + argsStr + ">, and no weak fallback formatter was specified.");
throw aotEx;
}
}
#endif
IFormatter 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
// VRC Unity John: PER-818 - Use the Formatter instance cache here too, to avoid creating duplicate ReflectionFormatter<T> instances.
if (!FormatterInstances.TryGetValue(type, out IFormatter reflectionFormatter))
{
reflectionFormatter = (IFormatter)Activator.CreateInstance(typeof(ReflectionFormatter<>).MakeGenericType(type));
FormatterInstances.Add(type, reflectionFormatter);
}
return reflectionFormatter;
// VRC Unity John: PER-818 end.
}
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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cf715e98fa96d74c81b4d3f4491d2592
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,60 @@
//-----------------------------------------------------------------------
// <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;
}
#if false //vrc security patch
try
{
formatter = (IFormatter)Activator.CreateInstance(typeof(GenericCollectionFormatter<,>).MakeGenericType(type, elementType));
}
catch (Exception ex)
{
#pragma warning disable CS0618 // Type or member is obsolete
if (allowWeakFallbackFormatters && (ex is ExecutionEngineException || ex.GetBaseException() is ExecutionEngineException))
#pragma warning restore CS0618 // Type or member is obsolete
{
formatter = new WeakGenericCollectionFormatter(type, elementType);
}
else throw;
}
#endif
formatter = (IFormatter)Activator.CreateInstance(typeof(GenericCollectionFormatter<,>).MakeGenericType(type, elementType));
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d35d0d1eb290f5d00e273d65e5db09d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f2a9beaeecdd6eb929ddb049d7846a14
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,62 @@
//-----------------------------------------------------------------------
// <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;
}
#if false //vrc security patch
try
{
formatter = (IFormatter)Activator.CreateInstance(typeof(SerializableFormatter<>).MakeGenericType(type));
}
catch (Exception ex)
{
#pragma warning disable CS0618 // Type or member is obsolete
if (allowWeakFallbackFormatters && (ex is ExecutionEngineException || ex.GetBaseException() is ExecutionEngineException))
#pragma warning restore CS0618 // Type or member is obsolete
{
formatter = new WeakSerializableFormatter(type);
}
else throw;
}
#endif
formatter = (IFormatter)Activator.CreateInstance(typeof(SerializableFormatter<>).MakeGenericType(type));
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cdd12b278851bfdc68ca0d9e1e4f2d28
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,63 @@
//-----------------------------------------------------------------------
// <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)
{
#if false //vrc security patch
try
{
formatter = (IFormatter)Activator.CreateInstance(typeof(SelfFormatterFormatter<>).MakeGenericType(type));
}
catch (Exception ex)
{
#pragma warning disable CS0618 // Type or member is obsolete
if (allowWeakFallbackFormatters && (ex is ExecutionEngineException || ex.GetBaseException() is ExecutionEngineException))
#pragma warning restore CS0618 // Type or member is obsolete
{
formatter = new WeakSelfFormatterFormatter(type);
}
else throw;
}
#endif
formatter = (IFormatter)Activator.CreateInstance(typeof(SelfFormatterFormatter<>).MakeGenericType(type));
return true;
}
return false;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 876ae9a404abe412e663fd9bc03d3525
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 00e10f526d476731ebc596ceb66271a6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a65e4386997bdeb42a6bc1c626d9f61c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,191 @@
//-----------------------------------------------------------------------
// <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
{
using System;
/// <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();
}
}
}
#if false //vrc security patch
public sealed class WeakArrayFormatter : WeakBaseFormatter
{
private readonly Serializer ValueReaderWriter;
private readonly Type ElementType;
public WeakArrayFormatter(Type arrayType, Type elementType) : base(arrayType)
{
this.ValueReaderWriter = Serializer.Get(elementType);
this.ElementType = elementType;
}
protected override object GetUninitializedObject()
{
return null;
}
protected override void DeserializeImplementation(ref object value, IDataReader reader)
{
string name;
var entry = reader.PeekEntry(out name);
if (entry == EntryType.StartOfArray)
{
long length;
reader.EnterArray(out length);
Array array = Array.CreateInstance(this.ElementType, length);
value = array;
// 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;
}
array.SetValue(ValueReaderWriter.ReadValueWeak(reader), i);
if (reader.PeekEntry(out name) == EntryType.EndOfStream)
{
break;
}
}
reader.ExitArray();
}
else
{
reader.SkipEntry();
}
}
protected override void SerializeImplementation(ref object value, IDataWriter writer)
{
Array array = (Array)value;
try
{
int length = array.Length;
writer.BeginArrayNode(length);
for (int i = 0; i < length; i++)
{
ValueReaderWriter.WriteValueWeak(array.GetValue(i), writer);
}
}
finally
{
writer.EndArrayNode();
}
}
}
#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9aaf14140a26e04b861b027d5ddb8fb6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3f5dc00eb17e568de42119a7f0f30ee8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,731 @@
//-----------------------------------------------------------------------
// <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)]
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
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);
}
#if false //vrc security pach
/// <summary>
/// Provides common functionality for serializing and deserializing weakly typed values of a given type, 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>
/// <seealso cref="IFormatter" />
public abstract class WeakBaseFormatter : IFormatter
{
protected delegate void SerializationCallback(object value, StreamingContext context);
protected readonly Type SerializedType;
protected readonly SerializationCallback[] OnSerializingCallbacks;
protected readonly SerializationCallback[] OnSerializedCallbacks;
protected readonly SerializationCallback[] OnDeserializingCallbacks;
protected readonly SerializationCallback[] OnDeserializedCallbacks;
protected readonly bool IsValueType;
protected readonly bool ImplementsISerializationCallbackReceiver;
protected readonly bool ImplementsIDeserializationCallback;
protected readonly bool ImplementsIObjectReference;
Type IFormatter.SerializedType { get { return this.SerializedType; } }
public WeakBaseFormatter(Type serializedType)
{
this.SerializedType = serializedType;
this.ImplementsISerializationCallbackReceiver = this.SerializedType.ImplementsOrInherits(typeof(UnityEngine.ISerializationCallbackReceiver));
this.ImplementsIDeserializationCallback = this.SerializedType.ImplementsOrInherits(typeof(IDeserializationCallback));
this.ImplementsIObjectReference = this.SerializedType.ImplementsOrInherits(typeof(IObjectReference));
if (this.SerializedType.ImplementsOrInherits(typeof(UnityEngine.Object)))
{
DefaultLoggers.DefaultLogger.LogWarning("A formatter has been created for the UnityEngine.Object type " + this.SerializedType.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 = this.SerializedType.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)
{
return (object value, StreamingContext context) =>
{
info.Invoke(value, null);
};
}
else if (parameters.Length == 1 && parameters[0].ParameterType == typeof(StreamingContext) && parameters[0].ParameterType.IsByRef == false)
{
return (object value, StreamingContext context) =>
{
info.Invoke(value, new object[] { context });
};
}
else
{
DefaultLoggers.DefaultLogger.LogWarning("The method " + info.GetNiceName() + " has an invalid signature and will be ignored by the serialization system.");
return null;
}
}
/// <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>
public void Serialize(object value, IDataWriter writer)
{
var context = writer.Context;
for (int i = 0; i < OnSerializingCallbacks.Length; i++)
{
try
{
OnSerializingCallbacks[i](value, context.StreamingContext);
}
catch (Exception ex)
{
context.Config.DebugContext.LogException(ex);
}
}
if (ImplementsISerializationCallbackReceiver)
{
try
{
UnityEngine.ISerializationCallbackReceiver v = value as UnityEngine.ISerializationCallbackReceiver;
v.OnBeforeSerialize();
value = 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](value, context.StreamingContext);
}
catch (Exception ex)
{
context.Config.DebugContext.LogException(ex);
}
}
}
/// <summary>
/// Deserializes a value using a specified <see cref="IDataReader" />.
/// </summary>
/// <param name="reader">The reader to use.</param>
/// <returns>
/// The deserialized value.
/// </returns>
public object Deserialize(IDataReader reader)
{
var context = reader.Context;
object 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 (this.IsValueType)
{
if (object.ReferenceEquals(null, value))
{
value = Activator.CreateInstance(this.SerializedType);
}
this.InvokeOnDeserializingCallbacks(value, context);
}
else
{
if (object.ReferenceEquals(value, null) == false)
{
this.RegisterReferenceID(value, reader);
this.InvokeOnDeserializingCallbacks(value, context);
if (ImplementsIObjectReference)
{
try
{
value = (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 (this.IsValueType || object.ReferenceEquals(value, null) == false)
{
for (int i = 0; i < OnDeserializedCallbacks.Length; i++)
{
try
{
OnDeserializedCallbacks[i](value, context.StreamingContext);
}
catch (Exception ex)
{
context.Config.DebugContext.LogException(ex);
}
}
if (ImplementsIDeserializationCallback)
{
IDeserializationCallback v = value as IDeserializationCallback;
v.OnDeserialization(this);
value = v;
}
if (ImplementsISerializationCallbackReceiver)
{
try
{
UnityEngine.ISerializationCallbackReceiver v = value as UnityEngine.ISerializationCallbackReceiver;
v.OnAfterDeserialize();
value = v;
}
catch (Exception ex)
{
context.Config.DebugContext.LogException(ex);
}
}
}
return value;
}
/// <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(object value, IDataReader reader)
{
if (!this.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>
protected void InvokeOnDeserializingCallbacks(object value, DeserializationContext context)
{
for (int i = 0; i < OnDeserializingCallbacks.Length; i++)
{
try
{
OnDeserializingCallbacks[i](value, context.StreamingContext);
}
catch (Exception ex)
{
context.Config.DebugContext.LogException(ex);
}
}
}
protected virtual object GetUninitializedObject()
{
return this.IsValueType ? Activator.CreateInstance(this.SerializedType) : FormatterServices.GetUninitializedObject(this.SerializedType);
}
/// <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 object 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 object value, IDataWriter writer);
}
#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9598679c29f3e3696941746c26f1ccf8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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());
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dff51bfb9b4d71aa78b3e5c8fec8c924
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3480954e7eecdc9745c1d08721b2f8b3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,396 @@
//-----------------------------------------------------------------------
// <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 class DelegateFormatter<T> : BaseFormatter<T> where T : class
{
public readonly Type DelegateType;
public DelegateFormatter(Type delegateType)
{
if (typeof(Delegate).IsAssignableFrom(delegateType) == false)
{
throw new ArgumentException("The type " + delegateType + " is not a delegate.");
}
this.DelegateType = delegateType;
}
/// <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)
{
#if false //vrc security patch
string name;
EntryType entry;
Type delegateType = this.DelegateType;
Type declaringType = null;
object target = null;
string methodName = null;
Type[] signature = null;
Type[] genericArguments = null;
Delegate[] invocationList = null;
while ((entry = reader.PeekEntry(out name)) != EntryType.EndOfNode && entry != EntryType.EndOfArray && entry != EntryType.EndOfStream)
{
switch (name)
{
case "invocationList":
{
invocationList = DelegateArraySerializer.ReadValue(reader);
}
break;
case "target":
{
target = ObjectSerializer.ReadValue(reader);
}
break;
case "declaringType":
{
var t = TypeSerializer.ReadValue(reader);
if (t != null)
{
declaringType = t;
}
}
break;
case "methodName":
{
methodName = StringSerializer.ReadValue(reader);
}
break;
case "delegateType":
{
var t = TypeSerializer.ReadValue(reader);
if (t != null)
{
delegateType = t;
}
}
break;
case "signature":
{
signature = TypeArraySerializer.ReadValue(reader);
}
break;
case "genericArguments":
{
genericArguments = TypeArraySerializer.ReadValue(reader);
}
break;
default:
reader.SkipEntry();
break;
}
}
if (invocationList != null)
{
Delegate combinedDelegate = null;
try
{
combinedDelegate = Delegate.Combine(invocationList);
}
catch (Exception ex)
{
reader.Context.Config.DebugContext.LogError("Recombining delegate invocation list upon deserialization failed with an exception of type " + ex.GetType().GetNiceFullName() + " with the message: " + ex.Message);
}
if (combinedDelegate != null)
{
try
{
value = (T)(object)combinedDelegate;
}
catch (InvalidCastException)
{
reader.Context.Config.DebugContext.LogWarning("Could not cast recombined delegate of type " + combinedDelegate.GetType().GetNiceFullName() + " to expected delegate type " + this.DelegateType.GetNiceFullName() + ".");
}
}
return;
}
if (declaringType == null)
{
reader.Context.Config.DebugContext.LogWarning("Missing declaring type of delegate on deserialize.");
return;
}
if (methodName == null)
{
reader.Context.Config.DebugContext.LogError("Missing method name of delegate on deserialize.");
return;
}
MethodInfo methodInfo;
bool useSignature = false;
bool wasAmbiguous = false;
if (signature != null)
{
useSignature = true;
for (int i = 0; i < signature.Length; i++)
{
if (signature[i] == null)
{
useSignature = false;
break;
}
}
}
if (useSignature)
{
try
{
methodInfo = declaringType.GetMethod(methodName, Flags.AllMembers, null, signature, null);
}
catch (AmbiguousMatchException)
{
methodInfo = null;
wasAmbiguous = true;
}
}
else
{
try
{
methodInfo = declaringType.GetMethod(methodName, Flags.AllMembers);
}
catch (AmbiguousMatchException)
{
methodInfo = null;
wasAmbiguous = true;
}
}
if (methodInfo == null)
{
if (useSignature)
{
reader.Context.Config.DebugContext.LogWarning("Could not find method with signature " + name + "(" + string.Join(", ", signature.Select(p => p.GetNiceFullName()).ToArray()) + ") on type '" + declaringType.FullName + (wasAmbiguous ? "; resolution was ambiguous between multiple methods" : string.Empty) + ".");
}
else
{
reader.Context.Config.DebugContext.LogWarning("Could not find method with name " + name + " on type '" + declaringType.GetNiceFullName() + (wasAmbiguous ? "; resolution was ambiguous between multiple methods" : string.Empty) + ".");
}
return;
}
if (methodInfo.IsGenericMethodDefinition)
{
if (genericArguments == null)
{
reader.Context.Config.DebugContext.LogWarning("Method '" + declaringType.GetNiceFullName() + "." + methodInfo.GetNiceName() + "' of delegate to deserialize is a generic method definition, but no generic arguments were in the serialization data.");
return;
}
int argCount = methodInfo.GetGenericArguments().Length;
if (genericArguments.Length != argCount)
{
reader.Context.Config.DebugContext.LogWarning("Method '" + declaringType.GetNiceFullName() + "." + methodInfo.GetNiceName() + "' of delegate to deserialize is a generic method definition, but there is the wrong number of generic arguments in the serialization data.");
return;
}
for (int i = 0; i < genericArguments.Length; i++)
{
if (genericArguments[i] == null)
{
reader.Context.Config.DebugContext.LogWarning("Method '" + declaringType.GetNiceFullName() + "." + methodInfo.GetNiceName() + "' of delegate to deserialize is a generic method definition, but one of the serialized generic argument types failed to bind on deserialization.");
return;
}
}
try
{
methodInfo = methodInfo.MakeGenericMethod(genericArguments);
}
catch (Exception ex)
{
reader.Context.Config.DebugContext.LogWarning("Method '" + declaringType.GetNiceFullName() + "." + methodInfo.GetNiceName() + "' of delegate to deserialize is a generic method definition, but failed to create generic method from definition, using generic arguments '" + string.Join(", ", genericArguments.Select(p => p.GetNiceFullName()).ToArray()) + "'. Method creation failed with an exception of type " + ex.GetType().GetNiceFullName() + ", with the message: " + ex.Message);
return;
}
}
if (methodInfo.IsStatic)
{
value = (T)(object)Delegate.CreateDelegate(delegateType, methodInfo, false);
}
else
{
Type targetType = methodInfo.DeclaringType;
if (typeof(UnityEngine.Object).IsAssignableFrom(targetType))
{
if ((target as UnityEngine.Object) == null)
{
reader.Context.Config.DebugContext.LogWarning("Method '" + declaringType.GetNiceFullName() + "." + methodInfo.GetNiceName() + "' of delegate to deserialize is an instance method, but Unity object target of type '" + targetType.GetNiceFullName() + "' was null on deserialization. Did something destroy it, or did you apply a delegate value targeting a scene-based UnityEngine.Object instance to a prefab?");
return;
}
}
else
{
if (object.ReferenceEquals(target, null))
{
reader.Context.Config.DebugContext.LogWarning("Method '" + declaringType.GetNiceFullName() + "." + methodInfo.GetNiceName() + "' of delegate to deserialize is an instance method, but no valid instance target of type '" + targetType.GetNiceFullName() + "' was in the serialization data. Has something been renamed since serialization?");
return;
}
}
value = (T)(object)Delegate.CreateDelegate(delegateType, target, methodInfo, false);
}
if (value == null)
{
reader.Context.Config.DebugContext.LogWarning("Failed to create delegate of type " + delegateType.GetNiceFullName() + " from method '" + declaringType.GetNiceFullName() + "." + methodInfo.GetNiceName() + "'.");
return;
}
this.RegisterReferenceID(value, reader);
this.InvokeOnDeserializingCallbacks(ref value, reader.Context);
#else
reader.Context.Config.DebugContext.LogWarning("Delegate Deserialization has been removed for security."); //VRC
#endif
}
/// <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 false //vrc security patch
Delegate del = (Delegate)(object)value;
Delegate[] invocationList = del.GetInvocationList();
if (invocationList.Length > 1)
{
// We're serializing an invocation list, not a single delegate
// Serialize that array of delegates instead
DelegateArraySerializer.WriteValue("invocationList", invocationList, writer);
return;
}
// We're serializing just one delegate invocation
MethodInfo methodInfo = del.Method;
if (methodInfo.GetType().Name.Contains("DynamicMethod"))
{
writer.Context.Config.DebugContext.LogError("Cannot serialize delegate made from dynamically emitted method " + methodInfo + ".");
return;
}
if (methodInfo.IsGenericMethodDefinition)
{
writer.Context.Config.DebugContext.LogError("Cannot serialize delegate made from the unresolved generic method definition " + methodInfo + "; how did this even happen? It should not even be possible to have a delegate for a generic method definition that hasn't been turned into a generic method yet.");
return;
}
if (del.Target != null)
{
ObjectSerializer.WriteValue("target", del.Target, writer);
}
TypeSerializer.WriteValue("declaringType", methodInfo.DeclaringType, writer);
StringSerializer.WriteValue("methodName", methodInfo.Name, writer);
TypeSerializer.WriteValue("delegateType", del.GetType(), writer);
ParameterInfo[] parameters;
if (methodInfo.IsGenericMethod)
{
parameters = methodInfo.GetGenericMethodDefinition().GetParameters();
}
else
{
parameters = methodInfo.GetParameters();
}
Type[] signature = new Type[parameters.Length];
for (int i = 0; i < signature.Length; i++)
{
signature[i] = parameters[i].ParameterType;
}
TypeArraySerializer.WriteValue("signature", signature, writer);
if (methodInfo.IsGenericMethod)
{
Type[] genericArguments = methodInfo.GetGenericArguments();
TypeArraySerializer.WriteValue("genericArguments", genericArguments, writer);
}
#endif
writer.Context.Config.DebugContext.LogWarning("Delegate Deserialization has been removed for security."); //VRC
}
/// <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;
}
}
#if false //vrc security patch
public class WeakDelegateFormatter : DelegateFormatter<Delegate>
{
public WeakDelegateFormatter(Type delegateType) : base(delegateType)
{
}
}
#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4f17b17e986ae6f3be6a2ea1b716fcaf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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<,,>), priority: -1)]
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();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4402da708267c579874c808a813bfe62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,435 @@
//-----------------------------------------------------------------------
// <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 Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
/// <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();
}
}
}
#if false //vrc security patch
internal sealed class WeakDictionaryFormatter : WeakBaseFormatter
{
private readonly bool KeyIsValueType;
private readonly Serializer EqualityComparerSerializer;
private readonly Serializer KeyReaderWriter;
private readonly Serializer ValueReaderWriter;
private readonly ConstructorInfo ComparerConstructor;
private readonly PropertyInfo ComparerProperty;
private readonly PropertyInfo CountProperty;
private readonly Type KeyType;
private readonly Type ValueType;
public WeakDictionaryFormatter(Type serializedType) : base(serializedType)
{
var args = serializedType.GetArgumentsOfInheritedOpenGenericClass(typeof(Dictionary<,>));
this.KeyType = args[0];
this.ValueType = args[1];
this.KeyIsValueType = this.KeyType.IsValueType;
this.KeyReaderWriter = Serializer.Get(this.KeyType);
this.ValueReaderWriter = Serializer.Get(this.ValueType);
this.CountProperty = serializedType.GetProperty("Count");
if (this.CountProperty == null)
{
throw new SerializationAbortException("Can't serialize/deserialize the type " + serializedType.GetNiceFullName() + " because it has no accessible Count property.");
}
try
{
// There's a very decent chance this type exists already and won't throw AOT-related exceptions
var equalityComparerType = typeof(IEqualityComparer<>).MakeGenericType(this.KeyType);
this.EqualityComparerSerializer = Serializer.Get(equalityComparerType);
this.ComparerConstructor = serializedType.GetConstructor(new Type[] { equalityComparerType });
this.ComparerProperty = serializedType.GetProperty("Comparer");
}
catch (Exception)
{
// This is allowed to fail though, so just use fallbacks in that case
this.EqualityComparerSerializer = Serializer.Get<object>();
this.ComparerConstructor = null;
this.ComparerProperty = null;
}
}
protected override object GetUninitializedObject()
{
return null;
}
protected override void DeserializeImplementation(ref object value, IDataReader reader)
{
string name;
var entry = reader.PeekEntry(out name);
object comparer = null;
if (name == "comparer" || entry == EntryType.StartOfNode)
{
// There is a comparer serialized
comparer = EqualityComparerSerializer.ReadValueWeak(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 = ComparerConstructor.Invoke(new object[] { comparer });
}
else
{
value = Activator.CreateInstance(this.SerializedType);
}
IDictionary dict = (IDictionary)value;
// 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);
object key = KeyReaderWriter.ReadValueWeak(reader);
object val = ValueReaderWriter.ReadValueWeak(reader);
if (!KeyIsValueType && object.ReferenceEquals(key, null))
{
reader.Context.Config.DebugContext.LogWarning("Dictionary key of type '" + this.KeyType.FullName + "' was null upon deserialization. A key has gone missing.");
continue;
}
dict[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();
}
}
protected override void SerializeImplementation(ref object value, IDataWriter writer)
{
try
{
IDictionary dict = (IDictionary)value;
if (this.ComparerProperty != null)
{
object comparer = this.ComparerProperty.GetValue(value, null);
if (!object.ReferenceEquals(comparer, null))
{
EqualityComparerSerializer.WriteValueWeak("comparer", comparer, writer);
}
}
writer.BeginArrayNode((int)this.CountProperty.GetValue(value, null));
var enumerator = dict.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
bool endNode = true;
try
{
writer.BeginStructNode(null, null);
KeyReaderWriter.WriteValueWeak("$k", enumerator.Key, writer);
ValueReaderWriter.WriteValueWeak("$v", enumerator.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
{
enumerator.Reset();
IDisposable dispose = enumerator as IDisposable;
if (dispose != null) dispose.Dispose();
}
}
finally
{
writer.EndArrayNode();
}
}
}
#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b80567603fe814a8b4341584f8c3b4a6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,322 @@
//-----------------------------------------------------------------------
// <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;
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();
}
}
}
#if false //vrc security patch
internal sealed class WeakDoubleLookupDictionaryFormatter : WeakBaseFormatter
{
private readonly Serializer PrimaryReaderWriter;
private readonly Serializer InnerReaderWriter;
public WeakDoubleLookupDictionaryFormatter(Type serializedType) : base(serializedType)
{
var args = serializedType.GetArgumentsOfInheritedOpenGenericClass(typeof(Dictionary<,>));
this.PrimaryReaderWriter = Serializer.Get(args[0]);
this.InnerReaderWriter = Serializer.Get(args[1]);
}
protected override object GetUninitializedObject()
{
return null;
}
protected override void SerializeImplementation(ref object value, IDataWriter writer)
{
try
{
var dict = (IDictionary)value;
writer.BeginArrayNode(dict.Count);
bool endNode = true;
var enumerator = dict.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
try
{
writer.BeginStructNode(null, null);
PrimaryReaderWriter.WriteValueWeak("$k", enumerator.Key, writer);
InnerReaderWriter.WriteValueWeak("$v", enumerator.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
{
enumerator.Reset();
IDisposable dispose = enumerator as IDisposable;
if (dispose != null) dispose.Dispose();
}
}
finally
{
writer.EndArrayNode();
}
}
protected override void DeserializeImplementation(ref object 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 = Activator.CreateInstance(this.SerializedType);
var dict = (IDictionary)value;
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);
object key = PrimaryReaderWriter.ReadValueWeak(reader);
object inner = InnerReaderWriter.ReadValueWeak(reader);
dict.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();
}
}
}
#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c21ee7e54dff531da57563e2f81fff5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 54578488936f8484c97ba7c52bfb0563
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
{
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e226537cbfa910681132da3718f41c34
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 149c482b2ab9c601b8bc2ecc20bbd8d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,611 @@
//-----------------------------------------------------------------------
// <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>
/// Used for generating unique formatter helper type names.
/// </summary>
private static int helperFormatterNameId;
/// <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___" +
System.Threading.Interlocked.Increment(ref helperFormatterNameId);
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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b7da6bf97199e0bb743f7639c17112ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,316 @@
//-----------------------------------------------------------------------
// <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;
using System.Collections.Generic;
using System.Reflection;
/// <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();
}
}
}
#if false //vrc security patch
public sealed class WeakGenericCollectionFormatter : WeakBaseFormatter
{
private readonly Serializer ValueReaderWriter;
private readonly Type ElementType;
private readonly PropertyInfo CountProperty;
private readonly MethodInfo AddMethod;
public WeakGenericCollectionFormatter(Type collectionType, Type elementType) : base(collectionType)
{
this.ElementType = elementType;
this.CountProperty = collectionType.GetProperty("Count", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
this.AddMethod = collectionType.GetMethod("Add", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { elementType }, null);
if (this.AddMethod == null)
{
throw new ArgumentException("Cannot treat the type " + collectionType.Name + " as a generic collection since it has no accessible Add method.");
}
if (this.CountProperty == null || this.CountProperty.PropertyType != typeof(int))
{
throw new ArgumentException("Cannot treat the type " + collectionType.Name + " as a generic collection since it has no accessible Count property.");
}
Type e;
if (GenericCollectionFormatter.CanFormat(collectionType, out e) == false)
{
throw new ArgumentException("Cannot treat the type " + collectionType.Name + " as a generic collection.");
}
if (e != elementType)
{
throw new ArgumentException("Type " + elementType.Name + " is not the element type of the generic collection type " + collectionType.Name + ".");
}
}
/// <summary>
/// Gets a new object of type <see cref="T" />.
/// </summary>
/// <returns>
/// A new object of type <see cref="T" />.
/// </returns>
protected override object GetUninitializedObject()
{
return Activator.CreateInstance(this.SerializedType);
}
/// <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 object 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;
}
var addParams = new object[1];
try
{
addParams[0] = ValueReaderWriter.ReadValueWeak(reader);
AddMethod.Invoke(value, addParams);
}
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 object value, IDataWriter writer)
{
try
{
writer.BeginArrayNode((int)CountProperty.GetValue(value, null));
foreach (var element in (IEnumerable)value)
{
ValueReaderWriter.WriteValueWeak(element, writer);
}
}
finally
{
writer.EndArrayNode();
}
}
}
#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 06ccb8250c692f2695d28bfd6bcd4273
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,266 @@
//-----------------------------------------------------------------------
// <copyright file="HashSetFormatter.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 Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
/// <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();
}
}
}
#if false //vrc security patch
public class WeakHashSetFormatter : WeakBaseFormatter
{
private readonly Serializer ElementSerializer;
private readonly MethodInfo AddMethod;
private readonly PropertyInfo CountProperty;
public WeakHashSetFormatter(Type serializedType) : base(serializedType)
{
var args = serializedType.GetArgumentsOfInheritedOpenGenericClass(typeof(HashSet<>));
this.ElementSerializer = Serializer.Get(args[0]);
this.AddMethod = serializedType.GetMethod("Add", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { args[0] }, null);
this.CountProperty = serializedType.GetProperty("Count", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
if (this.AddMethod == null)
{
throw new SerializationAbortException("Can't serialize/deserialize hashset of type '" + serializedType.GetNiceFullName() + "' since a proper Add method wasn't found.");
}
if (this.CountProperty == null)
{
throw new SerializationAbortException("Can't serialize/deserialize hashset of type '" + serializedType.GetNiceFullName() + "' since a proper Count property wasn't found.");
}
}
/// <summary>
/// Returns null.
/// </summary>
/// <returns>
/// A null value.
/// </returns>
protected override object 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 object value, IDataReader reader)
{
string name;
var entry = reader.PeekEntry(out name);
if (entry == EntryType.StartOfArray)
{
try
{
long length;
reader.EnterArray(out length);
value = Activator.CreateInstance(this.SerializedType);
// We must remember to register the hashset reference ourselves, since we return null in GetUninitializedObject
this.RegisterReferenceID(value, reader);
var addParams = new object[1];
// 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;
}
addParams[0] = ElementSerializer.ReadValueWeak(reader);
this.AddMethod.Invoke(value, addParams);
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 object value, IDataWriter writer)
{
try
{
writer.BeginArrayNode((int)this.CountProperty.GetValue(value, null));
foreach (object item in ((IEnumerable)value))
{
try
{
ElementSerializer.WriteValueWeak(item, writer);
}
catch (Exception ex)
{
writer.Context.Config.DebugContext.LogException(ex);
}
}
}
finally
{
writer.EndArrayNode();
}
}
}
#endif
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f1eaa1b43658215b6d81013928eac19e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0fcb040f1c493dc2a5224e446be8b3c9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More