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]); } } } }