parent
12f3ff451c
commit
b4a769e3a6
55 changed files with 1530 additions and 1192 deletions
@ -1,14 +1,14 @@ |
|||||||
using System; |
using System; |
||||||
using gaemstone.Bloxel; |
|
||||||
using gaemstone.Client; |
|
||||||
using gaemstone.ECS; |
using gaemstone.ECS; |
||||||
|
using static gaemstone.Bloxel.Components.CoreComponents; |
||||||
|
using static gaemstone.Client.Components.RenderingComponents; |
||||||
|
|
||||||
namespace Immersion; |
namespace Immersion; |
||||||
|
|
||||||
[Module] |
[Module] |
||||||
public class ObserverModule |
public class ObserverTest |
||||||
{ |
{ |
||||||
[Observer(ObserverEvent.OnSet)] |
[Observer(typeof(ObserverEvent.OnSet))] |
||||||
public static void DoObserver(in Chunk chunk, in Mesh _) |
public static void DoObserver(in Chunk chunk, in Mesh _) |
||||||
=> Console.WriteLine($"Chunk at {chunk.Position} now has a Mesh!"); |
=> Console.WriteLine($"Chunk at {chunk.Position} now has a Mesh!"); |
||||||
} |
} |
||||||
|
@ -1 +1 @@ |
|||||||
Subproject commit 1e36559cffa5ab2fb755feef563c4294a6f32b0c |
Subproject commit f5eea6704075601a674e3d759fdadc306b75573d |
@ -0,0 +1,22 @@ |
|||||||
|
using gaemstone.ECS; |
||||||
|
|
||||||
|
namespace gaemstone.Bloxel.Components; |
||||||
|
|
||||||
|
[Module] |
||||||
|
public partial class CoreComponents |
||||||
|
{ |
||||||
|
[Component] |
||||||
|
public readonly struct Chunk |
||||||
|
{ |
||||||
|
public ChunkPos Position { get; } |
||||||
|
public Chunk(ChunkPos pos) => Position = pos; |
||||||
|
} |
||||||
|
|
||||||
|
[Component] |
||||||
|
public class ChunkStoreBlocks |
||||||
|
: ChunkPaletteStorage<Entity> |
||||||
|
{ |
||||||
|
public ChunkStoreBlocks() |
||||||
|
: base(default) { } |
||||||
|
} |
||||||
|
} |
@ -1,17 +1,11 @@ |
|||||||
using gaemstone.ECS; |
|
||||||
|
|
||||||
namespace gaemstone.Bloxel; |
namespace gaemstone.Bloxel; |
||||||
|
|
||||||
[Component] |
public static class Constants |
||||||
public readonly struct Chunk |
|
||||||
{ |
{ |
||||||
// <summary> Length of the egde of a world chunk. </summary> |
|
||||||
public const int LENGTH = 16; |
|
||||||
// <summary> Amount of bit shifting to go from a BlockPos to a ChunkPos. </summary> |
// <summary> Amount of bit shifting to go from a BlockPos to a ChunkPos. </summary> |
||||||
public const int BIT_SHIFT = 4; |
public const int ChunkBitShift = 4; |
||||||
// <summary> Amount of bit masking to go from a BlockPos to a chunk-relative BlockPos. </summary> |
// <summary> Amount of bit masking to go from a BlockPos to a chunk-relative BlockPos. </summary> |
||||||
public const int BIT_MASK = 0b1111; |
public const int ChunkBitMask = ~(~0 << ChunkBitShift); |
||||||
|
// <summary> Length of the egde of a world chunk. </summary> |
||||||
public ChunkPos Position { get; } |
public const int ChunkLength = 1 << ChunkBitShift; |
||||||
public Chunk(ChunkPos pos) => Position = pos; |
|
||||||
} |
} |
@ -1,10 +1,9 @@ |
|||||||
using gaemstone.ECS; |
using gaemstone.ECS; |
||||||
using Silk.NET.Maths; |
using Silk.NET.Maths; |
||||||
|
|
||||||
namespace gaemstone.Client; |
namespace gaemstone.Client.Components; |
||||||
|
|
||||||
[Module] |
[Module] |
||||||
[DependsOn(typeof(Input))] |
|
||||||
public class CameraComponents |
public class CameraComponents |
||||||
{ |
{ |
||||||
[Component] |
[Component] |
@ -0,0 +1,62 @@ |
|||||||
|
using System.Drawing; |
||||||
|
using gaemstone.ECS; |
||||||
|
using Silk.NET.Maths; |
||||||
|
using Silk.NET.OpenGL; |
||||||
|
|
||||||
|
namespace gaemstone.Client.Components; |
||||||
|
|
||||||
|
[Module] |
||||||
|
public class RenderingComponents |
||||||
|
{ |
||||||
|
[Component] |
||||||
|
public readonly struct Mesh |
||||||
|
{ |
||||||
|
public uint Handle { get; } |
||||||
|
public int Count { get; } |
||||||
|
public bool IsIndexed { get; } |
||||||
|
|
||||||
|
public Mesh(uint handle, int count, bool indexed = true) |
||||||
|
{ Handle = handle; Count = count; IsIndexed = indexed; } |
||||||
|
} |
||||||
|
|
||||||
|
[Component] |
||||||
|
public readonly struct Texture |
||||||
|
{ |
||||||
|
public TextureTarget Target { get; } |
||||||
|
public uint Handle { get; } |
||||||
|
|
||||||
|
public Texture(TextureTarget target, uint handle) |
||||||
|
=> (Target, Handle) = (target, handle); |
||||||
|
} |
||||||
|
|
||||||
|
[Component] |
||||||
|
public readonly struct TextureCoords4 |
||||||
|
{ |
||||||
|
public Vector2D<float> TopLeft { get; } |
||||||
|
public Vector2D<float> TopRight { get; } |
||||||
|
public Vector2D<float> BottomLeft { get; } |
||||||
|
public Vector2D<float> BottomRight { get; } |
||||||
|
|
||||||
|
public TextureCoords4(float x1, float y1, float x2, float y2) |
||||||
|
{ |
||||||
|
TopLeft = new(x1, y1); |
||||||
|
TopRight = new(x2, y1); |
||||||
|
BottomLeft = new(x1, y2); |
||||||
|
BottomRight = new(x2, y2); |
||||||
|
} |
||||||
|
|
||||||
|
public static TextureCoords4 FromIntCoords(Size textureSize, Point origin, Size size) |
||||||
|
=> FromIntCoords(textureSize, origin.X, origin.Y, size.Width, size.Height); |
||||||
|
public static TextureCoords4 FromIntCoords(Size textureSize, int x, int y, int width, int height) => new( |
||||||
|
x / (float)textureSize.Width + 0.001F, |
||||||
|
y / (float)textureSize.Height + 0.001F, |
||||||
|
(x + width) / (float)textureSize.Width - 0.001F, |
||||||
|
(y + height) / (float)textureSize.Height - 0.001F); |
||||||
|
|
||||||
|
public static TextureCoords4 FromGrid(int numCellsX, int numCellsY, int cellX, int cellY) => new( |
||||||
|
cellX / (float)numCellsX + 0.001F, |
||||||
|
cellY / (float)numCellsY + 0.001F, |
||||||
|
(cellX + 1) / (float)numCellsX - 0.001F, |
||||||
|
(cellY + 1) / (float)numCellsY - 0.001F); |
||||||
|
} |
||||||
|
} |
@ -1,14 +0,0 @@ |
|||||||
using gaemstone.ECS; |
|
||||||
|
|
||||||
namespace gaemstone.Client; |
|
||||||
|
|
||||||
[Component] |
|
||||||
public readonly struct Mesh |
|
||||||
{ |
|
||||||
public uint Handle { get; } |
|
||||||
public int Count { get; } |
|
||||||
public bool IsIndexed { get; } |
|
||||||
|
|
||||||
public Mesh(uint handle, int count, bool indexed = true) |
|
||||||
{ Handle = handle; Count = count; IsIndexed = indexed; } |
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
using gaemstone.ECS; |
|
||||||
using Silk.NET.OpenGL; |
|
||||||
|
|
||||||
namespace gaemstone.Client; |
|
||||||
|
|
||||||
[Component] |
|
||||||
public readonly struct Texture |
|
||||||
{ |
|
||||||
public TextureTarget Target { get; } |
|
||||||
public uint Handle { get; } |
|
||||||
|
|
||||||
public Texture(TextureTarget target, uint handle) |
|
||||||
=> (Target, Handle) = (target, handle); |
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
using System.Drawing; |
|
||||||
using gaemstone.ECS; |
|
||||||
using Silk.NET.Maths; |
|
||||||
|
|
||||||
namespace gaemstone.Client; |
|
||||||
|
|
||||||
[Component] |
|
||||||
public readonly struct TextureCoords4 |
|
||||||
{ |
|
||||||
public Vector2D<float> TopLeft { get; } |
|
||||||
public Vector2D<float> TopRight { get; } |
|
||||||
public Vector2D<float> BottomLeft { get; } |
|
||||||
public Vector2D<float> BottomRight { get; } |
|
||||||
|
|
||||||
public TextureCoords4(float x1, float y1, float x2, float y2) |
|
||||||
{ |
|
||||||
TopLeft = new(x1, y1); |
|
||||||
TopRight = new(x2, y1); |
|
||||||
BottomLeft = new(x1, y2); |
|
||||||
BottomRight = new(x2, y2); |
|
||||||
} |
|
||||||
|
|
||||||
public static TextureCoords4 FromIntCoords(Size textureSize, Point origin, Size size) |
|
||||||
=> FromIntCoords(textureSize, origin.X, origin.Y, size.Width, size.Height); |
|
||||||
public static TextureCoords4 FromIntCoords(Size textureSize, int x, int y, int width, int height) => new( |
|
||||||
x / (float)textureSize.Width + 0.001F, |
|
||||||
y / (float)textureSize.Height + 0.001F, |
|
||||||
(x + width) / (float)textureSize.Width - 0.001F, |
|
||||||
(y + height) / (float)textureSize.Height - 0.001F); |
|
||||||
|
|
||||||
public static TextureCoords4 FromGrid(int numCellsX, int numCellsY, int cellX, int cellY) => new( |
|
||||||
cellX / (float)numCellsX + 0.001F, |
|
||||||
cellY / (float)numCellsY + 0.001F, |
|
||||||
(cellX + 1) / (float)numCellsX - 0.001F, |
|
||||||
(cellY + 1) / (float)numCellsY - 0.001F); |
|
||||||
} |
|
@ -0,0 +1,17 @@ |
|||||||
|
using gaemstone.ECS; |
||||||
|
using Silk.NET.Maths; |
||||||
|
|
||||||
|
namespace gaemstone.Components; |
||||||
|
|
||||||
|
[Module] |
||||||
|
public class TransformComponents |
||||||
|
{ |
||||||
|
[Component] |
||||||
|
public struct GlobalTransform |
||||||
|
{ |
||||||
|
public Matrix4X4<float> Value; |
||||||
|
public GlobalTransform(Matrix4X4<float> value) => Value = value; |
||||||
|
public static implicit operator GlobalTransform(in Matrix4X4<float> value) => new(value); |
||||||
|
public static implicit operator Matrix4X4<float>(in GlobalTransform index) => index.Value; |
||||||
|
} |
||||||
|
} |
@ -1,12 +0,0 @@ |
|||||||
using System; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] |
|
||||||
public class ComponentAttribute : Attribute { } |
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Struct)] |
|
||||||
public class TagAttribute : Attribute { } |
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] |
|
||||||
public class RelationAttribute : Attribute { } |
|
@ -0,0 +1,44 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using gaemstone.Utility; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] |
||||||
|
public class ComponentAttribute : Attribute { } |
||||||
|
|
||||||
|
public static class ComponentExtensions |
||||||
|
{ |
||||||
|
public static EntityRef RegisterComponent<T>(this Universe universe) |
||||||
|
=> universe.RegisterComponent(typeof(T)); |
||||||
|
public unsafe static EntityRef RegisterComponent(this Universe universe, Type type) |
||||||
|
{ |
||||||
|
var typeInfo = default(ecs_type_info_t); |
||||||
|
if (type.IsValueType) { |
||||||
|
var wrapper = TypeWrapper.For(type); |
||||||
|
if (!wrapper.IsUnmanaged) throw new Exception( |
||||||
|
"Struct component must satisfy the unmanaged constraint. " + |
||||||
|
"Consider making it a class if you need to store references."); |
||||||
|
var structLayout = type.StructLayoutAttribute; |
||||||
|
if (structLayout == null || structLayout.Value == LayoutKind.Auto) throw new Exception( |
||||||
|
"Struct component must have a sequential or explicit StructLayout. " + |
||||||
|
"This is to ensure that the struct fields are not reorganized."); |
||||||
|
typeInfo.size = wrapper.Size; |
||||||
|
typeInfo.alignment = structLayout.Pack; |
||||||
|
} else { |
||||||
|
typeInfo.size = sizeof(nint); |
||||||
|
typeInfo.alignment = sizeof(nint); |
||||||
|
} |
||||||
|
|
||||||
|
var name = type.GetFriendlyName(); |
||||||
|
var entity = new EntityBuilder(universe, name) { Symbol = name } .Build(); |
||||||
|
var desc = new ecs_component_desc_t { entity = entity, type = typeInfo }; |
||||||
|
|
||||||
|
entity = new(universe, new(ecs_component_init(universe, &desc))); |
||||||
|
universe.RegisterLookup(type, entity); |
||||||
|
// TODO: SetHooks(hooks, id); |
||||||
|
|
||||||
|
return entity; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
public unsafe readonly partial struct EntityRef |
||||||
|
{ |
||||||
|
public EntityRef Add(Identifier id) { ecs_add_id(Universe, this, id); return this; } |
||||||
|
public EntityRef Add(Entity relation, Entity target) => Add(relation & target); |
||||||
|
public EntityRef Remove(Identifier id) { ecs_remove_id(Universe, this, id); return this; } |
||||||
|
public EntityRef Remove(Entity relation, Entity target) => Remove(relation & target); |
||||||
|
public bool Has(Identifier id) => ecs_has_id(Universe, this, id); |
||||||
|
public bool Has(Entity relation, Entity target) => Has(relation & target); |
||||||
|
// public EntityRef Override(Identifier id) { ecs_override_id(Universe, this, id); return this; } |
||||||
|
// public EntityRef Override(Entity relation, Entity target) => Override(relation & target); |
||||||
|
|
||||||
|
public EntityRef Add<T>() |
||||||
|
=> Add(Universe.Lookup<T>()); |
||||||
|
public EntityRef Add<TRelation, TTarget>() |
||||||
|
=> Add(Universe.Lookup<TRelation>(), Universe.Lookup<TTarget>()); |
||||||
|
public EntityRef Add<TRelation>(Entity target) |
||||||
|
=> Add(Universe.Lookup<TRelation>(), target); |
||||||
|
|
||||||
|
public EntityRef Remove<T>() |
||||||
|
=> Remove(Universe.Lookup<T>()); |
||||||
|
public EntityRef Remove<TRelation, TTarget>() |
||||||
|
=> Remove(Universe.Lookup<TRelation>(), Universe.Lookup<TTarget>()); |
||||||
|
public EntityRef Remove<TRelation>(Entity target) |
||||||
|
=> Remove(Universe.Lookup<TRelation>(), target); |
||||||
|
|
||||||
|
public bool Has<T>() |
||||||
|
=> Has(Universe.Lookup<T>()); |
||||||
|
public bool Has<TRelation, TTarget>() |
||||||
|
=> Has(Universe.Lookup<TRelation>(), Universe.Lookup<TTarget>()); |
||||||
|
public bool Has<TRelation>(Entity target) |
||||||
|
=> Has(Universe.Lookup<TRelation>(), target); |
||||||
|
|
||||||
|
// public EntityRef Override<T>() |
||||||
|
// => Override(Universe.Lookup<T>()); |
||||||
|
// public EntityRef Override<TRelation, TTarget>() |
||||||
|
// => Override(Universe.Lookup<TRelation>(), Universe.Lookup<TTarget>()); |
||||||
|
// public EntityRef Override<TRelation>(Entity target) |
||||||
|
// => Override(Universe.Lookup<TRelation>(), target); |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.CompilerServices; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
public unsafe readonly partial struct EntityRef |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Gets a component value from this entity. If the component is a value |
||||||
|
/// type, this will return a copy. If the component is a reference type, |
||||||
|
/// it will return the reference itself. |
||||||
|
/// When modifying a reference, consider calling <see cref="Modified"/>. |
||||||
|
/// </summary> |
||||||
|
public T Get<T>() |
||||||
|
{ |
||||||
|
var comp = Universe.Lookup<T>(); |
||||||
|
var ptr = ecs_get_id(Universe, this, comp); |
||||||
|
return (typeof(T).IsValueType) ? Unsafe.Read<T>(ptr) |
||||||
|
: (T)((GCHandle)Unsafe.Read<nint>(ptr)).Target!; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Gets a reference to a component value from this entity. Only works for |
||||||
|
/// value types. When modifying, consider calling <see cref="Modified"/>. |
||||||
|
/// </summary> |
||||||
|
public ref T GetRef<T>() |
||||||
|
where T : unmanaged |
||||||
|
{ |
||||||
|
var comp = Universe.Lookup<T>(); |
||||||
|
var ptr = ecs_get_mut_id(Universe, this, comp); |
||||||
|
return ref Unsafe.AsRef<T>(ptr); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Marks a component as modified. Do this after getting a reference to |
||||||
|
/// it with <see cref="Get"/> or <see cref="GetRef"/>, making sure change |
||||||
|
/// detection will kick in. |
||||||
|
/// </summary> |
||||||
|
public void Modified<T>() |
||||||
|
=> ecs_modified_id(Universe, this, Universe.Lookup<T>()); |
||||||
|
|
||||||
|
|
||||||
|
public EntityRef Set<T>(in T value) |
||||||
|
where T : unmanaged |
||||||
|
{ |
||||||
|
var comp = Universe.Lookup<T>(); |
||||||
|
var size = (ulong)Unsafe.SizeOf<T>(); |
||||||
|
fixed (T* ptr = &value) |
||||||
|
ecs_set_id(Universe, this, comp, size, ptr); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
// public Entity SetOverride<T>(in T value) |
||||||
|
// where T : unmanaged |
||||||
|
// { |
||||||
|
// var comp = Universe.Lookup<T>(); |
||||||
|
// var size = (ulong)Unsafe.SizeOf<T>(); |
||||||
|
// ecs_add_id(Universe, this, Identifier.Combine(IdentifierFlags.Override, comp)); |
||||||
|
// fixed (T* ptr = &value) ecs_set_id(Universe, this, comp, size, ptr); |
||||||
|
// return this; |
||||||
|
// } |
||||||
|
|
||||||
|
public EntityRef Set<T>(T obj) where T : class
|
||||||
|
=> Set(typeof(T), obj); |
||||||
|
public EntityRef Set(Type type, object obj) |
||||||
|
{ |
||||||
|
var comp = Universe.Lookup(type); |
||||||
|
var handle = (nint)GCHandle.Alloc(obj); |
||||||
|
ecs_set_id(Universe, this, comp, (ulong)sizeof(nint), &handle); |
||||||
|
// FIXME: Handle needs to be freed when component is removed! |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
// public EntityRef SetOverride<T>(T obj) |
||||||
|
// where T : class |
||||||
|
// { |
||||||
|
// var comp = Universe.Lookup<T>(); |
||||||
|
// var handle = (nint)GCHandle.Alloc(obj); |
||||||
|
// ecs_add_id(Universe, this, Identifier.Combine(IdentifierFlags.Override, comp)); |
||||||
|
// ecs_set_id(Universe, this, comp, (ulong)sizeof(nint), &handle); |
||||||
|
// // FIXME: Handle needs to be freed when component is removed! |
||||||
|
// return this; |
||||||
|
// } |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
using System.Collections; |
||||||
|
using System.Collections.Generic; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
// TODO: Create an interface for EntityBuilder and EntityRef so common operations are available through it. |
||||||
|
public class EntityBuilder |
||||||
|
: IReadOnlyCollection<Identifier> |
||||||
|
{ |
||||||
|
public Universe Universe { get; } |
||||||
|
|
||||||
|
/// <summary> Set to modify existing entity (optional). </summary> |
||||||
|
public Entity ID { get; set; } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Name of the entity. If no entity is provided, an entity with this name |
||||||
|
/// will be looked up first. When an entity is provided, the name will be |
||||||
|
/// verified with the existing entity. |
||||||
|
/// </summary> |
||||||
|
public string? Name { 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 string? Symbol { get; set; } |
||||||
|
|
||||||
|
/// <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 List<Identifier> _add = new(); |
||||||
|
|
||||||
|
/// <summary> String expression with components to add. </summary> |
||||||
|
public string? Expression { get; } |
||||||
|
|
||||||
|
public EntityBuilder(Universe universe) => Universe = universe; |
||||||
|
public EntityBuilder(Universe universe, string name) : this(universe) => Name = name; |
||||||
|
|
||||||
|
public EntityBuilder Add(Identifier id) { _add.Add(id); return this; } |
||||||
|
public EntityBuilder Add(string name) => Add(Universe.Lookup(name)); |
||||||
|
public EntityBuilder Add<T>() => Add(Universe.Lookup<T>()); |
||||||
|
|
||||||
|
public EntityBuilder Add(Entity first, Entity second) => Add(first & second); |
||||||
|
public EntityBuilder Add<TFirst>(Entity second) => Add(Universe.Lookup<TFirst>(), second); |
||||||
|
public EntityBuilder Add<TFirst, TSecond>() => Add(Universe.Lookup<TFirst>(), Universe.Lookup<TSecond>()); |
||||||
|
|
||||||
|
// TODO: Add support for Set. |
||||||
|
|
||||||
|
public EntityBuilder ChildOf(Entity parent) => Add(Universe.Lookup<Flecs.ChildOf>(), parent); |
||||||
|
public EntityBuilder ChildOf<TParent>() => ChildOf(Universe.Lookup<TParent>()); |
||||||
|
|
||||||
|
public EntityBuilder Disabled() => Add(Universe.Lookup<Flecs.Disabled>()); |
||||||
|
|
||||||
|
public unsafe EntityRef Build() |
||||||
|
{ |
||||||
|
var desc = new ecs_entity_desc_t { |
||||||
|
id = ID, |
||||||
|
name = Name.FlecsToCString(), |
||||||
|
symbol = Symbol.FlecsToCString(), |
||||||
|
add_expr = Expression.FlecsToCString(), |
||||||
|
use_low_id = UseLowID, |
||||||
|
}; |
||||||
|
var add = desc.add; |
||||||
|
for (var i = 0; i < Count; i++) add[i] = _add[i]; |
||||||
|
var entity = ecs_entity_init(Universe, &desc); |
||||||
|
return new(Universe, new(entity)); |
||||||
|
} |
||||||
|
|
||||||
|
// IReadOnlyCollection implementation |
||||||
|
public int Count => _add.Count; |
||||||
|
public IEnumerator<Identifier> GetEnumerator() => _add.GetEnumerator(); |
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
||||||
|
} |
@ -1,20 +0,0 @@ |
|||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public struct EntityDesc |
|
||||||
{ |
|
||||||
public ecs_entity_desc_t Value; |
|
||||||
|
|
||||||
public string? Name { get => Value.name; set => Value.name.Set(value); } |
|
||||||
public string? Symbol { get => Value.symbol; set => Value.symbol.Set(value); } |
|
||||||
|
|
||||||
public EntityDesc(params ecs_id_t[] ids) |
|
||||||
{ |
|
||||||
Value = default; |
|
||||||
for (var i = 0; i < ids.Length; i++) |
|
||||||
Value.add[i] = ids[i]; |
|
||||||
} |
|
||||||
|
|
||||||
public static explicit operator ecs_entity_desc_t(EntityDesc desc) => desc.Value; |
|
||||||
} |
|
@ -0,0 +1,41 @@ |
|||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
public static class Flecs |
||||||
|
{ |
||||||
|
// Entities |
||||||
|
|
||||||
|
// [BuiltIn] public struct World { } |
||||||
|
[BuiltIn] public struct Flag { } |
||||||
|
|
||||||
|
// Tags |
||||||
|
|
||||||
|
[BuiltIn] public struct Prefab { } |
||||||
|
[BuiltIn] public struct SlotOf { } |
||||||
|
[BuiltIn] public struct Disabled { } |
||||||
|
[BuiltIn] public struct Empty { } |
||||||
|
|
||||||
|
// Component / relationship properties |
||||||
|
|
||||||
|
[BuiltIn(256 + 10)] public struct Wildcard { } |
||||||
|
[BuiltIn(256 + 11)] public struct Any { } |
||||||
|
[BuiltIn(256 + 12)] public struct This { } |
||||||
|
[BuiltIn(256 + 13)] public struct Variable { } |
||||||
|
|
||||||
|
[BuiltIn] public struct Transitive { } |
||||||
|
[BuiltIn] public struct Reflexive { } |
||||||
|
[BuiltIn] public struct Symmetric { } |
||||||
|
[BuiltIn] public struct Final { } |
||||||
|
[BuiltIn] public struct DontInherit { } |
||||||
|
[BuiltIn] public struct Tag { } |
||||||
|
[BuiltIn] public struct Union { } |
||||||
|
[BuiltIn] public struct Exclusive { } |
||||||
|
[BuiltIn] public struct Acyclic { } |
||||||
|
[BuiltIn] public struct With { } |
||||||
|
[BuiltIn] public struct OneOf { } |
||||||
|
|
||||||
|
// Relationships |
||||||
|
|
||||||
|
[BuiltIn] public struct IsA { } |
||||||
|
[BuiltIn] public struct ChildOf { } |
||||||
|
[BuiltIn] public struct DependsOn { } |
||||||
|
} |
@ -1,17 +1,35 @@ |
|||||||
using System; |
using System; |
||||||
using System.Diagnostics; |
using System.Diagnostics; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
namespace gaemstone.ECS; |
namespace gaemstone.ECS; |
||||||
|
|
||||||
public class FlecsException : Exception |
public class FlecsException |
||||||
|
: Exception |
||||||
{ |
{ |
||||||
public FlecsException() : base() { } |
public FlecsException() : base() { } |
||||||
public FlecsException(string message) : base(message) { } |
public FlecsException(string message) : base(message) { } |
||||||
} |
} |
||||||
|
|
||||||
public class FlecsAbortException : FlecsException |
public class FlecsAbortException |
||||||
|
: FlecsException |
||||||
{ |
{ |
||||||
private readonly string _stackTrace = new StackTrace(2, true).ToString(); |
private readonly string _stackTrace = new StackTrace(2, true).ToString(); |
||||||
internal FlecsAbortException() : base("Abort was called by flecs") { } |
|
||||||
public override string? StackTrace => _stackTrace; |
public override string? StackTrace => _stackTrace; |
||||||
|
|
||||||
|
private FlecsAbortException() |
||||||
|
: base("Abort was called by flecs") { } |
||||||
|
|
||||||
|
// TODO: This might not be ideal if we ever want to set other OS API settings. |
||||||
|
public unsafe static void SetupHook() |
||||||
|
{ |
||||||
|
ecs_os_set_api_defaults(); |
||||||
|
var api = ecs_os_get_api(); |
||||||
|
api.abort_ = new FnPtr_Void { Pointer = &Abort }; |
||||||
|
ecs_os_set_api(&api); |
||||||
|
} |
||||||
|
|
||||||
|
[UnmanagedCallersOnly] |
||||||
|
private static void Abort() => throw new FlecsAbortException(); |
||||||
} |
} |
||||||
|
@ -0,0 +1,15 @@ |
|||||||
|
using System; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Entity for storing global game state and configuration. |
||||||
|
/// Parameters can use <see cref="GameAttribute"/> to source this entity. |
||||||
|
/// </summary> |
||||||
|
[Entity] |
||||||
|
public struct Game { } |
||||||
|
|
||||||
|
/// <summary> Short for <c>[Source(typeof(Game))]</c>. </summary> |
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class GameAttribute : SourceAttribute |
||||||
|
{ public GameAttribute() : base(typeof(Game)) { } } |
@ -1,55 +1,82 @@ |
|||||||
using System; |
using System; |
||||||
|
using System.Diagnostics.CodeAnalysis; |
||||||
using static flecs_hub.flecs; |
using static flecs_hub.flecs; |
||||||
|
|
||||||
namespace gaemstone.ECS; |
namespace gaemstone.ECS; |
||||||
|
|
||||||
[Flags] |
public readonly struct Identifier |
||||||
public enum IdentifierFlags : ulong |
: IEquatable<Identifier> |
||||||
{ |
{ |
||||||
Pair = 1ul << 63, |
public readonly ecs_id_t Value; |
||||||
Override = 1ul << 62, |
|
||||||
Toggle = 1ul << 61, |
public IdentifierFlags Flags => (IdentifierFlags)(Value & ECS_ID_FLAGS_MASK); |
||||||
Or = 1ul << 60, |
public bool IsPair => ecs_id_is_pair(Value); |
||||||
And = 1ul << 59, |
public bool IsWildcard => ecs_id_is_wildcard(Value); |
||||||
Not = 1ul << 58, |
|
||||||
|
public Identifier(ecs_id_t value) => Value = value; |
||||||
|
|
||||||
|
public static Identifier Combine(IdentifierFlags flags, Identifier id) |
||||||
|
=> new((ulong)flags | id.Value); |
||||||
|
public static Identifier Pair(Entity first, Entity second) |
||||||
|
=> Combine(IdentifierFlags.Pair, new((first.Value.Data << 32) + (uint)second.Value.Data)); |
||||||
|
|
||||||
|
public bool Equals(Identifier other) => Value.Data == other.Value.Data; |
||||||
|
public override bool Equals([NotNullWhen(true)] object? obj) => (obj is Identifier other) && Equals(other); |
||||||
|
public override int GetHashCode() => Value.Data.GetHashCode(); |
||||||
|
public override string? ToString() |
||||||
|
=> (Flags != default) ? $"Identifier(0x{Value.Data:X}, Flags={Flags})" |
||||||
|
: $"Identifier(0x{Value.Data:X})"; |
||||||
|
|
||||||
|
public static bool operator ==(Identifier left, Identifier right) => left.Equals(right); |
||||||
|
public static bool operator !=(Identifier left, Identifier right) => !left.Equals(right); |
||||||
|
|
||||||
|
public static implicit operator ecs_id_t(Identifier i) => i.Value; |
||||||
} |
} |
||||||
|
|
||||||
public unsafe readonly struct Identifier |
public unsafe readonly struct IdentifierRef |
||||||
|
: IEquatable<IdentifierRef> |
||||||
{ |
{ |
||||||
public Universe Universe { get; } |
public Universe Universe { get; } |
||||||
public ecs_id_t Value { get; } |
public Identifier ID { get; } |
||||||
|
|
||||||
public IdentifierFlags Flags => (IdentifierFlags)(Value.Data & ECS_ID_FLAGS_MASK); |
|
||||||
public bool IsPair => ecs_id_is_pair(Value); |
|
||||||
|
|
||||||
public Identifier(Universe universe, ecs_id_t id) |
public IdentifierFlags Flags => ID.Flags; |
||||||
{ Universe = universe; Value = id; } |
public bool IsPair => ID.IsPair; |
||||||
public Identifier(Universe universe, ecs_id_t id, IdentifierFlags flags) |
public bool IsWildcard => ID.IsWildcard; |
||||||
: this(universe, Combine(flags, id)) { } |
public bool IsValid => ecs_id_is_valid(Universe, ID); |
||||||
|
|
||||||
public static ecs_id_t Combine(IdentifierFlags flags, ecs_id_t id) |
public IdentifierRef(Universe universe, Identifier id) |
||||||
=> (ulong)flags | id; |
{ Universe = universe; ID = id; } |
||||||
public static ecs_id_t Pair(ecs_id_t first, ecs_id_t second) |
|
||||||
=> Combine(IdentifierFlags.Pair, (first << 32) + (uint)second); |
|
||||||
|
|
||||||
public static Identifier Pair(Entity first, Entity second) |
public static IdentifierRef Combine(IdentifierFlags flags, IdentifierRef id) |
||||||
=> new(first.Universe, Pair((ecs_id_t)first, (ecs_id_t)second)); |
=> new(id.Universe, Identifier.Combine(flags, id)); |
||||||
public static Identifier Pair(ecs_entity_t first, Entity second) |
public static IdentifierRef Pair(EntityRef first, Entity second) |
||||||
=> new(second.Universe, Pair((ecs_id_t)first, (ecs_id_t)second)); |
=> new(first.Universe, Identifier.Pair(first, second)); |
||||||
|
public static IdentifierRef Pair(Entity first, EntityRef second) |
||||||
|
=> new(second.Universe, Identifier.Pair(first, second)); |
||||||
|
|
||||||
public (Entity, Entity) AsPair() |
public (EntityRef, EntityRef) AsPair() |
||||||
=> (Universe.Lookup((ecs_id_t)((Value & ECS_COMPONENT_MASK) >> 32)), |
=> (Universe.Lookup(new Entity(new() { Data = (ID.Value & ECS_COMPONENT_MASK) >> 32 })), |
||||||
Universe.Lookup((ecs_id_t) (Value & ECS_ENTITY_MASK))); |
Universe.Lookup(new Entity(new() { Data = ID.Value & ECS_ENTITY_MASK }))); |
||||||
|
|
||||||
// public Entity AsComponent() |
public bool Equals(IdentifierRef other) => Universe == other.Universe && ID == other.ID; |
||||||
// { |
public override bool Equals([NotNullWhen(true)] object? obj) => (obj is IdentifierRef other) && Equals(other); |
||||||
// var value = Value.Data & ECS_COMPONENT_MASK; |
public override int GetHashCode() => HashCode.Combine(Universe, ID); |
||||||
// return new Entity(Universe, new() { Data = value }); |
public override string? ToString() => ecs_id_str(Universe, this).FlecsToStringAndFree()!; |
||||||
// } |
|
||||||
|
|
||||||
public override string ToString() |
public static bool operator ==(IdentifierRef left, IdentifierRef right) => left.Equals(right); |
||||||
=> ecs_id_str(Universe, Value).ToStringAndFree(); |
public static bool operator !=(IdentifierRef left, IdentifierRef right) => !left.Equals(right); |
||||||
|
|
||||||
|
public static implicit operator Identifier(IdentifierRef i) => i.ID; |
||||||
|
public static implicit operator ecs_id_t(IdentifierRef i) => i.ID.Value; |
||||||
|
} |
||||||
|
|
||||||
public static implicit operator ecs_id_t(Identifier e) => e.Value; |
[Flags] |
||||||
|
public enum IdentifierFlags : ulong |
||||||
|
{ |
||||||
|
Pair = 1ul << 63, |
||||||
|
Override = 1ul << 62, |
||||||
|
Toggle = 1ul << 61, |
||||||
|
Or = 1ul << 60, |
||||||
|
And = 1ul << 59, |
||||||
|
Not = 1ul << 58, |
||||||
} |
} |
||||||
|
@ -1,32 +1,76 @@ |
|||||||
using System; |
using System; |
||||||
|
using System.Reflection; |
||||||
|
using gaemstone.Utility; |
||||||
|
using gaemstone.Utility.IL; |
||||||
using static flecs_hub.flecs; |
using static flecs_hub.flecs; |
||||||
|
|
||||||
namespace gaemstone.ECS; |
namespace gaemstone.ECS; |
||||||
|
|
||||||
public enum ObserverEvent |
public static class ObserverEvent |
||||||
{ |
{ |
||||||
OnAdd = ECS_HI_COMPONENT_ID + 33, |
[BuiltIn] public struct OnAdd { } |
||||||
OnRemove = ECS_HI_COMPONENT_ID + 34, |
[BuiltIn] public struct OnRemove { } |
||||||
OnSet = ECS_HI_COMPONENT_ID + 35, |
[BuiltIn] public struct OnSet { } |
||||||
UnSet = ECS_HI_COMPONENT_ID + 36, |
[BuiltIn] public struct UnSet { } |
||||||
OnDelete = ECS_HI_COMPONENT_ID + 37, |
// [BuiltIn] public struct OnDelete { } |
||||||
OnCreateTable = ECS_HI_COMPONENT_ID + 38, |
// [BuiltIn] public struct OnCreateTable { } |
||||||
OnDeleteTable = ECS_HI_COMPONENT_ID + 39, |
// [BuiltIn] public struct OnDeleteTable { } |
||||||
OnTableEmpty = ECS_HI_COMPONENT_ID + 40, |
[BuiltIn] public struct OnTableEmpty { } |
||||||
OnTableFill = ECS_HI_COMPONENT_ID + 41, |
[BuiltIn] public struct OnTableFilled { } |
||||||
OnCreateTrigger = ECS_HI_COMPONENT_ID + 42, |
// [BuiltIn] public struct OnCreateTrigger { } |
||||||
OnDeleteTrigger = ECS_HI_COMPONENT_ID + 43, |
// [BuiltIn] public struct OnDeleteTrigger { } |
||||||
OnDeleteObservable = ECS_HI_COMPONENT_ID + 44, |
// [BuiltIn] public struct OnDeleteObservable { } |
||||||
OnComponentHooks = ECS_HI_COMPONENT_ID + 45, |
// [BuiltIn] public struct OnComponentHooks { } |
||||||
OnDeleteTarget = ECS_HI_COMPONENT_ID + 46, |
// [BuiltIn] public struct OnDeleteTarget { } |
||||||
} |
} |
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Method)] |
[AttributeUsage(AttributeTargets.Method)] |
||||||
public class ObserverAttribute : Attribute |
public class ObserverAttribute : Attribute |
||||||
{ |
{ |
||||||
public ObserverEvent Event { get; } |
public Type Event { get; } |
||||||
public string? Expression { get; } |
public string? Expression { get; } |
||||||
|
|
||||||
public ObserverAttribute(ObserverEvent @event) |
public ObserverAttribute(Type @event) => Event = @event; |
||||||
=> Event = @event; |
} |
||||||
|
|
||||||
|
public static class ObserverExtensions |
||||||
|
{ |
||||||
|
public static unsafe EntityRef RegisterObserver(this Universe universe, |
||||||
|
string name, Entity @event, FilterDesc filter, Action<Iterator> callback) |
||||||
|
{ |
||||||
|
filter.Name = name; |
||||||
|
var desc = new ecs_observer_desc_t { |
||||||
|
filter = filter.ToFlecs(), |
||||||
|
entity = universe.Create(name), |
||||||
|
binding_ctx = (void*)CallbackContextHelper.Create(universe, callback), |
||||||
|
callback = new() { Data = new() { Pointer = &CallbackContextHelper.Callback } }, |
||||||
|
}; |
||||||
|
desc.events[0] = @event; |
||||||
|
var entity = ecs_observer_init(universe, &desc); |
||||||
|
return new(universe, new(entity)); |
||||||
|
} |
||||||
|
|
||||||
|
public static EntityRef RegisterObserver(this Universe universe, |
||||||
|
object? instance, MethodInfo method) |
||||||
|
{ |
||||||
|
var attr = method.Get<ObserverAttribute>() ?? throw new ArgumentException( |
||||||
|
"Observer must specify ObserverAttribute", nameof(method)); |
||||||
|
var filter = new FilterDesc(); |
||||||
|
Action<Iterator> iterAction; |
||||||
|
|
||||||
|
var param = method.GetParameters(); |
||||||
|
if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) { |
||||||
|
filter.Expression = attr.Expression ?? throw new Exception( |
||||||
|
"Observer must specify expression in ObserverAttribute"); |
||||||
|
iterAction = (Action<Iterator>)Delegate.CreateDelegate(typeof(Action<Iterator>), instance, method); |
||||||
|
} else { |
||||||
|
var gen = IterActionGenerator.GetOrBuild(universe, method); |
||||||
|
if (attr.Expression == null) filter.Terms = gen.Terms; |
||||||
|
else filter.Expression = attr.Expression; |
||||||
|
iterAction = iter => gen.RunWithTryCatch(instance, iter); |
||||||
|
} |
||||||
|
|
||||||
|
var @event = universe.Lookup(attr.Event); |
||||||
|
return universe.RegisterObserver(method.Name, @event, filter, iterAction); |
||||||
|
} |
||||||
} |
} |
||||||
|
@ -1,65 +1,56 @@ |
|||||||
using System; |
using System; |
||||||
|
using System.Collections.Generic; |
||||||
using System.Linq; |
using System.Linq; |
||||||
using System.Reflection; |
using System.Reflection; |
||||||
using gaemstone.Utility; |
using gaemstone.Utility; |
||||||
|
|
||||||
namespace gaemstone.ECS; |
namespace gaemstone.ECS; |
||||||
|
|
||||||
public enum RegisterableKind |
// TODO: Make this return an EntityBuilder instead. |
||||||
{ |
public delegate EntityRef RegisterFunc(Universe universe, object? instance, MemberInfo member); |
||||||
Entity, |
|
||||||
Tag, |
|
||||||
Component, |
|
||||||
Relation, |
|
||||||
System, |
|
||||||
Observer, |
|
||||||
Module, |
|
||||||
} |
|
||||||
|
|
||||||
public class RegisterableInfo |
public class RegisterableInfo |
||||||
{ |
{ |
||||||
public Type Type { get; } |
public bool? PartOfModule { get; } // true/false = must (not) be part of module; null = doesn't matter |
||||||
public RegisterableKind Kind { get; } |
public RegisterFunc Register { get; } |
||||||
public bool? PartOfModule { get; } |
public Type[] AllowedWith { get; } |
||||||
internal Type[] AllowedWith { get; } |
|
||||||
|
|
||||||
internal RegisterableInfo(Type type, RegisterableKind kind, bool? partOfModule, Type[]? allowedWith = null) |
public RegisterableInfo(bool? partOfModule, RegisterFunc register, params Type[] allowedWith) |
||||||
{ Type = type; Kind = kind; PartOfModule = partOfModule; AllowedWith = allowedWith ?? Array.Empty<Type>(); } |
{ PartOfModule = partOfModule; Register = register; AllowedWith = allowedWith; } |
||||||
} |
} |
||||||
|
|
||||||
public static class RegisterableExtensions |
public static class RegisterableExtensions |
||||||
{ |
{ |
||||||
|
// Ordered by priority. For example a type with [Component, Relation] |
||||||
// These are ordered by priority. For example a type marked with [Component, Relation] |
// will result in a Relation due to being first in the list. |
||||||
// will result in RegisterableKind.Relation due to being first in the list. |
private static readonly Dictionary<Type, RegisterableInfo> _knownAttributes = new() { |
||||||
private static readonly RegisterableInfo[] _knownAttributes = new RegisterableInfo[] { |
[typeof(RelationAttribute) ] = new(null, (u, i, m) => u.RegisterRelation ((Type)m), typeof(ComponentAttribute), typeof(TagAttribute)), |
||||||
new(typeof(RelationAttribute) , RegisterableKind.Relation , null, new[] { typeof(ComponentAttribute), typeof(TagAttribute) }), |
[typeof(ComponentAttribute)] = new(null, (u, i, m) => u.RegisterComponent((Type)m), typeof(EntityAttribute)), |
||||||
new(typeof(ComponentAttribute) , RegisterableKind.Component , null, new[] { typeof(EntityAttribute) }), |
[typeof(TagAttribute) ] = new(null, (u, i, m) => u.RegisterTag ((Type)m)), |
||||||
new(typeof(TagAttribute) , RegisterableKind.Tag , null), |
[typeof(EntityAttribute) ] = new(null, (u, i, m) => u.RegisterEntity ((Type)m)), |
||||||
new(typeof(EntityAttribute) , RegisterableKind.Entity , null), |
|
||||||
|
[typeof(ModuleAttribute) ] = new(false, (u, i, m) => u.RegisterModule((Type)m)), |
||||||
new(typeof(ModuleAttribute) , RegisterableKind.Module , false), |
[typeof(SystemAttribute) ] = new(true , (u, i, m) => u.RegisterSystem (i, (MethodInfo)m)), |
||||||
new(typeof(SystemAttribute) , RegisterableKind.System , true), |
[typeof(ObserverAttribute)] = new(true , (u, i, m) => u.RegisterObserver(i, (MethodInfo)m)), |
||||||
new(typeof(ObserverAttribute) , RegisterableKind.Observer , true), |
|
||||||
}; |
}; |
||||||
|
|
||||||
public static RegisterableInfo? GetRegisterableInfo(this MemberInfo member, out bool isPartOfModule) |
public static RegisterableInfo? GetRegisterableInfo(this MemberInfo member, out bool isPartOfModule) |
||||||
{ |
{ |
||||||
isPartOfModule = member.DeclaringType?.Has<ModuleAttribute>() == true; |
isPartOfModule = member.DeclaringType?.Has<ModuleAttribute>() == true; |
||||||
var matched = _knownAttributes.Where(a => member.GetCustomAttribute(a.Type) != null).ToList(); |
var matched = _knownAttributes.Where(e => member.GetCustomAttribute(e.Key) != null).ToList(); |
||||||
if (matched.Count == 0) return null; |
|
||||||
|
|
||||||
var attr = matched[0]; |
if (matched.Count == 0) return null; |
||||||
|
var (type, info) = matched[0]; |
||||||
|
|
||||||
var disallowed = matched.Except(new[] { attr }).Select(a => a.Type).Except(attr.AllowedWith); |
var disallowed = matched.Skip(1).Select(a => a.Key).Except(info.AllowedWith); |
||||||
if (disallowed.Any()) throw new InvalidOperationException( |
if (disallowed.Any()) throw new InvalidOperationException( |
||||||
$"{member} marked with {attr.Type} may not be used together with " + string.Join(", ", disallowed)); |
$"{member} marked with {type} may not be used together with " + string.Join(", ", disallowed)); |
||||||
|
|
||||||
if (attr.PartOfModule == true && !isPartOfModule) throw new InvalidOperationException( |
if (info.PartOfModule == true && !isPartOfModule) throw new InvalidOperationException( |
||||||
$"{member} marked with {attr.Type} must be part of a module"); |
$"{member} marked with {type} must be part of a module"); |
||||||
if (attr.PartOfModule == false && isPartOfModule) throw new InvalidOperationException( |
if (info.PartOfModule == false && isPartOfModule) throw new InvalidOperationException( |
||||||
$"{member} marked with {attr.Type} must not be part of a module"); |
$"{member} marked with {type} must not be part of a module"); |
||||||
|
|
||||||
return attr; |
return info; |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,14 @@ |
|||||||
|
using System; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] |
||||||
|
public class RelationAttribute : Attribute { } |
||||||
|
|
||||||
|
public static class RelationExtensions |
||||||
|
{ |
||||||
|
public static EntityRef RegisterRelation<T>(this Universe universe) |
||||||
|
=> universe.RegisterRelation(typeof(T)); |
||||||
|
public unsafe static EntityRef RegisterRelation(this Universe universe, Type type) |
||||||
|
=> throw new NotImplementedException(); // TODO: Implement me. |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections; |
||||||
|
using System.Collections.Generic; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
public unsafe sealed class Rule |
||||||
|
: IEnumerable<Iterator> |
||||||
|
, IDisposable |
||||||
|
{ |
||||||
|
public Universe Universe { get; } |
||||||
|
public ecs_rule_t* Handle { get; } |
||||||
|
|
||||||
|
private Rule(Universe universe, ecs_rule_t* handle) |
||||||
|
{ Universe = universe; Handle = handle; } |
||||||
|
private Rule(Universe universe, ecs_filter_desc_t desc) |
||||||
|
: this(universe, ecs_rule_init(universe, &desc)) { } |
||||||
|
|
||||||
|
public Rule(Universe universe, FilterDesc desc) |
||||||
|
: this(universe, desc.ToFlecs()) { } |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
=> ecs_rule_fini(this); |
||||||
|
|
||||||
|
public override string ToString() |
||||||
|
=> ecs_rule_str(Handle).FlecsToStringAndFree()!; |
||||||
|
|
||||||
|
public static implicit operator ecs_rule_t*(Rule q) => q.Handle; |
||||||
|
|
||||||
|
// IEnumerable implementation |
||||||
|
public Iterator Iter() => new(Universe, IteratorType.Rule, ecs_rule_iter(Universe, this)); |
||||||
|
public IEnumerator<Iterator> GetEnumerator() => Iter().GetEnumerator(); |
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
// TODO: We *can* use path lookup here, but we have to look in "flecs.pipeline" instead of "flecs.core". |
||||||
|
public static class SystemPhase |
||||||
|
{ |
||||||
|
[BuiltIn(256 + 65)] public struct PreFrame { } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// This phase contains all the systems that load data into your ECS. |
||||||
|
/// This would be a good place to load keyboard and mouse inputs. |
||||||
|
/// </summary> |
||||||
|
[BuiltIn(256 + 66)] public struct OnLoad { } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Often the imported data needs to be processed. Maybe you want to associate |
||||||
|
/// your keypresses with high level actions rather than comparing explicitly |
||||||
|
/// in your game code if the user pressed the 'K' key. |
||||||
|
/// </summary> |
||||||
|
[BuiltIn(256 + 67)] public struct PostLoad { } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Now that the input is loaded and processed, it's time to get ready to |
||||||
|
/// start processing our game logic. Anything that needs to happen after |
||||||
|
/// input processing but before processing the game logic can happen here. |
||||||
|
/// This can be a good place to prepare the frame, maybe clean up some |
||||||
|
/// things from the previous frame, etcetera. |
||||||
|
/// </summary> |
||||||
|
[BuiltIn(256 + 68)] public struct PreUpdate { } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// This is usually where the magic happens! This is where you put all of |
||||||
|
/// your gameplay systems. By default systems are added to this phase. |
||||||
|
/// </summary> |
||||||
|
[BuiltIn(256 + 69)] public struct OnUpdate { } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// This phase was introduced to deal with validating the state of the game |
||||||
|
/// after processing the gameplay systems. Sometimes you entities too close |
||||||
|
/// to each other, or the speed of an entity is increased too much. |
||||||
|
/// This phase is for righting that wrong. A typical feature to implement |
||||||
|
/// in this phase would be collision detection. |
||||||
|
/// </summary> |
||||||
|
[BuiltIn(256 + 70)] public struct OnValidate { } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// When your game logic has been updated, and your validation pass has ran, |
||||||
|
/// you may want to apply some corrections. For example, if your collision |
||||||
|
/// detection system detected collisions in the <c>OnValidate</c> phase, |
||||||
|
/// you may want to move the entities so that they no longer overlap. |
||||||
|
/// </summary> |
||||||
|
[BuiltIn(256 + 71)] public struct PostUpdate { } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Now that all of the frame data is computed, validated and corrected for, |
||||||
|
/// it is time to prepare the frame for rendering. Any systems that need to |
||||||
|
/// run before rendering, but after processing the game logic should go here. |
||||||
|
/// A good example would be a system that calculates transform matrices from |
||||||
|
/// a scene graph. |
||||||
|
/// </summary> |
||||||
|
[BuiltIn(256 + 72)] public struct PreStore { } |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// This is where it all comes together. Your frame is ready to be |
||||||
|
/// rendered, and that is exactly what you would do in this phase. |
||||||
|
/// </summary> |
||||||
|
[BuiltIn(256 + 73)] public struct OnStore { } |
||||||
|
|
||||||
|
[BuiltIn(256 + 74)] public struct PostFrame { } |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
using System; |
||||||
|
using gaemstone.Utility; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Struct)] |
||||||
|
public class TagAttribute : Attribute { } |
||||||
|
|
||||||
|
public static class TagExtensions |
||||||
|
{ |
||||||
|
public static EntityRef RegisterTag<T>(this Universe universe) |
||||||
|
where T : unmanaged => universe.RegisterTag(typeof(T)); |
||||||
|
public static EntityRef RegisterTag(this Universe universe, Type type) |
||||||
|
{ |
||||||
|
if (!type.IsValueType || type.IsPrimitive || type.GetFields().Length > 0) |
||||||
|
throw new Exception("Tag must be an empty, used-defined struct."); |
||||||
|
var name = type.GetFriendlyName(); |
||||||
|
var entity = new EntityBuilder(universe, name) { Symbol = name } .Build(); |
||||||
|
entity.Add<Flecs.Tag>(); |
||||||
|
universe.RegisterLookup(type, entity); |
||||||
|
return entity; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,134 @@ |
|||||||
|
using System; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
// TODO: Make it possible to use [Source] on systems. |
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class SourceAttribute : Attribute |
||||||
|
{ |
||||||
|
public Type Type { get; } |
||||||
|
public SourceAttribute(Type type) => Type = type; |
||||||
|
} |
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class HasAttribute : Attribute { } |
||||||
|
|
||||||
|
// Parameters with "in" modifier are equivalent to [In]. |
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class InAttribute : Attribute { } |
||||||
|
|
||||||
|
// Parameters with "out" modifier are equivalent to [Out]. |
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class OutAttribute : Attribute { } |
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class NotAttribute : Attribute { } |
||||||
|
|
||||||
|
// Parameters with nullable syntax are equivalent to [Optional]. |
||||||
|
[AttributeUsage(AttributeTargets.Parameter)] |
||||||
|
public class OptionalAttribute : Attribute { } |
||||||
|
|
||||||
|
|
||||||
|
public class Term |
||||||
|
{ |
||||||
|
public Identifier ID { get; set; } |
||||||
|
public TermID? Source { get; set; } |
||||||
|
public TermID? First { get; set; } |
||||||
|
public TermID? Second { get; set; } |
||||||
|
public TermInOutKind InOut { get; set; } |
||||||
|
public TermOperKind Oper { get; set; } |
||||||
|
public IdentifierFlags Flags { get; set; } |
||||||
|
|
||||||
|
public Term() { } |
||||||
|
public Term(Identifier id) => ID = id; |
||||||
|
public Term(TermID first, TermID second) { First = first; Second = second; } |
||||||
|
|
||||||
|
public static implicit operator Term(EntityRef entity) => new(entity); |
||||||
|
public static implicit operator Term(Entity entity) => new(entity); |
||||||
|
public static implicit operator Term(IdentifierRef id) => new(id); |
||||||
|
public static implicit operator Term(Identifier id) => new(id); |
||||||
|
|
||||||
|
public ecs_term_t ToFlecs() => new() { |
||||||
|
id = ID, |
||||||
|
src = Source?.ToFlecs() ?? default, |
||||||
|
first = First?.ToFlecs() ?? default, |
||||||
|
second = Second?.ToFlecs() ?? default, |
||||||
|
inout = (ecs_inout_kind_t)InOut, |
||||||
|
oper = (ecs_oper_kind_t)Oper, |
||||||
|
id_flags = (ecs_id_t)(ulong)Flags, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public enum TermInOutKind |
||||||
|
{ |
||||||
|
Default = ecs_inout_kind_t.EcsInOutDefault, |
||||||
|
None = ecs_inout_kind_t.EcsInOutNone, |
||||||
|
InOut = ecs_inout_kind_t.EcsInOut, |
||||||
|
In = ecs_inout_kind_t.EcsIn, |
||||||
|
Out = ecs_inout_kind_t.EcsOut, |
||||||
|
} |
||||||
|
|
||||||
|
public enum TermOperKind |
||||||
|
{ |
||||||
|
And = ecs_oper_kind_t.EcsAnd, |
||||||
|
Or = ecs_oper_kind_t.EcsOr, |
||||||
|
Not = ecs_oper_kind_t.EcsNot, |
||||||
|
Optional = ecs_oper_kind_t.EcsOptional, |
||||||
|
AndFrom = ecs_oper_kind_t.EcsAndFrom, |
||||||
|
OrFrom = ecs_oper_kind_t.EcsOrFrom, |
||||||
|
NotFrom = ecs_oper_kind_t.EcsNotFrom, |
||||||
|
} |
||||||
|
|
||||||
|
public class TermID |
||||||
|
{ |
||||||
|
public static TermID This { get; } = new("$This"); |
||||||
|
|
||||||
|
public Entity ID { get; } |
||||||
|
public string? Name { get; } |
||||||
|
public Entity Traverse { get; set; } |
||||||
|
public TermTraversalFlags Flags { get; set; } |
||||||
|
|
||||||
|
public TermID(Entity id) |
||||||
|
=> ID = id; |
||||||
|
public TermID(string name) |
||||||
|
{ |
||||||
|
if (name[0] == '$') { |
||||||
|
Name = name[1..]; |
||||||
|
Flags = TermTraversalFlags.IsVariable; |
||||||
|
} else Name = name; |
||||||
|
} |
||||||
|
|
||||||
|
public static implicit operator TermID(EntityRef entity) => new(entity); |
||||||
|
public static implicit operator TermID(Entity entity) => new(entity); |
||||||
|
public static implicit operator TermID(string name) => new(name); |
||||||
|
|
||||||
|
public ecs_term_id_t ToFlecs() => new() { |
||||||
|
id = ID, |
||||||
|
name = Name.FlecsToCString(), |
||||||
|
trav = Traverse, |
||||||
|
flags = (ecs_flags32_t)(uint)Flags |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
[Flags] |
||||||
|
public enum TermTraversalFlags : uint |
||||||
|
{ |
||||||
|
/// <summary> Match on self. </summary> |
||||||
|
Self = EcsSelf, |
||||||
|
/// <summary> Match by traversing upwards. </summary> |
||||||
|
Up = EcsUp, |
||||||
|
/// <summary> Match by traversing downwards (derived, cannot be set). </summary> |
||||||
|
Down = EcsDown, |
||||||
|
/// <summary> Sort results breadth first. </summary> |
||||||
|
Cascade = EcsCascade, |
||||||
|
/// <summary> Short for up(ChildOf). </summary> |
||||||
|
Parent = EcsParent, |
||||||
|
/// <summary> Term id is a variable. </summary> |
||||||
|
IsVariable = EcsIsVariable, |
||||||
|
/// <summary> Term id is an entity. </summary> |
||||||
|
IsEntity = EcsIsEntity, |
||||||
|
/// <summary> Prevent observer from triggering on term. </summary> |
||||||
|
Filter = EcsFilter, |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using gaemstone.Utility; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Struct)] |
||||||
|
public class BuiltInAttribute : Attribute |
||||||
|
{ |
||||||
|
public string? Name { get; } |
||||||
|
public uint ID { get; } |
||||||
|
|
||||||
|
internal BuiltInAttribute() { } // Defaults to type's name. |
||||||
|
internal BuiltInAttribute(string name) => Name = name; |
||||||
|
internal BuiltInAttribute(uint id) => ID = id; |
||||||
|
} |
||||||
|
|
||||||
|
public unsafe partial class Universe |
||||||
|
{ |
||||||
|
private readonly Dictionary<Type, Entity> _lookupByType = new(); |
||||||
|
|
||||||
|
private void RegisterBuiltInLookups() |
||||||
|
{ |
||||||
|
foreach (var type in typeof(Universe).Assembly.GetTypes()) |
||||||
|
if (type.Get<BuiltInAttribute>() is BuiltInAttribute attr) |
||||||
|
RegisterLookup(type, (attr.ID != 0) |
||||||
|
? TryLookup(new Entity(new() { Data = attr.ID })) |
||||||
|
: TryLookup("flecs.core." + (attr.Name ?? type.Name))); |
||||||
|
} |
||||||
|
|
||||||
|
public void RegisterLookup(Type type, Entity entity) |
||||||
|
=> _lookupByType.Add(type, entity.ThrowIfInvalid()); |
||||||
|
public void RemoveLookup(Type type) |
||||||
|
{ if (!_lookupByType.Remove(type)) throw new InvalidOperationException( |
||||||
|
$"Type {type} was not present in lookups"); } |
||||||
|
|
||||||
|
public Entity TryLookup<T>() |
||||||
|
=> TryLookup(typeof(T)); |
||||||
|
public Entity TryLookup(Type type) |
||||||
|
=> _lookupByType.TryGetValue(type, out var e) ? new(e) : default; |
||||||
|
|
||||||
|
public Entity TryLookup(Entity value) |
||||||
|
=> new(ecs_get_alive(this, value)); |
||||||
|
|
||||||
|
public Entity TryLookup(string path) |
||||||
|
=> TryLookup(default, path); |
||||||
|
public Entity TryLookup(Entity parent, string path) |
||||||
|
=> new(ecs_lookup_path_w_sep(this, parent, path.FlecsToCString(), ".", default, true)); |
||||||
|
public Entity TryLookupSymbol(string symbol) |
||||||
|
=> new(ecs_lookup_symbol(this, symbol.FlecsToCString(), false)); |
||||||
|
|
||||||
|
|
||||||
|
public EntityRef Lookup<T>() => new(this, TryLookup<T>()); |
||||||
|
public EntityRef Lookup(Type type) => new(this, TryLookup(type)); |
||||||
|
|
||||||
|
public EntityRef Lookup(Entity entity) => new(this, TryLookup(entity)); |
||||||
|
|
||||||
|
public EntityRef Lookup(string path) => new(this, TryLookup(path)); |
||||||
|
public EntityRef Lookup(Entity parent, string path) => new(this, TryLookup(parent, path)); |
||||||
|
public EntityRef LookupSymbol(string symbol) => new(this, TryLookupSymbol(symbol)); |
||||||
|
} |
@ -1,147 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Collections.Immutable; |
|
||||||
using System.Linq; |
|
||||||
using System.Reflection; |
|
||||||
using gaemstone.Utility; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe partial class Universe |
|
||||||
{ |
|
||||||
public void RegisterModule<T>() where T : class
|
|
||||||
=> RegisterModule(typeof(T)); |
|
||||||
public void RegisterModule(Type type) |
|
||||||
{ |
|
||||||
var builder = new ModuleBuilder(this, type); |
|
||||||
|
|
||||||
builder.UnmetDependencies.ExceptWith(Modules._modules.Keys); |
|
||||||
if (builder.UnmetDependencies.Count > 0) { |
|
||||||
// If builder has unmet dependencies, defer the registration. |
|
||||||
Modules._deferred.Add(type, builder); |
|
||||||
} else { |
|
||||||
// Otherwise register it right away, .. |
|
||||||
Modules._modules.Add(type, new ModuleInfo(builder)); |
|
||||||
// .. and tell other deferred modules this one is now loaded. |
|
||||||
RemoveDependency(builder.Type); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void RemoveDependency(Type type) |
|
||||||
{ |
|
||||||
var resolved = Modules._deferred.Values |
|
||||||
.Where(d => d.UnmetDependencies.Remove(type) |
|
||||||
&& d.UnmetDependencies.Count == 0) |
|
||||||
.ToArray(); |
|
||||||
foreach (var builder in resolved) { |
|
||||||
Modules._deferred.Remove(builder.Type); |
|
||||||
Modules._modules.Add(builder.Type, new ModuleInfo(builder)); |
|
||||||
RemoveDependency(builder.Type); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public class UniverseModules |
|
||||||
{ |
|
||||||
internal readonly Dictionary<Type, ModuleInfo> _modules = new(); |
|
||||||
internal readonly Dictionary<Type, ModuleBuilder> _deferred = new(); |
|
||||||
} |
|
||||||
|
|
||||||
public class ModuleInfo |
|
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public object Instance { get; } |
|
||||||
|
|
||||||
public IReadOnlyList<Entity> Relations { get; } |
|
||||||
public IReadOnlyList<Entity> Components { get; } |
|
||||||
public IReadOnlyList<Entity> Tags { get; } |
|
||||||
public IReadOnlyList<Entity> Entities { get; } |
|
||||||
public IReadOnlyList<SystemInfo> Systems { get; } |
|
||||||
public IReadOnlyList<ObserverInfo> Observers { get; } |
|
||||||
|
|
||||||
internal ModuleInfo(ModuleBuilder builder) |
|
||||||
{ |
|
||||||
Universe = builder.Universe; |
|
||||||
Instance = builder.HasSimpleConstructor |
|
||||||
? Activator.CreateInstance(builder.Type)! |
|
||||||
: Activator.CreateInstance(builder.Type, Universe)!; |
|
||||||
|
|
||||||
Relations = builder.Relations .Select(Universe.RegisterRelation).ToImmutableList(); |
|
||||||
Components = builder.Components.Select(Universe.RegisterComponent).ToImmutableList(); |
|
||||||
Tags = builder.Tags .Select(Universe.RegisterTag).ToImmutableList(); |
|
||||||
Entities = builder.Entities .Select(Universe.RegisterEntity).ToImmutableList(); |
|
||||||
|
|
||||||
Systems = builder.Systems .Select(s => Universe.RegisterSystem(Instance, s)).ToImmutableList(); |
|
||||||
Observers = builder.Observers.Select(s => Universe.RegisterObserver(Instance, s)).ToImmutableList(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public class ModuleBuilder |
|
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public Type Type { get; } |
|
||||||
public IReadOnlyList<Type> DependsOn { get; } |
|
||||||
public bool HasSimpleConstructor { get; } |
|
||||||
|
|
||||||
public IReadOnlyList<Type> Relations { get; } |
|
||||||
public IReadOnlyList<Type> Components { get; } |
|
||||||
public IReadOnlyList<Type> Tags { get; } |
|
||||||
public IReadOnlyList<Type> Entities { get; } |
|
||||||
public IReadOnlyList<MethodInfo> Systems { get; } |
|
||||||
public IReadOnlyList<MethodInfo> Observers { get; } |
|
||||||
|
|
||||||
public HashSet<Type> UnmetDependencies { get; } |
|
||||||
|
|
||||||
internal ModuleBuilder(Universe universe, Type type) |
|
||||||
{ |
|
||||||
if (!type.IsClass || type.IsAbstract) throw new Exception( |
|
||||||
"Module must be a non-abstract and non-static class"); |
|
||||||
if (!type.Has<ModuleAttribute>()) throw new Exception( |
|
||||||
"Module must be marked with ModuleAttribute"); |
|
||||||
|
|
||||||
Universe = universe; |
|
||||||
Type = type; |
|
||||||
DependsOn = type.GetMultiple<DependsOnAttribute>() |
|
||||||
.Select(d => d.Target).ToImmutableList(); |
|
||||||
|
|
||||||
HasSimpleConstructor = type.GetConstructor(Type.EmptyTypes) != null; |
|
||||||
var hasUniverseConstructor = type.GetConstructor(new[] { typeof(Universe) }) != null; |
|
||||||
if (!HasSimpleConstructor && !hasUniverseConstructor) throw new Exception( |
|
||||||
$"Module {Type} must define a public constructor with either no parameters, or a single {nameof(Universe)} parameter"); |
|
||||||
|
|
||||||
var relations = new List<Type>(); |
|
||||||
var components = new List<Type>(); |
|
||||||
var tags = new List<Type>(); |
|
||||||
var entities = new List<Type>(); |
|
||||||
var systems = new List<MethodInfo>(); |
|
||||||
var observers = new List<MethodInfo>(); |
|
||||||
|
|
||||||
foreach (var member in Type.GetNestedTypes().Concat<MemberInfo>(Type.GetMethods())) { |
|
||||||
var info = member.GetRegisterableInfo(out var _); |
|
||||||
if (info == null) continue; |
|
||||||
switch (info.Kind) { |
|
||||||
case RegisterableKind.Entity: entities.Add((Type)member); break; |
|
||||||
case RegisterableKind.Tag: tags.Add((Type)member); break; |
|
||||||
case RegisterableKind.Component: components.Add((Type)member); break; |
|
||||||
case RegisterableKind.Relation: relations.Add((Type)member); break; |
|
||||||
case RegisterableKind.System: systems.Add((MethodInfo)member); break; |
|
||||||
case RegisterableKind.Observer: observers.Add((MethodInfo)member); break; |
|
||||||
default: throw new InvalidOperationException(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var elements = new IList[] { entities, tags, components, relations, systems, observers }; |
|
||||||
if (elements.Sum(l => l.Count) == 0) throw new Exception( |
|
||||||
"Module must define at least one ECS related type or method"); |
|
||||||
|
|
||||||
Relations = relations.AsReadOnly(); |
|
||||||
Components = components.AsReadOnly(); |
|
||||||
Tags = tags.AsReadOnly(); |
|
||||||
Entities = entities.AsReadOnly(); |
|
||||||
Systems = systems.AsReadOnly(); |
|
||||||
Observers = observers.AsReadOnly(); |
|
||||||
|
|
||||||
UnmetDependencies = DependsOn.ToHashSet(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,93 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Reflection; |
|
||||||
using gaemstone.Utility; |
|
||||||
using gaemstone.Utility.IL; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe partial class Universe |
|
||||||
{ |
|
||||||
public ObserverInfo RegisterObserver(Action<Iterator> callback, string expression, |
|
||||||
ObserverEvent @event, string? name = null) |
|
||||||
=> RegisterObserver(name ?? callback.Method.Name, expression, @event, new() { expr = expression }, callback); |
|
||||||
public ObserverInfo RegisterObserver(Action<Iterator> callback, ecs_filter_desc_t filter, |
|
||||||
ObserverEvent @event, string? name = null) |
|
||||||
=> RegisterObserver(name ?? callback.Method.Name, null, @event, filter, callback); |
|
||||||
|
|
||||||
public ObserverInfo RegisterObserver(string name, string? expression, |
|
||||||
ObserverEvent @event, ecs_filter_desc_t filter, Action<Iterator> callback) |
|
||||||
{ |
|
||||||
var observerDesc = default(ecs_observer_desc_t); |
|
||||||
observerDesc.filter = filter; |
|
||||||
observerDesc.events[0] = (ecs_entity_t)(ecs_id_t)(uint)@event; |
|
||||||
observerDesc.binding_ctx = (void*)UniverseSystems.CreateSystemCallbackContext(this, callback); |
|
||||||
observerDesc.callback.Data.Pointer = &UniverseSystems.SystemCallback; |
|
||||||
observerDesc.entity = Create(name); |
|
||||||
|
|
||||||
var entity = new Entity(this, ecs_observer_init(Handle, &observerDesc)); |
|
||||||
var observer = new ObserverInfo(this, entity, name, expression, @event, filter, callback); |
|
||||||
Observers._observers.Add(observer); |
|
||||||
return observer; |
|
||||||
} |
|
||||||
|
|
||||||
public ObserverInfo RegisterObserver(object? instance, MethodInfo method) |
|
||||||
{ |
|
||||||
var attr = method.Get<ObserverAttribute>() ?? throw new ArgumentException( |
|
||||||
"Observer must specify ObserverAttribute", nameof(method)); |
|
||||||
|
|
||||||
var param = method.GetParameters(); |
|
||||||
if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) { |
|
||||||
if (attr.Expression == null) throw new Exception( |
|
||||||
"Observer must specify expression in ObserverAttribute"); |
|
||||||
var action = (Action<Iterator>)Delegate.CreateDelegate(typeof(Action<Iterator>), instance, method); |
|
||||||
return RegisterObserver(method.Name, attr.Expression, attr.Event, |
|
||||||
new() { expr = attr.Expression }, action); |
|
||||||
} else { |
|
||||||
var gen = QueryActionGenerator.GetOrBuild(this, method); |
|
||||||
var filter = (attr.Expression == null) ? gen.Filter : new() { expr = attr.Expression }; |
|
||||||
return RegisterObserver(method.Name, attr.Expression, attr.Event, |
|
||||||
filter, iter => gen.RunWithTryCatch(instance, iter)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public class UniverseObservers |
|
||||||
: IReadOnlyCollection<ObserverInfo> |
|
||||||
{ |
|
||||||
internal readonly List<ObserverInfo> _observers = new(); |
|
||||||
|
|
||||||
// IReadOnlyCollection implementation |
|
||||||
public int Count => _observers.Count; |
|
||||||
public IEnumerator<ObserverInfo> GetEnumerator() => _observers.GetEnumerator(); |
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|
||||||
} |
|
||||||
|
|
||||||
public class ObserverInfo |
|
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public Entity Entity { get; } |
|
||||||
|
|
||||||
public string Name { get; } |
|
||||||
public string? Expression { get; } |
|
||||||
|
|
||||||
public ObserverEvent Event { get; } |
|
||||||
public ecs_filter_desc_t Filter { get; } |
|
||||||
public Action<Iterator> Callback { get; } |
|
||||||
|
|
||||||
internal ObserverInfo(Universe universe, Entity entity, string name, string? expression, |
|
||||||
ObserverEvent @event, ecs_filter_desc_t filter, Action<Iterator> callback) |
|
||||||
{ |
|
||||||
Universe = universe; |
|
||||||
Entity = entity; |
|
||||||
|
|
||||||
Name = name; |
|
||||||
Expression = expression; |
|
||||||
|
|
||||||
Event = @event; |
|
||||||
Filter = filter; |
|
||||||
Callback = callback; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,152 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Reflection; |
|
||||||
using System.Runtime.InteropServices; |
|
||||||
using System.Threading; |
|
||||||
using gaemstone.Utility; |
|
||||||
using gaemstone.Utility.IL; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe partial class Universe |
|
||||||
{ |
|
||||||
public SystemInfo RegisterSystem(Action<Iterator> callback, string expression, |
|
||||||
SystemPhase? phase = null, string? name = null) |
|
||||||
=> RegisterSystem(name ?? callback.Method.Name, expression, phase ?? SystemPhase.OnUpdate, new() { expr = expression }, callback); |
|
||||||
public SystemInfo RegisterSystem(Action<Iterator> callback, ecs_filter_desc_t filter, |
|
||||||
SystemPhase? phase = null, string? name = null) |
|
||||||
=> RegisterSystem(name ?? callback.Method.Name, null, phase ?? SystemPhase.OnUpdate, filter, callback); |
|
||||||
|
|
||||||
public SystemInfo RegisterSystem(string name, string? expression, |
|
||||||
SystemPhase phase, ecs_filter_desc_t filter, Action<Iterator> callback) |
|
||||||
{ |
|
||||||
var _phase = (ecs_entity_t)(ecs_id_t)(uint)phase; |
|
||||||
var entityDesc = default(ecs_entity_desc_t); |
|
||||||
entityDesc.name = name; |
|
||||||
entityDesc.add[0] = _phase.Data != 0 ? Identifier.Pair(EcsDependsOn, _phase) : default; |
|
||||||
entityDesc.add[1] = _phase; |
|
||||||
// TODO: Provide a nice way to create these entity descriptors. |
|
||||||
|
|
||||||
var systemDesc = default(ecs_system_desc_t); |
|
||||||
systemDesc.entity = Create(entityDesc); |
|
||||||
systemDesc.binding_ctx = (void*)UniverseSystems.CreateSystemCallbackContext(this, callback); |
|
||||||
systemDesc.callback.Data.Pointer = &UniverseSystems.SystemCallback; |
|
||||||
systemDesc.query.filter = filter; |
|
||||||
|
|
||||||
var entity = new Entity(this, ecs_system_init(Handle, &systemDesc)); |
|
||||||
var system = new SystemInfo(this, entity, name, expression, phase, filter, callback); |
|
||||||
Systems._systems.Add(system); |
|
||||||
return system; |
|
||||||
} |
|
||||||
|
|
||||||
public SystemInfo RegisterSystem(Delegate action) |
|
||||||
{ |
|
||||||
var name = action.Method.Name; |
|
||||||
var attr = action.Method.Get<SystemAttribute>(); |
|
||||||
var phase = attr?.Phase ?? SystemPhase.OnUpdate; |
|
||||||
|
|
||||||
if (action is Action<Iterator> iterAction) { |
|
||||||
if (attr?.Expression == null) throw new Exception( |
|
||||||
"System must specify expression in SystemAttribute"); |
|
||||||
return RegisterSystem(name, attr.Expression, phase, new() { expr = attr.Expression }, iterAction); |
|
||||||
} else { |
|
||||||
var method = action.GetType().GetMethod("Invoke")!; |
|
||||||
var gen = QueryActionGenerator.GetOrBuild(this, method); |
|
||||||
var filter = (attr?.Expression == null) ? gen.Filter : new() { expr = attr.Expression }; |
|
||||||
return RegisterSystem(name, attr?.Expression, phase, filter, |
|
||||||
iter => gen.RunWithTryCatch(action.Target, iter)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public SystemInfo RegisterSystem(object? instance, MethodInfo method) |
|
||||||
{ |
|
||||||
var attr = method.Get<SystemAttribute>(); |
|
||||||
var phase = attr?.Phase ?? SystemPhase.OnUpdate; |
|
||||||
|
|
||||||
var param = method.GetParameters(); |
|
||||||
if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) { |
|
||||||
if (attr?.Expression == null) throw new Exception( |
|
||||||
"System must specify expression in SystemAttribute"); |
|
||||||
var action = (Action<Iterator>)Delegate.CreateDelegate(typeof(Action<Iterator>), instance, method); |
|
||||||
return RegisterSystem(method.Name, attr.Expression, phase, new() { expr = attr.Expression }, action); |
|
||||||
} else { |
|
||||||
var gen = QueryActionGenerator.GetOrBuild(this, method); |
|
||||||
var filter = (attr?.Expression == null) ? gen.Filter : new() { expr = attr.Expression }; |
|
||||||
return RegisterSystem(method.Name, attr?.Expression, phase, filter, |
|
||||||
iter => gen.RunWithTryCatch(instance, iter)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public class UniverseSystems |
|
||||||
: IReadOnlyCollection<SystemInfo> |
|
||||||
{ |
|
||||||
public readonly struct SystemCallbackContext |
|
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public Action<Iterator> Callback { get; } |
|
||||||
|
|
||||||
public SystemCallbackContext(Universe universe, Action<Iterator> callback) |
|
||||||
{ Universe = universe; Callback = callback; } |
|
||||||
} |
|
||||||
|
|
||||||
private static SystemCallbackContext[] _systemCallbackContexts = new SystemCallbackContext[64]; |
|
||||||
private static int _systemCallbackContextsCount = 0; |
|
||||||
|
|
||||||
public static nint CreateSystemCallbackContext(Universe universe, Action<Iterator> callback) |
|
||||||
{ |
|
||||||
var data = new SystemCallbackContext(universe, callback); |
|
||||||
var count = Interlocked.Increment(ref _systemCallbackContextsCount); |
|
||||||
if (count > _systemCallbackContexts.Length) |
|
||||||
Array.Resize(ref _systemCallbackContexts, count * 2); |
|
||||||
_systemCallbackContexts[count - 1] = data; |
|
||||||
return count; |
|
||||||
} |
|
||||||
|
|
||||||
public static SystemCallbackContext GetSystemCallbackContext(nint context) |
|
||||||
=> _systemCallbackContexts[(int)context - 1]; |
|
||||||
|
|
||||||
[UnmanagedCallersOnly] |
|
||||||
internal static void SystemCallback(ecs_iter_t* iter) |
|
||||||
{ |
|
||||||
var data = GetSystemCallbackContext((nint)iter->binding_ctx); |
|
||||||
data.Callback(new Iterator(data.Universe, null, *iter)); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
internal readonly List<SystemInfo> _systems = new(); |
|
||||||
|
|
||||||
// IReadOnlyCollection implementation |
|
||||||
public int Count => _systems.Count; |
|
||||||
public IEnumerator<SystemInfo> GetEnumerator() => _systems.GetEnumerator(); |
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|
||||||
} |
|
||||||
|
|
||||||
public class SystemInfo |
|
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public Entity Entity { get; } |
|
||||||
|
|
||||||
public string Name { get; } |
|
||||||
public string? Expression { get; } |
|
||||||
|
|
||||||
public SystemPhase Phase { get; } |
|
||||||
public ecs_filter_desc_t Filter { get; } |
|
||||||
public Action<Iterator> Callback { get; } |
|
||||||
|
|
||||||
internal SystemInfo(Universe universe, Entity entity, string name, string? expression, |
|
||||||
SystemPhase phase, ecs_filter_desc_t filter, Action<Iterator> callback) |
|
||||||
{ |
|
||||||
Universe = universe; |
|
||||||
Entity = entity; |
|
||||||
|
|
||||||
Name = name; |
|
||||||
Expression = expression; |
|
||||||
|
|
||||||
Phase = phase; |
|
||||||
Filter = filter; |
|
||||||
Callback = callback; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,186 +1,26 @@ |
|||||||
using System; |
using System; |
||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using System.Reflection; |
|
||||||
using System.Runtime.InteropServices; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
using static flecs_hub.flecs; |
||||||
|
using static flecs_hub.flecs.Runtime; |
||||||
|
|
||||||
namespace gaemstone.ECS; |
namespace gaemstone.ECS; |
||||||
|
|
||||||
[Entity] |
|
||||||
public struct Game { } |
|
||||||
|
|
||||||
[Component] |
|
||||||
public unsafe partial class Universe |
public unsafe partial class Universe |
||||||
{ |
{ |
||||||
// Relationships |
|
||||||
public static ecs_entity_t EcsIsA { get; } = pinvoke_EcsIsA(); |
|
||||||
public static ecs_entity_t EcsDependsOn { get; } = pinvoke_EcsDependsOn(); |
|
||||||
public static ecs_entity_t EcsChildOf { get; } = pinvoke_EcsChildOf(); |
|
||||||
public static ecs_entity_t EcsSlotOf { get; } = pinvoke_EcsSlotOf(); |
|
||||||
|
|
||||||
// Entity Tags |
|
||||||
public static ecs_entity_t EcsPrefab { get; } = pinvoke_EcsPrefab(); |
|
||||||
|
|
||||||
|
|
||||||
private readonly Dictionary<Type, ecs_entity_t> _byType = new(); |
|
||||||
|
|
||||||
public ecs_world_t* Handle { get; } |
public ecs_world_t* Handle { get; } |
||||||
|
|
||||||
public UniverseModules Modules { get; } = new(); |
public Universe(params string[] args) |
||||||
public UniverseSystems Systems { get; } = new(); |
|
||||||
public UniverseObservers Observers { get; } = new(); |
|
||||||
|
|
||||||
public Universe(string[]? args = null) |
|
||||||
{ |
{ |
||||||
[UnmanagedCallersOnly] |
var argv = CStrings.CStringArray(args); |
||||||
static void Abort() => throw new FlecsAbortException(); |
|
||||||
|
|
||||||
ecs_os_set_api_defaults(); |
|
||||||
var api = ecs_os_get_api(); |
|
||||||
api.abort_ = new FnPtr_Void { Pointer = &Abort }; |
|
||||||
ecs_os_set_api(&api); |
|
||||||
|
|
||||||
if (args?.Length > 0) { |
|
||||||
var argv = Runtime.CStrings.CStringArray(args); |
|
||||||
Handle = ecs_init_w_args(args.Length, argv); |
Handle = ecs_init_w_args(args.Length, argv); |
||||||
Runtime.CStrings.FreeCStrings(argv, args.Length); |
CStrings.FreeCStrings(argv, args.Length); |
||||||
} else { |
|
||||||
Handle = ecs_init(); |
|
||||||
} |
|
||||||
|
|
||||||
RegisterAll(typeof(Universe).Assembly); |
RegisterBuiltInLookups(); |
||||||
|
this.RegisterEntity<Game>(); |
||||||
|
this.RegisterComponent<Module>(); |
||||||
} |
} |
||||||
|
|
||||||
public bool Progress(TimeSpan delta) |
public bool Progress(TimeSpan delta) |
||||||
{ |
=> ecs_progress(this, (float)delta.TotalSeconds); |
||||||
if (Modules._deferred.Count > 0) throw new Exception( |
|
||||||
"Modules with unmet dependencies: \n" + |
|
||||||
string.Join(" \n", Modules._deferred.Values.Select( |
|
||||||
m => m.Type + " is missing " + string.Join(", ", m.UnmetDependencies)))); |
|
||||||
return ecs_progress(this, (float)delta.TotalSeconds); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public Entity TryLookup<T>() |
|
||||||
=> TryLookup(typeof(T)); |
|
||||||
public Entity TryLookup(Type type) |
|
||||||
=> _byType.TryGetValue(type, out var e) ? new(this, e) : default; |
|
||||||
public Entity TryLookup(ecs_entity_t value) |
|
||||||
=> new(this, ecs_get_alive(this, value)); |
|
||||||
public Entity TryLookup(string path) |
|
||||||
=> new(this, ecs_lookup_path_w_sep(this, default, path, ".", default, true)); |
|
||||||
|
|
||||||
public Entity Lookup<T>() |
|
||||||
=> TryLookup<T>().ThrowIfNone(); |
|
||||||
public Entity Lookup(Type type) |
|
||||||
=> TryLookup(type).ThrowIfNone(); |
|
||||||
public Entity Lookup(ecs_entity_t value) |
|
||||||
=> TryLookup(value).ThrowIfNone(); |
|
||||||
public Entity Lookup(string path) |
|
||||||
=> TryLookup(path).ThrowIfNone(); |
|
||||||
|
|
||||||
|
|
||||||
public void RegisterAll(Assembly? from = null) |
|
||||||
{ |
|
||||||
from ??= Assembly.GetEntryAssembly()!; |
|
||||||
foreach (var type in from.GetTypes()) { |
|
||||||
var info = type.GetRegisterableInfo(out var isPartOfModule); |
|
||||||
if (info == null || isPartOfModule) continue; |
|
||||||
switch (info.Kind) { |
|
||||||
case RegisterableKind.Entity: RegisterEntity(type); break; |
|
||||||
case RegisterableKind.Tag: RegisterTag(type); break; |
|
||||||
case RegisterableKind.Component: RegisterComponent(type); break; |
|
||||||
case RegisterableKind.Relation: RegisterRelation(type); break; |
|
||||||
case RegisterableKind.Module: RegisterModule(type); break; |
|
||||||
default: throw new InvalidOperationException(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public Entity RegisterRelation<T>() |
|
||||||
=> RegisterRelation(typeof(T)); |
|
||||||
public Entity RegisterRelation(Type type) |
|
||||||
=> throw new NotImplementedException(); |
|
||||||
|
|
||||||
public Entity RegisterComponent<T>() |
|
||||||
=> RegisterComponent(typeof(T)); |
|
||||||
public Entity RegisterComponent(Type type) |
|
||||||
{ |
|
||||||
var typeInfo = default(ecs_type_info_t); |
|
||||||
if (type.IsValueType) { |
|
||||||
var wrapper = TypeWrapper.For(type); |
|
||||||
if (!wrapper.IsUnmanaged) throw new Exception( |
|
||||||
"Component struct must satisfy the unmanaged constraint. " + |
|
||||||
"(Must not contain any reference types or structs that contain references.)"); |
|
||||||
var structLayout = type.StructLayoutAttribute; |
|
||||||
if (structLayout == null || structLayout.Value == LayoutKind.Auto) throw new Exception( |
|
||||||
"Component struct must have a StructLayout attribute with LayoutKind sequential or explicit. " + |
|
||||||
"This is to ensure that the struct fields are not reorganized by the C# compiler."); |
|
||||||
typeInfo.size = wrapper.Size; |
|
||||||
typeInfo.alignment = structLayout.Pack; |
|
||||||
} else { |
|
||||||
typeInfo.size = sizeof(nint); |
|
||||||
typeInfo.alignment = sizeof(nint); |
|
||||||
} |
|
||||||
|
|
||||||
var name = type.GetFriendlyName(); |
|
||||||
var entityDesc = new ecs_entity_desc_t { name = name, symbol = name }; |
|
||||||
var componentDesc = new ecs_component_desc_t { entity = Create(entityDesc), type = typeInfo }; |
|
||||||
|
|
||||||
var id = ecs_component_init(Handle, &componentDesc); |
|
||||||
_byType[type] = id; |
|
||||||
// TODO: SetHooks(hooks, id); |
|
||||||
var entity = new Entity(this, id); |
|
||||||
|
|
||||||
if (type.Has<EntityAttribute>()) { |
|
||||||
if (type.IsValueType) entity.Add(entity); |
|
||||||
else entity.Set(type, Activator.CreateInstance(type)!); |
|
||||||
} |
|
||||||
|
|
||||||
return entity; |
|
||||||
} |
|
||||||
|
|
||||||
public Entity RegisterTag<T>() |
|
||||||
where T : unmanaged |
|
||||||
=> RegisterTag(typeof(T)); |
|
||||||
public Entity RegisterTag(Type type) |
|
||||||
{ |
|
||||||
if (!type.IsValueType || type.IsPrimitive || type.GetFields().Length > 0) |
|
||||||
throw new Exception("Tag must be an empty, used-defined struct."); |
|
||||||
var entity = Create(type.GetFriendlyName()); |
|
||||||
_byType.Add(type, entity); |
|
||||||
return entity; |
|
||||||
} |
|
||||||
|
|
||||||
public Entity RegisterEntity<T>() |
|
||||||
where T : unmanaged |
|
||||||
=> RegisterEntity(typeof(T)); |
|
||||||
public Entity RegisterEntity(Type type) |
|
||||||
{ |
|
||||||
if (!type.IsValueType || type.IsPrimitive || type.GetFields().Length > 0) |
|
||||||
throw new Exception("Entity must be an empty, used-defined struct."); |
|
||||||
var id = type.Get<EntityAttribute>()?.ID ?? 0; |
|
||||||
var entity = Create(new ecs_entity_desc_t { |
|
||||||
name = type.GetFriendlyName(), |
|
||||||
id = new() { Data = id }, |
|
||||||
}); |
|
||||||
_byType.Add(type, entity); |
|
||||||
return entity; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public Entity Create() |
|
||||||
=> Create(new ecs_entity_desc_t()); |
|
||||||
public Entity Create(string name) |
|
||||||
=> Create(new ecs_entity_desc_t { name = name }); |
|
||||||
public Entity Create(ecs_entity_desc_t desc) |
|
||||||
{ |
|
||||||
var entity = ecs_entity_init(Handle, &desc); |
|
||||||
return new Entity(this, entity).ThrowIfNone(); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public static implicit operator ecs_world_t*(Universe w) => w.Handle; |
public static implicit operator ecs_world_t*(Universe w) => w.Handle; |
||||||
} |
} |
||||||
|
@ -1,13 +0,0 @@ |
|||||||
using gaemstone.ECS; |
|
||||||
using Silk.NET.Maths; |
|
||||||
|
|
||||||
namespace gaemstone; |
|
||||||
|
|
||||||
[Component] |
|
||||||
public struct GlobalTransform |
|
||||||
{ |
|
||||||
public Matrix4X4<float> Value; |
|
||||||
public GlobalTransform(Matrix4X4<float> value) => Value = value; |
|
||||||
public static implicit operator GlobalTransform(in Matrix4X4<float> value) => new(value); |
|
||||||
public static implicit operator Matrix4X4<float>(in GlobalTransform index) => index.Value; |
|
||||||
} |
|
@ -1,20 +1,21 @@ |
|||||||
using System.Runtime.InteropServices; |
using System.Runtime.InteropServices; |
||||||
using static flecs_hub.flecs; |
using static flecs_hub.flecs; |
||||||
|
using static flecs_hub.flecs.Runtime; |
||||||
|
|
||||||
namespace gaemstone; |
namespace gaemstone; |
||||||
|
|
||||||
internal static class CStringExtensions |
internal static unsafe class CStringExtensions |
||||||
{ |
{ |
||||||
public static string ToStringAndFree(this Runtime.CString str) |
public static CString FlecsToCString(this string? str) |
||||||
{ |
=> (str != null) ? new(Marshal.StringToHGlobalAnsi(str)) : default; |
||||||
var result = Marshal.PtrToStringAnsi(str)!; |
|
||||||
Marshal.FreeHGlobal(str); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
public static void Set(this ref Runtime.CString str, string? value) |
|
||||||
{ |
public static void FlecsFree(this CString str) |
||||||
if (!str.IsNull) Marshal.FreeHGlobal(str); |
{ if (!str.IsNull) ecs_os_get_api().free_.Data.Pointer((void*)(nint)str); } |
||||||
str = (value != null) ? new(Marshal.StringToHGlobalAnsi(value)) : default; |
|
||||||
} |
public static string? FlecsToString(this CString str) |
||||||
|
=> !str.IsNull ? Marshal.PtrToStringAnsi((nint)str)! : null; |
||||||
|
|
||||||
|
public static string? FlecsToStringAndFree(this CString str) |
||||||
|
{ var result = str.FlecsToString(); str.FlecsFree(); return result; } |
||||||
} |
} |
||||||
|
@ -0,0 +1,43 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using gaemstone.ECS; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.Utility; |
||||||
|
|
||||||
|
public static class CallbackContextHelper |
||||||
|
{ |
||||||
|
public readonly struct CallbackContext |
||||||
|
{ |
||||||
|
public Universe Universe { get; } |
||||||
|
public Action<Iterator> Callback { get; } |
||||||
|
|
||||||
|
public CallbackContext(Universe universe, Action<Iterator> callback) |
||||||
|
{ Universe = universe; Callback = callback; } |
||||||
|
} |
||||||
|
|
||||||
|
private static readonly object _lock = new(); |
||||||
|
private static CallbackContext[] _contexts = new CallbackContext[64]; |
||||||
|
private static int _count = 0; |
||||||
|
|
||||||
|
public static nint Create(Universe universe, Action<Iterator> callback) |
||||||
|
{ |
||||||
|
var data = new CallbackContext(universe, callback); |
||||||
|
lock (_lock) { |
||||||
|
if (++_count >= _contexts.Length) |
||||||
|
Array.Resize(ref _contexts, _count * 2); |
||||||
|
_contexts[_count - 1] = data; |
||||||
|
return _count - 1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static CallbackContext Get(nint context) |
||||||
|
=> _contexts[(int)context]; |
||||||
|
|
||||||
|
[UnmanagedCallersOnly] |
||||||
|
internal static unsafe void Callback(ecs_iter_t* iter) |
||||||
|
{ |
||||||
|
var data = Get((nint)iter->binding_ctx); |
||||||
|
data.Callback(new Iterator(data.Universe, null, *iter)); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue