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.
 
 

169 lines
6.0 KiB

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 sealed class EntityRef
: EntityBase<EntityRef>
, IEquatable<EntityRef>
{
public override Universe Universe { get; }
public Entity Entity { get; }
public uint ID => Entity.ID;
public bool IsAlive => ecs_is_alive(Universe, this);
public EntityType Type => new(Universe, ecs_get_type(Universe, this));
public string? Name {
get => ecs_get_name(Universe, this).FlecsToString()!;
set { using var alloc = TempAllocator.Use();
ecs_set_name(Universe, this, alloc.AllocateCString(value)); }
}
public string? Symbol {
get => ecs_get_symbol(Universe, this).FlecsToString()!;
set { using var alloc = TempAllocator.Use();
ecs_set_symbol(Universe, this, alloc.AllocateCString(value)); }
}
private EntityRef(Universe universe, Entity entity, bool throwOnInvalid)
{
if (throwOnInvalid && !ecs_is_valid(universe, entity))
throw new InvalidOperationException($"The entity {entity} is not valid");
Universe = universe;
Entity = entity;
}
public EntityRef(Universe universe, Entity entity)
: this(universe, entity, true) { }
public static EntityRef? CreateOrNull(Universe universe, Entity entity)
=> ecs_is_valid(universe, entity) ? new(universe, entity) : null;
public void Delete()
=> ecs_delete(Universe, this);
public EntityBuilder NewChild(EntityPath? path = null)
=> Universe.New(EnsureRelativePath(path)).ChildOf(this);
public EntityRef? Lookup(EntityPath path)
=> Universe.Lookup(this, EnsureRelativePath(path)!);
public EntityRef LookupOrThrow(EntityPath path)
=> Universe.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(Universe.ChildOf);
public IEnumerable<EntityRef> GetChildren()
{
foreach (var iter in Iterator.FromTerm(Universe, new(Universe.ChildOf, this)))
for (var i = 0; i < iter.Count; i++)
yield return iter.Entity(i);
}
public override EntityRef Add(Identifier id) { ecs_add_id(Universe, this, id); return this; }
public override EntityRef Remove(Identifier id) { ecs_remove_id(Universe, this, id); return this; }
public override bool Has(Identifier id) => ecs_has_id(Universe, this, id);
public override T Get<T>()
{
var comp = Universe.LookupOrThrow<T>();
var ptr = ecs_get_id(Universe, this, comp);
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? MaybeGet<T>()
{
var comp = Universe.LookupOrThrow<T>();
var ptr = ecs_get_id(Universe, this, comp);
return (ptr != null) ? Unsafe.Read<T>(ptr) : null;
}
public override T? MaybeGet<T>(T _ = null!)
where T : class
{
var comp = Universe.LookupOrThrow<T>();
var ptr = ecs_get_id(Universe, this, comp);
return (ptr != null) ? (T)((GCHandle)Unsafe.Read<nint>(ptr)).Target! : null;
}
public override ref T GetRefOrNull<T>()
{
var comp = Universe.LookupOrThrow<T>();
var @ref = ecs_ref_init_id(Universe, this, comp);
var ptr = ecs_ref_get_id(Universe, &@ref, comp);
return ref (ptr != null) ? ref Unsafe.AsRef<T>(ptr) : ref Unsafe.NullRef<T>();
}
public override ref T GetRefOrThrow<T>()
{
ref var ptr = ref GetRefOrNull<T>();
if (Unsafe.IsNullRef(ref ptr)) throw new Exception(
$"Component {typeof(T)} not found on {this}");
return ref ptr;
}
public override ref T GetMut<T>()
{
var comp = Universe.LookupOrThrow<T>();
var ptr = ecs_get_mut_id(Universe, this, comp);
// NOTE: Value is added if it doesn't exist on the entity.
return ref Unsafe.AsRef<T>(ptr);
}
public override void Modified<T>()
=> ecs_modified_id(Universe, this, Universe.LookupOrThrow<T>());
public override EntityRef Set<T>(in T value)
{
var comp = Universe.LookupOrThrow<T>();
var size = (ulong)Unsafe.SizeOf<T>();
fixed (T* ptr = &value)
if (ecs_set_id(Universe, this, comp, size, ptr).Data == 0)
throw new InvalidOperationException();
return this;
}
public override EntityRef Set<T>(T obj) where T : class
{
var comp = Universe.LookupOrThrow<T>();
var handle = (nint)GCHandle.Alloc(obj);
// FIXME: Previous handle needs to be freed.
if (ecs_set_id(Universe, this, comp, (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(Universe, new(ecs_get_target(Universe, this, relation, index)));
public EntityRef? GetTarget(string symbol, int index = 0)
=> GetTarget(Universe.LookupSymbolOrThrow(symbol), index);
public EntityRef? GetTarget<T>(int index = 0)
=> GetTarget(Universe.LookupOrThrow(typeof(T)), index);
public bool Equals(EntityRef? other) => (other is not null) && (Universe == other.Universe) && (Entity == other.Entity);
public override bool Equals(object? obj) => Equals(obj as EntityRef);
public override int GetHashCode() => HashCode.Combine(Universe, Entity);
public override string? ToString() => ecs_entity_str(Universe, 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;
}