Roslyn Source generators are not supposed to access I/O. To get around this we have to use `AdditionalFiles` to give the analyzer access to read/load the schema files.
To write the schema files we have to use a separate program altogether.
- [X] Adds SerializationSchemaGenerator
- [X] Splits out the SerializationGenerator code
After speaking with the C# devs, it is clear that code gen is not ready.
I can get around this by doing the following:
1. Creating a library
2. Splitting out the schema writing from the source generator
3. Moving the schema loading to `AdditionalFiles`
4. Writing a new post-build application using Roslyn to write the schema files after building.
- Adds `[ManualDirtyChecking]` for anyone that adds it themselves.
- Adds detection of `[Serializable]` and `[ManualDirtyChecking]` at startup to help identify scripts that need migration.
### Additions
- Automatically opts-out `Item/Mobile/Guild/Accounts` from dirty checking with a new property `UseDirtyChecking`
- Codegen now enables `UseDirtyChecking` via getter. This requires that the property is `virtual` for derived types.
### Fixes
- Fixes `EncodedInt` being broken
- Fixes issues with new custom serializable types that are not derived from Item/Mobile/etc.
- Removes double dirty checking.
### Example of a brand new serializable type that isn't an Item/Mobile/etc.
User created code:
```cs
using System;
namespace Server.Items
{
[Serializable(0)]
public partial class NewTestEntityObject : ISerializable
{
[EncodedInt]
[SerializableField(0)]
private int _someProperty;
public NewTestEntityObject()
{
SetTypeRef(GetType());
// Add to serial tracking like World.Item
/*
Serial = World.NewEntity;
World.AddEntity(this);
*/
}
[AfterDeserialization]
private void AfterDeserialization()
{
Console.WriteLine("This ran!");
}
public int TypeRef { get; }
public Serial Serial { get; }
public void Delete()
{
}
public bool Deleted { get; set; }
public void SetTypeRef(Type type)
{
// Type tracking for persistence goes here
/*
TypeRef = World.NewEntityTypes.IndexOf(type);
if (TypeRef == -1)
{
World.NewEntityTypes.Add(type);
TypeRef = World.NewEntityTypes.Count - 1;
}
*/
}
}
}
```
Generated code:
```cs
namespace Server.Items
{
public partial class NewTestEntityObject
{
#pragma warning disable 0414
private const int _version = 0;
#pragma warning restore 0414
public int SomeProperty
{
get => _someProperty;
set
{
if (value != _someProperty)
{
_someProperty = value;
((ISerializable)this).MarkDirty();
}
}
}
long ISerializable.SavePosition { get; set; } = -1;
BufferWriter ISerializable.SaveBuffer { get; set; }
bool ISerializable.UseDirtyChecking => true;
public NewTestEntityObject(Serial serial)
{
Serial = serial;
SetTypeRef(typeof(NewTestEntityObject));
}
public void Serialize(IGenericWriter writer)
{
writer.WriteEncodedInt(_version);
writer.WriteEncodedInt(SomeProperty);
}
public void Deserialize(IGenericReader reader)
{
var version = reader.ReadEncodedInt();
SomeProperty = reader.ReadEncodedInt();
Timer.DelayCall(AfterDeserialization);
}
}
}
```
- [X] Fixes an issue with ordering of properties in serialization.
- [X] Adds opt-in with existing properties.
Example:
```cs
private int _myExistingField;
[SerializableField(1)]
public int MyExistingProperty
{
get => _myExistingField;
set
{
if (value == 0)
{
Parent = null;
}
if (value != _myExistingField)
{
((ISerializable)this).MarkDirty();
_myExistingField = value;
}
}
}
```
Added the ability to execute arbitrary code after deserialization.
Example, let's say you want to delete an item after you deserialize it:
```cs
[AfterDeserialization]
private void OnAfterDeserialization()
{
Delete();
}
```
Generates this:
```cs
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadEncodedInt();
Timer.DelayCall(OnAfterDeserialization);
}
```
- [X] Adds Enum migration rule
- [X] Adds legacy version (writing full int for version field)
- [X] Adds encoded int attribute
- [X] Adds intern string attribute
- [X] Fixes missing base deserialize/serialize
### Features
* Fully abstracts serialization by using compile-time attributes.
* Supports serializing the following:
- Primitives (integers, strings, etc)
- IP Addresses
- BigDecimal
- DateTime, Delta DateTimes
- TimeSpan
- Server.Race
- Server.Map
- Point2D, Point3D, Rect2D, Rect3D
- Existing/New `ISerializable` references
- Lists/Sets of serializable types
- Type with a `Serialize` method and constructor that takes an `IGenericReader`
* Supports forward-only migration
* Supports existing RunUO deserialization for older versions by changing to the following signature:
- `public void OldDeserialize(IGenericReader reader, int version)`
- Must remove deserializing the version since this is already done
* Supports serializing from private fields or custom made properties.
* Types do not require inheriting Item/Mobile. Code gen will fully create `ISerializable` information.
- This is not recommended yet, since it requires wiring to `Persistence` which will cause lots of unresolved symbol errors until code gen is built.
### Example
```cs
using System.Collections.Generic;
namespace Server.Items
{
[Serializable(1)]
public partial class TestItem1 : Item
{
[SerializableField(1)]
[SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")]
private List<Item> _someProperty;
private void Deserialize(IGenericReader reader, int version)
{
}
}
}
```
Generates this:
```cs
namespace Server.Items
{
public partial class TestItem1
{
#pragma warning disable 0414
private const int _version = 1;
#pragma warning restore 0414
[CommandProperty(AccessLevel.Administrator)]
public System.Collections.Generic.List<Server.Item> SomeProperty
{
get => _someProperty;
set
{
if (value != _someProperty)
{
((ISerializable)this).MarkDirty();
_someProperty = value;
}
}
}
public TestItem1(Serial serial) : base(serial)
{
}
public override void Serialize(IGenericWriter writer)
{
var savePosition = ((Server.ISerializable)this).SavePosition;
if (savePosition > -1)
{
writer.Seek(savePosition, System.IO.SeekOrigin.Begin);
return;
}
writer.WriteEncodedInt(_version);
writer.Write(_someProperty);
}
public override void Deserialize(IGenericReader reader)
{
var version = reader.ReadEncodedInt();
if (version < 1)
{
OldDeserialize(reader, version);
((Server.ISerializable)this).MarkDirty();
return;
}
SomeProperty = reader.ReadEntityList<Server.Item>();
}
}
}
```
And this:
```json
{
"version": 1,
"type": "TestItem1",
"properties": [
{
"name": "SomeProperty",
"type": "System.Collections.Generic.List\u003CServer.Item\u003E",
"rule": "ListMigrationRule",
"ruleArguments": [
"Server.Item",
"SerializableInterfaceMigrationRule"
]
}
]
}
```
- [X] Adds a property to `CommandProperty` to allow modifying `PropertyObject` where there is no property setter that is accessible.
- [X] Updates AosAttributes and friends
Example of how to make this work:
```cs
[CommandProperty(AccessLevel.GameMaster, canModify: true)]
public AosWeaponAttributes WeaponAttributes { get; private set; }
```
This fix is necessary because in release mode the optimizer will sometimes _entirely remove the setter_ because it knows it can do something that is more efficient _when the property setter is private_. RunUO got around this by declaring a public setter explicitly that _was empty_.
### Screenshot
<img width="313" alt="Screen Shot 2021-05-15 at 11 34 01 PM" src="https://user-images.githubusercontent.com/3953314/118387955-49293c00-b5d6-11eb-8f31-a3b5e31bd18b.png">
- [X] Adds support for Fedora 32/33 (runtimes included)
- [X] Adds support for RHEL 7/8 (will require manual install of zlib and argon2)
- [X] Updates dependencies
Some architectures (specifically docker) have an issue with tick count and high precision timing.
This handles those weird cases so instead of outright freezing, you may get timer precision loss.
- [X] Adds `AdhocPersistence` which replaces RunUO `Persistence`
- [X] Fixes a few minor bugs with EntityPersistence deserialization
- [X] Reverts/updates some serialization changes