From 81e4087acc9eff1480df6b03d419f4fa3fa466a0 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Thu, 27 May 2021 00:54:21 -0700 Subject: [PATCH] fix(codegen): Adds manual dirty checking (#621) - Adds `[ManualDirtyChecking]` for anyone that adds it themselves. - Adds detection of `[Serializable]` and `[ManualDirtyChecking]` at startup to help identify scripts that need migration. --- Projects/Server/Guild.cs | 2 - Projects/Server/IEntity.cs | 9 ++-- Projects/Server/Items/Item.cs | 2 - Projects/Server/Main.cs | 50 ++++++++++--------- Projects/Server/Mobiles/Mobile.cs | 2 - .../Server/Serialization/ISerializable.cs | 8 +-- .../Serialization/ManualDirtyChecking.cs | 28 +++++++++++ Projects/Server/TileMatrix/TileMatrix.cs | 28 +++++++---- Projects/Server/World/World.cs | 1 + Projects/UOContent/Accounting/Account.cs | 2 - 10 files changed, 79 insertions(+), 53 deletions(-) create mode 100644 Projects/Server/Serialization/ManualDirtyChecking.cs diff --git a/Projects/Server/Guild.cs b/Projects/Server/Guild.cs index 58cfd4865..e9e6cc84a 100644 --- a/Projects/Server/Guild.cs +++ b/Projects/Server/Guild.cs @@ -63,8 +63,6 @@ namespace Server.Guilds [CommandProperty(AccessLevel.Counselor)] public Serial Serial { get; } - bool ISerializable.UseDirtyChecking => false; - long ISerializable.SavePosition { get; set; } = -1; BufferWriter ISerializable.SaveBuffer { get; set; } diff --git a/Projects/Server/IEntity.cs b/Projects/Server/IEntity.cs index 756aa18f5..827eb28ae 100644 --- a/Projects/Server/IEntity.cs +++ b/Projects/Server/IEntity.cs @@ -36,25 +36,24 @@ namespace Server public class Entity : IEntity { - public Entity(Serial serial, Point3D loc, Map map) + public Entity(Serial serial, Point3D loc, Map map) : this(serial) { - Serial = serial; Location = loc; Map = map; Deleted = false; } + public Entity(Serial serial) => Serial = serial; + public void SetTypeRef(Type type) { } - bool ISerializable.UseDirtyChecking => false; - long ISerializable.SavePosition { get; set; } = -1; BufferWriter ISerializable.SaveBuffer { get; set; } - public int TypeRef { get; } = -1; + public int TypeRef => -1; public Serial Serial { get; } diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index b6b01bdfc..5ad68b15c 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -783,8 +783,6 @@ namespace Server AddNameProperties(list); } - public virtual bool UseDirtyChecking => false; - long ISerializable.SavePosition { get; set; } = -1; BufferWriter ISerializable.SaveBuffer { get; set; } diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs index d2a691ae5..46d4fb1bf 100644 --- a/Projects/Server/Main.cs +++ b/Projects/Server/Main.cs @@ -24,6 +24,7 @@ using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Server.Buffers; using Server.Json; using Server.Logging; using Server.Network; @@ -232,7 +233,7 @@ namespace Server public static bool EJ => Expansion >= Expansion.EJ; - public static string FindDataFile(string path, bool throwNotFound = true, bool warnNotFound = false) + public static string FindDataFile(string path, bool throwNotFound = true) { string fullPath = null; @@ -248,16 +249,9 @@ namespace Server fullPath = null; } - if (fullPath == null && (throwNotFound || warnNotFound)) + if (fullPath == null && throwNotFound) { - Utility.PushColor(ConsoleColor.Red); - Console.WriteLine($"Data: {path} was not found"); - Console.WriteLine("Make sure modernuo.json is properly configured"); - Utility.PopColor(); - if (throwNotFound) - { - throw new FileNotFoundException($"Data: {path} was not found"); - } + throw new FileNotFoundException($"Data: {path} was not found"); } return fullPath; @@ -561,49 +555,57 @@ namespace Server private static void VerifyType(Type type) { - var isItem = type.IsSubclassOf(typeof(Item)); - - if (!isItem && !type.IsSubclassOf(typeof(Mobile))) + if (!type.IsAssignableTo(typeof(ISerializable)) || type.IsInterface || type.IsAbstract) { return; } - if (isItem) + if (type.IsSubclassOf(typeof(Item))) { Interlocked.Increment(ref _itemCount); } - else + else if (!type.IsSubclassOf(typeof(Mobile))) { Interlocked.Increment(ref _mobileCount); } - StringBuilder warningSb = null; + ValueStringBuilder errors = new ValueStringBuilder(); try { + if (World.DirtyTrackingEnabled) + { + var manualDirtyCheckingAttribute = type.GetCustomAttribute(false); + var codeGennedAttribute = type.GetCustomAttribute(false); + + if (manualDirtyCheckingAttribute == null && codeGennedAttribute == null) + { + errors.AppendLine(" - No property tracking (dirty checking)"); + } + } + if (type.GetConstructor(_serialTypeArray) == null) { - warningSb = new StringBuilder(); - warningSb.AppendLine(" - No serialization constructor"); + errors.AppendLine(" - No serialization constructor"); } const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly; if (type.GetMethod("Serialize", bindingFlags) == null) { - warningSb ??= new StringBuilder(); - warningSb.AppendLine(" - No Serialize() method"); + errors.AppendLine(" - No Serialize() method"); } if (type.GetMethod("Deserialize", bindingFlags) == null) { - warningSb ??= new StringBuilder(); - warningSb.AppendLine(" - No Deserialize() method"); + errors.AppendLine(" - No Deserialize() method"); } - if (warningSb?.Length > 0) + if (errors.Length > 0) { - Console.WriteLine("Warning: {0}\n{1}", type, warningSb); + Utility.PushColor(ConsoleColor.Red); + Console.WriteLine($"{type}\n{errors.ToString()}"); + Utility.PopColor(); } } catch diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 8a9da5fef..41ef6791c 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -2521,8 +2521,6 @@ namespace Server AddNameProperties(list); } - public virtual bool UseDirtyChecking => false; - long ISerializable.SavePosition { get; set; } = -1; BufferWriter ISerializable.SaveBuffer { get; set; } diff --git a/Projects/Server/Serialization/ISerializable.cs b/Projects/Server/Serialization/ISerializable.cs index bfc19fb86..d4e313b09 100644 --- a/Projects/Server/Serialization/ISerializable.cs +++ b/Projects/Server/Serialization/ISerializable.cs @@ -20,10 +20,6 @@ namespace Server { public interface ISerializable { - // Make sure all properties that will be serialized are calling `MarkDirty()` when they get modified. - // This should be done manually or via code gen through SerializedField attribute. - // This attribute should be virtual for any base serializalbe type (Item, Mobile, etc) that can be opt-in per type. - bool UseDirtyChecking { get; } long SavePosition { get; protected set; } BufferWriter SaveBuffer { get; protected internal set; } int TypeRef { get; } @@ -43,7 +39,7 @@ namespace Server public void InitializeSaveBuffer(byte[] buffer) { SaveBuffer = new BufferWriter(buffer, true); - if (UseDirtyChecking) + if (World.DirtyTrackingEnabled) { SavePosition = SaveBuffer.Position; } @@ -67,7 +63,7 @@ namespace Server SaveBuffer.Seek(0, SeekOrigin.Begin); Serialize(SaveBuffer); - if (UseDirtyChecking) + if (World.DirtyTrackingEnabled) { SavePosition = SaveBuffer.Position; } diff --git a/Projects/Server/Serialization/ManualDirtyChecking.cs b/Projects/Server/Serialization/ManualDirtyChecking.cs new file mode 100644 index 000000000..6042a5c8e --- /dev/null +++ b/Projects/Server/Serialization/ManualDirtyChecking.cs @@ -0,0 +1,28 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ManualDirtyChecking.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 Server +{ + /// + /// Indicates that the applied class has dirty checking. This is necessary for classes that are not code genned. + /// + [AttributeUsage(AttributeTargets.Class)] + public class ManualDirtyCheckingAttribute : Attribute + { + + } +} diff --git a/Projects/Server/TileMatrix/TileMatrix.cs b/Projects/Server/TileMatrix/TileMatrix.cs index 18fbb30f3..a6a542799 100644 --- a/Projects/Server/TileMatrix/TileMatrix.cs +++ b/Projects/Server/TileMatrix/TileMatrix.cs @@ -3,11 +3,13 @@ using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Server.Logging; namespace Server { public class TileMatrix { + private static readonly ILogger logger = LogFactory.GetLogger(typeof(TileMatrix)); private static readonly List _instances = new(); private readonly StaticTile[][][][][] _staticTiles; @@ -59,15 +61,9 @@ namespace Server _map = owner; -#if DEBUG - const bool warnNotFound = true; -#else - const bool warnNotFound = false; -#endif - if (fileIndex != 0x7F) { - var mapPath = Core.FindDataFile($"map{fileIndex}.mul", false, warnNotFound); + var mapPath = Core.FindDataFile($"map{fileIndex}.mul", false); if (mapPath != null) { @@ -75,29 +71,41 @@ namespace Server } else { - mapPath = Core.FindDataFile($"map{fileIndex}LegacyMUL.uop", false, warnNotFound); + mapPath = Core.FindDataFile($"map{fileIndex}LegacyMUL.uop", false); if (mapPath != null) { MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); _mapIndex = new UOPIndex(MapStream); } + else + { + logger.Warning($"map{fileIndex}.mul was not found."); + } } - var indexPath = Core.FindDataFile($"staidx{fileIndex}.mul", false, warnNotFound); + var indexPath = Core.FindDataFile($"staidx{fileIndex}.mul", false); if (indexPath != null) { IndexStream = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); IndexReader = new BinaryReader(IndexStream); } + else + { + logger.Warning($"staidx{fileIndex}.mul was not found."); + } - var staticsPath = Core.FindDataFile($"statics{fileIndex}.mul", false, warnNotFound); + var staticsPath = Core.FindDataFile($"statics{fileIndex}.mul", false); if (staticsPath != null) { DataStream = new FileStream(staticsPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } + else + { + logger.Warning($"statics{fileIndex}.mul was not found."); + } } _emptyStaticBlock = new StaticTile[8][][]; diff --git a/Projects/Server/World/World.cs b/Projects/Server/World/World.cs index 2fcfd2c79..f4bb661fa 100644 --- a/Projects/Server/World/World.cs +++ b/Projects/Server/World/World.cs @@ -48,6 +48,7 @@ namespace Server private static string _tempSavePath; // Path to the temporary folder for the save private static string _savePath; // Path to "Saves" folder + public const bool DirtyTrackingEnabled = false; public const uint ItemOffset = 0x40000000; public const uint MaxItemSerial = 0x7FFFFFFF; public const uint MaxMobileSerial = ItemOffset - 1; diff --git a/Projects/UOContent/Accounting/Account.cs b/Projects/UOContent/Accounting/Account.cs index 90f74715a..7a9b6c1d4 100644 --- a/Projects/UOContent/Accounting/Account.cs +++ b/Projects/UOContent/Accounting/Account.cs @@ -265,8 +265,6 @@ namespace Server.Accounting } } - bool ISerializable.UseDirtyChecking => false; - long ISerializable.SavePosition { get; set; } BufferWriter ISerializable.SaveBuffer { get; set; }