using System; using System.Collections.Generic; using gaemstone.ECS.Internal; using gaemstone.ECS.Utility; using static flecs_hub.flecs; using static gaemstone.ECS.Internal.FlecsBuiltIn; namespace gaemstone.ECS; public class EntityBuilder { public World World { 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(); // (ChildOf, *) is handled explicitly, it won't be added to _toAdd. 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(World world, EntityPath? path = null) { World = world; Path = path; } public EntityBuilder(World world, Entity parent, EntityPath? path = null) : this(world, path) { // If given path is absolute, the new entity won't be created as a // child of the specified parent. Alternatively, EntityRef.NewChild // can be used, which will throw when an absolute path is given. if ((path?.IsRelative != false) && parent.IsSome) Add(ChildOf, parent); } public EntityBuilder Add(Id id) { // If adding a ChildOf relation, store the parent separately. if (id.RelationUnsafe == ChildOf) { _parent = id.TargetUnsafe; 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 EntityBuilder Add(string symbol) => Add(World.LookupSymbolOrThrow(symbol)); public EntityBuilder Add() => Add(World.Entity()); public EntityBuilder Add(Entity relation, Entity target) => Add(World.Pair(relation, target)); public EntityBuilder Add(Entity target) => Add(World.Pair(target)); public EntityBuilder Add() => Add(World.Pair()); public EntityBuilder Set(Id id, in T value) where T : unmanaged // "in" can't be used with lambdas, so we make a local copy. { var copy = value; _toSet.Add(e => EntityAccess.Set(World, e, id, copy)); return this; } public EntityBuilder Set(Id id, T value) where T : class { _toSet.Add(e => EntityAccess.Set(World, e, id, value)); return this; } public EntityBuilder Set(in T value) where T : unmanaged => Set(World.Entity(), value); public EntityBuilder Set(T value) where T : class => Set(World.Entity(), value); public unsafe Entity 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(World, Path.Parent!, 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.Empty, }; var add = desc.add; var index = 0; if (parent.IsSome) add[index++] = World.Pair(ChildOf, parent); foreach (var id in _toAdd) add[index++] = id; var entityId = ecs_entity_init(World, &desc); var entity = Entity.GetOrInvalid(World, new(entityId)); foreach (var action in _toSet) action(entity); return entity; } }