feat(saves): idx v4 with embedded type table

Records reference a per-persistence name table by ushort index instead of a
9-byte tag+hash (33 -> 26 bytes per record), eliminating the per-record
xxHash over Type.FullName on the snapshot thread. The loader resolves each
table name once and indexes records into the constructor array. Legacy
v0-v3 loading is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kamron Batman 2026-07-16 12:03:22 -07:00
parent 4970e22541
commit 98b054ad8d
No known key found for this signature in database
GPG key ID: 7D81DF26D9A5D94A
2 changed files with 152 additions and 58 deletions

View file

@ -100,6 +100,8 @@ public class GenericEntityPersistenceRoundTripTests
};
}
persistence.RegisterType(typeof(RoundTripEntity));
// Freeze: what Persistence.SerializeAll + GenericEntityPersistence.Serialize do,
// against this test's chunk source instead of the world's.
foreach (var worker in workers)
@ -119,22 +121,16 @@ public class GenericEntityPersistenceRoundTripTests
worker.Sleep();
}
// Background write phase: snapshot from the segment logs, then the types db.
var typeSet = new HashSet<Type>();
persistence.WriteSnapshot(dir, typeSet);
var typesDb = new Dictionary<ulong, string>();
foreach (var type in typeSet)
{
typesDb[AssemblyHandler.GetTypeHash(type)] = type.FullName;
}
// Background write phase: snapshot from the segment logs. v4 embeds the type
// table in the idx, so no SerializedTypes.db is produced or needed.
persistence.WriteSnapshot(dir, []);
persistence.PostWorldSave(); // releases the entries snapshot
// Load into a fresh persistence, like a server boot would.
loaded = new RoundTripPersistence(2001);
loaded.DeserializeIndexes(dir, typesDb);
loaded.Deserialize(dir, typesDb);
loaded.DeserializeIndexes(dir, null);
loaded.Deserialize(dir, null);
Assert.True(loaded.SelfPayloadDeserialized);
Assert.Equal(persistence.EntitiesBySerial.Count, loaded.EntitiesBySerial.Count);
@ -146,6 +142,9 @@ public class GenericEntityPersistenceRoundTripTests
Assert.Equal(original.Name, entity.Name);
Assert.Equal(original.Created.Ticks, entity.Created.Ticks);
}
// Loading must hydrate the type table so the next save can write indexes.
Assert.True(loaded.TryGetTypeIndex(typeof(RoundTripEntity), out _));
}
finally
{