diff --git a/Projects/Server/Guild.cs b/Projects/Server/Guild.cs index 081e6fb4f..4fca230d8 100644 --- a/Projects/Server/Guild.cs +++ b/Projects/Server/Guild.cs @@ -38,10 +38,10 @@ namespace Server.Guilds public int TypeRef => 0; - public void Serialize(DateTime serializeStart) + public void Serialize() { SaveBuffer ??= new BufferWriter(true); - SaveBuffer.Flush(); + SaveBuffer.Reset(); Serialize(SaveBuffer); } diff --git a/Projects/Server/IEntity.cs b/Projects/Server/IEntity.cs index 6b4a28827..56e142da4 100644 --- a/Projects/Server/IEntity.cs +++ b/Projects/Server/IEntity.cs @@ -99,7 +99,7 @@ namespace Server && p.Y >= Location.m_Y - range && p.Y <= Location.m_Y + range; - public void Serialize(DateTime serializeStart) + public void Serialize() { } diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index 53e7ff141..b51b12c05 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -826,15 +826,10 @@ namespace Server public int TypeRef { get; } - public void Serialize(DateTime serializeStart) + public void Serialize() { - if (Decays && Parent == null && Map != Map.Internal && LastMoved + DecayTime <= serializeStart) - { - World.EnqueueForDecay(this); - } - SaveBuffer ??= new BufferWriter(true); - SaveBuffer.Flush(); + SaveBuffer.Reset(); Serialize(SaveBuffer); } @@ -2393,8 +2388,12 @@ namespace Server public bool AtPoint(int x, int y) => m_Location.m_X == x && m_Location.m_Y == y; + public virtual bool CanDecay() => + Decays && Parent == null && Map != Map.Internal; + + public virtual bool OnDecay() => - Decays && Parent == null && Map != Map.Internal && Region.Find(Location, Map).OnDecay(this); + CanDecay() && Region.Find(Location, Map).OnDecay(this); public void SetLastMoved() { diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index c5eff53e7..5ac033b25 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -2638,10 +2638,10 @@ namespace Server public int TypeRef { get; } - public void Serialize(DateTime serializeStart) + public void Serialize() { SaveBuffer ??= new BufferWriter(true); - SaveBuffer.Flush(); + SaveBuffer.Reset(); Serialize(SaveBuffer); } diff --git a/Projects/Server/Serialization/BinaryFileReader.cs b/Projects/Server/Serialization/BinaryFileReader.cs index 77b86b5ae..e720da180 100644 --- a/Projects/Server/Serialization/BinaryFileReader.cs +++ b/Projects/Server/Serialization/BinaryFileReader.cs @@ -145,22 +145,17 @@ namespace Server { var count = ReadInt(); - if (count > 0) + var list = new List(count); + + for (var i = 0; i < count; ++i) { - var list = new List(count); - - for (var i = 0; i < count; ++i) + if (ReadItem() is T item) { - if (ReadItem() is T item) - { - list.Add(item); - } + list.Add(item); } - - return list; } - return new List(); + return list; } public HashSet ReadItemSet() => ReadItemSet(); @@ -169,22 +164,17 @@ namespace Server { var count = ReadInt(); - if (count > 0) + var set = new HashSet(); + + for (var i = 0; i < count; ++i) { - var set = new HashSet(); - - for (var i = 0; i < count; ++i) + if (ReadItem() is T item) { - if (ReadItem() is T item) - { - set.Add(item); - } + set.Add(item); } - - return set; } - return new HashSet(); + return set; } public List ReadStrongMobileList() => ReadStrongMobileList(); @@ -193,22 +183,17 @@ namespace Server { var count = ReadInt(); - if (count > 0) + var list = new List(count); + + for (var i = 0; i < count; ++i) { - var list = new List(count); - - for (var i = 0; i < count; ++i) + if (ReadMobile() is T m) { - if (ReadMobile() is T m) - { - list.Add(m); - } + list.Add(m); } - - return list; } - return new List(); + return list; } public HashSet ReadMobileSet() => ReadMobileSet(); @@ -216,23 +201,17 @@ namespace Server public HashSet ReadMobileSet() where T : Mobile { var count = ReadInt(); + var set = new HashSet(); - if (count > 0) + for (var i = 0; i < count; ++i) { - var set = new HashSet(); - - for (var i = 0; i < count; ++i) + if (ReadMobile() is T item) { - if (ReadMobile() is T item) - { - set.Add(item); - } + set.Add(item); } - - return set; } - return new HashSet(); + return set; } public List ReadStrongGuildList() => ReadStrongGuildList(); @@ -240,11 +219,10 @@ namespace Server public List ReadStrongGuildList() where T : BaseGuild { var count = ReadInt(); + var list = new List(count); if (count > 0) { - var list = new List(count); - for (var i = 0; i < count; ++i) { if (ReadGuild() is T g) @@ -252,11 +230,9 @@ namespace Server list.Add(g); } } - - return list; } - return new List(); + return list; } public HashSet ReadGuildSet() => ReadGuildSet(); @@ -265,10 +241,10 @@ namespace Server { var count = ReadInt(); + var set = new HashSet(); + if (count > 0) { - var set = new HashSet(); - for (var i = 0; i < count; ++i) { if (ReadGuild() is T item) @@ -276,17 +252,17 @@ namespace Server set.Add(item); } } - - return set; } - return new HashSet(); + return set; } public Race ReadRace() => Race.Races[ReadByte()]; public bool End() => m_File.PeekChar() == -1; + public int Read(Span buffer) => m_File.Read(buffer); + public void Close() { m_File.Close(); diff --git a/Projects/Server/Serialization/BinaryFileWriter.cs b/Projects/Server/Serialization/BinaryFileWriter.cs index 26f822b22..f63b9d619 100644 --- a/Projects/Server/Serialization/BinaryFileWriter.cs +++ b/Projects/Server/Serialization/BinaryFileWriter.cs @@ -20,11 +20,18 @@ namespace Server public class BinaryFileWriter : BufferWriter { private readonly Stream m_File; + private long m_Position; public BinaryFileWriter(string filename, bool prefixStr) : base(prefixStr) => m_File = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - public BinaryFileWriter(Stream stream, bool prefixStr) : base(prefixStr) => m_File = stream; + public BinaryFileWriter(Stream stream, bool prefixStr) : base(prefixStr) + { + m_File = stream; + m_Position = m_File.Position; + } + + public override long Position => m_Position + m_Index; protected override int BufferSize => 512; @@ -35,21 +42,24 @@ namespace Server { m_Position += m_Index; - m_File.Write(m_Buffer, 0, m_Index); + m_File.Write(m_Buffer, 0, (int)m_Index); m_Index = 0; } } public override void Close() { - base.Close(); + if (m_Index > 0) + { + Flush(); + } + m_File.Close(); } public override long Seek(long offset, SeekOrigin origin) { - m_Position += m_Index; - m_Index = 0; + Flush(); return m_Position = m_File.Seek(offset, origin); } diff --git a/Projects/Server/Serialization/BufferWriter.cs b/Projects/Server/Serialization/BufferWriter.cs index afc0d7f8d..308083c32 100644 --- a/Projects/Server/Serialization/BufferWriter.cs +++ b/Projects/Server/Serialization/BufferWriter.cs @@ -31,8 +31,7 @@ namespace Server private readonly bool m_PrefixStrings; protected byte[] m_Buffer; - protected int m_Index; - protected long m_Position; + protected long m_Index; private readonly char[] m_SingleCharBuffer = new char[1]; @@ -40,6 +39,13 @@ namespace Server private int m_MaxBufferChars; + public BufferWriter(byte[] buffer, bool prefixStr) + { + m_PrefixStrings = prefixStr; + m_Encoding = Utility.UTF8; + m_Buffer = buffer; + } + public BufferWriter(bool prefixStr) { m_PrefixStrings = prefixStr; @@ -47,29 +53,23 @@ namespace Server m_Buffer = new byte[BufferSize]; } + public virtual long Position => m_Index; + protected virtual int BufferSize => 256; public byte[] Data => m_Buffer; - public long Position - { - get => m_Position + m_Index; - set => Seek(value, value < 0 ? SeekOrigin.End : SeekOrigin.Begin); - } - public virtual void Close() { - if (m_Index > 0) - { - Flush(); - } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Resize(int size) { - Array.Resize(ref m_Buffer, size); + var copy = new byte[size]; + Buffer.BlockCopy(m_Buffer, 0, copy, 0, Math.Min(size, m_Buffer.Length)); + m_Buffer = copy; } public virtual void Flush() @@ -77,16 +77,19 @@ namespace Server Resize(m_Buffer.Length * 2); } + public void Reset() + { + m_Index = 0; + } + public virtual long Seek(long offset, SeekOrigin origin) { - Flush(); - return origin switch { - SeekOrigin.Begin => m_Position = offset, - SeekOrigin.Current => m_Position += offset, - SeekOrigin.End => m_Position = BufferSize - offset, - _ => m_Position + SeekOrigin.Begin => m_Index = offset, + SeekOrigin.Current => m_Index += offset, + SeekOrigin.End => m_Index = BufferSize - offset, + _ => m_Index }; } @@ -321,7 +324,7 @@ namespace Server m_SingleCharBuffer[0] = value; - var byteCount = m_Encoding.GetBytes(m_SingleCharBuffer, 0, 1, m_Buffer, m_Index); + var byteCount = m_Encoding.GetBytes(m_SingleCharBuffer, 0, 1, m_Buffer, (int)m_Index); m_Index += byteCount; } @@ -337,13 +340,13 @@ namespace Server public void Write(byte[] value, int length) { - int remaining = length; - int idx = 0; + var remaining = length; + var idx = 0; while (remaining > 0) { - int size = Math.Min(m_Buffer.Length - m_Index, remaining); - Buffer.BlockCopy(value, idx, m_Buffer, m_Index, size); + int size = Math.Min(m_Buffer.Length - (int)m_Index, remaining); + Buffer.BlockCopy(value, idx, m_Buffer, (int)m_Index, size); // value.Slice(idx).CopyTo(m_Buffer.AsSpan(m_Index, size)); remaining -= size; @@ -775,7 +778,7 @@ namespace Server Flush(); } - Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); + Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, (int)m_Index, byteLength); m_Index += byteLength; current += charCount; @@ -791,7 +794,7 @@ namespace Server Flush(); } - Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); + Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, (int)m_Index, byteLength); m_Index += byteLength; } } diff --git a/Projects/Server/Serialization/IGenericReader.cs b/Projects/Server/Serialization/IGenericReader.cs index 3ea2700fd..1fcbfab56 100644 --- a/Projects/Server/Serialization/IGenericReader.cs +++ b/Projects/Server/Serialization/IGenericReader.cs @@ -68,5 +68,6 @@ namespace Server HashSet ReadGuildSet() where T : BaseGuild; Race ReadRace(); bool End(); + int Read(Span buffer); } } diff --git a/Projects/Server/Serialization/ISerializable.cs b/Projects/Server/Serialization/ISerializable.cs index f02cd776d..58f4bf41d 100644 --- a/Projects/Server/Serialization/ISerializable.cs +++ b/Projects/Server/Serialization/ISerializable.cs @@ -13,8 +13,6 @@ * along with this program. If not, see . * *************************************************************************/ -using System; - namespace Server { public interface ISerializable @@ -22,7 +20,7 @@ namespace Server BufferWriter SaveBuffer { get; set; } int TypeRef { get; } Serial Serial { get; } - void Serialize(DateTime serializeStart); + void Serialize(); void Deserialize(IGenericReader reader); void Serialize(IGenericWriter writer); void Delete(); diff --git a/Projects/Server/World/World.cs b/Projects/Server/World/World.cs index 92bd1acb0..f5c15783e 100644 --- a/Projects/Server/World/World.cs +++ b/Projects/Server/World/World.cs @@ -137,7 +137,7 @@ namespace Server m_DiskWriteHandle.WaitOne(); } - public static void EnqueueForDecay(Item item) + private static void EnqueueForDecay(Item item) { if (WorldState != WorldState.Saving) { @@ -338,6 +338,11 @@ namespace Server } t.Delete(); } + + reader.Seek(entry.Position, SeekOrigin.Begin); + + t.SaveBuffer = new BufferWriter(new byte[entry.Length], true); + reader.Read(t.SaveBuffer.Data); } reader.Close(); @@ -381,8 +386,6 @@ namespace Server ProcessSafetyQueues(); - var now = DateTime.UtcNow; - foreach (var item in Items.Values) { if (item.Parent == null) @@ -391,7 +394,6 @@ namespace Server } item.ClearProperties(); - item.Serialize(now); } foreach (var m in Mobiles.Values) @@ -400,12 +402,6 @@ namespace Server m.UpdateTotals(); m.ClearProperties(); - m.Serialize(now); - } - - foreach (var g in Guilds.Values) - { - g.Serialize(now); } watch.Stop(); @@ -466,6 +462,9 @@ namespace Server public static void WriteFiles(object state) { + Console.Write("Closing Save Files..."); + var watch = Stopwatch.StartNew(); + IIndexInfo itemIndexInfo = new EntityTypeIndex("Items"); IIndexInfo mobileIndexInfo = new EntityTypeIndex("Mobiles"); IIndexInfo guildIndexInfo = new EntityTypeIndex("Guilds"); @@ -474,10 +473,11 @@ namespace Server WriteEntities(mobileIndexInfo, Mobiles, MobileTypes); WriteEntities(guildIndexInfo, Guilds, GuildTypes); - if (m_DiskWriteHandle.Set()) - { - Console.WriteLine("Closing Save Files."); - } + watch.Stop(); + + m_DiskWriteHandle.Set(); + + Console.WriteLine("done {0:F1} seconds.", watch.Elapsed.TotalSeconds); Timer.DelayCall(FinishWorldSave); } @@ -525,8 +525,13 @@ namespace Server private static void SaveEntities(IEnumerable list, DateTime serializeStart) where T : class, ISerializable { - Parallel.ForEach(list, (t, now) => { - t.Serialize(serializeStart); + Parallel.ForEach(list, t => { + if (t is Item item && item.CanDecay() && item.LastMoved + item.DecayTime <= serializeStart) + { + EnqueueForDecay(item); + } + + t.Serialize(); }); } @@ -551,13 +556,13 @@ namespace Server return; } + WaitForWriteCompletion(); // Blocks Save until current disk flush is done.s + ++_Saves; NetState.FlushAll(); NetState.Pause(); - WaitForWriteCompletion(); // Blocks Save until current disk flush is done. - WorldState = WorldState.Saving; m_DiskWriteHandle.Reset(); @@ -593,12 +598,11 @@ namespace Server Console.WriteLine("Save done in {0:F2} seconds.", duration); - Broadcast( - 0x35, - true, - "World save complete. The entire process took {0:F2} seconds.", - duration - ); + // Only broadcast if it took at least 150ms + if (duration >= 0.15) + { + Broadcast(0x35, true, $"World save completed in {duration:F2} seconds."); + } NetState.Resume(); } diff --git a/Projects/UOContent/Commands/Handlers.cs b/Projects/UOContent/Commands/Handlers.cs index 94a64219e..085df0691 100644 --- a/Projects/UOContent/Commands/Handlers.cs +++ b/Projects/UOContent/Commands/Handlers.cs @@ -44,9 +44,6 @@ namespace Server.Commands Register("Help", AccessLevel.Player, Help_OnCommand); Register("Save", AccessLevel.Administrator, Save_OnCommand); - Register("BackgroundSave", AccessLevel.Administrator, BackgroundSave_OnCommand); - Register("BGSave", AccessLevel.Administrator, BackgroundSave_OnCommand); - Register("SaveBG", AccessLevel.Administrator, BackgroundSave_OnCommand); Register("Move", AccessLevel.GameMaster, Move_OnCommand); Register("Client", AccessLevel.Counselor, Client_OnCommand); @@ -482,20 +479,9 @@ namespace Server.Commands AutoSave.Save(); } - [Usage("BackgroundSave")] - [Aliases("BGSave", "SaveBG")] - [Description("Saves the world, writing to the disk in the background")] - private static void BackgroundSave_OnCommand(CommandEventArgs e) - { - AutoSave.Save(true); - } - - private static bool FixMap(ref Map map, ref Point3D loc, Item item) => map != null && map != Map.Internal || - item.RootParent is Mobile m && FixMap( - ref map, - ref loc, - m - ); + private static bool FixMap(ref Map map, ref Point3D loc, Item item) => + map != null && map != Map.Internal || item.RootParent is Mobile m && + FixMap(ref map, ref loc, m); private static bool FixMap(ref Map map, ref Point3D loc, Mobile m) { diff --git a/Projects/UOContent/Misc/AutoSave.cs b/Projects/UOContent/Misc/AutoSave.cs index c2bd71a70..a4dc1d071 100644 --- a/Projects/UOContent/Misc/AutoSave.cs +++ b/Projects/UOContent/Misc/AutoSave.cs @@ -50,7 +50,7 @@ namespace Server.Misc if (m_Warning == TimeSpan.Zero) { - Save(true); + Save(); } else { @@ -85,12 +85,7 @@ namespace Server.Misc public static void Save() { - Save(false); - } - - public static void Save(bool permitBackgroundWrite) - { - if (AutoRestart.Restarting) + if (AutoRestart.Restarting || !World.Running) { return; } @@ -118,10 +113,7 @@ namespace Server.Misc var root = Path.Combine(Core.BaseDirectory, "Backups/Automatic"); - if (!Directory.Exists(root)) - { - Directory.CreateDirectory(root); - } + AssemblyHandler.EnsureDirectory(root); var existing = Directory.GetDirectories(root);