diff --git a/Projects/Server.Tests/Network/Packets/Old/Outgoing/EquipmentPacketTests.cs b/Projects/Server.Tests/Network/Packets/Old/Outgoing/EquipmentPacketTests.cs index 1370ae992..34b4e1d63 100644 --- a/Projects/Server.Tests/Network/Packets/Old/Outgoing/EquipmentPacketTests.cs +++ b/Projects/Server.Tests/Network/Packets/Old/Outgoing/EquipmentPacketTests.cs @@ -13,7 +13,7 @@ namespace Server.Tests.Network var m = new Mobile(0x1); m.DefaultMobileInit(); - var item = new Item(Serial.LastItem + 1); + var item = new Item(World.NewItem); var info = new EquipmentInfo( 500000, @@ -77,7 +77,7 @@ namespace Server.Tests.Network var m = new Mobile(0x1); m.DefaultMobileInit(); - var item = new Item(Serial.LastItem + 1) { Parent = m }; + var item = new Item(World.NewItem) { Parent = m }; var data = new EquipUpdate(item).Compile(); diff --git a/Projects/Server.Tests/Network/Packets/Old/Outgoing/ItemPacketTests.cs b/Projects/Server.Tests/Network/Packets/Old/Outgoing/ItemPacketTests.cs index 28c7be233..74436f928 100644 --- a/Projects/Server.Tests/Network/Packets/Old/Outgoing/ItemPacketTests.cs +++ b/Projects/Server.Tests/Network/Packets/Old/Outgoing/ItemPacketTests.cs @@ -497,8 +497,8 @@ namespace Server.Tests.Network [Fact] public void TestContainerContent() { - var cont = new Container(Serial.LastItem + 1); - cont.AddItem(new Item(Serial.LastItem + 2)); + var cont = new Container(World.NewItem); + cont.AddItem(new Item(World.NewItem)); var m = new Mobile(0x1); m.DefaultMobileInit(); @@ -549,8 +549,8 @@ namespace Server.Tests.Network [Fact] public void TestContainerContent6017() { - var cont = new Container(Serial.LastItem + 1); - cont.AddItem(new Item(Serial.LastItem + 2)); + var cont = new Container(World.NewItem); + cont.AddItem(new Item(World.NewItem)); var m = new Mobile(0x1); m.DefaultMobileInit(); diff --git a/Projects/Server.Tests/Network/Packets/Old/Outgoing/MenuPacketTests.cs b/Projects/Server.Tests/Network/Packets/Old/Outgoing/MenuPacketTests.cs index 63ce26dba..15436c5e5 100644 --- a/Projects/Server.Tests/Network/Packets/Old/Outgoing/MenuPacketTests.cs +++ b/Projects/Server.Tests/Network/Packets/Old/Outgoing/MenuPacketTests.cs @@ -145,7 +145,7 @@ namespace Server.Tests.Network var m = new Mobile(0x1); m.DefaultMobileInit(); - var item = new ContextMenuItem(Serial.LastItem + 1); + var item = new ContextMenuItem(World.NewItem); var menu = new ContextMenu(m, item); var data = new DisplayContextMenu(menu).Compile(); @@ -196,7 +196,7 @@ namespace Server.Tests.Network var m = new Mobile(0x1); m.DefaultMobileInit(); - var item = new ContextMenuItem(Serial.LastItem + 1); + var item = new ContextMenuItem(World.NewItem); var menu = new ContextMenu(m, item); var data = new DisplayContextMenuOld(menu).Compile(); diff --git a/Projects/Server.Tests/Network/Packets/Old/Outgoing/SecureTradePacketTests.cs b/Projects/Server.Tests/Network/Packets/Old/Outgoing/SecureTradePacketTests.cs index d684c3ba8..b4780ffc2 100644 --- a/Projects/Server.Tests/Network/Packets/Old/Outgoing/SecureTradePacketTests.cs +++ b/Projects/Server.Tests/Network/Packets/Old/Outgoing/SecureTradePacketTests.cs @@ -16,8 +16,8 @@ namespace Server.Tests.Network var m = new Mobile(0x1); m.DefaultMobileInit(); - var firstCont = new Container(Serial.LastItem + 1); - var secondCont = new Container(Serial.LastItem + 2); + var firstCont = new Container(World.NewItem); + var secondCont = new Container(World.NewItem); var data = new DisplaySecureTrade(m, firstCont, secondCont, name).Compile(); @@ -44,7 +44,7 @@ namespace Server.Tests.Network [Fact] public void TestCloseSecureTrade() { - var cont = new Container(Serial.LastItem + 1); + var cont = new Container(World.NewItem); var data = new CloseSecureTrade(cont).Compile(); @@ -64,8 +64,8 @@ namespace Server.Tests.Network [InlineData(false, true)] // Update second public void TestUpdateSecureTrade(bool first, bool second) { - var firstCont = new Container(Serial.LastItem + 1); - var secondCont = new Container(Serial.LastItem + 2); + var firstCont = new Container(World.NewItem); + var secondCont = new Container(World.NewItem); var cont = first ? firstCont : secondCont; var data = new UpdateSecureTrade(cont, first, second).Compile(); @@ -88,7 +88,7 @@ namespace Server.Tests.Network [InlineData(250000, 50000, TradeFlag.UpdateLedger)] public void TestUpdateGoldSecureTrade(int gold, int plat, TradeFlag flag) { - var cont = new Container(Serial.LastItem + 1); + var cont = new Container(World.NewItem); var data = new UpdateSecureTrade(cont, flag, gold, plat).Compile(); Span expectedData = stackalloc byte[16]; @@ -110,8 +110,8 @@ namespace Server.Tests.Network var m = new Mobile(0x1); m.DefaultMobileInit(); - var cont = new Container(Serial.LastItem + 1); - var itemInCont = new Item(Serial.LastItem + 2) { Parent = cont }; + var cont = new Container(World.NewItem); + var itemInCont = new Item(World.NewItem) { Parent = cont }; var data = new SecureTradeEquip(itemInCont, m).Compile(); Span expectedData = stackalloc byte[20]; @@ -140,8 +140,8 @@ namespace Server.Tests.Network var m = new Mobile(0x1); m.DefaultMobileInit(); - var cont = new Container(Serial.LastItem + 1); - var itemInCont = new Item(Serial.LastItem + 2) { Parent = cont }; + var cont = new Container(World.NewItem); + var itemInCont = new Item(World.NewItem) { Parent = cont }; var data = new SecureTradeEquip6017(itemInCont, m).Compile(); diff --git a/Projects/Server.Tests/Network/Packets/Old/Outgoing/VendorBuyPacketTests.cs b/Projects/Server.Tests/Network/Packets/Old/Outgoing/VendorBuyPacketTests.cs index 38a933d37..dbe45d157 100644 --- a/Projects/Server.Tests/Network/Packets/Old/Outgoing/VendorBuyPacketTests.cs +++ b/Projects/Server.Tests/Network/Packets/Old/Outgoing/VendorBuyPacketTests.cs @@ -13,13 +13,13 @@ namespace Server.Tests.Network [Fact] public void TestVendorBuyContent() { - var cont = new Container(Serial.LastItem + 1); + var cont = new Container(World.NewItem); var buyStates = new List { - new BuyItemState("First Item", cont.Serial, Serial.NewItem, 10, 1, 0x01, 0), - new BuyItemState("Second Item", cont.Serial, Serial.NewItem, 20, 2, 0x0A, 0), - new BuyItemState("Third Item", cont.Serial, Serial.NewItem, 30, 10, 0x0F, 0) + new BuyItemState("First Item", cont.Serial, World.NewItem, 10, 1, 0x01, 0), + new BuyItemState("Second Item", cont.Serial, World.NewItem, 20, 2, 0x0A, 0), + new BuyItemState("Third Item", cont.Serial, World.NewItem, 30, 10, 0x0F, 0) }; var data = new VendorBuyContent(buyStates).Compile(); @@ -52,13 +52,13 @@ namespace Server.Tests.Network [Fact] public void TestVendorBuyContent6017() { - var cont = new Container(Serial.LastItem + 1); + var cont = new Container(World.NewItem); var buyStates = new List { - new BuyItemState("First Item", cont.Serial, Serial.NewItem, 10, 1, 0x01, 0), - new BuyItemState("Second Item", cont.Serial, Serial.NewItem, 20, 2, 0x0A, 0), - new BuyItemState("Third Item", cont.Serial, Serial.NewItem, 30, 10, 0x0F, 0) + new BuyItemState("First Item", cont.Serial, World.NewItem, 10, 1, 0x01, 0), + new BuyItemState("Second Item", cont.Serial, World.NewItem, 20, 2, 0x0A, 0), + new BuyItemState("Third Item", cont.Serial, World.NewItem, 30, 10, 0x0F, 0) }; var data = new VendorBuyContent6017(buyStates).Compile(); @@ -139,13 +139,13 @@ namespace Server.Tests.Network var vendor = new Mobile(0x1); vendor.DefaultMobileInit(); - var cont = new Container(Serial.LastItem + 1); + var cont = new Container(World.NewItem); var buyStates = new List { - new BuyItemState("First Item", cont.Serial, Serial.NewItem, 10, 1, 0x01, 0), - new BuyItemState("Second Item", cont.Serial, Serial.NewItem, 20, 2, 0x0A, 0), - new BuyItemState("Third Item", cont.Serial, Serial.NewItem, 30, 10, 0x0F, 0) + new BuyItemState("First Item", cont.Serial, World.NewItem, 10, 1, 0x01, 0), + new BuyItemState("Second Item", cont.Serial, World.NewItem, 20, 2, 0x0A, 0), + new BuyItemState("Third Item", cont.Serial, World.NewItem, 30, 10, 0x0F, 0) }; var data = new VendorBuyList(vendor, buyStates).Compile(); diff --git a/Projects/Server.Tests/Network/Packets/Old/Outgoing/VendorSellPacketTests.cs b/Projects/Server.Tests/Network/Packets/Old/Outgoing/VendorSellPacketTests.cs index 101f07d45..611ff1cdb 100644 --- a/Projects/Server.Tests/Network/Packets/Old/Outgoing/VendorSellPacketTests.cs +++ b/Projects/Server.Tests/Network/Packets/Old/Outgoing/VendorSellPacketTests.cs @@ -15,9 +15,9 @@ namespace Server.Tests.Network var vendor = new Mobile(0x1); vendor.DefaultMobileInit(); - var item1 = new Item(Serial.LastItem + 1); - var item2 = new Item(Serial.LastItem + 2) { Name = "Second Item" }; - var item3 = new Item(Serial.LastItem + 3); + var item1 = new Item(World.NewItem); + var item2 = new Item(World.NewItem) { Name = "Second Item" }; + var item3 = new Item(World.NewItem); var sellStates = new List { diff --git a/Projects/Server/Events/EventSink.cs b/Projects/Server/Events/EventSink.cs index ea8857318..66193a222 100644 --- a/Projects/Server/Events/EventSink.cs +++ b/Projects/Server/Events/EventSink.cs @@ -109,8 +109,8 @@ namespace Server public static event Action WorldLoad; public static void InvokeWorldLoad() => WorldLoad?.Invoke(); - public static event Action WorldSave; - public static void InvokeWorldSave(bool sendMessage) => WorldSave?.Invoke(sendMessage); + public static event Action WorldSave; + public static void InvokeWorldSave() => WorldSave?.Invoke(); public static event Action SetAbility; public static void InvokeSetAbility(Mobile mobile, int index) => SetAbility?.Invoke(mobile, index); diff --git a/Projects/Server/Guild.cs b/Projects/Server/Guild.cs index aa9585c46..081e6fb4f 100644 --- a/Projects/Server/Guild.cs +++ b/Projects/Server/Guild.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -12,68 +13,54 @@ namespace Server.Guilds public abstract class BaseGuild : ISerializable { - private static Serial m_NextID = 1; - - protected BaseGuild(uint id) // serialization ctor + protected BaseGuild(Serial serial) { - Serial = id; - List.Add(Serial, this); - if (Serial + 1 > m_NextID) - { - m_NextID = Serial + 1; - } - - SaveBuffer = new BufferedFileWriter(true); + Serial = serial; + World.AddGuild(this); } protected BaseGuild() { - Serial = m_NextID++; - List.Add(Serial, this); - SaveBuffer = new BufferedFileWriter(true); + Serial = World.NewGuild; + World.AddGuild(this); } public abstract string Abbreviation { get; set; } public abstract string Name { get; set; } public abstract GuildType Type { get; set; } public abstract bool Disbanded { get; } + public abstract void Delete(); - public static Dictionary List { get; } = new Dictionary(); - public BufferedFileWriter SaveBuffer { get; } + public BufferWriter SaveBuffer { get; set; } [CommandProperty(AccessLevel.Counselor)] public Serial Serial { get; } public int TypeRef => 0; - public void Serialize() + public void Serialize(DateTime serializeStart) { + SaveBuffer ??= new BufferWriter(true); SaveBuffer.Flush(); Serialize(SaveBuffer); } public abstract void Serialize(IGenericWriter writer); - public abstract void Deserialize(IGenericReader reader); public abstract void OnDelete(Mobile mob); - public static BaseGuild Find(uint id) - { - List.TryGetValue(id, out var g); + public static BaseGuild FindByName(string name) => + World.Guilds.Values.FirstOrDefault(g => g.Name == name); - return g; - } - - public static BaseGuild FindByName(string name) => List.Values.FirstOrDefault(g => g.Name == name); - - public static BaseGuild FindByAbbrev(string abbr) => List.Values.FirstOrDefault(g => g.Abbreviation == abbr); + public static BaseGuild FindByAbbrev(string abbr) => + World.Guilds.Values.FirstOrDefault(g => g.Abbreviation == abbr); public static List Search(string find) { var words = find.ToLower().Split(' '); var results = new List(); - foreach (var g in List.Values) + foreach (var g in World.Guilds.Values) { var name = g.Name.ToLower(); @@ -86,6 +73,6 @@ namespace Server.Guilds return results; } - public override string ToString() => $"0x{Serial:X} \"{Name} [{Abbreviation}]\""; + public override string ToString() => $"0x{Serial.Value:X} \"{Name} [{Abbreviation}]\""; } } diff --git a/Projects/Server/IEntity.cs b/Projects/Server/IEntity.cs index f646da213..6b4a28827 100644 --- a/Projects/Server/IEntity.cs +++ b/Projects/Server/IEntity.cs @@ -1,16 +1,31 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: IEntity.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 { - public interface IEntity : IPoint3D, IComparable + public interface IEntity : IPoint3D, ISerializable { - Serial Serial { get; } + // Serial Serial { get; } Point3D Location { get; } Map Map { get; } bool Deleted { get; } + void Delete(); void MoveToWorld(Point3D location, Map map); - void Delete(); void ProcessDelta(); bool InRange(Point2D p, int range); @@ -22,7 +37,7 @@ namespace Server void RemoveItem(Item item); } - public class Entity : IEntity, IComparable + public class Entity : IEntity { public Entity(Serial serial, Point3D loc, Map map) { @@ -32,10 +47,8 @@ namespace Server Deleted = false; } - public int CompareTo(Entity other) => CompareTo((IEntity)other); - - public int CompareTo(IEntity other) => other == null ? -1 : Serial.CompareTo(other.Serial); - + public BufferWriter SaveBuffer { get; set; } + public int TypeRef { get; } = -1; public Serial Serial { get; } public Point3D Location { get; private set; } @@ -85,5 +98,19 @@ namespace Server && p.X <= Location.m_X + range && p.Y >= Location.m_Y - range && p.Y <= Location.m_Y + range; + + public void Serialize(DateTime serializeStart) + { + } + + public void Deserialize(IGenericReader reader) + { + // Should not actually be saved + Timer.DelayCall(Delete); + } + + public void Serialize(IGenericWriter writer) + { + } } } diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index 5a4b10f8d..53e7ff141 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Threading.Tasks; using Server.ContextMenus; using Server.Items; using Server.Network; @@ -192,7 +191,7 @@ namespace Server Spawner = 0x100 } - public class Item : IHued, IComparable, ISerializable, ISpawnable, IPropertyListObject + public class Item : IHued, IComparable, ISpawnable, IPropertyListObject { public const int QuestItemHue = 0x4EA; // Hmmmm... "for EA"? public static readonly List EmptyItems = new List(); @@ -220,11 +219,14 @@ namespace Server private ObjectPropertyList m_PropertyList; + // Position in the save buffer where serialization ends. -1 if dirty + private int _savePosition = -1; + [Constructible] public Item(int itemID = 0) { m_ItemID = itemID; - Serial = Serial.NewItem; + Serial = World.NewItem; // m_Items = new ArrayList( 1 ); Visible = true; @@ -234,18 +236,16 @@ namespace Server SetLastMoved(); - World.AddItem(this); + World.AddEntity(this); var ourType = GetType(); - TypeRef = World.m_ItemTypes.IndexOf(ourType); + TypeRef = World.ItemTypes.IndexOf(ourType); if (TypeRef == -1) { - World.m_ItemTypes.Add(ourType); - TypeRef = World.m_ItemTypes.Count - 1; + World.ItemTypes.Add(ourType); + TypeRef = World.ItemTypes.Count - 1; } - - SaveBuffer = new BufferedFileWriter(true); } public Item(Serial serial) @@ -253,15 +253,13 @@ namespace Server Serial = serial; var ourType = GetType(); - TypeRef = World.m_ItemTypes.IndexOf(ourType); + TypeRef = World.ItemTypes.IndexOf(ourType); if (TypeRef == -1) { - World.m_ItemTypes.Add(ourType); - TypeRef = World.m_ItemTypes.Count - 1; + World.ItemTypes.Add(ourType); + TypeRef = World.ItemTypes.Count - 1; } - - SaveBuffer = new BufferedFileWriter(true); } public int TempFlags @@ -821,21 +819,34 @@ namespace Server AddNameProperties(list); } - public BufferedFileWriter SaveBuffer { get; } + public BufferWriter SaveBuffer { get; set; } [CommandProperty(AccessLevel.Counselor)] public Serial Serial { get; } public int TypeRef { get; } - public void Serialize() + public void Serialize(DateTime serializeStart) { + if (Decays && Parent == null && Map != Map.Internal && LastMoved + DecayTime <= serializeStart) + { + World.EnqueueForDecay(this); + } + + SaveBuffer ??= new BufferWriter(true); SaveBuffer.Flush(); Serialize(SaveBuffer); } public virtual void Serialize(IGenericWriter writer) { + // The item is clean, so let's skip + if (_savePosition > -1) + { + writer.Seek(_savePosition, SeekOrigin.Begin); + return; + } + writer.Write(9); // version var flags = SaveFlag.None; @@ -1112,8 +1123,6 @@ namespace Server } } - int IComparable.CompareTo(IEntity other) => other == null ? -1 : Serial.CompareTo(other.Serial); - /// /// Moves the Item to a given and . /// @@ -1518,7 +1527,7 @@ namespace Server public virtual void Delete() { - if (Deleted || !World.OnDelete(this)) + if (Deleted) { return; } @@ -1563,7 +1572,7 @@ namespace Server m_Map = null; } - World.RemoveItem(this); + World.RemoveEntity(this); OnAfterDelete(); diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 7a8b766a9..4679bcca9 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -423,7 +423,7 @@ namespace Server /// /// Base class representing players, npcs, and creatures. /// - public class Mobile : IHued, IComparable, ISerializable, ISpawnable, IPropertyListObject + public class Mobile : IHued, IComparable, ISpawnable, IPropertyListObject { private const int WarmodeCatchCount = 4; // Allow four warmode changes in 0.5 seconds, any more will be delay for two seconds @@ -582,6 +582,9 @@ namespace Server private bool m_YellowHealthbar; + // Position in the save buffer where serialization ends. -1 if dirty + private int _savePosition = -1; + public Mobile(Serial serial) { m_Region = Map.Internal.DefaultRegion; @@ -592,36 +595,32 @@ namespace Server DamageEntries = new List(); var ourType = GetType(); - TypeRef = World.m_MobileTypes.IndexOf(ourType); + TypeRef = World.MobileTypes.IndexOf(ourType); if (TypeRef == -1) { - World.m_MobileTypes.Add(ourType); - TypeRef = World.m_MobileTypes.Count - 1; + World.MobileTypes.Add(ourType); + TypeRef = World.MobileTypes.Count - 1; } - - SaveBuffer = new BufferedFileWriter(true); } public Mobile() { m_Region = Map.Internal.DefaultRegion; - Serial = Serial.NewMobile; + Serial = World.NewMobile; DefaultMobileInit(); - World.AddMobile(this); + World.AddEntity(this); var ourType = GetType(); - TypeRef = World.m_MobileTypes.IndexOf(ourType); + TypeRef = World.MobileTypes.IndexOf(ourType); if (TypeRef == -1) { - World.m_MobileTypes.Add(ourType); - TypeRef = World.m_MobileTypes.Count - 1; + World.MobileTypes.Add(ourType); + TypeRef = World.MobileTypes.Count - 1; } - - SaveBuffer = new BufferedFileWriter(true); } public static bool DragEffects { get; set; } = true; @@ -2628,21 +2627,29 @@ namespace Server AddNameProperties(list); } - public BufferedFileWriter SaveBuffer { get; } + public BufferWriter SaveBuffer { get; set; } [CommandProperty(AccessLevel.Counselor)] public Serial Serial { get; } public int TypeRef { get; } - public void Serialize() + public void Serialize(DateTime serializeStart) { + SaveBuffer ??= new BufferWriter(true); SaveBuffer.Flush(); Serialize(SaveBuffer); } public virtual void Serialize(IGenericWriter writer) { + // The item is clean, so let's skip + if (_savePosition > -1) + { + writer.Seek(_savePosition, SeekOrigin.Begin); + return; + } + writer.Write(32); // version writer.WriteDeltaTime(LastStrGain); @@ -2782,11 +2789,6 @@ namespace Server return; } - if (!World.OnDelete(this)) - { - return; - } - if (m_NetState != null) { m_NetState.CancelAllTrades(); @@ -2830,7 +2832,7 @@ namespace Server m_FacialHair = null; m_MountItem = null; - World.RemoveMobile(this); + World.RemoveEntity(this); OnAfterDelete(); @@ -3548,8 +3550,6 @@ namespace Server { } - int IComparable.CompareTo(IEntity other) => other == null ? -1 : Serial.CompareTo(other.Serial); - public virtual bool InRange(Point2D p, int range) => p.m_X >= Location.m_X - range && p.m_X <= Location.m_X + range @@ -9309,6 +9309,8 @@ namespace Server public bool RemoveStatMod(string name) { + StatMods ??= new List(); + for (var i = 0; i < StatMods.Count; ++i) { var check = StatMods[i]; @@ -9327,6 +9329,8 @@ namespace Server public StatMod GetStatMod(string name) { + StatMods ??= new List(); + for (var i = 0; i < StatMods.Count; ++i) { var check = StatMods[i]; @@ -9342,6 +9346,8 @@ namespace Server public void AddStatMod(StatMod mod) { + StatMods ??= new List(); + for (var i = 0; i < StatMods.Count; ++i) { var check = StatMods[i]; @@ -9388,6 +9394,8 @@ namespace Server { var offset = 0; + StatMods ??= new List(); + for (var i = 0; i < StatMods.Count; ++i) { var mod = StatMods[i]; diff --git a/Projects/Server/Network/Packets/OutgoingAccountPackets.cs b/Projects/Server/Network/Packets/OutgoingAccountPackets.cs index ff0d167e4..41cf43033 100644 --- a/Projects/Server/Network/Packets/OutgoingAccountPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingAccountPackets.cs @@ -374,7 +374,7 @@ namespace Server.Network writer.Write(ci.X); writer.Write(ci.Y); writer.Write(ci.Z); - writer.Write(ci.Map.MapID); + writer.Write(ci.Map?.MapID ?? 0); writer.Write(ci.Description); writer.Write(0); } diff --git a/Projects/Server/Persistence/BinaryMemoryWriter.cs b/Projects/Server/Persistence/BinaryMemoryWriter.cs deleted file mode 100644 index 929afe426..000000000 --- a/Projects/Server/Persistence/BinaryMemoryWriter.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.IO; - -namespace Server -{ - public sealed class BinaryMemoryWriter : BinaryFileWriter - { - private static byte[] indexBuffer; - private readonly MemoryStream stream; - - public BinaryMemoryWriter() - : base(new MemoryStream(512), true) => - stream = UnderlyingStream as MemoryStream; - - protected override int BufferSize => 512; - - public int CommitTo( - SequentialFileWriterStream dataFile, SequentialFileWriterStream indexFile, int typeCode, uint serial - ) - { - Flush(); - - var buffer = stream.GetBuffer(); - var length = (int)stream.Length; - - var position = dataFile.Position; - - dataFile.Write(buffer, 0, length); - - indexBuffer ??= new byte[20]; - - indexBuffer[0] = (byte)typeCode; - indexBuffer[1] = (byte)(typeCode >> 8); - indexBuffer[2] = (byte)(typeCode >> 16); - indexBuffer[3] = (byte)(typeCode >> 24); - - indexBuffer[4] = (byte)serial; - indexBuffer[5] = (byte)(serial >> 8); - indexBuffer[6] = (byte)(serial >> 16); - indexBuffer[7] = (byte)(serial >> 24); - - indexBuffer[8] = (byte)position; - indexBuffer[9] = (byte)(position >> 8); - indexBuffer[10] = (byte)(position >> 16); - indexBuffer[11] = (byte)(position >> 24); - indexBuffer[12] = (byte)(position >> 32); - indexBuffer[13] = (byte)(position >> 40); - indexBuffer[14] = (byte)(position >> 48); - indexBuffer[15] = (byte)(position >> 56); - - indexBuffer[16] = (byte)length; - indexBuffer[17] = (byte)(length >> 8); - indexBuffer[18] = (byte)(length >> 16); - indexBuffer[19] = (byte)(length >> 24); - - indexFile.Write(indexBuffer, 0, indexBuffer.Length); - - stream.SetLength(0); - - return length; - } - } -} diff --git a/Projects/Server/Persistence/DualSaveStrategy.cs b/Projects/Server/Persistence/DualSaveStrategy.cs deleted file mode 100644 index bbb412c2a..000000000 --- a/Projects/Server/Persistence/DualSaveStrategy.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Threading; - -namespace Server -{ - public sealed class DualSaveStrategy : StandardSaveStrategy - { - public override string Name => "Dual"; - - public override void Save(bool permitBackgroundWrite) - { - PermitBackgroundWrite = permitBackgroundWrite; - - var saveThread = new Thread(SaveItems); - - saveThread.Name = "Item Save Subset"; - saveThread.Start(); - - SaveMobiles(); - SaveGuilds(); - - saveThread.Join(); - - if (permitBackgroundWrite && UseSequentialWriters - ) // If we're permitted to write in the background, but we don't anyways, then notify. - { - World.NotifyDiskWriteComplete(); - } - } - } -} diff --git a/Projects/Server/Persistence/DynamicSaveStrategy.cs b/Projects/Server/Persistence/DynamicSaveStrategy.cs deleted file mode 100644 index 89a971eb9..000000000 --- a/Projects/Server/Persistence/DynamicSaveStrategy.cs +++ /dev/null @@ -1,287 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading.Tasks; -using Server.Guilds; - -namespace Server -{ - public sealed class DynamicSaveStrategy : SaveStrategy - { - private readonly ConcurrentBag _decayBag; - private SequentialFileWriterStream _guildData, _guildIndex; - private readonly BlockingCollection _guildThreadWriters; - - private SequentialFileWriterStream _itemData, _itemIndex; - - private readonly BlockingCollection _itemThreadWriters; - - private SequentialFileWriterStream _mobileData, _mobileIndex; - private readonly BlockingCollection _mobileThreadWriters; - - public DynamicSaveStrategy() - { - _decayBag = new ConcurrentBag(); - _itemThreadWriters = new BlockingCollection(); - _mobileThreadWriters = new BlockingCollection(); - _guildThreadWriters = new BlockingCollection(); - } - - public override string Name => "Dynamic"; - - public override void Save(bool permitBackgroundWrite) - { - OpenFiles(); - - var saveTasks = new Task[3]; - - saveTasks[0] = SaveItems(); - saveTasks[1] = SaveMobiles(); - saveTasks[2] = SaveGuilds(); - - SaveTypeDatabases(); - - if (permitBackgroundWrite) - { -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - // This option makes it finish the writing to disk in the background, continuing even after Save() returns. - Task.Factory.ContinueWhenAll( - saveTasks, - _ => - { - CloseFiles(); - - World.NotifyDiskWriteComplete(); - } - ); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - } - else - { - Task.WaitAll(saveTasks); // Waits for the completion of all of the tasks(committing to disk) - CloseFiles(); - } - } - - private Task StartCommitTask( - BlockingCollection threadWriter, SequentialFileWriterStream data, - SequentialFileWriterStream index - ) - { -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - var commitTask = Task.Factory.StartNew( - () => - { - while (!threadWriter.IsCompleted) - { - QueuedMemoryWriter writer; - - try - { - writer = threadWriter.Take(); - } - catch (InvalidOperationException) - { - // Per MSDN, it's fine if we're here, successful completion of adding can rarely put us into this state. - break; - } - - writer.CommitTo(data, index); - } - } - ); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - - return commitTask; - } - - private Task SaveItems() - { - // Start the blocking consumer; this runs in background. - var commitTask = StartCommitTask(_itemThreadWriters, _itemData, _itemIndex); - - IEnumerable items = World.Items.Values; - - // Start the producer. - Parallel.ForEach( - items, - () => new QueuedMemoryWriter(), - (item, state, writer) => - { - var startPosition = writer.Position; - - item.Serialize(writer); - - var size = (int)(writer.Position - startPosition); - - writer.QueueForIndex(item, size); - - if (item.Decays && item.Parent == null && item.Map != Map.Internal && - DateTime.UtcNow > item.LastMoved + item.DecayTime) - { - _decayBag.Add(item); - } - - return writer; - }, - writer => - { - writer.Flush(); - - _itemThreadWriters.Add(writer); - } - ); - - _itemThreadWriters.CompleteAdding(); // We only get here after the Parallel.ForEach completes. Lets our task - - return commitTask; - } - - private Task SaveMobiles() - { - // Start the blocking consumer; this runs in background. - var commitTask = StartCommitTask(_mobileThreadWriters, _mobileData, _mobileIndex); - - IEnumerable mobiles = World.Mobiles.Values; - - // Start the producer. - Parallel.ForEach( - mobiles, - () => new QueuedMemoryWriter(), - (mobile, state, writer) => - { - var startPosition = writer.Position; - - mobile.Serialize(writer); - - var size = (int)(writer.Position - startPosition); - - writer.QueueForIndex(mobile, size); - - return writer; - }, - writer => - { - writer.Flush(); - - _mobileThreadWriters.Add(writer); - } - ); - - _mobileThreadWriters - .CompleteAdding(); // We only get here after the Parallel.ForEach completes. Lets our task tell the consumer that we're done - - return commitTask; - } - - private Task SaveGuilds() - { - // Start the blocking consumer; this runs in background. - var commitTask = StartCommitTask(_guildThreadWriters, _guildData, _guildIndex); - - IEnumerable guilds = BaseGuild.List.Values; - - // Start the producer. - Parallel.ForEach( - guilds, - () => new QueuedMemoryWriter(), - (guild, state, writer) => - { - var startPosition = writer.Position; - - guild.Serialize(writer); - - var size = (int)(writer.Position - startPosition); - - writer.QueueForIndex(guild, size); - - return writer; - }, - writer => - { - writer.Flush(); - - _guildThreadWriters.Add(writer); - } - ); - - _guildThreadWriters.CompleteAdding(); // We only get here after the Parallel.ForEach completes. Lets our task - - return commitTask; - } - - public override void ProcessDecay() - { - while (_decayBag.TryTake(out var item)) - { - if (item.OnDecay()) - { - item.Delete(); - } - } - } - - private void OpenFiles() - { - _itemData = new SequentialFileWriterStream(World.ItemDataPath); - _itemIndex = new SequentialFileWriterStream(World.ItemIndexPath); - - _mobileData = new SequentialFileWriterStream(World.MobileDataPath); - _mobileIndex = new SequentialFileWriterStream(World.MobileIndexPath); - - _guildData = new SequentialFileWriterStream(World.GuildDataPath); - _guildIndex = new SequentialFileWriterStream(World.GuildIndexPath); - - WriteCount(_itemIndex, World.Items.Count); - WriteCount(_mobileIndex, World.Mobiles.Count); - WriteCount(_guildIndex, BaseGuild.List.Count); - } - - private void CloseFiles() - { - _itemData.Close(); - _itemIndex.Close(); - - _mobileData.Close(); - _mobileIndex.Close(); - - _guildData.Close(); - _guildIndex.Close(); - } - - private void WriteCount(SequentialFileWriterStream indexFile, int count) - { - // Equiv to GenericWriter.Write( (int)count ); - var buffer = new byte[4]; - - buffer[0] = (byte)count; - buffer[1] = (byte)(count >> 8); - buffer[2] = (byte)(count >> 16); - buffer[3] = (byte)(count >> 24); - - indexFile.Write(buffer, 0, buffer.Length); - } - - private void SaveTypeDatabases() - { - SaveTypeDatabase(World.ItemTypesPath, World.m_ItemTypes); - SaveTypeDatabase(World.MobileTypesPath, World.m_MobileTypes); - } - - private void SaveTypeDatabase(string path, List types) - { - var bfw = new BinaryFileWriter(path, false); - - bfw.Write(types.Count); - - foreach (var type in types) - { - bfw.Write(type.FullName); - } - - bfw.Flush(); - - bfw.Close(); - } - } -} diff --git a/Projects/Server/Persistence/FileOperations.cs b/Projects/Server/Persistence/FileOperations.cs deleted file mode 100644 index b42fd2151..000000000 --- a/Projects/Server/Persistence/FileOperations.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System.IO; - -#if WINDOWS -using System; -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; -#endif - -namespace Server -{ - public static class FileOperations - { - public const int KB = 1024; - public const int MB = 1024 * KB; - - public static int BufferSize { get; set; } = 1 * MB; - - public static int Concurrency { get; set; } = 1; - -#if WINDOWS - public static bool Unbuffered { get; set; } = true; -#endif - - public static bool AreSynchronous => Concurrency < 1; - - public static FileStream OpenSequentialStream(string path, FileMode mode, FileAccess access, FileShare share) - { - var options = FileOptions.SequentialScan; - - if (Concurrency > 0) - { - options |= FileOptions.Asynchronous; - } - -#if !WINDOWS - return new FileStream(path, mode, access, share, BufferSize, options); -#else - if (Unbuffered) - options |= NoBuffering; - else - return new FileStream(path, mode, access, share, BufferSize, options); - - var fileHandle = - UnsafeNativeMethods.CreateFile(path, (int)access, share, IntPtr.Zero, mode, (int)options, IntPtr.Zero); - - if (fileHandle.IsInvalid) throw new IOException(); - - return new UnbufferedFileStream(fileHandle, access, BufferSize, Concurrency > 0); -#endif - } - -#if WINDOWS - private class UnbufferedFileStream : FileStream - { - private readonly SafeFileHandle fileHandle; - - public UnbufferedFileStream(SafeFileHandle fileHandle, FileAccess access, int bufferSize, bool isAsync) - : base(fileHandle, access, bufferSize, isAsync) => - this.fileHandle = fileHandle; - - public override void Write(byte[] array, int offset, int count) - { - base.Write(array, offset, BufferSize); - } - - public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback userCallback, - object stateObject) => - base.BeginWrite(array, offset, BufferSize, userCallback, stateObject); - - protected override void Dispose(bool disposing) - { - if (!fileHandle.IsClosed) fileHandle.Close(); - - base.Dispose(disposing); - } - } -#endif - -#if WINDOWS - private const FileOptions NoBuffering = (FileOptions)0x20000000; - - internal static class UnsafeNativeMethods - { - [DllImport("Kernel32", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, FileShare dwShareMode, - IntPtr securityAttrs, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); - } -#endif - } -} diff --git a/Projects/Server/Persistence/FileQueue.cs b/Projects/Server/Persistence/FileQueue.cs deleted file mode 100644 index 04a2cb622..000000000 --- a/Projects/Server/Persistence/FileQueue.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Threading; - -namespace Server -{ - public delegate void FileCommitCallback(FileQueue.Chunk chunk); - - public sealed class FileQueue : IDisposable - { - private static readonly int bufferSize; - - private readonly Chunk[] active; - - private readonly FileCommitCallback callback; - - private readonly Queue pending; - - private readonly object syncRoot; - private int activeCount; - private Page buffered; - - private ManualResetEvent idle; - - static FileQueue() => bufferSize = FileOperations.BufferSize; - - public FileQueue(int concurrentWrites, FileCommitCallback callback) - { - if (concurrentWrites < 1) - { - throw new ArgumentOutOfRangeException(nameof(concurrentWrites)); - } - - if (bufferSize < 1) -#pragma warning disable CA2208 // Instantiate argument exceptions correctly - { - throw new ArgumentOutOfRangeException(nameof(FileOperations.BufferSize)); - } -#pragma warning restore CA2208 // Instantiate argument exceptions correctly - - syncRoot = new object(); - - active = new Chunk[concurrentWrites]; - pending = new Queue(); - - this.callback = callback; - - idle = new ManualResetEvent(true); - } - - public long Position { get; private set; } - - public void Dispose() - { - if (idle != null) - { - idle.Close(); - idle = null; - } - } - - private void Append(Page page) - { - lock (syncRoot) - { - if (activeCount == 0) - { - idle.Reset(); - } - - ++activeCount; - - for (var slot = 0; slot < active.Length; ++slot) - { - if (active[slot] == null) - { - active[slot] = new Chunk(this, slot, page.buffer, 0, page.length); - - callback(active[slot]); - - return; - } - } - - pending.Enqueue(page); - } - } - - public void Flush() - { - if (buffered.buffer != null) - { - Append(buffered); - - buffered.buffer = null; - buffered.length = 0; - } - - /*lock ( syncRoot ) { - if (pending.Count > 0 ) { - idle.Reset(); - } - - for ( int slot = 0; slot < active.Length && pending.Count > 0; ++slot ) { - if (active[slot] == null ) { - Page page = pending.Dequeue(); - - active[slot] = new Chunk( this, slot, page.buffer, 0, page.length ); - - ++activeCount; - - callback( active[slot] ); - } - } - }*/ - - idle.WaitOne(); - } - - private void Commit(Chunk chunk, int slot) - { - if (slot < 0 || slot >= active.Length) - { - throw new ArgumentOutOfRangeException(nameof(slot)); - } - - lock (syncRoot) - { - if (active[slot] != chunk) - { - throw new ArgumentException("active slot is not the current chunk"); - } - - ArrayPool.Shared.Return(chunk.Buffer); - - if (pending.Count > 0) - { - var page = pending.Dequeue(); - - active[slot] = new Chunk(this, slot, page.buffer, 0, page.length); - - callback(active[slot]); - } - else - { - active[slot] = null; - } - - --activeCount; - - if (activeCount == 0) - { - idle.Set(); - } - } - } - - public void Enqueue(byte[] buffer, int offset, int size) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (size < 0) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - - if (buffer.Length - offset < size) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - Position += size; - - while (size > 0) - { - buffered.buffer ??= ArrayPool.Shared.Rent(bufferSize); - - var page = buffered.buffer; // buffer page - var pageSpace = page.Length - buffered.length; // available bytes in page - var byteCount = size > pageSpace ? pageSpace : size; // how many bytes we can copy over - - Buffer.BlockCopy(buffer, offset, page, buffered.length, byteCount); - - buffered.length += byteCount; - offset += byteCount; - size -= byteCount; - - if (buffered.length == page.Length) - { - // page full - Append(buffered); - - buffered.buffer = null; - buffered.length = 0; - } - } - } - - public sealed class Chunk - { - private readonly FileQueue m_Owner; - private readonly int m_Slot; - - public Chunk(FileQueue owner, int slot, byte[] buffer, int offset, int size) - { - m_Owner = owner; - m_Slot = slot; - - Buffer = buffer; - Offset = offset; - Size = size; - } - - public byte[] Buffer { get; } - - public int Offset { get; } - - public int Size { get; } - - public void Commit() - { - m_Owner.Commit(this, m_Slot); - } - } - - private struct Page - { - public byte[] buffer; - public int length; - } - } -} diff --git a/Projects/Server/Persistence/ParallelSaveStrategy.cs b/Projects/Server/Persistence/ParallelSaveStrategy.cs deleted file mode 100644 index 14d076d47..000000000 --- a/Projects/Server/Persistence/ParallelSaveStrategy.cs +++ /dev/null @@ -1,370 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Threading; -using Server.Guilds; - -namespace Server -{ - public sealed class ParallelSaveStrategy : SaveStrategy, IDisposable - { - private readonly Queue _decayQueue; - - private readonly int processorCount; - - private Consumer[] consumers; - private int cycle; - - private bool disposedValue; // To detect redundant calls - - private bool finished; - private SequentialFileWriterStream guildData, guildIndex; - - private SequentialFileWriterStream itemData, itemIndex; - - private SequentialFileWriterStream mobileData, mobileIndex; - - public ParallelSaveStrategy(int processorCount) - { - this.processorCount = processorCount; - - _decayQueue = new Queue(); - } - - public override string Name => "Parallel"; - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~ParallelSaveStrategy() - // { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - - private int GetThreadCount() => processorCount - 1; - - public override void Save(bool permitBackgroundWrite) - { - OpenFiles(); - - consumers = new Consumer[GetThreadCount()]; - - for (var i = 0; i < consumers.Length; ++i) - { - consumers[i] = new Consumer(this, 256); - } - - IEnumerable collection = new Producer(); - - foreach (var value in collection) - { - while (!Enqueue(value)) - { - if (!Commit()) - { - Thread.Sleep(0); - } - } - } - - finished = true; - - SaveTypeDatabases(); - - WaitHandle.WaitAll( - Array.ConvertAll( - consumers, - input => input.completionEvent - ) - ); - - Commit(); - - CloseFiles(); - } - - public override void ProcessDecay() - { - while (_decayQueue.Count > 0) - { - var item = _decayQueue.Dequeue(); - - if (item.OnDecay()) - { - item.Delete(); - } - } - } - - private void SaveTypeDatabases() - { - SaveTypeDatabase(World.ItemTypesPath, World.m_ItemTypes); - SaveTypeDatabase(World.MobileTypesPath, World.m_MobileTypes); - } - - private void SaveTypeDatabase(string path, List types) - { - var bfw = new BinaryFileWriter(path, false); - - bfw.Write(types.Count); - - foreach (var type in types) - { - bfw.Write(type.FullName); - } - - bfw.Flush(); - - bfw.Close(); - } - - private void OpenFiles() - { - itemData = new SequentialFileWriterStream(World.ItemDataPath); - itemIndex = new SequentialFileWriterStream(World.ItemIndexPath); - - mobileData = new SequentialFileWriterStream(World.MobileDataPath); - mobileIndex = new SequentialFileWriterStream(World.MobileIndexPath); - - guildData = new SequentialFileWriterStream(World.GuildDataPath); - guildIndex = new SequentialFileWriterStream(World.GuildIndexPath); - - WriteCount(itemIndex, World.Items.Count); - WriteCount(mobileIndex, World.Mobiles.Count); - WriteCount(guildIndex, BaseGuild.List.Count); - } - - private void WriteCount(SequentialFileWriterStream indexFile, int count) - { - var buffer = new byte[4]; - - buffer[0] = (byte)count; - buffer[1] = (byte)(count >> 8); - buffer[2] = (byte)(count >> 16); - buffer[3] = (byte)(count >> 24); - - indexFile.Write(buffer, 0, buffer.Length); - } - - private void CloseFiles() - { - itemData.Close(); - itemIndex.Close(); - - mobileData.Close(); - mobileIndex.Close(); - - guildData.Close(); - guildIndex.Close(); - - World.NotifyDiskWriteComplete(); - } - - private void OnSerialized(ConsumableEntry entry) - { - var value = entry.value; - var writer = entry.writer; - - if (value is Item item) - { - Save(item, writer); - } - else if (value is Mobile mob) - { - Save(mob, writer); - } - else if (value is BaseGuild guild) - { - Save(guild, writer); - } - } - - private void Save(Item item, BinaryMemoryWriter writer) - { - writer.CommitTo(itemData, itemIndex, item.TypeRef, item.Serial); - - if (item.Decays && item.Parent == null && item.Map != Map.Internal && - DateTime.UtcNow > item.LastMoved + item.DecayTime) - { - _decayQueue.Enqueue(item); - } - } - - private void Save(Mobile mob, BinaryMemoryWriter writer) - { - writer.CommitTo(mobileData, mobileIndex, mob.TypeRef, mob.Serial); - } - - private void Save(BaseGuild guild, BinaryMemoryWriter writer) - { - writer.CommitTo(guildData, guildIndex, 0, guild.Serial); - } - - private bool Enqueue(ISerializable value) - { - for (var i = 0; i < consumers.Length; ++i) - { - var consumer = consumers[cycle++ % consumers.Length]; - - if (consumer.tail - consumer.head < consumer.buffer.Length) - { - consumer.buffer[consumer.tail % consumer.buffer.Length].value = value; - consumer.tail++; - - return true; - } - } - - return false; - } - - private bool Commit() - { - var committed = false; - - for (var i = 0; i < consumers.Length; ++i) - { - var consumer = consumers[i]; - - while (consumer.head < consumer.done) - { - OnSerialized(consumer.buffer[consumer.head % consumer.buffer.Length]); - consumer.head++; - - committed = true; - } - } - - return committed; - } - - public void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - private sealed class Producer : IEnumerable - { - private readonly IEnumerable guilds; - private readonly IEnumerable items; - private readonly IEnumerable mobiles; - - public Producer() - { - items = World.Items.Values; - mobiles = World.Mobiles.Values; - guilds = BaseGuild.List.Values; - } - - public IEnumerator GetEnumerator() - { - foreach (var item in items) - { - yield return item; - } - - foreach (var mob in mobiles) - { - yield return mob; - } - - foreach (var guild in guilds) - { - yield return guild; - } - } - - IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); - } - - private struct ConsumableEntry - { - public ISerializable value; - public BinaryMemoryWriter writer; - } - - private sealed class Consumer - { - public readonly ConsumableEntry[] buffer; - - public readonly ManualResetEvent completionEvent; - private readonly ParallelSaveStrategy owner; - - private readonly Thread thread; - public int head, done, tail; - - public Consumer(ParallelSaveStrategy owner, int bufferSize) - { - this.owner = owner; - - buffer = new ConsumableEntry[bufferSize]; - - for (var i = 0; i < buffer.Length; ++i) - { - buffer[i].writer = new BinaryMemoryWriter(); - } - - completionEvent = new ManualResetEvent(false); - - thread = new Thread(Processor); - - thread.Name = "Parallel Serialization Thread"; - - thread.Start(); - } - - private void Processor() - { - try - { - while (!owner.finished) - { - Process(); - Thread.Sleep(0); - } - - Process(); - - completionEvent.Set(); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - } - - private void Process() - { - ConsumableEntry entry; - - while (done < tail) - { - entry = buffer[done % buffer.Length]; - - entry.value.Serialize(entry.writer); - - ++done; - } - } - } - } -} diff --git a/Projects/Server/Persistence/Persistence.cs b/Projects/Server/Persistence/Persistence.cs deleted file mode 100644 index 07da3f710..000000000 --- a/Projects/Server/Persistence/Persistence.cs +++ /dev/null @@ -1,126 +0,0 @@ -/************************************************************************* - * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Persistence.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; -using System.IO; - -namespace Server -{ - public static class Persistence - { - public static void Serialize(string path, Action serializer) - { - Serialize(new FileInfo(path), serializer); - } - - public static void Serialize(FileInfo file, Action serializer) - { - file.Refresh(); - - if (file.Directory?.Exists == false) - { - file.Directory.Create(); - } - - if (!file.Exists) - { - file.Create().Close(); - } - - file.Refresh(); - - using var fs = file.OpenWrite(); - var writer = new BinaryFileWriter(fs, true); - - try - { - serializer(writer); - } - finally - { - writer.Flush(); - writer.Close(); - } - } - - public static void Deserialize(string path, Action deserializer) - { - Deserialize(path, deserializer, true); - } - - public static void Deserialize(FileInfo file, Action deserializer) - { - Deserialize(file, deserializer, true); - } - - public static void Deserialize(string path, Action deserializer, bool ensure) - { - Deserialize(new FileInfo(path), deserializer, ensure); - } - - public static void Deserialize(FileInfo file, Action deserializer, bool ensure) - { - file.Refresh(); - - if (file.Directory?.Exists == false) - { - if (!ensure) - { - throw new DirectoryNotFoundException(); - } - - file.Directory.Create(); - } - - if (!file.Exists) - { - if (!ensure) - { - throw new FileNotFoundException - { - Source = file.FullName - }; - } - - file.Create().Close(); - } - - file.Refresh(); - - using var fs = file.OpenRead(); - var reader = new BinaryFileReader(new BinaryReader(fs)); - - try - { - deserializer(reader); - } - catch (EndOfStreamException eos) - { - if (file.Length > 0) - { - Console.WriteLine("[Persistence]: {0}", eos); - } - } - catch (Exception e) - { - Console.WriteLine("[Persistence]: {0}", e); - } - finally - { - reader.Close(); - } - } - } -} diff --git a/Projects/Server/Persistence/QueuedMemoryWriter.cs b/Projects/Server/Persistence/QueuedMemoryWriter.cs deleted file mode 100644 index 5f5a77ad6..000000000 --- a/Projects/Server/Persistence/QueuedMemoryWriter.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -namespace Server -{ - public sealed class QueuedMemoryWriter : BinaryFileWriter - { - private readonly MemoryStream _memStream; - private readonly List _orderedIndexInfo = new List(); - - public QueuedMemoryWriter() - : base(new MemoryStream(1024 * 1024), true) => - _memStream = UnderlyingStream as MemoryStream; - - protected override int BufferSize => 512; - - public void QueueForIndex(ISerializable serializable, int size) - { - IndexInfo info; - - info.size = size; - - info.typeCode = serializable.TypeRef; // For guilds, this will automagically be zero. - info.serial = serializable.Serial; - - _orderedIndexInfo.Add(info); - } - - public void CommitTo(SequentialFileWriterStream dataFile, SequentialFileWriterStream indexFile) - { - Flush(); - - var memLength = (int)_memStream.Position; - - if (memLength > 0) - { - var memBuffer = _memStream.GetBuffer(); - - var actualPosition = dataFile.Position; - - dataFile.Write(memBuffer, 0, memLength); // The buffer contains the data from many items. - - // Console.WriteLine("Writing {0} bytes starting at {1}, with {2} things", memLength, actualPosition, _orderedIndexInfo.Count); - - var indexBuffer = new byte[20]; - - // int indexWritten = _orderedIndexInfo.Count * indexBuffer.Length; - // int totalWritten = memLength + indexWritten - - for (var i = 0; i < _orderedIndexInfo.Count; i++) - { - var info = _orderedIndexInfo[i]; - - indexBuffer[0] = (byte)info.typeCode; - indexBuffer[1] = (byte)(info.typeCode >> 8); - indexBuffer[2] = (byte)(info.typeCode >> 16); - indexBuffer[3] = (byte)(info.typeCode >> 24); - - indexBuffer[4] = (byte)info.serial; - indexBuffer[5] = (byte)(info.serial >> 8); - indexBuffer[6] = (byte)(info.serial >> 16); - indexBuffer[7] = (byte)(info.serial >> 24); - - indexBuffer[8] = (byte)actualPosition; - indexBuffer[9] = (byte)(actualPosition >> 8); - indexBuffer[10] = (byte)(actualPosition >> 16); - indexBuffer[11] = (byte)(actualPosition >> 24); - indexBuffer[12] = (byte)(actualPosition >> 32); - indexBuffer[13] = (byte)(actualPosition >> 40); - indexBuffer[14] = (byte)(actualPosition >> 48); - indexBuffer[15] = (byte)(actualPosition >> 56); - - indexBuffer[16] = (byte)info.size; - indexBuffer[17] = (byte)(info.size >> 8); - indexBuffer[18] = (byte)(info.size >> 16); - indexBuffer[19] = (byte)(info.size >> 24); - - indexFile.Write(indexBuffer, 0, indexBuffer.Length); - - actualPosition += info.size; - } - } - - Close(); // We're done with this writer. - } - - private struct IndexInfo - { - public int size; - public int typeCode; - public uint serial; - } - } -} diff --git a/Projects/Server/Persistence/SaveStrategy.cs b/Projects/Server/Persistence/SaveStrategy.cs deleted file mode 100644 index 287f017f3..000000000 --- a/Projects/Server/Persistence/SaveStrategy.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Server -{ - public abstract class SaveStrategy - { - public abstract string Name { get; } - - public static SaveStrategy Acquire() - { - if (Core.MultiProcessor) - { - var processorCount = Core.ProcessorCount; - - if (processorCount > 2) - { - return - new DualSaveStrategy(); // return new DynamicSaveStrategy(); (4.0 or return new ParallelSaveStrategy(processorCount); (2.0) - } - - return new DualSaveStrategy(); - } - - return new StandardSaveStrategy(); - } - - public abstract void Save(bool permitBackgroundWrite); - - public abstract void ProcessDecay(); - } -} diff --git a/Projects/Server/Persistence/SequentialFileWriterStream.cs b/Projects/Server/Persistence/SequentialFileWriterStream.cs deleted file mode 100644 index 2cf84499d..000000000 --- a/Projects/Server/Persistence/SequentialFileWriterStream.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.IO; - -namespace Server -{ - public sealed class SequentialFileWriterStream : Stream - { - private FileQueue fileQueue; - private FileStream fileStream; - - private AsyncCallback writeCallback; - - public SequentialFileWriterStream(string path) - { - if (path == null) - { - throw new ArgumentNullException(nameof(path)); - } - - fileStream = FileOperations.OpenSequentialStream(path, FileMode.Create, FileAccess.Write, FileShare.None); - - fileQueue = new FileQueue( - Math.Max(FileOperations.Concurrency, 1), - FileCallback - ); - } - - public override long Position - { - get => fileQueue.Position; - set => throw new InvalidOperationException(); - } - - public override bool CanRead => false; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override long Length => Position; - - private void FileCallback(FileQueue.Chunk chunk) - { - if (FileOperations.AreSynchronous) - { - fileStream.Write(chunk.Buffer, chunk.Offset, chunk.Size); - - chunk.Commit(); - } - else - { - writeCallback ??= OnWrite; - - fileStream.BeginWrite(chunk.Buffer, chunk.Offset, chunk.Size, writeCallback, chunk); - } - } - - private void OnWrite(IAsyncResult asyncResult) - { - var chunk = asyncResult.AsyncState as FileQueue.Chunk; - - fileStream.EndWrite(asyncResult); - - chunk?.Commit(); - } - - public override void Write(byte[] buffer, int offset, int size) - { - fileQueue.Enqueue(buffer, offset, size); - } - - public override void Flush() - { - fileQueue.Flush(); - fileStream.Flush(); - } - - protected override void Dispose(bool disposing) - { - if (fileStream != null) - { - Flush(); - - fileQueue.Dispose(); - fileQueue = null; - - fileStream.Close(); - fileStream = null; - } - - base.Dispose(disposing); - } - - public override int Read(byte[] buffer, int offset, int count) => throw new InvalidOperationException(); - - public override long Seek(long offset, SeekOrigin origin) => throw new InvalidOperationException(); - - public override void SetLength(long value) - { - fileStream.SetLength(value); - } - } -} diff --git a/Projects/Server/Persistence/StandardSaveStrategy.cs b/Projects/Server/Persistence/StandardSaveStrategy.cs deleted file mode 100644 index 5e016edf1..000000000 --- a/Projects/Server/Persistence/StandardSaveStrategy.cs +++ /dev/null @@ -1,238 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Server.Guilds; - -namespace Server -{ - public class StandardSaveStrategy : SaveStrategy - { - public enum SaveOption - { - Normal, - Threaded - } - - private readonly Queue _decayQueue; - - public StandardSaveStrategy() => _decayQueue = new Queue(); - - // TODO: Move to configuration - public static SaveOption SaveType => SaveOption.Normal; - - public override string Name => "Standard"; - - protected bool PermitBackgroundWrite { get; set; } - - protected bool UseSequentialWriters => SaveType == SaveOption.Normal || !PermitBackgroundWrite; - - public override void Save(bool permitBackgroundWrite) - { - PermitBackgroundWrite = permitBackgroundWrite; - -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.WaitAll( - Task.Factory.StartNew(SaveMobiles), - Task.Factory.StartNew(SaveItems), - Task.Factory.StartNew(SaveGuilds) - ); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - - if (permitBackgroundWrite && UseSequentialWriters - ) // If we're permitted to write in the background, but we don't anyways, then notify. - { - World.NotifyDiskWriteComplete(); - } - } - - protected void SaveMobiles() - { - var mobiles = World.Mobiles; - - IGenericWriter idx; - IGenericWriter tdb; - IGenericWriter bin; - - if (UseSequentialWriters) - { - idx = new BinaryFileWriter(World.MobileIndexPath, false); - tdb = new BinaryFileWriter(World.MobileTypesPath, false); - bin = new BinaryFileWriter(World.MobileDataPath, true); - } - else - { - idx = new AsyncWriter(World.MobileIndexPath, false); - tdb = new AsyncWriter(World.MobileTypesPath, false); - bin = new AsyncWriter(World.MobileDataPath, true); - } -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.Factory.StartNew( - () => - { - tdb.Write(World.m_MobileTypes.Count); - - for (var i = 0; i < World.m_MobileTypes.Count; ++i) - { - tdb.Write(World.m_MobileTypes[i].FullName); - } - - tdb.Close(); - } - ); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - - Parallel.ForEach(mobiles.Values, mobile => mobile.Serialize()); -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.Factory.StartNew( - () => - { - idx.Write(mobiles.Count); - foreach (var m in mobiles.Values) - { - var start = bin.Position; - - idx.Write(m.TypeRef); - idx.Write(m.Serial); - idx.Write(start); - idx.Write((int)m.SaveBuffer.Position); - - m.SaveBuffer.WriteTo(bin); - m.FreeCache(); - } - - idx.Close(); - bin.Close(); - } - ); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - } - - protected void SaveItems() - { - var items = World.Items; - - IGenericWriter idx; - IGenericWriter tdb; - IGenericWriter bin; - - if (UseSequentialWriters) - { - idx = new BinaryFileWriter(World.ItemIndexPath, false); - tdb = new BinaryFileWriter(World.ItemTypesPath, false); - bin = new BinaryFileWriter(World.ItemDataPath, true); - } - else - { - idx = new AsyncWriter(World.ItemIndexPath, false); - tdb = new AsyncWriter(World.ItemTypesPath, false); - bin = new AsyncWriter(World.ItemDataPath, true); - } - -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.Factory.StartNew( - () => - { - tdb.Write(World.m_ItemTypes.Count); - - for (var i = 0; i < World.m_ItemTypes.Count; ++i) - { - tdb.Write(World.m_ItemTypes[i].FullName); - } - - tdb.Close(); - } - ); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - Parallel.ForEach(items.Values, item => item.Serialize()); - - idx.Write(items.Count); - - var n = DateTime.UtcNow; - -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.Factory.StartNew( - () => - { - foreach (var item in items.Values) - { - if (item.Decays && item.Parent == null && item.Map != Map.Internal && - item.LastMoved + item.DecayTime <= n) - { - Console.WriteLine($"Decay Item {item.Name ?? item.DefaultName} ({item.GetType().FullName})"); - _decayQueue.Enqueue(item); - } - - var start = bin.Position; - - idx.Write(item.TypeRef); - idx.Write(item.Serial); - idx.Write(start); - idx.Write((int)item.SaveBuffer.Position); - - item.SaveBuffer.WriteTo(bin); - item.FreeCache(); - } - - idx.Close(); - bin.Close(); - } - ); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - } - - protected void SaveGuilds() - { - IGenericWriter idx; - IGenericWriter bin; - - if (UseSequentialWriters) - { - idx = new BinaryFileWriter(World.GuildIndexPath, false); - bin = new BinaryFileWriter(World.GuildDataPath, true); - } - else - { - idx = new AsyncWriter(World.GuildIndexPath, false); - bin = new AsyncWriter(World.GuildDataPath, true); - } - - Parallel.ForEach(BaseGuild.List.Values, guild => guild.Serialize()); - - idx.Write(BaseGuild.List.Count); -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.Factory.StartNew( - () => - { - foreach (var guild in BaseGuild.List.Values) - { - var start = bin.Position; - - idx.Write(0); // guilds have no typeid - idx.Write(guild.Serial); - idx.Write(start); - idx.Write((int)guild.SaveBuffer.Position); - - guild.SaveBuffer.WriteTo(bin); - } - - idx.Close(); - bin.Close(); - } - ); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - } - - public override void ProcessDecay() - { - while (_decayQueue.Count > 0) - { - var item = _decayQueue.Dequeue(); - - if (item.OnDecay()) - { - item.Delete(); - } - } - } - } -} diff --git a/Projects/Server/Serial.cs b/Projects/Server/Serial.cs index fa93c2bc8..5decdb46c 100644 --- a/Projects/Server/Serial.cs +++ b/Projects/Server/Serial.cs @@ -7,41 +7,13 @@ namespace Server public static readonly Serial MinusOne = new Serial(0xFFFFFFFF); public static readonly Serial Zero = new Serial(0); - public static Serial LastMobile { get; private set; } = Zero; - - public static Serial LastItem { get; private set; } = 0x40000000; - - public static Serial NewMobile - { - get - { - while (World.FindMobile(LastMobile += 1) != null) - { - } - - return LastMobile; - } - } - - public static Serial NewItem - { - get - { - while (World.FindItem(LastItem = LastItem + 1) != null) - { - } - - return LastItem; - } - } - private Serial(uint serial) => Value = serial; public uint Value { get; } - public bool IsMobile => Value > 0 && Value < 0x40000000; + public bool IsMobile => Value > 0 && Value < World.ItemOffset; - public bool IsItem => Value >= 0x40000000 && Value < 0x80000000; + public bool IsItem => Value >= World.ItemOffset && Value < World.MaxItemSerial; public bool IsValid => Value > 0; diff --git a/Projects/Server/Serialization/AsyncWriter.cs b/Projects/Server/Serialization/AsyncWriter.cs deleted file mode 100644 index b7550f845..000000000 --- a/Projects/Server/Serialization/AsyncWriter.cs +++ /dev/null @@ -1,718 +0,0 @@ -/************************************************************************* - * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: AsyncWriter.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; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Threading; -using Server.Guilds; - -namespace Server -{ - public sealed class AsyncWriter : IGenericWriter - { - private readonly int m_BufferSize; - private readonly FileStream m_File; - private readonly bool m_PrefixStrings; - - private readonly Queue m_WriteQueue; - private BinaryWriter m_Bin; - private bool m_Closed; - - private long m_LastPos; - - private MemoryStream m_Mem; - private Thread m_WorkerThread; - - public AsyncWriter(string filename, bool prefix) - : this(filename, 1048576, prefix) // 1 mb buffer - { - } - - public AsyncWriter(string filename, int buffSize, bool prefix) - { - m_PrefixStrings = prefix; - m_Closed = false; - m_WriteQueue = new Queue(); - m_BufferSize = buffSize; - - m_File = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - m_Mem = new MemoryStream(m_BufferSize + 1024); - m_Bin = new BinaryWriter(m_Mem, Utility.UTF8WithEncoding); - } - - public static int ThreadCount { get; private set; } - - public MemoryStream MemStream - { - get => m_Mem; - set - { - if (m_Mem.Length > 0) - { - Enqueue(m_Mem); - } - - m_Mem = value; - m_Bin = new BinaryWriter(m_Mem, Utility.UTF8WithEncoding); - m_LastPos = 0; - Position = m_Mem.Length; - m_Mem.Seek(0, SeekOrigin.End); - } - } - - public long Position { get; private set; } - - public void Close() - { - Enqueue(m_Mem); - m_Closed = true; - } - - public void Write(IPAddress value) - { - m_Bin.Write(Utility.GetLongAddressValue(value)); - OnWrite(); - } - - public void Write(string value) - { - if (m_PrefixStrings) - { - if (value == null) - { - m_Bin.Write((byte)0); - } - else - { - m_Bin.Write((byte)1); - m_Bin.Write(value); - } - } - else - { - m_Bin.Write(value); - } - - OnWrite(); - } - - public void WriteDeltaTime(DateTime value) - { - var ticks = value.Ticks; - var now = DateTime.UtcNow.Ticks; - - TimeSpan d; - - try - { - d = new TimeSpan(ticks - now); - } - catch - { - d = TimeSpan.MaxValue; - } - - Write(d); - } - - public void Write(DateTime value) - { - m_Bin.Write(value.Ticks); - OnWrite(); - } - - public void Write(DateTimeOffset value) - { - m_Bin.Write(value.Ticks); - m_Bin.Write(value.Offset.Ticks); - OnWrite(); - } - - public void Write(TimeSpan value) - { - m_Bin.Write(value.Ticks); - OnWrite(); - } - - public void Write(decimal value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(long value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(ulong value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void WriteEncodedInt(int value) - { - var v = (uint)value; - - while (v >= 0x80) - { - m_Bin.Write((byte)(v | 0x80)); - v >>= 7; - } - - m_Bin.Write((byte)v); - OnWrite(); - } - - public void Write(int value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(uint value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(short value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(ushort value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(double value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(float value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(char value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(byte value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(byte[] value) - { - Write(value, value.Length); - } - - public void Write(byte[] value, int length) - { - m_Bin.Write(value, 0, length); - OnWrite(); - } - - public void Write(sbyte value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(bool value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(Point3D value) - { - Write(value.m_X); - Write(value.m_Y); - Write(value.m_Z); - } - - public void Write(Point2D value) - { - Write(value.m_X); - Write(value.m_Y); - } - - public void Write(Rectangle2D value) - { - Write(value.Start); - Write(value.End); - } - - public void Write(Rectangle3D value) - { - Write(value.Start); - Write(value.End); - } - - public void Write(Map value) - { - if (value != null) - { - Write((byte)value.MapIndex); - } - else - { - Write((byte)0xFF); - } - } - - public void Write(Race value) - { - if (value != null) - { - Write((byte)value.RaceIndex); - } - else - { - Write((byte)0xFF); - } - } - - public void WriteEntity(IEntity value) - { - if (value?.Deleted != false) - { - Write(Serial.MinusOne); - } - else - { - Write(value.Serial); - } - } - - public void Write(Item value) - { - if (value?.Deleted != false) - { - Write(Serial.MinusOne); - } - else - { - Write(value.Serial); - } - } - - public void Write(Mobile value) - { - if (value?.Deleted != false) - { - Write(Serial.MinusOne); - } - else - { - Write(value.Serial); - } - } - - public void Write(BaseGuild value) - { - if (value == null) - { - Write(0); - } - else - { - Write(value.Serial); - } - } - - public void WriteItem(T value) where T : Item - { - Write(value); - } - - public void WriteMobile(T value) where T : Mobile - { - Write(value); - } - - public void WriteGuild(T value) where T : BaseGuild - { - Write(value); - } - - public void Write(List list) - { - WriteItemList(list); - } - - public void Write(List list, bool tidy) - { - WriteItemList(list, tidy); - } - - public void WriteItemList(List list) where T : Item - { - WriteItemList(list, false); - } - - public void WriteItemList(List list, bool tidy) where T : Item - { - if (tidy) - { - for (var i = 0; i < list.Count;) - { - if (list[i].Deleted) - { - list.RemoveAt(i); - } - else - { - ++i; - } - } - } - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - Write(list[i]); - } - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) - { - set.RemoveWhere(item => item.Deleted); - } - - Write(set.Count); - - foreach (var item in set) - { - Write(item); - } - } - - public void WriteItemSet(HashSet set) where T : Item - { - WriteItemSet(set, false); - } - - public void WriteItemSet(HashSet set, bool tidy) where T : Item - { - if (tidy) - { - set.RemoveWhere(item => item.Deleted); - } - - Write(set.Count); - - foreach (var item in set) - { - Write(item); - } - } - - public void Write(List list) - { - Write(list, false); - } - - public void Write(List list, bool tidy) - { - if (tidy) - { - for (var i = 0; i < list.Count;) - { - if (list[i].Deleted) - { - list.RemoveAt(i); - } - else - { - ++i; - } - } - } - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - Write(list[i]); - } - } - - public void WriteMobileList(List list) where T : Mobile - { - WriteMobileList(list, false); - } - - public void WriteMobileList(List list, bool tidy) where T : Mobile - { - if (tidy) - { - for (var i = 0; i < list.Count;) - { - if (list[i].Deleted) - { - list.RemoveAt(i); - } - else - { - ++i; - } - } - } - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - Write(list[i]); - } - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) - { - set.RemoveWhere(mobile => mobile.Deleted); - } - - Write(set.Count); - - foreach (var mob in set) - { - Write(mob); - } - } - - public void WriteMobileSet(HashSet set) where T : Mobile - { - WriteMobileSet(set, false); - } - - public void WriteMobileSet(HashSet set, bool tidy) where T : Mobile - { - if (tidy) - { - set.RemoveWhere(mob => mob.Deleted); - } - - Write(set.Count); - - foreach (var mob in set) - { - Write(mob); - } - } - - public void Write(List list) - { - Write(list, false); - } - - public void Write(List list, bool tidy) - { - if (tidy) - { - for (var i = 0; i < list.Count;) - { - if (list[i].Disbanded) - { - list.RemoveAt(i); - } - else - { - ++i; - } - } - } - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - Write(list[i]); - } - } - - public void WriteGuildList(List list) where T : BaseGuild - { - WriteGuildList(list, false); - } - - public void WriteGuildList(List list, bool tidy) where T : BaseGuild - { - if (tidy) - { - for (var i = 0; i < list.Count;) - { - if (list[i].Disbanded) - { - list.RemoveAt(i); - } - else - { - ++i; - } - } - } - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - Write(list[i]); - } - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) - { - set.RemoveWhere(guild => guild.Disbanded); - } - - Write(set.Count); - - foreach (var guild in set) - { - Write(guild); - } - } - - public void WriteGuildSet(HashSet set) where T : BaseGuild - { - WriteGuildSet(set, false); - } - - public void WriteGuildSet(HashSet set, bool tidy) where T : BaseGuild - { - if (tidy) - { - set.RemoveWhere(guild => guild.Disbanded); - } - - Write(set.Count); - - foreach (var guild in set) - { - Write(guild); - } - } - - private void Enqueue(MemoryStream mem) - { - lock (m_WriteQueue) - { - m_WriteQueue.Enqueue(mem); - } - - if (m_WorkerThread.IsAlive != true) - { - m_WorkerThread = new Thread(new WorkerThread(this).Worker) { Priority = ThreadPriority.BelowNormal }; - m_WorkerThread.Start(); - } - } - - private void OnWrite() - { - var curlen = m_Mem.Length; - Position += curlen - m_LastPos; - m_LastPos = curlen; - if (curlen >= m_BufferSize) - { - Enqueue(m_Mem); - m_Mem = new MemoryStream(m_BufferSize + 1024); - m_Bin = new BinaryWriter(m_Mem, Utility.UTF8WithEncoding); - m_LastPos = 0; - } - } - - private class WorkerThread - { - private readonly AsyncWriter m_Owner; - - public WorkerThread(AsyncWriter owner) => m_Owner = owner; - - public void Worker() - { - ThreadCount++; - - int lastCount; - - do - { - MemoryStream mem = null; - - lock (m_Owner.m_WriteQueue) - { - if ((lastCount = m_Owner.m_WriteQueue.Count) > 0) - { - mem = m_Owner.m_WriteQueue.Dequeue(); - } - } - - if (mem?.Length > 0) - { - mem.WriteTo(m_Owner.m_File); - } - } while (lastCount > 1); - - if (m_Owner.m_Closed) - { - m_Owner.m_File.Close(); - } - - ThreadCount--; - - if (ThreadCount <= 0) - { - World.NotifyDiskWriteComplete(); - } - } - } - } -} diff --git a/Projects/Server/Serialization/BinaryFileReader.cs b/Projects/Server/Serialization/BinaryFileReader.cs index 7edd1b5f3..77b86b5ae 100644 --- a/Projects/Server/Serialization/BinaryFileReader.cs +++ b/Projects/Server/Serialization/BinaryFileReader.cs @@ -131,7 +131,7 @@ namespace Server public Mobile ReadMobile() => World.FindMobile(ReadUInt()); - public BaseGuild ReadGuild() => BaseGuild.Find(ReadUInt()); + public BaseGuild ReadGuild() => World.FindGuild(ReadUInt()); public T ReadItem() where T : Item => ReadItem() as T; diff --git a/Projects/Server/Serialization/BinaryFileWriter.cs b/Projects/Server/Serialization/BinaryFileWriter.cs index c6ed00878..26f822b22 100644 --- a/Projects/Server/Serialization/BinaryFileWriter.cs +++ b/Projects/Server/Serialization/BinaryFileWriter.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2020 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: BinaryFileWriter.cs * + * File: BufferedFileWriter.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 * @@ -13,751 +13,23 @@ * along with this program. If not, see . * *************************************************************************/ -using System; -using System.Collections.Generic; using System.IO; -using System.Net; -using System.Text; -using Server.Guilds; namespace Server { - public class BinaryFileWriter : IGenericWriter + public class BinaryFileWriter : BufferWriter { - private const int LargeByteBufferSize = 256; - - private readonly byte[] m_Buffer; - - private readonly Encoding m_Encoding; private readonly Stream m_File; - private readonly bool m_PrefixStrings; - private readonly char[] m_SingleCharBuffer = new char[1]; - - private byte[] m_CharacterBuffer; - - private int m_Index; - private int m_MaxBufferChars; - - private long m_Position; - - public BinaryFileWriter(Stream strm, bool prefixStr) - { - m_PrefixStrings = prefixStr; - m_Encoding = Utility.UTF8; - m_Buffer = new byte[BufferSize]; - m_File = strm; - } - - public BinaryFileWriter(string filename, bool prefixStr) - { - m_PrefixStrings = prefixStr; - m_Buffer = new byte[BufferSize]; + public BinaryFileWriter(string filename, bool prefixStr) : base(prefixStr) => m_File = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - m_Encoding = Utility.UTF8WithEncoding; - } - protected virtual int BufferSize => 64 * 1024; + public BinaryFileWriter(Stream stream, bool prefixStr) : base(prefixStr) => m_File = stream; - public Stream UnderlyingStream - { - get - { - if (m_Index > 0) - { - Flush(); - } - return m_File; - } - } + protected override int BufferSize => 512; - public long Position => m_Position + m_Index; - - public void Close() - { - if (m_Index > 0) - { - Flush(); - } - - m_File.Close(); - } - - public void WriteEncodedInt(int value) - { - var v = (uint)value; - - while (v >= 0x80) - { - if (m_Index + 1 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index++] = (byte)(v | 0x80); - v >>= 7; - } - - if (m_Index + 1 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index++] = (byte)v; - } - - public void Write(string value) - { - if (m_PrefixStrings) - { - if (value == null) - { - if (m_Index + 1 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index++] = 0; - } - else - { - if (m_Index + 1 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index++] = 1; - - InternalWriteString(value); - } - } - else - { - InternalWriteString(value); - } - } - - public void Write(DateTime value) - { - Write(value.Ticks); - } - - public void Write(DateTimeOffset value) - { - Write(value.Ticks); - Write(value.Offset.Ticks); - } - - public void WriteDeltaTime(DateTime value) - { - var ticks = value.Ticks; - var now = DateTime.UtcNow.Ticks; - - TimeSpan d; - - try - { - d = new TimeSpan(ticks - now); - } - catch - { - d = TimeSpan.MaxValue; - } - - Write(d); - } - - public void Write(IPAddress value) - { - Write(Utility.GetLongAddressValue(value)); - } - - public void Write(TimeSpan value) - { - Write(value.Ticks); - } - - public void Write(decimal value) - { - var bits = decimal.GetBits(value); - - for (var i = 0; i < bits.Length; ++i) - { - Write(bits[i]); - } - } - - public void Write(long value) - { - if (m_Index + 8 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Buffer[m_Index + 4] = (byte)(value >> 32); - m_Buffer[m_Index + 5] = (byte)(value >> 40); - m_Buffer[m_Index + 6] = (byte)(value >> 48); - m_Buffer[m_Index + 7] = (byte)(value >> 56); - m_Index += 8; - } - - public void Write(ulong value) - { - if (m_Index + 8 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Buffer[m_Index + 4] = (byte)(value >> 32); - m_Buffer[m_Index + 5] = (byte)(value >> 40); - m_Buffer[m_Index + 6] = (byte)(value >> 48); - m_Buffer[m_Index + 7] = (byte)(value >> 56); - m_Index += 8; - } - - public void Write(int value) - { - if (m_Index + 4 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Index += 4; - } - - public void Write(uint value) - { - if (m_Index + 4 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Index += 4; - } - - public void Write(short value) - { - if (m_Index + 2 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Index += 2; - } - - public void Write(ushort value) - { - if (m_Index + 2 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Index += 2; - } - - public unsafe void Write(double value) - { - if (m_Index + 8 > m_Buffer.Length) - { - Flush(); - } - - fixed (byte* pBuffer = m_Buffer) - { - *(double*)(pBuffer + m_Index) = value; - } - - m_Index += 8; - } - - public unsafe void Write(float value) - { - if (m_Index + 4 > m_Buffer.Length) - { - Flush(); - } - - fixed (byte* pBuffer = m_Buffer) - { - *(float*)(pBuffer + m_Index) = value; - } - - m_Index += 4; - } - - public void Write(char value) - { - if (m_Index + 8 > m_Buffer.Length) - { - Flush(); - } - - m_SingleCharBuffer[0] = value; - - var byteCount = m_Encoding.GetBytes(m_SingleCharBuffer, 0, 1, m_Buffer, m_Index); - m_Index += byteCount; - } - - public void Write(byte value) - { - if (m_Index + 1 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index++] = value; - } - - public void Write(byte[] value) - { - Write(value, value.Length); - } - - public void Write(byte[] value, int length) - { - if (m_Index + length > m_Buffer.Length) - { - Flush(); - } - - Buffer.BlockCopy(value, 0, m_Buffer, m_Index, length); - m_Index += length; - } - - public void Write(sbyte value) - { - if (m_Index + 1 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index++] = (byte)value; - } - - public void Write(bool value) - { - if (m_Index + 1 > m_Buffer.Length) - { - Flush(); - } - - m_Buffer[m_Index++] = (byte)(value ? 1 : 0); - } - - public void Write(Point3D value) - { - Write(value.m_X); - Write(value.m_Y); - Write(value.m_Z); - } - - public void Write(Point2D value) - { - Write(value.m_X); - Write(value.m_Y); - } - - public void Write(Rectangle2D value) - { - Write(value.Start); - Write(value.End); - } - - public void Write(Rectangle3D value) - { - Write(value.Start); - Write(value.End); - } - - public void Write(Map value) - { - if (value != null) - { - Write((byte)value.MapIndex); - } - else - { - Write((byte)0xFF); - } - } - - public void Write(Race value) - { - if (value != null) - { - Write((byte)value.RaceIndex); - } - else - { - Write((byte)0xFF); - } - } - - public void WriteEntity(IEntity value) - { - if (value?.Deleted != false) - { - Write(Serial.MinusOne); - } - else - { - Write(value.Serial); - } - } - - public void Write(Item value) - { - if (value?.Deleted != false) - { - Write(Serial.MinusOne); - } - else - { - Write(value.Serial); - } - } - - public void Write(Mobile value) - { - if (value?.Deleted != false) - { - Write(Serial.MinusOne); - } - else - { - Write(value.Serial); - } - } - - public void Write(BaseGuild value) - { - if (value == null) - { - Write(0); - } - else - { - Write(value.Serial); - } - } - - public void WriteItem(T value) where T : Item - { - Write(value); - } - - public void WriteMobile(T value) where T : Mobile - { - Write(value); - } - - public void WriteGuild(T value) where T : BaseGuild - { - Write(value); - } - - public void Write(List list) - { - WriteItemList(list); - } - - public void Write(List list, bool tidy) - { - WriteItemList(list, tidy); - } - - public void WriteItemList(List list) where T : Item - { - WriteItemList(list, false); - } - - public void WriteItemList(List list, bool tidy) where T : Item - { - if (tidy) - { - for (var i = 0; i < list.Count;) - { - if (list[i]?.Deleted != false) - { - list.RemoveAt(i); - } - else - { - ++i; - } - } - } - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - Write(list[i]); - } - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) - { - set.RemoveWhere(item => item.Deleted); - } - - Write(set.Count); - - foreach (var item in set) - { - Write(item); - } - } - - public void WriteItemSet(HashSet set) where T : Item - { - WriteItemSet(set, false); - } - - public void WriteItemSet(HashSet set, bool tidy) where T : Item - { - if (tidy) - { - set.RemoveWhere(item => item.Deleted); - } - - Write(set.Count); - - foreach (var item in set) - { - Write(item); - } - } - - public void Write(List list) - { - Write(list, false); - } - - public void Write(List list, bool tidy) - { - if (tidy) - { - for (var i = 0; i < list.Count;) - { - if (list[i].Deleted) - { - list.RemoveAt(i); - } - else - { - ++i; - } - } - } - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - Write(list[i]); - } - } - - public void WriteMobileList(List list) where T : Mobile - { - WriteMobileList(list, false); - } - - public void WriteMobileList(List list, bool tidy) where T : Mobile - { - if (tidy) - { - for (var i = 0; i < list.Count;) - { - if (list[i].Deleted) - { - list.RemoveAt(i); - } - else - { - ++i; - } - } - } - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - Write(list[i]); - } - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) - { - set.RemoveWhere(mobile => mobile.Deleted); - } - - Write(set.Count); - - foreach (var mob in set) - { - Write(mob); - } - } - - public void WriteMobileSet(HashSet set) where T : Mobile - { - WriteMobileSet(set, false); - } - - public void WriteMobileSet(HashSet set, bool tidy) where T : Mobile - { - if (tidy) - { - set.RemoveWhere(mob => mob.Deleted); - } - - Write(set.Count); - - foreach (var mob in set) - { - Write(mob); - } - } - - public void Write(List list) - { - Write(list, false); - } - - public void Write(List list, bool tidy) - { - if (tidy) - { - for (var i = 0; i < list.Count;) - { - if (list[i].Disbanded) - { - list.RemoveAt(i); - } - else - { - ++i; - } - } - } - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - Write(list[i]); - } - } - - public void WriteGuildList(List list) where T : BaseGuild - { - WriteGuildList(list, false); - } - - public void WriteGuildList(List list, bool tidy) where T : BaseGuild - { - if (tidy) - { - for (var i = 0; i < list.Count;) - { - if (list[i].Disbanded) - { - list.RemoveAt(i); - } - else - { - ++i; - } - } - } - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - Write(list[i]); - } - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) - { - set.RemoveWhere(guild => guild.Disbanded); - } - - Write(set.Count); - - foreach (var guild in set) - { - Write(guild); - } - } - - public void WriteGuildSet(HashSet set) where T : BaseGuild - { - WriteGuildSet(set, false); - } - - public void WriteGuildSet(HashSet set, bool tidy) where T : BaseGuild - { - if (tidy) - { - set.RemoveWhere(guild => guild.Disbanded); - } - - Write(set.Count); - - foreach (var guild in set) - { - Write(guild); - } - } - - public void Flush() + public override void Flush() { if (m_Index > 0) { @@ -768,52 +40,18 @@ namespace Server } } - internal void InternalWriteString(string value) + public override void Close() { - var length = m_Encoding.GetByteCount(value); + base.Close(); + m_File.Close(); + } - WriteEncodedInt(length); + public override long Seek(long offset, SeekOrigin origin) + { + m_Position += m_Index; + m_Index = 0; - if (m_CharacterBuffer == null) - { - m_CharacterBuffer = new byte[LargeByteBufferSize]; - m_MaxBufferChars = LargeByteBufferSize / m_Encoding.GetMaxByteCount(1); - } - - if (length > LargeByteBufferSize) - { - var current = 0; - var charsLeft = value.Length; - - while (charsLeft > 0) - { - var charCount = charsLeft > m_MaxBufferChars ? m_MaxBufferChars : charsLeft; - var byteLength = m_Encoding.GetBytes(value, current, charCount, m_CharacterBuffer, 0); - - if (m_Index + byteLength > m_Buffer.Length) - { - Flush(); - } - - Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); - m_Index += byteLength; - - current += charCount; - charsLeft -= charCount; - } - } - else - { - var byteLength = m_Encoding.GetBytes(value, 0, value.Length, m_CharacterBuffer, 0); - - if (m_Index + byteLength > m_Buffer.Length) - { - Flush(); - } - - Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); - m_Index += byteLength; - } + return m_Position = m_File.Seek(offset, origin); } } } diff --git a/Projects/Server/Serialization/BufferedFileWriter.cs b/Projects/Server/Serialization/BufferWriter.cs similarity index 73% rename from Projects/Server/Serialization/BufferedFileWriter.cs rename to Projects/Server/Serialization/BufferWriter.cs index 2f25bf386..afc0d7f8d 100644 --- a/Projects/Server/Serialization/BufferedFileWriter.cs +++ b/Projects/Server/Serialization/BufferWriter.cs @@ -15,41 +15,79 @@ using System; using System.Collections.Generic; +using System.IO; using System.Net; +using System.Runtime.CompilerServices; using System.Text; using Server.Guilds; namespace Server { - public class BufferedFileWriter : IGenericWriter + public class BufferWriter : IGenericWriter { private const int LargeByteBufferSize = 256; private readonly Encoding m_Encoding; private readonly bool m_PrefixStrings; + protected byte[] m_Buffer; + protected int m_Index; + protected long m_Position; + private readonly char[] m_SingleCharBuffer = new char[1]; private byte[] m_CharacterBuffer; - private int m_Index; private int m_MaxBufferChars; - public BufferedFileWriter(bool prefixStr) + public BufferWriter(bool prefixStr) { m_PrefixStrings = prefixStr; m_Encoding = Utility.UTF8; - Data = new byte[BufferSize]; + m_Buffer = new byte[BufferSize]; } - protected virtual int BufferSize => Data?.Length ?? 64; + protected virtual int BufferSize => 256; - public byte[] Data { get; private set; } + public byte[] Data => m_Buffer; - public long Position => m_Index; - - public void Close() + 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); + } + + public virtual void Flush() + { + Resize(m_Buffer.Length * 2); + } + + 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 + }; } public void WriteEncodedInt(int value) @@ -58,21 +96,21 @@ namespace Server while (v >= 0x80) { - if (m_Index + 1 > Data.Length) + if (m_Index + 1 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index++] = (byte)(v | 0x80); + m_Buffer[m_Index++] = (byte)(v | 0x80); v >>= 7; } - if (m_Index + 1 > Data.Length) + if (m_Index + 1 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index++] = (byte)v; + m_Buffer[m_Index++] = (byte)v; } public void Write(string value) @@ -81,21 +119,21 @@ namespace Server { if (value == null) { - if (m_Index + 1 > Data.Length) + if (m_Index + 1 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index++] = 0; + m_Buffer[m_Index++] = 0; } else { - if (m_Index + 1 > Data.Length) + if (m_Index + 1 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index++] = 1; + m_Buffer[m_Index++] = 1; InternalWriteString(value); } @@ -158,100 +196,100 @@ namespace Server public void Write(long value) { - if (m_Index + 8 > Data.Length) + if (m_Index + 8 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index] = (byte)value; - Data[m_Index + 1] = (byte)(value >> 8); - Data[m_Index + 2] = (byte)(value >> 16); - Data[m_Index + 3] = (byte)(value >> 24); - Data[m_Index + 4] = (byte)(value >> 32); - Data[m_Index + 5] = (byte)(value >> 40); - Data[m_Index + 6] = (byte)(value >> 48); - Data[m_Index + 7] = (byte)(value >> 56); + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); + m_Buffer[m_Index + 2] = (byte)(value >> 16); + m_Buffer[m_Index + 3] = (byte)(value >> 24); + m_Buffer[m_Index + 4] = (byte)(value >> 32); + m_Buffer[m_Index + 5] = (byte)(value >> 40); + m_Buffer[m_Index + 6] = (byte)(value >> 48); + m_Buffer[m_Index + 7] = (byte)(value >> 56); m_Index += 8; } public void Write(ulong value) { - if (m_Index + 8 > Data.Length) + if (m_Index + 8 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index] = (byte)value; - Data[m_Index + 1] = (byte)(value >> 8); - Data[m_Index + 2] = (byte)(value >> 16); - Data[m_Index + 3] = (byte)(value >> 24); - Data[m_Index + 4] = (byte)(value >> 32); - Data[m_Index + 5] = (byte)(value >> 40); - Data[m_Index + 6] = (byte)(value >> 48); - Data[m_Index + 7] = (byte)(value >> 56); + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); + m_Buffer[m_Index + 2] = (byte)(value >> 16); + m_Buffer[m_Index + 3] = (byte)(value >> 24); + m_Buffer[m_Index + 4] = (byte)(value >> 32); + m_Buffer[m_Index + 5] = (byte)(value >> 40); + m_Buffer[m_Index + 6] = (byte)(value >> 48); + m_Buffer[m_Index + 7] = (byte)(value >> 56); m_Index += 8; } public void Write(int value) { - if (m_Index + 4 > Data.Length) + if (m_Index + 4 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index] = (byte)value; - Data[m_Index + 1] = (byte)(value >> 8); - Data[m_Index + 2] = (byte)(value >> 16); - Data[m_Index + 3] = (byte)(value >> 24); + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); + m_Buffer[m_Index + 2] = (byte)(value >> 16); + m_Buffer[m_Index + 3] = (byte)(value >> 24); m_Index += 4; } public void Write(uint value) { - if (m_Index + 4 > Data.Length) + if (m_Index + 4 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index] = (byte)value; - Data[m_Index + 1] = (byte)(value >> 8); - Data[m_Index + 2] = (byte)(value >> 16); - Data[m_Index + 3] = (byte)(value >> 24); + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); + m_Buffer[m_Index + 2] = (byte)(value >> 16); + m_Buffer[m_Index + 3] = (byte)(value >> 24); m_Index += 4; } public void Write(short value) { - if (m_Index + 2 > Data.Length) + if (m_Index + 2 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index] = (byte)value; - Data[m_Index + 1] = (byte)(value >> 8); + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); m_Index += 2; } public void Write(ushort value) { - if (m_Index + 2 > Data.Length) + if (m_Index + 2 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index] = (byte)value; - Data[m_Index + 1] = (byte)(value >> 8); + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); m_Index += 2; } public unsafe void Write(double value) { - if (m_Index + 8 > Data.Length) + if (m_Index + 8 > m_Buffer.Length) { - Expand(); + Flush(); } - fixed (byte* pBuffer = Data) + fixed (byte* pBuffer = m_Buffer) { *(double*)(pBuffer + m_Index) = value; } @@ -261,12 +299,12 @@ namespace Server public unsafe void Write(float value) { - if (m_Index + 4 > Data.Length) + if (m_Index + 4 > m_Buffer.Length) { - Expand(); + Flush(); } - fixed (byte* pBuffer = Data) + fixed (byte* pBuffer = m_Buffer) { *(float*)(pBuffer + m_Index) = value; } @@ -276,61 +314,67 @@ namespace Server public void Write(char value) { - if (m_Index + 8 > Data.Length) + if (m_Index + 8 > m_Buffer.Length) { - Expand(); + Flush(); } m_SingleCharBuffer[0] = value; - var byteCount = m_Encoding.GetBytes(m_SingleCharBuffer, 0, 1, Data, m_Index); + var byteCount = m_Encoding.GetBytes(m_SingleCharBuffer, 0, 1, m_Buffer, m_Index); m_Index += byteCount; } public void Write(byte value) { - if (m_Index + 1 > Data.Length) + if (m_Index + 1 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index++] = value; - } - - public void Write(byte[] value) - { - Write(value, value.Length); + m_Buffer[m_Index++] = value; } public void Write(byte[] value, int length) { - while (m_Index + length > Data.Length) - { - Expand(); - } + int remaining = length; + int idx = 0; - Buffer.BlockCopy(value, 0, Data, m_Index, length); - m_Index += length; + while (remaining > 0) + { + int size = Math.Min(m_Buffer.Length - m_Index, remaining); + Buffer.BlockCopy(value, idx, m_Buffer, m_Index, size); + // value.Slice(idx).CopyTo(m_Buffer.AsSpan(m_Index, size)); + + remaining -= size; + m_Index += size; + idx += size; + + if (m_Index == m_Buffer.Length) + { + Flush(); + } + } } public void Write(sbyte value) { - if (m_Index + 1 > Data.Length) + if (m_Index + 1 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index++] = (byte)value; + m_Buffer[m_Index++] = (byte)value; } public void Write(bool value) { - if (m_Index + 1 > Data.Length) + if (m_Index + 1 > m_Buffer.Length) { - Expand(); + Flush(); } - Data[m_Index++] = (byte)(value ? 1 : 0); + m_Buffer[m_Index++] = (byte)(value ? 1 : 0); } public void Write(Point3D value) @@ -704,18 +748,6 @@ namespace Server } } - public void Flush() - { - m_Index = 0; - } - - private void Expand() - { - var newBuffer = new byte[BufferSize * 2]; - Buffer.BlockCopy(Data, 0, newBuffer, 0, Data.Length); - Data = newBuffer; - } - internal void InternalWriteString(string value) { var length = m_Encoding.GetByteCount(value); @@ -738,12 +770,12 @@ namespace Server var charCount = charsLeft > m_MaxBufferChars ? m_MaxBufferChars : charsLeft; var byteLength = m_Encoding.GetBytes(value, current, charCount, m_CharacterBuffer, 0); - if (m_Index + byteLength > Data.Length) + if (m_Index + byteLength > m_Buffer.Length) { - Expand(); + Flush(); } - Buffer.BlockCopy(m_CharacterBuffer, 0, Data, m_Index, byteLength); + Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); m_Index += byteLength; current += charCount; @@ -754,19 +786,14 @@ namespace Server { var byteLength = m_Encoding.GetBytes(value, 0, value.Length, m_CharacterBuffer, 0); - if (m_Index + byteLength > Data.Length) + if (m_Index + byteLength > m_Buffer.Length) { - Expand(); + Flush(); } - Buffer.BlockCopy(m_CharacterBuffer, 0, Data, m_Index, byteLength); + Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); m_Index += byteLength; } } - - public void WriteTo(IGenericWriter writer) - { - writer.Write(Data, (int)Position); - } } } diff --git a/Projects/Server/Serialization/IGenericWriter.cs b/Projects/Server/Serialization/IGenericWriter.cs index 91376fbd6..052027690 100644 --- a/Projects/Server/Serialization/IGenericWriter.cs +++ b/Projects/Server/Serialization/IGenericWriter.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Net; using Server.Guilds; @@ -23,9 +24,7 @@ namespace Server public interface IGenericWriter { long Position { get; } - void Close(); - void Write(string value); void Write(DateTime value); void Write(DateTimeOffset value); @@ -41,66 +40,50 @@ namespace Server void Write(float value); void Write(char value); void Write(byte value); - void Write(byte[] value); void Write(byte[] value, int length); void Write(sbyte value); void Write(bool value); void WriteEncodedInt(int value); void Write(IPAddress value); - void WriteDeltaTime(DateTime value); - void Write(Point3D value); void Write(Point2D value); void Write(Rectangle2D value); void Write(Rectangle3D value); void Write(Map value); - void WriteEntity(IEntity value); void Write(Item value); void Write(Mobile value); void Write(BaseGuild value); - void WriteItem(T value) where T : Item; void WriteMobile(T value) where T : Mobile; void WriteGuild(T value) where T : BaseGuild; - void Write(Race value); - void Write(List list); void Write(List list, bool tidy); - void WriteItemList(List list) where T : Item; void WriteItemList(List list, bool tidy) where T : Item; - void Write(HashSet list); void Write(HashSet list, bool tidy); - void WriteItemSet(HashSet set) where T : Item; void WriteItemSet(HashSet set, bool tidy) where T : Item; - void Write(List list); void Write(List list, bool tidy); - void WriteMobileList(List list) where T : Mobile; void WriteMobileList(List list, bool tidy) where T : Mobile; - void Write(HashSet list); void Write(HashSet list, bool tidy); - void WriteMobileSet(HashSet set) where T : Mobile; void WriteMobileSet(HashSet set, bool tidy) where T : Mobile; - void Write(List list); void Write(List list, bool tidy); - void WriteGuildList(List list) where T : BaseGuild; void WriteGuildList(List list, bool tidy) where T : BaseGuild; - void Write(HashSet list); void Write(HashSet list, bool tidy); - void WriteGuildSet(HashSet set) where T : BaseGuild; void WriteGuildSet(HashSet set, bool tidy) where T : BaseGuild; + + long Seek(long offset, SeekOrigin origin); } } diff --git a/Projects/Server/Serialization/ISerializable.cs b/Projects/Server/Serialization/ISerializable.cs index 7fb2dfa6b..f02cd776d 100644 --- a/Projects/Server/Serialization/ISerializable.cs +++ b/Projects/Server/Serialization/ISerializable.cs @@ -13,14 +13,18 @@ * along with this program. If not, see . * *************************************************************************/ +using System; + namespace Server { public interface ISerializable { - BufferedFileWriter SaveBuffer { get; } + BufferWriter SaveBuffer { get; set; } int TypeRef { get; } Serial Serial { get; } - void Serialize(); + void Serialize(DateTime serializeStart); + void Deserialize(IGenericReader reader); void Serialize(IGenericWriter writer); + void Delete(); } } diff --git a/Projects/Server/World.cs b/Projects/Server/World.cs deleted file mode 100644 index adc6c5e6e..000000000 --- a/Projects/Server/World.cs +++ /dev/null @@ -1,866 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Threading; -using Server.Guilds; -using Server.Network; - -namespace Server -{ - public static class World - { - private static readonly ManualResetEvent m_DiskWriteHandle = new ManualResetEvent(true); - - private static Queue _addQueue, _deleteQueue; - - public static readonly string MobileIndexPath = Path.Combine("Saves/Mobiles/", "Mobiles.idx"); - public static readonly string MobileTypesPath = Path.Combine("Saves/Mobiles/", "Mobiles.tdb"); - public static readonly string MobileDataPath = Path.Combine("Saves/Mobiles/", "Mobiles.bin"); - - public static readonly string ItemIndexPath = Path.Combine("Saves/Items/", "Items.idx"); - public static readonly string ItemTypesPath = Path.Combine("Saves/Items/", "Items.tdb"); - public static readonly string ItemDataPath = Path.Combine("Saves/Items/", "Items.bin"); - - public static readonly string GuildIndexPath = Path.Combine("Saves/Guilds/", "Guilds.idx"); - public static readonly string GuildDataPath = Path.Combine("Saves/Guilds/", "Guilds.bin"); - - private static readonly Type[] m_SerialTypeArray = { typeof(Serial) }; - - internal static int m_Saves; - - internal static List m_ItemTypes = new List(); - internal static List m_MobileTypes = new List(); - - public static bool Saving { get; private set; } - - public static bool Loaded { get; private set; } - - public static bool Loading { get; private set; } - - public static Dictionary Mobiles { get; private set; } - - public static Dictionary Items { get; private set; } - - public static string LoadingType { get; private set; } - - public static void NotifyDiskWriteComplete() - { - if (m_DiskWriteHandle.Set()) - { - Console.WriteLine("Closing Save Files. "); - } - } - - public static void WaitForWriteCompletion() - { - m_DiskWriteHandle.WaitOne(); - } - - public static bool OnDelete(IEntity entity) - { - if (Saving || Loading) - { - if (Saving) - { - AppendSafetyLog("delete", entity); - } - - _deleteQueue.Enqueue(entity); - - return false; - } - - return true; - } - - public static void Broadcast(int hue, bool ascii, string text) - { - Packet p; - - if (ascii) - { - p = new AsciiMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "System", text); - } - else - { - p = new UnicodeMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "ENU", "System", text); - } - - var list = TcpServer.Instances; - - p.Acquire(); - - for (var i = 0; i < list.Count; ++i) - { - if (list[i].Mobile != null) - { - list[i].Send(p); - } - } - - p.Release(); - } - - public static void Broadcast(int hue, bool ascii, string format, params object[] args) - { - Broadcast(hue, ascii, string.Format(format, args)); - } - - private static List> ReadTypes(BinaryReader tdbReader) - { - var count = tdbReader.ReadInt32(); - - var types = new List>(count); - - for (var i = 0; i < count; ++i) - { - var typeName = tdbReader.ReadString(); - - var t = AssemblyHandler.FindFirstTypeForName(typeName); - - if (t == null) - { - Console.WriteLine("failed"); - - Console.WriteLine("Error: Type '{0}' was not found. Delete all of those types? (y/n)", typeName); - - if (Console.ReadKey(true).Key == ConsoleKey.Y) - { - types.Add(null); - Console.Write("World: Loading..."); - continue; - } - - Console.WriteLine("Types will not be deleted. An exception will be thrown."); - - throw new Exception($"Bad type '{typeName}'"); - } - - var ctor = t.GetConstructor(m_SerialTypeArray); - - if (ctor != null) - { - types.Add(new Tuple(ctor, typeName)); - } - else - { - throw new Exception($"Type '{t}' does not have a serialization constructor"); - } - } - - return types; - } - - public static void Load() - { - if (Loaded) - { - return; - } - - Loaded = true; - LoadingType = null; - - Console.Write("World: Loading..."); - - var watch = Stopwatch.StartNew(); - - Loading = true; - - _addQueue = new Queue(); - _deleteQueue = new Queue(); - - int mobileCount, itemCount, guildCount; - - var ctorArgs = new object[1]; - - var items = new List(); - var mobiles = new List(); - var guilds = new List(); - - if (File.Exists(MobileIndexPath) && File.Exists(MobileTypesPath)) - { - using var idx = new FileStream(MobileIndexPath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var idxReader = new BinaryReader(idx); - using var tdb = new FileStream(MobileTypesPath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var tdbReader = new BinaryReader(tdb); - var types = ReadTypes(tdbReader); - - mobileCount = idxReader.ReadInt32(); - - Mobiles = new Dictionary(mobileCount); - - for (var i = 0; i < mobileCount; ++i) - { - var typeID = idxReader.ReadInt32(); - var serial = idxReader.ReadUInt32(); - var pos = idxReader.ReadInt64(); - var length = idxReader.ReadInt32(); - - var objs = types[typeID]; - - if (objs == null) - { - continue; - } - - Mobile m = null; - var ctor = objs.Item1; - var typeName = objs.Item2; - - try - { - ctorArgs[0] = (Serial)serial; - m = (Mobile)ctor.Invoke(ctorArgs); - } - catch - { - // ignored - } - - if (m != null) - { - mobiles.Add(new MobileEntry(m, typeID, typeName, pos, length)); - AddMobile(m); - } - } - - tdbReader.Close(); - idxReader.Close(); - } - else - { - Mobiles = new Dictionary(); - } - - if (File.Exists(ItemIndexPath) && File.Exists(ItemTypesPath)) - { - using var idx = new FileStream(ItemIndexPath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var idxReader = new BinaryReader(idx); - - var tdb = new FileStream(ItemTypesPath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var tdbReader = new BinaryReader(tdb); - - var types = ReadTypes(tdbReader); - - itemCount = idxReader.ReadInt32(); - - Items = new Dictionary(itemCount); - - for (var i = 0; i < itemCount; ++i) - { - var typeID = idxReader.ReadInt32(); - var serial = idxReader.ReadUInt32(); - var pos = idxReader.ReadInt64(); - var length = idxReader.ReadInt32(); - - var objs = types[typeID]; - - if (objs == null) - { - continue; - } - - Item item = null; - var ctor = objs.Item1; - var typeName = objs.Item2; - - try - { - ctorArgs[0] = (Serial)serial; - item = (Item)ctor.Invoke(ctorArgs); - } - catch - { - // ignored - } - - if (item != null) - { - items.Add(new ItemEntry(item, typeID, typeName, pos, length)); - AddItem(item); - } - } - - tdbReader.Close(); - idxReader.Close(); - } - else - { - Items = new Dictionary(); - } - - if (File.Exists(GuildIndexPath)) - { - using var idx = new FileStream(GuildIndexPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var idxReader = new BinaryReader(idx); - - guildCount = idxReader.ReadInt32(); - - var createEventArgs = new CreateGuildEventArgs(0xFFFFFFFF); - for (var i = 0; i < guildCount; ++i) - { - idxReader.ReadInt32(); // no typeid for guilds - var id = idxReader.ReadUInt32(); - var pos = idxReader.ReadInt64(); - var length = idxReader.ReadInt32(); - - createEventArgs.Id = id; - EventSink.InvokeCreateGuild(createEventArgs); - var guild = createEventArgs.Guild; - if (guild != null) - { - guilds.Add(new GuildEntry(guild, pos, length)); - } - } - - idxReader.Close(); - } - - bool failedMobiles = false, failedItems = false, failedGuilds = false; - Type failedType = null; - var failedSerial = Serial.Zero; - Exception failed = null; - var failedTypeID = 0; - - if (File.Exists(MobileDataPath)) - { - using var bin = new FileStream(MobileDataPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var reader = new BinaryFileReader(new BinaryReader(bin)); - - for (var i = 0; i < mobiles.Count; ++i) - { - var entry = mobiles[i]; - var m = entry.Mobile; - - if (m != null) - { - reader.Seek(entry.Position, SeekOrigin.Begin); - - try - { - LoadingType = entry.TypeName; - m.Deserialize(reader); - - if (reader.Position != entry.Position + entry.Length) - { - throw new Exception($"***** Bad serialize on {m.GetType()} *****"); - } - } - catch (Exception e) - { - mobiles.RemoveAt(i); - - failed = e; - failedMobiles = true; - failedType = m.GetType(); - failedTypeID = entry.TypeID; - failedSerial = m.Serial; - - break; - } - } - } - - reader.Close(); - } - - if (!failedMobiles && File.Exists(ItemDataPath)) - { - using var bin = new FileStream(ItemDataPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var reader = new BinaryFileReader(new BinaryReader(bin)); - - for (var i = 0; i < items.Count; ++i) - { - var entry = items[i]; - var item = entry.Item; - - if (item != null) - { - reader.Seek(entry.Position, SeekOrigin.Begin); - - try - { - LoadingType = entry.TypeName; - item.Deserialize(reader); - - if (reader.Position != entry.Position + entry.Length) - { - throw new Exception($"***** Bad serialize on {item.GetType()} *****"); - } - } - catch (Exception e) - { - items.RemoveAt(i); - - failed = e; - failedItems = true; - failedType = item.GetType(); - failedTypeID = entry.TypeID; - failedSerial = item.Serial; - - break; - } - } - } - - reader.Close(); - } - - LoadingType = null; - - if (!failedMobiles && !failedItems && File.Exists(GuildDataPath)) - { - using var bin = new FileStream(GuildDataPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var reader = new BinaryFileReader(new BinaryReader(bin)); - - for (var i = 0; i < guilds.Count; ++i) - { - var entry = guilds[i]; - var g = entry.Guild; - - if (g != null) - { - reader.Seek(entry.Position, SeekOrigin.Begin); - - try - { - g.Deserialize(reader); - - if (reader.Position != entry.Position + entry.Length) - { - throw new Exception($"***** Bad serialize on Guild {g.Serial} *****"); - } - } - catch (Exception e) - { - guilds.RemoveAt(i); - - failed = e; - failedGuilds = true; - failedType = typeof(BaseGuild); - failedTypeID = g.Serial.ToInt32(); - failedSerial = g.Serial; - - break; - } - } - } - - reader.Close(); - } - - if (failedItems || failedMobiles || failedGuilds) - { - Console.WriteLine("An error was encountered while loading a saved object"); - - Console.WriteLine(" - Type: {0}", failedType); - Console.WriteLine(" - Serial: {0}", failedSerial); - - Console.WriteLine("Delete the object? (y/n)"); - - if (Console.ReadKey(true).Key == ConsoleKey.Y) - { - if (failedType != typeof(BaseGuild)) - { - Console.WriteLine("Delete all objects of that type? (y/n)"); - - if (Console.ReadKey(true).Key == ConsoleKey.Y) - { - if (failedMobiles) - { - for (var i = 0; i < mobiles.Count;) - { - if (mobiles[i].TypeID == failedTypeID) - { - mobiles.RemoveAt(i); - } - else - { - ++i; - } - } - } - else if (failedItems) - { - for (var i = 0; i < items.Count;) - { - if (items[i].TypeID == failedTypeID) - { - items.RemoveAt(i); - } - else - { - ++i; - } - } - } - } - } - - SaveIndex(mobiles, MobileIndexPath); - SaveIndex(items, ItemIndexPath); - SaveIndex(guilds, GuildIndexPath); - } - - Console.WriteLine("After pressing return an exception will be thrown and the server will terminate."); - Console.ReadLine(); - - throw new Exception( - $"Load failed (items={failedItems}, mobiles={failedMobiles}, guilds={failedGuilds}, type={failedType}, serial={failedSerial})", - failed - ); - } - - EventSink.InvokeWorldLoad(); - - Loading = false; - - ProcessSafetyQueues(); - - foreach (var item in Items.Values) - { - if (item.Parent == null) - { - item.UpdateTotals(); - } - - item.ClearProperties(); - } - - foreach (var m in Mobiles.Values) - { - m.UpdateRegion(); // Is this really needed? - m.UpdateTotals(); - - m.ClearProperties(); - } - - watch.Stop(); - - Console.WriteLine( - "done ({1} items, {2} mobiles) ({0:F2} seconds)", - watch.Elapsed.TotalSeconds, - Items.Count, - Mobiles.Count - ); - } - - private static void ProcessSafetyQueues() - { - while (_addQueue.Count > 0) - { - var entity = _addQueue.Dequeue(); - - if (entity is Item item) - { - AddItem(item); - } - else if (entity is Mobile mob) - { - AddMobile(mob); - } - } - - while (_deleteQueue.Count > 0) - { - var entity = _deleteQueue.Dequeue(); - - if (entity is Item item) - { - item.Delete(); - } - else if (entity is Mobile mob) - { - mob.Delete(); - } - } - } - - private static void AppendSafetyLog(string action, IEntity entity) - { - var message = - $"Warning: Attempted to {action} {entity} during world save.{Environment.NewLine}This action could cause inconsistent state.{Environment.NewLine}It is strongly advised that the offending scripts be corrected."; - - Console.WriteLine(message); - - try - { - using var op = new StreamWriter("world-save-errors.log", true); - op.WriteLine("{0}\t{1}", DateTime.UtcNow, message); - op.WriteLine(new StackTrace(2).ToString()); - op.WriteLine(); - } - catch - { - // ignored - } - } - - private static void SaveIndex(List list, string path) where T : IEntityEntry - { - if (!Directory.Exists("Saves/Mobiles/")) - { - Directory.CreateDirectory("Saves/Mobiles/"); - } - - if (!Directory.Exists("Saves/Items/")) - { - Directory.CreateDirectory("Saves/Items/"); - } - - if (!Directory.Exists("Saves/Guilds/")) - { - Directory.CreateDirectory("Saves/Guilds/"); - } - - using var idx = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); - var idxWriter = new BinaryWriter(idx); - - idxWriter.Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - var e = list[i]; - - idxWriter.Write(e.TypeID); - idxWriter.Write(e.Serial); - idxWriter.Write(e.Position); - idxWriter.Write(e.Length); - } - - idxWriter.Close(); - } - - public static void Save() - { - Save(true, false); - } - - public static void Save(bool message, bool permitBackgroundWrite) - { - if (Saving) - { - return; - } - - ++m_Saves; - - NetState.Pause(); - - WaitForWriteCompletion(); // Blocks Save until current disk flush is done. - - Saving = true; - - m_DiskWriteHandle.Reset(); - - if (message) - { - Broadcast(0x35, true, "The world is saving, please wait."); - } - - var strategy = SaveStrategy.Acquire(); - Console.WriteLine("Core: Using {0} save strategy", strategy.Name.ToLower()); - - Console.Write($"[{DateTime.UtcNow.ToLongTimeString()}] World: Saving..."); - - var watch = Stopwatch.StartNew(); - - if (!Directory.Exists("Saves/Mobiles/")) - { - Directory.CreateDirectory("Saves/Mobiles/"); - } - - if (!Directory.Exists("Saves/Items/")) - { - Directory.CreateDirectory("Saves/Items/"); - } - - if (!Directory.Exists("Saves/Guilds/")) - { - Directory.CreateDirectory("Saves/Guilds/"); - } - - strategy.Save(permitBackgroundWrite); - - try - { - EventSink.InvokeWorldSave(message); - } - catch (Exception e) - { - throw new Exception("World Save event threw an exception. Save failed!", e); - } - - watch.Stop(); - - Saving = false; - - if (!permitBackgroundWrite) - { - NotifyDiskWriteComplete(); // Sets the DiskWriteHandle. If we allow background writes, we leave this upto the individual save strategies. - } - - ProcessSafetyQueues(); - - strategy.ProcessDecay(); - - Console.WriteLine("Save done in {0:F2} seconds.", watch.Elapsed.TotalSeconds); - - if (message) - { - Broadcast( - 0x35, - true, - "World save complete. The entire process took {0:F1} seconds.", - watch.Elapsed.TotalSeconds - ); - } - - NetState.Resume(); - } - - public static IEntity FindEntity(Serial serial) - { - if (serial.IsItem) - { - return FindItem(serial); - } - - if (serial.IsMobile) - { - return FindMobile(serial); - } - - return null; - } - - public static Mobile FindMobile(Serial serial) - { - Mobiles.TryGetValue(serial, out var mob); - - return mob; - } - - public static void AddMobile(Mobile m) - { - if (Saving) - { - AppendSafetyLog("add", m); - _addQueue.Enqueue(m); - } - else - { - Mobiles.Add(m.Serial, m); - } - } - - public static Item FindItem(Serial serial) - { - Items.TryGetValue(serial, out var item); - - return item; - } - - public static void AddItem(Item item) - { - if (Saving) - { - AppendSafetyLog("add", item); - _addQueue.Enqueue(item); - } - else - { - Items.Add(item.Serial, item); - } - } - - public static void RemoveMobile(Mobile m) - { - Mobiles.Remove(m.Serial); - } - - public static void RemoveItem(Item item) - { - Items.Remove(item.Serial); - } - - private interface IEntityEntry - { - Serial Serial { get; } - int TypeID { get; } - long Position { get; } - int Length { get; } - } - - private sealed class GuildEntry : IEntityEntry - { - public GuildEntry(BaseGuild g, long pos, int length) - { - Guild = g; - Position = pos; - Length = length; - } - - public BaseGuild Guild { get; } - - public Serial Serial => Guild?.Serial ?? 0; - - public int TypeID => 0; - - public long Position { get; } - - public int Length { get; } - } - - private sealed class ItemEntry : IEntityEntry - { - public ItemEntry(Item item, int typeID, string typeName, long pos, int length) - { - Item = item; - TypeID = typeID; - TypeName = typeName; - Position = pos; - Length = length; - } - - public Item Item { get; } - - public string TypeName { get; } - - public Serial Serial => Item?.Serial ?? Serial.MinusOne; - - public int TypeID { get; } - - public long Position { get; } - - public int Length { get; } - } - - private sealed class MobileEntry : IEntityEntry - { - public MobileEntry(Mobile mobile, int typeID, string typeName, long pos, int length) - { - Mobile = mobile; - TypeID = typeID; - TypeName = typeName; - Position = pos; - Length = length; - } - - public Mobile Mobile { get; } - - public string TypeName { get; } - - public Serial Serial => Mobile?.Serial ?? Serial.MinusOne; - - public int TypeID { get; } - - public long Position { get; } - - public int Length { get; } - } - } -} diff --git a/Projects/Server/Events/CreateGuildEvent.cs b/Projects/Server/World/EntityIndex.cs similarity index 66% rename from Projects/Server/Events/CreateGuildEvent.cs rename to Projects/Server/World/EntityIndex.cs index 8acabea78..ff46fa7e5 100644 --- a/Projects/Server/Events/CreateGuildEvent.cs +++ b/Projects/Server/World/EntityIndex.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2020 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: CreateGuildEvent.cs * + * File: EntityIndex.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 * @@ -13,23 +13,24 @@ * along with this program. If not, see . * *************************************************************************/ -using System; -using Server.Guilds; - namespace Server { - public class CreateGuildEventArgs : EventArgs + public readonly struct EntityIndex where T : ISerializable { - public CreateGuildEventArgs(uint id) => Id = id; + public T Entity { get; } - public uint Id { get; set; } + public int TypeID { get; } - public BaseGuild Guild { get; set; } - } + public long Position { get; } - public static partial class EventSink - { - public static event Action CreateGuild; - public static void InvokeCreateGuild(CreateGuildEventArgs e) => CreateGuild?.Invoke(e); + public int Length { get; } + + public EntityIndex(T entity, int typeID, long position, int length) + { + Entity = entity; + TypeID = typeID; + Position = position; + Length = length; + } } } diff --git a/Projects/Server/World/EntityTypeIndex.cs b/Projects/Server/World/EntityTypeIndex.cs new file mode 100644 index 000000000..23208eecc --- /dev/null +++ b/Projects/Server/World/EntityTypeIndex.cs @@ -0,0 +1,26 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: EntityTypeIndex.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 . * + *************************************************************************/ + +namespace Server +{ + public readonly struct EntityTypeIndex : IIndexInfo + { + public string TypeName { get; } + + public EntityTypeIndex(string typeName) => TypeName = typeName; + + public Serial CreateIndex(uint num) => num; + } +} diff --git a/Projects/Server/World/IIndexInfo.cs b/Projects/Server/World/IIndexInfo.cs new file mode 100644 index 000000000..5538eaf85 --- /dev/null +++ b/Projects/Server/World/IIndexInfo.cs @@ -0,0 +1,24 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: EntityTypeIndex.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 . * + *************************************************************************/ + +namespace Server +{ + public interface IIndexInfo + { + I CreateIndex(uint num); + + string TypeName { get; } + } +} diff --git a/Projects/Server/World/World.cs b/Projects/Server/World/World.cs new file mode 100644 index 000000000..92bd1acb0 --- /dev/null +++ b/Projects/Server/World/World.cs @@ -0,0 +1,749 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: World.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; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Server.Guilds; +using Server.Network; + +namespace Server +{ + public enum WorldState + { + Initial, + Loading, + Running, + Saving, + WritingSave + } + + public static class World + { + private static readonly ManualResetEvent m_DiskWriteHandle = new ManualResetEvent(true); + + private static Dictionary _pendingAdd; + private static Dictionary _pendingDelete; + private static ConcurrentQueue _decayQueue = new ConcurrentQueue(); + + public const uint ItemOffset = 0x40000000; + public const uint MaxItemSerial = 0x7FFFFFFF; + private const uint _maxItems = int.MaxValue - ItemOffset; + private const uint _maxMobiles = ItemOffset; + + private static Serial _lastMobile = Serial.Zero; + private static Serial _lastItem = ItemOffset; + private static Serial _lastGuild = Serial.Zero; + + public static Serial NewMobile + { + get + { + uint last = _lastMobile; + + for (int i = 0; i < _maxMobiles; i++) + { + last++; + + if (last >= _lastMobile) + { + last = 0; + } + + if (FindMobile(last) == null) + { + _lastMobile = last; + return last; + } + } + + throw new Exception("No serials left to allocate for mobiles"); + } + } + + public static Serial NewItem + { + get + { + uint last = _lastItem; + + for (int i = 0; i < _maxItems; i++) + { + last++; + + if (last - ItemOffset >= _maxItems) + { + last = ItemOffset; + } + + if (FindItem(last) == null) + { + _lastItem = last; + return last; + } + } + + throw new Exception("No serials left to allocate for items"); + } + } + + public static Serial NewGuild + { + get + { + while (FindGuild(_lastGuild += 1) != null) + { + } + + return _lastGuild; + } + } + + + internal static int _Saves; + internal static List ItemTypes { get; } = new List(); + internal static List MobileTypes { get; } = new List(); + internal static List GuildTypes { get; } = new List(); + + public static WorldState WorldState { get; private set; } + public static bool Saving => WorldState == WorldState.Saving; + public static bool Running => WorldState == WorldState.Running; + public static bool Loading => WorldState == WorldState.Loading; + + public static Dictionary Mobiles { get; private set; } + public static Dictionary Items { get; private set; } + public static Dictionary Guilds { get; private set; } + + public static void WaitForWriteCompletion() + { + m_DiskWriteHandle.WaitOne(); + } + + public static void EnqueueForDecay(Item item) + { + if (WorldState != WorldState.Saving) + { + Console.WriteLine("Attempting to queue {0} for decay but the world is not saving", item); + return; + } + + _decayQueue.Enqueue(item); + } + + public static void Broadcast(int hue, bool ascii, string text) + { + Packet p; + + if (ascii) + { + p = new AsciiMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "System", text); + } + else + { + p = new UnicodeMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "ENU", "System", text); + } + + var list = TcpServer.Instances; + + p.Acquire(); + + for (var i = 0; i < list.Count; ++i) + { + if (list[i].Mobile != null) + { + list[i].Send(p); + } + } + + p.Release(); + } + + public static void Broadcast(int hue, bool ascii, string format, params object[] args) + { + Broadcast(hue, ascii, string.Format(format, args)); + } + + private static List> ReadTypes(BinaryReader tdbReader) + { + var constructorTypes = new[] { typeof(I) }; + + var count = tdbReader.ReadInt32(); + + var types = new List>(count); + + for (var i = 0; i < count; ++i) + { + var typeName = tdbReader.ReadString(); + + var t = AssemblyHandler.FindFirstTypeForName(typeName); + + if (t?.IsAbstract != false) + { + Console.WriteLine("failed"); + + Console.WriteLine( + "Error: Type '{0}' was {1}. Delete all of those types? (y/n)", + typeName, + t?.IsAbstract == true ? "marked abstract" : "not found" + ); + + if (Console.ReadKey(true).Key == ConsoleKey.Y) + { + types.Add(null); + Console.Write("World: Loading..."); + continue; + } + + Console.WriteLine("Types will not be deleted. An exception will be thrown."); + + throw new Exception($"Bad type '{typeName}'"); + } + + var ctor = t.GetConstructor(constructorTypes); + + if (ctor != null) + { + types.Add(new Tuple(ctor, typeName)); + } + else + { + throw new Exception($"Type '{t}' does not have a serialization constructor"); + } + } + + return types; + } + + private static Dictionary LoadIndex(IIndexInfo indexInfo, out List> entities) where T : class, ISerializable + { + var map = new Dictionary(); + object[] ctorArgs = new object[1]; + + var indexType = indexInfo.TypeName; + + string indexPath = Path.Combine("Saves", indexType, $"{indexType}.idx"); + string typesPath = Path.Combine("Saves", indexType, $"{indexType}.tdb"); + + entities = new List>(); + + if (!File.Exists(indexPath) || !File.Exists(typesPath)) + { + return map; + } + + using FileStream idx = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read); + BinaryReader idxReader = new BinaryReader(idx); + + using (FileStream tdb = new FileStream(typesPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + BinaryReader tdbReader = new BinaryReader(tdb); + + List> types = ReadTypes(tdbReader); + + var count = idxReader.ReadInt32(); + + for (int i = 0; i < count; ++i) + { + var typeID = idxReader.ReadInt32(); + var number = idxReader.ReadUInt32(); + var pos = idxReader.ReadInt64(); + var length = idxReader.ReadInt32(); + + Tuple objs = types[typeID]; + + if (objs == null) + { + continue; + } + + T t; + ConstructorInfo ctor = objs.Item1; + I indexer = indexInfo.CreateIndex(number); + + ctorArgs[0] = indexer; + t = ctor.Invoke(ctorArgs) as T; + + if (t != null) + { + entities.Add(new EntityIndex(t, typeID, pos, length)); + map[indexer] = t; + } + } + + tdbReader.Close(); + } + + idxReader.Close(); + + return map; + } + + private static void LoadData(IIndexInfo indexInfo, List> entities) where T : ISerializable + { + var indexType = indexInfo.TypeName; + + string dataPath = Path.Combine("Saves", indexType, $"{indexType}.bin"); + + if (!File.Exists(dataPath)) + { + return; + } + + using FileStream bin = new FileStream(dataPath, FileMode.Open, FileAccess.Read, FileShare.Read); + BinaryFileReader reader = new BinaryFileReader(new BinaryReader(bin)); + + foreach (var entry in entities) + { + T t = entry.Entity; + + if (t == null) + { + continue; + } + + reader.Seek(entry.Position, SeekOrigin.Begin); + + t.Deserialize(reader); + + if (reader.Position != entry.Position + entry.Length) + { + Console.WriteLine($"***** Bad deserialize on {t.GetType()} *****"); + Console.WriteLine( + $"Serialized object was {entry.Length} bytes, but {reader.Position - entry.Position} bytes deserialized" + ); + + Console.WriteLine("Delete the object and continue? (y/n)"); + + if (Console.ReadKey(true).Key != ConsoleKey.Y) + { + throw new Exception("Deserialization failed."); + } + t.Delete(); + } + } + + reader.Close(); + } + + public static void Load() + { + if (WorldState != WorldState.Initial) + { + return; + } + + WorldState = WorldState.Loading; + + Console.Write("World: Loading..."); + + var watch = Stopwatch.StartNew(); + + _pendingAdd = new Dictionary(); + _pendingDelete = new Dictionary(); + + List> items; + List> mobiles; + List> guilds; + + IIndexInfo itemIndexInfo = new EntityTypeIndex("Items"); + IIndexInfo mobileIndexInfo = new EntityTypeIndex("Mobiles"); + IIndexInfo guildIndexInfo = new EntityTypeIndex("Guilds"); + + Items = LoadIndex(itemIndexInfo, out items); + Mobiles = LoadIndex(mobileIndexInfo, out mobiles); + Guilds = LoadIndex(guildIndexInfo, out guilds); + + LoadData(itemIndexInfo, items); + LoadData(mobileIndexInfo, mobiles); + LoadData(guildIndexInfo, guilds); + + EventSink.InvokeWorldLoad(); + + WorldState = WorldState.Running; + + ProcessSafetyQueues(); + + var now = DateTime.UtcNow; + + foreach (var item in Items.Values) + { + if (item.Parent == null) + { + item.UpdateTotals(); + } + + item.ClearProperties(); + item.Serialize(now); + } + + foreach (var m in Mobiles.Values) + { + m.UpdateRegion(); // Is this really needed? + m.UpdateTotals(); + + m.ClearProperties(); + m.Serialize(now); + } + + foreach (var g in Guilds.Values) + { + g.Serialize(now); + } + + watch.Stop(); + + Console.WriteLine( + "done ({1} items, {2} mobiles) ({0:F2} seconds)", + watch.Elapsed.TotalSeconds, + Items.Count, + Mobiles.Count + ); + } + + private static void ProcessSafetyQueues() + { + foreach (var entity in _pendingAdd.Values) + { + AddEntity(entity); + } + + foreach (var entity in _pendingDelete.Values) + { + if (_pendingAdd.ContainsKey(entity.Serial)) + { + Console.Error.WriteLine("Entity {0} was both pending both deletion and addition after save", entity); + } + + RemoveEntity(entity); + } + } + + private static void AppendSafetyLog(string action, ISerializable entity) + { + var message = + $"Warning: Attempted to {action} {entity} during world save.{Environment.NewLine}This action could cause inconsistent state.{Environment.NewLine}It is strongly advised that the offending scripts be corrected."; + + Console.WriteLine(message); + + try + { + using var op = new StreamWriter("world-save-errors.log", true); + op.WriteLine("{0}\t{1}", DateTime.UtcNow, message); + op.WriteLine(new StackTrace(2).ToString()); + op.WriteLine(); + } + catch + { + // ignored + } + } + + private static void FinishWorldSave() + { + WorldState = WorldState.Running; + + ProcessDecay(); + ProcessSafetyQueues(); + } + + public static void WriteFiles(object state) + { + IIndexInfo itemIndexInfo = new EntityTypeIndex("Items"); + IIndexInfo mobileIndexInfo = new EntityTypeIndex("Mobiles"); + IIndexInfo guildIndexInfo = new EntityTypeIndex("Guilds"); + + WriteEntities(itemIndexInfo, Items, ItemTypes); + WriteEntities(mobileIndexInfo, Mobiles, MobileTypes); + WriteEntities(guildIndexInfo, Guilds, GuildTypes); + + if (m_DiskWriteHandle.Set()) + { + Console.WriteLine("Closing Save Files."); + } + + Timer.DelayCall(FinishWorldSave); + } + + private static void WriteEntities(IIndexInfo indexInfo, Dictionary entities, List types) where T : class, ISerializable + { + var typeName = indexInfo.TypeName; + + var path = Path.Combine("Saves", typeName); + + AssemblyHandler.EnsureDirectory(path); + + string idxPath = Path.Combine(path, $"{typeName}.idx"); + string tdbPath = Path.Combine(path, $"{typeName}.tdb"); + string binPath = Path.Combine(path, $"{typeName}.bin"); + + var idx = new BinaryFileWriter(idxPath, false); + var tdb = new BinaryFileWriter(tdbPath, false); + var bin = new BinaryFileWriter(binPath, true); + + idx.Write(entities.Count); + foreach (var mob in entities.Values) + { + long start = bin.Position; + + idx.Write(mob.TypeRef); + idx.Write(mob.Serial); + idx.Write(start); + + mob.SerializeTo(bin); + + idx.Write((int)(bin.Position - start)); + } + + tdb.Write(types.Count); + for (int i = 0; i < types.Count; ++i) + { + tdb.Write(types[i].FullName); + } + + idx.Close(); + tdb.Close(); + bin.Close(); + } + + private static void SaveEntities(IEnumerable list, DateTime serializeStart) where T : class, ISerializable + { + Parallel.ForEach(list, (t, now) => { + t.Serialize(serializeStart); + }); + } + + private static void ProcessDecay() + { + Item item; + + while (_decayQueue.TryDequeue(out item)) + { + if (item.OnDecay()) + { + // TODO: Add Logging + item.Delete(); + } + } + } + + public static void Save() + { + if (WorldState != WorldState.Running) + { + return; + } + + ++_Saves; + + NetState.FlushAll(); + NetState.Pause(); + + WaitForWriteCompletion(); // Blocks Save until current disk flush is done. + + WorldState = WorldState.Saving; + + m_DiskWriteHandle.Reset(); + + Broadcast(0x35, true, "The world is saving, please wait."); + + var now = DateTime.UtcNow; + + Console.Write("[{0}] World: Saving...", now.ToLongTimeString()); + + var watch = Stopwatch.StartNew(); + + SaveEntities(Items.Values, now); + SaveEntities(Mobiles.Values, now); + SaveEntities(Guilds.Values, now); + + try + { + EventSink.InvokeWorldSave(); + } + catch (Exception e) + { + throw new Exception("World Save event threw an exception. Save failed!", e); + } + + WorldState = WorldState.WritingSave; + + ThreadPool.QueueUserWorkItem(WriteFiles); + + watch.Stop(); + + var duration = watch.Elapsed.TotalSeconds; + + Console.WriteLine("Save done in {0:F2} seconds.", duration); + + Broadcast( + 0x35, + true, + "World save complete. The entire process took {0:F2} seconds.", + duration + ); + + NetState.Resume(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEntity FindEntity(Serial serial, bool returnDeleted = false) => FindEntity(serial); + + public static T FindEntity(Serial serial, bool returnDeleted = false) where T : class, IEntity + { + switch (WorldState) + { + default: return default; + case WorldState.Loading: + case WorldState.Saving: + case WorldState.WritingSave: + { + if (_pendingDelete.TryGetValue(serial, out var entity)) + { + return !returnDeleted ? null : entity as T; + } + + if (_pendingAdd.TryGetValue(serial, out entity)) + { + return entity as T; + } + + goto case WorldState.Running; + } + case WorldState.Running: + { + if (serial.IsItem) + { + Items.TryGetValue(serial, out var item); + return item as T; + } + + if (serial.IsMobile) + { + Mobiles.TryGetValue(serial, out var mob); + return mob as T; + } + + return default; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Item FindItem(Serial serial, bool returnDeleted = false) => FindEntity(serial, returnDeleted); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Mobile FindMobile(Serial serial, bool returnDeleted = false) => + FindEntity(serial, returnDeleted); + + public static BaseGuild FindGuild(Serial serial) => Guilds.TryGetValue(serial, out var guild) ? guild : null; + + + public static void AddEntity(T entity) where T : class, IEntity + { + switch (WorldState) + { + default: // Not Running + { + throw new Exception($"Added {typeof(T).Name} before world load.\n"); + } + case WorldState.Saving: + { + AppendSafetyLog("add", entity); + goto case WorldState.WritingSave; + } + case WorldState.Loading: + case WorldState.WritingSave: + { + if (_pendingDelete.Remove(entity.Serial)) + { + Utility.PushColor(ConsoleColor.Red); + Console.WriteLine($"Deleted then added {typeof(T).Name} during {WorldState.ToString().ToLower()} state."); + Utility.PopColor(); + } + _pendingAdd[entity.Serial] = entity; + break; + } + case WorldState.Running: + { + if (entity.Serial.IsItem) + { + Items[entity.Serial] = entity as Item; + } + + if (entity.Serial.IsMobile) + { + Mobiles[entity.Serial] = entity as Mobile; + } + break; + } + } + } + + public static void AddGuild(BaseGuild guild) => Guilds[guild.Serial] = guild; + + public static void RemoveEntity(T entity) where T : class, IEntity + { + switch (WorldState) + { + default: // Not Running + { + throw new Exception($"Removed {typeof(T).Name} before world load.\n"); + } + case WorldState.Saving: + { + AppendSafetyLog("delete", entity); + goto case WorldState.WritingSave; + } + case WorldState.Loading: + case WorldState.WritingSave: + { + _pendingAdd.Remove(entity.Serial); + _pendingDelete[entity.Serial] = entity; + break; + } + case WorldState.Running: + { + if (entity.Serial.IsItem) + { + Items.Remove(entity.Serial); + } + + if (entity.Serial.IsMobile) + { + Mobiles.Remove(entity.Serial); + } + break; + } + } + } + + public static void RemoveGuild(BaseGuild guild) => Guilds.Remove(guild.Serial); + + public static void SerializeTo(this ISerializable entity, IGenericWriter writer) + { + var saveBuffer = entity.SaveBuffer; + writer.Write(saveBuffer.Data, (int)saveBuffer.Position); + + // Resize to exact buffer size + entity.SaveBuffer.Resize((int)entity.SaveBuffer.Position); + } + } +} diff --git a/Projects/UOContent/Accounting/Accounts.cs b/Projects/UOContent/Accounting/Accounts.cs index 3dcc609ae..ec65bb588 100644 --- a/Projects/UOContent/Accounting/Accounts.cs +++ b/Projects/UOContent/Accounting/Accounts.cs @@ -69,12 +69,9 @@ namespace Server.Accounting } } - public static void Save(bool message) + public static void Save() { - if (!Directory.Exists("Saves/Accounts")) - { - Directory.CreateDirectory("Saves/Accounts"); - } + AssemblyHandler.EnsureDirectory("Saves/Accounts"); var filePath = Path.Combine("Saves/Accounts", "accounts.xml"); diff --git a/Projects/UOContent/Engines/Plants/PlantSystem.cs b/Projects/UOContent/Engines/Plants/PlantSystem.cs index c59c9ff8c..041ab6ac3 100644 --- a/Projects/UOContent/Engines/Plants/PlantSystem.cs +++ b/Projects/UOContent/Engines/Plants/PlantSystem.cs @@ -405,7 +405,7 @@ namespace Server.Engines.Plants GrowAll(); } - private static void EventSink_WorldSave(bool message) + private static void EventSink_WorldSave() { GrowAll(); } diff --git a/Projects/UOContent/Gumps/Guilds/New Guild System/DiplomacyGump.cs b/Projects/UOContent/Gumps/Guilds/New Guild System/DiplomacyGump.cs index 2c9554796..f7371cabf 100644 --- a/Projects/UOContent/Gumps/Guilds/New Guild System/DiplomacyGump.cs +++ b/Projects/UOContent/Gumps/Guilds/New Guild System/DiplomacyGump.cs @@ -26,7 +26,7 @@ namespace Server.Guilds "", 0, GuildDisplayType.All, - Utility.CastListCovariant(new List(BaseGuild.List.Values)), + Utility.CastListCovariant(new List(World.Guilds.Values)), 1063136 + (int)GuildDisplayType.All ) { @@ -44,7 +44,7 @@ namespace Server.Guilds filter, startNumber, display, - Utility.CastListCovariant(new List(BaseGuild.List.Values)), + Utility.CastListCovariant(new List(World.Guilds.Values)), 1063136 + (int)display ) { diff --git a/Projects/UOContent/Items/Food/Beverage.cs b/Projects/UOContent/Items/Food/Beverage.cs index 8789fa60e..967e81d29 100644 --- a/Projects/UOContent/Items/Food/Beverage.cs +++ b/Projects/UOContent/Items/Food/Beverage.cs @@ -101,7 +101,7 @@ namespace Server.Items } else { - throw new Exception(World.LoadingType); + throw new Exception("Invalid beverage type"); } break; @@ -380,7 +380,7 @@ namespace Server.Items } else { - throw new Exception(World.LoadingType); + throw new Exception("Invalid beverage type"); } break; @@ -550,7 +550,7 @@ namespace Server.Items } else { - throw new Exception(World.LoadingType); + throw new Exception("Invalid beverage type"); } break; @@ -1106,7 +1106,7 @@ namespace Server.Items writer.Write(m_Quantity); } - protected bool CheckType(string name) => World.LoadingType == $"Server.Items.{name}"; + protected bool CheckType(string name) => GetType().FullName == $"Server.Items.{name}"; public override void Deserialize(IGenericReader reader) { diff --git a/Projects/UOContent/Misc/AutoSave.cs b/Projects/UOContent/Misc/AutoSave.cs index 1f8c3bba7..c2bd71a70 100644 --- a/Projects/UOContent/Misc/AutoSave.cs +++ b/Projects/UOContent/Misc/AutoSave.cs @@ -106,7 +106,7 @@ namespace Server.Misc Console.WriteLine("WARNING: Automatic backup FAILED: {0}", e); } - World.Save(true, permitBackgroundWrite); + World.Save(); } private static void Backup() diff --git a/Projects/UOContent/Misc/Guild.cs b/Projects/UOContent/Misc/Guild.cs index 44539bbdb..aab0e898e 100644 --- a/Projects/UOContent/Misc/Guild.cs +++ b/Projects/UOContent/Misc/Guild.cs @@ -575,7 +575,7 @@ namespace Server.Guilds protected override void OnTick() { - foreach (var g in BaseGuild.List.Values) + foreach (var g in World.Guilds.Values) { (g as Guild)?.CheckExpiredWars(); } @@ -632,7 +632,7 @@ namespace Server.Guilds PendingWars = new List(); } - public Guild(uint id) : base(id) // serialization ctor + public Guild(Serial serial) : base(serial) { } @@ -815,7 +815,6 @@ namespace Server.Guilds public static void Configure() { - EventSink.CreateGuild += EventSink_CreateGuild; EventSink.GuildGumpRequest += EventSink_GuildGumpRequest; CommandSystem.Register("GuildProps", AccessLevel.Counselor, GuildProps_OnCommand); @@ -885,7 +884,7 @@ namespace Server.Guilds { m_Leader = null; - List.Remove(Serial); + World.RemoveGuild(this); foreach (var m in Members) { @@ -945,7 +944,7 @@ namespace Server.Guilds else { var g = uint.TryParse(arg, out var id) - ? Find(id) as Guild + ? World.FindGuild(id) as Guild : FindByAbbrev(arg) as Guild ?? FindByName(arg) as Guild; if (g != null) @@ -977,11 +976,6 @@ namespace Server.Guilds } } - public static void EventSink_CreateGuild(CreateGuildEventArgs args) - { - args.Guild = new Guild(args.Id); - } - public static Guild GetAllianceLeader(Guild g) { var alliance = g.Alliance; @@ -1234,6 +1228,11 @@ namespace Server.Guilds writer.Write(Website); } + public override void Delete() + { + World.RemoveGuild(this); + } + public override void Deserialize(IGenericReader reader) { var version = reader.ReadInt(); diff --git a/Projects/UOContent/Multis/Boats/BaseBoat.cs b/Projects/UOContent/Multis/Boats/BaseBoat.cs index 4cc345ba1..a4c136825 100644 --- a/Projects/UOContent/Multis/Boats/BaseBoat.cs +++ b/Projects/UOContent/Multis/Boats/BaseBoat.cs @@ -1986,7 +1986,7 @@ namespace Server.Multis EventSink.WorldSave += EventSink_WorldSave; } - private static void EventSink_WorldSave(bool message) + private static void EventSink_WorldSave() { new UpdateAllTimer().Start(); } diff --git a/version.json b/version.json index 38d4958f3..830bbe611 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.7.0" + "version": "0.8.0" }