mirror of
https://github.com/modernuo/ModernUO
synced 2026-08-11 22:23:06 -04:00
198 lines
6.8 KiB
Markdown
198 lines
6.8 KiB
Markdown
---
|
|
title: Serialization
|
|
---
|
|
|
|
# Serialization / Savings
|
|
|
|
=== "Generic persistence"
|
|
```Persistence.Serialize``` and ```Persistence.Deserialize``` is replaced with GenericPersistence class
|
|
|
|
Example of how to persist a custom system.
|
|
|
|
```cs
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
=== "Codegen"
|
|
### Basic info
|
|
ModernUO can programatically generate migrations. This feature is based on internal C# Source generators [More info](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/)
|
|
|
|
Old way of serializing objects:
|
|
```cs
|
|
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
|
|
```cs
|
|
[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```
|
|
```cs
|
|
[SerializationGenerator(0)]
|
|
public partial class ExampleItem : Item
|
|
```
|
|
1. Delete the constructor with ```Serial serial```
|
|
1. Delete the ```Serialize``` and ```Deserialize``` methods.
|
|
1. Add ```SerializableField(fieldOrder)``` attribute to all field you want to serialize.
|
|
```cs
|
|
[SerializableField(0)]
|
|
private string _exampleText;
|
|
```
|
|
1. 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:
|
|
```cs
|
|
[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.
|
|
|
|
```cs
|
|
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:
|
|
```cs
|
|
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.
|
|
```cs
|
|
[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"](https://github.com/modernuo/ModernUO/blob/main/Projects/UOContent/Items/Aquarium/AquariumState.cs).
|
|
|
|
|
|
!!! 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.
|