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, WithFormat.NoFormat, alwaysWrite); } /// /// 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. /// 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 virtual void DataFieldCached(ref T value, string name, T defaultValue, WithFormat withFormat, bool alwaysWrite = false) { DataField(ref value, name, defaultValue, withFormat, 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 virtual bool TryReadDataField(string name, out T value) { return TryReadDataField(name, WithFormat.NoFormat, out value); } public abstract bool TryReadDataField(string name, WithFormat format, out T value); /// /// Try- pattern version of . /// public virtual bool TryReadDataFieldCached(string name, out T value) { return TryReadDataFieldCached(name, WithFormat.NoFormat, out value); } public virtual bool TryReadDataFieldCached(string name, WithFormat format, out T value) { return TryReadDataField(name, format, 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; } } }