feat: add WorldSerializer for saving and loading world state
This commit is contained in:
parent
0a28890f2c
commit
9aa2a71257
|
|
@ -0,0 +1,3 @@
|
|||
y
|
||||
1 0
|
||||
0 2
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
0 0
|
||||
1 1
|
||||
0 1
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
using MessagePack;
|
||||
|
||||
namespace OECS;
|
||||
|
||||
/// <summary>
|
||||
/// Saves and loads a <see cref="World"/> to/from a MessagePack stream.
|
||||
///
|
||||
/// Components are serialized by their runtime type. The caller must
|
||||
/// ensure all component types used in the world are resolvable via
|
||||
/// <see cref="Type.GetType(string)"/> at load time.
|
||||
/// </summary>
|
||||
public static class WorldSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves the world state to a stream.
|
||||
/// </summary>
|
||||
public static void Save(World world, Stream stream)
|
||||
{
|
||||
var entityComponents = new Dictionary<Entity, List<ComponentEntry>>();
|
||||
|
||||
foreach (var ct in world.Components.ComponentTypes)
|
||||
{
|
||||
var set = world.Components.GetSet(ct);
|
||||
if (set == null || set.Count == 0) continue;
|
||||
|
||||
var entities = set.GetDenseEntities();
|
||||
for (int i = 0; i < set.Count; i++)
|
||||
{
|
||||
var entity = entities[i];
|
||||
var component = set.GetComponentAt(i);
|
||||
|
||||
if (!entityComponents.TryGetValue(entity, out var list))
|
||||
{
|
||||
list = new List<ComponentEntry>();
|
||||
entityComponents[entity] = list;
|
||||
}
|
||||
|
||||
list.Add(new ComponentEntry
|
||||
{
|
||||
TypeName = ct.AssemblyQualifiedName!,
|
||||
Data = MessagePackSerializer.Serialize(ct, component)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var snapshot = new WorldSnapshot
|
||||
{
|
||||
Entities = entityComponents
|
||||
.Select(kv => new EntitySnapshot
|
||||
{
|
||||
Id = kv.Key.Id,
|
||||
Version = kv.Key.Version,
|
||||
Components = kv.Value.ToArray()
|
||||
})
|
||||
.ToArray()
|
||||
};
|
||||
|
||||
MessagePackSerializer.Serialize(stream, snapshot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the world state from a stream, adding entities and components
|
||||
/// to the given world. The world should be empty or the caller is
|
||||
/// responsible for managing duplicate entities.
|
||||
/// </summary>
|
||||
public static void Load(World world, Stream stream)
|
||||
{
|
||||
var snapshot = MessagePackSerializer.Deserialize<WorldSnapshot>(stream);
|
||||
|
||||
foreach (var es in snapshot.Entities)
|
||||
{
|
||||
var entity = world.CreateEntity(es.Entity);
|
||||
|
||||
foreach (var ce in es.Components)
|
||||
{
|
||||
var type = Type.GetType(ce.TypeName)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Unknown component type '{ce.TypeName}'. " +
|
||||
$"Ensure the assembly is loaded.");
|
||||
|
||||
var component = MessagePackSerializer.Deserialize(type, ce.Data)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Failed to deserialize component of type '{ce.TypeName}'.");
|
||||
|
||||
var method = typeof(World).GetMethod(nameof(World.AddComponent))!
|
||||
.MakeGenericMethod(type);
|
||||
method.Invoke(world, [entity, component]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using MessagePack;
|
||||
|
||||
namespace OECS;
|
||||
|
||||
/// <summary>
|
||||
/// Serializable snapshot of the entire world state.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class WorldSnapshot
|
||||
{
|
||||
[Key(0)] public EntitySnapshot[] Entities { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single entity and all its components, serialized as name/data pairs.
|
||||
/// Entity ID and Version are stored separately to avoid issues with
|
||||
/// MessagePack deserialization of the opaque Entity struct.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class EntitySnapshot
|
||||
{
|
||||
[Key(0)] public uint Id { get; set; }
|
||||
[Key(1)] public uint Version { get; set; }
|
||||
[Key(2)] public ComponentEntry[] Components { get; set; } = [];
|
||||
|
||||
[IgnoreMember]
|
||||
public Entity Entity => new(Id, Version);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single component, identified by its fully-qualified type name and
|
||||
/// serialized as a MessagePack byte blob.
|
||||
/// </summary>
|
||||
[MessagePackObject]
|
||||
public class ComponentEntry
|
||||
{
|
||||
[Key(0)] public string TypeName { get; set; } = "";
|
||||
[Key(1)] public byte[] Data { get; set; } = [];
|
||||
}
|
||||
Loading…
Reference in New Issue