Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions ModernUO.Serialization.Annotations/DeserializeTimerAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: DeserializeTimerAttribute.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/

using System;

namespace ModernUO.Serialization;

/// <summary>
/// Declares how a serializable Timer field is restored at load. The named method must have the
/// signature <c>void Method(TimeSpan delay)</c> and is invoked only when a timer was running at
/// save, with its remaining delay.
/// <para>
/// By default the timer drifts: its next tick is written as anchored time, so downtime does not
/// consume the remaining delay. Set <paramref name="wallClock" /> to true for absolute
/// deadlines instead; the delay is then negative when the deadline passed during downtime.
/// </para>
/// <code>
/// [SerializableField(1)]
/// [DeserializeTimer(nameof(DeserializeRelockTimer))]
/// private Timer _relockTimer;
/// </code>
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class DeserializeTimerAttribute : Attribute
{
public string MethodName { get; }

public bool WallClock { get; }

public DeserializeTimerAttribute(string methodName, bool wallClock = false)
{
MethodName = methodName;
WallClock = wallClock;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: DeserializeTimerFieldAttribute.cs *
* *
Expand All @@ -18,16 +18,16 @@
namespace ModernUO.Serialization;

/// <summary>
/// Hints to the source generator that the specified serializable field, which must be a timer,
/// can be deserialized by this method. The method signature should look like this:
///
/// [DeserializeTimerField(0)]
/// private void DeserializeTimer(TimeSpan delay)
/// Removed in v4. Timer serialization is declared with [DeserializeTimer(nameof(Method))] on
/// the timer field instead of an order-linked attribute on the method. The method is now
/// invoked only when a timer was running at save.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
[Obsolete("Removed in v4. Use [DeserializeTimer(nameof(Method))] on the timer field. The method is invoked only when a timer was running at save.", true)]
public sealed class DeserializeTimerFieldAttribute : Attribute
{
public int Order { get; }

public DeserializeTimerFieldAttribute(int order) => Order = order;
[Obsolete("Removed in v4. Use [DeserializeTimer(nameof(Method))] on the timer field.", true)]
public DeserializeTimerFieldAttribute(int order)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<PackageId>ModernUO.Serialization.Annotations</PackageId>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>preview</LangVersion>
<AssemblyVersion>3.0.0</AssemblyVersion>
<PackageVersion>3.0.0</PackageVersion>
<AssemblyVersion>4.0.0</AssemblyVersion>
<PackageVersion>4.0.0</PackageVersion>
<AssemblyName>ModernUO.Serialization.Annotations</AssemblyName>
<RootNamespace>ModernUO.Serialization</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
Expand Down
45 changes: 45 additions & 0 deletions ModernUO.Serialization.Annotations/SaveFlagAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SaveFlagAttribute.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/

using System;

namespace ModernUO.Serialization;

/// <summary>
/// Declares conditional serialization for a serializable field, on the field itself. The
/// first method (<c>bool Method()</c>) decides whether the value is written; the optional
/// second method (returning the field's type, no parameters) supplies the value at load when
/// it was not written.
/// <code>
/// [SerializableField(0)]
/// [SaveFlag(nameof(ShouldSerializeName), nameof(NameDefaultValue))]
/// private string _name;
/// </code>
/// When the second method is omitted, the field keeps its default value at load when the
/// save flag indicates the value was not written.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class SaveFlagAttribute : Attribute
{
public string ShouldSerializeMethod { get; }

public string DefaultValueMethod { get; }

public SaveFlagAttribute(string shouldSerializeMethod, string defaultValueMethod = null)
{
ShouldSerializeMethod = shouldSerializeMethod;
DefaultValueMethod = defaultValueMethod;
}
}
32 changes: 30 additions & 2 deletions ModernUO.Serialization.Annotations/SerializableFieldAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,29 @@ namespace ModernUO.Serialization;

/// <summary>
/// Hints to the source generator that this field should be serialized.
/// The source generator will generate the property entirely
/// The source generator will generate the property entirely.
/// <para>
/// <c>allowFieldChange</c> names a gate with the signature <c>bool Method(ref T value)</c>
/// where T is the field's type. The generated setter invokes it before assignment (after the
/// equality check); it may coerce the incoming value through the ref parameter, and returning
/// false rejects the change entirely. The field still holds the old value while the gate
/// runs.
/// </para>
/// <para>
/// <c>fieldChanged</c> names a change callback with the signature
/// <c>void Method(T oldValue, T newValue)</c>; it is invoked by the generated setter after
/// assignment.
/// <code>
/// [SerializableField(2, allowFieldChange: nameof(AllowLevelChange), fieldChanged: nameof(OnLevelChanged))]
/// private int _level;
///
/// private bool AllowLevelChange(ref int value)
/// {
/// value = Math.Clamp(value, 0, 100);
/// return true;
/// }
/// </code>
/// </para>
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public sealed class SerializableFieldAttribute : Attribute
Expand All @@ -28,17 +50,23 @@ public sealed class SerializableFieldAttribute : Attribute
public string PropertyGetter { get; }
public string? PropertySetter { get; }
public bool IsVirtual { get; }
public string? FieldChanged { get; }
public string? AllowFieldChange { get; }

public SerializableFieldAttribute(
int order,
string getter = "public",
string setter = "public",
bool isVirtual = false
bool isVirtual = false,
string fieldChanged = null,
string allowFieldChange = null
)
{
Order = order;
PropertyGetter = getter;
PropertySetter = setter;
IsVirtual = isVirtual;
FieldChanged = fieldChanged;
AllowFieldChange = allowFieldChange;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableFieldChangedAttribute.cs *
* *
Expand All @@ -18,13 +18,19 @@
namespace ModernUO.Serialization;

/// <summary>
/// Hints to the source generator that the method should be called when the field with the same order value changes.
/// The method must have the signature: void MethodName(T oldValue, T newValue) where T is the field type.
/// Removed in v4. Change callbacks are declared as part of the field's serialization
/// attribute: <c>[SerializableField(order, fieldChanged: nameof(Method))]</c>. This
/// conversion does not change the wire format.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
[Obsolete("Removed in v4. Pass fieldChanged: nameof(Method) to [SerializableField] instead. The wire format does not change.", true)]
public sealed class SerializableFieldChangedAttribute : Attribute
{
public int Order { get; }
public SerializableFieldChangedAttribute(string fieldName)
{
}

public SerializableFieldChangedAttribute(int order) => Order = order;
public SerializableFieldChangedAttribute(int order)
{
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableFieldDefaultAttribute.cs *
* *
Expand All @@ -18,17 +18,19 @@
namespace ModernUO.Serialization;

/// <summary>
/// Hints to the source generator that the field with the same order should use this default value
/// while deserializing. The default is used when the save flag indicates that we don't need to serialize the value
/// because this default can be used instead.
///
/// Note: This is only used for the current version, not previous versions. Previous versions will always use null or default
/// for that type if it is not deserialized.
/// Removed in v4. A default value is declared as the second argument of the field's save
/// flag: <c>[SaveFlag(nameof(ShouldSerializeMethod), nameof(DefaultValueMethod))]</c>, so it
/// cannot exist without one. This conversion does not change the wire format.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
[Obsolete("Removed in v4. Declare [SaveFlag(nameof(ShouldSerializeMethod), nameof(DefaultValueMethod))] on the serializable field instead. The wire format does not change.", true)]
public sealed class SerializableFieldDefaultAttribute : Attribute
{
public int Order { get; }
public SerializableFieldDefaultAttribute(string fieldName)
{
}

public SerializableFieldDefaultAttribute(int order) => Order = order;
public SerializableFieldDefaultAttribute(int order)
{
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableFieldSaveFlagAttribute.cs *
* *
Expand All @@ -18,12 +18,19 @@
namespace ModernUO.Serialization;

/// <summary>
/// Hints to the source generator that the field with the same order value should use a save flag.
/// Removed in v4. Save flags are declared on the serializable field itself:
/// <c>[SaveFlag(nameof(ShouldSerializeMethod))]</c>. This conversion does not change the
/// wire format.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
[Obsolete("Removed in v4. Declare [SaveFlag(nameof(ShouldSerializeMethod))] on the serializable field instead. The wire format does not change.", true)]
public sealed class SerializableFieldSaveFlagAttribute : Attribute
{
public int Order { get; }
public SerializableFieldSaveFlagAttribute(string fieldName)
{
}

public SerializableFieldSaveFlagAttribute(int order) => Order = order;
public SerializableFieldSaveFlagAttribute(int order)
{
}
}
9 changes: 6 additions & 3 deletions ModernUO.Serialization.Annotations/TimerDriftAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: TimerDriftAttribute.cs *
* *
Expand All @@ -18,10 +18,13 @@
namespace ModernUO.Serialization;

/// <summary>
/// Hints to the source generator that this serializable timer field or property will drift
/// during deserialization.
/// Removed in v4. Timer serialization is declared with [DeserializeTimer(nameof(Method))] on
/// the timer field; drift is the default. Migrating a drifting timer changes its wire format
/// (anchored time), so bump the class's [SerializationGenerator] version and add a
/// MigrateFrom for the previous version.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[Obsolete("Removed in v4. Use [DeserializeTimer(nameof(Method))] on the timer field; drift is the default. Bump the class version - the wire format changes to anchored time.", true)]
public sealed class TimerDriftAttribute : Attribute
{
}
63 changes: 0 additions & 63 deletions ModernUO.Serialization.Generator.Tests/DiagnosticTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,67 +228,4 @@ public void MarkDirty() { }
Assert.NotNull(generatedSource);
}

[Fact]
public void DuplicateSaveFlagOrder_ReportsDiagnostic()
{
const string source = """
using ModernUO.Serialization;
using Server;

namespace TestNamespace
{
[SerializationGenerator(0)]
public partial class DuplicateFlagItem : ISerializable
{
[SerializableField(0)]
private string _name;

[SerializableField(1)]
private int _count;

[SerializableFieldSaveFlag(0)]
private bool ShouldSerializeName() => _name != null;

[SerializableFieldSaveFlag(0)]
private bool ShouldSerializeName2() => _name != null;

public Serial Serial => default;
public void MarkDirty() { }
}
}
""";

var (diagnostics, _) = SourceGeneratorTestHelper.RunGenerator(source);

Assert.Contains(diagnostics, d => d.Id == "SG3003");
}

[Fact]
public void NegativeSaveFlagOrder_ReportsDiagnostic()
{
const string source = """
using ModernUO.Serialization;
using Server;

namespace TestNamespace
{
[SerializationGenerator(0)]
public partial class NegativeFlagItem : ISerializable
{
[SerializableField(0)]
private string _name;

[SerializableFieldSaveFlag(-1)]
private bool ShouldSerializeName() => _name != null;

public Serial Serial => default;
public void MarkDirty() { }
}
}
""";

var (diagnostics, _) = SourceGeneratorTestHelper.RunGenerator(source);

Assert.Contains(diagnostics, d => d.Id == "SG3006");
}
}
Loading
Loading