Add VRChat and GitHub packages to Unity project
This commit is contained in:
1 parent
0334a52b21
commit
85538aaccc
3297 files changed
+847877
No files matched your search
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e398f0715f0456d4dbd437b07bf527a9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ac1e1612275111bd50db8a3de8ba9c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="BaseDataReaderWriter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Implements functionality that is shared by both data readers and data writers.
|
||||
/// </summary>
|
||||
public abstract class BaseDataReaderWriter
|
||||
{
|
||||
// Once, there was a stack here. But stacks are slow, so now there's no longer
|
||||
// a stack here and we just do it ourselves.
|
||||
private NodeInfo[] nodes = new NodeInfo[32];
|
||||
private int nodesLength = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the context's or writer's serialization binder.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The reader's or writer's serialization binder.
|
||||
/// </value>
|
||||
[Obsolete("Use the Binder member on the writer's SerializationContext/DeserializationContext instead.", error: false)]
|
||||
public TwoWaySerializationBinder Binder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this is IDataWriter)
|
||||
{
|
||||
return (this as IDataWriter).Context.Binder;
|
||||
}
|
||||
else if (this is IDataReader)
|
||||
{
|
||||
return (this as IDataReader).Context.Binder;
|
||||
}
|
||||
|
||||
return TwoWaySerializationBinder.Default;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (this is IDataWriter)
|
||||
{
|
||||
(this as IDataWriter).Context.Binder = value;
|
||||
}
|
||||
else if (this is IDataReader)
|
||||
{
|
||||
(this as IDataReader).Context.Binder = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the reader or writer is in an array node.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the reader or writer is in an array node; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsInArrayNode { get { return this.nodesLength == 0 ? false : this.nodes[this.nodesLength - 1].IsArray; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current node depth. In other words, the current count of the node stack.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The current node depth.
|
||||
/// </value>
|
||||
protected int NodeDepth { get { return this.nodesLength; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current node, or <see cref="NodeInfo.Empty"/> if there is no current node.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The current node.
|
||||
/// </value>
|
||||
protected NodeInfo CurrentNode { get { return this.nodesLength == 0 ? NodeInfo.Empty : this.nodes[this.nodesLength - 1]; } }
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a node onto the node stack.
|
||||
/// </summary>
|
||||
/// <param name="node">The node to push.</param>
|
||||
protected void PushNode(NodeInfo node)
|
||||
{
|
||||
if (this.nodesLength == this.nodes.Length)
|
||||
{
|
||||
this.ExpandNodes();
|
||||
}
|
||||
|
||||
this.nodes[this.nodesLength] = node;
|
||||
this.nodesLength++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a node with the given name, id and type onto the node stack.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the node.</param>
|
||||
/// <param name="id">The id of the node.</param>
|
||||
/// <param name="type">The type of the node.</param>
|
||||
protected void PushNode(string name, int id, Type type)
|
||||
{
|
||||
if (this.nodesLength == this.nodes.Length)
|
||||
{
|
||||
this.ExpandNodes();
|
||||
}
|
||||
|
||||
this.nodes[this.nodesLength] = new NodeInfo(name, id, type, false);
|
||||
this.nodesLength++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes an array node onto the node stack. This uses values from the current node to provide extra info about the array node.
|
||||
/// </summary>
|
||||
protected void PushArray()
|
||||
{
|
||||
if (this.nodesLength == this.nodes.Length)
|
||||
{
|
||||
this.ExpandNodes();
|
||||
}
|
||||
|
||||
if (this.nodesLength == 0 || this.nodes[this.nodesLength - 1].IsArray)
|
||||
{
|
||||
this.nodes[this.nodesLength] = new NodeInfo(null, -1, null, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var current = this.nodes[this.nodesLength - 1];
|
||||
this.nodes[this.nodesLength] = new NodeInfo(current.Name, current.Id, current.Type, true);
|
||||
}
|
||||
|
||||
this.nodesLength++;
|
||||
}
|
||||
|
||||
private void ExpandNodes()
|
||||
{
|
||||
var newArr = new NodeInfo[this.nodes.Length * 2];
|
||||
|
||||
var oldNodes = this.nodes;
|
||||
|
||||
for (int i = 0; i < oldNodes.Length; i++)
|
||||
{
|
||||
newArr[i] = oldNodes[i];
|
||||
}
|
||||
|
||||
this.nodes = newArr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops the current node off of the node stack.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the node to pop.</param>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// There are no nodes to pop.
|
||||
/// or
|
||||
/// Tried to pop node with given name, but the current node's name was different.
|
||||
/// </exception>
|
||||
protected void PopNode(string name)
|
||||
{
|
||||
if (this.nodesLength == 0)
|
||||
{
|
||||
throw new InvalidOperationException("There are no nodes to pop.");
|
||||
}
|
||||
|
||||
// @Speedup - this safety isn't worth the performance hit, and never happens with properly written writers
|
||||
//var current = this.CurrentNode;
|
||||
|
||||
//if (current.Name != name)
|
||||
//{
|
||||
// throw new InvalidOperationException("Tried to pop node with name " + name + " but current node's name is " + current.Name);
|
||||
//}
|
||||
|
||||
this.nodesLength--;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops the current node if the current node is an array node.
|
||||
/// </summary>
|
||||
protected void PopArray()
|
||||
{
|
||||
if (this.nodesLength == 0)
|
||||
{
|
||||
throw new InvalidOperationException("There are no nodes to pop.");
|
||||
}
|
||||
|
||||
if (this.nodes[this.nodesLength - 1].IsArray == false)
|
||||
{
|
||||
throw new InvalidOperationException("Was not in array when exiting array.");
|
||||
}
|
||||
|
||||
this.nodesLength--;
|
||||
}
|
||||
|
||||
protected void ClearNodes()
|
||||
{
|
||||
this.nodesLength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 501a7e1356f5fdc8e9bbefcd61a56490
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9638b18c6b6b6532b3b3cd3a73fefc2a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcdd8c6da1c47fc49bfb91079ff2426f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+2775
File diff suppressed because it is too large.
Load diff
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee0465a1838833eb878447b34339d4f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+2202
File diff suppressed because it is too large.
Load diff
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bc9e2503afdd0290574ebc14cf4a16d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+286
@@ -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
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1361420bc2b384389a065fd2fe59fb22
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,369 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="IDataReader.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a set of methods for reading data stored in a format written by a corresponding <see cref="IDataWriter"/> class.
|
||||
/// <para />
|
||||
/// If you implement this interface, it is VERY IMPORTANT that you implement each method to the *exact* specifications the documentation specifies.
|
||||
/// <para />
|
||||
/// It is strongly recommended to inherit from the <see cref="BaseDataReader"/> class if you wish to implement a new data reader.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
public interface IDataReader : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the reader's serialization binder.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The reader's serialization binder.
|
||||
/// </value>
|
||||
TwoWaySerializationBinder Binder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base stream of the reader.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The base stream of the reader.
|
||||
/// </value>
|
||||
[Obsolete("Data readers and writers don't necessarily have streams any longer, so this API has been made obsolete. Using this property may result in NotSupportedExceptions being thrown.", false)]
|
||||
Stream Stream { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the reader is in an array node.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the reader is in an array node; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
bool IsInArrayNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the current node.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the current node.
|
||||
/// </value>
|
||||
string CurrentNodeName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current node id. If this is less than zero, the current node has no id.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The current node id.
|
||||
/// </value>
|
||||
int CurrentNodeId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current node depth. In other words, the current count of the node stack.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The current node depth.
|
||||
/// </value>
|
||||
int CurrentNodeDepth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the deserialization context.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The deserialization context.
|
||||
/// </value>
|
||||
DeserializationContext Context { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dump of the data being read by the writer. The format of this dump varies, but should be useful for debugging purposes.
|
||||
/// </summary>
|
||||
string GetDataDump();
|
||||
|
||||
/// <summary>
|
||||
/// Tries to enter a node. This will succeed if the next entry is an <see cref="EntryType.StartOfNode"/>.
|
||||
/// <para />
|
||||
/// This call MUST (eventually) be followed by a corresponding call to <see cref="IDataReader.ExitNode(DeserializationContext)"/>
|
||||
/// <para />
|
||||
/// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> properties to the correct values for the current node.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the node. This value will be null if there was no metadata, or if the reader's serialization binder failed to resolve the type name.</param>
|
||||
/// <returns><c>true</c> if entering a node succeeded, otherwise <c>false</c></returns>
|
||||
bool EnterNode(out Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Exits the current node. This method will keep skipping entries using <see cref="IDataReader.SkipEntry(DeserializationContext)"/> until an <see cref="EntryType.EndOfNode"/> is reached, or the end of the stream is reached.
|
||||
/// <para />
|
||||
/// This call MUST have been preceded by a corresponding call to <see cref="IDataReader.EnterNode(out Type)"/>.
|
||||
/// <para />
|
||||
/// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> to the correct values for the node that was prior to the current node.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if the method exited a node, <c>false</c> if it reached the end of the stream.</returns>
|
||||
bool ExitNode();
|
||||
|
||||
/// <summary>
|
||||
/// Tries to enters an array node. This will succeed if the next entry is an <see cref="EntryType.StartOfArray"/>.
|
||||
/// <para />
|
||||
/// This call MUST (eventually) be followed by a corresponding call to <see cref="IDataReader.ExitArray(DeserializationContext)"/>
|
||||
/// <para />
|
||||
/// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> properties to the correct values for the current array node.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the array that was entered.</param>
|
||||
/// <returns><c>true</c> if an array was entered, otherwise <c>false</c></returns>
|
||||
bool EnterArray(out long length);
|
||||
|
||||
/// <summary>
|
||||
/// Exits the closest array. This method will keep skipping entries using <see cref="IDataReader.SkipEntry(DeserializationContext)"/> until an <see cref="EntryType.EndOfArray"/> is reached, or the end of the stream is reached.
|
||||
/// <para />
|
||||
/// This call MUST have been preceded by a corresponding call to <see cref="IDataReader.EnterArray(out long)"/>.
|
||||
/// <para />
|
||||
/// This call will change the values of the <see cref="IDataReader.IsInArrayNode"/>, <see cref="IDataReader.CurrentNodeName"/>, <see cref="IDataReader.CurrentNodeId"/> and <see cref="IDataReader.CurrentNodeDepth"/> to the correct values for the node that was prior to the exited array node.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if the method exited an array, <c>false</c> if it reached the end of the stream.</returns>
|
||||
bool ExitArray();
|
||||
|
||||
/// <summary>
|
||||
/// Reads a primitive array value. This call will succeed if the next entry is an <see cref="EntryType.PrimitiveArray"/>.
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)"/>.</typeparam>
|
||||
/// <param name="array">The resulting primitive array.</param>
|
||||
/// <returns><c>true</c> if reading a primitive array succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadPrimitiveArray<T>(out T[] array) where T : struct;
|
||||
|
||||
/// <summary>
|
||||
/// Peeks ahead and returns the type of the next entry in the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the next entry, if it has one.</param>
|
||||
/// <returns>The type of the next entry.</returns>
|
||||
EntryType PeekEntry(out string name);
|
||||
|
||||
/// <summary>
|
||||
/// Reads an internal reference id. This call will succeed if the next entry is an <see cref="EntryType.InternalReference"/>.
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The internal reference id.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadInternalReference(out int id);
|
||||
|
||||
/// <summary>
|
||||
/// Reads an external reference index. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByIndex"/>.
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="index">The external reference index.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadExternalReference(out int index);
|
||||
|
||||
/// <summary>
|
||||
/// Reads an external reference guid. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByGuid"/>.
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="guid">The external reference guid.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadExternalReference(out Guid guid);
|
||||
|
||||
/// <summary>
|
||||
/// Reads an external reference string. This call will succeed if the next entry is an <see cref="EntryType.ExternalReferenceByString"/>.
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The external reference string.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadExternalReference(out string id);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="char"/> value. This call will succeed if the next entry is an <see cref="EntryType.String"/>.
|
||||
/// <para />
|
||||
/// If the string of the entry is longer than 1 character, the first character of the string will be taken as the result.
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadChar(out char value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="string"/> value. This call will succeed if the next entry is an <see cref="EntryType.String"/>.
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadString(out string value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="Guid"/> value. This call will succeed if the next entry is an <see cref="EntryType.Guid"/>.
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadGuid(out Guid value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads an <see cref="sbyte"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>.
|
||||
/// <para />
|
||||
/// If the value of the stored integer is smaller than <see cref="sbyte.MinValue"/> or larger than <see cref="sbyte.MaxValue"/>, the result will be default(<see cref="sbyte"/>).
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadSByte(out sbyte value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="short"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>.
|
||||
/// <para />
|
||||
/// If the value of the stored integer is smaller than <see cref="short.MinValue"/> or larger than <see cref="short.MaxValue"/>, the result will be default(<see cref="short"/>).
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadInt16(out short value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads an <see cref="int"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>.
|
||||
/// <para />
|
||||
/// If the value of the stored integer is smaller than <see cref="int.MinValue"/> or larger than <see cref="int.MaxValue"/>, the result will be default(<see cref="int"/>).
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadInt32(out int value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="long"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>.
|
||||
/// <para />
|
||||
/// If the value of the stored integer is smaller than <see cref="long.MinValue"/> or larger than <see cref="long.MaxValue"/>, the result will be default(<see cref="long"/>).
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadInt64(out long value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="byte"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>.
|
||||
/// <para />
|
||||
/// If the value of the stored integer is smaller than <see cref="byte.MinValue"/> or larger than <see cref="byte.MaxValue"/>, the result will be default(<see cref="byte"/>).
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadByte(out byte value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads an <see cref="ushort"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>.
|
||||
/// <para />
|
||||
/// If the value of the stored integer is smaller than <see cref="ushort.MinValue"/> or larger than <see cref="ushort.MaxValue"/>, the result will be default(<see cref="ushort"/>).
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadUInt16(out ushort value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads an <see cref="uint"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>.
|
||||
/// <para />
|
||||
/// If the value of the stored integer is smaller than <see cref="uint.MinValue"/> or larger than <see cref="uint.MaxValue"/>, the result will be default(<see cref="uint"/>).
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadUInt32(out uint value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads an <see cref="ulong"/> value. This call will succeed if the next entry is an <see cref="EntryType.Integer"/>.
|
||||
/// <para />
|
||||
/// If the value of the stored integer is smaller than <see cref="ulong.MinValue"/> or larger than <see cref="ulong.MaxValue"/>, the result will be default(<see cref="ulong"/>).
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadUInt64(out ulong value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="decimal"/> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint"/> or an <see cref="EntryType.Integer"/>.
|
||||
/// <para />
|
||||
/// If the stored integer or floating point value is smaller than <see cref="decimal.MinValue"/> or larger than <see cref="decimal.MaxValue"/>, the result will be default(<see cref="decimal"/>).
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadDecimal(out decimal value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="float"/> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint"/> or an <see cref="EntryType.Integer"/>.
|
||||
/// <para />
|
||||
/// If the stored integer or floating point value is smaller than <see cref="float.MinValue"/> or larger than <see cref="float.MaxValue"/>, the result will be default(<see cref="float"/>).
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadSingle(out float value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="double"/> value. This call will succeed if the next entry is an <see cref="EntryType.FloatingPoint"/> or an <see cref="EntryType.Integer"/>.
|
||||
/// <para />
|
||||
/// If the stored integer or floating point value is smaller than <see cref="double.MinValue"/> or larger than <see cref="double.MaxValue"/>, the result will be default(<see cref="double"/>).
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadDouble(out double value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="bool"/> value. This call will succeed if the next entry is an <see cref="EntryType.Boolean"/>.
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value that has been read.</param>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadBoolean(out bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <c>null</c> value. This call will succeed if the next entry is an <see cref="EntryType.Null"/>.
|
||||
/// <para />
|
||||
/// If the call fails (and returns <c>false</c>), it will skip the current entry value, unless that entry is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if reading the value succeeded, otherwise <c>false</c></returns>
|
||||
bool ReadNull();
|
||||
|
||||
/// <summary>
|
||||
/// Skips the next entry value, unless it is an <see cref="EntryType.EndOfNode"/> or an <see cref="EntryType.EndOfArray"/>. If the next entry value is an <see cref="EntryType.StartOfNode"/> or an <see cref="EntryType.StartOfArray"/>, all of its contents will be processed, deserialized and registered in the deserialization context, so that internal reference values are not lost to entries further down the stream.
|
||||
/// </summary>
|
||||
void SkipEntry();
|
||||
|
||||
/// <summary>
|
||||
/// Tells the reader that a new serialization session is about to begin, and that it should clear all cached values left over from any prior serialization sessions.
|
||||
/// This method is only relevant when the same reader is used to deserialize several different, unrelated values.
|
||||
/// </summary>
|
||||
void PrepareNewSerializationSession();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc1fe25e670cf981ed66b3e85c3e4249
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,265 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="IDataWriter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a set of methods for reading data stored in a format that can be read by a corresponding <see cref="IDataReader"/> class.
|
||||
/// <para />
|
||||
/// If you implement this interface, it is VERY IMPORTANT that you implement each method to the *exact* specifications the documentation specifies.
|
||||
/// <para />
|
||||
/// It is strongly recommended to inherit from the <see cref="BaseDataWriter"/> class if you wish to implement a new data writer.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
public interface IDataWriter : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the reader's serialization binder.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The reader's serialization binder.
|
||||
/// </value>
|
||||
TwoWaySerializationBinder Binder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base stream of the writer.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The base stream of the writer.
|
||||
/// </value>
|
||||
[Obsolete("Data readers and writers don't necessarily have streams any longer, so this API has been made obsolete. Using this property may result in NotSupportedExceptions being thrown.", false)]
|
||||
Stream Stream { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the writer is in an array node.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the writer is in an array node; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
bool IsInArrayNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the serialization context.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The serialization context.
|
||||
/// </value>
|
||||
SerializationContext Context { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dump of the data currently written by the writer. The format of this dump varies, but should be useful for debugging purposes.
|
||||
/// </summary>
|
||||
string GetDataDump();
|
||||
|
||||
/// <summary>
|
||||
/// Flushes everything that has been written so far to the writer's base stream.
|
||||
/// </summary>
|
||||
void FlushToStream();
|
||||
|
||||
/// <summary>
|
||||
/// Writes the beginning of a reference node.
|
||||
/// <para />
|
||||
/// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)"/>, with the same name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the reference node.</param>
|
||||
/// <param name="type">The type of the reference node. If null, no type metadata will be written.</param>
|
||||
/// <param name="id">The id of the reference node. This id is acquired by calling <see cref="SerializationContext.TryRegisterInternalReference(object, out int)"/>.</param>
|
||||
void BeginReferenceNode(string name, Type type, int id);
|
||||
|
||||
/// <summary>
|
||||
/// Begins a struct/value type node. This is essentially the same as a reference node, except it has no internal reference id.
|
||||
/// <para />
|
||||
/// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)"/>, with the same name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the struct node.</param>
|
||||
/// <param name="type">The type of the struct node. If null, no type metadata will be written.</param>
|
||||
void BeginStructNode(string name, Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Ends the current node with the given name. If the current node has another name, an <see cref="InvalidOperationException"/> is thrown.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the node to end. This has to be the name of the current node.</param>
|
||||
void EndNode(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Begins an array node of the given length.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the array to come.</param>
|
||||
void BeginArrayNode(long length);
|
||||
|
||||
/// <summary>
|
||||
/// Ends the current array node, if the current node is an array node.
|
||||
/// </summary>
|
||||
void EndArrayNode();
|
||||
|
||||
/// <summary>
|
||||
/// Writes a primitive array to the stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)"/>.</typeparam>
|
||||
/// <param name="array">The primitive array to write.</param>
|
||||
void WritePrimitiveArray<T>(T[] array) where T : struct;
|
||||
|
||||
/// <summary>
|
||||
/// Writes a null value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
void WriteNull(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an internal reference to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="id">The value to write.</param>
|
||||
void WriteInternalReference(string name, int id);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an external index reference to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="index">The value to write.</param>
|
||||
void WriteExternalReference(string name, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an external guid reference to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="guid">The value to write.</param>
|
||||
void WriteExternalReference(string name, Guid guid);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an external string reference to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="id">The value to write.</param>
|
||||
void WriteExternalReference(string name, string id);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="char"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteChar(string name, char value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="string"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteString(string name, string value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="Guid"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteGuid(string name, Guid value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="sbyte"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteSByte(string name, sbyte value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="short"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteInt16(string name, short value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="int"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteInt32(string name, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="long"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteInt64(string name, long value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="byte"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteByte(string name, byte value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="ushort"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteUInt16(string name, ushort value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="uint"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteUInt32(string name, uint value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="ulong"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteUInt64(string name, ulong value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="decimal"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteDecimal(string name, decimal value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="float"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteSingle(string name, float value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="double"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteDouble(string name, double value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="bool"/> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
void WriteBoolean(string name, bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Tells the writer that a new serialization session is about to begin, and that it should clear all cached values left over from any prior serialization sessions.
|
||||
/// This method is only relevant when the same writer is used to serialize several different, unrelated values.
|
||||
/// </summary>
|
||||
void PrepareNewSerializationSession();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af6696e41807b3c3f9a1d73667f76701
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 054dc2e045b6cae4a9b4edf8fb6c7cf3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,76 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="JsonConfig.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains various string constants used by the <see cref="JsonDataWriter"/>, <see cref="JsonDataReader"/> and <see cref="JsonTextReader"/> classes.
|
||||
/// </summary>
|
||||
public static class JsonConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The named of a node id entry.
|
||||
/// </summary>
|
||||
public const string ID_SIG = "$id";
|
||||
|
||||
/// <summary>
|
||||
/// The name of a type entry.
|
||||
/// </summary>
|
||||
public const string TYPE_SIG = "$type";
|
||||
|
||||
/// <summary>
|
||||
/// The name of a regular array length entry.
|
||||
/// </summary>
|
||||
public const string REGULAR_ARRAY_LENGTH_SIG = "$rlength";
|
||||
|
||||
/// <summary>
|
||||
/// The name of a primitive array length entry.
|
||||
/// </summary>
|
||||
public const string PRIMITIVE_ARRAY_LENGTH_SIG = "$plength";
|
||||
|
||||
/// <summary>
|
||||
/// The name of a regular array content entry.
|
||||
/// </summary>
|
||||
public const string REGULAR_ARRAY_CONTENT_SIG = "$rcontent";
|
||||
|
||||
/// <summary>
|
||||
/// The name of a primitive array content entry.
|
||||
/// </summary>
|
||||
public const string PRIMITIVE_ARRAY_CONTENT_SIG = "$pcontent";
|
||||
|
||||
/// <summary>
|
||||
/// The beginning of the content of an internal reference entry.
|
||||
/// </summary>
|
||||
public const string INTERNAL_REF_SIG = "$iref";
|
||||
|
||||
/// <summary>
|
||||
/// The beginning of the content of an external reference by index entry.
|
||||
/// </summary>
|
||||
public const string EXTERNAL_INDEX_REF_SIG = "$eref";
|
||||
|
||||
/// <summary>
|
||||
/// The beginning of the content of an external reference by guid entry.
|
||||
/// </summary>
|
||||
public const string EXTERNAL_GUID_REF_SIG = "$guidref";
|
||||
|
||||
/// <summary>
|
||||
/// The beginning of the content of an external reference by string entry.
|
||||
/// </summary>
|
||||
public const string EXTERNAL_STRING_REF_SIG = "$strref";
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a3a6dce9e0b8317b3804b35f48f6a97
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+1268
File diff suppressed because it is too large.
Load diff
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ecc39ef0dc55ec10f83bb7eefd4f1db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+747
@@ -0,0 +1,747 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="JsonDataWriter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Writes json data to a stream that can be read by a <see cref="JsonDataReader"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="BaseDataWriter" />
|
||||
public class JsonDataWriter : BaseDataWriter
|
||||
{
|
||||
private static readonly uint[] ByteToHexCharLookup = CreateByteToHexLookup();
|
||||
private static readonly string NEW_LINE = Environment.NewLine;
|
||||
|
||||
private bool justStarted;
|
||||
private bool forceNoSeparatorNextLine;
|
||||
|
||||
//private StringBuilder escapeStringBuilder;
|
||||
//private StreamWriter writer;
|
||||
|
||||
private Dictionary<Type, Delegate> primitiveTypeWriters;
|
||||
private Dictionary<Type, int> seenTypes = new Dictionary<Type, int>(16);
|
||||
|
||||
private byte[] buffer = new byte[1024 * 100];
|
||||
private int bufferIndex = 0;
|
||||
|
||||
public JsonDataWriter() : this(null, null, true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonDataWriter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="stream">The base stream of the writer.</param>
|
||||
/// <param name="context">The serialization context to use.</param>>
|
||||
/// <param name="formatAsReadable">Whether the json should be packed, or formatted as human-readable.</param>
|
||||
public JsonDataWriter(Stream stream, SerializationContext context, bool formatAsReadable = true) : base(stream, context)
|
||||
{
|
||||
this.FormatAsReadable = formatAsReadable;
|
||||
this.justStarted = true;
|
||||
this.EnableTypeOptimization = true;
|
||||
|
||||
this.primitiveTypeWriters = new Dictionary<Type, Delegate>()
|
||||
{
|
||||
{ typeof(char), (Action<string, char>)this.WriteChar },
|
||||
{ typeof(sbyte), (Action<string, sbyte>)this.WriteSByte },
|
||||
{ typeof(short), (Action<string, short>)this.WriteInt16 },
|
||||
{ typeof(int), (Action<string, int>)this.WriteInt32 },
|
||||
{ typeof(long), (Action<string, long>)this.WriteInt64 },
|
||||
{ typeof(byte), (Action<string, byte>)this.WriteByte },
|
||||
{ typeof(ushort), (Action<string, ushort>)this.WriteUInt16 },
|
||||
{ typeof(uint), (Action<string, uint>)this.WriteUInt32 },
|
||||
{ typeof(ulong), (Action<string, ulong>)this.WriteUInt64 },
|
||||
{ typeof(decimal), (Action<string, decimal>)this.WriteDecimal },
|
||||
{ typeof(bool), (Action<string, bool>)this.WriteBoolean },
|
||||
{ typeof(float), (Action<string, float>)this.WriteSingle },
|
||||
{ typeof(double), (Action<string, double>)this.WriteDouble },
|
||||
{ typeof(Guid), (Action<string, Guid>)this.WriteGuid }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the json should be packed, or formatted as human-readable.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the json should be formatted as human-readable; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool FormatAsReadable;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to enable an optimization that ensures any given type name is only written once into the json stream, and thereafter kept track of by ID.
|
||||
/// </summary>
|
||||
public bool EnableTypeOptimization;
|
||||
|
||||
/// <summary>
|
||||
/// Enable the "just started" flag, causing the writer to start a new "base" json object container.
|
||||
/// </summary>
|
||||
public void MarkJustStarted()
|
||||
{
|
||||
this.justStarted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes everything that has been written so far to the writer's base stream.
|
||||
/// </summary>
|
||||
public override void FlushToStream()
|
||||
{
|
||||
if (this.bufferIndex > 0)
|
||||
{
|
||||
this.Stream.Write(this.buffer, 0, this.bufferIndex);
|
||||
this.bufferIndex = 0;
|
||||
}
|
||||
|
||||
base.FlushToStream();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the beginning of a reference node.
|
||||
/// <para />
|
||||
/// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)" />, with the same name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the reference node.</param>
|
||||
/// <param name="type">The type of the reference node. If null, no type metadata will be written.</param>
|
||||
/// <param name="id">The id of the reference node. This id is acquired by calling <see cref="SerializationContext.TryRegisterInternalReference(object, out int)" />.</param>
|
||||
public override void BeginReferenceNode(string name, Type type, int id)
|
||||
{
|
||||
this.WriteEntry(name, "{");
|
||||
this.PushNode(name, id, type);
|
||||
this.forceNoSeparatorNextLine = true;
|
||||
this.WriteInt32(JsonConfig.ID_SIG, id);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
this.WriteTypeEntry(type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins a struct/value type node. This is essentially the same as a reference node, except it has no internal reference id.
|
||||
/// <para />
|
||||
/// This call MUST eventually be followed by a corresponding call to <see cref="IDataWriter.EndNode(string)" />, with the same name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the struct node.</param>
|
||||
/// <param name="type">The type of the struct node. If null, no type metadata will be written.</param>
|
||||
public override void BeginStructNode(string name, Type type)
|
||||
{
|
||||
this.WriteEntry(name, "{");
|
||||
this.PushNode(name, -1, type);
|
||||
this.forceNoSeparatorNextLine = true;
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
this.WriteTypeEntry(type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the current node with the given name. If the current node has another name, an <see cref="InvalidOperationException" /> is thrown.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the node to end. This has to be the name of the current node.</param>
|
||||
public override void EndNode(string name)
|
||||
{
|
||||
this.PopNode(name);
|
||||
this.StartNewLine(true);
|
||||
|
||||
this.EnsureBufferSpace(1);
|
||||
this.buffer[this.bufferIndex++] = (byte)'}';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins an array node of the given length.
|
||||
/// </summary>
|
||||
/// <param name="length">The length of the array to come.</param>
|
||||
public override void BeginArrayNode(long length)
|
||||
{
|
||||
this.WriteInt64(JsonConfig.REGULAR_ARRAY_LENGTH_SIG, length);
|
||||
this.WriteEntry(JsonConfig.REGULAR_ARRAY_CONTENT_SIG, "[");
|
||||
this.forceNoSeparatorNextLine = true;
|
||||
this.PushArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the current array node, if the current node is an array node.
|
||||
/// </summary>
|
||||
public override void EndArrayNode()
|
||||
{
|
||||
this.PopArray();
|
||||
this.StartNewLine(true);
|
||||
|
||||
this.EnsureBufferSpace(1);
|
||||
this.buffer[this.bufferIndex++] = (byte)']';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a primitive array to the stream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the primitive array. Valid element types can be determined using <see cref="FormatterUtilities.IsPrimitiveArrayType(Type)" />.</typeparam>
|
||||
/// <param name="array">The primitive array to write.</param>
|
||||
/// <exception cref="System.ArgumentException">Type + typeof(T).Name + is not a valid primitive array type.</exception>
|
||||
/// <exception cref="System.ArgumentNullException">array</exception>
|
||||
public override void WritePrimitiveArray<T>(T[] array)
|
||||
{
|
||||
if (FormatterUtilities.IsPrimitiveArrayType(typeof(T)) == false)
|
||||
{
|
||||
throw new ArgumentException("Type " + typeof(T).Name + " is not a valid primitive array type.");
|
||||
}
|
||||
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException("array");
|
||||
}
|
||||
|
||||
Action<string, T> writer = (Action<string, T>)this.primitiveTypeWriters[typeof(T)];
|
||||
|
||||
this.WriteInt64(JsonConfig.PRIMITIVE_ARRAY_LENGTH_SIG, array.Length);
|
||||
this.WriteEntry(JsonConfig.PRIMITIVE_ARRAY_CONTENT_SIG, "[");
|
||||
this.forceNoSeparatorNextLine = true;
|
||||
this.PushArray();
|
||||
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
writer(null, array[i]);
|
||||
}
|
||||
|
||||
this.PopArray();
|
||||
this.StartNewLine(true);
|
||||
|
||||
this.EnsureBufferSpace(1);
|
||||
this.buffer[this.bufferIndex++] = (byte)']';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="bool" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteBoolean(string name, bool value)
|
||||
{
|
||||
this.WriteEntry(name, value ? "true" : "false");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="byte" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteByte(string name, byte value)
|
||||
{
|
||||
this.WriteUInt64(name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="char" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteChar(string name, char value)
|
||||
{
|
||||
this.WriteString(name, value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="decimal" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteDecimal(string name, decimal value)
|
||||
{
|
||||
this.WriteEntry(name, value.ToString("G", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="double" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteDouble(string name, double value)
|
||||
{
|
||||
this.WriteEntry(name, value.ToString("R", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="int" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteInt32(string name, int value)
|
||||
{
|
||||
this.WriteInt64(name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="long" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteInt64(string name, long value)
|
||||
{
|
||||
this.WriteEntry(name, value.ToString("D", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a null value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
public override void WriteNull(string name)
|
||||
{
|
||||
this.WriteEntry(name, "null");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an internal reference to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="id">The value to write.</param>
|
||||
public override void WriteInternalReference(string name, int id)
|
||||
{
|
||||
this.WriteEntry(name, JsonConfig.INTERNAL_REF_SIG + ":" + id.ToString("D", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="sbyte" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteSByte(string name, sbyte value)
|
||||
{
|
||||
this.WriteInt64(name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="short" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteInt16(string name, short value)
|
||||
{
|
||||
this.WriteInt64(name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="float" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteSingle(string name, float value)
|
||||
{
|
||||
this.WriteEntry(name, value.ToString("R", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="string" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteString(string name, string value)
|
||||
{
|
||||
this.StartNewLine();
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
this.EnsureBufferSpace(name.Length + value.Length + 6);
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)'"';
|
||||
|
||||
for (int i = 0; i < name.Length; i++)
|
||||
{
|
||||
this.buffer[this.bufferIndex++] = (byte)name[i];
|
||||
}
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)'"';
|
||||
this.buffer[this.bufferIndex++] = (byte)':';
|
||||
|
||||
if (this.FormatAsReadable)
|
||||
{
|
||||
this.buffer[this.bufferIndex++] = (byte)' ';
|
||||
}
|
||||
}
|
||||
else this.EnsureBufferSpace(value.Length + 2);
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)'"';
|
||||
|
||||
this.Buffer_WriteString_WithEscape(value);
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)'"';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="Guid" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteGuid(string name, Guid value)
|
||||
{
|
||||
this.WriteEntry(name, value.ToString("D", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="uint" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteUInt32(string name, uint value)
|
||||
{
|
||||
this.WriteUInt64(name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="ulong" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteUInt64(string name, ulong value)
|
||||
{
|
||||
this.WriteEntry(name, value.ToString("D", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an external index reference to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="index">The value to write.</param>
|
||||
public override void WriteExternalReference(string name, int index)
|
||||
{
|
||||
this.WriteEntry(name, JsonConfig.EXTERNAL_INDEX_REF_SIG + ":" + index.ToString("D", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an external guid reference to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="guid">The value to write.</param>
|
||||
public override void WriteExternalReference(string name, Guid guid)
|
||||
{
|
||||
this.WriteEntry(name, JsonConfig.EXTERNAL_GUID_REF_SIG + ":" + guid.ToString("D", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an external string reference to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="id">The value to write.</param>
|
||||
public override void WriteExternalReference(string name, string id)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
|
||||
this.WriteEntry(name, JsonConfig.EXTERNAL_STRING_REF_SIG + ":" + id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an <see cref="ushort" /> value to the stream.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the value. If this is null, no name will be written.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
public override void WriteUInt16(string name, ushort value)
|
||||
{
|
||||
this.WriteUInt64(name, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes all resources kept by the data writer, except the stream, which can be reused later.
|
||||
/// </summary>
|
||||
public override void Dispose()
|
||||
{
|
||||
//this.writer.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tells the writer that a new serialization session is about to begin, and that it should clear all cached values left over from any prior serialization sessions.
|
||||
/// This method is only relevant when the same writer is used to serialize several different, unrelated values.
|
||||
/// </summary>
|
||||
public override void PrepareNewSerializationSession()
|
||||
{
|
||||
base.PrepareNewSerializationSession();
|
||||
this.seenTypes.Clear();
|
||||
this.justStarted = true;
|
||||
}
|
||||
|
||||
public override string GetDataDump()
|
||||
{
|
||||
if (!this.Stream.CanRead)
|
||||
{
|
||||
return "Json data stream for writing cannot be read; cannot dump data.";
|
||||
}
|
||||
|
||||
if (!this.Stream.CanSeek)
|
||||
{
|
||||
return "Json data stream cannot seek; cannot dump data.";
|
||||
}
|
||||
|
||||
var oldPosition = this.Stream.Position;
|
||||
|
||||
var bytes = new byte[oldPosition];
|
||||
|
||||
this.Stream.Position = 0;
|
||||
this.Stream.Read(bytes, 0, (int)oldPosition);
|
||||
|
||||
this.Stream.Position = oldPosition;
|
||||
|
||||
return "Json: " + Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
private void WriteEntry(string name, string contents)
|
||||
{
|
||||
this.StartNewLine();
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
this.EnsureBufferSpace(name.Length + contents.Length + 4);
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)'"';
|
||||
|
||||
for (int i = 0; i < name.Length; i++)
|
||||
{
|
||||
this.buffer[this.bufferIndex++] = (byte)name[i];
|
||||
}
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)'"';
|
||||
this.buffer[this.bufferIndex++] = (byte)':';
|
||||
|
||||
if (this.FormatAsReadable)
|
||||
{
|
||||
this.buffer[this.bufferIndex++] = (byte)' ';
|
||||
}
|
||||
}
|
||||
else this.EnsureBufferSpace(contents.Length);
|
||||
|
||||
for (int i = 0; i < contents.Length; i++)
|
||||
{
|
||||
this.buffer[this.bufferIndex++] = (byte)contents[i];
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteEntry(string name, string contents, char surroundContentsWith)
|
||||
{
|
||||
this.StartNewLine();
|
||||
|
||||
if (name != null)
|
||||
{
|
||||
this.EnsureBufferSpace(name.Length + contents.Length + 6);
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)'"';
|
||||
|
||||
for (int i = 0; i < name.Length; i++)
|
||||
{
|
||||
this.buffer[this.bufferIndex++] = (byte)name[i];
|
||||
}
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)'"';
|
||||
this.buffer[this.bufferIndex++] = (byte)':';
|
||||
|
||||
if (this.FormatAsReadable)
|
||||
{
|
||||
this.buffer[this.bufferIndex++] = (byte)' ';
|
||||
}
|
||||
}
|
||||
else this.EnsureBufferSpace(contents.Length + 2);
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)surroundContentsWith;
|
||||
|
||||
for (int i = 0; i < contents.Length; i++)
|
||||
{
|
||||
this.buffer[this.bufferIndex++] = (byte)contents[i];
|
||||
}
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)surroundContentsWith;
|
||||
}
|
||||
|
||||
private void WriteTypeEntry(Type type)
|
||||
{
|
||||
int id;
|
||||
|
||||
if (this.EnableTypeOptimization)
|
||||
{
|
||||
if (this.seenTypes.TryGetValue(type, out id))
|
||||
{
|
||||
this.WriteInt32(JsonConfig.TYPE_SIG, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
id = this.seenTypes.Count;
|
||||
this.seenTypes.Add(type, id);
|
||||
this.WriteString(JsonConfig.TYPE_SIG, id + "|" + this.Context.Binder.BindToName(type, this.Context.Config.DebugContext));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.WriteString(JsonConfig.TYPE_SIG, this.Context.Binder.BindToName(type, this.Context.Config.DebugContext));
|
||||
}
|
||||
}
|
||||
|
||||
private void StartNewLine(bool noSeparator = false)
|
||||
{
|
||||
if (this.justStarted)
|
||||
{
|
||||
this.justStarted = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (noSeparator == false && this.forceNoSeparatorNextLine == false)
|
||||
{
|
||||
this.EnsureBufferSpace(1);
|
||||
this.buffer[this.bufferIndex++] = (byte)',';
|
||||
}
|
||||
|
||||
this.forceNoSeparatorNextLine = false;
|
||||
|
||||
if (this.FormatAsReadable)
|
||||
{
|
||||
int count = this.NodeDepth * 4;
|
||||
|
||||
this.EnsureBufferSpace(NEW_LINE.Length + count);
|
||||
|
||||
for (int i = 0; i < NEW_LINE.Length; i++)
|
||||
{
|
||||
this.buffer[this.bufferIndex++] = (byte)NEW_LINE[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
this.buffer[this.bufferIndex++] = (byte)' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void EnsureBufferSpace(int space)
|
||||
{
|
||||
var length = this.buffer.Length;
|
||||
|
||||
if (space > length)
|
||||
{
|
||||
throw new Exception("Insufficient buffer capacity");
|
||||
}
|
||||
|
||||
if (this.bufferIndex + space > length)
|
||||
{
|
||||
this.FlushToStream();
|
||||
}
|
||||
}
|
||||
|
||||
private void Buffer_WriteString_WithEscape(string str)
|
||||
{
|
||||
this.EnsureBufferSpace(str.Length);
|
||||
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
char c = str[i];
|
||||
|
||||
if (c < 0 || c > 127)
|
||||
{
|
||||
// We're outside the "standard" character range - so we write the character as a hexadecimal value instead
|
||||
// This ensures that we don't break the Json formatting.
|
||||
|
||||
this.EnsureBufferSpace((str.Length - i) + 6);
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)'\\';
|
||||
this.buffer[this.bufferIndex++] = (byte)'u';
|
||||
|
||||
var byte1 = c >> 8;
|
||||
var byte2 = (byte)c;
|
||||
|
||||
var lookup = ByteToHexCharLookup[byte1];
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)lookup;
|
||||
this.buffer[this.bufferIndex++] = (byte)(lookup >> 16);
|
||||
|
||||
lookup = ByteToHexCharLookup[byte2];
|
||||
|
||||
this.buffer[this.bufferIndex++] = (byte)lookup;
|
||||
this.buffer[this.bufferIndex++] = (byte)(lookup >> 16);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e05b98a26be61fa9203d4a45bfc1e95
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+741
@@ -0,0 +1,741 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="JsonTextReader.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Parses json entries from a stream.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
public class JsonTextReader : IDisposable
|
||||
{
|
||||
private static readonly Dictionary<char, EntryType?> EntryDelineators = new Dictionary<char, EntryType?>
|
||||
{
|
||||
{ '{', EntryType.StartOfNode },
|
||||
{ '}', EntryType.EndOfNode },
|
||||
{ ',', null },
|
||||
{ '[', EntryType.PrimitiveArray },
|
||||
{ ']', EntryType.EndOfArray },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<char, char> UnescapeDictionary = new Dictionary<char, char>()
|
||||
{
|
||||
{ 'a', '\a' },
|
||||
{ 'b', '\b' },
|
||||
{ 'f', '\f' },
|
||||
{ 'n', '\n' },
|
||||
{ 'r', '\r' },
|
||||
{ 't', '\t' },
|
||||
{ '0', '\0' }
|
||||
};
|
||||
|
||||
private StreamReader reader;
|
||||
private int bufferIndex = 0;
|
||||
private char[] buffer = new char[256];
|
||||
private char? lastReadChar;
|
||||
private char? peekedChar;
|
||||
private Queue<char> emergencyPlayback;
|
||||
|
||||
/// <summary>
|
||||
/// The current deserialization context used by the text reader.
|
||||
/// </summary>
|
||||
public DeserializationContext Context { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonTextReader" /> class.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to parse from.</param>
|
||||
/// <param name="context">The deserialization context to use.</param>
|
||||
/// <exception cref="System.ArgumentNullException">The stream is null.</exception>
|
||||
/// <exception cref="System.ArgumentException">Cannot read from the stream.</exception>
|
||||
public JsonTextReader(Stream stream, DeserializationContext context)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException("context");
|
||||
}
|
||||
|
||||
if (stream.CanRead == false)
|
||||
{
|
||||
throw new ArgumentException("Cannot read from stream");
|
||||
}
|
||||
|
||||
this.reader = new StreamReader(stream);
|
||||
this.Context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the reader instance's currently peeked char and emergency playback queue.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
this.peekedChar = null;
|
||||
|
||||
if (this.emergencyPlayback != null)
|
||||
{
|
||||
this.emergencyPlayback.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes all resources kept by the text reader, except the stream, which can be reused later.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
//this.reader.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads to (but not past) the beginning of the next json entry, and returns the entry name, contents and type.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the entry that was parsed.</param>
|
||||
/// <param name="valueContent">The content of the entry that was parsed.</param>
|
||||
/// <param name="entry">The type of the entry that was parsed.</param>
|
||||
public void ReadToNextEntry(out string name, out string valueContent, out EntryType entry)
|
||||
{
|
||||
// This is sort of complicated, so the method is heavily commented.
|
||||
int valueSeparatorIndex = -1;
|
||||
bool insideString = false;
|
||||
EntryType? foundEntryType;
|
||||
|
||||
this.bufferIndex = -1; // Reset buffer
|
||||
|
||||
while (this.reader.EndOfStream == false)
|
||||
{
|
||||
char c = this.PeekChar();
|
||||
|
||||
if (insideString && this.lastReadChar == '\\')
|
||||
{
|
||||
// A special character or hex value (\uXXXX format) has possibly been escaped - we resolve that escape here
|
||||
if (c == '\\')
|
||||
{
|
||||
// An escape character has been escaped by a previous escape character
|
||||
// We consume this escape character without adding it to the buffer, and clear the last read char,
|
||||
// so that each escape character can only escape another escape character once
|
||||
this.lastReadChar = null;
|
||||
this.SkipChar();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (c) // '\"' is handled further down
|
||||
{
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'f':
|
||||
case 'n':
|
||||
case 'r':
|
||||
case 't':
|
||||
case '0':
|
||||
// These are normally escaped "short" characters - tabs, carriage returns, newlines, etc.
|
||||
// We substitute the prior escape char with the escaped character
|
||||
c = UnescapeDictionary[c];
|
||||
|
||||
this.lastReadChar = c;
|
||||
this.buffer[this.bufferIndex] = c;
|
||||
this.SkipChar();
|
||||
continue;
|
||||
|
||||
case 'u':
|
||||
// This signifies the beginning of a hexadecimal sequence of four chars, describing one Unicode char
|
||||
this.SkipChar(); // Skip u
|
||||
|
||||
char c1 = this.ConsumeChar();
|
||||
char c2 = this.ConsumeChar();
|
||||
char c3 = this.ConsumeChar();
|
||||
char c4 = this.ConsumeChar();
|
||||
|
||||
if (this.IsHex(c1) && this.IsHex(c2) && this.IsHex(c3) && this.IsHex(c4))
|
||||
{
|
||||
// We substitute the prior escape char with the parsed hex char
|
||||
c = this.ParseHexChar(c1, c2, c3, c4);
|
||||
|
||||
this.lastReadChar = c;
|
||||
this.buffer[this.bufferIndex] = c;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Context.Config.DebugContext.LogError("A wild non-hex value appears at position " + this.reader.BaseStream.Position + "! \\-u-" + c1 + "-" + c2 + "-" + c3 + "-" + c4 + "; current buffer: '" + new string(this.buffer, 0, this.bufferIndex + 1) + "'. If the error handling policy is resilient, an attempt will be made to recover from this emergency without a fatal parse error...");
|
||||
|
||||
// Queue values plainly in emergency playback queue - u,c1,c2,c3,c4, and wipe lastReadChar to avoid the escape character triggering again
|
||||
this.lastReadChar = null;
|
||||
|
||||
if (this.emergencyPlayback == null)
|
||||
{
|
||||
this.emergencyPlayback = new Queue<char>(5);
|
||||
}
|
||||
|
||||
this.emergencyPlayback.Enqueue('u');
|
||||
this.emergencyPlayback.Enqueue(c1);
|
||||
this.emergencyPlayback.Enqueue(c2);
|
||||
this.emergencyPlayback.Enqueue(c3);
|
||||
this.emergencyPlayback.Enqueue(c4);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (insideString == false && c == ':' && valueSeparatorIndex == -1)
|
||||
{
|
||||
// We've found a value separator
|
||||
valueSeparatorIndex = this.bufferIndex + 1;
|
||||
}
|
||||
|
||||
if (c == '"')
|
||||
{
|
||||
if (insideString && this.lastReadChar == '\\')
|
||||
{
|
||||
// We're currently inside a string and this quotation mark has been escaped
|
||||
// Replace the escape character with the quotation mark instead, and read one character ahead
|
||||
this.lastReadChar = '"';
|
||||
this.buffer[this.bufferIndex] = '"';
|
||||
this.SkipChar();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This quotation mark hasn't been escaped; toggle the inside string bool
|
||||
this.ReadCharIntoBuffer();
|
||||
insideString = !insideString;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (insideString)
|
||||
{
|
||||
// Currently reading a string; read everything verbatim (escaped quotes handled above)
|
||||
this.ReadCharIntoBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
// While not inside strings, skip all whitespaces (this includes newlines)
|
||||
if (char.IsWhiteSpace(c))
|
||||
{
|
||||
this.SkipChar();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (EntryDelineators.TryGetValue(c, out foundEntryType))
|
||||
{
|
||||
// We've hit an entry delineator
|
||||
if (foundEntryType == null)
|
||||
{
|
||||
// This was a value entry, which could be a lot of things
|
||||
// We consume the character without adding it to the buffer
|
||||
this.SkipChar();
|
||||
|
||||
if (this.bufferIndex == -1)
|
||||
{
|
||||
// We encountered a value separator without having read anything into the buffer first
|
||||
// We probably just finished with a node. Either way, we read on
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We parse a value entry from the buffer and return that information
|
||||
this.ParseEntryFromBuffer(out name, out valueContent, out entry, valueSeparatorIndex, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
entry = foundEntryType.Value;
|
||||
|
||||
switch (entry)
|
||||
{
|
||||
case EntryType.StartOfNode:
|
||||
{
|
||||
// We're starting a node.
|
||||
// We consume the start of node character without adding it to the buffer,
|
||||
// then parse the entry information if it's there.
|
||||
EntryType dummy;
|
||||
this.ConsumeChar();
|
||||
this.ParseEntryFromBuffer(out name, out valueContent, out dummy, valueSeparatorIndex, EntryType.StartOfNode);
|
||||
return;
|
||||
}
|
||||
|
||||
case EntryType.PrimitiveArray:
|
||||
{
|
||||
// We're starting a primitive array (regular arrays are caught by parsing entries prior to this)
|
||||
// We consume the start of array character without adding it to the buffer,
|
||||
// then parse the entry information if it's there
|
||||
EntryType dummy;
|
||||
this.ConsumeChar();
|
||||
this.ParseEntryFromBuffer(out name, out valueContent, out dummy, valueSeparatorIndex, EntryType.PrimitiveArray);
|
||||
return;
|
||||
}
|
||||
|
||||
case EntryType.EndOfNode:
|
||||
if (this.bufferIndex == -1)
|
||||
{
|
||||
// This is an actual end of node, as we haven't read anything before this
|
||||
// So we consume it, and return as end of node
|
||||
this.ConsumeChar();
|
||||
name = null;
|
||||
valueContent = null;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We just finished reading the last value entry in a node, as there's content in the buffer
|
||||
// We don't consume the end of node character (which has only been peeked) - that we leave for the next call to find
|
||||
// Instead we parse the entry from the buffer and return that entry information
|
||||
this.ParseEntryFromBuffer(out name, out valueContent, out entry, valueSeparatorIndex, null);
|
||||
return;
|
||||
}
|
||||
|
||||
case EntryType.EndOfArray:
|
||||
{
|
||||
if (this.bufferIndex == -1)
|
||||
{
|
||||
// This is an actual end of array, as we haven't read anything before this
|
||||
// So we consume it, and return as end of array
|
||||
this.ConsumeChar();
|
||||
name = null;
|
||||
valueContent = null;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We just finished reading the last value entry in an array, as there's content in the buffer
|
||||
// We don't consume the end of array character (which has only been peeked) - that we leave for the next call to find
|
||||
// Instead we parse the entry from the buffer and return that entry information
|
||||
this.ParseEntryFromBuffer(out name, out valueContent, out entry, valueSeparatorIndex, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ReadCharIntoBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We've hit the end of stream
|
||||
if (this.bufferIndex == -1)
|
||||
{
|
||||
// We didn't manage to read any info before reaching end of stream
|
||||
name = null;
|
||||
valueContent = null;
|
||||
entry = EntryType.EndOfStream;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We managed to read some stuff before we reached the end of stream
|
||||
// We can try to parse that as an entry
|
||||
this.ParseEntryFromBuffer(out name, out valueContent, out entry, valueSeparatorIndex, EntryType.EndOfStream);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseEntryFromBuffer(out string name, out string valueContent, out EntryType entry, int valueSeparatorIndex, EntryType? hintEntry)
|
||||
{
|
||||
if (this.bufferIndex >= 0)
|
||||
{
|
||||
if (valueSeparatorIndex == -1)
|
||||
{
|
||||
// There is no value separator on this line at all
|
||||
if (hintEntry != null)
|
||||
{
|
||||
// We have a hint, so we'll try to handle it and assume that the entry's content is the whole thing
|
||||
name = null;
|
||||
valueContent = new string(this.buffer, 0, this.bufferIndex + 1);
|
||||
entry = hintEntry.Value;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've got no hint and no separator; we must assume that this is a primitive, and that the entry's content is the whole thing
|
||||
// This will happen while reading primitive arrays
|
||||
name = null;
|
||||
valueContent = new string(this.buffer, 0, this.bufferIndex + 1);
|
||||
|
||||
var guessedPrimitiveType = this.GuessPrimitiveType(valueContent);
|
||||
|
||||
if (guessedPrimitiveType != null)
|
||||
{
|
||||
entry = guessedPrimitiveType.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
entry = EntryType.Invalid;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We allow a node's name to *not* be inside quotation marks
|
||||
if (this.buffer[0] == '"')
|
||||
{
|
||||
name = new string(this.buffer, 1, valueSeparatorIndex - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
name = new string(this.buffer, 0, valueSeparatorIndex);
|
||||
}
|
||||
|
||||
if (string.Equals(name, JsonConfig.REGULAR_ARRAY_CONTENT_SIG, StringComparison.InvariantCulture) && hintEntry == EntryType.StartOfArray)
|
||||
{
|
||||
valueContent = null;
|
||||
entry = EntryType.StartOfArray;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(name, JsonConfig.PRIMITIVE_ARRAY_CONTENT_SIG, StringComparison.InvariantCulture) && hintEntry == EntryType.StartOfArray)
|
||||
{
|
||||
valueContent = null;
|
||||
entry = EntryType.PrimitiveArray;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(name, JsonConfig.INTERNAL_REF_SIG, StringComparison.InvariantCulture))
|
||||
{
|
||||
// It's an object reference without a name
|
||||
// The content is the whole buffer
|
||||
name = null;
|
||||
valueContent = new string(this.buffer, 0, this.bufferIndex + 1);
|
||||
entry = EntryType.InternalReference;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(name, JsonConfig.EXTERNAL_INDEX_REF_SIG, StringComparison.InvariantCulture))
|
||||
{
|
||||
// It's an external index reference without a name
|
||||
// The content is the whole buffer
|
||||
name = null;
|
||||
valueContent = new string(this.buffer, 0, this.bufferIndex + 1);
|
||||
entry = EntryType.ExternalReferenceByIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(name, JsonConfig.EXTERNAL_GUID_REF_SIG, StringComparison.InvariantCulture))
|
||||
{
|
||||
// It's an external guid reference without a name
|
||||
// The content is the whole buffer
|
||||
name = null;
|
||||
valueContent = new string(this.buffer, 0, this.bufferIndex + 1);
|
||||
entry = EntryType.ExternalReferenceByGuid;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(name, JsonConfig.EXTERNAL_STRING_REF_SIG, StringComparison.InvariantCulture))
|
||||
{
|
||||
// It's an external guid reference without a name
|
||||
// The content is the whole buffer
|
||||
name = null;
|
||||
valueContent = new string(this.buffer, 0, this.bufferIndex + 1);
|
||||
entry = EntryType.ExternalReferenceByString;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.bufferIndex >= valueSeparatorIndex)
|
||||
{
|
||||
valueContent = new string(this.buffer, valueSeparatorIndex + 1, this.bufferIndex - valueSeparatorIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
valueContent = null;
|
||||
}
|
||||
|
||||
if (valueContent != null)
|
||||
{
|
||||
// We can now try to see what the value content actually is, and as such determine the type of the entry
|
||||
if (string.Equals(name, JsonConfig.REGULAR_ARRAY_LENGTH_SIG, StringComparison.InvariantCulture)) // This is a special case for the length entry that must always come before an array
|
||||
{
|
||||
entry = EntryType.StartOfArray;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(name, JsonConfig.PRIMITIVE_ARRAY_LENGTH_SIG, StringComparison.InvariantCulture)) // This is a special case for the length entry that must always come before an array
|
||||
{
|
||||
entry = EntryType.PrimitiveArray;
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueContent.Length == 0 && hintEntry.HasValue)
|
||||
{
|
||||
entry = hintEntry.Value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(valueContent, "null", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
entry = EntryType.Null;
|
||||
return;
|
||||
}
|
||||
else if (string.Equals(valueContent, "{", StringComparison.InvariantCulture))
|
||||
{
|
||||
entry = EntryType.StartOfNode;
|
||||
return;
|
||||
}
|
||||
else if (string.Equals(valueContent, "}", StringComparison.InvariantCulture))
|
||||
{
|
||||
entry = EntryType.EndOfNode;
|
||||
return;
|
||||
}
|
||||
else if (string.Equals(valueContent, "[", StringComparison.InvariantCulture))
|
||||
{
|
||||
entry = EntryType.StartOfArray;
|
||||
return;
|
||||
}
|
||||
else if (string.Equals(valueContent, "]", StringComparison.InvariantCulture))
|
||||
{
|
||||
entry = EntryType.EndOfArray;
|
||||
return;
|
||||
}
|
||||
else if (valueContent.StartsWith(JsonConfig.INTERNAL_REF_SIG, StringComparison.InvariantCulture))
|
||||
{
|
||||
entry = EntryType.InternalReference;
|
||||
return;
|
||||
}
|
||||
else if (valueContent.StartsWith(JsonConfig.EXTERNAL_INDEX_REF_SIG, StringComparison.InvariantCulture))
|
||||
{
|
||||
entry = EntryType.ExternalReferenceByIndex;
|
||||
return;
|
||||
}
|
||||
else if (valueContent.StartsWith(JsonConfig.EXTERNAL_GUID_REF_SIG, StringComparison.InvariantCulture))
|
||||
{
|
||||
entry = EntryType.ExternalReferenceByGuid;
|
||||
return;
|
||||
}
|
||||
else if (valueContent.StartsWith(JsonConfig.EXTERNAL_STRING_REF_SIG, StringComparison.InvariantCulture))
|
||||
{
|
||||
entry = EntryType.ExternalReferenceByString;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
var guessedPrimitiveType = this.GuessPrimitiveType(valueContent);
|
||||
|
||||
if (guessedPrimitiveType != null)
|
||||
{
|
||||
entry = guessedPrimitiveType.Value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hintEntry != null)
|
||||
{
|
||||
name = null;
|
||||
valueContent = null;
|
||||
entry = hintEntry.Value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Parsing the entry somehow failed entirely
|
||||
// This means the JSON was actually invalid
|
||||
if (this.bufferIndex == -1)
|
||||
{
|
||||
this.Context.Config.DebugContext.LogError("Failed to parse empty entry in the stream.");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Context.Config.DebugContext.LogError("Tried and failed to parse entry with content '" + new string(this.buffer, 0, this.bufferIndex + 1) + "'.");
|
||||
}
|
||||
|
||||
if (hintEntry == EntryType.EndOfStream)
|
||||
{
|
||||
name = null;
|
||||
valueContent = null;
|
||||
entry = EntryType.EndOfStream;
|
||||
}
|
||||
else
|
||||
{
|
||||
name = null;
|
||||
valueContent = null;
|
||||
entry = EntryType.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsHex(char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9')
|
||||
|| (c >= 'a' && c <= 'f')
|
||||
|| (c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
private uint ParseSingleChar(char c, uint multiplier)
|
||||
{
|
||||
uint p = 0;
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
p = (uint)(c - '0') * multiplier;
|
||||
}
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
{
|
||||
p = (uint)((c - 'A') + 10) * multiplier;
|
||||
}
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
{
|
||||
p = (uint)((c - 'a') + 10) * multiplier;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
private char ParseHexChar(char c1, char c2, char c3, char c4)
|
||||
{
|
||||
uint p1 = this.ParseSingleChar(c1, 0x1000);
|
||||
uint p2 = this.ParseSingleChar(c2, 0x100);
|
||||
uint p3 = this.ParseSingleChar(c3, 0x10);
|
||||
uint p4 = this.ParseSingleChar(c4, 0x1);
|
||||
|
||||
try
|
||||
{
|
||||
return (char)(p1 + p2 + p3 + p4);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
this.Context.Config.DebugContext.LogError("Could not parse invalid hex values: " + c1 + c2 + c3 + c4);
|
||||
return ' ';
|
||||
}
|
||||
}
|
||||
|
||||
private char ReadCharIntoBuffer()
|
||||
{
|
||||
this.bufferIndex++;
|
||||
|
||||
if (this.bufferIndex >= this.buffer.Length - 1)
|
||||
{
|
||||
// Ensure there's space in the buffer
|
||||
var newBuffer = new char[this.buffer.Length * 2];
|
||||
Buffer.BlockCopy(this.buffer, 0, newBuffer, 0, this.buffer.Length * sizeof(char));
|
||||
this.buffer = newBuffer;
|
||||
}
|
||||
|
||||
char c = this.ConsumeChar();
|
||||
|
||||
this.buffer[this.bufferIndex] = c;
|
||||
this.lastReadChar = c;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
private EntryType? GuessPrimitiveType(string content)
|
||||
{
|
||||
// This method tries to guess what kind of primitive type the current entry is, as cheaply as possible
|
||||
if (string.Equals(content, "null", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return EntryType.Null;
|
||||
}
|
||||
else if (content.Length >= 2 && content[0] == '"' && content[content.Length - 1] == '"')
|
||||
{
|
||||
return EntryType.String;
|
||||
}
|
||||
else if (content.Length == 36 && content.LastIndexOf('-') > 0)
|
||||
{
|
||||
return EntryType.Guid;
|
||||
}
|
||||
else if (content.Contains(".") || content.Contains(","))
|
||||
{
|
||||
return EntryType.FloatingPoint;
|
||||
}
|
||||
else if (string.Equals(content, "true", StringComparison.InvariantCultureIgnoreCase) || string.Equals(content, "false", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return EntryType.Boolean;
|
||||
}
|
||||
else if (content.Length >= 1)
|
||||
{
|
||||
return EntryType.Integer;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private char PeekChar()
|
||||
{
|
||||
// Instead of peeking, we read ahead and store the last read character as a peeked character
|
||||
// this means we don't need seeking support in the stream
|
||||
if (this.peekedChar == null)
|
||||
{
|
||||
if (this.emergencyPlayback != null && this.emergencyPlayback.Count > 0)
|
||||
{
|
||||
this.peekedChar = this.emergencyPlayback.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.peekedChar = (char)this.reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
return this.peekedChar.Value;
|
||||
}
|
||||
|
||||
private void SkipChar()
|
||||
{
|
||||
if (this.peekedChar == null)
|
||||
{
|
||||
if (this.emergencyPlayback != null && this.emergencyPlayback.Count > 0)
|
||||
{
|
||||
this.emergencyPlayback.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.reader.Read();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.peekedChar = null;
|
||||
}
|
||||
}
|
||||
|
||||
private char ConsumeChar()
|
||||
{
|
||||
if (this.peekedChar == null)
|
||||
{
|
||||
if (this.emergencyPlayback != null && this.emergencyPlayback.Count > 0)
|
||||
{
|
||||
return this.emergencyPlayback.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
return (char)this.reader.Read();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var c = this.peekedChar;
|
||||
this.peekedChar = null;
|
||||
return c.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aad0a34e801ae645b359e4800ef7f636
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c071e721d9b168c4a9f683547f0992e3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+43
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a0f5e01b82ae0763f6f907157a2c9c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+1028
File diff suppressed because it is too large.
Load diff
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eab5938e837a8de93ce64c25399edde6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+30
@@ -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 = "|";
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9321fb650525f4bed18119d187111569
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+516
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd54f07c359d141095a031192c5ca084
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc6d459b42d798b4aa51e323793e2093
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ArrayFormatterLocator.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatterLocator(typeof(ArrayFormatterLocator), -80)]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
|
||||
internal class ArrayFormatterLocator : IFormatterLocator
|
||||
{
|
||||
public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter)
|
||||
{
|
||||
if (!type.IsArray)
|
||||
{
|
||||
formatter = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type.GetArrayRank() == 1)
|
||||
{
|
||||
if (FormatterUtilities.IsPrimitiveArrayType(type.GetElementType()))
|
||||
{
|
||||
formatter = (IFormatter)Activator.CreateInstance(typeof(PrimitiveArrayFormatter<>).MakeGenericType(type.GetElementType()));
|
||||
}
|
||||
else
|
||||
{
|
||||
formatter = (IFormatter)Activator.CreateInstance(typeof(ArrayFormatter<>).MakeGenericType(type.GetElementType()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
formatter = (IFormatter)Activator.CreateInstance(typeof(MultiDimensionalArrayFormatter<,>).MakeGenericType(type, type.GetElementType()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bdecd79f568c8a3252bb5a9f3e2acdc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="DelegateFormatterLocator.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatterLocator(typeof(DelegateFormatterLocator), -50)]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
|
||||
internal class DelegateFormatterLocator : IFormatterLocator
|
||||
{
|
||||
public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter)
|
||||
{
|
||||
if (!typeof(Delegate).IsAssignableFrom(type))
|
||||
{
|
||||
formatter = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
formatter = (IFormatter)Activator.CreateInstance(typeof(DelegateFormatter<>).MakeGenericType(type));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4228cdbba89e2a5d52357b998c3387d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,636 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="FormatterLocator.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for locating and caching formatters for all non-primitive types.
|
||||
/// </summary>
|
||||
#if UNITY_EDITOR
|
||||
|
||||
[UnityEditor.InitializeOnLoad]
|
||||
#endif
|
||||
|
||||
public static class FormatterLocator
|
||||
{
|
||||
private static readonly object LOCK = new object();
|
||||
|
||||
private static readonly Dictionary<Type, IFormatter> FormatterInstances = new Dictionary<Type, IFormatter>(FastTypeComparer.Instance);
|
||||
private static readonly DoubleLookupDictionary<Type, ISerializationPolicy, IFormatter> TypeFormatterMap = new DoubleLookupDictionary<Type, ISerializationPolicy, IFormatter>(FastTypeComparer.Instance, ReferenceEqualityComparer<ISerializationPolicy>.Default);
|
||||
|
||||
private struct FormatterInfo
|
||||
{
|
||||
public Type FormatterType;
|
||||
public Type TargetType;
|
||||
public bool AskIfCanFormatTypes;
|
||||
public int Priority;
|
||||
}
|
||||
|
||||
private struct FormatterLocatorInfo
|
||||
{
|
||||
public IFormatterLocator LocatorInstance;
|
||||
public int Priority;
|
||||
}
|
||||
|
||||
private static readonly List<FormatterLocatorInfo> FormatterLocators = new List<FormatterLocatorInfo>();
|
||||
private static readonly List<FormatterInfo> FormatterInfos = new List<FormatterInfo>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Editor-only event that fires whenever an emittable formatter has been located.
|
||||
/// This event is used by the AOT formatter pre-emitter to locate types that need to have formatters pre-emitted.
|
||||
/// </summary>
|
||||
public static event Action<Type> OnLocatedEmittableFormatterForType;
|
||||
|
||||
/// <summary>
|
||||
/// Editor-only event that fires whenever a formatter has been located.
|
||||
/// </summary>
|
||||
public static event Action<IFormatter> OnLocatedFormatter;
|
||||
|
||||
#endif
|
||||
|
||||
static FormatterLocator()
|
||||
{
|
||||
foreach (var ass in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
try
|
||||
{
|
||||
var name = ass.GetName().Name;
|
||||
|
||||
if (name.StartsWith("System.") || name.StartsWith("UnityEngine") || name.StartsWith("UnityEditor") || name == "mscorlib")
|
||||
{
|
||||
// Filter out various core .NET libraries and Unity engine assemblies
|
||||
continue;
|
||||
}
|
||||
else if (ass.GetName().Name == FormatterEmitter.PRE_EMITTED_ASSEMBLY_NAME || ass.IsDefined(typeof(EmittedAssemblyAttribute), true))
|
||||
{
|
||||
// Only include pre-emitted formatters if we are on an AOT platform.
|
||||
// Pre-emitted formatters will not work in newer .NET runtimes due to
|
||||
// lacking private member access privileges, but when compiled via
|
||||
// IL2CPP they work fine.
|
||||
|
||||
#if UNITY_EDITOR
|
||||
continue;
|
||||
#else
|
||||
if (EmitUtilities.CanEmit)
|
||||
{
|
||||
// Never include pre-emitted formatters if we can emit on the current platform
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (var attrUncast in ass.SafeGetCustomAttributes(typeof(RegisterFormatterAttribute), true))
|
||||
{
|
||||
var attr = (RegisterFormatterAttribute)attrUncast;
|
||||
|
||||
if (!attr.FormatterType.IsClass
|
||||
|| attr.FormatterType.IsAbstract
|
||||
|| attr.FormatterType.GetConstructor(Type.EmptyTypes) == null
|
||||
|| !attr.FormatterType.ImplementsOpenGenericInterface(typeof(IFormatter<>)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FormatterInfos.Add(new FormatterInfo()
|
||||
{
|
||||
FormatterType = attr.FormatterType,
|
||||
TargetType = attr.FormatterType.GetArgumentsOfInheritedOpenGenericInterface(typeof(IFormatter<>))[0],
|
||||
AskIfCanFormatTypes = typeof(IAskIfCanFormatTypes).IsAssignableFrom(attr.FormatterType),
|
||||
Priority = attr.Priority
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var attrUncast in ass.SafeGetCustomAttributes(typeof(RegisterFormatterLocatorAttribute), true))
|
||||
{
|
||||
var attr = (RegisterFormatterLocatorAttribute)attrUncast;
|
||||
|
||||
if (!attr.FormatterLocatorType.IsClass
|
||||
|| attr.FormatterLocatorType.IsAbstract
|
||||
|| attr.FormatterLocatorType.GetConstructor(Type.EmptyTypes) == null
|
||||
|| !typeof(IFormatterLocator).IsAssignableFrom(attr.FormatterLocatorType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FormatterLocators.Add(new FormatterLocatorInfo()
|
||||
{
|
||||
LocatorInstance = (IFormatterLocator)Activator.CreateInstance(attr.FormatterLocatorType),
|
||||
Priority = attr.Priority
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(new Exception("Exception was thrown while instantiating FormatterLocator of type " + attr.FormatterLocatorType.FullName + ".", ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TypeLoadException)
|
||||
{
|
||||
if (ass.GetName().Name == "OdinSerializer")
|
||||
{
|
||||
Debug.LogError("A TypeLoadException occurred when FormatterLocator tried to load types from assembly '" + ass.FullName + "'. No serialization formatters in this assembly will be found. Serialization will be utterly broken.");
|
||||
}
|
||||
}
|
||||
catch (ReflectionTypeLoadException)
|
||||
{
|
||||
if (ass.GetName().Name == "OdinSerializer")
|
||||
{
|
||||
Debug.LogError("A ReflectionTypeLoadException occurred when FormatterLocator tried to load types from assembly '" + ass.FullName + "'. No serialization formatters in this assembly will be found. Serialization will be utterly broken.");
|
||||
}
|
||||
}
|
||||
catch (MissingMemberException)
|
||||
{
|
||||
if (ass.GetName().Name == "OdinSerializer")
|
||||
{
|
||||
Debug.LogError("A ReflectionTypeLoadException occurred when FormatterLocator tried to load types from assembly '" + ass.FullName + "'. No serialization formatters in this assembly will be found. Serialization will be utterly broken.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Order formatters and formatter locators by priority and then by name, to ensure consistency regardless of the order of loaded types, which is important for cross-platform cases.
|
||||
|
||||
FormatterInfos.Sort((a, b) =>
|
||||
{
|
||||
int compare = -a.Priority.CompareTo(b.Priority);
|
||||
|
||||
if (compare == 0)
|
||||
{
|
||||
compare = a.FormatterType.Name.CompareTo(b.FormatterType.Name);
|
||||
}
|
||||
|
||||
return compare;
|
||||
});
|
||||
|
||||
FormatterLocators.Sort((a, b) =>
|
||||
{
|
||||
int compare = -a.Priority.CompareTo(b.Priority);
|
||||
|
||||
if (compare == 0)
|
||||
{
|
||||
compare = a.LocatorInstance.GetType().Name.CompareTo(b.LocatorInstance.GetType().Name);
|
||||
}
|
||||
|
||||
return compare;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event is invoked before everything else when a formatter is being resolved for a given type. If any invoked delegate returns a valid formatter, that formatter is used and the resolve process stops there.
|
||||
/// <para />
|
||||
/// This can be used to hook into and extend the serialization system's formatter resolution logic.
|
||||
/// </summary>
|
||||
[Obsolete("Use the new IFormatterLocator interface instead, and register your custom locator with the RegisterFormatterLocator assembly attribute.", true)]
|
||||
public static event Func<Type, IFormatter> FormatterResolve
|
||||
{
|
||||
add { throw new NotSupportedException(); }
|
||||
remove { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a formatter for the type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to get a formatter for.</typeparam>
|
||||
/// <param name="policy">The serialization policy to use if a formatter has to be emitted. If null, <see cref="SerializationPolicies.Strict"/> is used.</param>
|
||||
/// <returns>
|
||||
/// A formatter for the type <see cref="T" />.
|
||||
/// </returns>
|
||||
public static IFormatter<T> GetFormatter<T>(ISerializationPolicy policy)
|
||||
{
|
||||
return (IFormatter<T>)GetFormatter(typeof(T), policy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a formatter for a given type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to get a formatter for.</param>
|
||||
/// <param name="policy">The serialization policy to use if a formatter has to be emitted. If null, <see cref="SerializationPolicies.Strict"/> is used.</param>
|
||||
/// <returns>
|
||||
/// A formatter for the given type.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">The type argument is null.</exception>
|
||||
public static IFormatter GetFormatter(Type type, ISerializationPolicy policy)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException("type");
|
||||
}
|
||||
|
||||
if (policy == null)
|
||||
{
|
||||
policy = SerializationPolicies.Strict;
|
||||
}
|
||||
|
||||
IFormatter result;
|
||||
|
||||
lock (LOCK)
|
||||
{
|
||||
if (TypeFormatterMap.TryGetInnerValue(type, policy, out result) == false)
|
||||
{
|
||||
// System.ExecutionEngineException is marked obsolete in .NET 4.6.
|
||||
// That's all very good for .NET, but Unity still uses it, and that means we use it as well!
|
||||
#pragma warning disable 618
|
||||
try
|
||||
{
|
||||
result = CreateFormatter(type, policy);
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
if (ex.GetBaseException() is ExecutionEngineException)
|
||||
{
|
||||
LogAOTError(type, ex.GetBaseException() as ExecutionEngineException);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
catch (TypeInitializationException ex)
|
||||
{
|
||||
if (ex.GetBaseException() is ExecutionEngineException)
|
||||
{
|
||||
LogAOTError(type, ex.GetBaseException() as ExecutionEngineException);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
catch (ExecutionEngineException ex)
|
||||
{
|
||||
LogAOTError(type, ex);
|
||||
}
|
||||
|
||||
TypeFormatterMap.AddInner(type, policy, result);
|
||||
#pragma warning restore 618
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (OnLocatedFormatter != null)
|
||||
{
|
||||
OnLocatedFormatter(result);
|
||||
}
|
||||
|
||||
if (OnLocatedEmittableFormatterForType != null && result.GetType().IsGenericType)
|
||||
{
|
||||
#if CAN_EMIT
|
||||
if (result.GetType().GetGenericTypeDefinition() == typeof(FormatterEmitter.RuntimeEmittedFormatter<>))
|
||||
{
|
||||
OnLocatedEmittableFormatterForType(type);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (result.GetType().GetGenericTypeDefinition() == typeof(ReflectionFormatter<>))
|
||||
{
|
||||
OnLocatedEmittableFormatterForType(type);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void LogAOTError(Type type, Exception ex)
|
||||
{
|
||||
var types = new List<string>(GetAllPossibleMissingAOTTypes(type)).ToArray();
|
||||
|
||||
Debug.LogError("Creating a serialization formatter for the type '" + type.GetNiceFullName() + "' failed due to missing AOT support. \n\n" +
|
||||
" Please use Odin's AOT generation feature to generate an AOT dll before building, and MAKE SURE that all of the following " +
|
||||
"types were automatically added to the supported types list after a scan (if they were not, please REPORT AN ISSUE with the details of which exact types the scan is missing " +
|
||||
"and ADD THEM MANUALLY): \n\n" + string.Join("\n", types) + "\n\nIF ALL THE TYPES ARE IN THE SUPPORT LIST AND YOU STILL GET THIS ERROR, PLEASE REPORT AN ISSUE." +
|
||||
"The exception contained the following message: \n" + ex.Message);
|
||||
|
||||
throw new SerializationAbortException("AOT formatter support was missing for type '" + type.GetNiceFullName() + "'.");
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetAllPossibleMissingAOTTypes(Type type)
|
||||
{
|
||||
yield return type.GetNiceFullName() + " (name string: '" + TwoWaySerializationBinder.Default.BindToName(type) + "')";
|
||||
|
||||
if (!type.IsGenericType) yield break;
|
||||
|
||||
foreach (var arg in type.GetGenericArguments())
|
||||
{
|
||||
yield return arg.GetNiceFullName() + " (name string: '" + TwoWaySerializationBinder.Default.BindToName(arg) + "')";
|
||||
|
||||
if (arg.IsGenericType)
|
||||
{
|
||||
foreach (var subArg in GetAllPossibleMissingAOTTypes(arg))
|
||||
{
|
||||
yield return subArg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<IFormatter> GetAllCompatiblePredefinedFormatters(Type type, ISerializationPolicy policy)
|
||||
{
|
||||
if (FormatterUtilities.IsPrimitiveType(type))
|
||||
{
|
||||
throw new ArgumentException("Cannot create formatters for a primitive type like " + type.Name);
|
||||
}
|
||||
|
||||
List<IFormatter> formatters = new List<IFormatter>();
|
||||
|
||||
// First call formatter locators before checking for registered formatters
|
||||
for (int i = 0; i < FormatterLocators.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
IFormatter result;
|
||||
if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.BeforeRegisteredFormatters, policy, out result))
|
||||
{
|
||||
formatters.Add(result);
|
||||
}
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (TypeInitializationException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
catch (ExecutionEngineException ex)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex));
|
||||
}
|
||||
}
|
||||
|
||||
// Then check for valid registered formatters
|
||||
for (int i = 0; i < FormatterInfos.Count; i++)
|
||||
{
|
||||
var info = FormatterInfos[i];
|
||||
|
||||
Type formatterType = null;
|
||||
|
||||
if (type == info.TargetType)
|
||||
{
|
||||
formatterType = info.FormatterType;
|
||||
}
|
||||
else if (info.FormatterType.IsGenericType && info.TargetType.IsGenericParameter)
|
||||
{
|
||||
Type[] inferredArgs;
|
||||
|
||||
if (info.FormatterType.TryInferGenericParameters(out inferredArgs, type))
|
||||
{
|
||||
formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(inferredArgs);
|
||||
}
|
||||
}
|
||||
else if (type.IsGenericType && info.FormatterType.IsGenericType && info.TargetType.IsGenericType && type.GetGenericTypeDefinition() == info.TargetType.GetGenericTypeDefinition())
|
||||
{
|
||||
Type[] args = type.GetGenericArguments();
|
||||
|
||||
if (info.FormatterType.AreGenericConstraintsSatisfiedBy(args))
|
||||
{
|
||||
formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(args);
|
||||
}
|
||||
}
|
||||
|
||||
if (formatterType != null)
|
||||
{
|
||||
var instance = GetFormatterInstance(formatterType);
|
||||
|
||||
if (instance == null) continue;
|
||||
|
||||
if (info.AskIfCanFormatTypes && !((IAskIfCanFormatTypes)instance).CanFormatType(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
formatters.Add(instance);
|
||||
}
|
||||
}
|
||||
|
||||
// Then call formatter locators after checking for registered formatters
|
||||
for (int i = 0; i < FormatterLocators.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
IFormatter result;
|
||||
if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.AfterRegisteredFormatters, policy, out result))
|
||||
{
|
||||
formatters.Add(result);
|
||||
}
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (TypeInitializationException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
catch (ExecutionEngineException ex)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex));
|
||||
}
|
||||
}
|
||||
|
||||
formatters.Add((IFormatter)Activator.CreateInstance(typeof(ReflectionFormatter<>).MakeGenericType(type)));
|
||||
|
||||
return formatters;
|
||||
}
|
||||
|
||||
private static IFormatter CreateFormatter(Type type, ISerializationPolicy policy)
|
||||
{
|
||||
if (FormatterUtilities.IsPrimitiveType(type))
|
||||
{
|
||||
throw new ArgumentException("Cannot create formatters for a primitive type like " + type.Name);
|
||||
}
|
||||
|
||||
// First call formatter locators before checking for registered formatters
|
||||
for (int i = 0; i < FormatterLocators.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
IFormatter result;
|
||||
if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.BeforeRegisteredFormatters, policy, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (TypeInitializationException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
catch (ExecutionEngineException ex)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex));
|
||||
}
|
||||
}
|
||||
|
||||
// Then check for valid registered formatters
|
||||
for (int i = 0; i < FormatterInfos.Count; i++)
|
||||
{
|
||||
var info = FormatterInfos[i];
|
||||
|
||||
Type formatterType = null;
|
||||
|
||||
if (type == info.TargetType)
|
||||
{
|
||||
formatterType = info.FormatterType;
|
||||
}
|
||||
else if (info.FormatterType.IsGenericType && info.TargetType.IsGenericParameter)
|
||||
{
|
||||
Type[] inferredArgs;
|
||||
|
||||
if (info.FormatterType.TryInferGenericParameters(out inferredArgs, type))
|
||||
{
|
||||
formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(inferredArgs);
|
||||
}
|
||||
}
|
||||
else if (type.IsGenericType && info.FormatterType.IsGenericType && info.TargetType.IsGenericType && type.GetGenericTypeDefinition() == info.TargetType.GetGenericTypeDefinition())
|
||||
{
|
||||
Type[] args = type.GetGenericArguments();
|
||||
|
||||
if (info.FormatterType.AreGenericConstraintsSatisfiedBy(args))
|
||||
{
|
||||
formatterType = info.FormatterType.GetGenericTypeDefinition().MakeGenericType(args);
|
||||
}
|
||||
}
|
||||
|
||||
if (formatterType != null)
|
||||
{
|
||||
var instance = GetFormatterInstance(formatterType);
|
||||
|
||||
if (instance == null) continue;
|
||||
|
||||
if (info.AskIfCanFormatTypes && !((IAskIfCanFormatTypes)instance).CanFormatType(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Then call formatter locators after checking for registered formatters
|
||||
for (int i = 0; i < FormatterLocators.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
IFormatter result;
|
||||
if (FormatterLocators[i].LocatorInstance.TryGetFormatter(type, FormatterLocationStep.AfterRegisteredFormatters, policy, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (TypeInitializationException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
catch (ExecutionEngineException ex)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(new Exception("Exception was thrown while calling FormatterLocator " + FormatterLocators[i].GetType().FullName + ".", ex));
|
||||
}
|
||||
}
|
||||
|
||||
// If we can, emit a formatter to handle serialization of this object
|
||||
{
|
||||
if (EmitUtilities.CanEmit)
|
||||
{
|
||||
var result = FormatterEmitter.GetEmittedFormatter(type, policy);
|
||||
if (result != null) return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (EmitUtilities.CanEmit)
|
||||
{
|
||||
Debug.LogWarning("Fallback to reflection for type " + type.Name + " when emit is possible on this platform.");
|
||||
}
|
||||
|
||||
// Finally, we fall back to a reflection-based formatter if nothing else has been found
|
||||
return (IFormatter)Activator.CreateInstance(typeof(ReflectionFormatter<>).MakeGenericType(type));
|
||||
}
|
||||
|
||||
private static IFormatter GetFormatterInstance(Type type)
|
||||
{
|
||||
IFormatter formatter;
|
||||
if (!FormatterInstances.TryGetValue(type, out formatter))
|
||||
{
|
||||
try
|
||||
{
|
||||
formatter = (IFormatter)Activator.CreateInstance(type);
|
||||
FormatterInstances.Add(type, formatter);
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (TypeInitializationException ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
catch (ExecutionEngineException ex)
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(new Exception("Exception was thrown while instantiating formatter '" + type.GetNiceFullName() + "'.", ex));
|
||||
}
|
||||
}
|
||||
return formatter;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf715e98fa96d74c81b4d3f4491d2592
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="GenericCollectionFormatterLocator.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatterLocator(typeof(GenericCollectionFormatterLocator), -100)]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
|
||||
internal class GenericCollectionFormatterLocator : IFormatterLocator
|
||||
{
|
||||
public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter)
|
||||
{
|
||||
Type elementType;
|
||||
if (step != FormatterLocationStep.AfterRegisteredFormatters || !GenericCollectionFormatter.CanFormat(type, out elementType))
|
||||
{
|
||||
formatter = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
formatter = (IFormatter)Activator.CreateInstance(typeof(GenericCollectionFormatter<,>).MakeGenericType(type, elementType));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d35d0d1eb290f5d00e273d65e5db09d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2a9beaeecdd6eb929ddb049d7846a14
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ISerializableFormatterLocator.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatterLocator(typeof(ISerializableFormatterLocator), -110)]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
internal class ISerializableFormatterLocator : IFormatterLocator
|
||||
{
|
||||
public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter)
|
||||
{
|
||||
if (step != FormatterLocationStep.AfterRegisteredFormatters || !typeof(ISerializable).IsAssignableFrom(type))
|
||||
{
|
||||
formatter = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
formatter = (IFormatter)Activator.CreateInstance(typeof(SerializableFormatter<>).MakeGenericType(type));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdd12b278851bfdc68ca0d9e1e4f2d28
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="SelfFormatterLocator.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatterLocator(typeof(SelfFormatterLocator), -60)]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using Utilities;
|
||||
|
||||
internal class SelfFormatterLocator : IFormatterLocator
|
||||
{
|
||||
public bool TryGetFormatter(Type type, FormatterLocationStep step, ISerializationPolicy policy, out IFormatter formatter)
|
||||
{
|
||||
formatter = null;
|
||||
|
||||
if (!typeof(ISelfFormatter).IsAssignableFrom(type)) return false;
|
||||
|
||||
if ((step == FormatterLocationStep.BeforeRegisteredFormatters && type.IsDefined<AlwaysFormatsSelfAttribute>())
|
||||
|| step == FormatterLocationStep.AfterRegisteredFormatters)
|
||||
{
|
||||
formatter = (IFormatter)Activator.CreateInstance(typeof(SelfFormatterFormatter<>).MakeGenericType(type));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 876ae9a404abe412e663fd9bc03d3525
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00e10f526d476731ebc596ceb66271a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a0525634602c334ab57aae1246816bc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,109 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ArrayFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Formatter for all non-primitive one-dimensional arrays.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the formatted array.</typeparam>
|
||||
/// <seealso cref="BaseFormatter{T[]}" />
|
||||
public sealed class ArrayFormatter<T> : BaseFormatter<T[]>
|
||||
{
|
||||
private static Serializer<T> valueReaderWriter = Serializer.Get<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns null.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A null value.
|
||||
/// </returns>
|
||||
protected override T[] GetUninitializedObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected override void DeserializeImplementation(ref T[] value, IDataReader reader)
|
||||
{
|
||||
string name;
|
||||
var entry = reader.PeekEntry(out name);
|
||||
|
||||
if (entry == EntryType.StartOfArray)
|
||||
{
|
||||
long length;
|
||||
reader.EnterArray(out length);
|
||||
|
||||
value = new T[length];
|
||||
|
||||
// We must remember to register the array reference ourselves, since we return null in GetUninitializedObject
|
||||
this.RegisterReferenceID(value, reader);
|
||||
|
||||
// There aren't any OnDeserializing callbacks on arrays.
|
||||
// Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (reader.PeekEntry(out name) == EntryType.EndOfArray)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected.");
|
||||
break;
|
||||
}
|
||||
|
||||
value[i] = valueReaderWriter.ReadValue(reader);
|
||||
|
||||
if (reader.PeekEntry(out name) == EntryType.EndOfStream)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
reader.ExitArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.SkipEntry();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref T[] value, IDataWriter writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.BeginArrayNode(value.Length);
|
||||
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
valueReaderWriter.WriteValue(value[i], writer);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
writer.EndArrayNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9aaf14140a26e04b861b027d5ddb8fb6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f5dc00eb17e568de42119a7f0f30ee8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,417 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="BaseFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
#if (UNITY_EDITOR || UNITY_STANDALONE) && !ENABLE_IL2CPP
|
||||
#define CAN_EMIT
|
||||
#endif
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using VRC.Udon.Serialization.OdinSerializer.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Provides common functionality for serializing and deserializing values of type <see cref="T"/>, and provides automatic support for the following common serialization conventions:
|
||||
/// <para />
|
||||
/// <see cref="IObjectReference"/>, <see cref="ISerializationCallbackReceiver"/>, <see cref="OnSerializingAttribute"/>, <see cref="OnSerializedAttribute"/>, <see cref="OnDeserializingAttribute"/> and <see cref="OnDeserializedAttribute"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type which can be serialized and deserialized by the formatter.</typeparam>
|
||||
/// <seealso cref="IFormatter{T}" />
|
||||
public abstract class BaseFormatter<T> : IFormatter<T>
|
||||
{
|
||||
protected delegate void SerializationCallback(ref T value, StreamingContext context);
|
||||
|
||||
/// <summary>
|
||||
/// The on serializing callbacks for type <see cref="T"/>.
|
||||
/// </summary>
|
||||
protected static readonly SerializationCallback[] OnSerializingCallbacks;
|
||||
|
||||
/// <summary>
|
||||
/// The on serialized callbacks for type <see cref="T"/>.
|
||||
/// </summary>
|
||||
protected static readonly SerializationCallback[] OnSerializedCallbacks;
|
||||
|
||||
/// <summary>
|
||||
/// The on deserializing callbacks for type <see cref="T"/>.
|
||||
/// </summary>
|
||||
protected static readonly SerializationCallback[] OnDeserializingCallbacks;
|
||||
|
||||
/// <summary>
|
||||
/// The on deserialized callbacks for type <see cref="T"/>.
|
||||
/// </summary>
|
||||
protected static readonly SerializationCallback[] OnDeserializedCallbacks;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the serialized value is a value type.
|
||||
/// </summary>
|
||||
protected static readonly bool IsValueType = typeof(T).IsValueType;
|
||||
|
||||
protected static readonly bool ImplementsISerializationCallbackReceiver = typeof(T).ImplementsOrInherits(typeof(UnityEngine.ISerializationCallbackReceiver));
|
||||
protected static readonly bool ImplementsIDeserializationCallback = typeof(T).ImplementsOrInherits(typeof(IDeserializationCallback));
|
||||
protected static readonly bool ImplementsIObjectReference = typeof(T).ImplementsOrInherits(typeof(IObjectReference));
|
||||
|
||||
static BaseFormatter()
|
||||
{
|
||||
if (typeof(T).ImplementsOrInherits(typeof(UnityEngine.Object)))
|
||||
{
|
||||
DefaultLoggers.DefaultLogger.LogWarning("A formatter has been created for the UnityEngine.Object type " + typeof(T).Name + " - this is *strongly* discouraged. Unity should be allowed to handle serialization and deserialization of its own weird objects. Remember to serialize with a UnityReferenceResolver as the external index reference resolver in the serialization context.\n\n Stacktrace: " + new System.Diagnostics.StackTrace().ToString());
|
||||
}
|
||||
|
||||
MethodInfo[] methods = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
List<SerializationCallback> callbacks = new List<SerializationCallback>();
|
||||
|
||||
OnSerializingCallbacks = GetCallbacks(methods, typeof(OnSerializingAttribute), ref callbacks);
|
||||
OnSerializedCallbacks = GetCallbacks(methods, typeof(OnSerializedAttribute), ref callbacks);
|
||||
OnDeserializingCallbacks = GetCallbacks(methods, typeof(OnDeserializingAttribute), ref callbacks);
|
||||
OnDeserializedCallbacks = GetCallbacks(methods, typeof(OnDeserializedAttribute), ref callbacks);
|
||||
}
|
||||
|
||||
private static SerializationCallback[] GetCallbacks(MethodInfo[] methods, Type callbackAttribute, ref List<SerializationCallback> list)
|
||||
{
|
||||
for (int i = 0; i < methods.Length; i++)
|
||||
{
|
||||
var method = methods[i];
|
||||
|
||||
if (method.IsDefined(callbackAttribute, true))
|
||||
{
|
||||
var callback = CreateCallback(method);
|
||||
|
||||
if (callback != null)
|
||||
{
|
||||
list.Add(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = list.ToArray();
|
||||
list.Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static SerializationCallback CreateCallback(MethodInfo info)
|
||||
{
|
||||
var parameters = info.GetParameters();
|
||||
if (parameters.Length == 0)
|
||||
{
|
||||
#if CAN_EMIT
|
||||
var action = EmitUtilities.CreateInstanceRefMethodCaller<T>(info);
|
||||
return (ref T value, StreamingContext context) => action(ref value);
|
||||
#else
|
||||
return (ref T value, StreamingContext context) =>
|
||||
{
|
||||
object obj = value;
|
||||
info.Invoke(obj, null);
|
||||
value = (T)obj;
|
||||
};
|
||||
#endif
|
||||
}
|
||||
else if (parameters.Length == 1 && parameters[0].ParameterType == typeof(StreamingContext) && parameters[0].ParameterType.IsByRef == false)
|
||||
{
|
||||
#if CAN_EMIT
|
||||
var action = EmitUtilities.CreateInstanceRefMethodCaller<T, StreamingContext>(info);
|
||||
return (ref T value, StreamingContext context) => action(ref value, context);
|
||||
#else
|
||||
return (ref T value, StreamingContext context) =>
|
||||
{
|
||||
object obj = value;
|
||||
info.Invoke(obj, new object[] { context });
|
||||
value = (T)obj;
|
||||
};
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
DefaultLoggers.DefaultLogger.LogWarning("The method " + info.GetNiceName() + " has an invalid signature and will be ignored by the serialization system.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type that the formatter can serialize.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type that the formatter can serialize.
|
||||
/// </value>
|
||||
public Type SerializedType { get { return typeof(T); } }
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a value using a specified <see cref="IDataWriter" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to use.</param>
|
||||
void IFormatter.Serialize(object value, IDataWriter writer)
|
||||
{
|
||||
this.Serialize((T)value, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes a value using a specified <see cref="IDataReader" />.
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader to use.</param>
|
||||
/// <returns>
|
||||
/// The deserialized value.
|
||||
/// </returns>
|
||||
object IFormatter.Deserialize(IDataReader reader)
|
||||
{
|
||||
return this.Deserialize(reader);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes a value of type <see cref="T" /> using a specified <see cref="IDataReader" />.
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader to use.</param>
|
||||
/// <returns>
|
||||
/// The deserialized value.
|
||||
/// </returns>
|
||||
public T Deserialize(IDataReader reader)
|
||||
{
|
||||
var context = reader.Context;
|
||||
T value = this.GetUninitializedObject();
|
||||
|
||||
// We allow the above method to return null (for reference types) because of special cases like arrays,
|
||||
// where the size of the array cannot be known yet, and thus we cannot create an object instance at this time.
|
||||
//
|
||||
// Therefore, those who override GetUninitializedObject and return null must call RegisterReferenceID and InvokeOnDeserializingCallbacks manually.
|
||||
if (BaseFormatter<T>.IsValueType)
|
||||
{
|
||||
this.InvokeOnDeserializingCallbacks(ref value, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (object.ReferenceEquals(value, null) == false)
|
||||
{
|
||||
this.RegisterReferenceID(value, reader);
|
||||
this.InvokeOnDeserializingCallbacks(ref value, context);
|
||||
|
||||
if (ImplementsIObjectReference)
|
||||
{
|
||||
try
|
||||
{
|
||||
value = (T)(value as IObjectReference).GetRealObject(context.StreamingContext);
|
||||
this.RegisterReferenceID(value, reader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.DeserializeImplementation(ref value, reader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
|
||||
// The deserialized value might be null, so check for that
|
||||
if (BaseFormatter<T>.IsValueType || object.ReferenceEquals(value, null) == false)
|
||||
{
|
||||
for (int i = 0; i < OnDeserializedCallbacks.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnDeserializedCallbacks[i](ref value, context.StreamingContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImplementsIDeserializationCallback)
|
||||
{
|
||||
IDeserializationCallback v = value as IDeserializationCallback;
|
||||
v.OnDeserialization(this);
|
||||
value = (T)v;
|
||||
}
|
||||
|
||||
if (ImplementsISerializationCallbackReceiver)
|
||||
{
|
||||
try
|
||||
{
|
||||
UnityEngine.ISerializationCallbackReceiver v = value as UnityEngine.ISerializationCallbackReceiver;
|
||||
v.OnAfterDeserialize();
|
||||
value = (T)v;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a value of type <see cref="T" /> using a specified <see cref="IDataWriter" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to use.</param>
|
||||
public void Serialize(T value, IDataWriter writer)
|
||||
{
|
||||
var context = writer.Context;
|
||||
|
||||
for (int i = 0; i < OnSerializingCallbacks.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnSerializingCallbacks[i](ref value, context.StreamingContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImplementsISerializationCallbackReceiver)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
UnityEngine.ISerializationCallbackReceiver v = value as UnityEngine.ISerializationCallbackReceiver;
|
||||
v.OnBeforeSerialize();
|
||||
value = (T)v;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.SerializeImplementation(ref value, writer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
|
||||
for (int i = 0; i < OnSerializedCallbacks.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnSerializedCallbacks[i](ref value, context.StreamingContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an uninitialized object of type <see cref="T"/>. WARNING: If you override this and return null, the object's ID will not be automatically registered and its OnDeserializing callbacks will not be automatically called, before deserialization begins.
|
||||
/// You will have to call <see cref="BaseFormatter{T}.RegisterReferenceID(T, IDataReader)"/> and <see cref="BaseFormatter{T}.InvokeOnDeserializingCallbacks(ref T, DeserializationContext)"/> immediately after creating the object yourself during deserialization.
|
||||
/// </summary>
|
||||
/// <returns>An uninitialized object of type <see cref="T"/>.</returns>
|
||||
protected virtual T GetUninitializedObject()
|
||||
{
|
||||
if (BaseFormatter<T>.IsValueType)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (T)FormatterServices.GetUninitializedObject(typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the given object reference in the deserialization context.
|
||||
/// <para />
|
||||
/// NOTE that this method only does anything if <see cref="T"/> is not a value type.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to register.</param>
|
||||
/// <param name="reader">The reader which is currently being used.</param>
|
||||
protected void RegisterReferenceID(T value, IDataReader reader)
|
||||
{
|
||||
if (!BaseFormatter<T>.IsValueType)
|
||||
{
|
||||
int id = reader.CurrentNodeId;
|
||||
|
||||
if (id < 0)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogWarning("Reference type node is missing id upon deserialization. Some references may be broken. This tends to happen if a value type has changed to a reference type (IE, struct to class) since serialization took place.");
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Context.RegisterInternalReference(id, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all methods on the object with the [OnDeserializing] attribute.
|
||||
/// <para />
|
||||
/// WARNING: This method will not be called automatically if you override GetUninitializedObject and return null! You will have to call it manually after having created the object instance during deserialization.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to invoke the callbacks on.</param>
|
||||
/// <param name="context">The deserialization context.</param>
|
||||
[Obsolete("Use the InvokeOnDeserializingCallbacks variant that takes a ref T value instead. This is for struct compatibility reasons.", false)]
|
||||
protected void InvokeOnDeserializingCallbacks(T value, DeserializationContext context)
|
||||
{
|
||||
this.InvokeOnDeserializingCallbacks(ref value, context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes all methods on the object with the [OnDeserializing] attribute.
|
||||
/// <para />
|
||||
/// WARNING: This method will not be called automatically if you override GetUninitializedObject and return null! You will have to call it manually after having created the object instance during deserialization.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to invoke the callbacks on.</param>
|
||||
/// <param name="context">The deserialization context.</param>
|
||||
protected void InvokeOnDeserializingCallbacks(ref T value, DeserializationContext context)
|
||||
{
|
||||
for (int i = 0; i < OnDeserializingCallbacks.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnDeserializingCallbacks[i](ref value, context.StreamingContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected abstract void DeserializeImplementation(ref T value, IDataReader reader);
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected abstract void SerializeImplementation(ref T value, IDataWriter writer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9598679c29f3e3696941746c26f1ccf8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dff51bfb9b4d71aa78b3e5c8fec8c924
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3480954e7eecdc9745c1d08721b2f8b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="DelegateFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Formatter for all delegate types.
|
||||
/// <para />
|
||||
/// This formatter can handle anything but delegates for dynamic methods.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <seealso cref="BaseFormatter{T}" />
|
||||
public sealed class DelegateFormatter<T> : BaseFormatter<T> where T : class
|
||||
{
|
||||
static DelegateFormatter()
|
||||
{
|
||||
if (typeof(Delegate).IsAssignableFrom(typeof(T)) == false)
|
||||
{
|
||||
throw new ArgumentException("The type " + typeof(T) + " is not a delegate.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="!:T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="M:OdinSerializer.BaseFormatter`1.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected override void DeserializeImplementation(ref T value, IDataReader reader)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogWarning("Delegate Deserialization has been removed for security.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="!:T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref T value, IDataWriter writer)
|
||||
{
|
||||
writer.Context.Config.DebugContext.LogWarning("Delegate Deserialization has been removed for security.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an uninitialized object of type <see cref="!:T" />. WARNING: If you override this and return null, the object's ID will not be automatically registered and its OnDeserializing callbacks will not be automatically called, before deserialization begins.
|
||||
/// You will have to call <see cref="M:OdinSerializer.BaseFormatter`1.RegisterReferenceID(`0,OdinSerializer.IDataReader)" /> and <see cref="M:OdinSerializer.BaseFormatter`1.InvokeOnDeserializingCallbacks(`0,OdinSerializer.DeserializationContext)" /> immediately after creating the object yourself during deserialization.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An uninitialized object of type <see cref="!:T" />.
|
||||
/// </returns>
|
||||
protected override T GetUninitializedObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f17b17e986ae6f3be6a2ea1b716fcaf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="DerivedDictionaryFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatter(typeof(DerivedDictionaryFormatter<,,>))]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
/// <summary>
|
||||
/// Emergency hack class to support serialization of types derived from dictionary
|
||||
/// </summary>
|
||||
internal sealed class DerivedDictionaryFormatter<TDictionary, TKey, TValue> : BaseFormatter<TDictionary>
|
||||
where TDictionary : Dictionary<TKey, TValue>, new()
|
||||
{
|
||||
private static readonly bool KeyIsValueType = typeof(TKey).IsValueType;
|
||||
|
||||
private static readonly Serializer<IEqualityComparer<TKey>> EqualityComparerSerializer = Serializer.Get<IEqualityComparer<TKey>>();
|
||||
private static readonly Serializer<TKey> KeyReaderWriter = Serializer.Get<TKey>();
|
||||
private static readonly Serializer<TValue> ValueReaderWriter = Serializer.Get<TValue>();
|
||||
|
||||
private static readonly ConstructorInfo ComparerConstructor = typeof(TDictionary).GetConstructor(new Type[] { typeof(IEqualityComparer<TKey>) });
|
||||
|
||||
static DerivedDictionaryFormatter()
|
||||
{
|
||||
// This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor
|
||||
// which it otherwise seems prone to do, regardless of what might be defined in any link.xml file.
|
||||
|
||||
new DerivedDictionaryFormatter<Dictionary<int, string>, int, string>();
|
||||
}
|
||||
|
||||
public DerivedDictionaryFormatter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns null.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A value of null.
|
||||
/// </returns>
|
||||
protected override TDictionary GetUninitializedObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected override void DeserializeImplementation(ref TDictionary value, IDataReader reader)
|
||||
{
|
||||
string name;
|
||||
var entry = reader.PeekEntry(out name);
|
||||
|
||||
IEqualityComparer<TKey> comparer = null;
|
||||
|
||||
if (name == "comparer" || entry == EntryType.StartOfNode)
|
||||
{
|
||||
// There is a comparer serialized
|
||||
comparer = EqualityComparerSerializer.ReadValue(reader);
|
||||
entry = reader.PeekEntry(out name);
|
||||
}
|
||||
|
||||
if (entry == EntryType.StartOfArray)
|
||||
{
|
||||
try
|
||||
{
|
||||
long length;
|
||||
reader.EnterArray(out length);
|
||||
Type type;
|
||||
|
||||
if (!object.ReferenceEquals(comparer, null) && ComparerConstructor != null)
|
||||
{
|
||||
value = (TDictionary)ComparerConstructor.Invoke(new object[] { comparer });
|
||||
}
|
||||
else
|
||||
{
|
||||
value = new TDictionary();
|
||||
}
|
||||
|
||||
// We must remember to register the dictionary reference ourselves, since we returned null in GetUninitializedObject
|
||||
this.RegisterReferenceID(value, reader);
|
||||
|
||||
// There aren't any OnDeserializing callbacks on dictionaries that we're interested in.
|
||||
// Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (reader.PeekEntry(out name) == EntryType.EndOfArray)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected.");
|
||||
break;
|
||||
}
|
||||
|
||||
bool exitNode = true;
|
||||
|
||||
try
|
||||
{
|
||||
reader.EnterNode(out type);
|
||||
TKey key = KeyReaderWriter.ReadValue(reader);
|
||||
TValue val = ValueReaderWriter.ReadValue(reader);
|
||||
|
||||
if (!KeyIsValueType && object.ReferenceEquals(key, null))
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogWarning("Dictionary key of type '" + typeof(TKey).FullName + "' was null upon deserialization. A key has gone missing.");
|
||||
continue;
|
||||
}
|
||||
|
||||
value[key] = val;
|
||||
}
|
||||
catch (SerializationAbortException ex)
|
||||
{
|
||||
exitNode = false;
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (exitNode)
|
||||
{
|
||||
reader.ExitNode();
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.IsInArrayNode == false)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.ExitArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.SkipEntry();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref TDictionary value, IDataWriter writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (value.Comparer != null)
|
||||
{
|
||||
EqualityComparerSerializer.WriteValue("comparer", value.Comparer, writer);
|
||||
}
|
||||
|
||||
writer.BeginArrayNode(value.Count);
|
||||
|
||||
foreach (var pair in value)
|
||||
{
|
||||
bool endNode = true;
|
||||
|
||||
try
|
||||
{
|
||||
writer.BeginStructNode(null, null);
|
||||
KeyReaderWriter.WriteValue("$k", pair.Key, writer);
|
||||
ValueReaderWriter.WriteValue("$v", pair.Value, writer);
|
||||
}
|
||||
catch (SerializationAbortException ex)
|
||||
{
|
||||
endNode = false;
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
writer.Context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (endNode)
|
||||
{
|
||||
writer.EndNode(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
writer.EndArrayNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4402da708267c579874c808a813bfe62
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,215 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="DictionaryFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatter(typeof(DictionaryFormatter<,>))]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Custom generic formatter for the generic type definition <see cref="Dictionary{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the dictionary key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the dictionary value.</typeparam>
|
||||
/// <seealso cref="BaseFormatter{System.Collections.Generic.Dictionary{TKey, TValue}}" />
|
||||
public sealed class DictionaryFormatter<TKey, TValue> : BaseFormatter<Dictionary<TKey, TValue>>
|
||||
{
|
||||
private static readonly bool KeyIsValueType = typeof(TKey).IsValueType;
|
||||
|
||||
private static readonly Serializer<IEqualityComparer<TKey>> EqualityComparerSerializer = Serializer.Get<IEqualityComparer<TKey>>();
|
||||
private static readonly Serializer<TKey> KeyReaderWriter = Serializer.Get<TKey>();
|
||||
private static readonly Serializer<TValue> ValueReaderWriter = Serializer.Get<TValue>();
|
||||
|
||||
static DictionaryFormatter()
|
||||
{
|
||||
// This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor
|
||||
// which it otherwise seems prone to do, regardless of what might be defined in any link.xml file.
|
||||
|
||||
new DictionaryFormatter<int, string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="DictionaryFormatter{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
public DictionaryFormatter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns null.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A value of null.
|
||||
/// </returns>
|
||||
protected override Dictionary<TKey, TValue> GetUninitializedObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected override void DeserializeImplementation(ref Dictionary<TKey, TValue> value, IDataReader reader)
|
||||
{
|
||||
string name;
|
||||
var entry = reader.PeekEntry(out name);
|
||||
|
||||
IEqualityComparer<TKey> comparer = null;
|
||||
|
||||
// TODO: Remove this clause in patch 1.1 or later, when it has had time to take effect in people's serialized data
|
||||
// Clause was introduced in the patch released after 1.0.5.3
|
||||
if (name == "comparer" || entry != EntryType.StartOfArray)
|
||||
{
|
||||
// There is a comparer serialized
|
||||
comparer = EqualityComparerSerializer.ReadValue(reader);
|
||||
entry = reader.PeekEntry(out name);
|
||||
}
|
||||
|
||||
if (entry == EntryType.StartOfArray)
|
||||
{
|
||||
try
|
||||
{
|
||||
long length;
|
||||
reader.EnterArray(out length);
|
||||
Type type;
|
||||
|
||||
value = object.ReferenceEquals(comparer, null) ?
|
||||
new Dictionary<TKey, TValue>((int)length) :
|
||||
new Dictionary<TKey, TValue>((int)length, comparer);
|
||||
|
||||
// We must remember to register the dictionary reference ourselves, since we return null in GetUninitializedObject
|
||||
this.RegisterReferenceID(value, reader);
|
||||
|
||||
// There aren't any OnDeserializing callbacks on dictionaries that we're interested in.
|
||||
// Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (reader.PeekEntry(out name) == EntryType.EndOfArray)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected.");
|
||||
break;
|
||||
}
|
||||
|
||||
bool exitNode = true;
|
||||
|
||||
try
|
||||
{
|
||||
reader.EnterNode(out type);
|
||||
TKey key = KeyReaderWriter.ReadValue(reader);
|
||||
TValue val = ValueReaderWriter.ReadValue(reader);
|
||||
|
||||
if (!KeyIsValueType && object.ReferenceEquals(key, null))
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogWarning("Dictionary key of type '" + typeof(TKey).FullName + "' was null upon deserialization. A key has gone missing.");
|
||||
continue;
|
||||
}
|
||||
|
||||
value[key] = val;
|
||||
}
|
||||
catch (SerializationAbortException ex)
|
||||
{
|
||||
exitNode = false;
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (exitNode)
|
||||
{
|
||||
reader.ExitNode();
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.IsInArrayNode == false)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.ExitArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.SkipEntry();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref Dictionary<TKey, TValue> value, IDataWriter writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (value.Comparer != null)
|
||||
{
|
||||
EqualityComparerSerializer.WriteValue("comparer", value.Comparer, writer);
|
||||
}
|
||||
|
||||
writer.BeginArrayNode(value.Count);
|
||||
|
||||
foreach (var pair in value)
|
||||
{
|
||||
bool endNode = true;
|
||||
|
||||
try
|
||||
{
|
||||
writer.BeginStructNode(null, null);
|
||||
KeyReaderWriter.WriteValue("$k", pair.Key, writer);
|
||||
ValueReaderWriter.WriteValue("$v", pair.Value, writer);
|
||||
}
|
||||
catch (SerializationAbortException ex)
|
||||
{
|
||||
endNode = false;
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
writer.Context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (endNode)
|
||||
{
|
||||
writer.EndNode(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
writer.EndArrayNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b80567603fe814a8b4341584f8c3b4a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="DoubleLookupDictionaryFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatter(typeof(DoubleLookupDictionaryFormatter<,,>))]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using VRC.Udon.Serialization.OdinSerializer.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Custom Odin serialization formatter for <see cref="DoubleLookupDictionary{TFirstKey, TSecondKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPrimary">Type of primary key.</typeparam>
|
||||
/// <typeparam name="TSecondary">Type of secondary key.</typeparam>
|
||||
/// <typeparam name="TValue">Type of value.</typeparam>
|
||||
public sealed class DoubleLookupDictionaryFormatter<TPrimary, TSecondary, TValue> : BaseFormatter<DoubleLookupDictionary<TPrimary, TSecondary, TValue>>
|
||||
{
|
||||
private static readonly Serializer<TPrimary> PrimaryReaderWriter = Serializer.Get<TPrimary>();
|
||||
private static readonly Serializer<Dictionary<TSecondary, TValue>> InnerReaderWriter = Serializer.Get<Dictionary<TSecondary, TValue>>();
|
||||
|
||||
static DoubleLookupDictionaryFormatter()
|
||||
{
|
||||
new DoubleLookupDictionaryFormatter<int, int, string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="DoubleLookupDictionaryFormatter{TPrimary, TSecondary, TValue}"/>.
|
||||
/// </summary>
|
||||
public DoubleLookupDictionaryFormatter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns null.
|
||||
/// </summary>
|
||||
protected override DoubleLookupDictionary<TPrimary, TSecondary, TValue> GetUninitializedObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="!:T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref DoubleLookupDictionary<TPrimary, TSecondary, TValue> value, IDataWriter writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.BeginArrayNode(value.Count);
|
||||
|
||||
bool endNode = true;
|
||||
|
||||
foreach (var pair in value)
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.BeginStructNode(null, null);
|
||||
PrimaryReaderWriter.WriteValue("$k", pair.Key, writer);
|
||||
InnerReaderWriter.WriteValue("$v", pair.Value, writer);
|
||||
}
|
||||
catch (SerializationAbortException ex)
|
||||
{
|
||||
endNode = false;
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
writer.Context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (endNode)
|
||||
{
|
||||
writer.EndNode(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
writer.EndArrayNode();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="!:T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="M:OdinSerializer.BaseFormatter`1.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected override void DeserializeImplementation(ref DoubleLookupDictionary<TPrimary, TSecondary, TValue> value, IDataReader reader)
|
||||
{
|
||||
string name;
|
||||
var entry = reader.PeekEntry(out name);
|
||||
|
||||
if (entry == EntryType.StartOfArray)
|
||||
{
|
||||
try
|
||||
{
|
||||
long length;
|
||||
reader.EnterArray(out length);
|
||||
Type type;
|
||||
value = new DoubleLookupDictionary<TPrimary, TSecondary, TValue>();
|
||||
|
||||
this.RegisterReferenceID(value, reader);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (reader.PeekEntry(out name) == EntryType.EndOfArray)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected.");
|
||||
break;
|
||||
}
|
||||
|
||||
bool exitNode = true;
|
||||
|
||||
try
|
||||
{
|
||||
reader.EnterNode(out type);
|
||||
TPrimary key = PrimaryReaderWriter.ReadValue(reader);
|
||||
Dictionary<TSecondary, TValue> inner = InnerReaderWriter.ReadValue(reader);
|
||||
|
||||
value.Add(key, inner);
|
||||
}
|
||||
catch (SerializationAbortException ex)
|
||||
{
|
||||
exitNode = false;
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (exitNode)
|
||||
{
|
||||
reader.ExitNode();
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.IsInArrayNode == false)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.ExitArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.SkipEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c21ee7e54dff531da57563e2f81fff5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54578488936f8484c97ba7c52bfb0563
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e226537cbfa910681132da3718f41c34
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 149c482b2ab9c601b8bc2ecc20bbd8d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,602 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="FormatterEmitter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
#if (UNITY_EDITOR || UNITY_STANDALONE) && !ENABLE_IL2CPP && NET_4_6
|
||||
#define CAN_EMIT
|
||||
#endif
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using VRC.Udon.Serialization.OdinSerializer.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
#if CAN_EMIT
|
||||
|
||||
using System.Reflection.Emit;
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for emitting formatters using the <see cref="System.Reflection.Emit"/> namespace.
|
||||
/// <para />
|
||||
/// NOTE: Some platforms do not support emitting. Check whether you can emit on the current platform using <see cref="EmitUtilities.CanEmit"/>.
|
||||
/// </summary>
|
||||
public static class FormatterEmitter
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the pre-generated assembly that contains pre-emitted formatters for use on AOT platforms where emitting is not supported. Note that this assembly is not always present.
|
||||
/// </summary>
|
||||
public const string PRE_EMITTED_ASSEMBLY_NAME = "OdinSerializer.AOTGenerated";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the runtime-generated assembly that contains runtime-emitted formatters for use on non-AOT platforms where emitting is supported. Note that this assembly is not always present.
|
||||
/// </summary>
|
||||
public const string RUNTIME_EMITTED_ASSEMBLY_NAME = "OdinSerializer.RuntimeEmitted";
|
||||
|
||||
/// <summary>
|
||||
/// Base type for all AOT-emitted formatters.
|
||||
/// </summary>
|
||||
[EmittedFormatter]
|
||||
public abstract class AOTEmittedFormatter<T> : EasyBaseFormatter<T>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut class that makes it easier to emit empty AOT formatters.
|
||||
/// </summary>
|
||||
public abstract class EmptyAOTEmittedFormatter<T> : AOTEmittedFormatter<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Skips the entry to read.
|
||||
/// </summary>
|
||||
protected override void ReadDataEntry(ref T value, string entryName, EntryType entryType, IDataReader reader)
|
||||
{
|
||||
reader.SkipEntry();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does nothing at all.
|
||||
/// </summary>
|
||||
protected override void WriteDataEntries(ref T value, IDataWriter writer)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#if CAN_EMIT
|
||||
|
||||
private static readonly object LOCK = new object();
|
||||
private static readonly DoubleLookupDictionary<ISerializationPolicy, Type, IFormatter> Formatters = new DoubleLookupDictionary<ISerializationPolicy, Type, IFormatter>();
|
||||
|
||||
private static AssemblyBuilder runtimeEmittedAssembly;
|
||||
private static ModuleBuilder runtimeEmittedModule;
|
||||
|
||||
public delegate void ReadDataEntryMethodDelegate<T>(ref T value, string entryName, EntryType entryType, IDataReader reader);
|
||||
|
||||
public delegate void WriteDataEntriesMethodDelegate<T>(ref T value, IDataWriter writer);
|
||||
|
||||
[EmittedFormatter]
|
||||
public sealed class RuntimeEmittedFormatter<T> : EasyBaseFormatter<T>
|
||||
{
|
||||
public readonly ReadDataEntryMethodDelegate<T> Read;
|
||||
public readonly WriteDataEntriesMethodDelegate<T> Write;
|
||||
|
||||
public RuntimeEmittedFormatter(ReadDataEntryMethodDelegate<T> read, WriteDataEntriesMethodDelegate<T> write)
|
||||
{
|
||||
this.Read = read;
|
||||
this.Write = write;
|
||||
}
|
||||
|
||||
protected override void ReadDataEntry(ref T value, string entryName, EntryType entryType, IDataReader reader)
|
||||
{
|
||||
this.Read(ref value, entryName, entryType, reader);
|
||||
}
|
||||
|
||||
protected override void WriteDataEntries(ref T value, IDataWriter writer)
|
||||
{
|
||||
this.Write(ref value, writer);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets an emitted formatter for a given type.
|
||||
/// <para />
|
||||
/// NOTE: Some platforms do not support emitting. On such platforms, this method logs an error and returns null. Check whether you can emit on the current platform using <see cref="EmitUtilities.CanEmit"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to emit a formatter for.</param>
|
||||
/// <param name="policy">The serialization policy to use to determine which members the emitted formatter should serialize. If null, <see cref="SerializationPolicies.Strict"/> is used.</param>
|
||||
/// <returns>The type of the emitted formatter.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">The type argument is null.</exception>
|
||||
public static IFormatter GetEmittedFormatter(Type type, ISerializationPolicy policy)
|
||||
{
|
||||
#if !CAN_EMIT
|
||||
Debug.LogError("Cannot use Reflection.Emit on the current platform. The FormatterEmitter class is currently disabled. Check whether emitting is currently possible with EmitUtilities.CanEmit.");
|
||||
return null;
|
||||
#else
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException("type");
|
||||
}
|
||||
|
||||
if (policy == null)
|
||||
{
|
||||
policy = SerializationPolicies.Strict;
|
||||
}
|
||||
|
||||
IFormatter result = null;
|
||||
|
||||
if (Formatters.TryGetInnerValue(policy, type, out result) == false)
|
||||
{
|
||||
lock (LOCK)
|
||||
{
|
||||
if (Formatters.TryGetInnerValue(policy, type, out result) == false)
|
||||
{
|
||||
EnsureRuntimeAssembly();
|
||||
|
||||
try
|
||||
{
|
||||
result = CreateGenericFormatter(type, runtimeEmittedModule, policy);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError("The following error occurred while emitting a formatter for the type " + type.Name);
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
|
||||
Formatters.AddInner(policy, type, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CAN_EMIT
|
||||
|
||||
private static void EnsureRuntimeAssembly()
|
||||
{
|
||||
// We always hold the lock in this method
|
||||
|
||||
if (runtimeEmittedAssembly == null)
|
||||
{
|
||||
var assemblyName = new AssemblyName(RUNTIME_EMITTED_ASSEMBLY_NAME);
|
||||
|
||||
assemblyName.CultureInfo = System.Globalization.CultureInfo.InvariantCulture;
|
||||
assemblyName.Flags = AssemblyNameFlags.None;
|
||||
assemblyName.ProcessorArchitecture = ProcessorArchitecture.MSIL;
|
||||
assemblyName.VersionCompatibility = System.Configuration.Assemblies.AssemblyVersionCompatibility.SameDomain;
|
||||
|
||||
runtimeEmittedAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
|
||||
}
|
||||
|
||||
if (runtimeEmittedModule == null)
|
||||
{
|
||||
bool emitSymbolInfo;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
emitSymbolInfo = true;
|
||||
#else
|
||||
// Builds cannot emit symbol info
|
||||
emitSymbolInfo = false;
|
||||
#endif
|
||||
|
||||
runtimeEmittedModule = runtimeEmittedAssembly.DefineDynamicModule(RUNTIME_EMITTED_ASSEMBLY_NAME, emitSymbolInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits a formatter for a given type into a given module builder, using a given serialization policy to determine which members to serialize.
|
||||
/// </summary>
|
||||
/// <param name="formattedType">Type to create a formatter for.</param>
|
||||
/// <param name="moduleBuilder">The module builder to emit a formatter into.</param>
|
||||
/// <param name="policy">The serialization policy to use for creating the formatter.</param>
|
||||
/// <returns>The fully constructed, emitted formatter type.</returns>
|
||||
public static Type EmitAOTFormatter(Type formattedType, ModuleBuilder moduleBuilder, ISerializationPolicy policy)
|
||||
{
|
||||
Dictionary<string, MemberInfo> serializableMembers = FormatterUtilities.GetSerializableMembersMap(formattedType, policy);
|
||||
|
||||
string formatterName = moduleBuilder.Name + "." + formattedType.GetCompilableNiceFullName() + "__AOTFormatter";
|
||||
string formatterHelperName = moduleBuilder.Name + "." + formattedType.GetCompilableNiceFullName() + "__FormatterHelper";
|
||||
|
||||
if (serializableMembers.Count == 0)
|
||||
{
|
||||
return moduleBuilder.DefineType(
|
||||
formatterName,
|
||||
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class,
|
||||
typeof(EmptyAOTEmittedFormatter<>).MakeGenericType(formattedType)
|
||||
).CreateType();
|
||||
}
|
||||
|
||||
Dictionary<Type, MethodInfo> serializerReadMethods;
|
||||
Dictionary<Type, MethodInfo> serializerWriteMethods;
|
||||
Dictionary<Type, FieldBuilder> serializerFields;
|
||||
FieldBuilder dictField;
|
||||
Dictionary<MemberInfo, List<string>> memberNames;
|
||||
|
||||
BuildHelperType(
|
||||
moduleBuilder,
|
||||
formatterHelperName,
|
||||
formattedType,
|
||||
serializableMembers,
|
||||
out serializerReadMethods,
|
||||
out serializerWriteMethods,
|
||||
out serializerFields,
|
||||
out dictField,
|
||||
out memberNames
|
||||
);
|
||||
|
||||
TypeBuilder formatterType = moduleBuilder.DefineType(
|
||||
formatterName,
|
||||
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class,
|
||||
typeof(AOTEmittedFormatter<>).MakeGenericType(formattedType)
|
||||
);
|
||||
|
||||
// Read
|
||||
{
|
||||
MethodInfo readBaseMethod = formatterType.BaseType.GetMethod("ReadDataEntry", Flags.InstanceAnyVisibility);
|
||||
|
||||
MethodBuilder readMethod = formatterType.DefineMethod(
|
||||
readBaseMethod.Name,
|
||||
MethodAttributes.Family | MethodAttributes.Virtual,
|
||||
readBaseMethod.ReturnType,
|
||||
readBaseMethod.GetParameters().Select(n => n.ParameterType).ToArray()
|
||||
);
|
||||
|
||||
readBaseMethod.GetParameters().ForEach(n => readMethod.DefineParameter(n.Position, n.Attributes, n.Name));
|
||||
EmitReadMethodContents(readMethod.GetILGenerator(), formattedType, dictField, serializerFields, memberNames, serializerReadMethods);
|
||||
}
|
||||
|
||||
// Write
|
||||
{
|
||||
MethodInfo writeBaseMethod = formatterType.BaseType.GetMethod("WriteDataEntries", Flags.InstanceAnyVisibility);
|
||||
|
||||
MethodBuilder dynamicWriteMethod = formatterType.DefineMethod(
|
||||
writeBaseMethod.Name,
|
||||
MethodAttributes.Family | MethodAttributes.Virtual,
|
||||
writeBaseMethod.ReturnType,
|
||||
writeBaseMethod.GetParameters().Select(n => n.ParameterType).ToArray()
|
||||
);
|
||||
|
||||
writeBaseMethod.GetParameters().ForEach(n => dynamicWriteMethod.DefineParameter(n.Position + 1, n.Attributes, n.Name));
|
||||
EmitWriteMethodContents(dynamicWriteMethod.GetILGenerator(), formattedType, serializerFields, memberNames, serializerWriteMethods);
|
||||
}
|
||||
|
||||
var result = formatterType.CreateType();
|
||||
|
||||
// Register the formatter on the assembly
|
||||
((AssemblyBuilder)moduleBuilder.Assembly).SetCustomAttribute(new CustomAttributeBuilder(typeof(RegisterFormatterAttribute).GetConstructor(new Type[] { typeof(Type), typeof(int) }), new object[] { formatterType, -1 }));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IFormatter CreateGenericFormatter(Type formattedType, ModuleBuilder moduleBuilder, ISerializationPolicy policy)
|
||||
{
|
||||
Dictionary<string, MemberInfo> serializableMembers = FormatterUtilities.GetSerializableMembersMap(formattedType, policy);
|
||||
|
||||
if (serializableMembers.Count == 0)
|
||||
{
|
||||
return (IFormatter)Activator.CreateInstance(typeof(EmptyTypeFormatter<>).MakeGenericType(formattedType));
|
||||
}
|
||||
|
||||
string helperTypeName = moduleBuilder.Name + "." + formattedType.GetCompilableNiceFullName() + "___" + formattedType.Assembly.GetName().Name + "___FormatterHelper___" + Guid.NewGuid().ToString();
|
||||
|
||||
Dictionary<Type, MethodInfo> serializerReadMethods;
|
||||
Dictionary<Type, MethodInfo> serializerWriteMethods;
|
||||
Dictionary<Type, FieldBuilder> serializerFields;
|
||||
FieldBuilder dictField;
|
||||
Dictionary<MemberInfo, List<string>> memberNames;
|
||||
|
||||
BuildHelperType(
|
||||
moduleBuilder,
|
||||
helperTypeName,
|
||||
formattedType,
|
||||
serializableMembers,
|
||||
out serializerReadMethods,
|
||||
out serializerWriteMethods,
|
||||
out serializerFields,
|
||||
out dictField,
|
||||
out memberNames
|
||||
);
|
||||
|
||||
Type formatterType = typeof(RuntimeEmittedFormatter<>).MakeGenericType(formattedType);
|
||||
Delegate del1, del2;
|
||||
|
||||
// Read
|
||||
{
|
||||
Type readDelegateType = typeof(ReadDataEntryMethodDelegate<>).MakeGenericType(formattedType);
|
||||
MethodInfo readDataEntryMethod = formatterType.GetMethod("ReadDataEntry", Flags.InstanceAnyVisibility);
|
||||
DynamicMethod dynamicReadMethod = new DynamicMethod("Dynamic_" + formattedType.GetCompilableNiceFullName(), null, readDataEntryMethod.GetParameters().Select(n => n.ParameterType).ToArray(), true);
|
||||
readDataEntryMethod.GetParameters().ForEach(n => dynamicReadMethod.DefineParameter(n.Position, n.Attributes, n.Name));
|
||||
EmitReadMethodContents(dynamicReadMethod.GetILGenerator(), formattedType, dictField, serializerFields, memberNames, serializerReadMethods);
|
||||
del1 = dynamicReadMethod.CreateDelegate(readDelegateType);
|
||||
}
|
||||
|
||||
// Write
|
||||
{
|
||||
Type writeDelegateType = typeof(WriteDataEntriesMethodDelegate<>).MakeGenericType(formattedType);
|
||||
MethodInfo writeDataEntriesMethod = formatterType.GetMethod("WriteDataEntries", Flags.InstanceAnyVisibility);
|
||||
DynamicMethod dynamicWriteMethod = new DynamicMethod("Dynamic_Write_" + formattedType.GetCompilableNiceFullName(), null, writeDataEntriesMethod.GetParameters().Select(n => n.ParameterType).ToArray(), true);
|
||||
writeDataEntriesMethod.GetParameters().ForEach(n => dynamicWriteMethod.DefineParameter(n.Position + 1, n.Attributes, n.Name));
|
||||
EmitWriteMethodContents(dynamicWriteMethod.GetILGenerator(), formattedType, serializerFields, memberNames, serializerWriteMethods);
|
||||
del2 = dynamicWriteMethod.CreateDelegate(writeDelegateType);
|
||||
}
|
||||
|
||||
return (IFormatter)Activator.CreateInstance(formatterType, del1, del2);
|
||||
}
|
||||
|
||||
private static Type BuildHelperType(
|
||||
ModuleBuilder moduleBuilder,
|
||||
string helperTypeName,
|
||||
Type formattedType,
|
||||
Dictionary<string, MemberInfo> serializableMembers,
|
||||
out Dictionary<Type, MethodInfo> serializerReadMethods,
|
||||
out Dictionary<Type, MethodInfo> serializerWriteMethods,
|
||||
out Dictionary<Type, FieldBuilder> serializerFields,
|
||||
out FieldBuilder dictField,
|
||||
out Dictionary<MemberInfo, List<string>> memberNames)
|
||||
{
|
||||
TypeBuilder helperTypeBuilder = moduleBuilder.DefineType(helperTypeName, TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class);
|
||||
|
||||
memberNames = new Dictionary<MemberInfo, List<string>>();
|
||||
|
||||
foreach (var entry in serializableMembers)
|
||||
{
|
||||
List<string> list;
|
||||
|
||||
if (memberNames.TryGetValue(entry.Value, out list) == false)
|
||||
{
|
||||
list = new List<string>();
|
||||
memberNames.Add(entry.Value, list);
|
||||
}
|
||||
|
||||
list.Add(entry.Key);
|
||||
}
|
||||
|
||||
dictField = helperTypeBuilder.DefineField("SwitchLookup", typeof(Dictionary<string, int>), FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly);
|
||||
|
||||
List<Type> neededSerializers = memberNames.Keys.Select(n => FormatterUtilities.GetContainedType(n)).Distinct().ToList();
|
||||
|
||||
serializerReadMethods = new Dictionary<Type, MethodInfo>(neededSerializers.Count);
|
||||
serializerWriteMethods = new Dictionary<Type, MethodInfo>(neededSerializers.Count);
|
||||
serializerFields = new Dictionary<Type, FieldBuilder>(neededSerializers.Count);
|
||||
|
||||
foreach (var t in neededSerializers)
|
||||
{
|
||||
string name = t.GetCompilableNiceFullName() + "__Serializer";
|
||||
int counter = 1;
|
||||
|
||||
while (serializerFields.Values.Any(n => n.Name == name))
|
||||
{
|
||||
counter++;
|
||||
name = t.GetCompilableNiceFullName() + "__Serializer" + counter;
|
||||
}
|
||||
|
||||
Type serializerType = typeof(Serializer<>).MakeGenericType(t);
|
||||
|
||||
serializerReadMethods.Add(t, serializerType.GetMethod("ReadValue", Flags.InstancePublicDeclaredOnly));
|
||||
serializerWriteMethods.Add(t, serializerType.GetMethod("WriteValue", Flags.InstancePublicDeclaredOnly, null, new[] { typeof(string), t, typeof(IDataWriter) }, null));
|
||||
serializerFields.Add(t, helperTypeBuilder.DefineField(name, serializerType, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly));
|
||||
}
|
||||
|
||||
//FieldBuilder readMethodFieldBuilder = helperTypeBuilder.DefineField("ReadMethod", readDelegateType, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly);
|
||||
//FieldBuilder writeMethodFieldBuilder = helperTypeBuilder.DefineField("WriteMethod", writeDelegateType, FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly);
|
||||
|
||||
// We generate a static constructor for our formatter helper type that initializes our switch lookup dictionary and our needed Serializer references
|
||||
{
|
||||
var addMethod = typeof(Dictionary<string, int>).GetMethod("Add", Flags.InstancePublic);
|
||||
var dictionaryConstructor = typeof(Dictionary<string, int>).GetConstructor(Type.EmptyTypes);
|
||||
var serializerGetMethod = typeof(Serializer).GetMethod("Get", Flags.StaticPublic, null, new[] { typeof(Type) }, null);
|
||||
var typeOfMethod = typeof(Type).GetMethod("GetTypeFromHandle", Flags.StaticPublic, null, new Type[] { typeof(RuntimeTypeHandle) }, null);
|
||||
|
||||
ConstructorBuilder staticConstructor = helperTypeBuilder.DefineTypeInitializer();
|
||||
ILGenerator gen = staticConstructor.GetILGenerator();
|
||||
|
||||
gen.Emit(OpCodes.Newobj, dictionaryConstructor); // Create new dictionary
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (var entry in memberNames)
|
||||
{
|
||||
foreach (var name in entry.Value)
|
||||
{
|
||||
gen.Emit(OpCodes.Dup); // Load duplicate dictionary value
|
||||
gen.Emit(OpCodes.Ldstr, name); // Load entry name
|
||||
gen.Emit(OpCodes.Ldc_I4, count); // Load entry index
|
||||
gen.Emit(OpCodes.Call, addMethod); // Call dictionary add
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
gen.Emit(OpCodes.Stsfld, dictField); // Set static dictionary field to dictionary value
|
||||
|
||||
foreach (var entry in serializerFields)
|
||||
{
|
||||
gen.Emit(OpCodes.Ldtoken, entry.Key); // Load type token
|
||||
gen.Emit(OpCodes.Call, typeOfMethod); // Call typeof method (this pushes a type value onto the stack)
|
||||
gen.Emit(OpCodes.Call, serializerGetMethod); // Call Serializer.Get(Type type) method
|
||||
gen.Emit(OpCodes.Stsfld, entry.Value); // Set static serializer field to result of get method
|
||||
}
|
||||
|
||||
gen.Emit(OpCodes.Ret); // Return
|
||||
}
|
||||
|
||||
// Now we need to actually create the serializer container type so we can generate the dynamic methods below without getting TypeLoadExceptions up the wazoo
|
||||
return helperTypeBuilder.CreateType();
|
||||
}
|
||||
|
||||
private static void EmitReadMethodContents(
|
||||
ILGenerator gen,
|
||||
Type formattedType,
|
||||
FieldInfo dictField,
|
||||
Dictionary<Type, FieldBuilder> serializerFields,
|
||||
Dictionary<MemberInfo, List<string>> memberNames,
|
||||
Dictionary<Type, MethodInfo> serializerReadMethods)
|
||||
{
|
||||
MethodInfo skipMethod = typeof(IDataReader).GetMethod("SkipEntry", Flags.InstancePublic);
|
||||
MethodInfo tryGetValueMethod = typeof(Dictionary<string, int>).GetMethod("TryGetValue", Flags.InstancePublic);
|
||||
|
||||
//methodBuilder.DefineParameter(5, ParameterAttributes.None, "switchLookup");
|
||||
|
||||
LocalBuilder lookupResult = gen.DeclareLocal(typeof(int));
|
||||
|
||||
Label defaultLabel = gen.DefineLabel();
|
||||
Label switchLabel = gen.DefineLabel();
|
||||
Label endLabel = gen.DefineLabel();
|
||||
Label[] switchLabels = memberNames.Select(n => gen.DefineLabel()).ToArray();
|
||||
|
||||
gen.Emit(OpCodes.Ldarg_1); // Load entryName string
|
||||
gen.Emit(OpCodes.Ldnull); // Load null
|
||||
gen.Emit(OpCodes.Ceq); // Equality check
|
||||
gen.Emit(OpCodes.Brtrue, defaultLabel); // If entryName is null, go to default case
|
||||
|
||||
//gen.Emit(OpCodes.Ldarg, (short)4); // Load lookup dictionary argument (OLD CODE)
|
||||
|
||||
gen.Emit(OpCodes.Ldsfld, dictField); // Load lookup dictionary from static field on helper type
|
||||
gen.Emit(OpCodes.Ldarg_1); // Load entryName string
|
||||
gen.Emit(OpCodes.Ldloca, (short)lookupResult.LocalIndex); // Load address of lookupResult
|
||||
gen.Emit(OpCodes.Callvirt, tryGetValueMethod); // Call TryGetValue on the dictionary
|
||||
|
||||
gen.Emit(OpCodes.Brtrue, switchLabel); // If TryGetValue returned true, go to the switch case
|
||||
gen.Emit(OpCodes.Br, defaultLabel); // Else, go to the default case
|
||||
|
||||
gen.MarkLabel(switchLabel); // Switch starts here
|
||||
gen.Emit(OpCodes.Ldloc, lookupResult); // Load lookupResult
|
||||
gen.Emit(OpCodes.Switch, switchLabels); // Perform switch on switchLabels
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (var member in memberNames.Keys)
|
||||
{
|
||||
var memberType = FormatterUtilities.GetContainedType(member);
|
||||
|
||||
var propInfo = member as PropertyInfo;
|
||||
var fieldInfo = member as FieldInfo;
|
||||
|
||||
gen.MarkLabel(switchLabels[count]); // Switch case for [count] starts here
|
||||
|
||||
// Now we load the instance that we have to set the value on
|
||||
gen.Emit(OpCodes.Ldarg_0); // Load value reference
|
||||
|
||||
if (formattedType.IsValueType == false)
|
||||
{
|
||||
gen.Emit(OpCodes.Ldind_Ref); // Indirectly load value of reference
|
||||
}
|
||||
|
||||
// Now we deserialize the value itself
|
||||
gen.Emit(OpCodes.Ldsfld, serializerFields[memberType]); // Load serializer from serializer container type
|
||||
gen.Emit(OpCodes.Ldarg, (short)3); // Load reader argument
|
||||
gen.Emit(OpCodes.Callvirt, serializerReadMethods[memberType]); // Call Serializer.ReadValue(IDataReader reader)
|
||||
|
||||
// The stack now contains the formatted instance and the deserialized value to set the member to
|
||||
// Now we set the value
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
gen.Emit(OpCodes.Stfld, fieldInfo.DeAliasField()); // Set field
|
||||
}
|
||||
else if (propInfo != null)
|
||||
{
|
||||
gen.Emit(OpCodes.Callvirt, propInfo.DeAliasProperty().GetSetMethod(true)); // Call property setter
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
gen.Emit(OpCodes.Br, endLabel); // Jump to end of method
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
gen.MarkLabel(defaultLabel); // Default case starts here
|
||||
gen.Emit(OpCodes.Ldarg, (short)3); // Load reader argument
|
||||
gen.Emit(OpCodes.Callvirt, skipMethod); // Call IDataReader.SkipEntry
|
||||
|
||||
gen.MarkLabel(endLabel); // Method end starts here
|
||||
gen.Emit(OpCodes.Ret); // Return method
|
||||
}
|
||||
|
||||
private static void EmitWriteMethodContents(
|
||||
ILGenerator gen,
|
||||
Type formattedType,
|
||||
Dictionary<Type, FieldBuilder> serializerFields,
|
||||
Dictionary<MemberInfo, List<string>> memberNames,
|
||||
Dictionary<Type, MethodInfo> serializerWriteMethods)
|
||||
{
|
||||
foreach (var member in memberNames.Keys)
|
||||
{
|
||||
var memberType = FormatterUtilities.GetContainedType(member);
|
||||
|
||||
gen.Emit(OpCodes.Ldsfld, serializerFields[memberType]); // Load serializer instance for type
|
||||
gen.Emit(OpCodes.Ldstr, member.Name); // Load member name string
|
||||
|
||||
// Now we load the value of the actual member
|
||||
if (member is FieldInfo)
|
||||
{
|
||||
var fieldInfo = member as FieldInfo;
|
||||
|
||||
if (formattedType.IsValueType)
|
||||
{
|
||||
gen.Emit(OpCodes.Ldarg_0); // Load value argument
|
||||
gen.Emit(OpCodes.Ldfld, fieldInfo.DeAliasField()); // Load value of field
|
||||
}
|
||||
else
|
||||
{
|
||||
gen.Emit(OpCodes.Ldarg_0); // Load value argument reference
|
||||
gen.Emit(OpCodes.Ldind_Ref); // Indirectly load value of reference
|
||||
gen.Emit(OpCodes.Ldfld, fieldInfo.DeAliasField()); // Load value of field
|
||||
}
|
||||
}
|
||||
else if (member is PropertyInfo)
|
||||
{
|
||||
var propInfo = member as PropertyInfo;
|
||||
|
||||
if (formattedType.IsValueType)
|
||||
{
|
||||
gen.Emit(OpCodes.Ldarg_0); // Load value argument
|
||||
gen.Emit(OpCodes.Call, propInfo.DeAliasProperty().GetGetMethod(true)); // Call property getter
|
||||
}
|
||||
else
|
||||
{
|
||||
gen.Emit(OpCodes.Ldarg_0); // Load value argument reference
|
||||
gen.Emit(OpCodes.Ldind_Ref); // Indirectly load value of reference
|
||||
gen.Emit(OpCodes.Callvirt, propInfo.DeAliasProperty().GetGetMethod(true)); // Call property getter
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
gen.Emit(OpCodes.Ldarg_1); // Load writer argument
|
||||
gen.Emit(OpCodes.Callvirt, serializerWriteMethods[memberType]); // Call Serializer.WriteValue(string name, T value, IDataWriter writer)
|
||||
}
|
||||
|
||||
gen.Emit(OpCodes.Ret); // Return method
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7da6bf97199e0bb743f7639c17112ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="GenericCollectionFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using VRC.Udon.Serialization.OdinSerializer.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for the <see cref="GenericCollectionFormatter{TCollection, TElement}"/> class.
|
||||
/// </summary>
|
||||
public static class GenericCollectionFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the specified type can be formatted by a <see cref="GenericCollectionFormatter{TCollection, TElement}"/>.
|
||||
/// <para />
|
||||
/// The following criteria are checked: type implements <see cref="ICollection{T}"/>, type is not abstract, type is not a generic type definition, type is not an interface, type has a public parameterless constructor.
|
||||
/// </summary>
|
||||
/// <param name="type">The collection type to check.</param>
|
||||
/// <param name="elementType">The element type of the collection.</param>
|
||||
/// <returns><c>true</c> if the type can be formatted by a <see cref="GenericCollectionFormatter{TCollection, TElement}"/>, otherwise <c>false</c></returns>
|
||||
/// <exception cref="System.ArgumentNullException">The type argument is null.</exception>
|
||||
public static bool CanFormat(Type type, out Type elementType)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
if (type.IsAbstract || type.IsGenericTypeDefinition || type.IsInterface || type.GetConstructor(Type.EmptyTypes) == null || type.ImplementsOpenGenericInterface(typeof(ICollection<>)) == false)
|
||||
{
|
||||
elementType = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
elementType = type.GetArgumentsOfInheritedOpenGenericInterface(typeof(ICollection<>))[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formatter for all eligible types that implement the interface <see cref="ICollection{T}"/>, and which have no other formatters specified.
|
||||
/// <para />
|
||||
/// Eligibility for formatting by this class is determined by the <see cref="GenericCollectionFormatter.CanFormat(Type, out Type)"/> method.
|
||||
/// </summary>
|
||||
/// <typeparam name="TCollection">The type of the collection.</typeparam>
|
||||
/// <typeparam name="TElement">The type of the element.</typeparam>
|
||||
public sealed class GenericCollectionFormatter<TCollection, TElement> : BaseFormatter<TCollection> where TCollection : ICollection<TElement>, new()
|
||||
{
|
||||
private static Serializer<TElement> valueReaderWriter = Serializer.Get<TElement>();
|
||||
|
||||
static GenericCollectionFormatter()
|
||||
{
|
||||
Type e;
|
||||
|
||||
if (GenericCollectionFormatter.CanFormat(typeof(TCollection), out e) == false)
|
||||
{
|
||||
throw new ArgumentException("Cannot treat the type " + typeof(TCollection).Name + " as a generic collection.");
|
||||
}
|
||||
|
||||
if (e != typeof(TElement))
|
||||
{
|
||||
throw new ArgumentException("Type " + typeof(TElement).Name + " is not the element type of the generic collection type " + typeof(TCollection).Name + ".");
|
||||
}
|
||||
|
||||
// This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor
|
||||
// which it otherwise seems prone to do, regardless of what might be defined in any link.xml file.
|
||||
|
||||
new GenericCollectionFormatter<List<int>, int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="GenericCollectionFormatter{TCollection, TElement}"/>.
|
||||
/// </summary>
|
||||
public GenericCollectionFormatter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new object of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A new object of type <see cref="T" />.
|
||||
/// </returns>
|
||||
protected override TCollection GetUninitializedObject()
|
||||
{
|
||||
return new TCollection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected override void DeserializeImplementation(ref TCollection value, IDataReader reader)
|
||||
{
|
||||
string name;
|
||||
var entry = reader.PeekEntry(out name);
|
||||
|
||||
if (entry == EntryType.StartOfArray)
|
||||
{
|
||||
try
|
||||
{
|
||||
long length;
|
||||
reader.EnterArray(out length);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (reader.PeekEntry(out name) == EntryType.EndOfArray)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected.");
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
value.Add(valueReaderWriter.ReadValue(reader));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
|
||||
if (reader.IsInArrayNode == false)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.ExitArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.SkipEntry();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref TCollection value, IDataWriter writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.BeginArrayNode(value.Count);
|
||||
|
||||
foreach (var element in value)
|
||||
{
|
||||
valueReaderWriter.WriteValue(element, writer);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
writer.EndArrayNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06ccb8250c692f2695d28bfd6bcd4273
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,141 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ListFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatter(typeof(HashSetFormatter<>))]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Custom generic formatter for the generic type definition <see cref="HashSet{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the formatted list.</typeparam>
|
||||
/// <seealso cref="BaseFormatter{System.Collections.Generic.HashSet{T}}" />
|
||||
public class HashSetFormatter<T> : BaseFormatter<HashSet<T>>
|
||||
{
|
||||
private static readonly Serializer<T> TSerializer = Serializer.Get<T>();
|
||||
|
||||
static HashSetFormatter()
|
||||
{
|
||||
// This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor
|
||||
// which it otherwise seems prone to do, regardless of what might be defined in any link.xml file.
|
||||
|
||||
new HashSetFormatter<int>();
|
||||
}
|
||||
|
||||
public HashSetFormatter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns null.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A null value.
|
||||
/// </returns>
|
||||
protected override HashSet<T> GetUninitializedObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected override void DeserializeImplementation(ref HashSet<T> value, IDataReader reader)
|
||||
{
|
||||
string name;
|
||||
var entry = reader.PeekEntry(out name);
|
||||
|
||||
if (entry == EntryType.StartOfArray)
|
||||
{
|
||||
try
|
||||
{
|
||||
long length;
|
||||
reader.EnterArray(out length);
|
||||
value = new HashSet<T>();
|
||||
|
||||
// We must remember to register the hashset reference ourselves, since we return null in GetUninitializedObject
|
||||
this.RegisterReferenceID(value, reader);
|
||||
|
||||
// There aren't any relevant OnDeserializing callbacks on hash sets.
|
||||
// Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (reader.PeekEntry(out name) == EntryType.EndOfArray)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected.");
|
||||
break;
|
||||
}
|
||||
|
||||
value.Add(TSerializer.ReadValue(reader));
|
||||
|
||||
if (reader.IsInArrayNode == false)
|
||||
{
|
||||
// Something has gone wrong
|
||||
reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.ExitArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.SkipEntry();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref HashSet<T> value, IDataWriter writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.BeginArrayNode(value.Count);
|
||||
|
||||
foreach (T item in value)
|
||||
{
|
||||
try
|
||||
{
|
||||
TSerializer.WriteValue(item, writer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
writer.Context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
writer.EndArrayNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1eaa1b43658215b6d81013928eac19e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0fcb040f1c493dc2a5224e446be8b3c9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,63 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="KeyValuePairFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatter(typeof(KeyValuePairFormatter<,>))]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Custom generic formatter for the generic type definition <see cref="KeyValuePair{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
/// <seealso cref="BaseFormatter{System.Collections.Generic.KeyValuePair{TKey, TValue}}" />
|
||||
public sealed class KeyValuePairFormatter<TKey, TValue> : BaseFormatter<KeyValuePair<TKey, TValue>>
|
||||
{
|
||||
private static readonly Serializer<TKey> KeySerializer = Serializer.Get<TKey>();
|
||||
private static readonly Serializer<TValue> ValueSerializer = Serializer.Get<TValue>();
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref KeyValuePair<TKey, TValue> value, IDataWriter writer)
|
||||
{
|
||||
KeySerializer.WriteValue(value.Key, writer);
|
||||
ValueSerializer.WriteValue(value.Value, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
|
||||
protected override void DeserializeImplementation(ref KeyValuePair<TKey, TValue> value, IDataReader reader)
|
||||
{
|
||||
value = new KeyValuePair<TKey, TValue>(
|
||||
KeySerializer.ReadValue(reader),
|
||||
ValueSerializer.ReadValue(reader)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cae1c5d1116a090d70b6d0289061d21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,141 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ListFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatter(typeof(ListFormatter<>))]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Custom generic formatter for the generic type definition <see cref="List{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the formatted list.</typeparam>
|
||||
/// <seealso cref="BaseFormatter{System.Collections.Generic.List{T}}" />
|
||||
public class ListFormatter<T> : BaseFormatter<List<T>>
|
||||
{
|
||||
private static readonly Serializer<T> TSerializer = Serializer.Get<T>();
|
||||
|
||||
static ListFormatter()
|
||||
{
|
||||
// This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor
|
||||
// which it otherwise seems prone to do, regardless of what might be defined in any link.xml file.
|
||||
|
||||
new ListFormatter<int>();
|
||||
}
|
||||
|
||||
public ListFormatter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns null.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A null value.
|
||||
/// </returns>
|
||||
protected override List<T> GetUninitializedObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected override void DeserializeImplementation(ref List<T> value, IDataReader reader)
|
||||
{
|
||||
string name;
|
||||
var entry = reader.PeekEntry(out name);
|
||||
|
||||
if (entry == EntryType.StartOfArray)
|
||||
{
|
||||
try
|
||||
{
|
||||
long length;
|
||||
reader.EnterArray(out length);
|
||||
value = new List<T>((int)length);
|
||||
|
||||
// We must remember to register the list reference ourselves, since we return null in GetUninitializedObject
|
||||
this.RegisterReferenceID(value, reader);
|
||||
|
||||
// There aren't any OnDeserializing callbacks on lists.
|
||||
// Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (reader.PeekEntry(out name) == EntryType.EndOfArray)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reached end of array after " + i + " elements, when " + length + " elements were expected.");
|
||||
break;
|
||||
}
|
||||
|
||||
value.Add(TSerializer.ReadValue(reader));
|
||||
|
||||
if (reader.IsInArrayNode == false)
|
||||
{
|
||||
// Something has gone wrong
|
||||
reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.ExitArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.SkipEntry();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref List<T> value, IDataWriter writer)
|
||||
{
|
||||
try
|
||||
{
|
||||
writer.BeginArrayNode(value.Count);
|
||||
|
||||
for (int i = 0; i < value.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
TSerializer.WriteValue(value[i], writer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
writer.Context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
writer.EndArrayNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba4ee6777a44f6e9a8e2e0a222c0f7db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="MethodInfoFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatter(typeof(MethodInfoFormatter<>))]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Custom formatter for MethodInfo, since Unity Mono's MethodInfo ISerializable implementation will often crash if the method no longer exists upon deserialization.
|
||||
/// </summary>
|
||||
/// <seealso cref="BaseFormatter{T}" />
|
||||
public sealed class MethodInfoFormatter<T> : BaseFormatter<T>
|
||||
where T : MethodInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="!:T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="M:OdinSerializer.BaseFormatter`1.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected override void DeserializeImplementation(ref T value, IDataReader reader)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogWarning("MethodInfo deserialization has been removed for security.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="!:T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref T value, IDataWriter writer)
|
||||
{
|
||||
writer.Context.Config.DebugContext.LogWarning("MethodInfo serialization has been removed for security.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an uninitialized object of type <see cref="!:T" />. WARNING: If you override this and return null, the object's ID will not be automatically registered and its OnDeserializing callbacks will not be automatically called, before deserialization begins.
|
||||
/// You will have to call <see cref="M:OdinSerializer.BaseFormatter`1.RegisterReferenceID(`0,OdinSerializer.IDataReader)" /> and <see cref="M:OdinSerializer.BaseFormatter`1.InvokeOnDeserializingCallbacks(`0,OdinSerializer.DeserializationContext)" /> immediately after creating the object yourself during deserialization.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An uninitialized object of type <see cref="!:T" />.
|
||||
/// </returns>
|
||||
protected override T GetUninitializedObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21078ce134ce87231526dee77088e7ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,159 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="MinimalBaseFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal baseline formatter. Doesn't come with all the bells and whistles of any of the other BaseFormatter classes.
|
||||
/// Common serialization conventions aren't automatically supported, and common deserialization callbacks are not automatically invoked.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type which can be serialized and deserialized by the formatter.</typeparam>
|
||||
public abstract class MinimalBaseFormatter<T> : IFormatter<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the serialized value is a value type.
|
||||
/// </summary>
|
||||
protected static readonly bool IsValueType = typeof(T).IsValueType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type that the formatter can serialize.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type that the formatter can serialize.
|
||||
/// </value>
|
||||
public Type SerializedType { get { return typeof(T); } }
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes a value of type <see cref="!:T" /> using a specified <see cref="T:OdinSerializer.IDataReader" />.
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader to use.</param>
|
||||
/// <returns>
|
||||
/// The deserialized value.
|
||||
/// </returns>
|
||||
public T Deserialize(IDataReader reader)
|
||||
{
|
||||
T result = this.GetUninitializedObject();
|
||||
|
||||
// We allow the above method to return null (for reference types) because of special cases like arrays,
|
||||
// where the size of the array cannot be known yet, and thus we cannot create an object instance at this time.
|
||||
//
|
||||
// Therefore, those who override GetUninitializedObject and return null must call RegisterReferenceID manually.
|
||||
if (IsValueType == false && object.ReferenceEquals(result, null) == false)
|
||||
{
|
||||
this.RegisterReferenceID(result, reader);
|
||||
}
|
||||
|
||||
this.Read(ref result, reader);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a value of type <see cref="!:T" /> using a specified <see cref="T:OdinSerializer.IDataWriter" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to use.</param>
|
||||
public void Serialize(T value, IDataWriter writer)
|
||||
{
|
||||
this.Write(ref value, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes a value using a specified <see cref="IDataWriter" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to use.</param>
|
||||
void IFormatter.Serialize(object value, IDataWriter writer)
|
||||
{
|
||||
if (value is T)
|
||||
{
|
||||
this.Serialize((T)value, writer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes a value using a specified <see cref="IDataReader" />.
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader to use.</param>
|
||||
/// <returns>
|
||||
/// The deserialized value.
|
||||
/// </returns>
|
||||
object IFormatter.Deserialize(IDataReader reader)
|
||||
{
|
||||
return this.Deserialize(reader);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an uninitialized object of type <see cref="T"/>. WARNING: If you override this and return null, the object's ID will not be automatically registered.
|
||||
/// You will have to call <see cref="MinimalBaseFormatter{T}{T}.RegisterReferenceID(T, IDataReader, DeserializationContext)"/> immediately after creating the object yourself during deserialization.
|
||||
/// </summary>
|
||||
/// <returns>An uninitialized object of type <see cref="T"/>.</returns>
|
||||
protected virtual T GetUninitializedObject()
|
||||
{
|
||||
if (IsValueType)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (T)FormatterServices.GetUninitializedObject(typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads into the specified value using the specified reader.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read into.</param>
|
||||
/// <param name="reader">The reader to use.</param>
|
||||
protected abstract void Read(ref T value, IDataReader reader);
|
||||
|
||||
/// <summary>
|
||||
/// Writes from the specified value using the specified writer.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write from.</param>
|
||||
/// <param name="writer">The writer to use.</param>
|
||||
protected abstract void Write(ref T value, IDataWriter writer);
|
||||
|
||||
/// <summary>
|
||||
/// Registers the given object reference in the deserialization context.
|
||||
/// <para />
|
||||
/// NOTE that this method only does anything if <see cref="T"/> is not a value type.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to register.</param>
|
||||
/// <param name="reader">The reader which is currently being used.</param>
|
||||
protected void RegisterReferenceID(T value, IDataReader reader)
|
||||
{
|
||||
if (!IsValueType)
|
||||
{
|
||||
// Get ID and register object reference
|
||||
int id = reader.CurrentNodeId;
|
||||
|
||||
if (id < 0)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogWarning("Reference type node is missing id upon deserialization. Some references may be broken. This tends to happen if a value type has changed to a reference type (IE, struct to class) since serialization took place.");
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Context.RegisterInternalReference(id, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae604bc0ef4ef9938100804f05decb21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="MultiDimensionalArrayFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Formatter for all arrays with more than one dimension.
|
||||
/// </summary>
|
||||
/// <typeparam name="TArray">The type of the formatted array.</typeparam>
|
||||
/// <typeparam name="TElement">The element type of the formatted array.</typeparam>
|
||||
/// <seealso cref="BaseFormatter{TArray}" />
|
||||
public sealed class MultiDimensionalArrayFormatter<TArray, TElement> : BaseFormatter<TArray> where TArray : class
|
||||
{
|
||||
private const string RANKS_NAME = "ranks";
|
||||
private const char RANKS_SEPARATOR = '|';
|
||||
|
||||
private static readonly int ArrayRank;
|
||||
private static readonly Serializer<TElement> ValueReaderWriter = Serializer.Get<TElement>();
|
||||
|
||||
static MultiDimensionalArrayFormatter()
|
||||
{
|
||||
if (typeof(TArray).IsArray == false)
|
||||
{
|
||||
throw new ArgumentException("Type " + typeof(TArray).Name + " is not an array.");
|
||||
}
|
||||
|
||||
if (typeof(TArray).GetElementType() != typeof(TElement))
|
||||
{
|
||||
throw new ArgumentException("Array of type " + typeof(TArray).Name + " does not have the required element type of " + typeof(TElement).Name + ".");
|
||||
}
|
||||
|
||||
ArrayRank = typeof(TArray).GetArrayRank();
|
||||
|
||||
if (ArrayRank <= 1)
|
||||
{
|
||||
throw new ArgumentException("Array of type " + typeof(TArray).Name + " only has one rank.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns null.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A null value.
|
||||
/// </returns>
|
||||
protected override TArray GetUninitializedObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="BaseFormatter{T}.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected override void DeserializeImplementation(ref TArray value, IDataReader reader)
|
||||
{
|
||||
string name;
|
||||
var entry = reader.PeekEntry(out name);
|
||||
|
||||
if (entry == EntryType.StartOfArray)
|
||||
{
|
||||
long length;
|
||||
reader.EnterArray(out length);
|
||||
|
||||
entry = reader.PeekEntry(out name);
|
||||
|
||||
if (entry != EntryType.String || name != RANKS_NAME)
|
||||
{
|
||||
value = default(TArray);
|
||||
reader.SkipEntry();
|
||||
return;
|
||||
}
|
||||
|
||||
string lengthStr;
|
||||
reader.ReadString(out lengthStr);
|
||||
|
||||
string[] lengthsStrs = lengthStr.Split(RANKS_SEPARATOR);
|
||||
|
||||
if (lengthsStrs.Length != ArrayRank)
|
||||
{
|
||||
value = default(TArray);
|
||||
reader.SkipEntry();
|
||||
return;
|
||||
}
|
||||
|
||||
int[] lengths = new int[lengthsStrs.Length];
|
||||
|
||||
for (int i = 0; i < lengthsStrs.Length; i++)
|
||||
{
|
||||
int rankVal;
|
||||
if (int.TryParse(lengthsStrs[i], out rankVal))
|
||||
{
|
||||
lengths[i] = rankVal;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = default(TArray);
|
||||
reader.SkipEntry();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
long rankTotal = lengths[0];
|
||||
|
||||
for (int i = 1; i < lengths.Length; i++)
|
||||
{
|
||||
rankTotal *= lengths[i];
|
||||
}
|
||||
|
||||
if (rankTotal != length)
|
||||
{
|
||||
value = default(TArray);
|
||||
reader.SkipEntry();
|
||||
return;
|
||||
}
|
||||
|
||||
value = (TArray)(object)Array.CreateInstance(typeof(TElement), lengths);
|
||||
|
||||
// We must remember to register the array reference ourselves, since we return null in GetUninitializedObject
|
||||
this.RegisterReferenceID(value, reader);
|
||||
|
||||
// There aren't any OnDeserializing callbacks on arrays.
|
||||
// Hence we don't invoke this.InvokeOnDeserializingCallbacks(value, reader, context);
|
||||
int elements = 0;
|
||||
|
||||
try
|
||||
{
|
||||
this.IterateArrayWrite(
|
||||
(Array)(object)value,
|
||||
() =>
|
||||
{
|
||||
if (reader.PeekEntry(out name) == EntryType.EndOfArray)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reached end of array after " + elements + " elements, when " + length + " elements were expected.");
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var v = ValueReaderWriter.ReadValue(reader);
|
||||
|
||||
if (reader.IsInArrayNode == false)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogError("Reading array went wrong. Data dump: " + reader.GetDataDump());
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
elements++;
|
||||
return v;
|
||||
});
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
reader.Context.Config.DebugContext.LogException(ex);
|
||||
}
|
||||
|
||||
reader.ExitArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
value = default(TArray);
|
||||
reader.SkipEntry();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref TArray value, IDataWriter writer)
|
||||
{
|
||||
var array = value as Array;
|
||||
|
||||
try
|
||||
{
|
||||
writer.BeginArrayNode(array.LongLength);
|
||||
|
||||
int[] lengths = new int[ArrayRank];
|
||||
|
||||
for (int i = 0; i < ArrayRank; i++)
|
||||
{
|
||||
lengths[i] = array.GetLength(i);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < ArrayRank; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
sb.Append(RANKS_SEPARATOR);
|
||||
}
|
||||
|
||||
sb.Append(lengths[i].ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
string lengthStr = sb.ToString();
|
||||
|
||||
writer.WriteString(RANKS_NAME, lengthStr);
|
||||
|
||||
this.IterateArrayRead(
|
||||
(Array)(object)value,
|
||||
(v) =>
|
||||
{
|
||||
ValueReaderWriter.WriteValue(v, writer);
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
writer.EndArrayNode();
|
||||
}
|
||||
}
|
||||
|
||||
private void IterateArrayWrite(Array a, Func<TElement> write)
|
||||
{
|
||||
int[] indices = new int[ArrayRank];
|
||||
this.IterateArrayWrite(a, 0, indices, write);
|
||||
}
|
||||
|
||||
private void IterateArrayWrite(Array a, int rank, int[] indices, Func<TElement> write)
|
||||
{
|
||||
for (int i = 0; i < a.GetLength(rank); i++)
|
||||
{
|
||||
indices[rank] = i;
|
||||
|
||||
if (rank + 1 < a.Rank)
|
||||
{
|
||||
this.IterateArrayWrite(a, rank + 1, indices, write);
|
||||
}
|
||||
else
|
||||
{
|
||||
a.SetValue(write(), indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void IterateArrayRead(Array a, Action<TElement> read)
|
||||
{
|
||||
int[] indices = new int[ArrayRank];
|
||||
this.IterateArrayRead(a, 0, indices, read);
|
||||
}
|
||||
|
||||
private void IterateArrayRead(Array a, int rank, int[] indices, Action<TElement> read)
|
||||
{
|
||||
for (int i = 0; i < a.GetLength(rank); i++)
|
||||
{
|
||||
indices[rank] = i;
|
||||
|
||||
if (rank + 1 < a.Rank)
|
||||
{
|
||||
this.IterateArrayRead(a, rank + 1, indices, read);
|
||||
}
|
||||
else
|
||||
{
|
||||
read((TElement)a.GetValue(indices));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc1b5b3148988d0d4fc2dab60a5c146c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,89 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="NullableFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: RegisterFormatter(typeof(NullableFormatter<>))]
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Formatter for all <see cref="System.Nullable{T}"/> types.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that is nullable.</typeparam>
|
||||
/// <seealso cref="BaseFormatter{T?}" />
|
||||
public sealed class NullableFormatter<T> : BaseFormatter<T?> where T : struct
|
||||
{
|
||||
private static readonly Serializer<T> TSerializer = Serializer.Get<T>();
|
||||
|
||||
static NullableFormatter()
|
||||
{
|
||||
// This exists solely to prevent IL2CPP code stripping from removing the generic type's instance constructor
|
||||
// which it otherwise seems prone to do, regardless of what might be defined in any link.xml file.
|
||||
|
||||
new NullableFormatter<int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="NullableFormatter{T}"/>.
|
||||
/// </summary>
|
||||
public NullableFormatter()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for deserializing a value of type <see cref="!:T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The uninitialized value to serialize into. This value will have been created earlier using <see cref="M:OdinSerializer.BaseFormatter`1.GetUninitializedObject" />.</param>
|
||||
/// <param name="reader">The reader to deserialize with.</param>
|
||||
protected override void DeserializeImplementation(ref T? value, IDataReader reader)
|
||||
{
|
||||
string name;
|
||||
var entry = reader.PeekEntry(out name);
|
||||
|
||||
if (entry == EntryType.Null)
|
||||
{
|
||||
value = null;
|
||||
reader.ReadNull();
|
||||
}
|
||||
else
|
||||
{
|
||||
value = TSerializer.ReadValue(reader);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides the actual implementation for serializing a value of type <see cref="!:T" />.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="writer">The writer to serialize with.</param>
|
||||
protected override void SerializeImplementation(ref T? value, IDataWriter writer)
|
||||
{
|
||||
if (value.HasValue)
|
||||
{
|
||||
TSerializer.WriteValue(value.Value, writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteNull(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9ea00de8051ca957d994e11630671d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="PrimitiveArrayFormatter.cs" company="Sirenix IVS">
|
||||
// Copyright (c) 2018 Sirenix IVS
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace VRC.Udon.Serialization.OdinSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Formatter for all primitive one-dimensional arrays.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the formatted array. This type must be an eligible primitive array type, as determined by <see cref="FormatterUtilities.IsPrimitiveArrayType(System.Type)"/>.</typeparam>
|
||||
/// <seealso cref="MinimalBaseFormatter{T[]}" />
|
||||
public sealed class PrimitiveArrayFormatter<T> : MinimalBaseFormatter<T[]> where T : struct
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns null.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A null value.
|
||||
/// </returns>
|
||||
protected override T[] GetUninitializedObject()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads into the specified value using the specified reader.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to read into.</param>
|
||||
/// <param name="reader">The reader to use.</param>
|
||||
protected override void Read(ref T[] value, IDataReader reader)
|
||||
{
|
||||
string name;
|
||||
|
||||
if (reader.PeekEntry(out name) == EntryType.PrimitiveArray)
|
||||
{
|
||||
reader.ReadPrimitiveArray(out value);
|
||||
this.RegisterReferenceID(value, reader);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes from the specified value using the specified writer.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to write from.</param>
|
||||
/// <param name="writer">The writer to use.</param>
|
||||
protected override void Write(ref T[] value, IDataWriter writer)
|
||||
{
|
||||
writer.WritePrimitiveArray(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b6a62ea2fe943a4b261f832e8a1f3dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loaded 100 of 3297 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user