Additional refactors

wip/source-generators
copygirl 2 years ago
parent b4a769e3a6
commit c859bd37a1
  1. 48
      src/Immersion/Program.cs
  2. 2
      src/gaemstone.Bloxel/BlockFacing.cs
  3. 55
      src/gaemstone.Bloxel/ChunkPaletteStorage.cs
  4. 13
      src/gaemstone.Bloxel/Client/Systems/ChunkMeshGenerator.cs
  5. 2
      src/gaemstone.Bloxel/Neighbor.cs
  6. 2
      src/gaemstone.Bloxel/Systems/BasicWorldGenerator.cs
  7. 2
      src/gaemstone.Bloxel/Utility/ZOrder.cs
  8. 14
      src/gaemstone.Client/MeshManager.cs
  9. 14
      src/gaemstone.Client/Systems/Renderer.cs
  10. 4
      src/gaemstone.Client/TextureManager.cs
  11. 22
      src/gaemstone/ECS/BuiltIn+ObserverEvent.cs
  12. 22
      src/gaemstone/ECS/BuiltIn+SystemPhase.cs
  13. 30
      src/gaemstone/ECS/BuiltIn.cs
  14. 22
      src/gaemstone/ECS/Component.cs
  15. 90
      src/gaemstone/ECS/ComponentHooks.cs
  16. 43
      src/gaemstone/ECS/Entity+AddRemove.cs
  17. 86
      src/gaemstone/ECS/Entity+GetSet.cs
  18. 107
      src/gaemstone/ECS/Entity.cs
  19. 44
      src/gaemstone/ECS/EntityBase.cs
  20. 51
      src/gaemstone/ECS/EntityBuilder.cs
  21. 105
      src/gaemstone/ECS/EntityRef.cs
  22. 24
      src/gaemstone/ECS/EntityType.cs
  23. 4
      src/gaemstone/ECS/Filter.cs
  24. 41
      src/gaemstone/ECS/Flecs.cs
  25. 5
      src/gaemstone/ECS/Game.cs
  26. 46
      src/gaemstone/ECS/Identifier.cs
  27. 41
      src/gaemstone/ECS/IdentifierRef.cs
  28. 14
      src/gaemstone/ECS/Iterator.cs
  29. 130
      src/gaemstone/ECS/Module.cs
  30. 34
      src/gaemstone/ECS/Observer.cs
  31. 56
      src/gaemstone/ECS/Registerable.cs
  32. 14
      src/gaemstone/ECS/Relation.cs
  33. 25
      src/gaemstone/ECS/System.cs
  34. 23
      src/gaemstone/ECS/Tag.cs
  35. 38
      src/gaemstone/ECS/Term.cs
  36. 31
      src/gaemstone/ECS/TermAttributes.cs
  37. 98
      src/gaemstone/ECS/Universe+Lookup.cs
  38. 188
      src/gaemstone/ECS/Universe+Modules.cs
  39. 39
      src/gaemstone/ECS/Universe.cs
  40. 23
      src/gaemstone/Utility/CStringExtensions.cs
  41. 41
      src/gaemstone/Utility/CallbackContextHelper.cs
  42. 9
      src/gaemstone/Utility/IL/ILGeneratorWrapper.cs
  43. 14
      src/gaemstone/Utility/IL/IterActionGenerator.cs
  44. 19
      src/gaemstone/Utility/SpanToRef.cs
  45. 23
      src/gaemstone/Utility/TypeWrapper.cs

@ -20,7 +20,9 @@ FlecsAbortException.SetupHook();
Resources.ResourceAssembly = typeof(Program).Assembly;
var universe = new Universe();
var game = universe.Lookup<Game>();
var game = universe.LookupOrThrow<Game>();
universe.EnableRest();
// universe.EnableMonitor();
var window = Window.Create(WindowOptions.Default with {
Title = "gæmstone",
@ -30,26 +32,27 @@ var window = Window.Create(WindowOptions.Default with {
window.Initialize();
window.Center();
universe.RegisterModule<gaemstone.Client.Systems.Windowing>();
universe.Modules.Register<gaemstone.Client.Systems.Windowing>();
game.Set(new Canvas(window.CreateOpenGL()));
game.Set(new GameWindow(window));
universe.RegisterModule<gaemstone.Components.TransformComponents>();
universe.Modules.Register<gaemstone.Components.TransformComponents>();
universe.RegisterModule<gaemstone.Client.Components.RenderingComponents>();
universe.RegisterModule<gaemstone.Client.Systems.Renderer>();
universe.Modules.Register<gaemstone.Client.Components.RenderingComponents>();
universe.Modules.Register<gaemstone.Client.Systems.Renderer>();
TextureManager.Initialize(universe);
universe.RegisterModule<gaemstone.Client.Systems.Input>();
universe.Modules.Register<gaemstone.Client.Systems.Input>();
game.Set(new RawInput());
universe.RegisterModule<gaemstone.Client.Components.CameraComponents>();
universe.RegisterModule<gaemstone.Client.Systems.FreeCameraController>();
universe.Create("MainCamera")
universe.Modules.Register<gaemstone.Client.Components.CameraComponents>();
universe.Modules.Register<gaemstone.Client.Systems.FreeCameraController>();
universe.New("MainCamera")
.Set(Camera.Default3D)
.Set((GlobalTransform) Matrix4X4.CreateTranslation(0.0F, 2.0F, 0.0F))
.Set(new CameraController { MouseSensitivity = 12.0F });
.Set(new CameraController { MouseSensitivity = 12.0F })
.Build();
var heartMesh = MeshManager.Load(universe, "heart.glb");
var swordMesh = MeshManager.Load(universe, "sword.glb");
@ -59,20 +62,22 @@ for (var x = -12; x <= 12; x++)
for (var z = -12; z <= 12; z++) {
var position = Matrix4X4.CreateTranslation(x * 2, 0.0F, z * 2);
var rotation = Matrix4X4.CreateRotationY(rnd.NextFloat(MathF.PI * 2));
universe.Create()
var (type, mesh) = rnd.Pick(("Heart", heartMesh), ("Sword", swordMesh));
universe.New($"{type} {x}:{z}")
.Set((GlobalTransform)(rotation * position))
.Set(rnd.Pick(heartMesh, swordMesh));
.Set(mesh)
.Build();
}
universe.RegisterModule<gaemstone.Bloxel.Components.CoreComponents>();
universe.RegisterModule<gaemstone.Bloxel.Systems.BasicWorldGenerator>();
universe.RegisterModule<gaemstone.Bloxel.Client.Systems.ChunkMeshGenerator>();
universe.Modules.Register<gaemstone.Bloxel.Components.CoreComponents>();
universe.Modules.Register<gaemstone.Bloxel.Systems.BasicWorldGenerator>();
universe.Modules.Register<gaemstone.Bloxel.Client.Systems.ChunkMeshGenerator>();
var texture = TextureManager.Load(universe, "terrain.png");
var stone = universe.Create("Stone").Set(TextureCoords4.FromGrid(4, 4, 1, 0));
var dirt = universe.Create("Dirt" ).Set(TextureCoords4.FromGrid(4, 4, 2, 0));
var grass = universe.Create("Grass").Set(TextureCoords4.FromGrid(4, 4, 3, 0));
var stone = universe.New("Stone").Set(TextureCoords4.FromGrid(4, 4, 1, 0)).Build();
var dirt = universe.New("Dirt" ).Set(TextureCoords4.FromGrid(4, 4, 2, 0)).Build();
var grass = universe.New("Grass").Set(TextureCoords4.FromGrid(4, 4, 3, 0)).Build();
var sizeH = 4;
var sizeY = 2;
@ -81,14 +86,15 @@ for (var cy = -sizeY; cy < sizeY; cy++)
for (var cz = -sizeH; cz < sizeH; cz++) {
var pos = new ChunkPos(cx, cy - 2, cz);
var storage = new ChunkStoreBlocks();
universe.Create()
universe.New($"Chunk {cx}:{cy}:{cz}")
.Set((GlobalTransform)Matrix4X4.CreateTranslation(pos.GetOrigin()))
.Set(new Chunk(pos))
.Set(storage)
.Set(texture);
.Set(texture)
.Build();
}
// universe.RegisterModule<ObserverTest>();
// universe.Modules.Register<ObserverTest>();
var stopwatch = Stopwatch.StartNew();
var minFrameTime = TimeSpan.FromSeconds(1) / 30;

@ -18,7 +18,7 @@ public static class BlockFacings
{
public static readonly ImmutableHashSet<BlockFacing> Horizontals
= ImmutableHashSet.Create(BlockFacing.East , BlockFacing.West ,
BlockFacing.South, BlockFacing.North);
BlockFacing.South, BlockFacing.North);
public static readonly ImmutableHashSet<BlockFacing> Verticals
= ImmutableHashSet.Create(BlockFacing.Up, BlockFacing.Down);

@ -9,14 +9,14 @@ namespace gaemstone.Bloxel;
// https://www.reddit.com/r/VoxelGameDev/comments/9yu8qy/palettebased_compression_for_chunked_discrete/
public class ChunkPaletteStorage<T>
{
const int Size = 16 * 16 * 16;
static readonly EqualityComparer<T> COMPARER
private const int Size = Constants.ChunkLength * Constants.ChunkLength * Constants.ChunkLength;
private static readonly EqualityComparer<T> COMPARER
= EqualityComparer<T>.Default;
BitArray? _data;
PaletteEntry[]? _palette;
int _usedPalettes;
int _indicesLength;
private BitArray? _data;
private PaletteEntry[]? _palette;
private int _usedPalettes;
private int _indicesLength;
public T Default { get; }
@ -37,21 +37,18 @@ public class ChunkPaletteStorage<T>
=> Default = @default;
T Get(int x, int y, int z)
private T Get(int x, int y, int z)
{
if (_palette == null) return Default;
var entry = _palette[GetPaletteIndex(x, y, z)];
return !COMPARER.Equals(entry.Value, default!) ? entry.Value : Default;
}
void Set(int x, int y, int z, T value)
private void Set(int x, int y, int z, T value)
{
if (_palette == null)
{
if (_palette == null) {
if (COMPARER.Equals(value, Default)) return;
}
else
{
} else {
var index = GetIndex(x, y, z);
ref var current = ref _palette[GetPaletteIndex(index)];
if (COMPARER.Equals(value, current.Value)) return;
@ -60,15 +57,13 @@ public class ChunkPaletteStorage<T>
_usedPalettes--;
var replace = Array.FindIndex(_palette, entry => COMPARER.Equals(value, entry.Value));
if (replace != -1)
{
if (replace != -1) {
SetPaletteIndex(index, replace);
_palette[replace].RefCount += 1;
return;
}
if (current.RefCount == 0)
{
if (current.RefCount == 0) {
current.Value = value;
current.RefCount = 1;
_usedPalettes++;
@ -82,10 +77,9 @@ public class ChunkPaletteStorage<T>
_usedPalettes++;
}
int NewPaletteEntry()
private int NewPaletteEntry()
{
if (_palette != null)
{
if (_palette != null) {
int firstFree = Array.FindIndex(_palette, entry =>
entry.Value == null || entry.RefCount == 0);
if (firstFree != -1) return firstFree;
@ -95,10 +89,9 @@ public class ChunkPaletteStorage<T>
return NewPaletteEntry();
}
void GrowPalette()
private void GrowPalette()
{
if (_palette == null)
{
if (_palette == null) {
_data = new(Size);
_palette = new PaletteEntry[2];
_usedPalettes = 1;
@ -156,9 +149,9 @@ public class ChunkPaletteStorage<T>
// }
int GetPaletteIndex(int x, int y, int z)
private int GetPaletteIndex(int x, int y, int z)
=> GetPaletteIndex(GetIndex(x, y, z));
int GetPaletteIndex(int index)
private int GetPaletteIndex(int index)
{
var paletteIndex = 0;
for (var i = 0; i < _indicesLength; i++)
@ -166,19 +159,21 @@ public class ChunkPaletteStorage<T>
return paletteIndex;
}
void SetPaletteIndex(int x, int y, int z, int paletteIndex)
private void SetPaletteIndex(int x, int y, int z, int paletteIndex)
=> SetPaletteIndex(GetIndex(x, y, z), paletteIndex);
void SetPaletteIndex(int index, int paletteIndex)
private void SetPaletteIndex(int index, int paletteIndex)
{
for (var i = 0; i < _indicesLength; i++)
_data!.Set(index + i, (paletteIndex >> i & 0b1) == 0b1);
}
int GetIndex(int x, int y, int z)
=> (x | y << 4 | z << 8) * _indicesLength;
private int GetIndex(int x, int y, int z)
=> (x |
y << Constants.ChunkBitShift |
z << (Constants.ChunkBitShift * 2)) * _indicesLength;
struct PaletteEntry
private struct PaletteEntry
{
public T Value { get; set; }
public int RefCount { get; set; }

@ -60,9 +60,8 @@ public class ChunkMeshGenerator
for (var x = 0; x < 16; x++)
for (var y = 0; y < 16; y++)
for (var z = 0; z < 16; z++) {
var blockEntity = new Entity(centerBlocks[x, y, z]);
if (!blockEntity.IsValid) continue;
var block = new EntityRef(universe, blockEntity);
var block = universe.Lookup(centerBlocks[x, y, z]);
if (block == null) continue;
var blockVertex = new Vector3D<float>(x, y, z);
var textureCell = block.Get<TextureCoords4>();
@ -118,9 +117,9 @@ public class ChunkMeshGenerator
case BlockFacing.South : z += 1; if (z >= 16) cz += 1; break;
case BlockFacing.North : z -= 1; if (z < 0) cz -= 1; break;
}
var neighborChunk = blocks[cx, cy, cz];
if (neighborChunk == null) return true;
var neighborBlock = neighborChunk[x & 0b1111, y & 0b1111, z & 0b1111];
return !neighborBlock.IsValid;
var neighborChunk = blocks[cx, cy, cz];
if (neighborChunk == null) return true;
var neighborBlock = neighborChunk[x & 0b1111, y & 0b1111, z & 0b1111];
return neighborBlock.IsNone;
}
}

@ -51,7 +51,7 @@ public static class Neighbors
{
public static readonly ImmutableHashSet<Neighbor> Horizontals
= ImmutableHashSet.Create(Neighbor.East , Neighbor.West ,
Neighbor.South, Neighbor.North);
Neighbor.South, Neighbor.North);
public static readonly ImmutableHashSet<Neighbor> Verticals
= ImmutableHashSet.Create(Neighbor.Up, Neighbor.Down);

@ -27,7 +27,7 @@ public class BasicWorldGenerator
in Chunk chunk, ChunkStoreBlocks blocks,
[Not] HasBasicWorldGeneration _)
{
var stone = universe.Lookup("Stone");
var stone = universe.LookupOrThrow("Stone");
for (var lx = 0; lx < ChunkLength; lx++)
for (var ly = 0; ly < ChunkLength; ly++)
for (var lz = 0; lz < ChunkLength; lz++) {

@ -39,7 +39,7 @@ public readonly struct ZOrder
0b_00010010_01001001_00100100_10010010_01001001_00100100_10010010_01001001, // 0x1249249249249249
};
private static readonly long X_MASK = (long)MASKS[MASKS.Length - 1];
private static readonly long X_MASK = (long)MASKS[^1];
private static readonly long Y_MASK = X_MASK << 1;
private static readonly long Z_MASK = X_MASK << 2;
private static readonly long XY_MASK = X_MASK | Y_MASK;

@ -10,9 +10,9 @@ namespace gaemstone.Client;
public static class MeshManager
{
const uint PositionAttribIndex = 0;
const uint NormalAttribIndex = 1;
const uint UvAttribIndex = 2;
private const uint PositionAttribIndex = 0;
private const uint NormalAttribIndex = 1;
private const uint UvAttribIndex = 2;
public static Mesh Load(Universe universe, string name)
{
@ -25,12 +25,12 @@ public static class MeshManager
var vertices = primitive.VertexAccessors["POSITION"];
var normals = primitive.VertexAccessors["NORMAL"];
var GL = universe.Lookup<Game>().Get<Canvas>().GL;
var GL = universe.LookupOrThrow<Game>().Get<Canvas>().GL;
var vao = GL.GenVertexArray();
GL.BindVertexArray(vao);
GL.CreateBufferFromData(indices.SourceBufferView.Content,
BufferTargetARB.ElementArrayBuffer);
BufferTargetARB.ElementArrayBuffer);
GL.CreateBufferFromData(vertices.SourceBufferView.Content);
GL.EnableVertexAttribArray(PositionAttribIndex);
@ -52,7 +52,7 @@ public static class MeshManager
ReadOnlySpan<ushort> indices, ReadOnlySpan<Vector3D<float>> vertices,
ReadOnlySpan<Vector3D<float>> normals, ReadOnlySpan<Vector2D<float>> uvs)
{
var GL = universe.Lookup<Game>().Get<Canvas>().GL;
var GL = universe.LookupOrThrow<Game>().Get<Canvas>().GL;
var vao = GL.GenVertexArray();
GL.BindVertexArray(vao);
@ -80,7 +80,7 @@ public static class MeshManager
public static Mesh Create(Universe universe, ReadOnlySpan<Vector3D<float>> vertices,
ReadOnlySpan<Vector3D<float>> normals, ReadOnlySpan<Vector2D<float>> uvs)
{
var GL = universe.Lookup<Game>().Get<Canvas>().GL;
var GL = universe.LookupOrThrow<Game>().Get<Canvas>().GL;
var vao = GL.GenVertexArray();
GL.BindVertexArray(vao);

@ -16,14 +16,15 @@ namespace gaemstone.Client.Systems;
[Module]
[DependsOn(typeof(Windowing))]
public class Renderer
: IModuleInitializer
{
private readonly uint _program;
private readonly int _cameraMatrixUniform;
private readonly int _modelMatrixUniform;
private uint _program;
private int _cameraMatrixUniform;
private int _modelMatrixUniform;
public Renderer(Universe universe)
public void Initialize(EntityRef entity)
{
var GL = universe.Lookup<Game>().Get<Canvas>().GL;
var GL = entity.Universe.LookupOrThrow<Game>().Get<Canvas>().GL;
GL.Enable(EnableCap.DebugOutputSynchronous);
GL.DebugMessageCallback(DebugCallback, 0);
@ -83,8 +84,7 @@ public class Renderer
var cameraMatrix = cameraTransform * cameraProjection;
GL.UniformMatrix4(_cameraMatrixUniform, 1, false, in cameraMatrix.Row1.X);
Filter.RunOnce(universe, (in GlobalTransform transform, in Mesh mesh, Texture? texture) =>
{
Filter.RunOnce(universe, (in GlobalTransform transform, in Mesh mesh, Texture? texture) => {
// If entity has Texture, bind it now.
if (texture.HasValue) GL.BindTexture(texture.Value.Target, texture.Value.Handle);

@ -18,7 +18,7 @@ public static class TextureManager
public static void Initialize(Universe universe)
{
var GL = universe.Lookup<Game>().Get<Canvas>().GL;
var GL = universe.LookupOrThrow<Game>().Get<Canvas>().GL;
// Upload single-pixel white texture into texture slot 0, so when
// "no" texture is bound, we can still use the texture sampler.
GL.BindTexture(TextureTarget.Texture2D, 0);
@ -38,7 +38,7 @@ public static class TextureManager
public static Texture CreateFromStream(Universe universe, Stream stream, string? sourceFile = null)
{
var GL = universe.Lookup<Game>().Get<Canvas>().GL;
var GL = universe.LookupOrThrow<Game>().Get<Canvas>().GL;
var texture = new Texture(TextureTarget.Texture2D, GL.GenTexture());
GL.BindTexture(texture.Target, texture.Handle);

@ -0,0 +1,22 @@
using System.Reflection;
namespace gaemstone.ECS;
[Module(Name = "flecs.core")]
public static class ObserverEvent
{
[Entity] public struct OnAdd { }
[Entity] public struct OnRemove { }
[Entity] public struct OnSet { }
[Entity] public struct UnSet { }
// [Entity] public struct OnDelete { }
// [Entity] public struct OnCreateTable { }
// [Entity] public struct OnDeleteTable { }
[Entity] public struct OnTableEmpty { }
[Entity] public struct OnTableFilled { }
// [Entity] public struct OnCreateTrigger { }
// [Entity] public struct OnDeleteTrigger { }
// [Entity] public struct OnDeleteObservable { }
// [Entity] public struct OnComponentHooks { }
// [Entity] public struct OnDeleteTarget { }
}

@ -1,22 +1,22 @@
namespace gaemstone.ECS;
// TODO: We *can* use path lookup here, but we have to look in "flecs.pipeline" instead of "flecs.core".
[Module(Name = "flecs.pipeline")]
public static class SystemPhase
{
[BuiltIn(256 + 65)] public struct PreFrame { }
[Entity] 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 { }
[Entity] 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 { }
[Entity] public struct PostLoad { }
/// <summary>
/// Now that the input is loaded and processed, it's time to get ready to
@ -25,13 +25,13 @@ public static class SystemPhase
/// 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 { }
[Entity] 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 { }
[Entity] public struct OnUpdate { }
/// <summary>
/// This phase was introduced to deal with validating the state of the game
@ -40,7 +40,7 @@ public static class SystemPhase
/// 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 { }
[Entity] public struct OnValidate { }
/// <summary>
/// When your game logic has been updated, and your validation pass has ran,
@ -48,7 +48,7 @@ public static class SystemPhase
/// 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 { }
[Entity] public struct PostUpdate { }
/// <summary>
/// Now that all of the frame data is computed, validated and corrected for,
@ -57,13 +57,13 @@ public static class SystemPhase
/// A good example would be a system that calculates transform matrices from
/// a scene graph.
/// </summary>
[BuiltIn(256 + 72)] public struct PreStore { }
[Entity] 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 { }
[Entity] public struct OnStore { }
[BuiltIn(256 + 74)] public struct PostFrame { }
[Entity] public struct PostFrame { }
}

@ -0,0 +1,30 @@
namespace gaemstone.ECS;
[Module(Name = "flecs.core")]
public static class BuiltIn
{
// Entity Tags
[Tag] public struct Module { }
[Tag] public struct Prefab { }
[Tag] public struct SlotOf { }
[Tag] public struct Disabled { }
[Tag] public struct Empty { }
// Entity Relationships
[Tag, Relation] public struct IsA { }
[Tag, Relation] public struct ChildOf { }
[Tag, Relation] public struct DependsOn { }
// Component / Relationship Properties
[Tag] public struct Transitive { }
[Tag] public struct Reflexive { }
[Tag] public struct Symmetric { }
[Tag] public struct Final { }
[Tag] public struct DontInherit { }
[Tag] public struct Tag { }
[Tag] public struct Union { }
[Tag] public struct Exclusive { }
[Tag] public struct Acyclic { }
[Tag, Relation] public struct With { }
[Tag] public struct OneOf { }
}

@ -6,14 +6,16 @@ using static flecs_hub.flecs;
namespace gaemstone.ECS;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class ComponentAttribute : Attribute { }
public class ComponentAttribute : EntityAttribute { }
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)
public unsafe static EntityRef CreateComponent<T>(this EntityRef entity)
=> entity.CreateComponent(typeof(T));
public unsafe static EntityRef CreateComponent(this EntityRef entity, Type type)
{
// TODO: Do some additional sanity checking for this type.
var typeInfo = default(ecs_type_info_t);
if (type.IsValueType) {
var wrapper = TypeWrapper.For(type);
@ -31,14 +33,8 @@ public static class ComponentExtensions
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;
var desc = new ecs_component_desc_t { entity = entity, type = typeInfo };
ecs_component_init(entity.Universe, &desc);
return entity.CreateLookup(type);
}
}

@ -0,0 +1,90 @@
using System;
using System.Runtime.InteropServices;
using gaemstone.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
// FIXME: This API is flawed.
public unsafe class ComponentHooks
{
public EntityRef Entity { get; }
public ecs_type_hooks_t* Handle { get; }
private Action<Iterator>? _onAdd;
public event Action<Iterator>? OnAdd {
add { if ((_onAdd += value) != null) Handle->on_add.Data.Pointer = &CallbackOnAdd; }
remove { if ((_onAdd -= value) == null) Handle->on_add.Data.Pointer = null; }
}
private Action<Iterator>? _onSet;
public event Action<Iterator>? OnSet {
add { if ((_onSet += value) != null) Handle->on_set.Data.Pointer = &CallbackOnSet; }
remove { if ((_onSet -= value) == null) Handle->on_set.Data.Pointer = null; }
}
private Action<Iterator>? _onRemove;
public event Action<Iterator>? OnRemove {
add { if ((_onRemove += value) != null) Handle->on_remove.Data.Pointer = &CallbackOnRemove; }
remove { if ((_onRemove -= value) == null) Handle->on_remove.Data.Pointer = null; }
}
private Action<nint, int>? _constructor;
public event Action<nint, int>? Construct {
add { if ((_constructor += value) != null) Handle->ctor.Data.Pointer = &CallbackConstructor; }
remove { if ((_constructor -= value) == null) Handle->ctor.Data.Pointer = null; }
}
private Action<nint, int>? _destructor;
public event Action<nint, int>? Destruct {
add { if ((_destructor += value) != null) Handle->dtor.Data.Pointer = &CallbackDestructor; }
remove { if ((_destructor -= value) == null) Handle->dtor.Data.Pointer = null; }
}
private Action<nint, nint, int>? _copy;
public event Action<nint, nint, int>? Copy {
add { if ((_copy += value) != null) Handle->copy.Data.Pointer = &CallbackCopy; }
remove { if ((_copy -= value) == null) Handle->copy.Data.Pointer = null; }
}
private Action<nint, nint, int>? _move;
public event Action<nint, nint, int>? Move {
add { if ((_move += value) != null) Handle->move.Data.Pointer = &CallbackMove; }
remove { if ((_move -= value) == null) Handle->move.Data.Pointer = null; }
}
internal ComponentHooks(EntityRef entity, ecs_type_hooks_t* handle)
{ Entity = entity; Handle = handle; }
private static ComponentHooks Get(void* ptr)
=> CallbackContextHelper.Get<ComponentHooks>((nint)ptr);
[UnmanagedCallersOnly] private static void CallbackOnAdd(ecs_iter_t* it)
{ var hooks = Get(it->binding_ctx); hooks._onAdd?.Invoke(new(hooks.Entity.Universe, null, *it)); }
[UnmanagedCallersOnly] private static void CallbackOnSet(ecs_iter_t* it)
{ var hooks = Get(it->binding_ctx); hooks._onSet?.Invoke(new(hooks.Entity.Universe, null, *it)); }
[UnmanagedCallersOnly] private static void CallbackOnRemove(ecs_iter_t* it)
{ var hooks = Get(it->binding_ctx); hooks._onRemove?.Invoke(new(hooks.Entity.Universe, null, *it)); }
[UnmanagedCallersOnly] private static void CallbackConstructor(void* ptr, int count, ecs_type_info_t* type)
=> Get(type->hooks.binding_ctx)._constructor?.Invoke((nint)ptr, count);
[UnmanagedCallersOnly] private static void CallbackDestructor(void* ptr, int count, ecs_type_info_t* type)
=> Get(type->hooks.binding_ctx)._destructor?.Invoke((nint)ptr, count);
[UnmanagedCallersOnly] private static void CallbackCopy(void* dest, void* src, int count, ecs_type_info_t* type)
=> Get(type->hooks.binding_ctx)._copy?.Invoke((nint)dest, (nint)src, count);
[UnmanagedCallersOnly] private static void CallbackMove(void* dest, void* src, int count, ecs_type_info_t* type)
=> Get(type->hooks.binding_ctx)._move?.Invoke((nint)dest, (nint)src, count);
}
public unsafe static class ComponentHooksExtensions
{
public static ComponentHooks GetComponentHooks(this EntityRef entity)
{
var handle = ecs_get_hooks_id(entity.Universe, entity);
if (handle == null) throw new ArgumentException($"No type info found for {entity}");
if (handle->binding_ctx == null) {
var hooks = new ComponentHooks(entity, handle);
handle->binding_ctx = (void*)CallbackContextHelper.Create(hooks);
return hooks;
} else {
return CallbackContextHelper.Get<ComponentHooks>((nint)handle->binding_ctx);
}
}
}

@ -1,43 +0,0 @@
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);
}

@ -1,86 +0,0 @@
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;
// }
}

@ -1,32 +1,32 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using gaemstone.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class EntityAttribute : Attribute
{
public uint ID { get; set; }
public string? Name { get; set; }
}
{ public string? Name { get; set; } }
[AttributeUsage(AttributeTargets.Struct)]
public class TagAttribute : EntityAttribute { }
/// <summary> Unused, purely informational. </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class RelationAttribute : Attribute { }
public readonly struct Entity
: IEquatable<Entity>
{
public readonly ecs_entity_t Value;
// FIXME: IsValid is a function that should go on EntityRef, this should be IsNone instead.
public bool IsValid => Value.Data != 0;
public Entity ThrowIfInvalid() => IsValid ? this : throw new FlecsException(this + " is not valid");
public bool IsNone => Value.Data == 0;
public Entity ThrowIfNone() => !IsNone ? this
: throw new FlecsException(this + " is none");
public Entity(ecs_entity_t value) => Value = value;
public bool Equals(Entity other) => Value.Data == other.Value.Data;
public override bool Equals([NotNullWhen(true)] object? obj) => (obj is Entity other) && Equals(other);
public override bool Equals(object? obj) => (obj is Entity other) && Equals(other);
public override int GetHashCode() => Value.Data.GetHashCode();
public override string? ToString() => $"Entity(0x{Value.Data.Data:X})";
@ -39,86 +39,3 @@ public readonly struct Entity
public static implicit operator Identifier(Entity e) => new(e.Value.Data);
public static implicit operator ecs_id_t(Entity e) => e.Value.Data;
}
public unsafe readonly partial struct EntityRef
: IEquatable<EntityRef>
, IDisposable
{
public Universe Universe { get; }
public Entity Entity { get; }
public bool IsAlive => ecs_is_alive(Universe, this);
public string Name => ecs_get_name(Universe, this).FlecsToString()!;
public string FullPath => ecs_get_path_w_sep(Universe, default, this, ".", default).FlecsToStringAndFree()!;
public EntityType Type => new(Universe, ecs_get_type(Universe, this));
// TODO: public IEnumerable<Entity> Children => ...
public EntityRef(Universe universe, Entity entity)
{ Universe = universe; Entity = entity.ThrowIfInvalid(); }
void IDisposable.Dispose() => Delete();
public unsafe void Delete() => ecs_delete(Universe, this);
public EntityRef Disable() => Add<Flecs.Disabled>();
public EntityRef Enable() => Remove<Flecs.Disabled>();
public bool Equals(EntityRef other) => Universe == other.Universe && Entity == other.Entity;
public override bool Equals([NotNullWhen(true)] object? obj) => (obj is EntityRef other) && Equals(other);
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) => left.Equals(right);
public static bool operator !=(EntityRef left, EntityRef right) => !left.Equals(right);
public static IdentifierRef operator &(EntityRef first, Entity second) => IdentifierRef.Pair(first, second);
public static IdentifierRef operator &(Entity first, EntityRef second) => IdentifierRef.Pair(first, second);
public static implicit operator Entity(EntityRef e) => new(e.Entity);
public static implicit operator ecs_entity_t(EntityRef e) => e.Entity.Value;
public static implicit operator Identifier(EntityRef e) => new(e.Entity.Value.Data);
public static implicit operator ecs_id_t(EntityRef e) => e.Entity.Value.Data;
}
public unsafe readonly struct EntityType
: IReadOnlyList<IdentifierRef>
{
public Universe Universe { get; }
public ecs_type_t* Handle { get; }
public EntityType(Universe universe, ecs_type_t* handle)
{ Universe = universe; Handle = handle; }
public override string ToString()
=> ecs_type_str(Universe, Handle).FlecsToStringAndFree()!;
// IReadOnlyList implementation
public int Count => Handle->count;
public IdentifierRef this[int index] => new(Universe, new(Handle->array[index]));
public IEnumerator<IdentifierRef> GetEnumerator() { for (var i = 0; i < Count; i++) yield return this[i]; }
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public unsafe static class EntityExtensions
{
public static EntityRef Create(this Universe universe)
=> new EntityBuilder(universe).Build();
public static EntityRef Create(this Universe universe, string name)
=> new EntityBuilder(universe, name).Build();
public static EntityRef RegisterEntity<T>(this Universe universe)
where T : unmanaged => universe.RegisterEntity(typeof(T));
public static EntityRef RegisterEntity(this Universe universe, Type type)
{
if (!type.IsValueType || type.IsPrimitive || type.GetFields().Length > 0)
throw new Exception("Entity must be an empty, used-defined struct.");
var name = type.GetFriendlyName();
var entity = new EntityBuilder(universe, name) { Symbol = name } .Build();
// TODO: Automatically add fields as IDs of an entity?
universe.RegisterLookup(type, entity);
return entity;
}
}

@ -0,0 +1,44 @@
using static gaemstone.ECS.BuiltIn;
namespace gaemstone.ECS;
public abstract class EntityBase<TReturn>
{
public abstract Universe Universe { get; }
public abstract TReturn Add(Identifier id);
public abstract TReturn Remove(Identifier id);
public abstract bool Has(Identifier id);
public abstract T Get<T>();
public abstract ref T GetRef<T>() where T : unmanaged;
public abstract void Modified<T>();
public abstract TReturn Set<T>(in T value) where T : unmanaged;
public abstract TReturn Set<T>(T obj) where T : class;
public TReturn Add(string name) => Add(Universe.LookupOrThrow(name));
public TReturn Add<T>() => Add(Universe.LookupOrThrow(typeof(T)));
public TReturn Add(Entity relation, Entity target) => Add(relation & target);
public TReturn Add<TRelation>(Entity target) => Add(Universe.LookupOrThrow<TRelation>(), target);
public TReturn Add<TRelation, TTarget>() => Add(Universe.LookupOrThrow<TRelation>(), Universe.LookupOrThrow<TTarget>());
public TReturn Remove(string name) => Remove(Universe.LookupOrThrow(name));
public TReturn Remove<T>() => Remove(Universe.LookupOrThrow(typeof(T)));
public TReturn Remove(Entity relation, Entity target) => Remove(relation & target);
public TReturn Remove<TRelation>(Entity target) => Remove(Universe.LookupOrThrow<TRelation>(), target);
public TReturn Remove<TRelation, TTarget>() => Remove(Universe.LookupOrThrow<TRelation>(), Universe.LookupOrThrow<TTarget>());
public bool Has(string name) => Has(Universe.LookupOrThrow(name));
public bool Has<T>() => Has(Universe.LookupOrThrow(typeof(T)));
public bool Has(Entity relation, Entity target) => Has(relation & target);
public bool Has<TRelation>(Entity target) => Has(Universe.LookupOrThrow<TRelation>(), target);
public bool Has<TRelation, TTarget>() => Has(Universe.LookupOrThrow<TRelation>(), Universe.LookupOrThrow<TTarget>());
public TReturn ChildOf(Entity parent) => Add<ChildOf>(parent);
public TReturn ChildOf<TParent>() => Add<ChildOf, TParent>();
public TReturn Disable() => Add<Disabled>();
public TReturn Enable() => Remove<Disabled>();
public bool IsDisabled => Has<Disabled>();
}

@ -1,14 +1,13 @@
using System.Collections;
using System;
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>
: EntityBase<EntityBuilder>
{
public Universe Universe { get; }
public override Universe Universe { get; }
/// <summary> Set to modify existing entity (optional). </summary>
public Entity ID { get; set; }
@ -36,28 +35,31 @@ public class EntityBuilder
public bool UseLowID { get; set; }
/// <summary> IDs to add to the new or existing entity. </summary>
private readonly List<Identifier> _add = new();
private readonly HashSet<Identifier> _toAdd = 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;
/// <summary> Actions to run once the entity has been created. </summary>
private readonly List<Action<EntityRef>> _toSet = new();
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(Universe universe, string? name = null, string? symbol = null)
{ Universe = universe; Name = name; Symbol = symbol; }
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>());
public override EntityBuilder Add(Identifier id) { _toAdd.Add(id); return this; }
public override EntityBuilder Remove(Identifier id) => throw new NotSupportedException();
public override bool Has(Identifier id) => !ecs_id_is_wildcard(id) ? _toAdd.Contains(id) : throw new NotSupportedException();
// TODO: Add support for Set.
public override T Get<T>() => throw new NotSupportedException();
public override ref T GetRef<T>() => throw new NotSupportedException();
public override void Modified<T>() => throw new NotImplementedException();
public EntityBuilder ChildOf(Entity parent) => Add(Universe.Lookup<Flecs.ChildOf>(), parent);
public EntityBuilder ChildOf<TParent>() => ChildOf(Universe.Lookup<TParent>());
public override EntityBuilder Set<T>(in T value)
// "in" can't be used with lambdas, so we make a local copy.
{ var copy = value; _toSet.Add(e => e.Set(copy)); return this; }
public EntityBuilder Disabled() => Add(Universe.Lookup<Flecs.Disabled>());
public override EntityBuilder Set<T>(T obj)
{ _toSet.Add(e => e.Set(obj)); return this; }
public unsafe EntityRef Build()
{
@ -68,14 +70,11 @@ public class EntityBuilder
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));
var add = desc.add; var index = 0;
foreach (var id in _toAdd) add[index++] = id;
var entityID = ecs_entity_init(Universe, &desc);
var entity = new EntityRef(Universe, new(entityID));
foreach (var action in _toSet) action(entity);
return entity;
}
// IReadOnlyCollection implementation
public int Count => _add.Count;
public IEnumerator<Identifier> GetEnumerator() => _add.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

@ -0,0 +1,105 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
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 bool IsValid => ecs_is_valid(Universe, this);
public bool IsAlive => ecs_is_alive(Universe, this);
public EntityType Type => new(Universe, ecs_get_type(Universe, this));
public string FullPath => ecs_get_path_w_sep(Universe, default, this, ".", default).FlecsToStringAndFree()!;
public string? Name {
get => ecs_get_name(Universe, this).FlecsToString()!;
set => ecs_set_name(Universe, this, value.FlecsToCStringThenFree());
}
public string? Symbol {
get => ecs_get_symbol(Universe, this).FlecsToString()!;
set => ecs_set_symbol(Universe, this, value.FlecsToCStringThenFree());
}
// TODO: public IEnumerable<Entity> Children => ...
public EntityRef(Universe universe, Entity entity, bool throwOnInvalid = true)
{
Universe = universe;
Entity = entity;
if (throwOnInvalid && !IsValid) throw new InvalidOperationException(
$"The entity {entity} is not valid");
}
public void Delete() => ecs_delete(Universe, this);
public EntityBuilder NewChild(string? name = null, string? symbol = null)
=> Universe.New(name, symbol).ChildOf(this);
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 ref T GetRef<T>()
{
var comp = Universe.LookupOrThrow<T>();
var ptr = ecs_get_mut_id(Universe, this, comp);
if (ptr == null) throw new Exception($"Component {typeof(T)} not found on {this}");
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 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 IdentifierRef operator &(EntityRef relation, Entity target) => IdentifierRef.Pair(relation, target);
public static IdentifierRef operator &(Entity relation, EntityRef target) => IdentifierRef.Pair(relation, target);
public static implicit operator Entity(EntityRef e) => e.Entity;
public static implicit operator ecs_entity_t(EntityRef e) => e.Entity.Value;
public static implicit operator Identifier(EntityRef e) => new(e.Entity.Value.Data);
public static implicit operator ecs_id_t(EntityRef e) => e.Entity.Value.Data;
}

@ -0,0 +1,24 @@
using System.Collections;
using System.Collections.Generic;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe readonly struct EntityType
: IReadOnlyList<IdentifierRef>
{
public Universe Universe { get; }
public ecs_type_t* Handle { get; }
public EntityType(Universe universe, ecs_type_t* handle)
{ Universe = universe; Handle = handle; }
public override string ToString()
=> ecs_type_str(Universe, Handle).FlecsToStringAndFree()!;
// IReadOnlyList implementation
public int Count => Handle->count;
public IdentifierRef this[int index] => new(Universe, new(Handle->array[index]));
public IEnumerator<IdentifierRef> GetEnumerator() { for (var i = 0; i < Count; i++) yield return this[i]; }
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

@ -78,9 +78,9 @@ public class FilterDesc
instanced = Instanced,
};
var span = desc.terms;
if (Terms.Count > desc.terms.Length) {
if (Terms.Count > span.Length) {
var byteCount = sizeof(ecs_term_t) * Terms.Count;
var ptr = (ecs_term_t*)Marshal.AllocHGlobal(byteCount);
var ptr = (ecs_term_t*)Marshal.AllocHGlobal(byteCount); // FIXME: Free this.
desc.terms_buffer = ptr;
desc.terms_buffer_count = Terms.Count;
span = new(ptr, Terms.Count);

@ -1,41 +0,0 @@
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 { }
}

@ -12,4 +12,7 @@ public struct Game { }
/// <summary> Short for <c>[Source(typeof(Game))]</c>. </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class GameAttribute : SourceAttribute
{ public GameAttribute() : base(typeof(Game)) { } }
{
public GameAttribute()
: base(typeof(Game)) { }
}

@ -1,5 +1,4 @@
using System;
using System.Diagnostics.CodeAnalysis;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
@ -17,11 +16,13 @@ public readonly struct Identifier
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 static Identifier Pair(Entity relation, Entity target)
=> Combine(IdentifierFlags.Pair, new(
(relation.Value.Data << 32) |
(target.Value.Data & ECS_ENTITY_MASK)));
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 bool Equals(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})"
@ -33,43 +34,6 @@ public readonly struct Identifier
public static implicit operator ecs_id_t(Identifier i) => i.Value;
}
public unsafe readonly struct IdentifierRef
: IEquatable<IdentifierRef>
{
public Universe Universe { get; }
public Identifier ID { get; }
public IdentifierFlags Flags => ID.Flags;
public bool IsPair => ID.IsPair;
public bool IsWildcard => ID.IsWildcard;
public bool IsValid => ecs_id_is_valid(Universe, ID);
public IdentifierRef(Universe universe, Identifier id)
{ Universe = universe; ID = id; }
public static IdentifierRef Combine(IdentifierFlags flags, IdentifierRef id)
=> new(id.Universe, Identifier.Combine(flags, id));
public static IdentifierRef Pair(EntityRef first, Entity second)
=> new(first.Universe, Identifier.Pair(first, second));
public static IdentifierRef Pair(Entity first, EntityRef second)
=> new(second.Universe, Identifier.Pair(first, second));
public (EntityRef, EntityRef) AsPair()
=> (Universe.Lookup(new Entity(new() { Data = (ID.Value & ECS_COMPONENT_MASK) >> 32 })),
Universe.Lookup(new Entity(new() { Data = ID.Value & ECS_ENTITY_MASK })));
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);
public override int GetHashCode() => HashCode.Combine(Universe, ID);
public override string? ToString() => ecs_id_str(Universe, this).FlecsToStringAndFree()!;
public static bool operator ==(IdentifierRef left, IdentifierRef right) => left.Equals(right);
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;
}
[Flags]
public enum IdentifierFlags : ulong
{

@ -0,0 +1,41 @@
using System;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe class IdentifierRef
: IEquatable<IdentifierRef>
{
public Universe Universe { get; }
public Identifier ID { get; }
public IdentifierFlags Flags => ID.Flags;
public bool IsPair => ID.IsPair;
public bool IsWildcard => ID.IsWildcard;
public bool IsValid => ecs_id_is_valid(Universe, ID);
public IdentifierRef(Universe universe, Identifier id)
{ Universe = universe; ID = id; }
public static IdentifierRef Combine(IdentifierFlags flags, IdentifierRef id)
=> new(id.Universe, Identifier.Combine(flags, id));
public static IdentifierRef Pair(EntityRef relation, Entity target)
=> new(relation.Universe, Identifier.Pair(relation, target));
public static IdentifierRef Pair(Entity relation, EntityRef target)
=> new(target.Universe, Identifier.Pair(relation, target));
public (EntityRef Relation, EntityRef Target) AsPair()
=> (Universe.LookupOrThrow(new Entity(new() { Data = (ID.Value & ECS_COMPONENT_MASK) >> 32 })),
Universe.LookupOrThrow(new Entity(new() { Data = ID.Value & ECS_ENTITY_MASK })));
public bool Equals(IdentifierRef? other) => (other is not null) && Universe == other.Universe && ID == other.ID;
public override bool Equals(object? obj) => Equals(obj as IdentifierRef);
public override int GetHashCode() => HashCode.Combine(Universe, ID);
public override string? ToString() => ecs_id_str(Universe, this).FlecsToStringAndFree()!;
public static bool operator ==(IdentifierRef? left, IdentifierRef? right) => ReferenceEquals(left, right) || (left?.Equals(right) ?? false);
public static bool operator !=(IdentifierRef? left, IdentifierRef? right) => !(left == right);
public static implicit operator Identifier(IdentifierRef i) => i.ID;
public static implicit operator ecs_id_t(IdentifierRef i) => i.ID.Value;
}

@ -2,12 +2,12 @@ using System;
using System.Collections;
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 Iterator
public unsafe partial class Iterator
: IEnumerable<Iterator>
{
public Universe Universe { get; }
@ -68,17 +68,15 @@ public unsafe class Iterator
{
fixed (ecs_iter_t* ptr = &Value) {
var id = ecs_field_id(ptr, index);
var comp = Universe.Lookup<T>();
var comp = Universe.LookupOrThrow<T>();
return id == comp.Entity.Value.Data;
}
}
public readonly ref struct SpanToRef<T>
public override string ToString()
{
private readonly Span<nint> _span;
internal SpanToRef(Span<nint> span) => _span = span;
public int Length => _span.Length;
public T this[int index] => (T)((GCHandle)_span[index]).Target!;
fixed (ecs_iter_t* ptr = &Value)
return ecs_iter_str(ptr).FlecsToStringAndFree()!;
}
// IEnumerable implementation

@ -1,133 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using gaemstone.Utility;
namespace gaemstone.ECS;
[AttributeUsage(AttributeTargets.Class)]
public class ModuleAttribute : Attribute { }
public class ModuleAttribute : Attribute
{
public string? Name { get; set; }
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class DependsOnAttribute : Attribute
{
public Type Target { get; } // TODO: Should probably move to string.
public DependsOnAttribute(Type target) => Target = target;
}
public string Name { get; }
[Component]
public class Module
{
public Type Type { get; }
public object? Instance { get; internal set; }
public Module(Type type) => Type = type;
public DependsOnAttribute(string name)
=> Name = name;
public DependsOnAttribute(Type type)
: this(ModuleManager.GetModuleName(type)) { }
}
public static class ModuleExtensions
public interface IModuleInitializer
{
private static readonly Regex _removeCommon = new("(Module|Components)$");
// TODO: This wouldn't work with multiple universes. Find a different way to do it.
private static Query? _disabledModulesQuery;
private static Rule? _disabledModulesWithDisabledDepsRule;
public static EntityRef RegisterModule<T>(this Universe universe)
where T : class => universe.RegisterModule(typeof(T));
public static EntityRef RegisterModule(this Universe universe, Type type)
{
// TODO: Would be nice to make this more straight-forward, especially for in-module code.
var Module = universe.Lookup<Module>();
var Disabled = universe.Lookup<Flecs.Disabled>();
var DependsOn = universe.Lookup<Flecs.DependsOn>();
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");
var hasSimpleCtor = type.GetConstructor(Type.EmptyTypes) != null;
var hasUniverseCtor = type.GetConstructor(new[] { typeof(Universe) }) != null;
if (!hasSimpleCtor && !hasUniverseCtor) throw new Exception(
$"Module {type} must define public new() or new(Universe)");
static EntityRef LookupOrCreateModule(Universe universe, Type type)
{
var entity = universe.TryLookup(type);
if (entity.IsValid) return new(universe, entity);
// Using startat: 1, regex shouldn't match strings whose entire name matches.
if (type.Namespace == null) throw new NotSupportedException("Module must have a namespace");
var name = _removeCommon.Replace(type.GetFriendlyName(), "", 1, startat: 1);
var newEntity = new EntityBuilder(universe, $"{type.Namespace}.{name}")
// NOTE: Modules should not be accessed by symbol.
// (Could collide with component symbols.)
// { Symbol = symbol }
.Disabled().Build();
universe.RegisterLookup(type, newEntity);
return newEntity;
}
// Create an entity for the module that is being registered.
var entity = LookupOrCreateModule(universe, type);
var dependencies = type.GetMultiple<DependsOnAttribute>()
.Select(attr => LookupOrCreateModule(universe, attr.Target));
foreach (var dep in dependencies) entity.Add(DependsOn, dep);
entity.Set(new Module(type));
// Collect all currently disabled modules.
var disabledModules = new Dictionary<Entity, Module>();
_disabledModulesQuery ??= new Query(universe, new(
"DisabledModulesQuery",
Module,
Disabled
));
foreach (var iter in _disabledModulesQuery) {
var modules = iter.FieldRef<Module>(1);
for (var i = 0; i < iter.Count; i++)
disabledModules.Add(iter.Entity(i), modules[i]);
}
while (true) {
// Collect all modules that can now be enabled.
var modulesToEnable = new Dictionary<Entity, Module>(disabledModules);
_disabledModulesWithDisabledDepsRule ??= new Rule(universe, new(
"DisabledModulesWithDisabledDepsQuery",
Module,
Disabled,
new(DependsOn, "$Dependency"),
new(Disabled) { Source = "$Dependency" }
));
foreach (var iter in _disabledModulesWithDisabledDepsRule)
for (var i = 0; i < iter.Count; i++)
modulesToEnable.Remove(iter.Entity(i));
if (modulesToEnable.Count == 0) break;
foreach (var (e, c) in modulesToEnable) {
var module = new EntityRef(universe, e);
c.Instance = EnableModule(module, c.Type);
disabledModules.Remove(e);
}
}
return entity;
}
private static object EnableModule(EntityRef module, Type type)
{
var instance = (type.GetConstructor(Type.EmptyTypes) != null)
? Activator.CreateInstance(type)!
: Activator.CreateInstance(type, module.Universe)!;
foreach (var member in type.GetNestedTypes().Concat<MemberInfo>(type.GetMethods()))
member.GetRegisterableInfo(out var _)?
.Register(module.Universe, instance, member)
.Add<Flecs.ChildOf>(module);
module.Remove<Flecs.Disabled>();
return instance;
}
void Initialize(EntityRef entity);
}

@ -6,29 +6,11 @@ using static flecs_hub.flecs;
namespace gaemstone.ECS;
public static class ObserverEvent
{
[BuiltIn] public struct OnAdd { }
[BuiltIn] public struct OnRemove { }
[BuiltIn] public struct OnSet { }
[BuiltIn] public struct UnSet { }
// [BuiltIn] public struct OnDelete { }
// [BuiltIn] public struct OnCreateTable { }
// [BuiltIn] public struct OnDeleteTable { }
[BuiltIn] public struct OnTableEmpty { }
[BuiltIn] public struct OnTableFilled { }
// [BuiltIn] public struct OnCreateTrigger { }
// [BuiltIn] public struct OnDeleteTrigger { }
// [BuiltIn] public struct OnDeleteObservable { }
// [BuiltIn] public struct OnComponentHooks { }
// [BuiltIn] public struct OnDeleteTarget { }
}
[AttributeUsage(AttributeTargets.Method)]
public class ObserverAttribute : Attribute
{
public Type Event { get; }
public string? Expression { get; }
public string? Expression { get; set; }
public ObserverAttribute(Type @event) => Event = @event;
}
@ -41,9 +23,9 @@ public static class ObserverExtensions
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 } },
entity = universe.New(name).Build(),
binding_ctx = (void*)CallbackContextHelper.Create((universe, callback)),
callback = new() { Data = new() { Pointer = &SystemExtensions.Callback } },
};
desc.events[0] = @event;
var entity = ecs_observer_init(universe, &desc);
@ -61,8 +43,10 @@ public static class ObserverExtensions
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);
"Observer must specify ObserverAttribute.Expression");
if (method.IsStatic) instance = null;
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;
@ -70,7 +54,7 @@ public static class ObserverExtensions
iterAction = iter => gen.RunWithTryCatch(instance, iter);
}
var @event = universe.Lookup(attr.Event);
var @event = universe.LookupOrThrow(attr.Event);
return universe.RegisterObserver(method.Name, @event, filter, iterAction);
}
}

@ -1,56 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using gaemstone.Utility;
namespace gaemstone.ECS;
// TODO: Make this return an EntityBuilder instead.
public delegate EntityRef RegisterFunc(Universe universe, object? instance, MemberInfo member);
public class RegisterableInfo
{
public bool? PartOfModule { get; } // true/false = must (not) be part of module; null = doesn't matter
public RegisterFunc Register { get; }
public Type[] AllowedWith { get; }
public RegisterableInfo(bool? partOfModule, RegisterFunc register, params Type[] allowedWith)
{ PartOfModule = partOfModule; Register = register; AllowedWith = allowedWith; }
}
public static class RegisterableExtensions
{
// Ordered by priority. For example a type with [Component, Relation]
// will result in a Relation due to being first in the list.
private static readonly Dictionary<Type, RegisterableInfo> _knownAttributes = new() {
[typeof(RelationAttribute) ] = new(null, (u, i, m) => u.RegisterRelation ((Type)m), typeof(ComponentAttribute), typeof(TagAttribute)),
[typeof(ComponentAttribute)] = new(null, (u, i, m) => u.RegisterComponent((Type)m), typeof(EntityAttribute)),
[typeof(TagAttribute) ] = new(null, (u, i, m) => u.RegisterTag ((Type)m)),
[typeof(EntityAttribute) ] = new(null, (u, i, m) => u.RegisterEntity ((Type)m)),
[typeof(ModuleAttribute) ] = new(false, (u, i, m) => u.RegisterModule((Type)m)),
[typeof(SystemAttribute) ] = new(true , (u, i, m) => u.RegisterSystem (i, (MethodInfo)m)),
[typeof(ObserverAttribute)] = new(true , (u, i, m) => u.RegisterObserver(i, (MethodInfo)m)),
};
public static RegisterableInfo? GetRegisterableInfo(this MemberInfo member, out bool isPartOfModule)
{
isPartOfModule = member.DeclaringType?.Has<ModuleAttribute>() == true;
var matched = _knownAttributes.Where(e => member.GetCustomAttribute(e.Key) != null).ToList();
if (matched.Count == 0) return null;
var (type, info) = matched[0];
var disallowed = matched.Skip(1).Select(a => a.Key).Except(info.AllowedWith);
if (disallowed.Any()) throw new InvalidOperationException(
$"{member} marked with {type} may not be used together with " + string.Join(", ", disallowed));
if (info.PartOfModule == true && !isPartOfModule) throw new InvalidOperationException(
$"{member} marked with {type} must be part of a module");
if (info.PartOfModule == false && isPartOfModule) throw new InvalidOperationException(
$"{member} marked with {type} must not be part of a module");
return info;
}
}

@ -1,14 +0,0 @@
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.
}

@ -1,5 +1,6 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using gaemstone.Utility;
using gaemstone.Utility.IL;
using static flecs_hub.flecs;
@ -24,12 +25,12 @@ public static class SystemExtensions
query.Name = name;
var desc = new ecs_system_desc_t {
query = query.ToFlecs(),
entity = new EntityBuilder(universe, name)
.Add<Flecs.DependsOn>(phase)
entity = universe.New(name)
.Add<BuiltIn.DependsOn>(phase)
.Add(phase)
.Build(),
binding_ctx = (void*)CallbackContextHelper.Create(universe, callback),
callback = new() { Data = new() { Pointer = &CallbackContextHelper.Callback } },
binding_ctx = (void*)CallbackContextHelper.Create((universe, callback)),
callback = new() { Data = new() { Pointer = &Callback } },
};
var entity = ecs_system_init(universe, &desc);
return new(universe, new(entity));
@ -42,7 +43,7 @@ public static class SystemExtensions
if (action is Action<Iterator> iterAction) {
query.Expression = attr?.Expression ?? throw new ArgumentException(
"System must specify expression in SystemAttribute", nameof(action));
"System must specify SystemAttribute.Expression", nameof(action));
} else {
var method = action.GetType().GetMethod("Invoke")!;
var gen = IterActionGenerator.GetOrBuild(universe, method);
@ -51,7 +52,7 @@ public static class SystemExtensions
iterAction = iter => gen.RunWithTryCatch(action.Target, iter);
}
var phase = universe.Lookup(attr?.Phase ?? typeof(SystemPhase.OnUpdate));
var phase = universe.LookupOrThrow(attr?.Phase ?? typeof(SystemPhase.OnUpdate));
return universe.RegisterSystem(action.Method.Name, phase, query, iterAction);
}
@ -65,7 +66,7 @@ public static class SystemExtensions
var param = method.GetParameters();
if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) {
query.Expression = attr?.Expression ?? throw new ArgumentException(
"System must specify expression in SystemAttribute", nameof(method));
"System must specify SystemAttribute.Expression", nameof(method));
iterAction = (Action<Iterator>)Delegate.CreateDelegate(typeof(Action<Iterator>), instance, method);
} else {
var gen = IterActionGenerator.GetOrBuild(universe, method);
@ -74,7 +75,15 @@ public static class SystemExtensions
iterAction = iter => gen.RunWithTryCatch(instance, iter);
}
var phase = universe.Lookup(attr?.Phase ?? typeof(SystemPhase.OnUpdate));
var phase = universe.LookupOrThrow(attr?.Phase ?? typeof(SystemPhase.OnUpdate));
return universe.RegisterSystem(method.Name, phase, query, iterAction);
}
[UnmanagedCallersOnly]
internal static unsafe void Callback(ecs_iter_t* iter)
{
var (universe, callback) = CallbackContextHelper
.Get<(Universe, Action<Iterator>)>((nint)iter->binding_ctx);
callback(new Iterator(universe, null, *iter));
}
}

@ -1,23 +0,0 @@
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;
}
}

@ -3,34 +3,6 @@ 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; }
@ -43,12 +15,22 @@ public class Term
public Term() { }
public Term(Identifier id) => ID = id;
public Term(string name) => First = name;
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 static implicit operator Term(string name) => new(name);
public Term None { get { InOut = TermInOutKind.None; return this; } }
public Term In { get { InOut = TermInOutKind.In; return this; } }
public Term Out { get { InOut = TermInOutKind.Out; return this; } }
public Term Or { get { Oper = TermOperKind.Or; return this; } }
public Term Not { get { Oper = TermOperKind.Not; return this; } }
public Term Optional { get { Oper = TermOperKind.Optional; return this; } }
public ecs_term_t ToFlecs() => new() {
id = ID,

@ -0,0 +1,31 @@
using System;
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;
}
// Parameters types marked with [Tag] are equivalent to [Has].
[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 { }

@ -1,62 +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 void AddLookupByType(Type type, Entity entity)
=> _lookupByType.Add(type, entity);
public void RemoveLookupByType(Type type)
{ if (!_lookupByType.Remove(type)) throw new InvalidOperationException(
$"Lookup for {type} does not exist"); }
private EntityRef? GetOrNull(Entity entity)
{ var e = new EntityRef(this, entity, false); return e.IsValid ? e : null; }
public EntityRef? Lookup<T>()
=> Lookup(typeof(T));
public EntityRef? Lookup(Type type)
=> Lookup(_lookupByType.GetValueOrDefault(type));
public EntityRef? Lookup(Entity value)
=> GetOrNull(new(ecs_get_alive(this, value)));
public EntityRef? Lookup(string path)
=> Lookup(default, path);
public EntityRef? Lookup(Entity parent, string path)
=> GetOrNull(new(ecs_lookup_path_w_sep(
this, parent, path.FlecsToCString(), ".", default, true)));
public EntityRef? LookupSymbol(string symbol)
=> GetOrNull(new(ecs_lookup_symbol(
this, symbol.FlecsToCString(), false)));
public EntityRef LookupOrThrow<T>() => LookupOrThrow(typeof(T));
public EntityRef LookupOrThrow(Type type) => Lookup(type)
?? throw new EntityNotFoundException($"Entity of type {type} not found");
public EntityRef LookupOrThrow(Entity entity) => Lookup(entity)
?? throw new EntityNotFoundException($"Entity {entity} not alive");
public EntityRef LookupOrThrow(string path) => Lookup(default, path)
?? throw new EntityNotFoundException($"Entity '{path}' not found");
public EntityRef LookupOrThrow(Entity parent, string path) => Lookup(parent, path)
?? throw new EntityNotFoundException($"Child entity of {parent} '{path}' not found");
public EntityRef LookupSymbolOrThrow(string symbol) => LookupSymbol(symbol)
?? throw new EntityNotFoundException($"Entity with symbol '{symbol}' not found");
public class EntityNotFoundException : Exception
{ public EntityNotFoundException(string message) : base(message) { } }
}
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));
public static class LookupExtensions
{
public static EntityRef CreateLookup<T>(this EntityRef entity)
=> entity.CreateLookup(typeof(T));
public static EntityRef CreateLookup(this EntityRef entity, Type type)
{ entity.Universe.AddLookupByType(type, entity); return entity; }
}

@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using gaemstone.Utility;
using static gaemstone.ECS.BuiltIn;
namespace gaemstone.ECS;
public class ModuleManager
{
private readonly Dictionary<Entity, ModuleInfo> _modules = new();
public Universe Universe { get; }
public ModuleManager(Universe universe)
=> Universe = universe;
internal ModuleInfo? Lookup(Entity entity)
=> _modules.GetValueOrDefault(entity);
public EntityRef Register<T>()
where T : class => Register(typeof(T));
public EntityRef Register(Type type)
{
if (!type.IsClass || type.IsGenericType || type.IsGenericTypeDefinition) throw new Exception(
$"Module {type} must be a non-generic class");
if (type.Get<ModuleAttribute>() is not ModuleAttribute attr) throw new Exception(
$"Module {type} must be marked with ModuleAttribute");
var moduleName = type.Get<ModuleAttribute>()?.Name ?? type.FullName!;
// Check if module type is static.
if (type.IsAbstract && type.IsSealed) {
// Static modules represent existing modules, as such they don't
// create entities, only look up existing ones to add type lookups
// for use with the Lookup(Type) method.
if (attr.Name == null) throw new Exception(
$"Existing module {type} must have ModuleAttribute.Name set");
var entity = Universe.Lookup(attr.Name) ?? throw new Exception(
$"Existing module {type} with name '{attr.Name}' not found");
// This implementation is pretty naive. It simply gets all nested
// types which are tagged with [Entity] attribute or a subtype
// thereof and creates a lookup mapping. No sanity checking.
foreach (var nested in type.GetNestedTypes()) {
if (nested.Get<EntityAttribute>() is not EntityAttribute nestedAttr) continue;
var name = nestedAttr.Name ?? nested.Name;
if (name.Contains('.')) throw new Exception(
$"EntityAttribute.Name for {type} must not contain a dot (path separator)");
Universe.LookupOrThrow($"{moduleName}.{name}")
.CreateLookup(nested);
}
return entity;
} else {
var name = GetModuleName(type);
if (Universe.Lookup(name) != null) throw new Exception(
$"Can't register module {type}: '{type.FullName}' is already in use");
var module = new ModuleInfo(Universe, type, name);
_modules.Add(module.Entity, module);
TryEnableModule(module);
return module.Entity;
}
}
private void TryEnableModule(ModuleInfo module)
{
if (module.UnmetDependencies.Count > 0) return;
module.Enable();
// Find other modules that might be missing this module as a dependency.
foreach (var other in _modules.Values) {
if (!other.IsActive) continue;
if (!other.UnmetDependencies.Contains(module.Entity)) continue;
// Move the just enabled module from unmet to met depedencies.
other.UnmetDependencies.Remove(module.Entity);
other.MetDependencies.Add(module);
TryEnableModule(other);
}
}
public static string GetModuleName(Type type)
{
var attr = type.Get<ModuleAttribute>();
if (attr == null) throw new ArgumentException(
$"Module {type} must be marked with ModuleAttribute", nameof(type));
return attr.Name ?? type.FullName!;
}
}
internal class ModuleInfo
{
public Universe Universe { get; }
public Type ModuleType { get; }
public string ModuleName { get; }
public EntityRef Entity { get; }
public object? Instance { get; internal set; }
public bool IsActive => Instance != null;
public HashSet<ModuleInfo> MetDependencies { get; } = new();
public HashSet<Entity> UnmetDependencies { get; } = new();
public ModuleInfo(Universe universe, Type type, string name)
{
Universe = universe;
ModuleType = type;
ModuleName = name;
if (ModuleType.IsAbstract || ModuleType.IsSealed) throw new Exception(
$"Module {ModuleType} must not be abstract or sealed");
if (ModuleType.GetConstructor(Type.EmptyTypes) == null) throw new Exception(
$"Module {ModuleType} must define public parameterless constructor");
var entity = Universe.New(name).Add<Module>();
// Add module dependencies from [DependsOn] attributes.
foreach (var dependsAttr in ModuleType.GetMultiple<DependsOnAttribute>()) {
var dependency = Universe.Lookup(dependsAttr.Name) ??
Universe.New(dependsAttr.Name).Add<Module>().Disable().Build();
var depModule = Universe.Modules.Lookup(dependency);
if (depModule?.IsActive == true) MetDependencies.Add(depModule);
else { UnmetDependencies.Add(dependency); entity.Disable(); }
entity.Add<DependsOn>(dependency);
}
Entity = entity.Build().CreateLookup(type);
}
public void Enable()
{
Entity.Enable();
Instance = Activator.CreateInstance(ModuleType)!;
RegisterNestedTypes();
(Instance as IModuleInitializer)?.Initialize(Entity);
RegisterMethods(Instance);
}
private void RegisterNestedTypes()
{
foreach (var type in ModuleType.GetNestedTypes()) {
if (type.Get<EntityAttribute>() is not EntityAttribute attr) continue;
var name = attr.Name ?? type.Name;
var entity = Universe.New($"{ModuleName}.{name}", name);
switch (attr) {
case TagAttribute:
if (!type.IsValueType || type.GetFields().Length > 0) throw new Exception(
$"Tag {type} must be an empty, used-defined struct.");
entity.Add<Tag>().Build().CreateLookup(type);
break;
case ComponentAttribute:
entity.Build().CreateComponent(type);
break;
default:
if (!type.IsValueType || type.GetFields().Length > 0) throw new Exception(
$"Entity {type} must be an empty, used-defined struct.");
entity.Build().CreateLookup(type);
break;
}
}
}
private void RegisterMethods(object? instance)
{
foreach (var method in ModuleType.GetMethods()) {
if (method.Has<SystemAttribute>())
Universe.RegisterSystem(instance, method).ChildOf(Entity);
if (method.Has<ObserverAttribute>())
Universe.RegisterObserver(instance, method).ChildOf(Entity);
}
}
}

@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using static flecs_hub.flecs;
using static flecs_hub.flecs.Runtime;
@ -7,6 +8,9 @@ namespace gaemstone.ECS;
public unsafe partial class Universe
{
public ecs_world_t* Handle { get; }
public ModuleManager Modules { get; }
public bool IsDeferred => ecs_is_deferred(this);
public Universe(params string[] args)
{
@ -14,9 +18,38 @@ public unsafe partial class Universe
Handle = ecs_init_w_args(args.Length, argv);
CStrings.FreeCStrings(argv, args.Length);
RegisterBuiltInLookups();
this.RegisterEntity<Game>();
this.RegisterComponent<Module>();
Modules = new(this);
Modules.Register(typeof(BuiltIn));
Modules.Register(typeof(ObserverEvent));
Modules.Register(typeof(SystemPhase));
New("Game", "Game").Build().CreateLookup<Game>();
}
public EntityBuilder New(string? name = null, string? symbol = null)
=> new(this, name, symbol);
// TODO: Move to module.
public void EnableRest(ushort port = 27750)
{
[UnmanagedCallersOnly]
static void RestImport(ecs_world_t* world)
=> FlecsRestImport(world);
ecs_import(this, new() { Data = new() { Pointer = &RestImport } }, "FlecsRest");
var rest = LookupOrThrow("flecs.rest.Rest");
_lookupByType.Add(typeof(EcsRest), rest);
rest.Set(new EcsRest { port = port });
}
public void EnableMonitor()
{
[UnmanagedCallersOnly]
static void MonitorImport(ecs_world_t* world)
=> FlecsMonitorImport(world);
ecs_import(this, new() { Data = new() { Pointer = &MonitorImport } }, "FlecsMonitor");
}
public bool Progress(TimeSpan delta)

@ -1,3 +1,4 @@
using System;
using System.Runtime.InteropServices;
using static flecs_hub.flecs;
using static flecs_hub.flecs.Runtime;
@ -6,9 +7,13 @@ namespace gaemstone;
internal static unsafe class CStringExtensions
{
// FIXME: Most if not all strings passed to flecs probably need to be freed.
public static CString FlecsToCString(this string? str)
=> (str != null) ? new(Marshal.StringToHGlobalAnsi(str)) : default;
public static AutoFreeCString FlecsToCStringThenFree(this string? str)
=> new(str);
public static void FlecsFree(this CString str)
{ if (!str.IsNull) ecs_os_get_api().free_.Data.Pointer((void*)(nint)str); }
@ -18,4 +23,22 @@ internal static unsafe class CStringExtensions
public static string? FlecsToStringAndFree(this CString str)
{ var result = str.FlecsToString(); str.FlecsFree(); return result; }
public class AutoFreeCString
: IDisposable
{
private readonly CString _cString;
public AutoFreeCString(string? str)
=> _cString = str.FlecsToCString();
~AutoFreeCString() => Dispose();
public void Dispose()
{
if (!_cString.IsNull) Marshal.FreeHGlobal((nint)_cString);
GC.SuppressFinalize(this);
}
public static implicit operator CString(AutoFreeCString str) => str._cString;
}
}

@ -1,43 +1,14 @@
using System;
using System.Runtime.InteropServices;
using gaemstone.ECS;
using static flecs_hub.flecs;
using System.Collections.Generic;
namespace gaemstone.Utility;
public static class CallbackContextHelper
{
public readonly struct CallbackContext
{
public Universe Universe { get; }
public Action<Iterator> Callback { get; }
private static readonly List<object> _contexts = new();
public CallbackContext(Universe universe, Action<Iterator> callback)
{ Universe = universe; Callback = callback; }
}
public static nint Create<T>(T context) where T : notnull
{ _contexts.Add(context); return _contexts.Count; }
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));
}
public static T Get<T>(nint context)
=> (T)_contexts[(int)context - 1];
}

@ -98,8 +98,12 @@ public class ILGeneratorWrapper
private void AddInstr(OpCode code, object? arg = null) => _instructions.Add((_il.ILOffset, _indents.Count, code, arg));
public void Comment(string comment) => _instructions.Add((-1, _indents.Count, OpCodes.Nop, comment));
private static readonly MethodInfo _writeLine = typeof(Console).GetMethod(nameof(Console.WriteLine), new[] { typeof(string) })!;
public void Log(string str) { Load(str); Call(_writeLine); }
internal void Emit(OpCode code) { AddInstr(code, null); _il.Emit(code); }
internal void Emit(OpCode code, int arg) { AddInstr(code, arg); _il.Emit(code, arg); }
internal void Emit(OpCode code, string arg) { AddInstr(code, arg); _il.Emit(code, arg); }
internal void Emit(OpCode code, Type type) { AddInstr(code, type); _il.Emit(code, type); }
internal void Emit(OpCode code, Label label) { AddInstr(code, label); _il.Emit(code, label); }
internal void Emit(OpCode code, ILocal local) { AddInstr(code, local); _il.Emit(code, local.Builder); }
@ -107,8 +111,11 @@ public class ILGeneratorWrapper
internal void Emit(OpCode code, MethodInfo method) { AddInstr(code, method); _il.Emit(code, method); }
internal void Emit(OpCode code, ConstructorInfo constr) { AddInstr(code, constr); _il.Emit(code, constr); }
public void Dup() => Emit(OpCodes.Dup);
public void LoadNull() => Emit(OpCodes.Ldnull);
public void LoadConst(int value) => Emit(OpCodes.Ldc_I4, value);
public void Load(string value) => Emit(OpCodes.Ldstr, value);
public void Load(IArgument arg) => Emit(OpCodes.Ldarg, arg);
public void LoadAddr(IArgument arg) => Emit(OpCodes.Ldarga, arg);
@ -175,6 +182,8 @@ public class ILGeneratorWrapper
public void Cast(Type type) => Emit(OpCodes.Castclass, type);
public void Cast<T>() => Cast(typeof(T));
public void Box(Type type) => Emit(OpCodes.Box, type);
public void Box<T>() => Box(typeof(T));
public void Goto(Label label) => Emit(OpCodes.Br, label);
public void GotoIfTrue(Label label) => Emit(OpCodes.Brtrue, label);

@ -58,8 +58,8 @@ public unsafe class IterActionGenerator
Method = method;
Parameters = method.GetParameters().Select(ParamInfo.Build).ToArray();
if (!Parameters.Any(c => c.IsRequired && (c.Kind != ParamKind.Unique)))
throw new ArgumentException($"At least one parameter in {method} is required");
// if (!Parameters.Any(c => c.IsRequired && (c.Kind != ParamKind.Unique)))
// throw new ArgumentException($"At least one parameter in {method} is required");
var terms = new List<Term>();
var name = "<>Query_" + string.Join("_", Parameters.Select(p => p.UnderlyingType.Name));
@ -77,8 +77,8 @@ public unsafe class IterActionGenerator
if (p.Kind == ParamKind.Unique) continue;
// Add an entry to the terms to look for this type.
terms.Add(new(universe.Lookup(p.UnderlyingType)) {
Source = p.Source != null ? Universe.Lookup(p.Source) : null,
terms.Add(new(universe.LookupOrThrow(p.UnderlyingType)) {
Source = (p.Source != null) ? (TermID)Universe.LookupOrThrow(p.Source) : null,
InOut = p.Kind switch {
ParamKind.In => TermInOutKind.In,
ParamKind.Out => TermInOutKind.Out,
@ -93,7 +93,7 @@ public unsafe class IterActionGenerator
});
// Create a Span<T> local and initialize it to iterator.Field<T>(i).
var spanType = typeof(Span<>).MakeGenericType(p.FieldType);
var spanType = typeof(Span<>).MakeGenericType(p.FieldType);
fieldLocals[i] = IL.Local(spanType, $"field_{i}");
if (p.Kind is ParamKind.Has or ParamKind.Not) {
// If a "has" or "not" parameter is a struct, we require a temporary local that
@ -242,8 +242,8 @@ public unsafe class IterActionGenerator
// Reference types have a backing type of nint - they're pointers.
FieldType = underlyingType.IsValueType ? underlyingType : typeof(nint);
// If the underlying type has EntityAttribute, it's a singleton.
if (UnderlyingType.Has<EntityAttribute>()) Source = underlyingType;
// FIXME: Reimplement singletons somehow.
// if (UnderlyingType.Has<EntityAttribute>()) Source = underlyingType;
if (Info.Get<SourceAttribute>() is SourceAttribute attr) Source = attr.Type;
// TODO: Needs support for the new attributes.
}

@ -0,0 +1,19 @@
using System;
using System.Runtime.InteropServices;
namespace gaemstone.Utility;
public readonly ref struct SpanToRef<T>
where T : class
{
private readonly Span<nint> _span;
public int Length => _span.Length;
public T? this[int index] => (_span[index] != 0)
? (T)((GCHandle)_span[index]).Target! : null;
internal SpanToRef(Span<nint> span) => _span = span;
public void Clear() => _span.Clear();
public void CopyTo(SpanToRef<T> dest) => _span.CopyTo(dest._span);
}

@ -31,7 +31,7 @@ public interface IFieldWrapper
public static class TypeWrapper
{
static readonly Dictionary<Type, ITypeWrapper> _typeCache = new();
private static readonly Dictionary<Type, ITypeWrapper> _typeCache = new();
public static TypeWrapper<T> For<T>()
=> TypeWrapper<T>.Instance;
@ -49,14 +49,13 @@ public class TypeWrapper<TType> : ITypeWrapper
{
internal static TypeWrapper<TType> Instance { get; } = new();
readonly Dictionary<FieldInfo, IFieldWrapperForType> _fieldCache = new();
private readonly Dictionary<FieldInfo, IFieldWrapperForType> _fieldCache = new();
public Type Type => typeof(TType);
public int Size { get; } = Unsafe.SizeOf<TType>();
public bool IsUnmanaged { get; } = !RuntimeHelpers.IsReferenceOrContainsReferences<TType>();
TypeWrapper() { }
private TypeWrapper() { }
IFieldWrapper ITypeWrapper.GetFieldForAutoProperty(string propertyName) => GetFieldForAutoProperty(propertyName);
IFieldWrapper ITypeWrapper.GetFieldForAutoProperty(PropertyInfo property) => GetFieldForAutoProperty(property);
@ -125,7 +124,7 @@ public class TypeWrapper<TType> : ITypeWrapper
}
IFieldWrapperForType GetField(FieldInfo field, PropertyInfo? property)
private IFieldWrapperForType GetField(FieldInfo field, PropertyInfo? property)
{
if (_fieldCache.TryGetValue(field, out var cached)) return cached;
var type = typeof(FieldWrapper<>).MakeGenericType(typeof(TType), field.FieldType);
@ -136,7 +135,7 @@ public class TypeWrapper<TType> : ITypeWrapper
return wrapper;
}
FieldWrapper<TField> GetField<TField>(FieldInfo field, PropertyInfo? property)
private FieldWrapper<TField> GetField<TField>(FieldInfo field, PropertyInfo? property)
{
if (_fieldCache.TryGetValue(field, out var cached)) return (FieldWrapper<TField>)cached;
if (field.FieldType != typeof(TField)) throw new ArgumentException(
@ -166,10 +165,10 @@ public class TypeWrapper<TType> : ITypeWrapper
public delegate TField ValueGetterAction(in TType obj);
public delegate void ValueSetterAction(ref TType obj, TField value);
Func<TType, TField>? _classGetter;
Action<TType, TField>? _classSetter;
ValueGetterAction? _byRefGetter;
ValueSetterAction? _byRefSetter;
private Func<TType, TField>? _classGetter;
private Action<TType, TField>? _classSetter;
private ValueGetterAction? _byRefGetter;
private ValueSetterAction? _byRefSetter;
public ITypeWrapper DeclaringType { get; }
public FieldInfo FieldInfo { get; }
@ -189,7 +188,7 @@ public class TypeWrapper<TType> : ITypeWrapper
public ValueSetterAction ByRefSetter => _byRefSetter ??= BuildSetter<ValueSetterAction>(true);
TDelegate BuildGetter<TDelegate>(bool byRef)
private TDelegate BuildGetter<TDelegate>(bool byRef)
where TDelegate : Delegate
{
if (DeclaringType.Type.IsValueType && !byRef) throw new InvalidOperationException(
@ -207,7 +206,7 @@ public class TypeWrapper<TType> : ITypeWrapper
return method.CreateDelegate<TDelegate>();
}
TDelegate BuildSetter<TDelegate>(bool byRef)
private TDelegate BuildSetter<TDelegate>(bool byRef)
where TDelegate : Delegate
{
if (DeclaringType.Type.IsValueType && !byRef) throw new InvalidOperationException(

Loading…
Cancel
Save