using MessagePack;
namespace OECS;
///
/// Saves and loads a 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
/// at load time.
///
public static class WorldSerializer
{
///
/// Saves the world state to a stream.
///
public static void Save(World world, Stream stream)
{
var entityComponents = new Dictionary>();
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();
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);
}
///
/// 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.
///
public static void Load(World world, Stream stream)
{
var snapshot = MessagePackSerializer.Deserialize(stream);
foreach (var es in snapshot.Entities)
{
var entity = world.CreateEntity(es.Entity);
// If the loaded entity is the singleton entity, ensure the
// singleton infrastructure is initialized so that subsequent
// SetSingleton/GetSingleton calls work correctly.
if (entity.Id == World.SingletonEntity.Id)
{
world.EnsureSingleton();
}
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]);
}
}
}
}