Skip to content

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

  1. Add SerializationGenerator(versionNumber) to your class and make the class partial
    [SerializationGenerator(0)]
    public partial class ExampleItem : Item
    
  2. Delete the constructor with Serial serial
  3. Delete the Serialize and Deserialize methods.
  4. Add SerializableField(fieldOrder) attribute to all field you want to serialize.
    [SerializableField(0)]
    private string _exampleText;
    
  5. 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.