Fixes bugs in world save (#297)

- [X] Fixes bugs in the buffer writer
- [X] Removes background save commands
- [X] Avoids calling Serialize() on deserialization
    - In a future optimization I will copy the entire object to a buffer, deserialize using a SpanReader, then use that buffer for the SaveBuffer
- [X] Fixes issue with decay queue.

Bumps release version
This commit is contained in:
Kamron Batman 2020-11-01 17:22:18 -08:00 • committed by GitHub
parent daf48ebdf2
commit 55935d30b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 122 additions and 153 deletions

View file

@ -38,10 +38,10 @@ namespace Server.Guilds
public int TypeRef => 0; public int TypeRef => 0;
public void Serialize(DateTime serializeStart) public void Serialize()
{ {
SaveBuffer ??= new BufferWriter(true); SaveBuffer ??= new BufferWriter(true);
SaveBuffer.Flush(); SaveBuffer.Reset();
Serialize(SaveBuffer); Serialize(SaveBuffer);
} }

View file

@ -99,7 +99,7 @@ namespace Server
&& p.Y >= Location.m_Y - range && p.Y >= Location.m_Y - range
&& p.Y <= Location.m_Y + range; && p.Y <= Location.m_Y + range;
public void Serialize(DateTime serializeStart) public void Serialize()
{ {
} }

View file

@ -826,15 +826,10 @@ namespace Server
public int TypeRef { get; } public int TypeRef { get; }
public void Serialize(DateTime serializeStart) public void Serialize()
{ {
if (Decays && Parent == null && Map != Map.Internal && LastMoved + DecayTime <= serializeStart)
{
World.EnqueueForDecay(this);
}
SaveBuffer ??= new BufferWriter(true); SaveBuffer ??= new BufferWriter(true);
SaveBuffer.Flush(); SaveBuffer.Reset();
Serialize(SaveBuffer); Serialize(SaveBuffer);
} }
@ -2393,8 +2388,12 @@ namespace Server
public bool AtPoint(int x, int y) => m_Location.m_X == x && m_Location.m_Y == y; public bool AtPoint(int x, int y) => m_Location.m_X == x && m_Location.m_Y == y;
public virtual bool CanDecay() =>
Decays && Parent == null && Map != Map.Internal;
public virtual bool OnDecay() => public virtual bool OnDecay() =>
Decays && Parent == null && Map != Map.Internal && Region.Find(Location, Map).OnDecay(this); CanDecay() && Region.Find(Location, Map).OnDecay(this);
public void SetLastMoved() public void SetLastMoved()
{ {

View file

@ -2638,10 +2638,10 @@ namespace Server
public int TypeRef { get; } public int TypeRef { get; }
public void Serialize(DateTime serializeStart) public void Serialize()
{ {
SaveBuffer ??= new BufferWriter(true); SaveBuffer ??= new BufferWriter(true);
SaveBuffer.Flush(); SaveBuffer.Reset();
Serialize(SaveBuffer); Serialize(SaveBuffer);
} }

View file

@ -145,22 +145,17 @@ namespace Server
{ {
var count = ReadInt(); var count = ReadInt();
if (count > 0) var list = new List<T>(count);
for (var i = 0; i < count; ++i)
{ {
var list = new List<T>(count); if (ReadItem() is T item)
for (var i = 0; i < count; ++i)
{ {
if (ReadItem() is T item) list.Add(item);
{
list.Add(item);
}
} }
return list;
} }
return new List<T>(); return list;
} }
public HashSet<Item> ReadItemSet() => ReadItemSet<Item>(); public HashSet<Item> ReadItemSet() => ReadItemSet<Item>();
@ -169,22 +164,17 @@ namespace Server
{ {
var count = ReadInt(); var count = ReadInt();
if (count > 0) var set = new HashSet<T>();
for (var i = 0; i < count; ++i)
{ {
var set = new HashSet<T>(); if (ReadItem() is T item)
for (var i = 0; i < count; ++i)
{ {
if (ReadItem() is T item) set.Add(item);
{
set.Add(item);
}
} }
return set;
} }
return new HashSet<T>(); return set;
} }
public List<Mobile> ReadStrongMobileList() => ReadStrongMobileList<Mobile>(); public List<Mobile> ReadStrongMobileList() => ReadStrongMobileList<Mobile>();
@ -193,22 +183,17 @@ namespace Server
{ {
var count = ReadInt(); var count = ReadInt();
if (count > 0) var list = new List<T>(count);
for (var i = 0; i < count; ++i)
{ {
var list = new List<T>(count); if (ReadMobile() is T m)
for (var i = 0; i < count; ++i)
{ {
if (ReadMobile() is T m) list.Add(m);
{
list.Add(m);
}
} }
return list;
} }
return new List<T>(); return list;
} }
public HashSet<Mobile> ReadMobileSet() => ReadMobileSet<Mobile>(); public HashSet<Mobile> ReadMobileSet() => ReadMobileSet<Mobile>();
@ -216,23 +201,17 @@ namespace Server
public HashSet<T> ReadMobileSet<T>() where T : Mobile public HashSet<T> ReadMobileSet<T>() where T : Mobile
{ {
var count = ReadInt(); var count = ReadInt();
var set = new HashSet<T>();
if (count > 0) for (var i = 0; i < count; ++i)
{ {
var set = new HashSet<T>(); if (ReadMobile() is T item)
for (var i = 0; i < count; ++i)
{ {
if (ReadMobile() is T item) set.Add(item);
{
set.Add(item);
}
} }
return set;
} }
return new HashSet<T>(); return set;
} }
public List<BaseGuild> ReadStrongGuildList() => ReadStrongGuildList<BaseGuild>(); public List<BaseGuild> ReadStrongGuildList() => ReadStrongGuildList<BaseGuild>();
@ -240,11 +219,10 @@ namespace Server
public List<T> ReadStrongGuildList<T>() where T : BaseGuild public List<T> ReadStrongGuildList<T>() where T : BaseGuild
{ {
var count = ReadInt(); var count = ReadInt();
var list = new List<T>(count);
if (count > 0) if (count > 0)
{ {
var list = new List<T>(count);
for (var i = 0; i < count; ++i) for (var i = 0; i < count; ++i)
{ {
if (ReadGuild() is T g) if (ReadGuild() is T g)
@ -252,11 +230,9 @@ namespace Server
list.Add(g); list.Add(g);
} }
} }
return list;
} }
return new List<T>(); return list;
} }
public HashSet<BaseGuild> ReadGuildSet() => ReadGuildSet<BaseGuild>(); public HashSet<BaseGuild> ReadGuildSet() => ReadGuildSet<BaseGuild>();
@ -265,10 +241,10 @@ namespace Server
{ {
var count = ReadInt(); var count = ReadInt();
var set = new HashSet<T>();
if (count > 0) if (count > 0)
{ {
var set = new HashSet<T>();
for (var i = 0; i < count; ++i) for (var i = 0; i < count; ++i)
{ {
if (ReadGuild() is T item) if (ReadGuild() is T item)
@ -276,17 +252,17 @@ namespace Server
set.Add(item); set.Add(item);
} }
} }
return set;
} }
return new HashSet<T>(); return set;
} }
public Race ReadRace() => Race.Races[ReadByte()]; public Race ReadRace() => Race.Races[ReadByte()];
public bool End() => m_File.PeekChar() == -1; public bool End() => m_File.PeekChar() == -1;
public int Read(Span<byte> buffer) => m_File.Read(buffer);
public void Close() public void Close()
{ {
m_File.Close(); m_File.Close();

View file

@ -20,11 +20,18 @@ namespace Server
public class BinaryFileWriter : BufferWriter public class BinaryFileWriter : BufferWriter
{ {
private readonly Stream m_File; private readonly Stream m_File;
private long m_Position;
public BinaryFileWriter(string filename, bool prefixStr) : base(prefixStr) => public BinaryFileWriter(string filename, bool prefixStr) : base(prefixStr) =>
m_File = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); m_File = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
public BinaryFileWriter(Stream stream, bool prefixStr) : base(prefixStr) => m_File = stream; public BinaryFileWriter(Stream stream, bool prefixStr) : base(prefixStr)
{
m_File = stream;
m_Position = m_File.Position;
}
public override long Position => m_Position + m_Index;
protected override int BufferSize => 512; protected override int BufferSize => 512;
@ -35,21 +42,24 @@ namespace Server
{ {
m_Position += m_Index; m_Position += m_Index;
m_File.Write(m_Buffer, 0, m_Index); m_File.Write(m_Buffer, 0, (int)m_Index);
m_Index = 0; m_Index = 0;
} }
} }
public override void Close() public override void Close()
{ {
base.Close(); if (m_Index > 0)
{
Flush();
}
m_File.Close(); m_File.Close();
} }
public override long Seek(long offset, SeekOrigin origin) public override long Seek(long offset, SeekOrigin origin)
{ {
m_Position += m_Index; Flush();
m_Index = 0;
return m_Position = m_File.Seek(offset, origin); return m_Position = m_File.Seek(offset, origin);
} }

View file

@ -31,8 +31,7 @@ namespace Server
private readonly bool m_PrefixStrings; private readonly bool m_PrefixStrings;
protected byte[] m_Buffer; protected byte[] m_Buffer;
protected int m_Index; protected long m_Index;
protected long m_Position;
private readonly char[] m_SingleCharBuffer = new char[1]; private readonly char[] m_SingleCharBuffer = new char[1];
@ -40,6 +39,13 @@ namespace Server
private int m_MaxBufferChars; private int m_MaxBufferChars;
public BufferWriter(byte[] buffer, bool prefixStr)
{
m_PrefixStrings = prefixStr;
m_Encoding = Utility.UTF8;
m_Buffer = buffer;
}
public BufferWriter(bool prefixStr) public BufferWriter(bool prefixStr)
{ {
m_PrefixStrings = prefixStr; m_PrefixStrings = prefixStr;
@ -47,29 +53,23 @@ namespace Server
m_Buffer = new byte[BufferSize]; m_Buffer = new byte[BufferSize];
} }
public virtual long Position => m_Index;
protected virtual int BufferSize => 256; protected virtual int BufferSize => 256;
public byte[] Data => m_Buffer; public byte[] Data => m_Buffer;
public long Position
{
get => m_Position + m_Index;
set => Seek(value, value < 0 ? SeekOrigin.End : SeekOrigin.Begin);
}
public virtual void Close() public virtual void Close()
{ {
if (m_Index > 0)
{
Flush();
}
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Resize(int size) public void Resize(int size)
{ {
Array.Resize(ref m_Buffer, size); var copy = new byte[size];
Buffer.BlockCopy(m_Buffer, 0, copy, 0, Math.Min(size, m_Buffer.Length));
m_Buffer = copy;
} }
public virtual void Flush() public virtual void Flush()
@ -77,16 +77,19 @@ namespace Server
Resize(m_Buffer.Length * 2); Resize(m_Buffer.Length * 2);
} }
public void Reset()
{
m_Index = 0;
}
public virtual long Seek(long offset, SeekOrigin origin) public virtual long Seek(long offset, SeekOrigin origin)
{ {
Flush();
return origin switch return origin switch
{ {
SeekOrigin.Begin => m_Position = offset, SeekOrigin.Begin => m_Index = offset,
SeekOrigin.Current => m_Position += offset, SeekOrigin.Current => m_Index += offset,
SeekOrigin.End => m_Position = BufferSize - offset, SeekOrigin.End => m_Index = BufferSize - offset,
_ => m_Position _ => m_Index
}; };
} }
@ -321,7 +324,7 @@ namespace Server
m_SingleCharBuffer[0] = value; m_SingleCharBuffer[0] = value;
var byteCount = m_Encoding.GetBytes(m_SingleCharBuffer, 0, 1, m_Buffer, m_Index); var byteCount = m_Encoding.GetBytes(m_SingleCharBuffer, 0, 1, m_Buffer, (int)m_Index);
m_Index += byteCount; m_Index += byteCount;
} }
@ -337,13 +340,13 @@ namespace Server
public void Write(byte[] value, int length) public void Write(byte[] value, int length)
{ {
int remaining = length; var remaining = length;
int idx = 0; var idx = 0;
while (remaining > 0) while (remaining > 0)
{ {
int size = Math.Min(m_Buffer.Length - m_Index, remaining); int size = Math.Min(m_Buffer.Length - (int)m_Index, remaining);
Buffer.BlockCopy(value, idx, m_Buffer, m_Index, size); Buffer.BlockCopy(value, idx, m_Buffer, (int)m_Index, size);
// value.Slice(idx).CopyTo(m_Buffer.AsSpan(m_Index, size)); // value.Slice(idx).CopyTo(m_Buffer.AsSpan(m_Index, size));
remaining -= size; remaining -= size;
@ -775,7 +778,7 @@ namespace Server
Flush(); Flush();
} }
Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, (int)m_Index, byteLength);
m_Index += byteLength; m_Index += byteLength;
current += charCount; current += charCount;
@ -791,7 +794,7 @@ namespace Server
Flush(); Flush();
} }
Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, (int)m_Index, byteLength);
m_Index += byteLength; m_Index += byteLength;
} }
} }

View file

@ -68,5 +68,6 @@ namespace Server
HashSet<T> ReadGuildSet<T>() where T : BaseGuild; HashSet<T> ReadGuildSet<T>() where T : BaseGuild;
Race ReadRace(); Race ReadRace();
bool End(); bool End();
int Read(Span<byte> buffer);
} }
} }

View file

@ -13,8 +13,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * * along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/ *************************************************************************/
using System;
namespace Server namespace Server
{ {
public interface ISerializable public interface ISerializable
@ -22,7 +20,7 @@ namespace Server
BufferWriter SaveBuffer { get; set; } BufferWriter SaveBuffer { get; set; }
int TypeRef { get; } int TypeRef { get; }
Serial Serial { get; } Serial Serial { get; }
void Serialize(DateTime serializeStart); void Serialize();
void Deserialize(IGenericReader reader); void Deserialize(IGenericReader reader);
void Serialize(IGenericWriter writer); void Serialize(IGenericWriter writer);
void Delete(); void Delete();

View file

@ -137,7 +137,7 @@ namespace Server
m_DiskWriteHandle.WaitOne(); m_DiskWriteHandle.WaitOne();
} }
public static void EnqueueForDecay(Item item) private static void EnqueueForDecay(Item item)
{ {
if (WorldState != WorldState.Saving) if (WorldState != WorldState.Saving)
{ {
@ -338,6 +338,11 @@ namespace Server
} }
t.Delete(); t.Delete();
} }
reader.Seek(entry.Position, SeekOrigin.Begin);
t.SaveBuffer = new BufferWriter(new byte[entry.Length], true);
reader.Read(t.SaveBuffer.Data);
} }
reader.Close(); reader.Close();
@ -381,8 +386,6 @@ namespace Server
ProcessSafetyQueues(); ProcessSafetyQueues();
var now = DateTime.UtcNow;
foreach (var item in Items.Values) foreach (var item in Items.Values)
{ {
if (item.Parent == null) if (item.Parent == null)
@ -391,7 +394,6 @@ namespace Server
} }
item.ClearProperties(); item.ClearProperties();
item.Serialize(now);
} }
foreach (var m in Mobiles.Values) foreach (var m in Mobiles.Values)
@ -400,12 +402,6 @@ namespace Server
m.UpdateTotals(); m.UpdateTotals();
m.ClearProperties(); m.ClearProperties();
m.Serialize(now);
}
foreach (var g in Guilds.Values)
{
g.Serialize(now);
} }
watch.Stop(); watch.Stop();
@ -466,6 +462,9 @@ namespace Server
public static void WriteFiles(object state) public static void WriteFiles(object state)
{ {
Console.Write("Closing Save Files...");
var watch = Stopwatch.StartNew();
IIndexInfo<Serial> itemIndexInfo = new EntityTypeIndex("Items"); IIndexInfo<Serial> itemIndexInfo = new EntityTypeIndex("Items");
IIndexInfo<Serial> mobileIndexInfo = new EntityTypeIndex("Mobiles"); IIndexInfo<Serial> mobileIndexInfo = new EntityTypeIndex("Mobiles");
IIndexInfo<Serial> guildIndexInfo = new EntityTypeIndex("Guilds"); IIndexInfo<Serial> guildIndexInfo = new EntityTypeIndex("Guilds");
@ -474,10 +473,11 @@ namespace Server
WriteEntities(mobileIndexInfo, Mobiles, MobileTypes); WriteEntities(mobileIndexInfo, Mobiles, MobileTypes);
WriteEntities(guildIndexInfo, Guilds, GuildTypes); WriteEntities(guildIndexInfo, Guilds, GuildTypes);
if (m_DiskWriteHandle.Set()) watch.Stop();
{
Console.WriteLine("Closing Save Files."); m_DiskWriteHandle.Set();
}
Console.WriteLine("done {0:F1} seconds.", watch.Elapsed.TotalSeconds);
Timer.DelayCall(FinishWorldSave); Timer.DelayCall(FinishWorldSave);
} }
@ -525,8 +525,13 @@ namespace Server
private static void SaveEntities<T>(IEnumerable<T> list, DateTime serializeStart) where T : class, ISerializable private static void SaveEntities<T>(IEnumerable<T> list, DateTime serializeStart) where T : class, ISerializable
{ {
Parallel.ForEach(list, (t, now) => { Parallel.ForEach(list, t => {
t.Serialize(serializeStart); if (t is Item item && item.CanDecay() && item.LastMoved + item.DecayTime <= serializeStart)
{
EnqueueForDecay(item);
}
t.Serialize();
}); });
} }
@ -551,13 +556,13 @@ namespace Server
return; return;
} }
WaitForWriteCompletion(); // Blocks Save until current disk flush is done.s
++_Saves; ++_Saves;
NetState.FlushAll(); NetState.FlushAll();
NetState.Pause(); NetState.Pause();
WaitForWriteCompletion(); // Blocks Save until current disk flush is done.
WorldState = WorldState.Saving; WorldState = WorldState.Saving;
m_DiskWriteHandle.Reset(); m_DiskWriteHandle.Reset();
@ -593,12 +598,11 @@ namespace Server
Console.WriteLine("Save done in {0:F2} seconds.", duration); Console.WriteLine("Save done in {0:F2} seconds.", duration);
Broadcast( // Only broadcast if it took at least 150ms
0x35, if (duration >= 0.15)
true, {
"World save complete. The entire process took {0:F2} seconds.", Broadcast(0x35, true, $"World save completed in {duration:F2} seconds.");
duration }
);
NetState.Resume(); NetState.Resume();
} }

View file

@ -44,9 +44,6 @@ namespace Server.Commands
Register("Help", AccessLevel.Player, Help_OnCommand); Register("Help", AccessLevel.Player, Help_OnCommand);
Register("Save", AccessLevel.Administrator, Save_OnCommand); Register("Save", AccessLevel.Administrator, Save_OnCommand);
Register("BackgroundSave", AccessLevel.Administrator, BackgroundSave_OnCommand);
Register("BGSave", AccessLevel.Administrator, BackgroundSave_OnCommand);
Register("SaveBG", AccessLevel.Administrator, BackgroundSave_OnCommand);
Register("Move", AccessLevel.GameMaster, Move_OnCommand); Register("Move", AccessLevel.GameMaster, Move_OnCommand);
Register("Client", AccessLevel.Counselor, Client_OnCommand); Register("Client", AccessLevel.Counselor, Client_OnCommand);
@ -482,20 +479,9 @@ namespace Server.Commands
AutoSave.Save(); AutoSave.Save();
} }
[Usage("BackgroundSave")] private static bool FixMap(ref Map map, ref Point3D loc, Item item) =>
[Aliases("BGSave", "SaveBG")] map != null && map != Map.Internal || item.RootParent is Mobile m &&
[Description("Saves the world, writing to the disk in the background")] FixMap(ref map, ref loc, m);
private static void BackgroundSave_OnCommand(CommandEventArgs e)
{
AutoSave.Save(true);
}
private static bool FixMap(ref Map map, ref Point3D loc, Item item) => map != null && map != Map.Internal ||
item.RootParent is Mobile m && FixMap(
ref map,
ref loc,
m
);
private static bool FixMap(ref Map map, ref Point3D loc, Mobile m) private static bool FixMap(ref Map map, ref Point3D loc, Mobile m)
{ {

View file

@ -50,7 +50,7 @@ namespace Server.Misc
if (m_Warning == TimeSpan.Zero) if (m_Warning == TimeSpan.Zero)
{ {
Save(true); Save();
} }
else else
{ {
@ -85,12 +85,7 @@ namespace Server.Misc
public static void Save() public static void Save()
{ {
Save(false); if (AutoRestart.Restarting || !World.Running)
}
public static void Save(bool permitBackgroundWrite)
{
if (AutoRestart.Restarting)
{ {
return; return;
} }
@ -118,10 +113,7 @@ namespace Server.Misc
var root = Path.Combine(Core.BaseDirectory, "Backups/Automatic"); var root = Path.Combine(Core.BaseDirectory, "Backups/Automatic");
if (!Directory.Exists(root)) AssemblyHandler.EnsureDirectory(root);
{
Directory.CreateDirectory(root);
}
var existing = Directory.GetDirectories(root); var existing = Directory.GetDirectories(root);