using System; using System.Collections.Generic; using gaemstone.Utility; using static flecs_hub.flecs; namespace gaemstone.ECS; public class EntityBuilder : EntityBase { public override Universe Universe { get; } /// Set to modify existing entity (optional). public Entity ID { get; set; } /// /// Path of the entity. If no entity is provided, an entity with this path /// will be looked up first. When an entity is provided, the path will be /// verified with the existing entity. /// public EntityPath? Path { get; set; } /// /// Optional entity symbol. A symbol is an unscoped identifier that can /// be used to lookup an entity. The primary use case for this is to /// associate the entity with a language identifier, such as a type or /// function name, where these identifiers differ from the name they are /// registered with in flecs. /// public EntityBuilder Symbol(string symbol) { _symbol = symbol; return this; } private string? _symbol = null; /// /// When set to true, a low id (typically reserved for components) /// will be used to create the entity, if no id is specified. /// public bool UseLowID { get; set; } /// IDs to add to the new or existing entity. private readonly HashSet _toAdd = new(); private Entity _parent = Entity.None; /// String expression with components to add. public string? Expression { get; } /// Actions to run once the entity has been created. private readonly List> _toSet = new(); public EntityBuilder(Universe universe, EntityPath? path = null) { Universe = universe; Path = path; } public override EntityBuilder Add(Identifier id) { // If adding a ChildOf relation, store the parent separately. if (id.AsPair(Universe) is (EntityRef relation, EntityRef target) && (relation == Universe.ChildOf)) { _parent = target; return this; } if (_toAdd.Count == 31) throw new NotSupportedException( "Must not add more than 31 IDs at once with EntityBuilder"); _toAdd.Add(id); return this; } public override EntityBuilder Remove(Identifier id) => throw new NotSupportedException(); public override bool Has(Identifier id) => !ecs_id_is_wildcard(id) ? _toAdd.Contains(id) : throw new NotSupportedException(); // TODO: Support wildcard. public override T Get() => throw new NotSupportedException(); public override T? MaybeGet() => throw new NotSupportedException(); public override T? MaybeGet(T _ = null!) where T : class => throw new NotSupportedException(); public override ref T GetMut() => throw new NotSupportedException(); public override ref T GetRefOrNull() => throw new NotSupportedException(); public override ref T GetRefOrThrow() => throw new NotSupportedException(); public override void Modified() => throw new NotImplementedException(); public override EntityBuilder Set(in T value) // "in" can't be used with lambdas, so we make a local copy. { var copy = value; _toSet.Add(e => e.Set(copy)); return this; } public override EntityBuilder Set(T obj) { _toSet.Add(e => e.Set(obj)); return this; } public unsafe EntityRef Build() { var parent = _parent; if (Path != null) { if (parent.IsSome && Path.IsAbsolute) throw new InvalidOperationException( "Entity already has parent set (via ChildOf), so path must not be absolute"); // If path specifies more than just a name, ensure the parent entity exists. if (Path.Count > 1) parent = EntityPath.EnsureEntityExists(Universe, parent, Path.Parent!); } using var alloc = TempAllocator.Use(); var desc = new ecs_entity_desc_t { id = ID, name = (Path != null) ? alloc.AllocateCString(Path.Name.AsSpan()) : default, symbol = alloc.AllocateCString(_symbol), add_expr = alloc.AllocateCString(Expression), use_low_id = UseLowID, sep = CStringExtensions.ETX, }; var add = desc.add; var index = 0; if (parent.IsSome) add[index++] = Identifier.Pair(Universe.ChildOf, parent); foreach (var id in _toAdd) add[index++] = id; var entityID = ecs_entity_init(Universe, &desc); var entity = new EntityRef(Universe, new(entityID)); foreach (var action in _toSet) action(entity); return entity; } }