Compare commits
2 Commits
main
...
wip/bindge
Author | SHA1 | Date |
---|---|---|
|
22dd934e95 | 2 years ago |
|
c6613679cc | 2 years ago |
46 changed files with 21688 additions and 1110 deletions
@ -1,3 +1,6 @@ |
|||||||
[submodule "src/flecs-cs"] |
[submodule "src/flecs-cs"] |
||||||
path = src/flecs-cs |
path = src/flecs-cs |
||||||
url = https://github.com/flecs-hub/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, |
||||||
|
} |
||||||
|
} |
@ -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" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -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("<", "<").Replace(">", ">"); |
||||||
|
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,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; |
||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using gaemstone.ECS.Internal; |
|
||||||
using static flecs_hub.flecs; |
using static flecs_hub.flecs; |
||||||
|
|
||||||
namespace gaemstone.ECS; |
namespace gaemstone.ECS; |
||||||
|
|
||||||
public unsafe readonly partial struct Entity<TContext> |
public readonly struct Entity |
||||||
: IEquatable<Entity<TContext>> |
: IEquatable<Entity> |
||||||
{ |
{ |
||||||
public static readonly Entity<TContext> None = default; |
public static readonly Entity None = default; |
||||||
|
|
||||||
public readonly World<TContext> World; |
public readonly ecs_entity_t Value; |
||||||
public readonly Entity Value; |
public uint Id => (uint)Value.Data; |
||||||
|
|
||||||
public uint NumericId => Value.NumericId; |
public bool IsSome => Value.Data != 0; |
||||||
public bool IsNone => Value.IsNone; |
public bool IsNone => Value.Data == 0; |
||||||
public bool IsSome => Value.IsSome; |
|
||||||
|
|
||||||
public bool IsValid => EntityAccess.IsValid(World, this); |
public Entity(ecs_entity_t value) => Value = value; |
||||||
public bool IsAlive => IsSome && EntityAccess.IsAlive(World, this); |
|
||||||
|
|
||||||
public string? Name { get => EntityAccess.GetName(World, this); set => EntityAccess.SetName(World, this, value); } |
public bool Equals(Entity other) => Value.Data == other.Value.Data; |
||||||
public string? Symbol { get => EntityAccess.GetSymbol(World, this); set => EntityAccess.SetSymbol(World, this, value); } |
public override bool Equals(object? obj) => (obj is Entity other) && Equals(other); |
||||||
|
public override int GetHashCode() => Value.Data.GetHashCode(); |
||||||
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 override string? ToString() |
public override string? ToString() |
||||||
=> Value.ToString(); |
=> IsSome ? $"Entity(0x{Value.Data.Data:X})" |
||||||
|
: "Entity.None"; |
||||||
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; |
|
||||||
|
|
||||||
public static implicit operator Id<TContext>(Entity<TContext> entity) => Id<TContext>.GetUnsafe(entity.World, entity); |
public static bool operator ==(Entity left, Entity right) => left.Equals(right); |
||||||
public static implicit operator Id (Entity<TContext> entity) => new(entity.Value.Value.Data); |
public static bool operator !=(Entity left, Entity right) => !left.Equals(right); |
||||||
public static implicit operator ecs_id_t (Entity<TContext> entity) => entity.Value.Value.Data; |
|
||||||
|
|
||||||
public static implicit operator Term (Entity<TContext> entity) => new(entity.Value); |
public static implicit operator ecs_entity_t(Entity e) => e.Value; |
||||||
public static implicit operator TermId(Entity<TContext> entity) => new(entity.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); |
||||||
|
} |
@ -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; |
||||||
|
} |
@ -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") { } |
|
||||||
} |
|
@ -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); |
|
||||||
} |
|
@ -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; |
||||||
using System.Collections; |
using System.Collections; |
||||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||||
using System.Linq; |
using System.Runtime.CompilerServices; |
||||||
using gaemstone.ECS.Internal; |
|
||||||
using gaemstone.ECS.Utility; |
using gaemstone.ECS.Utility; |
||||||
using static flecs_hub.flecs; |
using static flecs_hub.flecs; |
||||||
|
|
||||||
namespace gaemstone.ECS; |
namespace gaemstone.ECS; |
||||||
|
|
||||||
public unsafe class Iterator<TContext> : Iterator |
public unsafe class Iterator |
||||||
, IEnumerable<Iterator<TContext>> |
: 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 bool Completed { get; private set; } |
||||||
public Iterator(ecs_iter_t value) : base(value) { } |
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(); |
using var alloc = TempAllocator.Use(); |
||||||
var flecsTerm = term.ToFlecs(alloc); |
var flecsTerm = term.ToFlecs(alloc); |
||||||
var flecsIter = ecs_term_iter(world, &flecsTerm); |
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() |
public bool Next() |
||||||
=> Entity<TContext>.GetOrNull(World, base.First()); |
{ |
||||||
public new IEnumerable<Entity<TContext>> GetAllEntities() |
fixed (ecs_iter_t* ptr = &Value) { |
||||||
=> base.GetAllEntities().Select(e => Entity<TContext>.GetOrInvalid(World, e)); |
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) |
public bool FieldIsSet(int index) |
||||||
=> Id<TContext>.GetUnsafe(World, base.FieldId(index)); |
{ |
||||||
|
fixed (ecs_iter_t* ptr = &Value) |
||||||
|
return ecs_field_is_set(ptr, index); |
||||||
|
} |
||||||
|
|
||||||
public new Entity<TContext> Entity(int index) |
public bool FieldIs(int index, Id id) |
||||||
=> Entity<TContext>.GetOrInvalid(World, base.Entity(index)); |
{ |
||||||
|
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 |
// IEnumerable implementation |
||||||
public virtual IEnumerator<Iterator<TContext>> GetEnumerator() |
public IEnumerator<Iterator> GetEnumerator() { while (Next()) yield return this; } |
||||||
{ while (Next()) yield return this; } |
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
||||||
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 class Variable |
||||||
{ |
{ |
||||||
public int Index { get; } |
public int Index { get; } |
||||||
public string Name { get; } |
public string Name { get; } |
||||||
public Variable(int index, string name) |
public Variable(int index, string name) |
||||||
{ Index = index; Name = 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> |
public enum IteratorType |
||||||
: Iterator<TContext> |
|
||||||
{ |
{ |
||||||
internal TermIterator(ecs_iter_t value) |
Term, |
||||||
: base(value) { } |
Filter, |
||||||
public override bool Next() |
Query, |
||||||
=> ecs_term_next(Handle); |
Rule, |
||||||
} |
} |
||||||
|
@ -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(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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 System; |
||||||
using gaemstone.ECS.Internal; |
using gaemstone.ECS.Utility; |
||||||
using static flecs_hub.flecs; |
using static flecs_hub.flecs; |
||||||
|
|
||||||
namespace gaemstone.ECS; |
namespace gaemstone.ECS; |
||||||
|
|
||||||
public unsafe struct World<TContext> |
public unsafe partial class World |
||||||
: IEquatable<World<TContext>> |
|
||||||
, IDisposable |
|
||||||
{ |
{ |
||||||
public readonly World Value; |
public ecs_world_t* Handle { get; } |
||||||
|
|
||||||
public bool IsDeferred => Value.IsDeferred; |
// Flecs built-ins that are important to internals. |
||||||
public bool IsQuitRequested => Value.IsQuitRequested; |
internal EntityRef ChildOf { get; } |
||||||
|
internal EntityRef Disabled { get; } |
||||||
|
internal EntityRef DependsOn { get; } |
||||||
|
|
||||||
public World(World value) |
public bool IsDeferred => ecs_is_deferred(this); |
||||||
=> Value = value; |
public bool IsQuitRequested => ecs_should_quit(this); |
||||||
|
|
||||||
/// <summary> Initializes a new Flecs world. </summary> |
public World(params string[] args) |
||||||
/// <param name="minimal"> If true, doesn't automatically import built-in modules. </param> |
{ |
||||||
public World(bool minimal = false) |
ecs_os_set_api_defaults(); |
||||||
: this(new World(minimal)) { } |
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); |
Handle = ecs_init_w_args(args.Length, null); |
||||||
public void Quit() => Value.Quit(); |
|
||||||
public void Dispose() => Value.Dispose(); |
|
||||||
|
|
||||||
|
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) |
public void Dispose() |
||||||
=> new(this, parent, path); |
=> ecs_fini(this); |
||||||
|
|
||||||
|
|
||||||
public Entity<TContext>? LookupAliveOrNull(Entity value) |
public EntityBuilder New(EntityPath? path = null) |
||||||
=> ECS.Entity<TContext>.GetOrNull(this, Value.LookupAlive(value)); |
=> new(this, path); |
||||||
public Entity<TContext> LookupAliveOrThrow(Entity value) |
|
||||||
=> LookupAliveOrNull(value) ?? throw new EntityNotFoundException( |
|
||||||
$"Entity '{value}' could not be found"); |
|
||||||
|
|
||||||
public Entity<TContext>? LookupPathOrNull(EntityPath path, Entity parent = default) |
public EntityBuilder New(EntityRef? parent, EntityPath? path = null) |
||||||
=> ECS.Entity<TContext>.GetOrNull(this, Value.LookupPath(path, parent, false)); |
{ |
||||||
public Entity<TContext> LookupPathOrThrow(EntityPath path, Entity parent = default) |
var entity = New(path); |
||||||
=> ECS.Entity<TContext>.GetOrInvalid(this, Value.LookupPath(path, parent, true)); |
// 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>() |
public void Quit() |
||||||
=> ECS.Entity<TContext>.GetOrThrow(this, |
=> ecs_quit(this); |
||||||
Lookup<TContext>.Entity<T>.Value); |
|
||||||
|
|
||||||
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 static implicit operator ecs_world_t*(World w) => w.Handle; |
||||||
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; |
|
||||||
} |
} |
||||||
|
@ -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
Loading…
Reference in new issue