using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
namespace Robust.Shared.Serialization
{
///
/// Handles serialization of objects to/from a storage medium (which medium is implementation defined).
/// Provides methods for most common cases of data field reading/writing.
///
///
/// Object serialization may be "cached".
/// This is a non-guaranteed on-request case where, if the serializer is sure it's deserialized something before,
/// it can return a previous instance of the value instead of running deserialization logic again.
/// Caching only occurs for cases where data would the same, e.g. deserializing the same prototype twice.
/// Most methods that read data have a cached variant, which MAY return data shared with other objects (for reference objects).
/// This is not guaranteed and should be considered a situational optimization only.
/// It is also possible to write cached "data" fields that are not stored anywhere, but can be referenced later in other deserialization runs.
/// This is useful for complex cases like sprites which cannot be expressed with a single cached field read, but would like to still take advantage of caching.
/// The persistence of this cached data is in no way guaranteed.
///
public abstract class ObjectSerializer
{
public const string LogCategory = "serialization";
public delegate void ReadFunctionDelegate(T value);
public delegate T WriteFunctionDelegate();
///
/// True if this serializer is reading, false if it is writing.
///
public bool Reading { get; protected set; }
public bool Writing => !Reading;
///
/// Writes or reads a simple field by reference.
///
/// The reference to the field that will be read/written into.
/// The name of the field in the serialization medium. Most likely the name in YAML.
/// A default value. Used if the field does not exist while reading or to know if writing would be redundant.
/// If true, always write this field to map saving, even if it matches the default.
/// The type of the field that will be read/written.
public void DataField(ref T value, string name, T defaultValue, bool alwaysWrite = false)
{
DataField(ref value, name, defaultValue, WithFormat.NoFormat, alwaysWrite);
}
///
/// Writes or reads a simple field by reference.
///
/// The reference to the field that will be read/written into.
/// The name of the field in the serialization medium. Most likely the name in YAML.
/// A default value. Used if the field does not exist while reading or to know if writing would be redundant.
/// The formatter to use for representing this particular value in the medium.
/// If true, always write this field to map saving, even if it matches the default.
/// The type of the field that will be read/written.
public abstract void DataField(ref T value, string name, T defaultValue, WithFormat withFormat, bool alwaysWrite = false);
///
/// Writes or reads a field or property via reflection.
///
/// The reference to the object that has the property or field referenced.
/// The reference to the field or property that will be read/written into.
/// The name of the field in the serialization medium. Most likely the name in YAML.
/// A default value. Used if the field does not exist while reading or to know if writing would be redundant.
/// If true, always write this field to map saving, even if it matches the default.
/// The type of the object that has the property or field referenced.
/// The type of the field that will be read/written.
///
///
/// szr.DataField(this, x => x.SomeProperty, "some-property", SomeDefaultValue);
///
///
public abstract void DataField(TRoot root, Expression> expr, string name, T defaultValue, bool alwaysWrite = false);
///
/// Writes or reads a simple field by reference.
/// This method can cache results and share them with other objects.
/// As such, when reading, your value may NOT be private. Do not modify it as if it's purely your own.
/// This can cut out parsing steps and memory cost for commonly used objects such as walls.
///
/// The reference to the field that will be read/written into.
/// The name of the field in the serialization medium. Most likely the name in YAML.
/// A default value. Used if the field does not exist while reading or to know if writing would be redundant.
/// If true, always write this field to map saving, even if it matches the default.
/// The type of the field that will be read/written.
public virtual void DataFieldCached(ref T value, string name, T defaultValue, bool alwaysWrite = false)
{
DataField(ref value, name, defaultValue, alwaysWrite);
}
///
/// Writes or reads a simple field by reference.
/// Runs the provided delegate to do conversion from a more easy to (de)serialize data type.
///
/// The reference to the field that will be read/written into.
/// The name of the field in the serialization medium. Most likely the name in YAML.
/// A default value. Used if the field does not exist while reading or to know if writing would be redundant.
///
/// A delegate invoked to convert the intermediate serialization object
/// to the actual value while reading.
///
///
/// A delegate invoked to convert the actual value
/// to an intermediate serialization object that will be written.
///
/// If true, always write this field to map saving, even if it matches the default.
/// The type of the field that will be read/written.
/// The type of the intermediate object that will be (de)serialized.
public abstract void DataField(
ref TTarget value,
string name,
TTarget defaultValue,
Func ReadConvertFunc,
Func WriteConvertFunc = null,
bool alwaysWrite = false
);
///
/// Writes or reads a simple field by reference.
/// This method can cache results and share them with other objects.
/// As such, when reading, your value may NOT be private.
/// This can cut out parsing steps and memory cost for commonly used objects such as walls.
/// This method can cache results and share them with other objects.
/// As such, when reading, your value may NOT be private. Do not modify it as if it's purely your own.
/// This can cut out parsing steps and memory cost for commonly used objects such as walls.
///
/// The reference to the field that will be read/written into.
/// The name of the field in the serialization medium. Most likely the name in YAML.
/// A default value. Used if the field does not exist while reading or to know if writing would be redundant.
///
/// A delegate invoked to convert the intermediate serialization object
/// to the actual value while reading.
///
///
/// A delegate invoked to convert the actual value
/// to an intermediate serialization object that will be written.
///
/// If true, always write this field to map saving, even if it matches the default.
/// The type of the field that will be read/written.
/// The type of the intermediate object that will be (de)serialized.
public virtual void DataFieldCached(
ref TTarget value,
string name,
TTarget defaultValue,
Func ReadConvertFunc,
Func WriteConvertFunc = null,
bool alwaysWrite = false
)
{
DataField(ref value, name, defaultValue, ReadConvertFunc, WriteConvertFunc, alwaysWrite);
}
///
/// While reading, reads a data field and immediately returns it as value.
///
/// The name of the field to read.
/// Default value of the field if it does not exist.
/// The type of the field.
/// The value of the field.
///
/// Thrown if the reader is not currently reading.
///
public abstract T ReadDataField(string name, T defaultValue);
///
/// While reading, reads a data field and immediately returns it as value.
///
/// The name of the field to read.
/// The type of the field.
/// The value of the field.
///
/// Thrown if the reader is not currently reading.
///
///
/// Thrown if the field does not exist.
///
public virtual T ReadDataField(string name)
{
if (TryReadDataField(name, out T val))
{
return val;
}
throw new KeyNotFoundException(name);
}
///
/// While reading, reads a data field and immediately returns it as value.
/// This method can cache results and share them with other objects.
/// As such, when reading, your value may NOT be private. Do not modify it as if it's purely your own.
/// This can cut out parsing steps and memory cost for commonly used objects such as walls.
///
/// The name of the field to read.
/// Default value of the field if it does not exist.
/// The type of the field.
/// The value of the field.
///
/// Thrown if the reader is not currently reading.
///
public virtual T ReadDataFieldCached(string name, T defaultValue)
{
return ReadDataField(name, defaultValue);
}
///
/// Try- pattern version of .
///
public abstract bool TryReadDataField(string name, out T value);
///
/// Try- pattern version of .
///
public virtual bool TryReadDataFieldCached(string name, out T value)
{
return TryReadDataField(name, out value);
}
///
/// Sets a cached field for this serialization context.
/// This field does not get written in any way,
/// but can be recalled during other serialization runs of the same data with or .
///
/// The cache key to write to.
/// The object to write.
public virtual void SetCacheData(string key, object value)
{
}
///
/// Gets cached data set by in a previous deserialization run of the same data.
///
/// The key to recall.
/// The type to cast the return object to.
/// The data previously stored.
public virtual T GetCacheData(string key)
{
throw new NotImplementedException();
}
///
/// Try- pattern version of .
///
public virtual bool TryGetCacheData(string key, out T data)
{
data = default;
return false;
}
///
/// Provides a delegate to parse data from a more simpler type that can be mapped to mediums like YAML.
/// This is useful if your data does not map 1:1 to the prototype.
///
/// The name of the field in the serialization medium. Most likely the name in YAML.
/// A default value to read in case the field is not exist.
/// A delegate that takes in the simpler data and is expected to set internal state on the caller.
/// The type of the data that will be read from the storage medium.
public abstract void DataReadFunction(string name, T defaultValue, ReadFunctionDelegate func);
///
/// Provides a delegate to write custom data to a more simpler type that can be mapped to mediums like YAML.
/// This is useful if your data does not map 1:1 to the prototype.
///
/// The name of the field in the serialization medium. Most likely the name in YAML.
/// The default value. Used to check if writing can be skipped when is true.
/// A delegate that produces simpler data based on the internal state of the caller.
/// If true, data will always be written even if it matches .
/// The type of the data that will be written to the storage medium.
public abstract void DataWriteFunction(string name, T defaultValue, WriteFunctionDelegate func, bool alwaysWrite = false);
///
/// It's and in one, so you don't need to pass name and default twice!
/// Marvelous!
///
public virtual void DataReadWriteFunction(string name, T defaultValue, ReadFunctionDelegate readFunc, WriteFunctionDelegate writeFunc, bool alwaysWrite = false)
{
if (Reading)
{
DataReadFunction(name, defaultValue, readFunc);
}
else
{
DataWriteFunction(name, defaultValue, writeFunc, alwaysWrite);
}
}
///
/// Returns a "string or enum" key value from a field.
/// These values are either a string, or an enum.
/// This is good for identifiers that can either be a string (any value, prototypes go wild),
/// or an enum when type safety is required for the code.
///
/// The name of the field to read the key from.
///
public virtual object ReadStringEnumKey(string fieldName)
{
var reflectionManager = IoCManager.Resolve();
var keyString = ReadDataField(fieldName);
if (reflectionManager.TryParseEnumReference(keyString, out var @enum))
{
return @enum;
}
return keyString;
}
}
}