You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
4.7 KiB
129 lines
4.7 KiB
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<TContext> |
|
{ |
|
public World<TContext> World { get; } |
|
|
|
/// <summary> Set to modify existing entity (optional). </summary> |
|
public Entity<TContext> Id { get; set; } |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
public EntityPath? Path { get; set; } |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
public EntityBuilder<TContext> Symbol(string symbol) { _symbol = symbol; return this; } |
|
private string? _symbol = null; |
|
|
|
/// <summary> |
|
/// When set to true, a low id (typically reserved for components) |
|
/// will be used to create the entity, if no id is specified. |
|
/// </summary> |
|
public bool UseLowId { get; set; } |
|
|
|
/// <summary> Ids to add to the new or existing entity. </summary> |
|
private readonly HashSet<Id> _toAdd = new(); |
|
// (ChildOf, *) is handled explicitly, it won't be added to _toAdd. |
|
private Entity _parent = Entity.None; |
|
|
|
/// <summary> String expression with components to add. </summary> |
|
public string? Expression { get; } |
|
|
|
/// <summary> Actions to run once the entity has been created. </summary> |
|
private readonly List<Action<Entity>> _toSet = new(); |
|
|
|
public EntityBuilder(World<TContext> world, EntityPath? path = null) |
|
{ World = world; Path = path; } |
|
|
|
public EntityBuilder(World<TContext> 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<TContext> 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<TContext> Add(string symbol) |
|
=> Add(World.LookupSymbolOrThrow(symbol)); |
|
|
|
public EntityBuilder<TContext> Add<TEntity>() |
|
=> Add(World.Entity<TEntity>()); |
|
|
|
public EntityBuilder<TContext> Add(Entity relation, Entity target) |
|
=> Add(World.Pair(relation, target)); |
|
public EntityBuilder<TContext> Add<TRelation>(Entity target) |
|
=> Add(World.Pair<TRelation>(target)); |
|
public EntityBuilder<TContext> Add<TRelation, TTarget>() |
|
=> Add(World.Pair<TRelation, TTarget>()); |
|
|
|
public EntityBuilder<TContext> Set<T>(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<TContext> Set<T>(Id id, T value) where T : class |
|
{ _toSet.Add(e => EntityAccess.Set(World, e, id, value)); return this; } |
|
|
|
|
|
public EntityBuilder<TContext> Set<T>(in T value) where T : unmanaged |
|
=> Set(World.Entity<T>(), value); |
|
public EntityBuilder<TContext> Set<T>(T value) where T : class |
|
=> Set(World.Entity<T>(), value); |
|
|
|
public unsafe Entity<TContext> 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<TContext>.GetOrInvalid(World, new(entityId)); |
|
foreach (var action in _toSet) action(entity); |
|
|
|
return entity; |
|
} |
|
}
|
|
|