Add VRChat and GitHub packages to Unity project

This commit is contained in:
gunnbr committed 2021-01-18 09:14:18 -08:00
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();
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4ac1e1612275111bd50db8a3de8ba9c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -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();
}
}
@@ -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:
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee0465a1838833eb878447b34339d4f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1bc9e2503afdd0290574ebc14cf4a16d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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
}
}
@@ -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";
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a3a6dce9e0b8317b3804b35f48f6a97
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ecc39ef0dc55ec10f83bb7eefd4f1db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3e05b98a26be61fa9203d4a45bfc1e95
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aad0a34e801ae645b359e4800ef7f636
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c071e721d9b168c4a9f683547f0992e3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6a0f5e01b82ae0763f6f907157a2c9c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eab5938e837a8de93ce64c25399edde6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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 = "|";
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9321fb650525f4bed18119d187111569
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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();
}
}
}
@@ -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:
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0bdecd79f568c8a3252bb5a9f3e2acdc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cf715e98fa96d74c81b4d3f4491d2592
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -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);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f2a9beaeecdd6eb929ddb049d7846a14
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cdd12b278851bfdc68ca0d9e1e4f2d28
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 876ae9a404abe412e663fd9bc03d3525
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -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));
}
}
}
@@ -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:
@@ -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();
}
}
}
}
@@ -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:
@@ -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();
}
}
}
}
@@ -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
{
}
}
@@ -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:
@@ -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();
}
}
}
}
@@ -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)
);
}
}
}
@@ -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:
@@ -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));
}
}
}
}
}
@@ -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);
}
}
}
@@ -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