Updates Serialization (#292)

- [X] Removes all save strategies
- [X] Removes duplicate file writer that won't be used
- [X] Removes persistence (it will become a duplicate system)
- [X] Add a save position variable to skip serializing a clean item/mobile
- [X] Update Guilds/Accounts to be IEntity types
    - Because guilds are abstract, this may make serialization tricky.
- [X] Update the generalized IEntity writing
- [X] Update the load/save to write to buffers then to files in background


Bumps release version
This commit is contained in:
Kamron Batman 2020-10-31 17:38:29 -07:00 committed by GitHub
parent a8d486a969
commit 1ca8655fc1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 1148 additions and 4351 deletions

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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<byte> 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<byte> 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();

View file

@ -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<BuyItemState>
{
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<BuyItemState>
{
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<BuyItemState>
{
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();

View file

@ -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<SellItemState>
{

View file

@ -109,8 +109,8 @@ namespace Server
public static event Action WorldLoad;
public static void InvokeWorldLoad() => WorldLoad?.Invoke();
public static event Action<bool> 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<Mobile, int> SetAbility;
public static void InvokeSetAbility(Mobile mobile, int index) => SetAbility?.Invoke(mobile, index);

View file

@ -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<uint, BaseGuild> List { get; } = new Dictionary<uint, BaseGuild>();
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<BaseGuild> Search(string find)
{
var words = find.ToLower().Split(' ');
var results = new List<BaseGuild>();
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}]\"";
}
}

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
namespace Server
{
public interface IEntity : IPoint3D, IComparable<IEntity>
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<Entity>
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)
{
}
}
}

View file

@ -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<Item>, ISerializable, ISpawnable, IPropertyListObject
public class Item : IHued, IComparable<Item>, ISpawnable, IPropertyListObject
{
public const int QuestItemHue = 0x4EA; // Hmmmm... "for EA"?
public static readonly List<Item> EmptyItems = new List<Item>();
@ -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<IEntity>.CompareTo(IEntity other) => other == null ? -1 : Serial.CompareTo(other.Serial);
/// <summary>
/// Moves the Item to a given <paramref name="location" /> and <paramref name="map" />.
/// </summary>
@ -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();

View file

@ -423,7 +423,7 @@ namespace Server
/// <summary>
/// Base class representing players, npcs, and creatures.
/// </summary>
public class Mobile : IHued, IComparable<Mobile>, ISerializable, ISpawnable, IPropertyListObject
public class Mobile : IHued, IComparable<Mobile>, 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<DamageEntry>();
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<IEntity>.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<StatMod>();
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<StatMod>();
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<StatMod>();
for (var i = 0; i < StatMods.Count; ++i)
{
var check = StatMods[i];
@ -9388,6 +9394,8 @@ namespace Server
{
var offset = 0;
StatMods ??= new List<StatMod>();
for (var i = 0; i < StatMods.Count; ++i)
{
var mod = StatMods[i];

View file

@ -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);
}

View file

@ -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;
}
}
}

View file

@ -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();
}
}
}
}

View file

@ -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<Item> _decayBag;
private SequentialFileWriterStream _guildData, _guildIndex;
private readonly BlockingCollection<QueuedMemoryWriter> _guildThreadWriters;
private SequentialFileWriterStream _itemData, _itemIndex;
private readonly BlockingCollection<QueuedMemoryWriter> _itemThreadWriters;
private SequentialFileWriterStream _mobileData, _mobileIndex;
private readonly BlockingCollection<QueuedMemoryWriter> _mobileThreadWriters;
public DynamicSaveStrategy()
{
_decayBag = new ConcurrentBag<Item>();
_itemThreadWriters = new BlockingCollection<QueuedMemoryWriter>();
_mobileThreadWriters = new BlockingCollection<QueuedMemoryWriter>();
_guildThreadWriters = new BlockingCollection<QueuedMemoryWriter>();
}
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<QueuedMemoryWriter> 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<Item> 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<Mobile> 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<BaseGuild> 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<Type> types)
{
var bfw = new BinaryFileWriter(path, false);
bfw.Write(types.Count);
foreach (var type in types)
{
bfw.Write(type.FullName);
}
bfw.Flush();
bfw.Close();
}
}
}

View file

@ -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
}
}

View file

@ -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<Page> 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<Page>();
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<byte>.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<byte>.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;
}
}
}

View file

@ -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<Item> _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<Item>();
}
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<ISerializable> collection = new Producer();
foreach (var value in collection)
{
while (!Enqueue(value))
{
if (!Commit())
{
Thread.Sleep(0);
}
}
}
finished = true;
SaveTypeDatabases();
WaitHandle.WaitAll(
Array.ConvertAll<Consumer, WaitHandle>(
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<Type> 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<ISerializable>
{
private readonly IEnumerable<BaseGuild> guilds;
private readonly IEnumerable<Item> items;
private readonly IEnumerable<Mobile> mobiles;
public Producer()
{
items = World.Items.Values;
mobiles = World.Mobiles.Values;
guilds = BaseGuild.List.Values;
}
public IEnumerator<ISerializable> 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;
}
}
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.IO;
namespace Server
{
public static class Persistence
{
public static void Serialize(string path, Action<IGenericWriter> serializer)
{
Serialize(new FileInfo(path), serializer);
}
public static void Serialize(FileInfo file, Action<IGenericWriter> 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<IGenericReader> deserializer)
{
Deserialize(path, deserializer, true);
}
public static void Deserialize(FileInfo file, Action<IGenericReader> deserializer)
{
Deserialize(file, deserializer, true);
}
public static void Deserialize(string path, Action<IGenericReader> deserializer, bool ensure)
{
Deserialize(new FileInfo(path), deserializer, ensure);
}
public static void Deserialize(FileInfo file, Action<IGenericReader> 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();
}
}
}
}

View file

@ -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<IndexInfo> _orderedIndexInfo = new List<IndexInfo>();
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;
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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<Item> _decayQueue;
public StandardSaveStrategy() => _decayQueue = new Queue<Item>();
// 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();
}
}
}
}
}

View file

@ -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;

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
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<MemoryStream> 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<MemoryStream>();
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>(T value) where T : Item
{
Write(value);
}
public void WriteMobile<T>(T value) where T : Mobile
{
Write(value);
}
public void WriteGuild<T>(T value) where T : BaseGuild
{
Write(value);
}
public void Write(List<Item> list)
{
WriteItemList(list);
}
public void Write(List<Item> list, bool tidy)
{
WriteItemList(list, tidy);
}
public void WriteItemList<T>(List<T> list) where T : Item
{
WriteItemList(list, false);
}
public void WriteItemList<T>(List<T> 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<Item> set)
{
Write(set, false);
}
public void Write(HashSet<Item> set, bool tidy)
{
if (tidy)
{
set.RemoveWhere(item => item.Deleted);
}
Write(set.Count);
foreach (var item in set)
{
Write(item);
}
}
public void WriteItemSet<T>(HashSet<T> set) where T : Item
{
WriteItemSet(set, false);
}
public void WriteItemSet<T>(HashSet<T> 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<Mobile> list)
{
Write(list, false);
}
public void Write(List<Mobile> 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<T>(List<T> list) where T : Mobile
{
WriteMobileList(list, false);
}
public void WriteMobileList<T>(List<T> 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<Mobile> set)
{
Write(set, false);
}
public void Write(HashSet<Mobile> set, bool tidy)
{
if (tidy)
{
set.RemoveWhere(mobile => mobile.Deleted);
}
Write(set.Count);
foreach (var mob in set)
{
Write(mob);
}
}
public void WriteMobileSet<T>(HashSet<T> set) where T : Mobile
{
WriteMobileSet(set, false);
}
public void WriteMobileSet<T>(HashSet<T> 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<BaseGuild> list)
{
Write(list, false);
}
public void Write(List<BaseGuild> 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<T>(List<T> list) where T : BaseGuild
{
WriteGuildList(list, false);
}
public void WriteGuildList<T>(List<T> 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<BaseGuild> set)
{
Write(set, false);
}
public void Write(HashSet<BaseGuild> set, bool tidy)
{
if (tidy)
{
set.RemoveWhere(guild => guild.Disbanded);
}
Write(set.Count);
foreach (var guild in set)
{
Write(guild);
}
}
public void WriteGuildSet<T>(HashSet<T> set) where T : BaseGuild
{
WriteGuildSet(set, false);
}
public void WriteGuildSet<T>(HashSet<T> 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();
}
}
}
}
}

View file

@ -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<T>() where T : Item => ReadItem() as T;

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
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>(T value) where T : Item
{
Write(value);
}
public void WriteMobile<T>(T value) where T : Mobile
{
Write(value);
}
public void WriteGuild<T>(T value) where T : BaseGuild
{
Write(value);
}
public void Write(List<Item> list)
{
WriteItemList(list);
}
public void Write(List<Item> list, bool tidy)
{
WriteItemList(list, tidy);
}
public void WriteItemList<T>(List<T> list) where T : Item
{
WriteItemList(list, false);
}
public void WriteItemList<T>(List<T> 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<Item> set)
{
Write(set, false);
}
public void Write(HashSet<Item> set, bool tidy)
{
if (tidy)
{
set.RemoveWhere(item => item.Deleted);
}
Write(set.Count);
foreach (var item in set)
{
Write(item);
}
}
public void WriteItemSet<T>(HashSet<T> set) where T : Item
{
WriteItemSet(set, false);
}
public void WriteItemSet<T>(HashSet<T> 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<Mobile> list)
{
Write(list, false);
}
public void Write(List<Mobile> 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<T>(List<T> list) where T : Mobile
{
WriteMobileList(list, false);
}
public void WriteMobileList<T>(List<T> 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<Mobile> set)
{
Write(set, false);
}
public void Write(HashSet<Mobile> set, bool tidy)
{
if (tidy)
{
set.RemoveWhere(mobile => mobile.Deleted);
}
Write(set.Count);
foreach (var mob in set)
{
Write(mob);
}
}
public void WriteMobileSet<T>(HashSet<T> set) where T : Mobile
{
WriteMobileSet(set, false);
}
public void WriteMobileSet<T>(HashSet<T> 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<BaseGuild> list)
{
Write(list, false);
}
public void Write(List<BaseGuild> 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<T>(List<T> list) where T : BaseGuild
{
WriteGuildList(list, false);
}
public void WriteGuildList<T>(List<T> 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<BaseGuild> set)
{
Write(set, false);
}
public void Write(HashSet<BaseGuild> set, bool tidy)
{
if (tidy)
{
set.RemoveWhere(guild => guild.Disbanded);
}
Write(set.Count);
foreach (var guild in set)
{
Write(guild);
}
}
public void WriteGuildSet<T>(HashSet<T> set) where T : BaseGuild
{
WriteGuildSet(set, false);
}
public void WriteGuildSet<T>(HashSet<T> 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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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>(T value) where T : Item;
void WriteMobile<T>(T value) where T : Mobile;
void WriteGuild<T>(T value) where T : BaseGuild;
void Write(Race value);
void Write(List<Item> list);
void Write(List<Item> list, bool tidy);
void WriteItemList<T>(List<T> list) where T : Item;
void WriteItemList<T>(List<T> list, bool tidy) where T : Item;
void Write(HashSet<Item> list);
void Write(HashSet<Item> list, bool tidy);
void WriteItemSet<T>(HashSet<T> set) where T : Item;
void WriteItemSet<T>(HashSet<T> set, bool tidy) where T : Item;
void Write(List<Mobile> list);
void Write(List<Mobile> list, bool tidy);
void WriteMobileList<T>(List<T> list) where T : Mobile;
void WriteMobileList<T>(List<T> list, bool tidy) where T : Mobile;
void Write(HashSet<Mobile> list);
void Write(HashSet<Mobile> list, bool tidy);
void WriteMobileSet<T>(HashSet<T> set) where T : Mobile;
void WriteMobileSet<T>(HashSet<T> set, bool tidy) where T : Mobile;
void Write(List<BaseGuild> list);
void Write(List<BaseGuild> list, bool tidy);
void WriteGuildList<T>(List<T> list) where T : BaseGuild;
void WriteGuildList<T>(List<T> list, bool tidy) where T : BaseGuild;
void Write(HashSet<BaseGuild> list);
void Write(HashSet<BaseGuild> list, bool tidy);
void WriteGuildSet<T>(HashSet<T> set) where T : BaseGuild;
void WriteGuildSet<T>(HashSet<T> set, bool tidy) where T : BaseGuild;
long Seek(long offset, SeekOrigin origin);
}
}

View file

@ -13,14 +13,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
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();
}
}

View file

@ -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<IEntity> _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<Type> m_ItemTypes = new List<Type>();
internal static List<Type> m_MobileTypes = new List<Type>();
public static bool Saving { get; private set; }
public static bool Loaded { get; private set; }
public static bool Loading { get; private set; }
public static Dictionary<Serial, Mobile> Mobiles { get; private set; }
public static Dictionary<Serial, Item> 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<Tuple<ConstructorInfo, string>> ReadTypes(BinaryReader tdbReader)
{
var count = tdbReader.ReadInt32();
var types = new List<Tuple<ConstructorInfo, string>>(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<ConstructorInfo, string>(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<IEntity>();
_deleteQueue = new Queue<IEntity>();
int mobileCount, itemCount, guildCount;
var ctorArgs = new object[1];
var items = new List<ItemEntry>();
var mobiles = new List<MobileEntry>();
var guilds = new List<GuildEntry>();
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<Serial, Mobile>(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<Serial, Mobile>();
}
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<Serial, Item>(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<Serial, Item>();
}
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<T>(List<T> 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; }
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using Server.Guilds;
namespace Server
{
public class CreateGuildEventArgs : EventArgs
public readonly struct EntityIndex<T> 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<CreateGuildEventArgs> 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;
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
namespace Server
{
public readonly struct EntityTypeIndex : IIndexInfo<Serial>
{
public string TypeName { get; }
public EntityTypeIndex(string typeName) => TypeName = typeName;
public Serial CreateIndex(uint num) => num;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
namespace Server
{
public interface IIndexInfo<I>
{
I CreateIndex(uint num);
string TypeName { get; }
}
}

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
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<Serial, IEntity> _pendingAdd;
private static Dictionary<Serial, IEntity> _pendingDelete;
private static ConcurrentQueue<Item> _decayQueue = new ConcurrentQueue<Item>();
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<Type> ItemTypes { get; } = new List<Type>();
internal static List<Type> MobileTypes { get; } = new List<Type>();
internal static List<Type> GuildTypes { get; } = new List<Type>();
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<Serial, Mobile> Mobiles { get; private set; }
public static Dictionary<Serial, Item> Items { get; private set; }
public static Dictionary<Serial, BaseGuild> 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<Tuple<ConstructorInfo, string>> ReadTypes<I>(BinaryReader tdbReader)
{
var constructorTypes = new[] { typeof(I) };
var count = tdbReader.ReadInt32();
var types = new List<Tuple<ConstructorInfo, string>>(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<ConstructorInfo, string>(ctor, typeName));
}
else
{
throw new Exception($"Type '{t}' does not have a serialization constructor");
}
}
return types;
}
private static Dictionary<I, T> LoadIndex<I, T>(IIndexInfo<I> indexInfo, out List<EntityIndex<T>> entities) where T : class, ISerializable
{
var map = new Dictionary<I, T>();
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<EntityIndex<T>>();
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<Tuple<ConstructorInfo, string>> types = ReadTypes<I>(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<ConstructorInfo, string> 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>(t, typeID, pos, length));
map[indexer] = t;
}
}
tdbReader.Close();
}
idxReader.Close();
return map;
}
private static void LoadData<I, T>(IIndexInfo<I> indexInfo, List<EntityIndex<T>> 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<Serial, IEntity>();
_pendingDelete = new Dictionary<Serial, IEntity>();
List<EntityIndex<Item>> items;
List<EntityIndex<Mobile>> mobiles;
List<EntityIndex<BaseGuild>> guilds;
IIndexInfo<Serial> itemIndexInfo = new EntityTypeIndex("Items");
IIndexInfo<Serial> mobileIndexInfo = new EntityTypeIndex("Mobiles");
IIndexInfo<Serial> 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<Serial> itemIndexInfo = new EntityTypeIndex("Items");
IIndexInfo<Serial> mobileIndexInfo = new EntityTypeIndex("Mobiles");
IIndexInfo<Serial> 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<I, T>(IIndexInfo<I> indexInfo, Dictionary<I, T> entities, List<Type> 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<T>(IEnumerable<T> 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<IEntity>(serial);
public static T FindEntity<T>(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<Item>(serial, returnDeleted);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Mobile FindMobile(Serial serial, bool returnDeleted = false) =>
FindEntity<Mobile>(serial, returnDeleted);
public static BaseGuild FindGuild(Serial serial) => Guilds.TryGetValue(serial, out var guild) ? guild : null;
public static void AddEntity<T>(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>(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);
}
}
}

View file

@ -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");

View file

@ -405,7 +405,7 @@ namespace Server.Engines.Plants
GrowAll();
}
private static void EventSink_WorldSave(bool message)
private static void EventSink_WorldSave()
{
GrowAll();
}

View file

@ -26,7 +26,7 @@ namespace Server.Guilds
"",
0,
GuildDisplayType.All,
Utility.CastListCovariant<BaseGuild, Guild>(new List<BaseGuild>(BaseGuild.List.Values)),
Utility.CastListCovariant<BaseGuild, Guild>(new List<BaseGuild>(World.Guilds.Values)),
1063136 + (int)GuildDisplayType.All
)
{
@ -44,7 +44,7 @@ namespace Server.Guilds
filter,
startNumber,
display,
Utility.CastListCovariant<BaseGuild, Guild>(new List<BaseGuild>(BaseGuild.List.Values)),
Utility.CastListCovariant<BaseGuild, Guild>(new List<BaseGuild>(World.Guilds.Values)),
1063136 + (int)display
)
{

View file

@ -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)
{

View file

@ -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()

View file

@ -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<WarDeclaration>();
}
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();

View file

@ -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();
}

View file

@ -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"
}