Serialization / Savings¶
Persistence.Serialize
and Persistence.Deserialize
is replaced with GenericPersistence class
Example of how to persist a custom system.
namespace Server.ExampleSystem
{
public static class ExampleSerialization
{
public static void Configure()
{
GenericPersistence.Register("ExampleSystem", Serialize, Deserialize);
}
public static void Serialize(IGenericWriter writer)
{
// Do serialization here
writer.WriteEncodedInt(0); // version
}
public static void Deserialize(IGenericReader reader)
{
// Do deserialization here
var version = reader.ReadEncodedInt();
}
}
}
Basic info¶
ModernUO can programatically generate migrations. This feature is based on internal C# Source generators More info
Old way of serializing objects:
public class ExampleItem : Item
{
private string _exampleText;
[CommandProperty(AccessLevel.GameMaster)]
public string ExampleText
{
get => _exampleText;
set
{
if (value != _exampleText)
{
_exampleText = value;
this.MarkDirty();
}
}
}
[Constructible]
public ExampleItem(string text) : base(0)
{
Example = text;
}
public ExampleItem(Serial serial) : base(serial)
{
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); //Version
writer.Write(_exampleText);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadEncodedInt();
// version 0
_exampleText = reader.ReadString();
}
}
Same class serialized with codegen
[SerializationGenerator(0)]
public partial class ExampleItem : Item
{
[SerializableField(0)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private string _exampleText;
[Constructible]
public ExampleItem(string text) : base(0)
{
_exampleText = text;
}
}
Step by step¶
- Add
SerializationGenerator(versionNumber)
to your class and make the classpartial
[SerializationGenerator(0)] public partial class ExampleItem : Item
- Delete the constructor with
Serial serial
- Delete the
Serialize
andDeserialize
methods. - Add
SerializableField(fieldOrder)
attribute to all field you want to serialize.[SerializableField(0)] private string _exampleText;
- Run
publish.cmd
.
ModernUO will create migration files for you. In this case "Server.Items.ExampleItem.v0.json" and "Server.Items.ExampleItem.Serialization.cs". These files contains all information and classes needed for ModernUO to serialize/deserialize your objects.
Migrations¶
When new field is added to the serialization, you need to increment versionNumber
and run publish.cmd
again to generate a migration file for the new version.
Here is little example.
New class code will look like this:
[SerializationGenerator(1)]
public partial class ExampleItem : Item
{
[SerializableField(0)]
private string _exampleText;
[SerializableField(1)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private string _addedExampleTest;
[Constructible]
public ExampleItem(string text, string addedText) : base(0)
{
_exampleText = text;
_addedExampleTest = addedText;
}
}
Your IDE (Visual Studio, Rider, or VSCode), will show errors and the ModernUO will not compile.
This happens because the code generator builds a migration from V0 to V1 and a new struct V0Content
with all of the V0 fields is generated.
ModernUO is expecting the developer to create a migration from the old version to the new.
Now create a MigrateFrom
method for each of the older versions, in this case V0, to the new version.
private void MigrateFrom(V0Content content)
{
_exampleText = content.ExampleText;
}
Tip
Since the class is partial
, you can create a standalone file for migrations to keep them organized. For example "ExampleItem.Migrations.cs"
Your migration is now complete.
Migrating from pre-codegen¶
To migrate from pre-codegen serialization, change the old Deserialize method to this:
private void Deserialize(IGenericReader reader, int version)
This method will be automatically called when the serialization generator doesn't have a migration for for that older version. In this method you can make old fashioned deserializations that are mostly compatible with RunUO.
After Deserialization¶
To execute code after the deserialization, add the AfterDeserialization()
attribute to a method.
[AfterDeserialization]
private void AfterDeserialization()
{
// Some code here
}
Tip
By default, AfterDeserialization
is executed synchronously right after the actual deserialization. Passing false
to the attribute will make it execute after all deserializations.
Serializing Non-Entity Classes¶
Sometimes you might need to serialize a nested object that is not an ISerializable
. The syntax is largely the same except you must also mark the parent object for dirty tracking by using the DirtyTrackingEntity
attribute.
A good example of this is "AquariumState".
Note
Non-entity classes cannot be serialized by reference since that would require a reference Serial or ID. This means if the object is a serializable property on multiple objects, it will be serialized
multiple times and will effectively get duplicated on world load. Consider either making it an ISerializable
type and building world load/save mechanisms, or do not serialize the nested object directly.
Instead opt for a global lookup, serialize with generic persistence, and then reattach to the objects using the WorldLoad
event sink.