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.
163 lines
5.6 KiB
163 lines
5.6 KiB
2 years ago
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
using System.Runtime.InteropServices;
|
||
|
using gaemstone.Utility;
|
||
|
using static flecs_hub.flecs;
|
||
|
|
||
|
namespace gaemstone.ECS;
|
||
|
|
||
|
public unsafe class EntityRef
|
||
|
: EntityBase<EntityRef>
|
||
|
, IEquatable<EntityRef>
|
||
|
{
|
||
|
public override World World { get; }
|
||
|
public Entity Entity { get; }
|
||
|
public uint Id => Entity.Id;
|
||
|
|
||
|
public bool IsAlive => ecs_is_alive(World, this);
|
||
|
public EntityType Type => new(World, ecs_get_type(World, this));
|
||
|
|
||
|
public string? Name {
|
||
|
get => ecs_get_name(World, this).FlecsToString()!;
|
||
|
set { using var alloc = TempAllocator.Use();
|
||
|
ecs_set_name(World, this, alloc.AllocateCString(value)); }
|
||
|
}
|
||
|
public string? Symbol {
|
||
|
get => ecs_get_symbol(World, this).FlecsToString()!;
|
||
|
set { using var alloc = TempAllocator.Use();
|
||
|
ecs_set_symbol(World, this, alloc.AllocateCString(value)); }
|
||
|
}
|
||
|
|
||
|
|
||
|
private EntityRef(World world, Entity entity, bool throwOnInvalid)
|
||
|
{
|
||
|
if (throwOnInvalid && !ecs_is_valid(world, entity))
|
||
|
throw new InvalidOperationException($"The entity {entity} is not valid");
|
||
|
World = world;
|
||
|
Entity = entity;
|
||
|
}
|
||
|
|
||
|
public EntityRef(World world, Entity entity)
|
||
|
: this(world, entity, true) { }
|
||
|
|
||
|
public static EntityRef? CreateOrNull(World world, Entity entity)
|
||
|
=> ecs_is_valid(world, entity) ? new(world, entity) : null;
|
||
|
|
||
|
|
||
|
public void Delete()
|
||
|
=> ecs_delete(World, this);
|
||
|
|
||
|
public EntityBuilder NewChild(EntityPath? path = null)
|
||
|
=> World.New(EnsureRelativePath(path)).ChildOf(this);
|
||
|
public EntityRef? Lookup(EntityPath path)
|
||
|
=> World.Lookup(this, EnsureRelativePath(path)!);
|
||
|
public EntityRef LookupOrThrow(EntityPath path)
|
||
|
=> World.LookupOrThrow(this, EnsureRelativePath(path)!);
|
||
|
|
||
|
private static EntityPath? EnsureRelativePath(EntityPath? path)
|
||
|
{ if (path?.IsAbsolute == true) throw new ArgumentException("path must not be absolute", nameof(path)); return path; }
|
||
|
|
||
|
|
||
|
public EntityRef? Parent
|
||
|
=> GetTarget(World.ChildOf);
|
||
|
|
||
|
public IEnumerable<EntityRef> GetChildren()
|
||
|
{
|
||
|
foreach (var iter in Iterator.FromTerm(World, new(World.ChildOf, this)))
|
||
|
for (var i = 0; i < iter.Count; i++)
|
||
|
yield return iter.Entity(i);
|
||
|
}
|
||
|
|
||
|
|
||
|
public override EntityRef Add(Identifier id) { ecs_add_id(World, this, id); return this; }
|
||
|
public override EntityRef Remove(Identifier id) { ecs_remove_id(World, this, id); return this; }
|
||
|
public override bool Has(Identifier id) => ecs_has_id(World, this, id);
|
||
|
|
||
|
public override T Get<T>(Identifier id)
|
||
|
{
|
||
|
var ptr = ecs_get_id(World, this, id);
|
||
|
if (ptr == null) throw new Exception($"Component {typeof(T)} not found on {this}");
|
||
|
return typeof(T).IsValueType ? Unsafe.Read<T>(ptr)
|
||
|
: (T)((GCHandle)Unsafe.Read<nint>(ptr)).Target!;
|
||
|
}
|
||
|
|
||
|
public override T? GetOrNull<T>(Identifier id)
|
||
|
{
|
||
|
var ptr = ecs_get_id(World, this, id);
|
||
|
return (ptr != null) ? Unsafe.Read<T>(ptr) : null;
|
||
|
}
|
||
|
|
||
|
public override T? GetOrNull<T>(Identifier id, T _ = null!)
|
||
|
where T : class
|
||
|
{
|
||
|
var ptr = ecs_get_id(World, this, id);
|
||
|
return (ptr != null) ? (T)((GCHandle)Unsafe.Read<nint>(ptr)).Target! : null;
|
||
|
}
|
||
|
|
||
|
public override ref T GetRefOrNull<T>(Identifier id)
|
||
|
{
|
||
|
var @ref = ecs_ref_init_id(World, this, id);
|
||
|
var ptr = ecs_ref_get_id(World, &@ref, id);
|
||
|
return ref (ptr != null) ? ref Unsafe.AsRef<T>(ptr) : ref Unsafe.NullRef<T>();
|
||
|
}
|
||
|
|
||
|
public override ref T GetRefOrThrow<T>(Identifier id)
|
||
|
{
|
||
|
ref var ptr = ref GetRefOrNull<T>(id);
|
||
|
if (Unsafe.IsNullRef(ref ptr)) throw new Exception(
|
||
|
$"Component {typeof(T)} not found on {this}");
|
||
|
return ref ptr;
|
||
|
}
|
||
|
|
||
|
public override ref T GetMut<T>(Identifier id)
|
||
|
{
|
||
|
var ptr = ecs_get_mut_id(World, this, id);
|
||
|
// NOTE: Value is added if it doesn't exist on the entity.
|
||
|
return ref Unsafe.AsRef<T>(ptr);
|
||
|
}
|
||
|
|
||
|
public override void Modified<T>(Identifier id)
|
||
|
=> ecs_modified_id(World, this, id);
|
||
|
|
||
|
public override EntityRef Set<T>(Identifier id, in T value)
|
||
|
{
|
||
|
var size = (ulong)Unsafe.SizeOf<T>();
|
||
|
fixed (T* ptr = &value)
|
||
|
if (ecs_set_id(World, this, id, size, ptr).Data == 0)
|
||
|
throw new InvalidOperationException();
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
public override EntityRef Set<T>(Identifier id, T obj) where T : class
|
||
|
{
|
||
|
var handle = (nint)GCHandle.Alloc(obj);
|
||
|
// FIXME: Previous handle needs to be freed.
|
||
|
if (ecs_set_id(World, this, id, (ulong)sizeof(nint), &handle).Data == 0)
|
||
|
throw new InvalidOperationException();
|
||
|
// FIXME: Handle needs to be freed when component is removed!
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
public EntityRef? GetTarget(Entity relation, int index = 0)
|
||
|
=> CreateOrNull(World, new(ecs_get_target(World, this, relation, index)));
|
||
|
public EntityRef? GetTarget(string symbol, int index = 0)
|
||
|
=> GetTarget(World.LookupSymbolOrThrow(symbol), index);
|
||
|
public EntityRef? GetTarget<T>(int index = 0)
|
||
|
=> GetTarget(World.LookupOrThrow(typeof(T)), index);
|
||
|
|
||
|
public bool Equals(EntityRef? other) => (other is not null) && (World == other.World) && (Entity == other.Entity);
|
||
|
public override bool Equals(object? obj) => Equals(obj as EntityRef);
|
||
|
public override int GetHashCode() => HashCode.Combine(World, Entity);
|
||
|
public override string? ToString() => ecs_entity_str(World, this).FlecsToStringAndFree()!;
|
||
|
|
||
|
public static bool operator ==(EntityRef? left, EntityRef? right) => ReferenceEquals(left, right) || (left?.Equals(right) ?? false);
|
||
|
public static bool operator !=(EntityRef? left, EntityRef? right) => !(left == right);
|
||
|
|
||
|
public static implicit operator Entity(EntityRef? e) => e?.Entity ?? default;
|
||
|
public static implicit operator ecs_entity_t(EntityRef? e) => e?.Entity.Value ?? default;
|
||
|
|
||
|
public static implicit operator Identifier(EntityRef? e) => new(e?.Entity.Value.Data ?? default);
|
||
|
public static implicit operator ecs_id_t(EntityRef? e) => e?.Entity.Value.Data ?? default;
|
||
|
}
|