fix: Adds Created, LastSerialized, and BeforeSerialize for all entities. (#775)

* Adds `BeforeSerialized` for entities to handle cleanup.
* Adds `Created` and `LastSerialized` fields to all entities. While this is a big bloat, this will be necessary for identifying dangling references to other invalid entities.
* Changes formula for determining a valid reference to be _not null, not deleted, and reference's created date must be at or before the entities last serialized date_.
* Adds versioning to idx file and serializes `Created` and `LastSerialized`.
* Fixes Save Stats and also disables it by default.
This commit is contained in:
Kamron Batman 2021-09-26 01:09:45 -07:00 committed by GitHub
parent 3f6a87483b
commit fa5eafdaff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 325 additions and 88 deletions

View file

@ -24,6 +24,8 @@ namespace Server
{
public static class EntityPersistence
{
private const int _idxVersion = 1;
public static void WriteEntities<I, T>(
IIndexInfo<I> indexInfo,
Dictionary<I, T> entities,
@ -48,6 +50,7 @@ namespace Server
using var tdb = new BinaryFileWriter(tdbPath, false);
using var bin = new BinaryFileWriter(binPath, true);
idx.Write(1); // Version
idx.Write(entities.Count);
foreach (var e in entities.Values)
{
@ -55,6 +58,8 @@ namespace Server
idx.Write(e.TypeRef);
idx.Write(e.Serial);
idx.Write(e.Created.Ticks);
idx.Write(e.LastSerialized.Ticks);
idx.Write(start);
e.SerializeTo(bin);
@ -110,12 +115,28 @@ namespace Server
List<Tuple<ConstructorInfo, string>> types = ReadTypes<I>(tdbReader);
var count = idxReader.ReadInt32();
int count;
var version = idxReader.ReadInt32();
// Handle non-versioned (version 0).
if (version > _idxVersion || idx.Length - 4 - version * 20 == 0)
{
count = version;
version = 0;
}
else
{
count = idxReader.ReadInt32();
}
var now = DateTime.UtcNow;
for (int i = 0; i < count; ++i)
{
var typeID = idxReader.ReadInt32();
var number = idxReader.ReadUInt32();
var serial = idxReader.ReadUInt32();
var created = version == 0 ? now : new DateTime(idxReader.ReadInt64(), DateTimeKind.Utc);
var lastSerialized = version == 0 ? DateTime.MinValue : new DateTime(idxReader.ReadInt64(), DateTimeKind.Utc);
var pos = idxReader.ReadInt64();
var length = idxReader.ReadInt32();
@ -127,12 +148,14 @@ namespace Server
}
ConstructorInfo ctor = objs.Item1;
I indexer = indexInfo.CreateIndex(number);
I indexer = indexInfo.CreateIndex(serial);
ctorArgs[0] = indexer;
if (ctor.Invoke(ctorArgs) is T t)
{
t.Created = created;
t.LastSerialized = lastSerialized;
entities.Add(new EntityIndex<T>(t, typeID, pos, length));
map[indexer] = t;
}
@ -180,7 +203,7 @@ namespace Server
var buffer = GC.AllocateUninitializedArray<byte>(entry.Length);
if (br == null)
{
br = new BufferReader(buffer);
br = new BufferReader(buffer, t.LastSerialized);
}
else
{

View file

@ -47,6 +47,7 @@ namespace Server
private static string _tempSavePath; // Path to the temporary folder for the save
private static string _savePath; // Path to "Saves" folder
private static bool _enableSaveStats;
public const bool DirtyTrackingEnabled = false;
public const uint ItemOffset = 0x40000000;
@ -144,6 +145,7 @@ namespace Server
_tempSavePath = Path.Combine(Core.BaseDirectory, tempSavePath);
var savePath = ServerConfiguration.GetOrUpdateSetting("world.savePath", "Saves");
_savePath = Path.Combine(Core.BaseDirectory, savePath);
_enableSaveStats = ServerConfiguration.GetOrUpdateSetting("world.enableSaveStats", false);
// Mobiles & Items
Persistence.Register("Mobiles & Items", SaveEntities, WriteEntities, LoadEntities, 1);
@ -341,7 +343,10 @@ namespace Server
int count = 0;
var timestamp = Utility.GetTimeStamp();
using var op = new StreamWriter("Logs/Saves/Save-Stats-{0}.log", true);
var saveStatsPath = Path.Combine(Core.BaseDirectory, $"Logs/Saves/Save-Stats-{timestamp}.log");
AssemblyHandler.EnsureDirectory(saveStatsPath);
using var op = new StreamWriter(saveStatsPath, true);
for (var i = 0; i < entityTypes.Length; i++)
{
@ -373,7 +378,10 @@ namespace Server
EntityPersistence.WriteEntities(itemIndexInfo, Items, ItemTypes, basePath, out var itemCounts);
EntityPersistence.WriteEntities(guildIndexInfo, Guilds, GuildTypes, basePath, out var guildCounts);
TraceSave(mobileCounts?.ToList(), itemCounts?.ToList(), guildCounts?.ToList());
if (_enableSaveStats)
{
TraceSave(mobileCounts?.ToList(), itemCounts?.ToList(), guildCounts?.ToList());
}
}
public static void WriteFiles(object state)
@ -652,13 +660,24 @@ namespace Server
Serial serial = reader.ReadSerial();
var typeT = typeof(T);
T entity;
// Add to this list when creating new serializable types
if (typeof(BaseGuild).IsAssignableTo(typeT))
{
return FindGuild(serial) as T;
entity = FindGuild(serial) as T;
}
else
{
entity = FindEntity<IEntity>(serial) as T;
}
return FindEntity<IEntity>(serial) as T;
if (entity?.Deleted == false)
{
return entity;
}
return entity?.Created <= reader.LastSerialized ? entity : null;
}
public static List<T> ReadEntityList<T>(this IGenericReader reader) where T : class, ISerializable