Compare commits

..

2 Commits

  1. 3
      .gitmodules
  2. 17
      .vscode/launch.json
  3. 8
      .vscode/settings.json
  4. 14
      .vscode/tasks.json
  5. 59
      README.md
  6. 14548
      flecs.h-ast
  7. 34
      gaemstone.ECS.sln
  8. 1
      src/flecs
  9. 2
      src/flecs-cs
  10. 421
      src/gaemstone.ECS.BindGen/Program.cs
  11. 21
      src/gaemstone.ECS.BindGen/gaemstone.ECS.BindGen.csproj
  12. 87
      src/gaemstone.ECS/Component.cs
  13. 35
      src/gaemstone.ECS/Entity+Bare.cs
  14. 176
      src/gaemstone.ECS/Entity.cs
  15. 61
      src/gaemstone.ECS/EntityBase.cs
  16. 89
      src/gaemstone.ECS/EntityBuilder.cs
  17. 76
      src/gaemstone.ECS/EntityPath.cs
  18. 169
      src/gaemstone.ECS/EntityRef.cs
  19. 16
      src/gaemstone.ECS/EntityType.cs
  20. 31
      src/gaemstone.ECS/Exceptions.cs
  21. 27
      src/gaemstone.ECS/Filter.cs
  22. 36
      src/gaemstone.ECS/Id+Bare.cs
  23. 81
      src/gaemstone.ECS/Id.cs
  24. 61
      src/gaemstone.ECS/IdRef.cs
  25. 117
      src/gaemstone.ECS/Internal/EntityAccess.cs
  26. 11
      src/gaemstone.ECS/Internal/FlecsBuiltIn.cs
  27. 113
      src/gaemstone.ECS/Internal/Iterator.cs
  28. 9
      src/gaemstone.ECS/Internal/Lookup.cs
  29. 194
      src/gaemstone.ECS/Iterator.cs
  30. 26
      src/gaemstone.ECS/Observer.cs
  31. 31
      src/gaemstone.ECS/Query.cs
  32. 53
      src/gaemstone.ECS/Rule.cs
  33. 41
      src/gaemstone.ECS/System.cs
  34. 60
      src/gaemstone.ECS/Term.cs
  35. 15
      src/gaemstone.ECS/Utility/Allocators.cs
  36. 1
      src/gaemstone.ECS/Utility/CStringExtensions.cs
  37. 26
      src/gaemstone.ECS/Utility/FlecsException.cs
  38. 71
      src/gaemstone.ECS/Utility/ReferenceHandle.cs
  39. 2
      src/gaemstone.ECS/Utility/SpanExtensions.cs
  40. 75
      src/gaemstone.ECS/World+Bare.cs
  41. 69
      src/gaemstone.ECS/World+Lookup.cs
  42. 96
      src/gaemstone.ECS/World.cs
  43. 182
      src/gaemstone.ECS/flecs/flecs+Constants.g.cs
  44. 3773
      src/gaemstone.ECS/flecs/flecs+Functions.g.cs
  45. 1754
      src/gaemstone.ECS/flecs/flecs+Structs.g.cs
  46. 6
      src/gaemstone.ECS/gaemstone.ECS.csproj

3
.gitmodules vendored

@ -1,3 +1,6 @@
[submodule "src/flecs-cs"]
path = src/flecs-cs
url = https://github.com/flecs-hub/flecs-cs
[submodule "src/flecs"]
path = src/flecs
url = https://github.com/SanderMertens/flecs.git

@ -0,0 +1,17 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch BindGen",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"env": { "LIBCLANG_DISABLE_CRASH_RECOVERY": "true" },
"cwd": "${workspaceFolder}/src/gaemstone.ECS.BindGen",
"program": "${workspaceFolder}/src/gaemstone.ECS.BindGen/bin/Debug/net7.0/gaemstone.ECS.BindGen.dll",
"args": [],
"console": "internalConsole",
"stopAtEntry": false,
}
]
}

@ -0,0 +1,8 @@
{
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true,
"**/bin": true,
"**/obj": true,
}
}

14
.vscode/tasks.json vendored

@ -0,0 +1,14 @@
{
"version": "2.0.0",
"tasks": [
{
"group": "build",
"label": "build",
"type": "shell", "command": "dotnet",
"options": { "cwd": "src/gaemstone.ECS.BindGen" },
"args": [ "build", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ],
"presentation": { "reveal": "silent" },
"problemMatcher": "$msCompile"
}
]
}

@ -1,8 +1,10 @@
# gaemstone.ECS
.. is a medium-level managed wrapper library around the [flecs-cs] bindings for the amazing [Entity Component System (ECS)][ECS] framework [Flecs]. It is used as part of the [gæmstone] game engine, but may be used in other projects as well. To efficiently use this library, a thorough understanding of [Flecs] is required.
These classes have been split from the main [gæmstone] project. It is still a little unclear what functionality belongs where, and structural changes may occur. In its current state, I recommend to use this repository simply as a reference for building similar projects.
.. is a medium-level managed wrapper library around the [flecs-cs] bindings
for the amazing [Entity Component System (ECS)][ECS] framework [Flecs]. It is
used as part of the [gæmstone] game engine, but may be used in other projects
as well. To efficiently use this library, a thorough understanding of [Flecs]
is required.
[ECS]: https://en.wikipedia.org/wiki/Entity_component_system
[Flecs]: https://github.com/SanderMertens/flecs
@ -11,18 +13,20 @@ These classes have been split from the main [gæmstone] project. It is still a l
## Features
- Convenient wrapper types such as [Id], [Entity] and [EntityType].
These classes have only recently been split from the main **gæmstone** project. It is currently unclear what functionality belongs where, and structural changes are still very likely. In its current state, feel free to use **gæmstone.ECS** as a reference for building similar projects, or be aware that things are in flux, and in general nowhere near stable.
- Simple wrapper structs such as [Entity] and [Identifier].
- Classes with convenience functions like [EntityRef] and [EntityType].
- [EntityPath] uses a unix-like path, for example `/Game/Players/copygirl`.
- Fast type-to-entity lookup using generic context on [World]. (See below.)
- Define your own [Components] as both value or reference types.
- Query the ECS with [Iterators], [Filters], [Queries] and [Rules].
- Create [Systems] for game logic and [Observers] to act on changes.
[Id]: ./src/gaemstone.ECS/Id.cs
[Entity]: ./src/gaemstone.ECS/Entity.cs
[Identifier]: ./src/gaemstone.ECS/Identifier.cs
[EntityRef]: ./src/gaemstone.ECS/EntityRef.cs
[EntityType]: ./src/gaemstone.ECS/EntityType.cs
[EntityPath]: ./src/gaemstone.ECS/EntityPath.cs
[World]: ./src/gaemstone.ECS/World.cs
[Components]: ./src/gaemstone.ECS/Component.cs
[Iterators]: ./src/gaemstone.ECS/Iterator.cs
[Filters]: ./src/gaemstone.ECS/Filter.cs
@ -34,7 +38,7 @@ These classes have been split from the main [gæmstone] project. It is still a l
## Example
```cs
var world = new World<Program>();
var world = new World();
var position = world
.New("Position") // Create a new EntityBuilder, and set its name.
@ -51,31 +55,26 @@ entities.NewChild("Two").Set(new Position(10, 20)).Build();
// Changed my mind: Let's multiply each entity's position by 10.
foreach (var child in entities.GetChildren()) {
ref var pos = ref child.GetRefOrThrow<Position>();
pos = new(pos.X * 10, pos.Y * 10);
ref var pos = ref child.GetRefOrThrow<Position>();
pos = new(pos.X * 10, pos.Y * 10);
}
// The following systems run in the "OnUpdate"
// phase of the default pipeline provided by Flecs.
var dependsOn = world.LookupPathOrThrow("/flecs/core/DependsOn");
var onUpdate = world.LookupPathOrThrow("/flecs/pipeline/OnUpdate");
var onUpdate = world.LookupByPathOrThrow("/flecs/pipeline/OnUpdate");
// Create a system that will move all entities with
// the "Position" component downwards by 2 every frame.
world.New("FallSystem")
.Add(dependsOn, onUpdate)
.Build().InitSystem(new("Position"), iter => {
world.New("FallSystem").Build()
.InitSystem(onUpdate, new("Position"), iter => {
var posColumn = iter.Field<Position>(1);
for (var i = 0; i < iter.Count; i++) {
ref var pos = ref posColumn[i];
pos = new(pos.X, pos.Y - 2);
pos = new(pos.X, pos.Y + 2);
}
});
// Create a system that will print out entities' positions.
world.New("PrintPositionSystem").Build()
.Add(dependsOn, onUpdate)
.InitSystem(new("[in] Position"), iter => {
.InitSystem(onUpdate, new("[in] Position"), iter => {
var posColumn = iter.Field<Position>(1);
for (var i = 0; i < iter.Count; i++) {
var entity = iter.Entity(i);
@ -105,26 +104,8 @@ git clone --recurse-submodules https://git.mcft.net/copygirl/gaemstone.ECS.git
git submodule add https://git.mcft.net/copygirl/gaemstone.ECS.git
# To add a reference to this library to your .NET project:
dotnet add reference gaemstone.ECS/gaemstone.ECS.csproj
dotnet add reference gaemstone.ECS/src/gaemstone.ECS/gaemstone.ECS.csproj
# To generate flecs-cs' bindings:
./gaemstone.ECS/src/flecs-cs/library.sh
```
## On the `TContext` type parameter
Entities may be looked up simply by their type, once they're registered with `CreateLookup` or `InitComponent`. Under the hood, this is made possible using a nested static generic class that hold onto an `Entity` field. Theoretically this could be compiled into a simple field lookup and therefore be faster than dictionary lookups.
To support scenarios where multiple worlds may be used simultaneously, each with their own unique type lookups, we specify a generic type as that context.
In cases where only a single world is used, the amount of typing can be reduced by including a file similar to the following, defining global aliases:
```cs
global using Entity = gaemstone.ECS.Entity<Context>;
global using Id = gaemstone.ECS.Id<Context>;
global using Iterator = gaemstone.ECS.Iterator<Context>;
global using World = gaemstone.ECS.World<Context>;
// Add more aliases as you feel they are needed.
public struct Context { }
```

File diff suppressed because it is too large Load Diff

@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0FC51081-529F-4DC2-91D0-18002C10B733}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaemstone.ECS", "src\gaemstone.ECS\gaemstone.ECS.csproj", "{7CDAB372-16EB-452C-B984-0BD5F1D0D411}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaemstone.ECS.BindGen", "src\gaemstone.ECS.BindGen\gaemstone.ECS.BindGen.csproj", "{4DA4F739-1C38-41D3-804B-B1114090FF53}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7CDAB372-16EB-452C-B984-0BD5F1D0D411}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CDAB372-16EB-452C-B984-0BD5F1D0D411}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CDAB372-16EB-452C-B984-0BD5F1D0D411}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CDAB372-16EB-452C-B984-0BD5F1D0D411}.Release|Any CPU.Build.0 = Release|Any CPU
{4DA4F739-1C38-41D3-804B-B1114090FF53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4DA4F739-1C38-41D3-804B-B1114090FF53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4DA4F739-1C38-41D3-804B-B1114090FF53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4DA4F739-1C38-41D3-804B-B1114090FF53}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{7CDAB372-16EB-452C-B984-0BD5F1D0D411} = {0FC51081-529F-4DC2-91D0-18002C10B733}
{4DA4F739-1C38-41D3-804B-B1114090FF53} = {0FC51081-529F-4DC2-91D0-18002C10B733}
EndGlobalSection
EndGlobal

@ -0,0 +1 @@
Subproject commit ddf4dfc8d0b09eec42876ea06f5f42849a0c23be

@ -1 +1 @@
Subproject commit 1ae8ffade56a279d450dbf42545afbf873bdbafe
Subproject commit a2047983917aa462a8c2f34d5315aea48502f4d8

@ -0,0 +1,421 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using ClangSharp.Interop;
namespace gaemstone.ECS.BindGen;
public unsafe static class Program
{
// TODO: Handle being called from other working directories gracefully.
private const string FlecsRepositoryLocation = "../flecs";
private const string OutputFolderLocation = "../gaemstone.ECS/flecs";
private static readonly Dictionary<string, string> TypeAliases = new() {
{ "int8_t" , "sbyte" },
{ "int16_t", "short" },
{ "int32_t", "int" },
{ "int64_t", "long" },
{ "uint8_t" , "byte" },
{ "uint16_t", "ushort" },
{ "uint32_t", "uint" },
{ "uint64_t", "ulong" },
{ "ecs_flags8_t" , "byte" },
{ "ecs_flags16_t", "ushort" },
{ "ecs_flags32_t", "uint" },
{ "ecs_flags64_t", "ulong" },
{ "ecs_size_t", "int" },
};
public static void Main()
{
using var fileConstants = File.CreateText(Path.Combine(OutputFolderLocation, "flecs+Constants.g.cs"));
using var fileStructs = File.CreateText(Path.Combine(OutputFolderLocation, "flecs+Structs.g.cs"));
using var fileFunctions = File.CreateText(Path.Combine(OutputFolderLocation, "flecs+Functions.g.cs"));
static string StartProcessAndReturnOutput(string program, string arguments, string workDir = "")
{
var gitProcess = new Process { StartInfo = new() {
FileName = "git", Arguments = "describe --tags --always", WorkingDirectory = workDir,
RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true,
} };
gitProcess.Start();
var result = gitProcess.StandardOutput.ReadToEnd().Trim();
gitProcess.WaitForExit();
return result;
}
var bindgenVersion = StartProcessAndReturnOutput("git", "describe --tags --always");
var flecsVersion = StartProcessAndReturnOutput("git", "describe --tags --always", FlecsRepositoryLocation);
var autoGeneratedHeader = $$"""
// <auto-generated>
// Generated by gaemstone.ECS.BindGen {{ bindgenVersion }}
// Time: {{ DateTime.UtcNow :u}}
// Flecs version: {{ flecsVersion }}
// </auto-generated>
""";
fileConstants.WriteLine($$"""
{{ autoGeneratedHeader }}
#pragma warning disable CS8981
public static partial class flecs
{
""");
fileStructs.Write($$"""
{{ autoGeneratedHeader }}
using System.Runtime.InteropServices;
#pragma warning disable CS8981
public static unsafe partial class flecs
{
""");
fileFunctions.WriteLine($$"""
{{ autoGeneratedHeader }}
using System;
using System.Runtime.InteropServices;
#pragma warning disable CS8981
public static unsafe partial class flecs
{
private const string LibraryName = "flecs";
/// <summary> Indicates types are marked as "const" on C's end. </summary>
[AttributeUsage(AttributeTargets.Parameter, AttributeTargets.ReturnValue)]
public class ConstAttribute : Attribute { }
""");
using var unit = CXTranslationUnit.CreateFromSourceFile(
CXIndex.Create(), Path.Combine(FlecsRepositoryLocation, "flecs.h"),
default, default);
unit.Cursor.VisitChildren((cursor, _, _) => {
switch (cursor) {
case { Location.IsFromMainFile: false }: // Not from the file we're trying to parse.
case { Kind: CXCursorKind.CXCursor_MacroInstantiation
or CXCursorKind.CXCursor_InclusionDirective }: // Ignore these altogether.
break;
case { Kind: CXCursorKind.CXCursor_MacroDefinition }:
WriteMacro(fileConstants, 1, cursor);
break;
// case { Kind: CXCursorKind.CXCursor_TypedefDecl }:
// WriteTypedef(cursor, output);
// break;
case { Kind: CXCursorKind.CXCursor_EnumDecl }:
fileStructs.WriteLine();
WriteEnum(fileStructs, 1, cursor);
break;
case { Kind: CXCursorKind.CXCursor_StructDecl }:
// Skip forward declarations, unless they're not defined at all.
if (!cursor.IsDefinition && !cursor.Definition.IsNull) break;
fileStructs.WriteLine();
WriteStruct(fileStructs, 1, cursor);
break;
case { Kind: CXCursorKind.CXCursor_FunctionDecl }:
fileFunctions.WriteLine();
WriteFunction(fileFunctions, 1, cursor);
break;
default:
Console.WriteLine($"{cursor.Kind} {cursor}");
Console.WriteLine(GetSource(unit, cursor.Extent));
break;
}
return CXChildVisitResult.CXChildVisit_Continue;
}, default);
fileConstants.WriteLine("}");
fileStructs .WriteLine("}");
fileFunctions.WriteLine("}");
}
private static void WriteMacro(StreamWriter writer, int indent, CXCursor cursor)
{
if (cursor.IsMacroFunctionLike) return;
var unit = cursor.TranslationUnit;
var tokens = unit.Tokenize(cursor.Extent).ToArray();
if (tokens.Length < 2) return; // No value.
var type = "uint";
var name = cursor.Spelling.ToString();
if (name is "NULL" or "ECS_VECTOR_T_SIZE"
or "EcsLastInternalComponentId"
or "ECS_FUNC_NAME_BACK") return;
var value = GetSource(unit, Range(unit, tokens[1], tokens[^1]));
if (value is not [ '(', .., ')']) return;
if (value.Contains('\\')) value = value.Replace("\\\n", "").Replace(" ", "");
if (value.Contains("ull")) { value = value.Replace("ull", "ul"); type = "ulong"; }
if (name == "ECS_MAX_COMPONENT_ID") value = value.Replace("(uint32_t)", "(uint)");
WriteComment(writer, indent, cursor);
WriteIndent(writer, indent).WriteLine($"public const {type} {name} = {value};");
}
private static void WriteEnum(StreamWriter writer, int indent, CXCursor cursor)
{
WriteComment(writer, indent, cursor);
WriteIndent(writer, indent).WriteLine($"public {cursor.Type}");
WriteIndent(writer, indent).WriteLine("{");
cursor.VisitChildren((value, _, _) => {
WriteComment(writer, indent + 1, value);
WriteIndent(writer, indent + 1).WriteLine($"{value},");
return CXChildVisitResult.CXChildVisit_Continue;
}, default);
WriteIndent(writer, indent).WriteLine("}");
}
private static void WriteStruct(StreamWriter writer, int indent, CXCursor cursor,
string? name = null, bool noComment = false)
{
if (!noComment) WriteComment(writer, indent, cursor);
if (cursor.Kind == CXCursorKind.CXCursor_UnionDecl)
WriteIndent(writer, indent).WriteLine("[StructLayout(LayoutKind.Explicit)]");
name ??= cursor.Type.ToString();
if (name.StartsWith("struct ")) name = name[("struct ".Length)..];
WriteIndent(writer, indent).Write($"public struct {name}");
var hasFields = false; // For creating a shorthand struct definition that doesn't take up 3 lines.
cursor.VisitChildren((field, _, _) => {
// Nested struct and union declarations will be handled when field is written.
if (field.Kind is CXCursorKind.CXCursor_StructDecl
or CXCursorKind.CXCursor_UnionDecl)
return CXChildVisitResult.CXChildVisit_Continue;
if (field.Kind is not CXCursorKind.CXCursor_FieldDecl)
throw new NotSupportedException();
if (!hasFields) {
writer.WriteLine();
WriteIndent(writer, indent).WriteLine("{");
hasFields = true;
}
if (cursor.Kind == CXCursorKind.CXCursor_UnionDecl)
WriteIndent(writer, indent).WriteLine("[FieldOffset(0)]");
WriteComment(writer, indent + 1, field);
WriteIndent(writer, indent + 1);
var name = field.DisplayName.ToString();
if (IsReservedKeyword(name)) name = $"@{name}";
if (field.Type.Declaration.IsAnonymous) {
writer.WriteLine($"public _{field.DisplayName} {name};");
WriteStruct(writer, indent + 1, field.Type.Declaration,
name: $"_{field.DisplayName}", noComment: true);
} else if (field.Type.kind == CXTypeKind.CXType_ConstantArray) {
writer.WriteLine($"public fixed {field.Type.ArrayElementType} {name}[{field.Type.ArraySize}];");
} else {
var type = GetTypeString(field.Type, out var isConst);
if (isConst) writer.Write($"[Const] ");
writer.WriteLine($"public {type} {name};");
}
return CXChildVisitResult.CXChildVisit_Continue;
}, default);
if (!hasFields) writer.WriteLine(" { }");
else WriteIndent(writer, indent).WriteLine("}");
}
private static void WriteFunction(StreamWriter writer, int indent, CXCursor cursor)
{
WriteComment(writer, indent, cursor);
var returnType = GetTypeString(cursor.ReturnType, out var returnIsConst);
if (returnIsConst) WriteIndent(writer, indent).WriteLine("[return: Const]");
WriteIndent(writer, indent).WriteLine("[DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)]");
WriteIndent(writer, indent).Write($"public static extern {returnType} {cursor.Spelling}(");
for (var i = 0; i < cursor.NumArguments; i++) {
var arg = cursor.GetArgument((uint)i);
if (i > 0) writer.Write(", ");
var argType = GetTypeString(arg.Type, out var argIsConst);
if (argIsConst) writer.Write("[Const] ");
var name = arg.DisplayName.ToString();
if (IsReservedKeyword(name)) name = $"@{name}";
writer.Write($"{argType} {name}");
}
writer.WriteLine(");");
}
private static void WriteComment(StreamWriter writer, int indent, CXCursor cursor)
{
var comment = cursor.ParsedComment;
if (comment.Kind == CXCommentKind.CXComment_Null) return;
if (comment.Kind != CXCommentKind.CXComment_FullComment) throw new NotSupportedException();
var children = Children(comment).ToArray();
var paragraphs = children
.TakeWhile(c => c.Kind == CXCommentKind.CXComment_Paragraph)
.Select(GetCommentText).Where(p => p != null).ToArray();
var @params = children
.Where (c => c.Kind == CXCommentKind.CXComment_ParamCommand)
.Select(c => (c.ParamCommandComment_ParamName, GetCommentText(Children(c).Single()))).ToArray();
var @return = children
.Where (c => c.Kind == CXCommentKind.CXComment_BlockCommand)
.Where (c => c.BlockCommandComment_CommandName.ToString() == "return")
.Select(c => GetCommentText(Children(c).Single())).FirstOrDefault();
if (paragraphs.Length > 1) {
WriteIndent(writer, indent).WriteLine($"/// <summary>");
foreach (var paragraph in paragraphs)
WriteIndent(writer, indent).WriteLine($"/// <p> {paragraph} </p>");
WriteIndent(writer, indent).WriteLine($"/// </summary>");
} else if (paragraphs.Length == 1)
WriteIndent(writer, indent).WriteLine($"/// <summary> {paragraphs[0]} </summary>");
foreach (var (name, text) in @params)
WriteIndent(writer, indent).WriteLine($$"""/// <param name="{{ name }}"> {{ text }} </param>""");
if (@return != null)
WriteIndent(writer, indent).WriteLine($$"""/// <returns> {{ @return }} </returns>""");
foreach (var child in Children(comment))
switch (child.Kind) {
case CXCommentKind.CXComment_Paragraph:
case CXCommentKind.CXComment_ParamCommand:
case CXCommentKind.CXComment_BlockCommand when child.BlockCommandComment_CommandName.ToString() == "return":
break;
default:
WriteIndent(writer, indent);
writer.WriteLine($"//// {child.Kind}");
break;
}
static IEnumerable<CXComment> Children(CXComment parent)
=> Enumerable.Range(0, (int)parent.NumChildren).Select(i => parent.GetChild((uint)i));
static string? GetCommentText(CXComment paragraph)
{
if (paragraph.Kind != CXCommentKind.CXComment_Paragraph) throw new NotSupportedException();
var sb = new StringBuilder();
foreach (var part in Children(paragraph)) {
if (part.Kind != CXCommentKind.CXComment_Text) throw new NotSupportedException();
var str = part.TextComment_Text.ToString().Trim();
if (str == "") continue;
if (sb.Length > 0) sb.Append(' ');
sb.Append(str);
}
if (sb.Length == 0) return null;
sb.Replace("<", "&lt;").Replace(">", "&gt;");
if (sb[^1] != '.') sb.Append('.');
return sb.ToString();
}
}
private static StreamWriter WriteIndent(StreamWriter writer, int indent)
{
for (var i = 0; i < indent; i++)
writer.Write('\t');
return writer;
}
private static CXSourceRange Range(CXTranslationUnit unit, CXToken start, CXToken end)
=> CXSourceRange.Create(start.GetExtent(unit).Start, end.GetExtent(unit).End);
private static string GetTypeString(CXType type, out bool isConst)
{
isConst = false;
switch (type) {
case { kind: CXTypeKind.CXType_Pointer,
PointeeType: { kind: CXTypeKind.CXType_FunctionProto } funcType }:
var resultType = GetTypeString(funcType.ResultType, out _);
var argTypes = Enumerable.Range(0, funcType.NumArgTypes)
.Select(i => funcType.GetArgType((uint)i))
.Select(a => GetTypeString(a, out _))
.ToArray();
return $"delegate* unmanaged<{string.Join(", ", argTypes)}, {resultType}>";
case { kind: CXTypeKind.CXType_Pointer,
PointeeType: { kind: CXTypeKind.CXType_Elaborated,
Declaration.IsDefined: false} }:
return "void*";
case { kind: CXTypeKind.CXType_Pointer }:
return GetTypeString(type.PointeeType, out isConst) + "*";
case { kind: CXTypeKind.CXType_VariableArray
or CXTypeKind.CXType_IncompleteArray }:
return GetTypeString(type.ArrayElementType, out isConst) + "[]";
case { kind: CXTypeKind.CXType_Record }:
throw new NotSupportedException();
case { kind: CXTypeKind.CXType_Elaborated }:
var name = type.Declaration.ToString();
if (name == "") throw new Exception();
return name;
case { kind: CXTypeKind.CXType_FunctionProto }:
throw new NotSupportedException();
default:
name = type.ToString();
if (name == "") throw new Exception();
if (type.IsConstQualified) {
if (!name.StartsWith("const ")) throw new Exception();
name = name[("const ".Length)..];
isConst = true;
}
if (name.Contains(' ')) throw new Exception();
if (TypeAliases.TryGetValue(name, out var alias)) name = alias;
return name;
}
}
private static string GetSource(CXTranslationUnit unit, CXSourceRange range)
{
range.Start.GetFileLocation(out var startFile, out _, out _, out var start);
range.End .GetFileLocation(out var endFile , out _, out _, out var end);
if (startFile != endFile) return string.Empty;
return Encoding.UTF8.GetString(
unit.GetFileContents(startFile, out _)
.Slice((int)start, (int)(end - start)));
}
// See https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/
// Reserved keywords are unlikely to change in future C# versions, as they'd break
// existing programs. New ones are instead added as contextual keywords.
private static bool IsReservedKeyword(string str)
=> str is "abstract" or "as" or "base" or "bool" or "break" or "byte"
or "case" or "catch" or "char" or "checked" or "class"
or "const" or "continue" or "decimal" or "default"
or "delegate" or "do" or "double" or "else" or "enum"
or "event" or "explicit" or "extern" or "false" or "finally"
or "fixed" or "float" or "for" or "foreach" or "goto" or "if"
or "implicit" or "in" or "int" or "interface" or "internal"
or "is" or "lock" or "long" or "namespace" or "new" or "null"
or "object" or "operator" or "out" or "override" or "params"
or "private" or "protected" or "public" or "readonly" or "ref"
or "return" or "sbyte" or "sealed" or "short" or"sizeof"
or "stackalloc" or "static" or "string" or "struct" or "switch"
or "this" or "throw" or "true" or "try" or "typeof" or "uint"
or "ulong" or "unchecked" or "unsafe" or "ushort" or "using"
or "virtual" or "void" or "volatile" or "while";
}

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<LangVersion>preview</LangVersion>
<TargetFramework>net7.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<RuntimeIdentifier>ubuntu.22.04-x64</RuntimeIdentifier>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ClangSharp" Version="15.0.2" />
</ItemGroup>
</Project>

@ -1,13 +1,18 @@
using System;
using System.Runtime.CompilerServices;
using gaemstone.ECS.Utility;
using System.Runtime.InteropServices;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe partial struct Entity<TContext>
public static unsafe class ComponentExtensions
{
public Entity<TContext> InitComponent<T>()
public static EntityRef InitComponent(this EntityRef entity, Type type)
=> (EntityRef)typeof(ComponentExtensions)
.GetMethod(nameof(InitComponent), new[] { typeof(EntityRef) })!
.MakeGenericMethod(type).Invoke(null, new[]{ entity })!;
public static EntityRef InitComponent<T>(this EntityRef entity)
{
if (typeof(T).IsPrimitive) throw new ArgumentException(
"Must not be primitive");
@ -17,8 +22,8 @@ public unsafe partial struct Entity<TContext>
var size = typeof(T).IsValueType ? Unsafe.SizeOf<T>() : sizeof(ReferenceHandle);
var typeInfo = new ecs_type_info_t { size = size, alignment = size };
var componentDesc = new ecs_component_desc_t { entity = this, type = typeInfo };
ecs_component_init(World, &componentDesc);
var componentDesc = new ecs_component_desc_t { entity = entity, type = typeInfo };
ecs_component_init(entity.World, &componentDesc);
if (!typeof(T).IsValueType) {
// Set up component hooks for proper freeing of GCHandles.
@ -29,9 +34,77 @@ public unsafe partial struct Entity<TContext>
move = new() { Data = new() { Pointer = &ReferenceHandle.Move } },
copy = new() { Data = new() { Pointer = &ReferenceHandle.Copy } },
};
ecs_set_hooks_id(World, this, &typeHooks);
ecs_set_hooks_id(entity.World, entity, &typeHooks);
}
return CreateLookup<T>();
return entity.CreateLookup(typeof(T));
}
}
public unsafe readonly struct ReferenceHandle
: IDisposable
{
public static int NumActiveHandles { get; private set; }
private readonly nint _value;
public object? Target =>
(_value != default)
? ((GCHandle)_value).Target
: null;
private ReferenceHandle(nint value)
=> _value = value;
public static ReferenceHandle Alloc(object? target)
{
if (target == null) return default;
NumActiveHandles++;
return new((nint)GCHandle.Alloc(target));
}
public ReferenceHandle Clone()
=> Alloc(Target);
public void Dispose()
{
if (_value == default) return;
NumActiveHandles--;
((GCHandle)_value).Free();
}
[UnmanagedCallersOnly]
internal static void Construct(void* ptr, int count, ecs_type_info_t* _)
=> new Span<ReferenceHandle>(ptr, count).Clear();
[UnmanagedCallersOnly]
internal static void Destruct(void* ptr, int count, ecs_type_info_t* _)
{
var span = new Span<ReferenceHandle>(ptr, count);
foreach (var handle in span) handle.Dispose();
span.Clear();
}
[UnmanagedCallersOnly]
internal static void Move(void* dstPtr, void* srcPtr, int count, ecs_type_info_t* _)
{
var dst = new Span<ReferenceHandle>(dstPtr, count);
var src = new Span<ReferenceHandle>(srcPtr, count);
foreach (var handle in dst) handle.Dispose();
src.CopyTo(dst);
src.Clear();
}
[UnmanagedCallersOnly]
internal static void Copy(void* dstPtr, void* srcPtr, int count, ecs_type_info_t* _)
{
var dst = new Span<ReferenceHandle>(dstPtr, count);
var src = new Span<ReferenceHandle>(srcPtr, count);
for (var i = 0; i < count; i++) {
dst[i].Dispose();
dst[i] = src[i].Clone();
}
}
}

@ -1,35 +0,0 @@
using System;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public readonly partial struct Entity
: IEquatable<Entity>
{
public static readonly Entity None = default;
public readonly ecs_entity_t Value;
public uint NumericId => (uint)Value.Data;
public bool IsSome => Value.Data != 0;
public bool IsNone => Value.Data == 0;
public Entity(ecs_entity_t value) => Value = value;
public bool Equals(Entity other) => Value.Data == other.Value.Data;
public override bool Equals(object? obj) => (obj is Entity other) && Equals(other);
public override int GetHashCode() => Value.Data.GetHashCode();
public override string? ToString()
=> IsSome ? $"Entity({Value.Data.Data})"
: "Entity.None";
public static bool operator ==(Entity left, Entity right) => left.Equals(right);
public static bool operator !=(Entity left, Entity right) => !left.Equals(right);
public static implicit operator Id (Entity e) => new(e.Value.Data);
public static implicit operator ecs_entity_t(Entity e) => e.Value;
public static implicit operator ecs_id_t (Entity e) => e.Value.Data;
public static implicit operator Term (Entity entity) => new(entity);
public static implicit operator TermId(Entity entity) => new(entity);
}

@ -1,172 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using gaemstone.ECS.Internal;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe readonly partial struct Entity<TContext>
: IEquatable<Entity<TContext>>
public readonly struct Entity
: IEquatable<Entity>
{
public static readonly Entity<TContext> None = default;
public static readonly Entity None = default;
public readonly World<TContext> World;
public readonly Entity Value;
public readonly ecs_entity_t Value;
public uint Id => (uint)Value.Data;
public uint NumericId => Value.NumericId;
public bool IsNone => Value.IsNone;
public bool IsSome => Value.IsSome;
public bool IsSome => Value.Data != 0;
public bool IsNone => Value.Data == 0;
public bool IsValid => EntityAccess.IsValid(World, this);
public bool IsAlive => IsSome && EntityAccess.IsAlive(World, this);
public Entity(ecs_entity_t value) => Value = value;
public string? Name { get => EntityAccess.GetName(World, this); set => EntityAccess.SetName(World, this, value); }
public string? Symbol { get => EntityAccess.GetSymbol(World, this); set => EntityAccess.SetSymbol(World, this, value); }
public EntityPath Path => EntityPath.From(World, this);
public EntityType<TContext> Type => new(World, ecs_get_type(World, this));
public Entity<TContext>? Parent
=> GetOrNull(World, GetTargets(FlecsBuiltIn.ChildOf).FirstOrDefault());
public IEnumerable<Entity<TContext>> Children
=> World.Term(new(FlecsBuiltIn.ChildOf, this)).GetAllEntities();
private Entity(World<TContext> world, Entity value)
{ World = world; Value = value; }
public static Entity<TContext> GetOrInvalid(World<TContext> world, Entity value)
=> new(world, value);
public static Entity<TContext>? GetOrNull(World<TContext> world, Entity value)
=> new Entity<TContext>(world, value).ValidOrNull();
public static Entity<TContext> GetOrThrow(World<TContext> world, Entity value)
=> new Entity<TContext>(world, value).ValidOrThrow();
public Entity<TContext>? ValidOrNull() => IsValid ? this : null;
public Entity<TContext> ValidOrThrow() => IsValid ? this
: throw new InvalidOperationException($"The entity {this} is not valid");
public Entity<TContext>? AliveOrNull() => IsAlive ? this : null;
public Entity<TContext> AliveOrThrow() => IsAlive ? this
: throw new InvalidOperationException($"The entity {this} is not alive");
public void Delete()
=> ecs_delete(World, this);
public Entity<TContext> CreateLookup<T>()
{
ref var lookup = ref Lookup<TContext>.Entity<T>.Value;
if (lookup == this) { /* Don't throw if lookup already has the same entity set. */ }
else if (lookup.IsSome) throw new InvalidOperationException(
$"The lookup for type {typeof(T)} in context {typeof(TContext)} is already in use by {lookup}");
lookup = this;
return this;
}
public EntityBuilder<TContext> NewChild(EntityPath? path = null)
=> World.New(path?.ThrowIfAbsolute(), this);
public Entity<TContext>? LookupChildOrNull(EntityPath path)
=> World.LookupPathOrNull(path.ThrowIfAbsolute(), this);
public Entity<TContext> LookupChildOrThrow(EntityPath path)
=> World.LookupPathOrThrow(path.ThrowIfAbsolute()!, this);
public Entity<TContext> ChildOf(Entity parent)
=> Add(FlecsBuiltIn.ChildOf, parent);
public bool IsDisabled => Has(FlecsBuiltIn.Disabled);
public bool IsEnabled => !Has(FlecsBuiltIn.Disabled);
public Entity<TContext> Disable() => Add(FlecsBuiltIn.Disabled);
public Entity<TContext> Enable() => Remove(FlecsBuiltIn.Disabled);
public Entity<TContext> Add(Id id) { EntityAccess.Add(World, this, id); return this; }
public Entity<TContext> Add(string symbol) => Add(World.LookupSymbolOrThrow(symbol));
public Entity<TContext> Add(Entity relation, Entity target) => Add(Id.Pair(relation, target));
public Entity<TContext> Add<TEntity>() => Add(World.Entity<TEntity>());
public Entity<TContext> Add<TRelation>(Entity target) => Add(World.Entity<TRelation>(), target);
public Entity<TContext> Add<TRelation, TTarget>() => Add(World.Entity<TRelation>(), World.Entity<TTarget>());
public Entity<TContext> Remove(Id id) { EntityAccess.Remove(World, this, id); return this; }
public Entity<TContext> Remove(string symbol) => Remove(World.LookupSymbolOrThrow(symbol));
public Entity<TContext> Remove(Entity relation, Entity target) => Remove(Id.Pair(relation, target));
public Entity<TContext> Remove<TEntity>() => Remove(World.Entity<TEntity>());
public Entity<TContext> Remove<TRelation>(Entity target) => Remove(World.Entity<TRelation>(), target);
public Entity<TContext> Remove<TRelation, TTarget>() => Remove(World.Entity<TRelation>(), World.Entity<TTarget>());
public bool Has(Id id) => EntityAccess.Has(World, this, id);
public bool Has(string symbol) => Has(World.LookupSymbolOrThrow(symbol));
public bool Has(Entity relation, Entity target) => Has(Id.Pair(relation, target));
public bool Has<TEntity>() => Has(World.Entity<TEntity>());
public bool Has<TRelation>(Entity target) => Has(World.Entity<TRelation>(), target);
public bool Has<TRelation, TTarget>() => Has(World.Entity<TRelation>(), World.Entity<TTarget>());
public T? GetOrNull<T>(Id id) where T : unmanaged => EntityAccess.GetOrNull<T>(World, this, id);
public T? GetOrNull<T>(Id id, T _ = null!) where T : class => EntityAccess.GetOrNull<T>(World, this, id);
public T GetOrThrow<T>(Id id) => EntityAccess.GetOrThrow<T>(World, this, id);
public ref T GetMut<T>(Id id) where T : unmanaged => ref EntityAccess.GetMut<T>(World, this, id);
public ref T GetRefOrNull<T>(Id id) where T : unmanaged => ref EntityAccess.GetRefOrNull<T>(World, this, id);
public ref T GetRefOrThrow<T>(Id id) where T : unmanaged => ref EntityAccess.GetRefOrThrow<T>(World, this, id);
public Entity<TContext> Modified(Id id) { EntityAccess.Modified(World, this, id); return this; }
public Entity<TContext> Set<T>(Id id, in T value) where T : unmanaged { EntityAccess.Set(World, this, id, value); return this; }
public Entity<TContext> Set<T>(Id id, T value) where T : class { EntityAccess.Set(World, this, id, value); return this; }
public T? GetOrNull<T>() where T : unmanaged => GetOrNull<T>(World.Entity<T>());
public T? GetOrNull<T>(T _ = null!) where T : class => GetOrNull<T>(World.Entity<T>());
public T GetOrThrow<T>() => GetOrThrow<T>(World.Entity<T>());
public ref T GetMut<T>() where T : unmanaged => ref GetMut<T>(World.Entity<T>());
public ref T GetRefOrNull<T>() where T : unmanaged => ref GetRefOrNull<T>(World.Entity<T>());
public ref T GetRefOrThrow<T>() where T : unmanaged => ref GetRefOrThrow<T>(World.Entity<T>());
public Entity<TContext> Modified<T>() => Modified(World.Entity<T>());
public Entity<TContext> Set<T>(in T value) where T : unmanaged => Set(World.Entity<T>(), value);
public Entity<TContext> Set<T>(T value) where T : class => Set(World.Entity<T>(), value);
public IEnumerable<Entity<TContext>> GetTargets(Entity relation)
{
foreach (var entity in EntityAccess.GetTargets(World, this, relation))
yield return new(World, entity);
}
public IEnumerable<Entity<TContext>> GetTargets(string symbol)
=> GetTargets(World.LookupSymbolOrThrow(symbol));
public IEnumerable<Entity<TContext>> GetTargets<TRelation>()
=> GetTargets(World.Entity<TRelation>());
public bool Equals(Entity<TContext> other)
{
#if DEBUG
// In DEBUG mode, we additionally check if the worlds the two compared
// values are from the same world. This accounts for the world being a
// stage, hence why it might not be the cheapest operation.
if (World != other.World) throw new ArgumentException(
"The specified values are not from the same world");
#endif
return Value == other.Value;
}
public override bool Equals(object? obj)
=> (obj is Entity<TContext> other) && Equals(other);
public override int GetHashCode()
=> Value.GetHashCode();
public bool Equals(Entity other) => Value.Data == other.Value.Data;
public override bool Equals(object? obj) => (obj is Entity other) && Equals(other);
public override int GetHashCode() => Value.Data.GetHashCode();
public override string? ToString()
=> Value.ToString();
public static bool operator ==(Entity<TContext> left, Entity<TContext> right) => left.Equals(right);
public static bool operator !=(Entity<TContext> left, Entity<TContext> right) => !left.Equals(right);
public static implicit operator Entity (Entity<TContext> entity) => entity.Value;
public static implicit operator ecs_entity_t(Entity<TContext> entity) => entity.Value.Value;
=> IsSome ? $"Entity(0x{Value.Data.Data:X})"
: "Entity.None";
public static implicit operator Id<TContext>(Entity<TContext> entity) => Id<TContext>.GetUnsafe(entity.World, entity);
public static implicit operator Id (Entity<TContext> entity) => new(entity.Value.Value.Data);
public static implicit operator ecs_id_t (Entity<TContext> entity) => entity.Value.Value.Data;
public static bool operator ==(Entity left, Entity right) => left.Equals(right);
public static bool operator !=(Entity left, Entity right) => !left.Equals(right);
public static implicit operator Term (Entity<TContext> entity) => new(entity.Value);
public static implicit operator TermId(Entity<TContext> entity) => new(entity.Value);
public static implicit operator ecs_entity_t(Entity e) => e.Value;
public static implicit operator Id(Entity e) => new(e.Value.Data);
public static implicit operator ecs_id_t(Entity e) => e.Value.Data;
}

@ -0,0 +1,61 @@
namespace gaemstone.ECS;
public abstract class EntityBase<TReturn>
{
public abstract World World { get; }
public abstract TReturn Add(Id id);
public abstract TReturn Remove(Id id);
public abstract bool Has(Id id);
public TReturn Add(string symbol) => Add(World.LookupBySymbolOrThrow(symbol));
public TReturn Add<T>() => Add(World.LookupByTypeOrThrow(typeof(T)));
public TReturn Add(Entity relation, Entity target) => Add(Id.Pair(relation, target));
public TReturn Add<TRelation>(Entity target) => Add(World.LookupByTypeOrThrow<TRelation>(), target);
public TReturn Add<TRelation, TTarget>() => Add(World.LookupByTypeOrThrow<TRelation>(), World.LookupByTypeOrThrow<TTarget>());
public TReturn Remove(string symbol) => Remove(World.LookupBySymbolOrThrow(symbol));
public TReturn Remove<T>() => Remove(World.LookupByTypeOrThrow(typeof(T)));
public TReturn Remove(Entity relation, Entity target) => Remove(Id.Pair(relation, target));
public TReturn Remove<TRelation>(Entity target) => Remove(World.LookupByTypeOrThrow<TRelation>(), target);
public TReturn Remove<TRelation, TTarget>() => Remove(World.LookupByTypeOrThrow<TRelation>(), World.LookupByTypeOrThrow<TTarget>());
public bool Has(string symbol) => Has(World.LookupBySymbolOrThrow(symbol));
public bool Has<T>() => Has(World.LookupByTypeOrThrow(typeof(T)));
public bool Has(Entity relation, Entity target) => Has(Id.Pair(relation, target));
public bool Has<TRelation>(Entity target) => Has(World.LookupByTypeOrThrow<TRelation>(), target);
public bool Has<TRelation, TTarget>() => Has(World.LookupByTypeOrThrow<TRelation>(), World.LookupByTypeOrThrow<TTarget>());
public abstract T? GetOrNull<T>(Id id) where T : unmanaged;
public abstract T? GetOrNull<T>(Id id, T _ = null!) where T : class;
public abstract T GetOrThrow<T>(Id id);
public abstract ref T GetMut<T>(Id id) where T : unmanaged;
public abstract ref T GetRefOrNull<T>(Id id) where T : unmanaged;
public abstract ref T GetRefOrThrow<T>(Id id) where T : unmanaged;
public abstract void Modified<T>(Id id);
public T? GetOrNull<T>() where T : unmanaged => GetOrNull<T>(World.LookupByTypeOrThrow<T>());
public T? GetOrNull<T>(T _ = null!) where T : class => GetOrNull<T>(World.LookupByTypeOrThrow<T>());
public T GetOrThrow<T>() => GetOrThrow<T>(World.LookupByTypeOrThrow<T>());
public ref T GetMut<T>() where T : unmanaged => ref GetMut<T>(World.LookupByTypeOrThrow<T>());
public ref T GetRefOrNull<T>() where T : unmanaged => ref GetRefOrNull<T>(World.LookupByTypeOrThrow<T>());
public ref T GetRefOrThrow<T>() where T : unmanaged => ref GetRefOrThrow<T>(World.LookupByTypeOrThrow<T>());
public void Modified<T>() => Modified<T>(World.LookupByTypeOrThrow<T>());
public abstract TReturn Set<T>(Id id, in T value) where T : unmanaged;
public abstract TReturn Set<T>(Id id, T obj) where T : class;
public TReturn Set<T>(in T value) where T : unmanaged => Set(World.LookupByTypeOrThrow<T>(), value);
public TReturn Set<T>(T obj) where T : class => Set(World.LookupByTypeOrThrow<T>(), obj);
public TReturn ChildOf(Entity parent) => Add(World.ChildOf, parent);
public TReturn ChildOf<TParent>() => Add(World.ChildOf, World.LookupByTypeOrThrow<TParent>());
public TReturn Disable() => Add(World.Disabled);
public TReturn Enable() => Remove(World.Disabled);
public bool IsDisabled => Has(World.Disabled);
}

@ -1,18 +1,17 @@
using System;
using System.Collections.Generic;
using gaemstone.ECS.Internal;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
using static gaemstone.ECS.Internal.FlecsBuiltIn;
namespace gaemstone.ECS;
public class EntityBuilder<TContext>
public class EntityBuilder
: EntityBase<EntityBuilder>
{
public World<TContext> World { get; }
public override World World { get; }
/// <summary> Set to modify existing entity (optional). </summary>
public Entity<TContext> Id { get; set; }
public Entity Id { get; set; }
/// <summary>
/// Path of the entity. If no entity is provided, an entity with this path
@ -28,7 +27,7 @@ public class EntityBuilder<TContext>
/// function name, where these identifiers differ from the name they are
/// registered with in flecs.
/// </summary>
public EntityBuilder<TContext> Symbol(string symbol) { _symbol = symbol; return this; }
public EntityBuilder Symbol(string symbol) { _symbol = symbol; return this; }
private string? _symbol = null;
/// <summary>
@ -39,63 +38,51 @@ public class EntityBuilder<TContext>
/// <summary> Ids to add to the new or existing entity. </summary>
private readonly HashSet<Id> _toAdd = new();
// (ChildOf, *) is handled explicitly, it won't be added to _toAdd.
private Entity _parent = Entity.None;
/// <summary> String expression with components to add. </summary>
public string? Expression { get; }
/// <summary> Actions to run once the entity has been created. </summary>
private readonly List<Action<Entity>> _toSet = new();
private readonly List<Action<EntityRef>> _toSet = new();
public EntityBuilder(World<TContext> world, EntityPath? path = null)
public EntityBuilder(World world, EntityPath? path = null)
{ World = world; Path = path; }
public EntityBuilder(World<TContext> world, Entity parent, EntityPath? path = null)
: this(world, path)
{
// If given path is absolute, the new entity won't be created as a
// child of the specified parent. Alternatively, EntityRef.NewChild
// can be used, which will throw when an absolute path is given.
if ((path?.IsRelative != false) && parent.IsSome) Add(ChildOf, parent);
}
public EntityBuilder<TContext> Add(Id id)
public override EntityBuilder Add(Id id)
{
// If adding a ChildOf relation, store the parent separately.
if (id.RelationUnsafe == ChildOf)
{ _parent = id.TargetUnsafe; return this; }
if (id.AsPair(World) is (EntityRef relation, EntityRef target) &&
(relation == World.ChildOf)) { _parent = target; return this; }
if (_toAdd.Count == 31) throw new NotSupportedException(
"Must not add more than 31 Ids at once with EntityBuilder");
_toAdd.Add(id);
return this;
}
public EntityBuilder<TContext> Add(string symbol)
=> Add(World.LookupSymbolOrThrow(symbol));
public EntityBuilder<TContext> Add<TEntity>()
=> Add(World.Entity<TEntity>());
public EntityBuilder<TContext> Add(Entity relation, Entity target)
=> Add(World.Pair(relation, target));
public EntityBuilder<TContext> Add<TRelation>(Entity target)
=> Add(World.Pair<TRelation>(target));
public EntityBuilder<TContext> Add<TRelation, TTarget>()
=> Add(World.Pair<TRelation, TTarget>());
public EntityBuilder<TContext> Set<T>(Id id, in T value) where T : unmanaged
public override EntityBuilder Remove(Id id)
=> throw new NotSupportedException();
public override bool Has(Id id)
=> !id.IsWildcard ? _toAdd.Contains(id)
: throw new NotSupportedException(); // TODO: Support wildcard.
public override T? GetOrNull<T>(Id id) => throw new NotSupportedException();
public override T? GetOrNull<T>(Id id, T _ = null!) where T : class => throw new NotSupportedException();
public override T GetOrThrow<T>(Id id) => throw new NotSupportedException();
public override ref T GetMut<T>(Id id) => throw new NotSupportedException();
public override ref T GetRefOrNull<T>(Id id) => throw new NotSupportedException();
public override ref T GetRefOrThrow<T>(Id id) => throw new NotSupportedException();
public override void Modified<T>(Id id) => throw new NotImplementedException();
public override EntityBuilder Set<T>(Id id, in T value)
// "in" can't be used with lambdas, so we make a local copy.
{ var copy = value; _toSet.Add(e => EntityAccess.Set(World, e, id, copy)); return this; }
public EntityBuilder<TContext> Set<T>(Id id, T value) where T : class
{ _toSet.Add(e => EntityAccess.Set(World, e, id, value)); return this; }
{ var copy = value; _toSet.Add(e => e.Set(id, copy)); return this; }
public EntityBuilder<TContext> Set<T>(in T value) where T : unmanaged
=> Set(World.Entity<T>(), value);
public EntityBuilder<TContext> Set<T>(T value) where T : class
=> Set(World.Entity<T>(), value);
public override EntityBuilder Set<T>(Id id, T obj)
{ _toSet.Add(e => e.Set(id, obj)); return this; }
public unsafe Entity<TContext> Build()
public unsafe EntityRef Build()
{
var parent = _parent;
@ -103,25 +90,25 @@ public class EntityBuilder<TContext>
if (parent.IsSome && Path.IsAbsolute) throw new InvalidOperationException(
"Entity already has parent set (via ChildOf), so path must not be absolute");
// If path specifies more than just a name, ensure the parent entity exists.
if (Path.Count > 1) parent = EntityPath.EnsureEntityExists(World, Path.Parent!, parent);
if (Path.Count > 1) parent = EntityPath.EnsureEntityExists(World, parent, Path.Parent!);
}
using var alloc = TempAllocator.Use();
var desc = new ecs_entity_desc_t {
id = Id,
_name = (Path != null) ? alloc.AllocateCString(Path.Name.AsSpan()) : default,
_symbol = alloc.AllocateCString(_symbol),
_add_expr = alloc.AllocateCString(Expression),
name = (Path != null) ? alloc.AllocateCString(Path.Name.AsSpan()) : default,
symbol = alloc.AllocateCString(_symbol),
add_expr = alloc.AllocateCString(Expression),
use_low_id = UseLowId,
_sep = CStringExtensions.Empty,
sep = CStringExtensions.ETX,
};
var add = desc.add; var index = 0;
if (parent.IsSome) add[index++] = World.Pair(ChildOf, parent);
if (parent.IsSome) add[index++] = ECS.Id.Pair(World.ChildOf, parent);
foreach (var id in _toAdd) add[index++] = id;
var entityId = ecs_entity_init(World, &desc);
var entity = Entity<TContext>.GetOrInvalid(World, new(entityId));
var entity = new EntityRef(World, new(entityId));
foreach (var action in _toSet) action(entity);
return entity;

@ -4,13 +4,11 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using gaemstone.ECS.Internal;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
// TODO: Redo this with a single UTF8 byte array.
public class EntityPath
{
private readonly byte[][] _parts;
@ -50,29 +48,6 @@ public class EntityPath
return bytes;
}).ToArray()) { }
public static unsafe EntityPath From(World world, Entity entity)
{
if (entity.IsNone) throw new ArgumentException(
"entity is Entity.None", nameof(entity));
var parts = new List<byte[]>(32);
do {
var name = ecs_get_name(world, entity).FlecsToBytes();
if (name != null) parts.Add(name);
else {
// If name is not set, use the numeric Id, instead.
var id = entity.NumericId.ToString();
var bytes = new byte[Encoding.UTF8.GetByteCount(id) + 1];
Encoding.UTF8.GetBytes(id, bytes);
parts.Add(bytes);
}
} while ((entity = new(ecs_get_target(world, entity, EcsChildOf, 0))).IsSome);
parts.Reverse();
return new(true, parts.ToArray());
}
public static bool TryParse(string str, [NotNullWhen(true)] out EntityPath? result)
{
result = null;
@ -131,10 +106,6 @@ public class EntityPath
// throw new ArgumentException($"Must not contain {Rune.GetUnicodeCategory(rune)} character");
// }
public EntityPath ThrowIfAbsolute()
=> IsRelative ? this : throw new InvalidOperationException(
$"Path '{this}' must not be absolute");
public string[] GetParts()
{
var result = new string[Count];
@ -164,7 +135,7 @@ public class EntityPath
internal static unsafe Entity Lookup(
World world, EntityPath path, Entity parent, bool throwOnNotFound)
World world, Entity parent, EntityPath path, bool throwOnNotFound)
{
var start = path.IsAbsolute ? Entity.None // If path is absolute, ignore parent and use root.
: parent.IsNone ? new(ecs_get_scope(world)) // If no parent is specified, use the current scope.
@ -174,13 +145,14 @@ public class EntityPath
foreach (var part in path)
fixed (byte* ptr = part.AsSpan()) {
current = new(ecs_lookup_child(world, current, ptr));
if (current.IsNone || !ecs_is_alive(world, current)) {
if (!throwOnNotFound) return Entity.None;
else throw new EntityNotFoundException(
start.IsNone ? $"Entity at '{path}' not found"
: (start == parent) ? $"Child entity of '{From(world, start)}' at '{path}' not found"
: $"Entity at scope '{From(world, start)}' at '{path}' not found");
}
if (current.IsSome && ecs_is_alive(world, current)) continue;
if (!throwOnNotFound) return Entity.None;
var startStr = EntityRef.CreateOrNull(world, start)?.GetFullPath().ToString() ?? start.ToString();
throw new World.EntityNotFoundException(
start.IsNone ? $"Entity at '{path}' not found"
: (start == parent) ? $"Child entity of '{startStr}' at '{path}' not found"
: $"Entity at scope '{startStr}' at '{path}' not found");
}
return current;
@ -188,7 +160,7 @@ public class EntityPath
/// <summary> Used by <see cref="EntityBuilder.Build"/>. </summary>
internal static unsafe Entity EnsureEntityExists(
World world, EntityPath path, Entity parent)
World world, Entity parent, EntityPath path)
{
// If no parent is specified and path is relative, use the current scope.
if (parent.IsNone && path.IsRelative) parent = new(ecs_get_scope(world));
@ -197,8 +169,8 @@ public class EntityPath
foreach (var part in path)
fixed (byte* ptr = part.AsSpan())
if (skipLookup || (parent = new(ecs_lookup_child(world, parent, ptr))).IsNone) {
var desc = new ecs_entity_desc_t { _name = ptr, _sep = CStringExtensions.Empty };
if (parent.IsSome) desc.add[0] = Id.Pair(FlecsBuiltIn.ChildOf, parent);
var desc = new ecs_entity_desc_t { name = ptr, sep = CStringExtensions.ETX };
if (parent.IsSome) desc.add[0] = Id.Pair(world.ChildOf, parent);
parent = new(ecs_entity_init(world, &desc));
skipLookup = true;
}
@ -207,6 +179,30 @@ public class EntityPath
}
}
public static class EntityPathExtensions
{
public static unsafe EntityPath GetFullPath(this EntityRef entity)
{
var current = (Entity)entity;
var parts = new List<byte[]>(32);
do {
var name = ecs_get_name(entity.World, current).FlecsToBytes();
if (name != null) parts.Add(name);
else {
// If name is not set, use the numeric Id, instead.
var id = current.Id.ToString();
var bytes = new byte[Encoding.UTF8.GetByteCount(id) + 1];
Encoding.UTF8.GetBytes(id, bytes);
parts.Add(bytes);
}
} while ((current = new(ecs_get_target(entity.World, current, EcsChildOf, 0))).IsSome);
parts.Reverse();
return new(true, parts.ToArray());
}
}
public readonly ref struct UTF8View
{
private readonly ReadOnlySpan<byte> _bytes;

@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe class EntityRef
: EntityBase<EntityRef>
, IEquatable<EntityRef>
{
public override World World { get; }
public Entity Entity { get; }
public uint Id => Entity.Id;
public bool IsAlive => ecs_is_alive(World, this);
public EntityType Type => new(World, ecs_get_type(World, this));
public string? Name {
get => ecs_get_name(World, this).FlecsToString()!;
set { using var alloc = TempAllocator.Use();
ecs_set_name(World, this, alloc.AllocateCString(value)); }
}
public string? Symbol {
get => ecs_get_symbol(World, this).FlecsToString()!;
set { using var alloc = TempAllocator.Use();
ecs_set_symbol(World, this, alloc.AllocateCString(value)); }
}
private EntityRef(World world, Entity entity, bool throwOnInvalid)
{
if (throwOnInvalid && !ecs_is_valid(world, entity))
throw new InvalidOperationException($"The entity {entity} is not valid");
World = world;
Entity = entity;
}
public EntityRef(World world, Entity entity)
: this(world, entity, true) { }
public static EntityRef? CreateOrNull(World world, Entity entity)
=> ecs_is_valid(world, entity) ? new(world, entity) : null;
public void Delete()
=> ecs_delete(World, this);
public EntityBuilder NewChild(EntityPath? path = null)
=> World.New(this, EnsureRelativePath(path));
public EntityRef? LookupChild(EntityPath path)
=> World.LookupByPath(this, EnsureRelativePath(path)!);
public EntityRef LookupChildOrThrow(EntityPath path)
=> World.LookupByPathOrThrow(this, EnsureRelativePath(path)!);
private static EntityPath? EnsureRelativePath(EntityPath? path)
{
if (path?.IsAbsolute == true) throw new ArgumentException(
$"Path '{path}' must not be absolute", nameof(path)); return path;
}
public EntityRef? Parent
=> GetTargets(World.ChildOf).FirstOrDefault();
public IEnumerable<EntityRef> GetChildren()
{
foreach (var iter in Iterator.FromTerm(World, new(World.ChildOf, this)))
for (var i = 0; i < iter.Count; i++)
yield return iter.Entity(i);
}
public override EntityRef Add(Id id) { ecs_add_id(World, this, id); return this; }
public override EntityRef Remove(Id id) { ecs_remove_id(World, this, id); return this; }
public override bool Has(Id id) => ecs_has_id(World, this, id);
public override T? GetOrNull<T>(Id id)
{
var ptr = ecs_get_id(World, this, id);
return (ptr != null) ? Unsafe.Read<T>(ptr) : null;
}
public override T? GetOrNull<T>(Id id, T _ = null!)
where T : class
{
var ptr = ecs_get_id(World, this, id);
return (T?)Unsafe.Read<ReferenceHandle>(ptr).Target;
}
public override T GetOrThrow<T>(Id id)
{
var ptr = ecs_get_id(World, this, id);
if (ptr == null) throw new Exception($"Component {typeof(T)} not found on {this}");
if (typeof(T).IsValueType) return Unsafe.Read<T>(ptr);
else return (T)Unsafe.Read<ReferenceHandle>(ptr).Target!;
}
public override ref T GetRefOrNull<T>(Id id)
{
var @ref = ecs_ref_init_id(World, this, id);
var ptr = ecs_ref_get_id(World, &@ref, id);
return ref (ptr != null) ? ref Unsafe.AsRef<T>(ptr)
: ref Unsafe.NullRef<T>();
}
public override ref T GetRefOrThrow<T>(Id id)
{
ref var ptr = ref GetRefOrNull<T>(id);
if (Unsafe.IsNullRef(ref ptr)) throw new Exception(
$"Component {typeof(T)} not found on {this}");
return ref ptr;
}
public override ref T GetMut<T>(Id id)
{
var ptr = ecs_get_mut_id(World, this, id);
// NOTE: Value is added if it doesn't exist on the entity.
return ref Unsafe.AsRef<T>(ptr);
}
public override void Modified<T>(Id id)
=> ecs_modified_id(World, this, id);
public override EntityRef Set<T>(Id id, in T value)
{
var size = (ulong)Unsafe.SizeOf<T>();
fixed (T* ptr = &value)
if (ecs_set_id(World, this, id, size, ptr).Data == 0)
throw new InvalidOperationException();
return this;
}
public override EntityRef Set<T>(Id id, T obj) where T : class
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
var size = (ulong)sizeof(ReferenceHandle);
// Dispose this handle afterwards, since Flecs clones it.
using var handle = ReferenceHandle.Alloc(obj);
if (ecs_set_id(World, this, id, size, &handle).Data == 0)
throw new InvalidOperationException();
return this;
}
private EntityRef? GetTarget(Entity relation, int index)
=> CreateOrNull(World, new(ecs_get_target(World, this, relation, index)));
public IEnumerable<EntityRef> GetTargets(Entity relation)
{ var index = 0; while (GetTarget(relation, index++) is EntityRef target) yield return target; }
public IEnumerable<EntityRef> GetTargets(string symbol)
=> GetTargets(World.LookupBySymbolOrThrow(symbol));
public IEnumerable<EntityRef> GetTargets<T>()
=> GetTargets(World.LookupByTypeOrThrow(typeof(T)));
public bool Equals(EntityRef? other) => (other is not null) && (World == other.World) && (Entity == other.Entity);
public override bool Equals(object? obj) => Equals(obj as EntityRef);
public override int GetHashCode() => HashCode.Combine(World, Entity);
public override string? ToString() => ecs_entity_str(World, this).FlecsToStringAndFree()!;
public static bool operator ==(EntityRef? left, EntityRef? right) => ReferenceEquals(left, right) || (left?.Equals(right) ?? false);
public static bool operator !=(EntityRef? left, EntityRef? right) => !(left == right);
public static implicit operator Entity(EntityRef? e) => e?.Entity ?? default;
public static implicit operator ecs_entity_t(EntityRef? e) => e?.Entity.Value ?? default;
public static implicit operator Id(EntityRef? e) => new(e?.Entity.Value.Data ?? default);
public static implicit operator ecs_id_t(EntityRef? e) => e?.Entity.Value.Data ?? default;
}

@ -5,13 +5,13 @@ using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe readonly struct EntityType<TContext>
: IReadOnlyList<Id<TContext>>
public unsafe readonly struct EntityType
: IReadOnlyList<IdRef>
{
public readonly World<TContext> World;
public readonly ecs_type_t* Handle;
public World World { get; }
public ecs_type_t* Handle { get; }
public EntityType(World<TContext> world, ecs_type_t* handle)
public EntityType(World world, ecs_type_t* handle)
{ World = world; Handle = handle; }
public override string ToString()
@ -19,9 +19,7 @@ public unsafe readonly struct EntityType<TContext>
// IReadOnlyList implementation
public int Count => Handle->count;
public Id<TContext> this[int index]
=> Id<TContext>.GetUnsafe(World, new(Handle->array[index]));
public IEnumerator<Id<TContext>> GetEnumerator()
{ for (var i = 0; i < Count; i++) yield return this[i]; }
public IdRef this[int index] => new(World, new(Handle->array[index]));
public IEnumerator<IdRef> GetEnumerator() { for (var i = 0; i < Count; i++) yield return this[i]; }
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

@ -1,31 +0,0 @@
using System;
namespace gaemstone.ECS;
public class EntityNotFoundException
: Exception
{
public EntityNotFoundException(string message)
: base(message) { }
}
public class ComponentNotFoundException
: Exception
{
public ComponentNotFoundException(string message)
: base(message) { }
}
public class FlecsException
: Exception
{
public FlecsException() : base() { }
public FlecsException(string message) : base(message) { }
}
public class FlecsAbortException
: FlecsException
{
internal FlecsAbortException()
: base("Abort was called by flecs") { }
}

@ -6,21 +6,21 @@ using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe class Filter<TContext>
public unsafe class Filter
: IDisposable
{
public World<TContext> World { get; }
public World World { get; }
public ecs_filter_t* Handle { get; }
public Variable? ThisVar { get {
public Iterator.Variable? ThisVar { get {
var index = ecs_filter_find_this_var(this);
return (index >= 0) ? new(index, "This") : null;
} }
internal Filter(World<TContext> world, ecs_filter_t* handle)
internal Filter(World world, ecs_filter_t* handle)
{ World = world; Handle = handle; }
public Filter(World<TContext> world, FilterDesc desc)
public Filter(World world, FilterDesc desc)
{
using var alloc = TempAllocator.Use();
var flecsDesc = desc.ToFlecs(alloc);
@ -31,22 +31,13 @@ public unsafe class Filter<TContext>
public void Dispose()
=> ecs_filter_fini(this);
public FilterIterator<TContext> Iter()
=> new(ecs_filter_iter(World, this));
public Iterator Iter()
=> new(World, IteratorType.Filter, ecs_filter_iter(World, this));
public override string ToString()
=> ecs_filter_str(World, this).FlecsToStringAndFree()!;
public static implicit operator ecs_filter_t*(Filter<TContext> filter) => filter.Handle;
}
public unsafe class FilterIterator<TContext>
: Iterator<TContext>
{
internal FilterIterator(ecs_iter_t value)
: base(value) { }
public override bool Next()
=> ecs_filter_next(Handle);
public static implicit operator ecs_filter_t*(Filter q) => q.Handle;
}
public class FilterDesc
@ -77,7 +68,7 @@ public class FilterDesc
public unsafe ecs_filter_desc_t ToFlecs(IAllocator allocator)
{
var desc = new ecs_filter_desc_t {
_expr = allocator.AllocateCString(Expression),
expr = allocator.AllocateCString(Expression),
instanced = Instanced,
entity = Entity,
};

@ -1,36 +0,0 @@
using System;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public readonly struct Id
: IEquatable<Id>
{
public readonly ecs_id_t Value;
public IdFlags Flags => (IdFlags)(Value & ECS_ID_FLAGS_MASK);
public bool IsPair => ecs_id_is_pair(this);
public bool IsWildcard => ecs_id_is_wildcard(this);
public Entity RelationUnsafe => new(new() { Data = (Value & ECS_COMPONENT_MASK) >> 32 });
public Entity TargetUnsafe => new(new() { Data = Value & ECS_ENTITY_MASK });
public Id(ecs_id_t value) => Value = value;
public static Id Pair(Entity relation, Entity target)
=> new(ecs_make_pair(relation, target));
public bool Equals(Id other) => Value.Data == other.Value.Data;
public override bool Equals(object? obj) => (obj is Id other) && Equals(other);
public override int GetHashCode() => Value.Data.GetHashCode();
public override string? ToString()
=> (Flags != default) ? $"Id({Value.Data}, Flags={Flags})"
: $"Id({Value.Data})";
public static bool operator ==(Id left, Id right) => left.Equals(right);
public static bool operator !=(Id left, Id right) => !left.Equals(right);
public static implicit operator ecs_id_t(Id id) => id.Value;
public static implicit operator Term(Id id) => new(id);
}

@ -1,66 +1,47 @@
using System;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe struct Id<TContext>
: IEquatable<Id<TContext>>
public readonly struct Id
: IEquatable<Id>
{
public readonly World<TContext> World;
public readonly Id Value;
public readonly ecs_id_t Value;
public IdFlags Flags => Value.Flags;
public bool IsPair => Value.IsPair;
public bool IsWildcard => Value.IsWildcard;
public bool IsPair => ecs_id_is_pair(this);
public bool IsWildcard => ecs_id_is_wildcard(this);
public bool IsValid => ecs_id_is_valid(World, this);
public bool IsTag => ecs_id_is_tag(World, this);
public bool IsInUse => ecs_id_in_use(World, this);
public int Count => ecs_count_id(World, this);
public IdFlags Flags => (IdFlags)(Value & ECS_ID_FLAGS_MASK);
private Id(World<TContext> world, Id id)
{ World = world; Value = id; }
public Entity RelationUnsafe => new(new() { Data = (Value & ECS_COMPONENT_MASK) >> 32 });
public Entity TargetUnsafe => new(new() { Data = Value & ECS_ENTITY_MASK });
public static Id<TContext> GetUnsafe(World<TContext> world, Id value)
=> new(world, value);
public static Id<TContext>? GetOrNull(World<TContext> world, Id value)
=> ecs_id_is_valid(world, value) ? new(world, value) : null;
public static Id<TContext> GetOrThrow(World<TContext> world, Id value)
=> ecs_id_is_valid(world, value) ? new(world, value) : throw new InvalidOperationException($"The id {value} is not valid");
public Id(ecs_id_t value) => Value = value;
public Entity<TContext>? AsEntity()
=> !IsPair ? World.LookupAliveOrNull(new Entity(new() { Data = Value })) : null;
public (Entity<TContext> Relation, Entity<TContext> Target)? AsPair()
=> IsPair && (World.LookupAliveOrNull(Value.RelationUnsafe) is Entity<TContext> relation) &&
(World.LookupAliveOrNull(Value.TargetUnsafe ) is Entity<TContext> target )
? (relation, target) : null;
public static Id Combine(IdFlags flags, Id id)
=> new((ulong)flags | id.Value);
public bool Equals(Id<TContext> other)
{
#if DEBUG
// In DEBUG mode, we additionally check if the worlds the two compared
// values are from the same world. This accounts for the world being a
// stage, hence why it might not be the cheapest operation.
if (World != other.World) throw new ArgumentException(
"The specified values are not from the same world");
#endif
return Value == other.Value;
}
public override bool Equals(object? obj)
=> (obj is Id<TContext> other) && Equals(other);
public override int GetHashCode()
=> Value.GetHashCode();
public override string? ToString()
=> ecs_id_str(World, this).FlecsToStringAndFree()!;
public static Id Pair(Entity relation, Entity target)
=> Combine(IdFlags.Pair, new(
((relation.Value.Data << 32) & ECS_COMPONENT_MASK) |
( target.Value.Data & ECS_ENTITY_MASK )));
public EntityRef? AsEntity(World world)
=> new IdRef(world, this).AsEntity();
public (EntityRef Relation, EntityRef Target)? AsPair(World world)
=> new IdRef(world, this).AsPair();
public static bool operator ==(Id<TContext> left, Id<TContext> right) => left.Equals(right);
public static bool operator !=(Id<TContext> left, Id<TContext> right) => !left.Equals(right);
public bool Equals(Id other) => Value.Data == other.Value.Data;
public override bool Equals(object? obj) => (obj is Id 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 implicit operator Id (Id<TContext> id) => id.Value;
public static implicit operator ecs_id_t(Id<TContext> id) => id.Value.Value;
public static bool operator ==(Id left, Id right) => left.Equals(right);
public static bool operator !=(Id left, Id right) => !left.Equals(right);
public static implicit operator Term(Id<TContext> id) => new(id.Value);
public static implicit operator ecs_id_t(Id i) => i.Value;
}
[Flags]
@ -69,5 +50,7 @@ public enum IdFlags : ulong
Pair = 1ul << 63,
Override = 1ul << 62,
Toggle = 1ul << 61,
And = 1ul << 60,
Or = 1ul << 60,
And = 1ul << 59,
Not = 1ul << 58,
}

@ -0,0 +1,61 @@
using System;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe class IdRef
: IEquatable<IdRef>
{
public World World { get; }
public Id Id { get; }
public IdFlags Flags => Id.Flags;
public bool IsPair => Id.IsPair;
public bool IsWildcard => Id.IsWildcard;
public bool IsValid => ecs_id_is_valid(World, this);
public bool IsInUse => ecs_id_in_use(World, this);
public int Count => ecs_count_id(World, this);
public IdRef(World world, Id id)
{ World = world; Id = id; }
public static IdRef Combine(IdFlags flags, IdRef id)
=> new(id.World, Id.Combine(flags, id));
public static IdRef Pair(World world, Entity relation, Entity target)
=> new(world, Id.Pair(relation, target));
public static IdRef Pair(Entity relation, EntityRef target)
=> Pair(target.World, relation, target);
public static IdRef Pair(EntityRef relation, Entity target)
=> Pair(relation.World, relation, target);
public static IdRef Pair(EntityRef relation, EntityRef target)
=> Pair(relation.World, relation, target);
public static IdRef Pair<TRelation>(World world, Entity target)
=> Pair(world, world.LookupByTypeOrThrow<TRelation>(), target);
public static IdRef Pair<TRelation>(EntityRef target)
=> Pair(target.World.LookupByTypeOrThrow<TRelation>(), target);
public static IdRef Pair<TRelation, TTarget>(World world)
=> Pair(world, world.LookupByTypeOrThrow<TRelation>(),
world.LookupByTypeOrThrow<TTarget>());
public EntityRef? AsEntity()
=> (Flags == default) ? World.LookupAlive(new Entity(new() { Data = Id })) : null;
public (EntityRef Relation, EntityRef Target)? AsPair()
=> IsPair && (World.LookupAlive(Id.RelationUnsafe) is EntityRef relation) &&
(World.LookupAlive(Id.TargetUnsafe ) is EntityRef target )
? (relation, target) : null;
public bool Equals(IdRef? other) => (other is not null) && (World == other.World) && (Id == other.Id);
public override bool Equals(object? obj) => Equals(obj as IdRef);
public override int GetHashCode() => HashCode.Combine(World, Id);
public override string? ToString() => ecs_id_str(World, this).FlecsToStringAndFree()!;
public static bool operator ==(IdRef? left, IdRef? right) => ReferenceEquals(left, right) || (left?.Equals(right) ?? false);
public static bool operator !=(IdRef? left, IdRef? right) => !(left == right);
public static implicit operator Id(IdRef i) => i.Id;
public static implicit operator ecs_id_t(IdRef i) => i.Id.Value;
}

@ -1,117 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS.Internal;
public unsafe static class EntityAccess
{
public static bool IsValid(World world, Entity entity)
=> ecs_is_valid(world, entity);
public static bool IsAlive(World world, Entity entity)
=> ecs_is_alive(world, entity);
public static string? GetName(World world, Entity entity)
=> ecs_get_name(world, entity).FlecsToString()!;
public static void SetName(World world, Entity entity, string? value)
{ using var alloc = TempAllocator.Use(); ecs_set_name(world, entity, alloc.AllocateCString(value)); }
public static string? GetSymbol(World world, Entity entity)
=> ecs_get_symbol(world, entity).FlecsToString()!;
public static void SetSymbol(World world, Entity entity, string? value)
{ using var alloc = TempAllocator.Use(); ecs_set_symbol(world, entity, alloc.AllocateCString(value)); }
public static void Add(World world, Entity entity, Id id)
=> ecs_add_id(world, entity, id);
public static void Remove(World world, Entity entity, Id id)
=> ecs_remove_id(world, entity, id);
public static bool Has(World world, Entity entity, Id id)
=> ecs_has_id(world, entity, id);
public static IEnumerable<Entity> GetTargets(World world, Entity entity, Entity relation)
{
Entity GetTarget(int index)
=> new(ecs_get_target(world, entity, relation, index));
for (var i = 0; GetTarget(i) is { IsSome: true } target; i++)
yield return target;
}
public static T? GetOrNull<T>(World world, Entity entity, Id id)
where T : unmanaged
{
var ptr = ecs_get_id(world, entity, id);
return (ptr != null) ? Unsafe.Read<T>(ptr) : null;
}
public static T? GetOrNull<T>(World world, Entity entity, Id id, T _ = null!)
where T : class
{
var ptr = ecs_get_id(world, entity, id);
return (T?)Unsafe.Read<ReferenceHandle>(ptr).Target;
}
public static T GetOrThrow<T>(World world, Entity entity, Id id)
{
var ptr = ecs_get_id(world, entity, id);
if (ptr == null) throw new ComponentNotFoundException($"Component {id} not found on {entity}");
if (typeof(T).IsValueType) return Unsafe.Read<T>(ptr);
else return (T)Unsafe.Read<ReferenceHandle>(ptr).Target!;
}
public static ref T GetMut<T>(World world, Entity entity, Id id)
where T : unmanaged
{
var ptr = ecs_get_mut_id(world, entity, id);
// NOTE: Value is added if it doesn't exist on the entity.
return ref Unsafe.AsRef<T>(ptr);
}
public static ref T GetRefOrNull<T>(World world, Entity entity, Id id)
where T : unmanaged
{
var @ref = ecs_ref_init_id(world, entity, id);
var ptr = ecs_ref_get_id(world, &@ref, id);
return ref (ptr != null) ? ref Unsafe.AsRef<T>(ptr)
: ref Unsafe.NullRef<T>();
}
public static ref T GetRefOrThrow<T>(World world, Entity entity, Id id)
where T : unmanaged
{
ref var ptr = ref GetRefOrNull<T>(world, entity, id);
if (Unsafe.IsNullRef(ref ptr)) throw new Exception(
$"Component {typeof(T)} not found on {entity}");
return ref ptr;
}
public static void Modified(World world, Entity entity, Id id)
=> ecs_modified_id(world, entity, id);
public static Entity Set<T>(World world, Entity entity, Id id, in T value)
where T : unmanaged
{
var size = (ulong)Unsafe.SizeOf<T>();
fixed (T* ptr = &value)
if (ecs_set_id(world, entity, id, size, ptr).Data == 0)
throw new InvalidOperationException();
return entity;
}
public static Entity Set<T>(World world, Entity entity, Id id, T value)
where T : class
{
if (value == null) throw new ArgumentNullException(nameof(value));
var size = (ulong)sizeof(ReferenceHandle);
// Dispose entity handle afterwards, since Flecs clones it.
using var handle = ReferenceHandle.Alloc(value);
if (ecs_set_id(world, entity, id, size, &handle).Data == 0)
throw new InvalidOperationException();
return entity;
}
}

@ -1,11 +0,0 @@
using static flecs_hub.flecs;
namespace gaemstone.ECS.Internal;
public static class FlecsBuiltIn
{
public static Entity ChildOf { get; } = new(pinvoke_EcsChildOf());
// FIXME: Hopefully flecs-cs will expose this one day.
public static Entity Disabled { get; } = new(new() { Data = FLECS_HI_COMPONENT_ID + 7 });
}

@ -1,113 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS.Internal;
public unsafe class Iterator
: IDisposable
{
public ecs_iter_t* Handle;
private readonly bool _handleIsOwned;
public World World => new(Handle->world);
public int Count => Handle->count;
public IteratorFlags Flags => (IteratorFlags)(uint)Handle->flags;
public bool IsValid => (Flags & IteratorFlags.IsValid) != 0;
public TimeSpan DeltaTime => TimeSpan.FromSeconds(Handle->delta_time);
public TimeSpan DeltaSystemTime => TimeSpan.FromSeconds(Handle->delta_system_time);
public Iterator(ecs_iter_t* handle)
{
Handle = handle;
_handleIsOwned = false;
}
public Iterator(ecs_iter_t value)
{
Handle = GlobalHeapAllocator.Instance.AllocateCopy(value);
_handleIsOwned = true;
}
public void Dispose()
{
if (Handle == null) return;
// When an iterator is iterated until completion,
// ecs_iter_fini will be called automatically.
if (IsValid) ecs_iter_fini(Handle);
if (_handleIsOwned)
GlobalHeapAllocator.Instance.Free(Handle);
Handle = null;
}
public Entity GetVar(Variable var)
=> new(ecs_iter_get_var(Handle, var.Index));
public Iterator SetVar(Variable var, Entity entity)
{ ecs_iter_set_var(Handle, var.Index, entity); return this; }
public virtual bool Next()
=> ecs_iter_next(Handle);
public Entity First()
=> new(ecs_iter_first(Handle));
public bool Any()
=> ecs_iter_is_true(Handle);
public IEnumerable<Entity> GetAllEntities()
{
while (Next())
for (var i = 0; i < Count; i++)
yield return Entity(i);
}
public bool FieldIsSet(int index)
=> ecs_field_is_set(Handle, index);
public Id FieldId(int index)
=> new(ecs_field_id(Handle, index));
public bool FieldIs(int index, Id id)
=> ecs_field_id(Handle, index) == id.Value;
public Entity Entity(int index)
=> new(Handle->entities[index]);
public Span<T> Field<T>(int index)
where T : unmanaged
{
var size = (ulong)Unsafe.SizeOf<T>();
var isSelf = ecs_field_is_self(Handle, index);
var pointer = ecs_field_w_size(Handle, size, index);
return new(pointer, isSelf ? Count : 1);
}
public Span<T> FieldOrEmpty<T>(int index) where T : unmanaged
=> FieldIsSet(index) ? Field<T>(index) : Span<T>.Empty;
public SpanToRef<T> Field<T>(int index, T _ = null!) where T : class
=> new(Field<ReferenceHandle>(index));
public SpanToRef<T> FieldOrEmpty<T>(int index, T _ = null!) where T : class
=> FieldIsSet(index) ? Field<T>(index) : SpanToRef<T>.Empty;
public override string ToString()
=> ecs_iter_str(Handle).FlecsToStringAndFree()!;
}
public readonly ref struct SpanToRef<T>
where T : class
{
public static SpanToRef<T> Empty => default;
private readonly Span<ReferenceHandle> _span;
public int Length => _span.Length;
public T this[int index] => (T)_span[index].Target!;
public T? GetOrNull(int index) => ((index >= 0) && (index < Length)) ? this[index] : null;
internal SpanToRef(Span<ReferenceHandle> span) => _span = span;
}

@ -1,9 +0,0 @@
namespace gaemstone.ECS.Internal;
internal static class Lookup<TContext>
{
internal static class Entity<T>
{
public static Entity Value;
}
}

@ -1,92 +1,158 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using gaemstone.ECS.Internal;
using System.Runtime.CompilerServices;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe class Iterator<TContext> : Iterator
, IEnumerable<Iterator<TContext>>
public unsafe class Iterator
: IEnumerable<Iterator>
, IDisposable
{
public new World<TContext> World => new(base.World);
public World World { get; }
public IteratorType? Type { get; }
public ecs_iter_t Value;
public Iterator(ecs_iter_t* handle) : base(handle) { }
public Iterator(ecs_iter_t value) : base(value) { }
public bool Completed { get; private set; }
public int Count => Value.count;
public TimeSpan DeltaTime => TimeSpan.FromSeconds(Value.delta_time);
public TimeSpan DeltaSystemTime => TimeSpan.FromSeconds(Value.delta_system_time);
public static TermIterator<TContext> FromTerm(World world, Term term)
public Iterator(World world, IteratorType? type, ecs_iter_t value)
{ World = world; Type = type; Value = value; }
public static Iterator FromTerm(World world, Term term)
{
using var alloc = TempAllocator.Use();
var flecsTerm = term.ToFlecs(alloc);
var flecsIter = ecs_term_iter(world, &flecsTerm);
return new(flecsIter);
return new(world, IteratorType.Term, flecsIter);
}
public void Dispose()
{
// When an iterator is iterated until completion,
// ecs_iter_fini will be called automatically.
if (!Completed)
fixed (ecs_iter_t* ptr = &Value)
ecs_iter_fini(ptr);
}
public EntityRef GetVar(Variable var)
{
fixed (ecs_iter_t* ptr = &Value)
return new(World, new(ecs_iter_get_var(ptr, var.Index)));
}
public Iterator SetVar(Variable var, Entity entity)
{
fixed (ecs_iter_t* ptr = &Value)
ecs_iter_set_var(ptr, var.Index, entity);
return this;
}
public new Entity<TContext>? GetVar(Variable var)
=> Entity<TContext>.GetOrNull(World, base.GetVar(var));
public new Iterator<TContext> SetVar(Variable var, Entity entity)
=> (Iterator<TContext>)base.SetVar(var, entity);
public new Entity<TContext>? First()
=> Entity<TContext>.GetOrNull(World, base.First());
public new IEnumerable<Entity<TContext>> GetAllEntities()
=> base.GetAllEntities().Select(e => Entity<TContext>.GetOrInvalid(World, e));
public bool Next()
{
fixed (ecs_iter_t* ptr = &Value) {
var result = Type switch {
IteratorType.Term => ecs_term_next(ptr),
IteratorType.Filter => ecs_filter_next(ptr),
IteratorType.Query => ecs_query_next(ptr),
IteratorType.Rule => ecs_rule_next(ptr),
_ => ecs_iter_next(ptr),
};
Completed = !result;
return result;
}
}
public new Id<TContext> FieldId(int index)
=> Id<TContext>.GetUnsafe(World, base.FieldId(index));
public bool FieldIsSet(int index)
{
fixed (ecs_iter_t* ptr = &Value)
return ecs_field_is_set(ptr, index);
}
public new Entity<TContext> Entity(int index)
=> Entity<TContext>.GetOrInvalid(World, base.Entity(index));
public bool FieldIs(int index, Id id)
{
fixed (ecs_iter_t* ptr = &Value)
return ecs_field_id(ptr, index) == id.Value;
}
// TODO: Potentially misleading, doesn't check the field's backing data type.
// The id might be "(Identifier, Name)", but its data type "Identifier".
public bool FieldIs<T>(int index)
=> FieldIs(index, World.LookupByType<T>());
public IdRef FieldId(int index)
{
fixed (ecs_iter_t* ptr = &Value)
return new(World, new(ecs_field_id(ptr, index)));
}
public EntityRef Entity(int index)
=> new(World, new(Value.entities[index]));
public Span<T> Field<T>(int index)
where T : unmanaged
{
fixed (ecs_iter_t* ptr = &Value) {
var size = (ulong)Unsafe.SizeOf<T>();
var isSelf = ecs_field_is_self(ptr, index);
var pointer = ecs_field_w_size(ptr, size, index);
return new(pointer, isSelf ? Count : 1);
}
}
public SpanToRef<T> Field<T>(int index, T _ = null!) where T : class
=> new(Field<ReferenceHandle>(index));
public Span<T> FieldOrEmpty<T>(int index) where T : unmanaged
=> FieldIsSet(index) ? Field<T>(index) : Span<T>.Empty;
public SpanToRef<T> FieldOrEmpty<T>(int index, T _ = null!) where T : class
=> FieldIsSet(index) ? Field(index, _) : SpanToRef<T>.Empty;
public override string ToString()
{
fixed (ecs_iter_t* ptr = &Value)
return ecs_iter_str(ptr).FlecsToStringAndFree()!;
}
// IEnumerable implementation
public virtual IEnumerator<Iterator<TContext>> GetEnumerator()
{ while (Next()) yield return this; }
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
}
public IEnumerator<Iterator> GetEnumerator() { while (Next()) yield return this; }
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
[Flags]
public enum IteratorFlags : uint
{
/// <summary> Does iterator contain valid result. </summary>
IsValid = EcsIterIsValid,
/// <summary> Does iterator provide (component). </summary>
NoData = EcsIterNoData,
/// <summary> Is iterator instanced. </summary>
IsInstanced = EcsIterIsInstanced,
/// <summary> Does result have shared terms. </summary>
HasShared = EcsIterHasShared,
/// <summary> Result only populates table. </summary>
TableOnly = EcsIterTableOnly,
/// <summary> Treat terms with entity subject as optional. </summary>
EntityOptional = EcsIterEntityOptional,
/// <summary> Iterator has no results. </summary>
NoResults = EcsIterNoResults,
/// <summary> Only evaluate non-this terms. </summary>
IgnoreThis = EcsIterIgnoreThis,
/// <summary> Does iterator have conditionally set fields. </summary>
MatchVar = EcsIterMatchVar,
/// <summary> </summary>
HasCondSet = EcsIterHasCondSet,
/// <summary> Profile iterator performance. </summary>
Profile = EcsIterProfile,
}
public class Variable
{
public int Index { get; }
public string Name { get; }
public Variable(int index, string name)
{ Index = index; Name = name; }
public class Variable
{
public int Index { get; }
public string Name { get; }
public Variable(int index, string name)
{ Index = index; Name = name; }
}
public readonly ref struct SpanToRef<T>
where T : class
{
public static SpanToRef<T> Empty => default;
private readonly Span<ReferenceHandle> _span;
public int Length => _span.Length;
public T? this[int index] => (T?)_span[index].Target;
internal SpanToRef(Span<ReferenceHandle> span) => _span = span;
}
}
public unsafe class TermIterator<TContext>
: Iterator<TContext>
public enum IteratorType
{
internal TermIterator(ecs_iter_t value)
: base(value) { }
public override bool Next()
=> ecs_term_next(Handle);
Term,
Filter,
Query,
Rule,
}

@ -7,35 +7,29 @@ namespace gaemstone.ECS;
public static class ObserverExtensions
{
public static unsafe Entity<TContext> InitObserver<TContext>(this Entity<TContext> entity,
FilterDesc filter, Action<Iterator<TContext>> callback, params Entity[] events)
public static unsafe EntityRef InitObserver(this EntityRef entity,
Entity @event, FilterDesc filter, Action<Iterator> callback)
{
if (events.Length == 0) throw new ArgumentException("Must specify at least 1 event", nameof(events));
if (events.Length > 8) throw new ArgumentException("Must specify at most 8 events", nameof(events));
var world = entity.World;
var internalCallback = (nint iterPtr) => callback(new((ecs_iter_t*)iterPtr));
using var alloc = TempAllocator.Use();
var desc = new ecs_observer_desc_t {
entity = entity,
filter = filter.ToFlecs(alloc),
binding_ctx = (void*)CallbackContextHelper.Create(internalCallback),
binding_ctx = (void*)CallbackContextHelper.Create((world, callback)),
binding_ctx_free = new() { Data = new() { Pointer = &FreeContext } },
callback = new() { Data = new() { Pointer = &Callback } },
};
var span = desc.events;
for (var i = 0; i < events.Length; i++)
span[i] = events[i];
if (ecs_observer_init(world, &desc).Data == default)
throw new InvalidOperationException();
return entity;
desc.events[0] = @event;
return new(world, new(ecs_observer_init(world, &desc)));
}
[UnmanagedCallersOnly]
private static unsafe void Callback(ecs_iter_t* iter)
=> CallbackContextHelper
.Get<Action<nint>>((nint)iter->binding_ctx)
.Invoke((nint)iter);
{
var (world, callback) = CallbackContextHelper
.Get<(World, Action<Iterator>)>((nint)iter->binding_ctx);
callback(new Iterator(world, null, *iter));
}
[UnmanagedCallersOnly]
private static unsafe void FreeContext(void* context)

@ -4,16 +4,16 @@ using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe class Query<TContext>
public unsafe class Query
: IDisposable
{
public World<TContext> World { get; }
public World World { get; }
public ecs_query_t* Handle { get; }
public Filter<TContext> Filter { get; }
public Filter Filter { get; }
public Variable? ThisVar => Filter.ThisVar;
public Iterator.Variable? ThisVar => Filter.ThisVar;
public Query(World<TContext> world, QueryDesc desc)
public Query(World world, QueryDesc desc)
{
using var alloc = TempAllocator.Use();
var flecsDesc = desc.ToFlecs(alloc);
@ -25,33 +25,24 @@ public unsafe class Query<TContext>
public void Dispose()
=> ecs_query_fini(this);
public QueryIterator<TContext> Iter()
=> new(ecs_query_iter(World, this));
public Iterator Iter()
=> new(World, IteratorType.Query, ecs_query_iter(World, this));
public override string ToString()
=> ecs_query_str(Handle).FlecsToStringAndFree()!;
public static implicit operator ecs_query_t*(Query<TContext> query) => query.Handle;
}
public unsafe class QueryIterator<TContext>
: Iterator<TContext>
{
internal QueryIterator(ecs_iter_t value)
: base(value) { }
public override bool Next()
=> ecs_query_next(Handle);
public static implicit operator ecs_query_t*(Query q) => q.Handle;
}
public class QueryDesc : FilterDesc
{
public QueryDesc(params Term[] terms) : base(terms) { }
public QueryDesc(string expression) : base(expression) { }
public QueryDesc(params Term[] terms) : base(terms) { }
public new unsafe ecs_query_desc_t ToFlecs(IAllocator alloc)
public new unsafe ecs_query_desc_t ToFlecs(IAllocator allocator)
{
var desc = new ecs_query_desc_t {
filter = base.ToFlecs(alloc),
filter = base.ToFlecs(allocator),
// TODO: Implement more Query features.
};
return desc;

@ -6,18 +6,18 @@ using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe class Rule<TContext>
public unsafe class Rule
: IDisposable
{
public World<TContext> World { get; }
public World World { get; }
public ecs_rule_t* Handle { get; }
public Filter<TContext> Filter { get; }
public Filter Filter { get; }
private VariableCollection? _variables;
public VariableCollection Variables => _variables ??= new(this);
public Variable? ThisVar => Filter.ThisVar;
public Iterator.Variable? ThisVar => Filter.ThisVar;
public Rule(World<TContext> world, FilterDesc desc)
public Rule(World world, FilterDesc desc)
{
using var alloc = TempAllocator.Use();
var flecsDesc = desc.ToFlecs(alloc);
@ -29,53 +29,48 @@ public unsafe class Rule<TContext>
public void Dispose()
=> ecs_rule_fini(this);
public RuleIterator<TContext> Iter()
=> new(ecs_rule_iter(World, this));
public Iterator Iter()
=> new(World, IteratorType.Rule, ecs_rule_iter(World, this));
public override string ToString()
=> ecs_rule_str(Handle).FlecsToStringAndFree()!;
public static implicit operator ecs_rule_t*(Rule<TContext> rule) => rule.Handle;
public static implicit operator ecs_rule_t*(Rule q) => q.Handle;
public unsafe class VariableCollection
: IReadOnlyCollection<Variable>
: IReadOnlyCollection<Iterator.Variable>
{
private readonly List<Variable> _variables = new();
private readonly List<Iterator.Variable> _variables = new();
public Rule Rule { get; }
public int Count => _variables.Count;
public Variable? this[int index]
public Iterator.Variable? this[int index]
=> _variables.Find(v => v.Index == index);
public Variable? this[string name]
public Iterator.Variable? this[string name]
=> _variables.Find(v => string.Equals(
v.Name, name, StringComparison.OrdinalIgnoreCase));
internal VariableCollection(ecs_rule_t* handle)
internal VariableCollection(Rule rule)
{
// Find the $this variable, if the rule has one.
var thisIndex = ecs_filter_find_this_var(ecs_rule_get_filter(handle));
if (thisIndex >= 0) _variables.Add(new(thisIndex, "this"));
Rule = rule;
// Find the $This variable, if the rule has one.
var thisIndex = ecs_filter_find_this_var(ecs_rule_get_filter(Rule));
if (thisIndex >= 0) _variables.Add(new(thisIndex, "This"));
// Find all the other "accessible" variables.
var count = ecs_rule_var_count(handle);
var count = ecs_rule_var_count(Rule);
for (var i = 0; i < count; i++) {
if ((i == thisIndex) || !ecs_rule_var_is_entity(handle, i)) continue;
var name = ecs_rule_var_name(handle, i).FlecsToString()!;
if ((i == thisIndex) || !ecs_rule_var_is_entity(Rule, i)) continue;
var name = ecs_rule_var_name(Rule, i).FlecsToString()!;
_variables.Add(new(i, name));
}
}
// IEnumerable implementation
public IEnumerator<Variable> GetEnumerator() => _variables.GetEnumerator();
public IEnumerator<Iterator.Variable> GetEnumerator() => _variables.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _variables.GetEnumerator();
}
}
public unsafe class RuleIterator<TContext>
: Iterator<TContext>
{
internal RuleIterator(ecs_iter_t value)
: base(value) { }
public override bool Next()
=> ecs_rule_next(Handle);
}

@ -7,31 +7,48 @@ namespace gaemstone.ECS;
public static class SystemExtensions
{
public static unsafe Entity<TContext> InitSystem<TContext>(this Entity<TContext> entity,
QueryDesc query, Action<Iterator<TContext>> callback)
public static unsafe EntityRef InitSystem(this EntityRef entity,
Entity phase, QueryDesc query, Action<Iterator> callback)
{
var world = entity.World;
var internalCallback = (nint iter) =>
callback(new((ecs_iter_t*)iter));
entity.Add(world.DependsOn, phase);
using var alloc = TempAllocator.Use();
var desc = new ecs_system_desc_t {
entity = entity,
query = query.ToFlecs(alloc),
binding_ctx = (void*)CallbackContextHelper.Create(internalCallback),
binding_ctx = (void*)CallbackContextHelper.Create((world, callback)),
binding_ctx_free = new() { Data = new() { Pointer = &FreeContext } },
callback = new() { Data = new() { Pointer = &Callback } },
};
if (ecs_system_init(world, &desc).Data == default)
throw new InvalidOperationException();
return entity;
return new(world, new(ecs_system_init(world, &desc)));
}
[UnmanagedCallersOnly]
private static unsafe void Callback(ecs_iter_t* iter)
=> CallbackContextHelper
.Get<Action<nint>>((nint)iter->binding_ctx)
.Invoke((nint)iter);
{
var (world, callback) = CallbackContextHelper
.Get<(World, Action<Iterator>)>((nint)iter->binding_ctx);
callback(new Iterator(world, null, *iter));
}
// [UnmanagedCallersOnly]
// private static unsafe void Run(ecs_iter_t* flecsIter)
// {
// var (world, callback) = CallbackContextHelper
// .Get<(World, Action<Iterator>)>((nint)flecsIter->binding_ctx);
// // This is what flecs does, so I guess we'll do it too!
// var type = (&flecsIter->next == (delegate*<ecs_iter_t*, Runtime.CBool>)&ecs_query_next)
// ? IteratorType.Query : (IteratorType?)null;
// using var iter = new Iterator(world, type, *flecsIter);
// If the method is marked with [Source], set the $This variable.
// if (Method.Get<SourceAttribute>()?.Type is Type sourceType)
// iter.SetThis(World.LookupOrThrow(sourceType));
// if (flecsIter->field_count == 0) callback(iter);
// else while (iter.Next()) callback(iter);
// }
[UnmanagedCallersOnly]
private static unsafe void FreeContext(void* context)

@ -10,8 +10,8 @@ public class Term
public TermId? Source { get; set; }
public TermId? Relation { get; set; }
public TermId? Target { get; set; }
public TermInOutKind InOutKind { get; set; }
public TermOperKind OperKind { get; set; }
public TermInOutKind InOut { get; set; }
public TermOperKind Oper { get; set; }
public IdFlags Flags { get; set; }
public Term() { }
@ -19,22 +19,26 @@ public class Term
public Term(TermId relation, TermId target)
{ Relation = relation; Target = target; }
public Term None { get { InOutKind = TermInOutKind.None; return this; } }
public Term In { get { InOutKind = TermInOutKind.In; return this; } }
public Term Out { get { InOutKind = TermInOutKind.Out; return this; } }
public Term InOut { get { InOutKind = TermInOutKind.InOut; return this; } }
public static implicit operator Term(EntityRef entity) => new(entity);
public static implicit operator Term(Entity entity) => new(entity);
public static implicit operator Term(IdRef id) => new(id);
public static implicit operator Term(Id id) => new(id);
public Term Or { get { OperKind = TermOperKind.Or; return this; } }
public Term Not { get { OperKind = TermOperKind.Not; return this; } }
public Term Optional { get { OperKind = TermOperKind.Optional; return this; } }
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(IAllocator allocator) => new() {
id = Id,
src = Source ?.ToFlecs(allocator) ?? default,
src = Source?.ToFlecs(allocator) ?? default,
first = Relation?.ToFlecs(allocator) ?? default,
second = Target ?.ToFlecs(allocator) ?? default,
inout = (ecs_inout_kind_t)InOutKind,
oper = (ecs_oper_kind_t)OperKind,
second = Target?.ToFlecs(allocator) ?? default,
inout = (ecs_inout_kind_t)InOut,
oper = (ecs_oper_kind_t)Oper,
id_flags = (ecs_id_t)(ulong)Flags,
};
}
@ -61,7 +65,7 @@ public enum TermOperKind
public class TermId
{
public static TermId This { get; } = new("$this");
public static TermId This { get; } = new("$This");
public Entity Id { get; }
public string? Name { get; }
@ -78,9 +82,13 @@ public class TermId
} 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(IAllocator allocator) => new() {
id = Id,
_name = allocator.AllocateCString(Name),
name = allocator.AllocateCString(Name),
trav = Traverse,
flags = (ecs_flags32_t)(uint)Flags
};
@ -90,23 +98,19 @@ public class TermId
public enum TermTraversalFlags : uint
{
/// <summary> Match on self. </summary>
Self = EcsSelf,
Self = EcsSelf,
/// <summary> Match by traversing upwards. </summary>
Up = EcsUp,
Up = EcsUp,
/// <summary> Match by traversing downwards (derived, cannot be set). </summary>
Down = EcsDown,
/// <summary> Match all entities encountered through traversal. </summary>
TraverseAll = EcsTraverseAll,
Down = EcsDown,
/// <summary> Sort results breadth first. </summary>
Cascade = EcsCascade,
Cascade = EcsCascade,
/// <summary> Short for up(ChildOf). </summary>
Parent = EcsParent,
Parent = EcsParent,
/// <summary> Term id is a variable. </summary>
IsVariable = EcsIsVariable,
IsVariable = EcsIsVariable,
/// <summary> Term id is an entity. </summary>
IsEntity = EcsIsEntity,
/// <summary> Term id is a name (don't attempt to lookup as entity). </summary>
IsName = EcsIsName,
/// <summary> revent observer from triggering on term. </summary>
Filter = EcsFilter,
IsEntity = EcsIsEntity,
/// <summary> Prevent observer from triggering on term. </summary>
Filter = EcsFilter,
}

@ -15,19 +15,18 @@ public interface IAllocator
public unsafe static class AllocatorExtensions
{
public static Span<T> AllocateCopy<T>(this IAllocator allocator, ReadOnlySpan<T> orig) where T : unmanaged
{ var copy = allocator.Allocate<T>(orig.Length); orig.CopyTo(copy); return copy; }
public static Span<T> Allocate<T>(this IAllocator allocator, int count) where T : unmanaged
=> new((void*)allocator.Allocate(sizeof(T) * count), count);
public static void Free<T>(this IAllocator allocator, Span<T> span) where T : unmanaged
=> allocator.Free((nint)Unsafe.AsPointer(ref span[0]));
public static T* AllocateCopy<T>(this IAllocator allocator, T orig) where T : unmanaged
{ var ptr = allocator.Allocate<T>(); *ptr = orig; return ptr; }
public static T* Allocate<T>(this IAllocator allocator) where T : unmanaged
=> (T*)allocator.Allocate(sizeof(T));
public static void Free<T>(this IAllocator allocator, T* ptr) where T : unmanaged
=> allocator.Free((nint)ptr);
public static Span<T> AllocateCopy<T>(this IAllocator allocator, ReadOnlySpan<T> orig) where T : unmanaged
{ var copy = allocator.Allocate<T>(orig.Length); orig.CopyTo(copy); return copy; }
public static ref T Allocate<T>(this IAllocator allocator) where T : unmanaged
=> ref Unsafe.AsRef<T>((void*)allocator.Allocate(sizeof(T)));
public static void Free<T>(this IAllocator allocator, ref T value) where T : unmanaged
=> allocator.Free((nint)Unsafe.AsPointer(ref value));
public static CString AllocateCString(this IAllocator allocator, string? value)
{

@ -8,6 +8,7 @@ namespace gaemstone.ECS.Utility;
public unsafe static class CStringExtensions
{
public static CString Empty { get; } = (CString)"";
public static CString ETX { get; } = (CString)"\x3"; // FIXME: Temporary, until flecs supports Empty.
public static unsafe byte[]? FlecsToBytes(this CString str)
{

@ -0,0 +1,26 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace gaemstone.ECS.Utility;
public class FlecsException
: Exception
{
public FlecsException() : base() { }
public FlecsException(string message) : base(message) { }
}
public class FlecsAbortException
: FlecsException
{
private readonly string _stackTrace = new StackTrace(2, true).ToString();
public override string? StackTrace => _stackTrace;
private FlecsAbortException()
: base("Abort was called by flecs") { }
[UnmanagedCallersOnly]
internal static void Callback()
=> throw new FlecsAbortException();
}

@ -1,71 +0,0 @@
using System;
using System.Runtime.InteropServices;
using static flecs_hub.flecs;
namespace gaemstone.ECS.Utility;
public unsafe readonly struct ReferenceHandle
: IDisposable
{
public static int NumActiveHandles { get; private set; }
private readonly nint _value;
public object? Target => (_value != default)
? ((GCHandle)_value).Target : null;
private ReferenceHandle(nint value)
=> _value = value;
public static ReferenceHandle Alloc(object? target)
{
if (target == null) return default;
NumActiveHandles++;
return new((nint)GCHandle.Alloc(target));
}
public ReferenceHandle Clone()
=> Alloc(Target);
public void Dispose()
{
if (_value == default) return;
NumActiveHandles--;
((GCHandle)_value).Free();
}
[UnmanagedCallersOnly]
internal static void Construct(void* ptr, int count, ecs_type_info_t* _)
=> new Span<ReferenceHandle>(ptr, count).Clear();
[UnmanagedCallersOnly]
internal static void Destruct(void* ptr, int count, ecs_type_info_t* _)
{
var span = new Span<ReferenceHandle>(ptr, count);
foreach (var handle in span) handle.Dispose();
span.Clear();
}
[UnmanagedCallersOnly]
internal static void Move(void* dstPtr, void* srcPtr, int count, ecs_type_info_t* _)
{
var dst = new Span<ReferenceHandle>(dstPtr, count);
var src = new Span<ReferenceHandle>(srcPtr, count);
foreach (var handle in dst) handle.Dispose();
src.CopyTo(dst);
src.Clear();
}
[UnmanagedCallersOnly]
internal static void Copy(void* dstPtr, void* srcPtr, int count, ecs_type_info_t* _)
{
var dst = new Span<ReferenceHandle>(dstPtr, count);
var src = new Span<ReferenceHandle>(srcPtr, count);
for (var i = 0; i < count; i++) {
dst[i].Dispose();
dst[i] = src[i].Clone();
}
}
}

@ -7,7 +7,7 @@ public static class SpanExtensions
public static T? GetOrNull<T>(this Span<T> span, int index)
where T : struct => GetOrNull((ReadOnlySpan<T>)span, index);
public static T? GetOrNull<T>(this ReadOnlySpan<T> span, int index)
where T : struct => ((index >= 0) && (index < span.Length)) ? span[index] : null;
where T : struct => (index >= 0 && index < span.Length) ? span[index] : null;
public static ReadOnlySpanSplitEnumerator<T> Split<T>(this ReadOnlySpan<T> span, T splitOn)

@ -1,75 +0,0 @@
using System;
using System.Runtime.InteropServices;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe struct World
: IEquatable<World>
, IDisposable
{
public ecs_world_t* Handle { get; }
public bool IsDeferred => ecs_is_deferred(this);
public bool IsQuitRequested => ecs_should_quit(this);
public World(ecs_world_t* handle)
=> Handle = handle;
/// <summary> Initializes a new Flecs world. </summary>
/// <param name="minimal"> If true, doesn't automatically import built-in modules. </param>
public World(bool minimal = false)
{
[UnmanagedCallersOnly]
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);
Handle = minimal ? ecs_mini() : ecs_init();
}
public bool Progress(TimeSpan delta)
=> ecs_progress(this, (float)delta.TotalSeconds);
public void Quit()
=> ecs_quit(this);
public void Dispose()
=> ecs_fini(this);
public Entity LookupAlive(Entity value)
=> new(ecs_get_alive(this, value));
public Entity LookupPath(EntityPath path, Entity parent = default,
bool throwOnNotFound = false)
=> new(EntityPath.Lookup(this, path, parent, throwOnNotFound));
// TODO: Provide overload that uses a UTF-8 byte span?
public Entity LookupSymbol(string symbol)
{
using var alloc = TempAllocator.Use();
return new(ecs_lookup_symbol(this, alloc.AllocateCString(symbol), false));
}
public bool Equals(World other)
{
if (Handle == other.Handle) return true;
return ecs_get_world((ecs_poly_t*)Handle)
== ecs_get_world((ecs_poly_t*)other.Handle);
}
public override bool Equals(object? obj)
=> (obj is World other) && Equals(other);
public override int GetHashCode()
=> ((nint)ecs_get_world((ecs_poly_t*)Handle)).GetHashCode();
public static bool operator ==(World left, World right) => left.Equals(right);
public static bool operator !=(World left, World right) => !left.Equals(right);
public static implicit operator ecs_world_t*(World w) => w.Handle;
}

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe partial class World
{
private readonly Dictionary<Type, Entity> _lookupByType = new();
public void AddLookupByType(Type type, Entity entity)
{
// If an existing lookup already exists with the same entity, don't throw an exception.
if (_lookupByType.TryGetValue(type, out var existing) && (existing == entity)) return;
_lookupByType.Add(type, entity);
}
public void RemoveLookupByType(Type type)
{ if (!_lookupByType.Remove(type)) throw new InvalidOperationException(
$"Lookup for {type} does not exist"); }
public EntityRef? LookupByType<T>()
=> LookupByType(typeof(T));
public EntityRef? LookupByType(Type type)
=> LookupAlive(_lookupByType.GetValueOrDefault(type));
public EntityRef LookupByTypeOrThrow<T>()
=> LookupByTypeOrThrow(typeof(T));
public EntityRef LookupByTypeOrThrow(Type type)
=> LookupByType(type) ?? throw new EntityNotFoundException(
$"Entity of type {type} not found");
public EntityRef? LookupAlive(Entity value)
=> EntityRef.CreateOrNull(this, new(ecs_get_alive(this, value)));
public EntityRef LookupAliveOrThrow(Entity entity)
=> LookupAlive(entity) ?? throw new EntityNotFoundException(
$"Entity {entity} is not alive");
public EntityRef? LookupByPath(EntityPath path)
=> LookupByPath(default, path);
public EntityRef? LookupByPath(Entity parent, EntityPath path)
=> EntityRef.CreateOrNull(this, EntityPath.Lookup(this, parent, path, false));
public EntityRef LookupByPathOrThrow(EntityPath path)
=> LookupByPathOrThrow(default, path);
public EntityRef LookupByPathOrThrow(Entity parent, EntityPath path)
=> new(this, EntityPath.Lookup(this, parent, path, true));
public EntityRef? LookupBySymbol(string symbol)
{
using var alloc = TempAllocator.Use();
var entity = ecs_lookup_symbol(this, alloc.AllocateCString(symbol), false);
return EntityRef.CreateOrNull(this, new(entity));
}
public EntityRef LookupBySymbolOrThrow(string symbol)
=> LookupBySymbol(symbol) ?? throw new EntityNotFoundException(
$"Entity with symbol '{symbol}' not found");
public class EntityNotFoundException : Exception
{ public EntityNotFoundException(string message) : base(message) { } }
}
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.World.AddLookupByType(type, entity); return entity; }
}

@ -1,78 +1,60 @@
using System;
using gaemstone.ECS.Internal;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.ECS;
public unsafe struct World<TContext>
: IEquatable<World<TContext>>
, IDisposable
public unsafe partial class World
{
public readonly World Value;
public ecs_world_t* Handle { get; }
public bool IsDeferred => Value.IsDeferred;
public bool IsQuitRequested => Value.IsQuitRequested;
// Flecs built-ins that are important to internals.
internal EntityRef ChildOf { get; }
internal EntityRef Disabled { get; }
internal EntityRef DependsOn { get; }
public World(World value)
=> Value = value;
public bool IsDeferred => ecs_is_deferred(this);
public bool IsQuitRequested => ecs_should_quit(this);
/// <summary> Initializes a new Flecs world. </summary>
/// <param name="minimal"> If true, doesn't automatically import built-in modules. </param>
public World(bool minimal = false)
: this(new World(minimal)) { }
public World(params string[] args)
{
ecs_os_set_api_defaults();
var api = ecs_os_get_api();
api.abort_ = new FnPtr_Void { Pointer = &FlecsAbortException.Callback };
ecs_os_set_api(&api);
public bool Progress(TimeSpan delta) => Value.Progress(delta);
public void Quit() => Value.Quit();
public void Dispose() => Value.Dispose();
Handle = ecs_init_w_args(args.Length, null);
ChildOf = LookupByPathOrThrow("/flecs/core/ChildOf");
Disabled = LookupByPathOrThrow("/flecs/core/Disabled");
DependsOn = LookupByPathOrThrow("/flecs/core/DependsOn");
}
public EntityBuilder<TContext> New(EntityPath? path = null, Entity parent = default)
=> new(this, parent, path);
public void Dispose()
=> ecs_fini(this);
public Entity<TContext>? LookupAliveOrNull(Entity value)
=> ECS.Entity<TContext>.GetOrNull(this, Value.LookupAlive(value));
public Entity<TContext> LookupAliveOrThrow(Entity value)
=> LookupAliveOrNull(value) ?? throw new EntityNotFoundException(
$"Entity '{value}' could not be found");
public EntityBuilder New(EntityPath? path = null)
=> new(this, path);
public Entity<TContext>? LookupPathOrNull(EntityPath path, Entity parent = default)
=> ECS.Entity<TContext>.GetOrNull(this, Value.LookupPath(path, parent, false));
public Entity<TContext> LookupPathOrThrow(EntityPath path, Entity parent = default)
=> ECS.Entity<TContext>.GetOrInvalid(this, Value.LookupPath(path, parent, true));
public EntityBuilder New(EntityRef? parent, EntityPath? path = null)
{
var entity = New(path);
// If given path is absolute, the new entity won't be created as a
// child of the specified parent. Alternatively, EntityRef.NewChild
// can be used, which will throw when an absolute path is given.
if ((path?.IsRelative != false) && (parent != null))
entity.ChildOf(parent);
return entity;
}
public Entity<TContext>? LookupSymbolOrNull(string symbol)
=> ECS.Entity<TContext>.GetOrNull(this, Value.LookupSymbol(symbol));
public Entity<TContext> LookupSymbolOrThrow(string symbol)
=> LookupSymbolOrNull(symbol) ?? throw new EntityNotFoundException(
$"Entity with symbol '{symbol}' could not be found");
public bool Progress(TimeSpan delta)
=> ecs_progress(this, (float)delta.TotalSeconds);
public Entity<TContext> Entity<T>()
=> ECS.Entity<TContext>.GetOrThrow(this,
Lookup<TContext>.Entity<T>.Value);
public void Quit()
=> ecs_quit(this);
public Id<TContext> Pair(Entity relation, Entity target)
=> Id<TContext>.GetOrThrow(this, Id.Pair(relation, target));
public Id<TContext> Pair<TRelation>(Entity target)
=> Pair(Entity<TRelation>(), target);
public Id<TContext> Pair<TRelation, TTarget>()
=> Pair(Entity<TRelation>(), Entity<TTarget>());
public Iterator<TContext> Term(Term term) => Iterator<TContext>.FromTerm(this, term);
public Filter<TContext> Filter(FilterDesc desc) => new(this, desc);
public Query<TContext> Query(QueryDesc desc) => new(this, desc);
public Rule<TContext> Rule(FilterDesc desc) => new(this, desc);
public bool Equals(World<TContext> other) => Value == other.Value;
public override bool Equals(object? obj) => (obj is World<TContext> other) && Equals(other);
public override int GetHashCode() => Value.GetHashCode();
public static bool operator ==(World<TContext> left, World<TContext> right) => left.Equals(right);
public static bool operator !=(World<TContext> left, World<TContext> right) => !left.Equals(right);
public static implicit operator World (World<TContext> world) => world.Value;
public static implicit operator ecs_world_t*(World<TContext> world) => world.Value.Handle;
public static implicit operator ecs_world_t*(World w) => w.Handle;
}

@ -0,0 +1,182 @@
// <auto-generated>
// Generated by gaemstone.ECS.BindGen c661367
// Time: 2023-01-09 18:48:29Z
// Flecs version: v3.1.3
// </auto-generated>
#pragma warning disable CS8981
public static partial class flecs
{
public const uint ECS_ID_CACHE_SIZE = (32);
public const uint ECS_TERM_DESC_CACHE_SIZE = (16);
public const uint ECS_OBSERVER_DESC_EVENT_COUNT_MAX = (8);
public const uint ECS_VARIABLE_COUNT_MAX = (64);
public const uint EcsWorldQuitWorkers = (1u << 0);
public const uint EcsWorldReadonly = (1u << 1);
public const uint EcsWorldQuit = (1u << 2);
public const uint EcsWorldFini = (1u << 3);
public const uint EcsWorldMeasureFrameTime = (1u << 4);
public const uint EcsWorldMeasureSystemTime = (1u << 5);
public const uint EcsWorldMultiThreaded = (1u << 6);
public const uint EcsOsApiHighResolutionTimer = (1u << 0);
public const uint EcsOsApiLogWithColors = (1u << 1);
public const uint EcsOsApiLogWithTimeStamp = (1u << 2);
public const uint EcsOsApiLogWithTimeDelta = (1u << 3);
public const uint EcsEntityObserved = (1u << 31);
public const uint EcsEntityObservedId = (1u << 30);
public const uint EcsEntityObservedTarget = (1u << 29);
public const uint EcsEntityObservedAcyclic = (1u << 28);
public const uint EcsIdOnDeleteRemove = (1u << 0);
public const uint EcsIdOnDeleteDelete = (1u << 1);
public const uint EcsIdOnDeletePanic = (1u << 2);
public const uint EcsIdOnDeleteMask = (EcsIdOnDeletePanic|EcsIdOnDeleteRemove|EcsIdOnDeleteDelete);
public const uint EcsIdOnDeleteObjectRemove = (1u << 3);
public const uint EcsIdOnDeleteObjectDelete = (1u << 4);
public const uint EcsIdOnDeleteObjectPanic = (1u << 5);
public const uint EcsIdOnDeleteObjectMask = (EcsIdOnDeleteObjectPanic|EcsIdOnDeleteObjectRemove|EcsIdOnDeleteObjectDelete);
public const uint EcsIdExclusive = (1u << 6);
public const uint EcsIdDontInherit = (1u << 7);
public const uint EcsIdAcyclic = (1u << 8);
public const uint EcsIdTag = (1u << 9);
public const uint EcsIdWith = (1u << 10);
public const uint EcsIdUnion = (1u << 11);
public const uint EcsIdHasOnAdd = (1u << 15);
public const uint EcsIdHasOnRemove = (1u << 16);
public const uint EcsIdHasOnSet = (1u << 17);
public const uint EcsIdHasUnSet = (1u << 18);
public const uint EcsIdEventMask = (EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet|EcsIdHasUnSet);
public const uint EcsIdMarkedForDelete = (1u << 30);
public const uint EcsIterIsValid = (1u << 0u);
public const uint EcsIterIsFilter = (1u << 1u);
public const uint EcsIterIsInstanced = (1u << 2u);
public const uint EcsIterHasShared = (1u << 3u);
public const uint EcsIterTableOnly = (1u << 4u);
public const uint EcsIterEntityOptional = (1u << 5u);
public const uint EcsIterNoResults = (1u << 6u);
public const uint EcsIterIgnoreThis = (1u << 7u);
public const uint EcsIterMatchVar = (1u << 8u);
public const uint EcsEventTableOnly = (1u << 8u);
public const uint EcsEventNoOnSet = (1u << 16u);
public const uint EcsFilterMatchThis = (1u << 1u);
public const uint EcsFilterMatchOnlyThis = (1u << 2u);
public const uint EcsFilterMatchPrefab = (1u << 3u);
public const uint EcsFilterMatchDisabled = (1u << 4u);
public const uint EcsFilterMatchEmptyTables = (1u << 5u);
public const uint EcsFilterMatchAnything = (1u << 6u);
public const uint EcsFilterIsFilter = (1u << 7u);
public const uint EcsFilterIsInstanced = (1u << 8u);
public const uint EcsFilterPopulate = (1u << 9u);
public const uint EcsTableHasBuiltins = (1u << 1u);
public const uint EcsTableIsPrefab = (1u << 2u);
public const uint EcsTableHasIsA = (1u << 3u);
public const uint EcsTableHasChildOf = (1u << 4u);
public const uint EcsTableHasPairs = (1u << 5u);
public const uint EcsTableHasModule = (1u << 6u);
public const uint EcsTableIsDisabled = (1u << 7u);
public const uint EcsTableHasCtors = (1u << 8u);
public const uint EcsTableHasDtors = (1u << 9u);
public const uint EcsTableHasCopy = (1u << 10u);
public const uint EcsTableHasMove = (1u << 11u);
public const uint EcsTableHasUnion = (1u << 12u);
public const uint EcsTableHasToggle = (1u << 13u);
public const uint EcsTableHasOverrides = (1u << 14u);
public const uint EcsTableHasOnAdd = (1u << 15u);
public const uint EcsTableHasOnRemove = (1u << 16u);
public const uint EcsTableHasOnSet = (1u << 17u);
public const uint EcsTableHasUnSet = (1u << 18u);
public const uint EcsTableHasObserved = (1u << 20u);
public const uint EcsTableMarkedForDelete = (1u << 30u);
public const uint EcsTableHasLifecycle = (EcsTableHasCtors | EcsTableHasDtors);
public const uint EcsTableIsComplex = (EcsTableHasLifecycle | EcsTableHasUnion | EcsTableHasToggle);
public const uint EcsTableHasAddActions = (EcsTableHasIsA | EcsTableHasUnion | EcsTableHasCtors | EcsTableHasOnAdd | EcsTableHasOnSet);
public const uint EcsTableHasRemoveActions = (EcsTableHasIsA | EcsTableHasDtors | EcsTableHasOnRemove | EcsTableHasUnSet);
public const uint EcsQueryHasRefs = (1u << 1u);
public const uint EcsQueryIsSubquery = (1u << 2u);
public const uint EcsQueryIsOrphaned = (1u << 3u);
public const uint EcsQueryHasOutColumns = (1u << 4u);
public const uint EcsQueryHasMonitor = (1u << 5u);
public const uint EcsAperiodicEmptyTables = (1u << 1u);
public const uint EcsAperiodicComponentMonitors = (1u << 2u);
public const uint EcsAperiodicEmptyQueries = (1u << 4u);
public const uint ecs_world_t_magic = (0x65637377);
public const uint ecs_stage_t_magic = (0x65637373);
public const uint ecs_query_t_magic = (0x65637371);
public const uint ecs_rule_t_magic = (0x65637375);
public const uint ecs_table_t_magic = (0x65637374);
public const uint ecs_filter_t_magic = (0x65637366);
public const uint ecs_trigger_t_magic = (0x65637372);
public const uint ecs_observer_t_magic = (0x65637362);
public const uint ECS_ROW_MASK = (0x0FFFFFFFu);
public const uint ECS_ROW_FLAGS_MASK = (~ECS_ROW_MASK);
public const ulong ECS_ID_FLAGS_MASK = (0xFFul << 60);
public const ulong ECS_ENTITY_MASK = (0xFFFFFFFFul);
public const ulong ECS_GENERATION_MASK = (0xFFFFul << 32);
public const uint ECS_COMPONENT_MASK = (~ECS_ID_FLAGS_MASK);
public const uint FLECS_SPARSE_CHUNK_SIZE = (4096);
public const uint ECS_STRBUF_ELEMENT_SIZE = (511);
public const uint ECS_STRBUF_MAX_LIST_DEPTH = (32);
public const uint EcsSelf = (1u << 1);
public const uint EcsUp = (1u << 2);
public const uint EcsDown = (1u << 3);
public const uint EcsTraverseAll = (1u << 4);
public const uint EcsCascade = (1u << 5);
public const uint EcsParent = (1u << 6);
public const uint EcsIsVariable = (1u << 7);
public const uint EcsIsEntity = (1u << 8);
public const uint EcsFilter = (1u << 9);
public const uint EcsTraverseFlags = (EcsUp|EcsDown|EcsTraverseAll|EcsSelf|EcsCascade|EcsParent);
public const uint flecs_iter_cache_ids = (1u << 0u);
public const uint flecs_iter_cache_columns = (1u << 1u);
public const uint flecs_iter_cache_sources = (1u << 2u);
public const uint flecs_iter_cache_sizes = (1u << 3u);
public const uint flecs_iter_cache_ptrs = (1u << 4u);
public const uint flecs_iter_cache_match_indices = (1u << 5u);
public const uint flecs_iter_cache_variables = (1u << 6u);
public const uint flecs_iter_cache_all = (255);
public const uint ECS_HI_COMPONENT_ID = (256);
public const uint ECS_MAX_COMPONENT_ID = (~((uint)(ECS_ID_FLAGS_MASK >> 32)));
public const uint ECS_MAX_RECURSION = (512);
public const uint ECS_MAX_TOKEN_SIZE = (256);
public const ulong ECS_ID_FLAG_BIT = (1ul << 63);
public const uint EcsFirstUserComponentId = (32);
public const uint EcsFirstUserEntityId = (ECS_HI_COMPONENT_ID + 128);
public const uint ECS_INVALID_OPERATION = (1);
public const uint ECS_INVALID_PARAMETER = (2);
public const uint ECS_CONSTRAINT_VIOLATED = (3);
public const uint ECS_OUT_OF_MEMORY = (4);
public const uint ECS_OUT_OF_RANGE = (5);
public const uint ECS_UNSUPPORTED = (6);
public const uint ECS_INTERNAL_ERROR = (7);
public const uint ECS_ALREADY_DEFINED = (8);
public const uint ECS_MISSING_OS_API = (9);
public const uint ECS_OPERATION_FAILED = (10);
public const uint ECS_INVALID_CONVERSION = (11);
public const uint ECS_ID_IN_USE = (12);
public const uint ECS_CYCLE_DETECTED = (13);
public const uint ECS_LEAK_DETECTED = (14);
public const uint ECS_INCONSISTENT_NAME = (20);
public const uint ECS_NAME_IN_USE = (21);
public const uint ECS_NOT_A_COMPONENT = (22);
public const uint ECS_INVALID_COMPONENT_SIZE = (23);
public const uint ECS_INVALID_COMPONENT_ALIGNMENT = (24);
public const uint ECS_COMPONENT_NOT_REGISTERED = (25);
public const uint ECS_INCONSISTENT_COMPONENT_ID = (26);
public const uint ECS_INCONSISTENT_COMPONENT_ACTION = (27);
public const uint ECS_MODULE_UNDEFINED = (28);
public const uint ECS_MISSING_SYMBOL = (29);
public const uint ECS_ALREADY_IN_USE = (30);
public const uint ECS_ACCESS_VIOLATION = (40);
public const uint ECS_COLUMN_INDEX_OUT_OF_RANGE = (41);
public const uint ECS_COLUMN_IS_NOT_SHARED = (42);
public const uint ECS_COLUMN_IS_SHARED = (43);
public const uint ECS_COLUMN_TYPE_MISMATCH = (45);
public const uint ECS_INVALID_WHILE_READONLY = (70);
public const uint ECS_LOCKED_STORAGE = (71);
public const uint ECS_INVALID_FROM_WORKER = (72);
public const uint ECS_REST_DEFAULT_PORT = (27750);
public const uint ECS_STAT_WINDOW = (60);
public const uint ECS_MEMBER_DESC_CACHE_SIZE = (32);
public const uint ECS_META_MAX_SCOPE_DEPTH = (32);
public const uint ECS_HTTP_HEADER_COUNT_MAX = (32);
public const uint ECS_HTTP_QUERY_PARAM_COUNT_MAX = (32);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -8,11 +8,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Remove="src/flecs-cs/**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="src/flecs-cs/src/cs/production/Flecs/Flecs.csproj" />
<ProjectReference Include="../flecs-cs/src/cs/production/Flecs/Flecs.csproj" />
</ItemGroup>
</Project>
Loading…
Cancel
Save