diff --git a/ModernUO.Serialization.Annotations/AfterDeserializationAttribute.cs b/ModernUO.Serialization.Annotations/AfterDeserializationAttribute.cs
index dc39484..13ccf54 100644
--- a/ModernUO.Serialization.Annotations/AfterDeserializationAttribute.cs
+++ b/ModernUO.Serialization.Annotations/AfterDeserializationAttribute.cs
@@ -33,5 +33,5 @@ public class AfterDeserializationAttribute : Attribute
///
public bool Synchronous { get; set; }
- public AfterDeserializationAttribute(bool synchronous = true) => Synchronous = true;
+ public AfterDeserializationAttribute(bool synchronous = true) => Synchronous = synchronous;
}
diff --git a/ModernUO.Serialization.Annotations/AnchoredDateTimeAttribute.cs b/ModernUO.Serialization.Annotations/AnchoredDateTimeAttribute.cs
new file mode 100644
index 0000000..659905c
--- /dev/null
+++ b/ModernUO.Serialization.Annotations/AnchoredDateTimeAttribute.cs
@@ -0,0 +1,29 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2026 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: AnchoredDateTimeAttribute.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System;
+
+namespace ModernUO.Serialization;
+
+///
+/// Hints to the source generator that a serializable DateTime field or property is anchored to
+/// the save time: it is written as an absolute value and re-anchored once at load, so downtime
+/// does not age it and an unchanged entity serializes to identical bytes. Takes precedence
+/// over when both are present.
+///
+[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+public class AnchoredDateTimeAttribute : Attribute
+{
+}
diff --git a/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs b/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs
index 3c34ac1..3314029 100644
--- a/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs
+++ b/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs
@@ -31,6 +31,7 @@ public interface IGenericReader
decimal ReadDecimal();
System.DateTime ReadDateTime();
System.DateTime ReadDeltaTime();
+ System.DateTime ReadAnchoredTime();
System.TimeSpan ReadTimeSpan();
System.Guid ReadGuid();
int ReadEncodedInt();
@@ -59,6 +60,7 @@ public interface IGenericWriter
void Write(decimal value);
void Write(System.DateTime value);
void WriteDeltaTime(System.DateTime value);
+ void WriteAnchoredTime(System.DateTime value);
void Write(System.TimeSpan value);
void Write(System.Guid value);
void WriteEncodedInt(int value);
@@ -168,6 +170,7 @@ public class Timer
{
public System.TimeSpan Delay { get; set; }
public System.DateTime Next { get; set; }
+ public static void DelayCall(System.Action callback) { }
}
public static class Core
diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/BasicFields/Expected/Server.TestContent.BasicFieldsItem.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/BasicFields/Expected/Server.TestContent.BasicFieldsItem.Serialization.g.cs
index 7ce5196..43e8181 100644
--- a/ModernUO.Serialization.Generator.Tests/Snapshots/BasicFields/Expected/Server.TestContent.BasicFieldsItem.Serialization.g.cs
+++ b/ModernUO.Serialization.Generator.Tests/Snapshots/BasicFields/Expected/Server.TestContent.BasicFieldsItem.Serialization.g.cs
@@ -275,6 +275,19 @@ private set
}
}
+ public System.DateTime LastRested
+ {
+ get => _lastRested;
+ set
+ {
+ if (value != _lastRested)
+ {
+ _lastRested = value;
+ Server.ISerializableExtensions.MarkDirty(this);
+ }
+ }
+ }
+
public BasicFieldsItem(Server.Serial serial)
{
Serial = serial;
@@ -323,6 +336,8 @@ public virtual void Serialize(Server.IGenericWriter writer)
writer.Write(_floatValue);
writer.Write(_privateSet);
+
+ writer.WriteAnchoredTime(_lastRested);
}
public virtual void Deserialize(Server.IGenericReader reader)
@@ -368,6 +383,8 @@ public virtual void Deserialize(Server.IGenericReader reader)
_floatValue = reader.ReadFloat();
_privateSet = reader.ReadInt();
+
+ _lastRested = reader.ReadAnchoredTime();
}
}
}
diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/BasicFields/Input.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/BasicFields/Input.cs
index 039045b..5133ad8 100644
--- a/ModernUO.Serialization.Generator.Tests/Snapshots/BasicFields/Input.cs
+++ b/ModernUO.Serialization.Generator.Tests/Snapshots/BasicFields/Input.cs
@@ -70,6 +70,10 @@ public partial class BasicFieldsItem : ISerializable
[SerializableField(19, setter: "private")]
private int _privateSet;
+ [SerializableField(20)]
+ [AnchoredDateTime]
+ private DateTime _lastRested;
+
public DateTime Created { get; set; }
public Serial Serial { get; }
public bool Deleted => false;
diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/NonServerNamespace/Expected/TestContent.ExternalItem.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/NonServerNamespace/Expected/TestContent.ExternalItem.Serialization.g.cs
index edefd8c..0ea30de 100644
--- a/ModernUO.Serialization.Generator.Tests/Snapshots/NonServerNamespace/Expected/TestContent.ExternalItem.Serialization.g.cs
+++ b/ModernUO.Serialization.Generator.Tests/Snapshots/NonServerNamespace/Expected/TestContent.ExternalItem.Serialization.g.cs
@@ -72,6 +72,8 @@ public virtual void Deserialize(Server.IGenericReader reader)
{
MigrateFrom(new V0Content(reader, this));
Server.ISerializableExtensions.MarkDirty(this);
+ ValidateState();
+ Server.Timer.DelayCall(RebuildCaches);
return;
}
@@ -80,6 +82,8 @@ public virtual void Deserialize(Server.IGenericReader reader)
var RefreshTimerNext = reader.ReadDateTime();
var RefreshTimerDelay = RefreshTimerNext == System.DateTime.MinValue ? System.TimeSpan.MinValue : RefreshTimerNext - Server.Core.Now;
DeserializeRefreshTimer(RefreshTimerDelay);
+ ValidateState();
+ Server.Timer.DelayCall(RebuildCaches);
}
}
}
diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/NonServerNamespace/Input.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/NonServerNamespace/Input.cs
index f300329..7e2e1ec 100644
--- a/ModernUO.Serialization.Generator.Tests/Snapshots/NonServerNamespace/Input.cs
+++ b/ModernUO.Serialization.Generator.Tests/Snapshots/NonServerNamespace/Input.cs
@@ -28,5 +28,15 @@ private void MigrateFrom(V0Content content)
{
_name = content.Name;
}
+
+ [AfterDeserialization]
+ private void ValidateState()
+ {
+ }
+
+ [AfterDeserialization(false)]
+ private void RebuildCaches()
+ {
+ }
}
}
diff --git a/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs b/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs
index 939fb6b..519d129 100644
--- a/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs
+++ b/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs
@@ -205,7 +205,7 @@ private void GenerateAfterDeserialization(
}
else
{
- source.AppendLine($"{indent}Timer.DelayCall({method.MethodName});");
+ source.AppendLine($"{indent}Server.Timer.DelayCall({method.MethodName});");
}
}
}
diff --git a/ModernUO.Serialization.Generator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs b/ModernUO.Serialization.Generator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs
index aec840c..b16ba64 100644
--- a/ModernUO.Serialization.Generator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs
+++ b/ModernUO.Serialization.Generator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs
@@ -68,6 +68,8 @@ SpecialType.System_Decimal and not
{
SpecialType.System_Int32 when attributes.Any(a => a.IsEncodedInt(compilation)) =>
["EncodedInt"],
+ SpecialType.System_DateTime when attributes.Any(a => a.IsAnchoredDateTime(compilation)) =>
+ ["AnchoredTime"],
SpecialType.System_DateTime when attributes.Any(a => a.IsDeltaDateTime(compilation)) =>
["DeltaTime"],
SpecialType.System_String when attributes.Any(a => a.IsInternString(compilation)) =>
@@ -114,8 +116,9 @@ public override void GenerateDeserializationMethod(
"double" => "ReadDouble",
"string" => "ReadString",
"decimal" => "ReadDecimal",
- date when argument == "DeltaTime" => "ReadDeltaTime",
- date => "ReadDateTime",
+ date when argument == "DeltaTime" => "ReadDeltaTime",
+ date when argument == "AnchoredTime" => "ReadAnchoredTime",
+ date => "ReadDateTime",
SymbolMetadata.IPADDRESS_CLASS => "ReadIPAddress",
SymbolMetadata.TIMESPAN_STRUCT => "ReadTimeSpan",
SymbolMetadata.GUID_STRUCT => "ReadGuid",
@@ -144,9 +147,10 @@ public override void GenerateSerializationMethod(StringBuilder source, string in
var writeMethod = property.Type switch
{
- date when argument == "DeltaTime" => "WriteDeltaTime",
- "int" when argument == "EncodedInt" => "WriteEncodedInt",
- _ => "Write"
+ date when argument == "DeltaTime" => "WriteDeltaTime",
+ date when argument == "AnchoredTime" => "WriteAnchoredTime",
+ "int" when argument == "EncodedInt" => "WriteEncodedInt",
+ _ => "Write"
};
source.AppendLine($"{indent}writer.{writeMethod}({propertyName});");
diff --git a/ModernUO.Serialization.Generator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs b/ModernUO.Serialization.Generator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs
index 5db536e..cff5894 100644
--- a/ModernUO.Serialization.Generator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs
+++ b/ModernUO.Serialization.Generator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs
@@ -28,6 +28,7 @@ public static partial class SymbolMetadata
public const string SERIALIZABLE_FIELD_ATTRIBUTE = "ModernUO.Serialization.SerializableFieldAttribute";
public const string SERIALIZABLE_PROPERTY_ATTRIBUTE = "ModernUO.Serialization.SerializablePropertyAttribute";
public const string DELTA_DATE_TIME_ATTRIBUTE = "ModernUO.Serialization.DeltaDateTimeAttribute";
+ public const string ANCHORED_DATE_TIME_ATTRIBUTE = "ModernUO.Serialization.AnchoredDateTimeAttribute";
public const string INTERN_STRING_ATTRIBUTE = "ModernUO.Serialization.InternStringAttribute";
public const string ENCODED_INT_ATTRIBUTE = "ModernUO.Serialization.EncodedIntAttribute";
public const string CAN_BE_NULL_ATTRIBUTE = "ModernUO.Serialization.CanBeNullAttribute";
@@ -104,6 +105,9 @@ public bool IsEncodedInt(Compilation compilation) =>
public bool IsDeltaDateTime(Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(DELTA_DATE_TIME_ATTRIBUTE)) == true;
+ public bool IsAnchoredDateTime(Compilation compilation) =>
+ attr?.IsAttribute(compilation.GetTypeByMetadataName(ANCHORED_DATE_TIME_ATTRIBUTE)) == true;
+
public bool IsInternString(Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(INTERN_STRING_ATTRIBUTE)) == true;