119 lines
4.1 KiB
C#
119 lines
4.1 KiB
C#
using MessagePack;
|
|
|
|
namespace OECS;
|
|
|
|
/// <summary>
|
|
/// Saves and loads a <see cref="World"/> to/from a MessagePack stream.
|
|
///
|
|
/// Uses the compile-time generated <see cref="ComponentRegistry"/> to
|
|
/// serialize and deserialize components without reflection. All component
|
|
/// types used with the World API are discovered by the OECS.SourceGen
|
|
/// incremental generator at build 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 desc in ComponentRegistry.Descriptors)
|
|
{
|
|
var set = world.Components.GetSet(desc.Type);
|
|
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 = desc.TypeName,
|
|
Data = desc.Serialize(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);
|
|
|
|
// Build a lookup by type name for O(1) descriptor resolution.
|
|
var lookup = new Dictionary<string, ComponentDescriptor>();
|
|
foreach (var desc in ComponentRegistry.Descriptors)
|
|
lookup[desc.TypeName] = desc;
|
|
|
|
// First pass: identify which entities are singleton backings.
|
|
// Singletons are registered in ComponentRegistry with a known marker.
|
|
var singletonTypes = new HashSet<Type>();
|
|
foreach (var desc in ComponentRegistry.Descriptors)
|
|
{
|
|
if (desc.IsSingleton)
|
|
singletonTypes.Add(desc.Type);
|
|
}
|
|
|
|
foreach (var es in snapshot.Entities)
|
|
{
|
|
var entity = world.CreateEntity(es.Entity);
|
|
bool isSingleton = false;
|
|
|
|
foreach (var ce in es.Components)
|
|
{
|
|
if (!lookup.TryGetValue(ce.TypeName, out var desc))
|
|
throw new InvalidOperationException(
|
|
$"Unknown component type '{ce.TypeName}'. " +
|
|
$"Ensure the component type is used with the World " +
|
|
$"API so the source generator can discover it.");
|
|
|
|
if (desc.IsSingleton)
|
|
isSingleton = true;
|
|
|
|
// Deserialize and add via the typed internal method.
|
|
var component = desc.Deserialize(ce.Data);
|
|
world.AddComponentBoxed(entity, component, desc.Type);
|
|
}
|
|
|
|
// If the entity has a singleton component, register it.
|
|
if (isSingleton)
|
|
{
|
|
foreach (var ce in es.Components)
|
|
{
|
|
if (lookup.TryGetValue(ce.TypeName, out var desc) && desc.IsSingleton)
|
|
{
|
|
world.RegisterSingletonEntity(desc.Type, entity);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |