fix(core): Fixes BufferWriter Seek and removes World.SaveBuffers (#359)
- [X] Fixes an issue in BufferWriter where if SeekOrigin.End is used, it will yield the wrong index. - [X] Changes BinaryFileWriter to dispose pattern - [X] Removes World.SaveBuffers. They were dangerous and should be done in-line while the world is loading, even if it is slower - [X] Fixes BufferReader Seek too - [X] Changes BufferWriter to use uninitialized arrays to speed up resizing - [X] Changes World loading so it doesn't buffer the whole file, even if it makes things slower. It lowers allocations/fragmentation over all which is good. - [X] Changes World Loading to use uninitialized arrays and directly saves them to the SaveBuffer which eliminates having to open/load the files _again_ which is dangerous. Also looping through items while the world is running is dangerous since async references can and will cause exceptions. Bumps release version
This commit is contained in:
parent
4ddb3de026
commit
c2143584bc
4 changed files with 178 additions and 134 deletions
|
|
@ -13,36 +13,38 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class BinaryFileWriter : BufferWriter
|
||||
public class BinaryFileWriter : BufferWriter, IDisposable
|
||||
{
|
||||
private readonly Stream m_File;
|
||||
private long m_Position;
|
||||
private readonly Stream _file;
|
||||
private long _position;
|
||||
|
||||
public BinaryFileWriter(string filename, bool prefixStr) : base(prefixStr) =>
|
||||
m_File = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
public BinaryFileWriter(string filename, bool prefixStr) :
|
||||
this(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None), prefixStr)
|
||||
{}
|
||||
|
||||
public BinaryFileWriter(Stream stream, bool prefixStr) : base(prefixStr)
|
||||
{
|
||||
m_File = stream;
|
||||
m_Position = m_File.Position;
|
||||
_file = stream;
|
||||
_position = _file.Position;
|
||||
}
|
||||
|
||||
public override long Position => m_Position + Index;
|
||||
public override long Position => _position + Index;
|
||||
|
||||
|
||||
protected override int BufferSize => 512;
|
||||
protected override int BufferSize => 81920;
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
if (Index > 0)
|
||||
{
|
||||
m_Position += Index;
|
||||
_position += Index;
|
||||
|
||||
m_File.Write(Buffer, 0, (int)Index);
|
||||
_file.Write(Buffer, 0, (int)Index);
|
||||
Index = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -54,14 +56,19 @@ namespace Server
|
|||
Flush();
|
||||
}
|
||||
|
||||
m_File.Close();
|
||||
_file.Close();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
Flush();
|
||||
|
||||
return m_Position = m_File.Seek(offset, origin);
|
||||
return _position = _file.Seek(offset, origin);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
|
@ -27,7 +28,11 @@ namespace Server
|
|||
{
|
||||
private readonly Encoding _encoding;
|
||||
private byte[] _buffer;
|
||||
public int Position { get; private set; }
|
||||
private int _position;
|
||||
|
||||
public int Position => _position;
|
||||
|
||||
public byte[] Buffer => _buffer;
|
||||
|
||||
public BufferReader(byte[] buffer)
|
||||
{
|
||||
|
|
@ -35,6 +40,13 @@ namespace Server
|
|||
_encoding = Utility.UTF8;
|
||||
}
|
||||
|
||||
public void SwapBuffers(byte[] newBuffer, out byte[] oldBuffer)
|
||||
{
|
||||
oldBuffer = _buffer;
|
||||
_buffer = newBuffer;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public string ReadString()
|
||||
{
|
||||
if (!ReadBool())
|
||||
|
|
@ -49,7 +61,7 @@ namespace Server
|
|||
}
|
||||
|
||||
var s = _encoding.GetString(_buffer.AsSpan(Position, length));
|
||||
Position += length;
|
||||
_position += length;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
|
@ -64,64 +76,64 @@ namespace Server
|
|||
public long ReadLong()
|
||||
{
|
||||
var v = BinaryPrimitives.ReadInt64LittleEndian(_buffer.AsSpan(Position, 8));
|
||||
Position += 8;
|
||||
_position += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
public ulong ReadULong()
|
||||
{
|
||||
var v = BinaryPrimitives.ReadUInt64LittleEndian(_buffer.AsSpan(Position, 8));
|
||||
Position += 8;
|
||||
_position += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
public int ReadInt()
|
||||
{
|
||||
var v = BinaryPrimitives.ReadInt32LittleEndian(_buffer.AsSpan(Position, 4));
|
||||
Position += 4;
|
||||
_position += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
public uint ReadUInt()
|
||||
{
|
||||
var v = BinaryPrimitives.ReadUInt32LittleEndian(_buffer.AsSpan(Position, 4));
|
||||
Position += 4;
|
||||
_position += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
public short ReadShort()
|
||||
{
|
||||
var v = BinaryPrimitives.ReadInt16LittleEndian(_buffer.AsSpan(Position, 2));
|
||||
Position += 2;
|
||||
_position += 2;
|
||||
return v;
|
||||
}
|
||||
|
||||
public ushort ReadUShort()
|
||||
{
|
||||
var v = BinaryPrimitives.ReadUInt16LittleEndian(_buffer.AsSpan(Position, 2));
|
||||
Position += 2;
|
||||
_position += 2;
|
||||
return v;
|
||||
}
|
||||
|
||||
public double ReadDouble()
|
||||
{
|
||||
var v = BinaryPrimitives.ReadDoubleLittleEndian(_buffer.AsSpan(Position, 8));
|
||||
Position += 8;
|
||||
_position += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
public float ReadFloat()
|
||||
{
|
||||
var v = BinaryPrimitives.ReadSingleLittleEndian(_buffer.AsSpan(Position, 4));
|
||||
Position += 4;
|
||||
_position += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
public byte ReadByte() => _buffer[Position++];
|
||||
public byte ReadByte() => _buffer[_position++];
|
||||
|
||||
public sbyte ReadSByte() => (sbyte)_buffer[Position++];
|
||||
public sbyte ReadSByte() => (sbyte)_buffer[_position++];
|
||||
|
||||
public bool ReadBool() => _buffer[Position++] != 0;
|
||||
public bool ReadBool() => _buffer[_position++] != 0;
|
||||
|
||||
public int ReadEncodedInt()
|
||||
{
|
||||
|
|
@ -217,18 +229,31 @@ namespace Server
|
|||
}
|
||||
|
||||
_buffer.AsSpan(Position, length).CopyTo(buffer);
|
||||
Position += length;
|
||||
_position += length;
|
||||
return length;
|
||||
}
|
||||
|
||||
public virtual int Seek(int offset, SeekOrigin origin)
|
||||
{
|
||||
return origin switch
|
||||
Debug.Assert(
|
||||
origin != SeekOrigin.End || offset <= 0 && offset > -_buffer.Length,
|
||||
"Attempting to seek to an invalid position using SeekOrigin.End"
|
||||
);
|
||||
Debug.Assert(
|
||||
origin != SeekOrigin.Begin || offset >= 0 && offset < _buffer.Length,
|
||||
"Attempting to seek to an invalid position using SeekOrigin.Begin"
|
||||
);
|
||||
Debug.Assert(
|
||||
origin != SeekOrigin.Current || _position + offset >= 0 && _position + offset < _buffer.Length,
|
||||
"Attempting to seek to an invalid position using SeekOrigin.Current"
|
||||
);
|
||||
|
||||
return _position = Math.Max(0, origin switch
|
||||
{
|
||||
SeekOrigin.Current => Position += offset,
|
||||
SeekOrigin.End => Position = _buffer.Length - offset,
|
||||
_ => Position = offset // Begin
|
||||
};
|
||||
SeekOrigin.Current => _position + offset,
|
||||
SeekOrigin.End => _buffer.Length + offset,
|
||||
_ => offset // Begin
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
|
@ -26,8 +27,30 @@ namespace Server
|
|||
{
|
||||
private readonly Encoding m_Encoding;
|
||||
private readonly bool m_PrefixStrings;
|
||||
private long _bytesWritten;
|
||||
private long _index;
|
||||
|
||||
protected long Index
|
||||
{
|
||||
get => _index;
|
||||
set
|
||||
{
|
||||
if (value < 0 || value > _buffer.Length)
|
||||
{
|
||||
// If you are receiving this exception and your value is too large, you may need to use `Resize`
|
||||
// If you are receiving this exception and your value is negative, you probably used Seek incorrectly.
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
|
||||
_index = value;
|
||||
|
||||
if (value > _bytesWritten)
|
||||
{
|
||||
_bytesWritten = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected long Index { get; set; }
|
||||
private byte[] _buffer;
|
||||
|
||||
public BufferWriter(byte[] buffer, bool prefixStr)
|
||||
|
|
@ -37,18 +60,15 @@ namespace Server
|
|||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public BufferWriter(bool prefixStr)
|
||||
public BufferWriter(bool prefixStr) : this(0, prefixStr)
|
||||
{
|
||||
m_PrefixStrings = prefixStr;
|
||||
m_Encoding = Utility.UTF8;
|
||||
_buffer = GC.AllocateUninitializedArray<byte>(BufferSize);
|
||||
}
|
||||
|
||||
public BufferWriter(int count, bool prefixStr)
|
||||
{
|
||||
m_PrefixStrings = prefixStr;
|
||||
m_Encoding = Utility.UTF8;
|
||||
_buffer = GC.AllocateUninitializedArray<byte>(count);
|
||||
_buffer = GC.AllocateUninitializedArray<byte>(count < 1 ? BufferSize : count);
|
||||
}
|
||||
|
||||
public virtual long Position => Index;
|
||||
|
|
@ -70,7 +90,16 @@ namespace Server
|
|||
size = BufferSize;
|
||||
}
|
||||
|
||||
Array.Resize(ref _buffer, size);
|
||||
if (size < _buffer.Length)
|
||||
{
|
||||
_bytesWritten = size;
|
||||
}
|
||||
|
||||
var newBuffer = GC.AllocateUninitializedArray<byte>(size);
|
||||
_buffer.AsSpan(0, Math.Min(size, _buffer.Length)).CopyTo(newBuffer);
|
||||
_buffer = newBuffer;
|
||||
|
||||
// Array.Resize(ref _buffer, size);
|
||||
}
|
||||
|
||||
public virtual void Flush()
|
||||
|
|
@ -82,15 +111,12 @@ namespace Server
|
|||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool FlushIfNeeded(int amount)
|
||||
private void FlushIfNeeded(int amount)
|
||||
{
|
||||
if (Index + amount > _buffer.Length)
|
||||
{
|
||||
Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Write(ReadOnlySpan<byte> bytes)
|
||||
|
|
@ -113,12 +139,25 @@ namespace Server
|
|||
|
||||
public virtual long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
return origin switch
|
||||
Debug.Assert(
|
||||
origin != SeekOrigin.End || offset <= 0 && offset > -_buffer.Length,
|
||||
"Attempting to seek to an invalid position using SeekOrigin.End"
|
||||
);
|
||||
Debug.Assert(
|
||||
origin != SeekOrigin.Begin || offset >= 0 && offset < _buffer.Length,
|
||||
"Attempting to seek to an invalid position using SeekOrigin.Begin"
|
||||
);
|
||||
Debug.Assert(
|
||||
origin != SeekOrigin.Current || Index + offset >= 0 && Index + offset < _buffer.Length,
|
||||
"Attempting to seek to an invalid position using SeekOrigin.Current"
|
||||
);
|
||||
|
||||
return Index = Math.Max(0, origin switch
|
||||
{
|
||||
SeekOrigin.Current => Index += offset,
|
||||
SeekOrigin.End => Index = _buffer.Length - offset,
|
||||
_ => Index = offset // Begin
|
||||
};
|
||||
SeekOrigin.Current => Index + offset,
|
||||
SeekOrigin.End => _bytesWritten + offset,
|
||||
_ => offset // Begin
|
||||
});
|
||||
}
|
||||
|
||||
public void WriteEncodedInt(int value)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ namespace Server
|
|||
{
|
||||
Initial,
|
||||
Loading,
|
||||
WritingLoadBuffers,
|
||||
Running,
|
||||
Saving,
|
||||
WritingSave
|
||||
|
|
@ -142,7 +141,7 @@ namespace Server
|
|||
{
|
||||
if (WorldState != WorldState.Saving)
|
||||
{
|
||||
Console.WriteLine("Attempting to queue {0} for decay but the world is not saving", item);
|
||||
WriteConsoleLine($"Attempting to queue {item} for decay but the world is not saving");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -191,22 +190,20 @@ namespace Server
|
|||
|
||||
if (t?.IsAbstract != false)
|
||||
{
|
||||
Console.WriteLine("failed");
|
||||
WriteConsoleLine("failed");
|
||||
|
||||
Console.WriteLine(
|
||||
"Error: Type '{0}' was {1}. Delete all of those types? (y/n)",
|
||||
typeName,
|
||||
t?.IsAbstract == true ? "marked abstract" : "not found"
|
||||
);
|
||||
var issue = t?.IsAbstract == true ? "marked abstract" : "not found";
|
||||
|
||||
WriteConsoleLine($"Error: Type '{typeName}' was {issue}. Delete all of those types? (y/n)");
|
||||
|
||||
if (Console.ReadKey(true).Key == ConsoleKey.Y)
|
||||
{
|
||||
types.Add(null);
|
||||
Console.Write("World: Loading...");
|
||||
WriteConsole("Loading...");
|
||||
continue;
|
||||
}
|
||||
|
||||
Console.WriteLine("Types will not be deleted. An exception will be thrown.");
|
||||
WriteConsoleLine("Types will not be deleted. An exception will be thrown.");
|
||||
|
||||
throw new Exception($"Bad type '{typeName}'");
|
||||
}
|
||||
|
|
@ -290,35 +287,6 @@ namespace Server
|
|||
return map;
|
||||
}
|
||||
|
||||
private static void SaveBuffers<I, T>(IIndexInfo<I> indexInfo, List<EntityIndex<T>> entities) where T : class, 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);
|
||||
BinaryReader reader = new BinaryReader(bin);
|
||||
|
||||
foreach (var entry in entities)
|
||||
{
|
||||
T e = entry.Entity;
|
||||
|
||||
if (e == null || e is IEntity entity && entity.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] saveBuffer = GC.AllocateUninitializedArray<byte>(entry.Length);
|
||||
reader.Read(saveBuffer, 0, entry.Length);
|
||||
e.InitializeSaveBuffer(saveBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadData<I, T>(IIndexInfo<I> indexInfo, List<EntityIndex<T>> entities) where T : class, ISerializable
|
||||
{
|
||||
var indexType = indexInfo.TypeName;
|
||||
|
|
@ -331,11 +299,8 @@ namespace Server
|
|||
}
|
||||
|
||||
using FileStream bin = new FileStream(dataPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
var buffer = bin.Length <= int.MaxValue ? GC.AllocateUninitializedArray<byte>((int)bin.Length) : new byte[bin.Length];
|
||||
bin.Read(buffer);
|
||||
bin.Close();
|
||||
|
||||
var br = new BufferReader(buffer);
|
||||
BufferReader br = null;
|
||||
|
||||
foreach (var entry in entities)
|
||||
{
|
||||
|
|
@ -344,29 +309,42 @@ namespace Server
|
|||
// Skip this entry
|
||||
if (t == null)
|
||||
{
|
||||
br.Seek(entry.Length, SeekOrigin.Current);
|
||||
bin.Seek(entry.Length, SeekOrigin.Current);
|
||||
continue;
|
||||
}
|
||||
|
||||
t.Deserialize(br);
|
||||
var end = entry.Position + entry.Length;
|
||||
|
||||
if (br.Position != end)
|
||||
var buffer = GC.AllocateUninitializedArray<byte>(entry.Length);
|
||||
if (br == null)
|
||||
{
|
||||
Console.WriteLine($"***** Bad deserialize on {t.GetType()} *****");
|
||||
Console.WriteLine(
|
||||
$"Serialized object was {entry.Length} bytes, but {br.Position - entry.Position} bytes deserialized"
|
||||
br = new BufferReader(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
br.SwapBuffers(buffer, out _);
|
||||
}
|
||||
|
||||
bin.Read(buffer.AsSpan());
|
||||
|
||||
t.Deserialize(br);
|
||||
|
||||
if (br.Position != entry.Length)
|
||||
{
|
||||
WriteConsoleLine($"***** Bad deserialize on {t.GetType()} *****");
|
||||
WriteConsoleLine(
|
||||
$"Serialized object was {entry.Length} bytes, but {br.Position} bytes deserialized"
|
||||
);
|
||||
|
||||
Console.WriteLine("Delete the object and continue? (y/n)");
|
||||
WriteConsoleLine("Delete the object and continue? (y/n)");
|
||||
|
||||
if (Console.ReadKey(true).Key != ConsoleKey.Y)
|
||||
{
|
||||
throw new Exception("Deserialization failed.");
|
||||
}
|
||||
t.Delete();
|
||||
|
||||
br.Seek((int)end, SeekOrigin.Begin);
|
||||
}
|
||||
else
|
||||
{
|
||||
t.InitializeSaveBuffer(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -380,8 +358,7 @@ namespace Server
|
|||
|
||||
WorldState = WorldState.Loading;
|
||||
|
||||
Console.Write("World: Loading...");
|
||||
|
||||
WriteConsole("Loading...");
|
||||
var watch = Stopwatch.StartNew();
|
||||
|
||||
List<EntityIndex<Item>> items;
|
||||
|
|
@ -402,8 +379,6 @@ namespace Server
|
|||
|
||||
EventSink.InvokeWorldLoad();
|
||||
|
||||
WorldState = WorldState.WritingLoadBuffers;
|
||||
|
||||
ProcessSafetyQueues();
|
||||
|
||||
foreach (var item in Items.Values)
|
||||
|
|
@ -433,15 +408,8 @@ namespace Server
|
|||
Mobiles.Count
|
||||
);
|
||||
|
||||
// Async save buffers
|
||||
ThreadPool.QueueUserWorkItem(state =>
|
||||
{
|
||||
SaveBuffers(mobileIndexInfo, mobiles);
|
||||
SaveBuffers(itemIndexInfo, items);
|
||||
SaveBuffers(guildIndexInfo, guilds);
|
||||
WorldState = WorldState.Running;
|
||||
}
|
||||
);
|
||||
|
||||
WorldState = WorldState.Running;
|
||||
}
|
||||
|
||||
private static void ProcessSafetyQueues()
|
||||
|
|
@ -467,7 +435,7 @@ namespace Server
|
|||
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);
|
||||
WriteConsoleLine(message);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -493,6 +461,7 @@ namespace Server
|
|||
public static void WriteFiles(object state)
|
||||
{
|
||||
var watch = Stopwatch.StartNew();
|
||||
WriteConsole("Writing snapshot...");
|
||||
|
||||
IIndexInfo<Serial> itemIndexInfo = new EntityTypeIndex("Items");
|
||||
IIndexInfo<Serial> mobileIndexInfo = new EntityTypeIndex("Mobiles");
|
||||
|
|
@ -506,7 +475,7 @@ namespace Server
|
|||
|
||||
m_DiskWriteHandle.Set();
|
||||
|
||||
Console.WriteLine("World: Writing snapshot took {0:F1} seconds.", watch.Elapsed.TotalSeconds);
|
||||
Console.WriteLine("done ({0:F2} seconds)", watch.Elapsed.TotalSeconds);
|
||||
|
||||
Timer.DelayCall(FinishWorldSave);
|
||||
}
|
||||
|
|
@ -523,9 +492,9 @@ namespace Server
|
|||
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);
|
||||
using var idx = new BinaryFileWriter(idxPath, false);
|
||||
using var tdb = new BinaryFileWriter(tdbPath, false);
|
||||
using var bin = new BinaryFileWriter(binPath, true);
|
||||
|
||||
idx.Write(entities.Count);
|
||||
foreach (var e in entities.Values)
|
||||
|
|
@ -546,10 +515,6 @@ namespace Server
|
|||
{
|
||||
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
|
||||
|
|
@ -600,7 +565,7 @@ namespace Server
|
|||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
Console.Write("[{0}] World: Saving...", now.ToLongTimeString());
|
||||
WriteConsole("Saving...");
|
||||
|
||||
var watch = Stopwatch.StartNew();
|
||||
|
||||
|
|
@ -619,20 +584,19 @@ namespace Server
|
|||
|
||||
WorldState = WorldState.WritingSave;
|
||||
|
||||
ThreadPool.QueueUserWorkItem(WriteFiles);
|
||||
|
||||
watch.Stop();
|
||||
|
||||
var duration = watch.Elapsed.TotalSeconds;
|
||||
|
||||
Console.WriteLine("Save done in {0:F2} seconds.", duration);
|
||||
Console.WriteLine("done ({0:F2} seconds)", duration);
|
||||
|
||||
// Only broadcast if it took at least 150ms
|
||||
if (duration >= 0.15)
|
||||
{
|
||||
Broadcast(0x35, true, $"World save completed in {duration:F2} seconds.");
|
||||
Broadcast(0x35, true, $"World Save completed in {duration:F2} seconds.");
|
||||
}
|
||||
|
||||
ThreadPool.QueueUserWorkItem(WriteFiles);
|
||||
|
||||
NetState.Resume();
|
||||
}
|
||||
|
||||
|
|
@ -660,7 +624,6 @@ namespace Server
|
|||
|
||||
goto case WorldState.Running;
|
||||
}
|
||||
case WorldState.WritingLoadBuffers:
|
||||
case WorldState.Running:
|
||||
{
|
||||
if (serial.IsItem)
|
||||
|
|
@ -708,13 +671,12 @@ namespace Server
|
|||
if (_pendingDelete.Remove(entity.Serial))
|
||||
{
|
||||
Utility.PushColor(ConsoleColor.Red);
|
||||
Console.WriteLine($"Deleted then added {typeof(T).Name} during {WorldState.ToString().ToLower()} state.");
|
||||
WriteConsoleLine($"Deleted then added {typeof(T).Name} during {WorldState.ToString().ToLower()} state.");
|
||||
Utility.PopColor();
|
||||
}
|
||||
_pendingAdd[entity.Serial] = entity;
|
||||
break;
|
||||
}
|
||||
case WorldState.WritingLoadBuffers:
|
||||
case WorldState.Running:
|
||||
{
|
||||
if (entity.Serial.IsItem)
|
||||
|
|
@ -753,7 +715,6 @@ namespace Server
|
|||
_pendingDelete[entity.Serial] = entity;
|
||||
break;
|
||||
}
|
||||
case WorldState.WritingLoadBuffers:
|
||||
case WorldState.Running:
|
||||
{
|
||||
if (entity.Serial.IsItem)
|
||||
|
|
@ -780,5 +741,17 @@ namespace Server
|
|||
// Resize to exact buffer size
|
||||
entity.SaveBuffer.Resize((int)entity.SaveBuffer.Position);
|
||||
}
|
||||
|
||||
private static void WriteConsole(string message)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
Console.Write("[{0} {1}] World: {2}", now.ToShortDateString(), now.ToLongTimeString(), message);
|
||||
}
|
||||
|
||||
private static void WriteConsoleLine(string message)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
Console.WriteLine("[{0} {1}] World: {2}", now.ToShortDateString(), now.ToLongTimeString(), message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue