using MessagePack; namespace OECS; /// /// Saves and loads a to/from a MessagePack stream. /// /// Uses the compile-time generated 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. /// 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 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(); 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); } /// /// 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); // Build a lookup by type name for O(1) descriptor resolution. var lookup = new Dictionary(); 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(); 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); } } } } } }