Compare commits

..

9 Commits

Author SHA1 Message Date
copygirl 6b78cb2985 Refactor namespaces 1 year ago
copygirl 47530de27e Add attributes for entity documentation 1 year ago
copygirl 902ff21e48 Update README 1 year ago
copygirl a27800150a Add README 1 year ago
copygirl 330c11f217 Initialize doc color of display types 1 year ago
copygirl 50c3ccd014 Fix IModuleLifetime not working 1 year ago
copygirl 5da1f9c937 Update gaemstone.ECS to fix compile errors 1 year ago
copygirl 6b92e1ce8c Refactor ModuleManager and built-in modules 1 year ago
copygirl d80e69006c Remove Universe constructor using args 1 year ago
  1. 38
      README.md
  2. BIN
      docs/2023-05-16_Immersion.png
  3. 1
      src/Immersion/ManagedComponentTest.cs
  4. 4
      src/Immersion/ObserverTest.cs
  5. 16
      src/Immersion/Program.cs
  6. 2
      src/gaemstone.Bloxel/Client/Systems/ChunkMeshGenerator.cs
  7. 163
      src/gaemstone.Client/Systems/EntityInspector.cs
  8. 13
      src/gaemstone.Client/Systems/ImGuiManager.cs
  9. 24
      src/gaemstone.Client/Systems/InputManager.cs
  10. 11
      src/gaemstone.Client/Systems/Renderer.cs
  11. 3
      src/gaemstone.Client/Systems/TextureManager.cs
  12. 4
      src/gaemstone.Client/Systems/Windowing.cs
  13. 1622
      src/gaemstone.Client/Utility/ForkAwesome.cs
  14. 5
      src/gaemstone.Client/Utility/ImGuiUtility.cs
  15. 2
      src/gaemstone.ECS
  16. 37
      src/gaemstone.SourceGen/ModuleGenerator.cs
  17. 23
      src/gaemstone.SourceGen/RelevantSymbolReceiver.cs
  18. 13
      src/gaemstone.SourceGen/Structure/BaseEntityInfo.cs
  19. 4
      src/gaemstone.SourceGen/Structure/MethodEntityInfo.cs
  20. 8
      src/gaemstone.SourceGen/Structure/ModuleEntityInfo.cs
  21. 12
      src/gaemstone.SourceGen/Structure/ParameterInfo.cs
  22. 20
      src/gaemstone.SourceGen/Utility/SymbolExtensions.cs
  23. 66
      src/gaemstone/Doc+Attributes.cs
  24. 49
      src/gaemstone/Doc+Extensions.cs
  25. 101
      src/gaemstone/Doc.cs
  26. 15
      src/gaemstone/Flecs/Core.cs
  27. 28
      src/gaemstone/Flecs/CoreDoc.cs
  28. 57
      src/gaemstone/Flecs/Doc.cs
  29. 26
      src/gaemstone/Flecs/Meta.cs
  30. 29
      src/gaemstone/Flecs/Metrics.cs
  31. 26
      src/gaemstone/Flecs/Monitor.cs
  32. 25
      src/gaemstone/Flecs/Pipeline.cs
  33. 34
      src/gaemstone/Flecs/Rest.cs
  34. 27
      src/gaemstone/Flecs/Script.cs
  35. 29
      src/gaemstone/Flecs/System.cs
  36. 23
      src/gaemstone/Flecs/Systems/Monitor.cs
  37. 27
      src/gaemstone/Flecs/Systems/Rest.cs
  38. 27
      src/gaemstone/Flecs/Timer.cs
  39. 26
      src/gaemstone/Flecs/Units.cs
  40. 22
      src/gaemstone/Module+Attributes.cs
  41. 2
      src/gaemstone/Module+Components.cs
  42. 25
      src/gaemstone/Module.cs
  43. 2
      src/gaemstone/System+Terms.cs
  44. 2
      src/gaemstone/System.cs
  45. 168
      src/gaemstone/Universe+Modules.cs
  46. 17
      src/gaemstone/Universe.cs

@ -0,0 +1,38 @@
# gæmstone Game Engine
.. is meant to (eventually) function as a foundation for ultra-moddable games with runtime inspection and modification features, that allow fast iteration in game development, basically *while* actively playing your games.
This is a custom game engine written from scratch in C#, using modern .NET, targeting cross-platform development and deployment. It's making heavy use of [Entity Component System (ECS)][ECS] design, made possible with the powerful and versatile ECS library [Flecs], using the [flecs-cs] bindings on top of which lives [gaemstone.ECS] – our own "medium-level" wrapper.
The name *gæmstone* (also written "gaemstone") is supposed to be pronounced /ɡɛmstoʊn/ – like "g**em**" but with the G from "**g**ame". As we all know, fancy project names and their weird pronounciations (looking at you, *Godot Engine*) is of utmost importance.
[ECS]: https://en.wikipedia.org/wiki/Entity_component_system
[Flecs]: https://github.com/SanderMertens/flecs
[flecs-cs]: https://github.com/flecs-hub/flecs-cs
[gaemstone.ECS]: https://git.mcft.net/copygirl/gaemstone.ECS
## Goals, aka. Future Features
Core features, some of which have yet to be written, include:
- Incredibly modular, in part due to ECS design
- Tools to modify entities, components and their relations in-game
- Hot-reloading of assets and systems (which run game logic)
- Safety will (hopefully) be achieved by compiling modules from source
- Multiplayer support, including co-op development
- Module for creating "bloxel" (Minecraft-like) games
## Showcase
![Screenshot of "Immersion" from 2023-05-16.](./docs/2023-05-16_Immersion.png)
Screenshot of the "Immersion" test project, which is included in this repository, showing basic functionality and the built-in but work-in-progress entity inspector and input debug window.
## Motivation
A lot of time nowadays is spent either re-inventing the wheel (often due to copyright), or waiting for changes you've made to be ready for testing or playing (compiling, pushing changes to servers and players, restarting, reconnecting, ...), and gæmstone is supposed to provide an environment where this is not a problem.
With this engine and eventually the games I plan to create, I'm hoping to create a community in which sharing and remixing work – both assets and code – is the norm. No more saying goodbye to outdated mods. No more incompatibilities because an author didn't decide to support something. Need to adjust anything for your version of the game, or your server? Go ahead.
As such, I have not yet decided whether to pick a permissible license like MIT, or a [copyleft] one like GPL, to hopefully encourage this kind of sharing. However, I could leave that for my games. Feel free to share your thoughts, since as of now I'm the only consumer of the engine.
[copyleft]: https://en.wikipedia.org/wiki/Copyleft

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 KiB

@ -1,4 +1,5 @@
using System; using System;
using gaemstone;
using gaemstone.ECS; using gaemstone.ECS;
using gaemstone.ECS.Utility; using gaemstone.ECS.Utility;

@ -1,7 +1,7 @@
using System; using System;
using gaemstone.ECS; using gaemstone;
using static flecs.core;
using static gaemstone.Bloxel.Components.CoreComponents; using static gaemstone.Bloxel.Components.CoreComponents;
using static gaemstone.Flecs.Core;
namespace Immersion; namespace Immersion;

@ -23,11 +23,15 @@ var universe = new Universe<Program>();
var world = universe.World; var world = universe.World;
// TODO: Figure out a nice way to get rid of "compile errors" here. // TODO: Figure out a nice way to get rid of "compile errors" here.
// FIXME: universe.Modules.Register<gaemstone.Flecs.Systems.Monitor>(); universe.Modules.Import<flecs.timer>();
universe.Modules.Register<gaemstone.Flecs.Systems.Rest>(); universe.Modules.Import<flecs.script>();
universe.Modules.Import<flecs.rest>();
universe.Modules.Import<flecs.monitor>();
universe.Modules.Import<flecs.units>();
universe.Modules.Import<flecs.metrics>();
var window = Window.Create(WindowOptions.Default with { var window = Window.Create(WindowOptions.Default with {
Title = "gæmstone", Title = "gæmstone - Immersion",
Size = new(1280, 720), Size = new(1280, 720),
PreferredDepthBufferBits = 24, PreferredDepthBufferBits = 24,
}); });
@ -57,9 +61,9 @@ universe.Modules.Register<gaemstone.Client.Systems.EntityInspector>();
universe.Modules.Register<gaemstone.Client.Components.CameraComponents>(); universe.Modules.Register<gaemstone.Client.Components.CameraComponents>();
universe.Modules.Register<gaemstone.Client.Systems.FreeCameraController>(); universe.Modules.Register<gaemstone.Client.Systems.FreeCameraController>();
foreach (var module in universe.Modules) using (var disabledModules = world.Filter(new("ModuleInfo, Disabled")))
if (!module.IsInitialized) throw new InvalidOperationException( foreach (var module in disabledModules.Iter().GetAllEntities())
$"Module '{module.Entity.Path}' is not initialized"); throw new InvalidOperationException($"Module '{module.Path}' is not ednbled");
// Initialize Canvas and GameWindow singletons with actual values. // Initialize Canvas and GameWindow singletons with actual values.
world.Entity<Canvas>().Set(new Canvas(Silk.NET.OpenGL.ContextSourceExtensions.CreateOpenGL(window))); world.Entity<Canvas>().Set(new Canvas(Silk.NET.OpenGL.ContextSourceExtensions.CreateOpenGL(window)));

@ -43,7 +43,7 @@ public partial class ChunkMeshGenerator
public static void GenerateChunkMeshes<T>( public static void GenerateChunkMeshes<T>(
World<T> world, Canvas canvas, World<T> world, Canvas canvas,
Entity<T> entity, in Chunk chunk, ChunkStoreBlocks blocks, Entity<T> entity, in Chunk chunk, ChunkStoreBlocks blocks,
Has<BasicWorldGenerationDone> _1, Not<Mesh, Flecs.Core.Wildcard> _2) Has<BasicWorldGenerationDone> _1, Not<Mesh, flecs.core.Wildcard> _2)
{ {
var result = Generate(world, canvas.GL, chunk.Position, blocks); var result = Generate(world, canvas.GL, chunk.Position, blocks);
if (result is MeshHandle handle) { if (result is MeshHandle handle) {

@ -4,10 +4,8 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using gaemstone.Client.Utility; using gaemstone.Client.Utility;
using gaemstone.ECS; using gaemstone.ECS;
using gaemstone.Flecs;
using ImGuiNET; using ImGuiNET;
using static gaemstone.Client.Systems.ImGuiManager; using static gaemstone.Client.Systems.ImGuiManager;
using Icon = gaemstone.Client.Utility.ForkAwesome;
using ImGuiInternal = ImGuiNET.Internal.ImGui; using ImGuiInternal = ImGuiNET.Internal.ImGui;
namespace gaemstone.Client.Systems; namespace gaemstone.Client.Systems;
@ -15,13 +13,13 @@ namespace gaemstone.Client.Systems;
[Module] [Module]
[DependsOn<gaemstone.Client.Systems.ImGuiManager>] [DependsOn<gaemstone.Client.Systems.ImGuiManager>]
public partial class EntityInspector public partial class EntityInspector
: IModuleInitializer : IModuleLifetime
{ {
[Tag] [Tag]
public struct InspectorWindow { } public struct InspectorWindow { }
[Relation, Exclusive] [Relation, Exclusive]
[Add<Core.OnDeleteTarget, Core.Delete>] [Add<flecs.core.OnDeleteTarget, flecs.core.Delete>]
public struct Selected { } public struct Selected { }
[Tag] [Tag]
@ -54,43 +52,68 @@ public partial class EntityInspector
} }
[Component] [Component]
public struct DocPriority { public float Value; } public record struct Priority(float Value);
[Component] [Component]
public struct DocIcon { public char Value; } public record struct Icon(char Value);
[Path("/flecs/core/Module")]
[Add<Doc.DisplayType>, Set<Priority>(0), Set<Icon>(ForkAwesome.Archive)]
public struct Module { }
[Path("/flecs/system/System")]
[Add<Doc.DisplayType>, Set<Priority>(1), Set<Icon>(ForkAwesome.Cog)]
public struct System { }
[Path("/flecs/core/Observer")]
[Add<Doc.DisplayType>, Set<Priority>(2), Set<Icon>(ForkAwesome.Eye)]
public struct Observer { }
[Path("/gaemstone/Doc/Relation")]
[Add<Doc.DisplayType>, Set<Priority>(3), Set<Icon>(ForkAwesome.ShareAlt)]
public struct Relation { }
[Path("/flecs/core/Component")]
[Add<Doc.DisplayType>, Set<Priority>(4), Set<Icon>(ForkAwesome.PencilSquare)]
public struct Component { }
[Path("/gaemstone/Doc/Tag")]
[Add<Doc.DisplayType>, Set<Priority>(5), Set<Icon>(ForkAwesome.Tag)]
public struct Tag { }
[Path("/flecs/core/Prefab")]
[Add<Doc.DisplayType>, Set<Priority>(6), Set<Icon>(ForkAwesome.Cube)]
public struct Prefab { }
private const string DefaultWindowTitle = "Inspector Gadget"; private const string DefaultWindowTitle = "Inspector Gadget";
public static void Initialize<T>(Entity<T> module)
{
void SetDocInfo(string path, float priority, string icon, float r, float g, float b)
=> module.World.LookupPathOrThrow(path)
.Add<Doc.DisplayType>()
.Set(new DocPriority { Value = priority })
.Set(new DocIcon { Value = icon[0] })
.SetDocColor(Color.FromRGB(r, g, b).ToHexString());
SetDocInfo("/flecs/core/Module" , 0 , Icon.Archive , 1.0f, 0.9f, 0.7f); public static void OnEnable<T>(Entity<T> module)
SetDocInfo("/flecs/system/System" , 1 , Icon.Cog , 1.0f, 0.7f, 0.7f); {
SetDocInfo("/flecs/core/Observer" , 2 , Icon.Eye , 1.0f, 0.8f, 0.8f); var world = module.World;
SetDocInfo("/gaemstone/Doc/Relation" , 3 , Icon.ShareAlt , 0.7f, 1.0f, 0.8f); world.LookupPathOrNull("/flecs/core/Module")?.SetDocColor("#FFE4B2");
SetDocInfo("/flecs/core/Component" , 4 , Icon.PencilSquare , 0.6f, 0.6f, 1.0f); world.LookupPathOrNull("/flecs/system/System")?.SetDocColor("#FFB2B2");
// TODO: Handle tags like Flecs does. world.LookupPathOrNull("/flecs/core/Observer")?.SetDocColor("#FFCCCC");
SetDocInfo("/flecs/core/Tag" , 5 , Icon.Tag , 0.7f, 0.8f, 1.0f); world.LookupPathOrNull("/gaemstone/Doc/Relation")?.SetDocColor("#B2FFCC");
SetDocInfo("/flecs/core/Prefab" , 6 , Icon.Cube , 0.9f, 0.8f, 1.0f); world.LookupPathOrNull("/flecs/core/Component")?.SetDocColor("#9999FF");
world.LookupPathOrNull("/gaemstone/Doc/Tag")?.SetDocColor("#B2CCFF");
world.LookupPathOrNull("/flecs/core/Prefab")?.SetDocColor("#E4CCFF");
} }
public static void OnDisable<T>(Entity<T> module) { }
[System] [System]
public static void ShowUIButton<T>(World<T> world, ImGuiData _) public static void ShowUIButton<T>(World<T> world, ImGuiData _)
{ {
var hasAnyInspector = false; var hasAnyInspector = false;
var inspectorWindow = world.Entity<InspectorWindow>(); var inspectorWindow = world.Entity<InspectorWindow>();
foreach (var entity in Iterator<T>.FromTerm(world, new(inspectorWindow))) foreach (var entity in world.Term(new(inspectorWindow)))
{ hasAnyInspector = true; break; } { hasAnyInspector = true; break; }
if (ImGuiUtility.UIButton(0, Icon.Search, DefaultWindowTitle, hasAnyInspector)) if (ImGuiUtility.UIButton(0, ForkAwesome.Search, DefaultWindowTitle, hasAnyInspector))
NewEntityInspectorWindow(world); NewEntityInspectorWindow(world);
} }
@ -104,7 +127,7 @@ public partial class EntityInspector
ImGui.SetNextWindowSize(new(fontSize * 40, fontSize * 25), ImGuiCond.Appearing); ImGui.SetNextWindowSize(new(fontSize * 40, fontSize * 25), ImGuiCond.Appearing);
ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[1]); ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[1]);
var title = window.GetDocName() ?? DefaultWindowTitle; var title = window.GetDocName() ?? DefaultWindowTitle;
if (ImGui.Begin($"{Icon.Search} {title}###{window.NumericId}", if (ImGui.Begin($"{ForkAwesome.Search} {title}###{window.NumericId}",
ref isOpen, ImGuiWindowFlags.NoScrollbar)) { ref isOpen, ImGuiWindowFlags.NoScrollbar)) {
ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[0]); ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[0]);
@ -132,9 +155,9 @@ public partial class EntityInspector
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.BeginChild("EntityView", new(-float.Epsilon, -float.Epsilon)); ImGui.BeginChild("EntityView", new(-float.Epsilon, -float.Epsilon));
if (!ImGui.BeginTabBar("Tabs")) return; if (!ImGui.BeginTabBar("Tabs")) return;
Tab($"{Icon.PencilSquare} Components", ComponentsTab); Tab($"{ForkAwesome.PencilSquare} Components", ComponentsTab);
Tab($"{Icon.ShareAlt} References", ReferencesTab); Tab($"{ForkAwesome.ShareAlt} References", ReferencesTab);
Tab($"{Icon.InfoCircle} Documentation", DocumentationTab); Tab($"{ForkAwesome.InfoCircle} Documentation", DocumentationTab);
ImGui.EndTabBar(); ImGui.EndTabBar();
ImGui.EndChild(); ImGui.EndChild();
@ -149,7 +172,7 @@ public partial class EntityInspector
if (!isOpen) window.Delete(); if (!isOpen) window.Delete();
} }
[Observer<Core.OnRemove>] [Observer<flecs.core.OnRemove>]
public static void ClearStorageOnRemove<T>(Entity<T> _1, InspectorWindow _2) public static void ClearStorageOnRemove<T>(Entity<T> _1, InspectorWindow _2)
{ {
// TODO: Clear out settings store for the window. // TODO: Clear out settings store for the window.
@ -157,9 +180,9 @@ public partial class EntityInspector
private static void ActionBarAndPath<T>(Entity<T> window, History? history, Entity<T>? selected) private static void ActionBarAndPath<T>(Entity<T> window, History? history, Entity<T>? selected)
{ {
static bool IconButtonWithToolTip(string icon, string tooltip, bool enabled = true) { static bool IconButtonWithToolTip(char icon, string tooltip, bool enabled = true) {
if (!enabled) ImGui.BeginDisabled(); if (!enabled) ImGui.BeginDisabled();
var clicked = ImGui.Button(icon); var clicked = ImGui.Button(icon.ToString());
if (!enabled) ImGui.EndDisabled(); if (!enabled) ImGui.EndDisabled();
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip(tooltip); ImGui.SetTooltip(tooltip);
@ -173,23 +196,23 @@ public partial class EntityInspector
ImGui.TableSetupColumn("Entity", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Entity", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var hasExpanded = window.Has<Expanded, Core.Wildcard>(); var hasExpanded = window.Has<Expanded, flecs.core.Wildcard>();
if (IconButtonWithToolTip(Icon.Outdent, "Collapse all items in the Explorer View", hasExpanded)) if (IconButtonWithToolTip(ForkAwesome.Outdent, "Collapse all items in the Explorer View", hasExpanded))
window.Remove<Expanded, Core.Wildcard>(); window.Remove<Expanded, flecs.core.Wildcard>();
if (history != null) { if (history != null) {
var hasPrev = ((selected != null) ? history.Current?.Prev : history.Current) != null; var hasPrev = ((selected != null) ? history.Current?.Prev : history.Current) != null;
var hasNext = history.Current?.Next != null; var hasNext = history.Current?.Next != null;
ImGui.SameLine(); ImGui.SameLine();
if (IconButtonWithToolTip(Icon.ArrowLeft, "Go to the previously viewed entity", hasPrev)) if (IconButtonWithToolTip(ForkAwesome.ArrowLeft, "Go to the previously viewed entity", hasPrev))
GoToPrevious(window, history, selected); GoToPrevious(window, history, selected);
ImGui.SameLine(); ImGui.SameLine();
if (IconButtonWithToolTip(Icon.ArrowRight, "Go to the next viewed entity", hasNext)) if (IconButtonWithToolTip(ForkAwesome.ArrowRight, "Go to the next viewed entity", hasNext))
GoToNext(window, history); GoToNext(window, history);
} }
ImGui.SameLine(); ImGui.SameLine();
if (IconButtonWithToolTip(Icon.Crosshairs, "Scroll to the current entity in the Explorer View", (selected != null))) if (IconButtonWithToolTip(ForkAwesome.Crosshairs, "Scroll to the current entity in the Explorer View", (selected != null)))
window.Add<ScrollToSelected>(); window.Add<ScrollToSelected>();
ImGui.TableNextColumn(); ImGui.TableNextColumn();
@ -197,22 +220,22 @@ public partial class EntityInspector
PathInput(window, history, selected, availableWidth); PathInput(window, history, selected, availableWidth);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (IconButtonWithToolTip(Icon.PlusCircle, "Create a new child entity", (selected != null))) if (IconButtonWithToolTip(ForkAwesome.PlusCircle, "Create a new child entity", (selected != null)))
SetSelected(window, history, selected?.NewChild().Build()); SetSelected(window, history, selected?.NewChild().Build());
ImGui.SameLine(); ImGui.SameLine();
if (IconButtonWithToolTip(Icon.Pencil, "Rename the current entity", false && (selected != null))) if (IconButtonWithToolTip(ForkAwesome.Pencil, "Rename the current entity", false && (selected != null)))
{ } // TODO: Implement this! { } // TODO: Implement this!
ImGui.SameLine(); ImGui.SameLine();
var isDisabled = (selected?.IsDisabled == true); var isDisabled = (selected?.IsDisabled == true);
var icon = !isDisabled ? Icon.BellSlash : Icon.Bell; var icon = !isDisabled ? ForkAwesome.BellSlash : ForkAwesome.Bell;
var tooltip = $"{(!isDisabled ? "Disable" : "Enable")} the current entity"; var tooltip = $"{(!isDisabled ? "Disable" : "Enable")} the current entity";
if (IconButtonWithToolTip(icon, tooltip, (selected != null))) if (IconButtonWithToolTip(icon, tooltip, (selected != null)))
{ if (isDisabled) selected?.Enable(); else selected?.Disable(); } { if (isDisabled) selected?.Enable(); else selected?.Disable(); }
ImGui.SameLine(); ImGui.SameLine();
if (IconButtonWithToolTip(Icon.Trash, "Delete the current entity", (selected != null))) { if (IconButtonWithToolTip(ForkAwesome.Trash, "Delete the current entity", (selected != null))) {
// TODO: Delete history for deleted entity? // TODO: Delete history for deleted entity?
SetSelected(window, history, selected?.Parent); SetSelected(window, history, selected?.Parent);
selected?.Delete(); // TODO: Confirmation dialog? selected?.Delete(); // TODO: Confirmation dialog?
@ -350,10 +373,10 @@ public partial class EntityInspector
// nullable, so let's be explicit about the type here. // nullable, so let's be explicit about the type here.
var world = window.World; var world = window.World;
var Wildcard = world.Entity<Core.Wildcard>().Value; var Wildcard = world.Entity<flecs.core.Wildcard>().Value;
var Any = world.Entity<Core.Any>().Value; var Any = world.Entity<flecs.core.Any>().Value;
var This = world.Entity<Core.This>().Value; var This = world.Entity<flecs.core.This>().Value;
var Variable = world.Entity<Core.Variable>().Value; var Variable = world.Entity<flecs.core.Variable>().Value;
bool IsSpecialEntity(Entity entity) bool IsSpecialEntity(Entity entity)
=> (entity == Wildcard) || (entity == Any) => (entity == Wildcard) || (entity == Any)
|| (entity == This) || (entity == Variable); || (entity == This) || (entity == Variable);
@ -361,9 +384,9 @@ public partial class EntityInspector
var expId = world.Entity<Expanded>().NumericId; var expId = world.Entity<Expanded>().NumericId;
List<IExplorerEntry> GetEntries(Entity? parent) { List<IExplorerEntry> GetEntries(Entity? parent) {
var result = new List<IExplorerEntry>(); var result = new List<IExplorerEntry>();
using var rule = new Rule<T>(world, new( using var rule = world.Rule(new(
$"(ChildOf, {parent?.NumericId ?? 0})" // Must be child of parent, or root entity. $"(ChildOf, {parent?.NumericId ?? 0})" // Must be child of parent, or root entity.
+ $",?{expId}({window.NumericId}, $This)" // Whether entity is expanded in explorer view. + $",?{expId}({window.NumericId}, $this)" // Whether entity is expanded in explorer view.
+ $",?Disabled" // Don't filter out disabled entities. + $",?Disabled" // Don't filter out disabled entities.
)); ));
foreach (var iter in rule.Iter()) { foreach (var iter in rule.Iter()) {
@ -372,7 +395,7 @@ public partial class EntityInspector
for (var i = 0; i < iter.Count; i++) { for (var i = 0; i < iter.Count; i++) {
var entity = iter.Entity(i); var entity = iter.Entity(i);
var count = IsSpecialEntity(entity) ? 0 var count = IsSpecialEntity(entity) ? 0
: world.Pair<Core.ChildOf>(entity).Count; : world.Pair<flecs.core.ChildOf>(entity).Count;
result.Add(new ExplorerEntry<T>(entity, count, isExpanded, isDisabled)); result.Add(new ExplorerEntry<T>(entity, count, isExpanded, isDisabled));
} }
} }
@ -456,7 +479,7 @@ public partial class EntityInspector
private static void ComponentsTab<T>(Entity<T> window, History? history, Entity<T>? sel) private static void ComponentsTab<T>(Entity<T> window, History? history, Entity<T>? sel)
{ {
if (sel is not Entity<T> selected) return; if (sel is not Entity<T> selected) return;
var ChildOf = window.World.Entity<Core.ChildOf>(); var ChildOf = window.World.Entity<flecs.core.ChildOf>();
foreach (var id in selected.Type) { foreach (var id in selected.Type) {
// Hide ChildOf relations, as they are visible in the explorer. // Hide ChildOf relations, as they are visible in the explorer.
if (id.IsPair && (id.Value.RelationUnsafe == ChildOf)) continue; if (id.IsPair && (id.Value.RelationUnsafe == ChildOf)) continue;
@ -468,16 +491,16 @@ public partial class EntityInspector
{ {
if (sel is not Entity<T> selected) return; if (sel is not Entity<T> selected) return;
var world = window.World; var world = window.World;
var ChildOf = world.Entity<Core.ChildOf>(); var ChildOf = world.Entity<flecs.core.ChildOf>();
var Wildcard = world.Entity<Core.Wildcard>(); var Wildcard = world.Entity<flecs.core.Wildcard>();
if (ImGui.CollapsingHeader($"As {Icon.Tag} Component", ImGuiTreeNodeFlags.DefaultOpen)) if (ImGui.CollapsingHeader($"As {ForkAwesome.Tag} Component", ImGuiTreeNodeFlags.DefaultOpen))
foreach (var iter in Iterator<T>.FromTerm(world, new(selected))) foreach (var iter in world.Term(new(selected)))
for (var i = 0; i < iter.Count; i++) for (var i = 0; i < iter.Count; i++)
RenderEntity(window, history, iter.Entity(i)); RenderEntity(window, history, iter.Entity(i));
if (ImGui.CollapsingHeader($"As {Icon.ShareAlt} Relation", ImGuiTreeNodeFlags.DefaultOpen)) if (ImGui.CollapsingHeader($"As {ForkAwesome.ShareAlt} Relation", ImGuiTreeNodeFlags.DefaultOpen))
foreach (var iter in Iterator<T>.FromTerm(world, new(selected, Wildcard))) { foreach (var iter in world.Term(new(selected, Wildcard))) {
var id = iter.FieldId(1); var id = iter.FieldId(1);
if (id.AsPair() is not (Entity<T> relation, Entity<T> target)) throw new InvalidOperationException(); if (id.AsPair() is not (Entity<T> relation, Entity<T> target)) throw new InvalidOperationException();
if (relation == ChildOf) continue; // Hide ChildOf relations. if (relation == ChildOf) continue; // Hide ChildOf relations.
@ -489,8 +512,8 @@ public partial class EntityInspector
} }
} }
if (ImGui.CollapsingHeader($"As {Icon.Bullseye} Target", ImGuiTreeNodeFlags.DefaultOpen)) if (ImGui.CollapsingHeader($"As {ForkAwesome.Bullseye} Target", ImGuiTreeNodeFlags.DefaultOpen))
foreach (var iter in Iterator<T>.FromTerm(world, new(Wildcard, selected))) { foreach (var iter in world.Term(new(Wildcard, selected))) {
var id = iter.FieldId(1); var id = iter.FieldId(1);
if (id.AsPair() is not (Entity<T> relation, Entity<T> target)) throw new InvalidOperationException(); if (id.AsPair() is not (Entity<T> relation, Entity<T> target)) throw new InvalidOperationException();
if (relation == ChildOf) continue; // Hide ChildOf relations. if (relation == ChildOf) continue; // Hide ChildOf relations.
@ -521,7 +544,7 @@ public partial class EntityInspector
if (fill) ImGui.SetNextItemWidth(-float.Epsilon); if (fill) ImGui.SetNextItemWidth(-float.Epsilon);
} }
Column($"{Icon.Tag} Display Name", """ Column($"{ForkAwesome.Tag} Display Name", """
A display name for this entity. A display name for this entity.
Names in the entity hierarchy must be unique within the parent entity, Names in the entity hierarchy must be unique within the parent entity,
This doesn't apply to display names - they are mostly informational. This doesn't apply to display names - they are mostly informational.
@ -532,7 +555,7 @@ public partial class EntityInspector
selected?.SetDocName((name.Length > 0) ? name : null); selected?.SetDocName((name.Length > 0) ? name : null);
if (!hasSelected) ImGui.EndDisabled(); if (!hasSelected) ImGui.EndDisabled();
Column($"{Icon.Comment} Description", Column($"{ForkAwesome.Comment} Description",
"A brief description of this entity."); "A brief description of this entity.");
if (!hasSelected) ImGui.BeginDisabled(); if (!hasSelected) ImGui.BeginDisabled();
var brief = selected?.GetDocBrief() ?? ""; var brief = selected?.GetDocBrief() ?? "";
@ -540,7 +563,7 @@ public partial class EntityInspector
selected?.SetDocBrief((brief.Length > 0) ? brief : null); selected?.SetDocBrief((brief.Length > 0) ? brief : null);
if (!hasSelected) ImGui.EndDisabled(); if (!hasSelected) ImGui.EndDisabled();
Column($"{Icon.FileText} Documentation", """ Column($"{ForkAwesome.FileText} Documentation", """
A detailed description, or full documentation, of this entity's purpose and behaviors. A detailed description, or full documentation, of this entity's purpose and behaviors.
It's encouraged to use multiple paragraphs and markdown formatting if necessary. It's encouraged to use multiple paragraphs and markdown formatting if necessary.
"""); """);
@ -556,7 +579,7 @@ public partial class EntityInspector
selected?.SetDocDetail((detail.Length > 0) ? detail : null); selected?.SetDocDetail((detail.Length > 0) ? detail : null);
if (!hasSelected) ImGui.EndDisabled(); if (!hasSelected) ImGui.EndDisabled();
Column($"{Icon.Link} Link", """ Column($"{ForkAwesome.Link} Link", """
A link to a website relating to this entity, such as A link to a website relating to this entity, such as
a module's repository, or further documentation. a module's repository, or further documentation.
"""); """);
@ -566,7 +589,7 @@ public partial class EntityInspector
selected?.SetDocLink((link.Length > 0) ? link : null); selected?.SetDocLink((link.Length > 0) ? link : null);
if (!hasSelected) ImGui.EndDisabled(); if (!hasSelected) ImGui.EndDisabled();
Column($"{Icon.PaintBrush} Color", """ Column($"{ForkAwesome.PaintBrush} Color", """
A custom color to represent this entity. A custom color to represent this entity.
Used in the entity inspector's explorer view. Used in the entity inspector's explorer view.
""", false); """, false);
@ -607,7 +630,7 @@ public partial class EntityInspector
bool scrollTo = true) // Should entity be scrolled to in the explorer view? bool scrollTo = true) // Should entity be scrolled to in the explorer view?
{ {
if (entity is Entity<T> e1) window.Add<Selected>(e1); if (entity is Entity<T> e1) window.Add<Selected>(e1);
else window.Remove<Selected, Core.Wildcard>(); else window.Remove<Selected, flecs.core.Wildcard>();
for (var p = entity?.Parent; p is Entity<T> parent; p = parent.Parent) for (var p = entity?.Parent; p is Entity<T> parent; p = parent.Parent)
window.Add<Expanded>(parent); window.Add<Expanded>(parent);
@ -647,9 +670,9 @@ public partial class EntityInspector
private static (Entity<T>? DisplayType, float Priority) FindDisplayType<T>(Entity<T> entity) private static (Entity<T>? DisplayType, float Priority) FindDisplayType<T>(Entity<T> entity)
{ {
var world = entity.World; var world = entity.World;
var component = world.Entity<Core.Component>(); var component = world.Entity<flecs.core.Component>();
var rule = (Rule<T>)(_findDisplayTypeRule ??= new Rule<T>(world, new( var rule = (Rule<T>)(_findDisplayTypeRule ??= world.Rule(new(
$"$Type, gaemstone.Doc.DisplayType($Type)"))); $"$Type, gaemstone.Doc.DisplayType($Type)")));
var typeVar = rule.Variables["Type"]!; var typeVar = rule.Variables["Type"]!;
@ -658,9 +681,9 @@ public partial class EntityInspector
foreach (var iter in rule.Iter().SetVar(rule.ThisVar!, entity)) foreach (var iter in rule.Iter().SetVar(rule.ThisVar!, entity))
for (var i = 0; i < iter.Count; i++) { for (var i = 0; i < iter.Count; i++) {
var type = iter.GetVar(typeVar); var type = iter.GetVar(typeVar);
if ((type == component) && (entity.GetOrNull<Core.Component>(component)?.Size == 0)) if ((type == component) && (entity.GetOrNull<flecs.core.Component>(component)?.Size == 0))
type = world.Entity<Core.Tag>(); type = world.Entity<flecs.core.Tag>();
var priority = type?.GetOrNull<DocPriority>()?.Value ?? float.MaxValue; var priority = type?.GetOrNull<Priority>()?.Value ?? float.MaxValue;
if (priority <= curPriority) { curType = type; curPriority = priority; } if (priority <= curPriority) { curType = type; curPriority = priority; }
} }
@ -706,7 +729,7 @@ public partial class EntityInspector
var (displayType, _) = FindDisplayType(entity); var (displayType, _) = FindDisplayType(entity);
var docColor = Color.TryParseHex(entity.GetDocColor()) ?? Color.TryParseHex(displayType?.GetDocColor()); var docColor = Color.TryParseHex(entity.GetDocColor()) ?? Color.TryParseHex(displayType?.GetDocColor());
var docIcon = entity.GetOrNull<DocIcon>()?.Value.ToString() ?? displayType?.GetOrNull<DocIcon>()?.Value.ToString(); var docIcon = entity.GetOrNull<Icon>()?.Value.ToString() ?? displayType?.GetOrNull<Icon>()?.Value.ToString();
var docName = entity.GetDocName(false); var docName = entity.GetDocName(false);
var isDisabled = entity.IsDisabled; var isDisabled = entity.IsDisabled;
@ -749,7 +772,7 @@ public partial class EntityInspector
if (isHeaderLike) pos.X += ImGui.GetStyle().FramePadding.X; if (isHeaderLike) pos.X += ImGui.GetStyle().FramePadding.X;
drawList.AddText(ImGui.GetFont(), ImGui.GetFontSize(), pos, color.RGBA, displayName); drawList.AddText(ImGui.GetFont(), ImGui.GetFontSize(), pos, color.RGBA, displayName);
if (!isHeaderLike && canClick && hovered) { if (!isHeaderLike && canClick && hovered) {
// Draw a hyperlink-link underscore. // Draw a hyperlink-link undersflecs.core.
var p1 = pos + new Vector2( 0, size.Y - 1.75f); var p1 = pos + new Vector2( 0, size.Y - 1.75f);
var p2 = pos + new Vector2(size.X, size.Y - 1.75f); var p2 = pos + new Vector2(size.X, size.Y - 1.75f);
if (docIcon != null) p1.X += ImGui.CalcTextSize($"{docIcon} ").X; if (docIcon != null) p1.X += ImGui.CalcTextSize($"{docIcon} ").X;

@ -5,7 +5,6 @@ using System.Text;
using gaemstone.Client.Utility; using gaemstone.Client.Utility;
using gaemstone.ECS; using gaemstone.ECS;
using gaemstone.ECS.Utility; using gaemstone.ECS.Utility;
using gaemstone.Flecs;
using ImGuiNET; using ImGuiNET;
using Silk.NET.Input; using Silk.NET.Input;
using Silk.NET.OpenGL.Extensions.ImGui; using Silk.NET.OpenGL.Extensions.ImGui;
@ -21,12 +20,12 @@ namespace gaemstone.Client.Systems;
[DependsOn<gaemstone.Client.Systems.Windowing>] [DependsOn<gaemstone.Client.Systems.Windowing>]
public partial class ImGuiManager public partial class ImGuiManager
{ {
[Entity, Add<Pipeline.Phase>] [Entity, Add<flecs.pipeline.Phase>]
[DependsOn<Pipeline.OnLoad>] [DependsOn<flecs.pipeline.OnLoad>]
public struct ImGuiUpdatePhase { } public struct ImGuiUpdatePhase { }
[Entity, Add<Pipeline.Phase>] [Entity, Add<flecs.pipeline.Phase>]
[DependsOn<Pipeline.OnStore>] [DependsOn<flecs.pipeline.OnStore>]
public struct ImGuiRenderPhase { } public struct ImGuiRenderPhase { }
[Singleton] [Singleton]
@ -85,7 +84,7 @@ public partial class ImGuiManager
} }
[System] [System]
[DependsOn<Pipeline.OnLoad>] [DependsOn<flecs.pipeline.OnLoad>]
public static unsafe void Initialize<T>(World<T> world, GameWindow window, Canvas canvas, public static unsafe void Initialize<T>(World<T> world, GameWindow window, Canvas canvas,
[Source<Input>] InputContext inputContext, Not<ImGuiData> _) [Source<Input>] InputContext inputContext, Not<ImGuiData> _)
=> world.Entity<ImGuiData>().Set(new ImGuiData( => world.Entity<ImGuiData>().Set(new ImGuiData(
@ -141,7 +140,7 @@ public partial class ImGuiManager
}))); })));
[System] [System]
[DependsOn<Pipeline.OnLoad>] [DependsOn<flecs.pipeline.OnLoad>]
public static void UpdateMouse<T>(World<T> world, public static void UpdateMouse<T>(World<T> world,
[Source<Mouse>] MouseImpl impl, ImGuiData _) [Source<Mouse>] MouseImpl impl, ImGuiData _)
{ {

@ -1,9 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using gaemstone.ECS; using gaemstone.ECS;
using gaemstone.Flecs;
using Silk.NET.Input; using Silk.NET.Input;
using static gaemstone.Client.Components.InputComponents; using static gaemstone.Client.Components.InputComponents;
using static gaemstone.Client.Systems.Windowing; using static gaemstone.Client.Systems.Windowing;
@ -22,7 +20,7 @@ public partial class InputManager
[Component] public record class GamepadImpl(IGamepad Value) { } [Component] public record class GamepadImpl(IGamepad Value) { }
[System] [System]
[DependsOn<Pipeline.OnLoad>] [DependsOn<flecs.pipeline.OnLoad>]
public static void Initialize<T>(World<T> world, public static void Initialize<T>(World<T> world,
GameWindow window, [Source<Input>] Not<InputContext> _) GameWindow window, [Source<Input>] Not<InputContext> _)
{ {
@ -43,25 +41,25 @@ public partial class InputManager
} }
[Observer<Core.OnAdd>] [Observer<flecs.core.OnAdd>]
public static void OnCursorCaptured<T>(World<T> universe, public static void OnCursorCaptured<T>(World<T> universe,
[Source<Input>] Has<CursorCapturedBy, Core.Wildcard> _) [Source<Input>] Has<CursorCapturedBy, flecs.core.Wildcard> _)
=> universe.Entity<Mouse>().GetOrThrow<MouseImpl>() => universe.Entity<Mouse>().GetOrThrow<MouseImpl>()
.Value.Cursor.CursorMode = CursorMode.Raw; .Value.Cursor.CursorMode = CursorMode.Raw;
[Observer<Core.OnRemove>] [Observer<flecs.core.OnRemove>]
public static void OnCursorReleased<T>(World<T> universe, public static void OnCursorReleased<T>(World<T> universe,
[Source<Input>] Has<CursorCapturedBy, Core.Wildcard> _) [Source<Input>] Has<CursorCapturedBy, flecs.core.Wildcard> _)
=> universe.Entity<Mouse>().GetOrThrow<MouseImpl>() => universe.Entity<Mouse>().GetOrThrow<MouseImpl>()
.Value.Cursor.CursorMode = CursorMode.Normal; .Value.Cursor.CursorMode = CursorMode.Normal;
[System] [System]
[DependsOn<Pipeline.OnLoad>] [DependsOn<flecs.pipeline.OnLoad>]
public static void ProcessMouse<T>(TimeSpan delta, Entity<T> entity, MouseImpl impl) public static void ProcessMouse<T>(TimeSpan delta, Entity<T> entity, MouseImpl impl)
{ {
var mouse = impl.Value; var mouse = impl.Value;
var isCaptured = entity.Parent?.Has<CursorCapturedBy, Core.Any>() ?? false; var isCaptured = entity.Parent?.Has<CursorCapturedBy, flecs.core.Any>() ?? false;
ref var position = ref entity.NewChild("Position").Build().GetMut<RawValue2D>(); ref var position = ref entity.NewChild("Position").Build().GetMut<RawValue2D>();
ref var posDelta = ref entity.NewChild("Delta" ).Build().GetMut<RawValue2D>(); ref var posDelta = ref entity.NewChild("Delta" ).Build().GetMut<RawValue2D>();
posDelta = mouse.Position - position; posDelta = mouse.Position - position;
@ -77,7 +75,7 @@ public partial class InputManager
} }
[System] [System]
[DependsOn<Pipeline.OnLoad>] [DependsOn<flecs.pipeline.OnLoad>]
public static void ProcessKeyboard<T>(TimeSpan delta, Entity<T> entity, KeyboardImpl impl) public static void ProcessKeyboard<T>(TimeSpan delta, Entity<T> entity, KeyboardImpl impl)
{ {
var keyboard = impl.Value; var keyboard = impl.Value;
@ -88,7 +86,7 @@ public partial class InputManager
} }
[System] [System]
[DependsOn<Pipeline.OnLoad>] [DependsOn<flecs.pipeline.OnLoad>]
public static void ProcessGamepad<T>(TimeSpan delta, Entity<T> entity, GamepadImpl impl) public static void ProcessGamepad<T>(TimeSpan delta, Entity<T> entity, GamepadImpl impl)
{ {
var gamepad = impl.Value; var gamepad = impl.Value;
@ -151,12 +149,12 @@ public partial class InputManager
// public static void OnActiveAdded(EntityRef entity, Active _) // public static void OnActiveAdded(EntityRef entity, Active _)
// => entity.Add<Activated>(); // => entity.Add<Activated>();
// [Observer<Core.OnRemove>] // [Observer<flecs.core.OnRemove>]
// public static void OnActiveRemoved<T>(Entity<T> entity, Active _) // public static void OnActiveRemoved<T>(Entity<T> entity, Active _)
// => entity.Add<Deactivated>(); // => entity.Add<Deactivated>();
// [System] // [System]
// [DependsOn<Pipeline.PostFrame>] // [DependsOn<flecs.pipeline.PostFrame>]
// public static void ClearDeActivated<T>(Entity<T> entity, Has<Or<Activated, Deactivated>> _) // public static void ClearDeActivated<T>(Entity<T> entity, Has<Or<Activated, Deactivated>> _)
// => entity.Remove<Activated>().Remove<Deactivated>(); // => entity.Remove<Activated>().Remove<Deactivated>();
} }

@ -4,7 +4,6 @@ using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using gaemstone.ECS; using gaemstone.ECS;
using gaemstone.ECS.Utility; using gaemstone.ECS.Utility;
using gaemstone.Flecs;
using Silk.NET.OpenGL; using Silk.NET.OpenGL;
using Silk.NET.Windowing; using Silk.NET.Windowing;
using static gaemstone.Client.Components.CameraComponents; using static gaemstone.Client.Components.CameraComponents;
@ -26,7 +25,7 @@ public partial class Renderer
private static int _modelMatrixUniform; private static int _modelMatrixUniform;
private static object? _renderEntityRule; private static object? _renderEntityRule;
[Observer<Core.OnSet>] [Observer<flecs.core.OnSet>]
public static void OnCanvasSet(Canvas canvas) public static void OnCanvasSet(Canvas canvas)
{ {
var GL = canvas.GL; var GL = canvas.GL;
@ -55,7 +54,7 @@ public partial class Renderer
} }
[System] [System]
[DependsOn<Pipeline.PreStore>] [DependsOn<flecs.pipeline.PreStore>]
public static void Clear(Canvas canvas) public static void Clear(Canvas canvas)
{ {
var GL = canvas.GL; var GL = canvas.GL;
@ -66,7 +65,7 @@ public partial class Renderer
} }
[System] [System]
[DependsOn<Pipeline.OnStore>] [DependsOn<flecs.pipeline.OnStore>]
public static void Render<T>(World<T> world, Canvas canvas, public static void Render<T>(World<T> world, Canvas canvas,
in GlobalTransform cameraTransform, in Camera camera, CameraViewport? viewport) in GlobalTransform cameraTransform, in Camera camera, CameraViewport? viewport)
{ {
@ -95,7 +94,7 @@ public partial class Renderer
var cameraMatrix = invertedTransform * cameraProjection; var cameraMatrix = invertedTransform * cameraProjection;
GL.UniformMatrix4(_cameraMatrixUniform, 1, false, in cameraMatrix.M11); GL.UniformMatrix4(_cameraMatrixUniform, 1, false, in cameraMatrix.M11);
var rule = (Rule<T>)(_renderEntityRule ??= new Rule<T>(world, new(""" var rule = (Rule<T>)(_renderEntityRule ??= world.Rule(new("""
[in] GlobalTransform, [in] GlobalTransform,
(Mesh, $mesh), [in] MeshHandle($mesh), (Mesh, $mesh), [in] MeshHandle($mesh),
?(Texture, $tex), [in] ?TextureHandle($tex) ?(Texture, $tex), [in] ?TextureHandle($tex)
@ -128,7 +127,7 @@ public partial class Renderer
} }
[System] [System]
[DependsOn<Pipeline.PostFrame>] [DependsOn<flecs.pipeline.PostFrame>]
public static void SwapBuffers(GameWindow window) public static void SwapBuffers(GameWindow window)
=> window.Handle.SwapBuffers(); => window.Handle.SwapBuffers();

@ -1,7 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using gaemstone.ECS; using gaemstone.ECS;
using gaemstone.Flecs;
using Silk.NET.OpenGL; using Silk.NET.OpenGL;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -17,7 +16,7 @@ namespace gaemstone.Client.Systems;
[DependsOn<gaemstone.Client.Systems.Windowing>] [DependsOn<gaemstone.Client.Systems.Windowing>]
public partial class TextureManager public partial class TextureManager
{ {
[Observer<Core.OnSet>] [Observer<flecs.core.OnSet>]
public static void OnCanvasSet(Canvas canvas) public static void OnCanvasSet(Canvas canvas)
{ {
var GL = canvas.GL; var GL = canvas.GL;

@ -1,6 +1,4 @@
using System.Drawing; using System.Drawing;
using gaemstone.ECS;
using gaemstone.Flecs;
using Silk.NET.OpenGL; using Silk.NET.OpenGL;
using Silk.NET.Windowing; using Silk.NET.Windowing;
@ -29,7 +27,7 @@ public partial class Windowing
} }
[System] [System]
[DependsOn<Pipeline.PreFrame>] [DependsOn<flecs.pipeline.PreFrame>]
public static void ProcessWindow(GameWindow window, Canvas canvas) public static void ProcessWindow(GameWindow window, Canvas canvas)
{ {
canvas.Size = new(window.Handle.Size.X, window.Handle.Size.Y); canvas.Size = new(window.Handle.Size.X, window.Handle.Size.Y);

File diff suppressed because it is too large Load Diff

@ -6,8 +6,13 @@ namespace gaemstone.Client.Utility;
public static class ImGuiUtility public static class ImGuiUtility
{ {
public static bool UIButtonToggle(int index, char icon, string tooltip, ref bool enabled)
=> UIButtonToggle(index, icon.ToString(), tooltip, ref enabled);
public static bool UIButtonToggle(int index, string label, string tooltip, ref bool enabled) public static bool UIButtonToggle(int index, string label, string tooltip, ref bool enabled)
{ if (UIButton(index, label, tooltip, enabled)) enabled = !enabled; return enabled; } { if (UIButton(index, label, tooltip, enabled)) enabled = !enabled; return enabled; }
public static bool UIButton(int index, char icon, string tooltip, bool active)
=> UIButton(index, icon.ToString(), tooltip, active);
public static bool UIButton(int index, string label, string tooltip, bool active) public static bool UIButton(int index, string label, string tooltip, bool active)
{ {
var start = new Vector2(4, 4); var start = new Vector2(4, 4);

@ -1 +1 @@
Subproject commit eec968d6361930eebad180f6300d126c4bac70f3 Subproject commit 1607506e323b0c1513733d93dea619c22edd5cc7

@ -81,8 +81,10 @@ public class ModuleGenerator
private void AppendHeader(StringBuilder sb, string @namespace) private void AppendHeader(StringBuilder sb, string @namespace)
=> sb.AppendLine($$""" => sb.AppendLine($$"""
// <auto-generated/> // <auto-generated/>
#pragma warning disable
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using gaemstone;
using gaemstone.ECS; using gaemstone.ECS;
using gaemstone.ECS.Utility; using gaemstone.ECS.Utility;
@ -120,7 +122,7 @@ public class ModuleGenerator
sb.AppendLine(); sb.AppendLine();
sb.AppendLine($$""" sb.AppendLine($$"""
static void IModule.Initialize<T>(Entity<T> module) static void IModule.OnEnable<T>(Entity<T> module)
{ {
var world = module.World; var world = module.World;
"""); """);
@ -134,12 +136,25 @@ public class ModuleGenerator
// TODO: Can BuiltIn modules have systems and such? // TODO: Can BuiltIn modules have systems and such?
if (module.HasInitializer) if (module.HasLifetimeInterface)
sb.AppendLine("\t\tInitialize(module);"); sb.AppendLine("\t\tOnEnable(module);");
sb.AppendLine("\t}"); sb.AppendLine($$"""
}
static void IModule.OnDisable<T>(Entity<T> module)
{
""");
if (module.IsBuiltIn)
sb.AppendLine("\t\tthrow new global::System.InvalidOperationException();");
if (module.HasLifetimeInterface)
sb.AppendLine("\t\tOnDisable(module);");
sb.AppendLine("}"); sb.AppendLine($$"""
}
}
""");
} }
private void AppendEntityRegistration( private void AppendEntityRegistration(
@ -175,8 +190,8 @@ public class ModuleGenerator
sb.AppendLine($"\t\t\t.Symbol({e.EntitySymbol.ToStringLiteral()})"); sb.AppendLine($"\t\t\t.Symbol({e.EntitySymbol.ToStringLiteral()})");
// Tags and relations in Flecs are marked as empty components. // Tags and relations in Flecs are marked as empty components.
if (e.IsTag || e.IsRelation) sb.AppendLine("\t\t\t.Add<gaemstone.Flecs.Core.Component>()"); if (e.IsTag || e.IsRelation) sb.AppendLine("\t\t\t.Add<flecs.core.Component>()");
if (e.IsTag && e.IsRelation) sb.AppendLine("\t\t\t.Add<gaemstone.Flecs.Core.Tag>()"); if (e.IsTag && e.IsRelation) sb.AppendLine("\t\t\t.Add<flecs.core.Tag>()");
sb.Append( "\t\t\t"); sb.Append( "\t\t\t");
if (!e.IsBuiltIn) sb.Append(".Build()"); if (!e.IsBuiltIn) sb.Append(".Build()");
@ -184,6 +199,12 @@ public class ModuleGenerator
else sb.Append($".CreateLookup<{e.FullName}>()"); else sb.Append($".CreateLookup<{e.FullName}>()");
sb.AppendLine(); sb.AppendLine();
if (e.DocName != null) sb.AppendLine($"\t\t\t.SetDocName({e.DocName.ToStringLiteral()})");
if (e.DocBrief != null) sb.AppendLine($"\t\t\t.SetDocBrief({e.DocBrief.ToStringLiteral()})");
if (e.DocDetail != null) sb.AppendLine($"\t\t\t.SetDocDetail({e.DocDetail.ToStringLiteral()})");
if (e.DocLink != null) sb.AppendLine($"\t\t\t.SetDocLink({e.DocLink.ToStringLiteral()})");
if (e.DocColor != null) sb.AppendLine($"\t\t\t.SetDocColor({e.DocColor.ToStringLiteral()})");
// I don't think it makes sense to have singletons pre-initialized to zero. // I don't think it makes sense to have singletons pre-initialized to zero.
// Especially for singletons that are reference types, which would default to null. // Especially for singletons that are reference types, which would default to null.
// if (e.IsSingleton) sb.AppendLine($"\t\t\t.Add<{e.FullName}>()"); // if (e.IsSingleton) sb.AppendLine($"\t\t\t.Add<{e.FullName}>()");
@ -303,7 +324,7 @@ public class ModuleGenerator
// If system doesn't have an explicit phase set, default to OnUpdate. // If system doesn't have an explicit phase set, default to OnUpdate.
if (e is MethodEntityInfo { IsSystem: true, HasPhaseSet: false }) if (e is MethodEntityInfo { IsSystem: true, HasPhaseSet: false })
sb.AppendLine($"\t\t{@var}.Add<gaemstone.Flecs.Core.DependsOn, gaemstone.Flecs.Pipeline.OnUpdate>();"); sb.AppendLine($"\t\t{@var}.Add<flecs.core.DependsOn, flecs.pipeline.OnUpdate>();");
} }
} }

@ -12,6 +12,7 @@ namespace gaemstone.SourceGen;
public class RelevantSymbolReceiver public class RelevantSymbolReceiver
: ISyntaxContextReceiver : ISyntaxContextReceiver
{ {
// Attributes from gaemstone and gaemstone.Doc are considered.
private static readonly HashSet<string> RelevantAttributeNames = new(){ private static readonly HashSet<string> RelevantAttributeNames = new(){
// Base entity attributes // Base entity attributes
"Module", // Can also be [Singleton] "Module", // Can also be [Singleton]
@ -24,7 +25,7 @@ public class RelevantSymbolReceiver
"Observer", "Observer",
// Entity properties that specify additional info / behavior // Entity properties that specify additional info / behavior
"Path", // TODO: When referring to a pre-existing entity, only [Path] should be necessary, right? "Path",
"Symbol", "Symbol",
"Add", "Add",
"Set", "Set",
@ -37,6 +38,13 @@ public class RelevantSymbolReceiver
"Has", "Has",
"Not", "Not",
"Or", "Or",
// Documentation attributes
"Name",
"Brief",
"Detail",
"Link",
"Color",
}; };
public Dictionary<ISymbol, BaseInfo> Symbols { get; } = new(SymbolEqualityComparer.Default); public Dictionary<ISymbol, BaseInfo> Symbols { get; } = new(SymbolEqualityComparer.Default);
@ -60,7 +68,7 @@ public class RelevantSymbolReceiver
Symbols.Add(symbol, symbol switch { Symbols.Add(symbol, symbol switch {
INamedTypeSymbol typeSymbol => INamedTypeSymbol typeSymbol =>
typeSymbol.GetAttributes().Any(attr => attr.AttributeClass! typeSymbol.GetAttributes().Any(attr => attr.AttributeClass!
.GetFullName() == "gaemstone.ECS.ModuleAttribute") .GetFullName() == "gaemstone.ModuleAttribute")
? new ModuleEntityInfo(typeSymbol) ? new ModuleEntityInfo(typeSymbol)
: new TypeEntityInfo(typeSymbol), : new TypeEntityInfo(typeSymbol),
IMethodSymbol methodSymbol => new MethodEntityInfo(methodSymbol), IMethodSymbol methodSymbol => new MethodEntityInfo(methodSymbol),
@ -75,8 +83,13 @@ public class RelevantSymbolReceiver
public static string? ToRelevantAttributeName(INamedTypeSymbol symbol) public static string? ToRelevantAttributeName(INamedTypeSymbol symbol)
{ {
if (symbol.GetNamespace() != "gaemstone.ECS") return null; var name = symbol.GetFullName(FullNameStyle.NoGeneric);
var name = symbol.MetadataName.Split('`')[0]; if (!name.EndsWith("Attribute")) return null;
return name.EndsWith("Attribute") ? name[..^"Attribute".Length] : null;
var sep = name.LastIndexOf('.');
if (sep < 0) return null;
if (name.AsSpan()[..sep] is not ("gaemstone" or "gaemstone.Doc")) return null;
return name[(sep+1)..^"Attribute".Length];
} }
} }

@ -13,6 +13,12 @@ public abstract class BaseEntityInfo : BaseInfo
public string? EntityPath { get; } public string? EntityPath { get; }
public string? EntitySymbol { get; } public string? EntitySymbol { get; }
public string? DocName { get; }
public string? DocBrief { get; }
public string? DocDetail { get; }
public string? DocLink { get; }
public string? DocColor { get; }
public List<INamedTypeSymbol> EntitiesToAdd { get; } = new(); public List<INamedTypeSymbol> EntitiesToAdd { get; } = new();
public List<(INamedTypeSymbol Relation, INamedTypeSymbol Target)> RelationsToAdd { get; } = new(); public List<(INamedTypeSymbol Relation, INamedTypeSymbol Target)> RelationsToAdd { get; } = new();
public List<(INamedTypeSymbol Component, ImmutableArray<TypedConstant> Arguments)> ComponentsToAdd { get; } = new(); public List<(INamedTypeSymbol Component, ImmutableArray<TypedConstant> Arguments)> ComponentsToAdd { get; } = new();
@ -32,6 +38,13 @@ public abstract class BaseEntityInfo : BaseInfo
?? EntityPath?.Split('/')[^1] // .. otherwise default to the name in [Path], .. ?? EntityPath?.Split('/')[^1] // .. otherwise default to the name in [Path], ..
?? Name // .. or just use the default: The symbol's name. ?? Name // .. or just use the default: The symbol's name.
: null; : null;
// TODO: Get Detail from <summary> + <details> xml documentation.
DocName = Get("Name" )?.ConstructorArguments.FirstOrDefault().Value as string;
DocBrief = Get("Brief" )?.ConstructorArguments.FirstOrDefault().Value as string;
DocDetail = Get("Detail")?.ConstructorArguments.FirstOrDefault().Value as string;
DocLink = Get("Link" )?.ConstructorArguments.FirstOrDefault().Value as string;
DocColor = Get("Color" )?.ConstructorArguments.FirstOrDefault().Value as string;
} }
protected override IEnumerable<Diagnostic> ValidateSelf() protected override IEnumerable<Diagnostic> ValidateSelf()

@ -88,9 +88,9 @@ public class MethodEntityInfo : BaseEntityInfo
param.TermIndex = termIndex++; param.TermIndex = termIndex++;
// See if we have any [DependsOn<...>] attributes for this system. // See if we have any [DependsOn<...>] attributes for this system.
// If not, ModuleGenerator will add [DependsOn<Flecs.Pipeline.OnUpdate>]. // If not, ModuleGenerator will add [DependsOn<flecs.pipeline.OnUpdate>].
HasPhaseSet = IsSystem && RelationsToAdd.Any(entry => entry.Relation HasPhaseSet = IsSystem && RelationsToAdd.Any(entry => entry.Relation
.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.ECS.DependsOnAttribute"); .GetFullName(FullNameStyle.NoGeneric) == "gaemstone.DependsOnAttribute");
// TODO: Handle systems with [Source]. // TODO: Handle systems with [Source].
// TODO: Validate ObserverEvents. // TODO: Validate ObserverEvents.

@ -10,7 +10,7 @@ namespace gaemstone.SourceGen.Structure;
public class ModuleEntityInfo : TypeEntityInfo public class ModuleEntityInfo : TypeEntityInfo
{ {
public bool IsPartial { get; } public bool IsPartial { get; }
public bool HasInitializer { get; } public bool HasLifetimeInterface { get; }
public ModuleEntityInfo(ISymbol symbol) public ModuleEntityInfo(ISymbol symbol)
: base(symbol) : base(symbol)
@ -18,8 +18,8 @@ public class ModuleEntityInfo : TypeEntityInfo
var classDecl = (TypeDeclarationSyntax)Symbol.DeclaringSyntaxReferences.First().GetSyntax(); var classDecl = (TypeDeclarationSyntax)Symbol.DeclaringSyntaxReferences.First().GetSyntax();
IsPartial = classDecl.Modifiers.Any(t => t.IsKind(SyntaxKind.PartialKeyword)); IsPartial = classDecl.Modifiers.Any(t => t.IsKind(SyntaxKind.PartialKeyword));
HasInitializer = Symbol.AllInterfaces.Any(i => HasLifetimeInterface = Symbol.AllInterfaces.Any(i =>
i.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.ECS.IModuleInitializer"); i.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.IModuleLifetime");
IsBuiltIn = Has("BuiltIn"); IsBuiltIn = Has("BuiltIn");
} }
@ -38,7 +38,7 @@ public class ModuleEntityInfo : TypeEntityInfo
public IEnumerable<string> GetDependencies() public IEnumerable<string> GetDependencies()
{ {
foreach (var (relation, target) in RelationsToAdd) foreach (var (relation, target) in RelationsToAdd)
if (relation.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.Flecs.Core.DependsOn") if (relation.GetFullName(FullNameStyle.NoGeneric) == "flecs.core.DependsOn")
yield return GetModulePath(target); yield return GetModulePath(target);
} }

@ -54,9 +54,9 @@ public class ParameterInfo : BaseInfo
} }
else else
{ {
IsOr = typeFullName.StartsWith("gaemstone.ECS.Or"); IsOr = typeFullName.StartsWith("gaemstone.Or");
var isHas = typeFullName.StartsWith("gaemstone.ECS.Has"); var isHas = typeFullName.StartsWith("gaemstone.Has");
var isNot = typeFullName.StartsWith("gaemstone.ECS.Not"); var isNot = typeFullName.StartsWith("gaemstone.Not");
if (IsGeneric) if (IsGeneric)
{ {
@ -66,7 +66,7 @@ public class ParameterInfo : BaseInfo
// Has<...> usually doesn't support a generic type as its own type parameter. // Has<...> usually doesn't support a generic type as its own type parameter.
// However, Has<Or<...>> is an exception to this rule, so we check for this here. // However, Has<Or<...>> is an exception to this rule, so we check for this here.
if (isHas && (args is [ INamedTypeSymbol { IsGenericType: true } argType ]) if (isHas && (args is [ INamedTypeSymbol { IsGenericType: true } argType ])
&& argType.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.ECS.Or") && argType.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.Or")
{ {
TermTypes = argType.TypeArguments.ToImmutableList(); TermTypes = argType.TypeArguments.ToImmutableList();
FieldType = null; FieldType = null;
@ -91,13 +91,13 @@ public class ParameterInfo : BaseInfo
// If the type of the parameter has the [Tag] attribute, // If the type of the parameter has the [Tag] attribute,
// the only way to sensibly use it is to check for its (non-)existance. // the only way to sensibly use it is to check for its (non-)existance.
// (This would also apply to [Relation, Tag] but should be no issue.) // (This would also apply to [Relation, Tag] but should be no issue.)
if (Symbol.Type.HasAttribute("gaemstone.ECS.TagAttribute")) isHas = true; if (Symbol.Type.HasAttribute("gaemstone.TagAttribute")) isHas = true;
// TODO: Make sure [Tag] is used appropriately. // TODO: Make sure [Tag] is used appropriately.
} }
Source = Get("Source")?.AttributeClass!.TypeArguments[0] Source = Get("Source")?.AttributeClass!.TypeArguments[0]
// If the type of the parameter has the [Singleton] attribute, use it as the default Source. // If the type of the parameter has the [Singleton] attribute, use it as the default Source.
?? ((FieldType?.HasAttribute("gaemstone.ECS.SingletonAttribute") == true) ? FieldType : null); ?? ((FieldType?.HasAttribute("gaemstone.SingletonAttribute") == true) ? FieldType : null);
Kind = isHas ? ParameterKind.Has Kind = isHas ? ParameterKind.Has
: isNot ? ParameterKind.Not : isNot ? ParameterKind.Not

@ -96,23 +96,3 @@ public enum FullNameStyle
Metadata, // Namespace.Foo+Bar`1 Metadata, // Namespace.Foo+Bar`1
NoGeneric, // Namespace.Foo.Bar NoGeneric, // Namespace.Foo.Bar
} }
public struct StringifyOptions
{
public static readonly StringifyOptions Default = new();
public static readonly StringifyOptions StripGeneric = new(){ Generic = GenericDisplayMode.None };
public static readonly StringifyOptions MetadataGeneric = new(){ Generic = GenericDisplayMode.Metadata };
public bool Namespace { get; set; } = true;
// TODO: public bool FriendlyNames { get; set; } = true;
public GenericDisplayMode Generic { get; set; } = GenericDisplayMode.Full;
public StringifyOptions() { }
public enum GenericDisplayMode
{
None, // Foo
Metadata, // Foo`1
Full, // Foo<Bar<Baz>>
}
}

@ -0,0 +1,66 @@
using System;
namespace gaemstone;
public partial class Doc
{
/// <summary>
/// A display name for this entity.
/// Names in the entity hierarchy must be unique within the parent entity,
/// This doesn't apply to display names - they are mostly informational.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class NameAttribute : Attribute
{
public string Value { get; }
public NameAttribute(string value) => Value = value;
}
/// <summary>
/// A brief description of this entity.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class BriefAttribute : Attribute
{
public string Value { get; }
public BriefAttribute(string value) => Value = value;
}
/// <summary>
/// A detailed description, or full documentation, of this entity's purpose and behaviors.
/// It's encouraged to use multiple paragraphs and markdown formatting if necessary.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class DetailAttribute : Attribute
{
public string Value { get; }
public DetailAttribute(string value) => Value = value;
}
/// <summary>
/// A link to a website relating to this entity, such as
/// a module's repository, or further documentation.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class LinkAttribute : Attribute
{
public string Value { get; }
public LinkAttribute(string value) => Value = value;
}
/// <summary>
/// A custom color to represent this entity.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class ColorAttribute : Attribute
{
// TODO: Should we be passing a string?
public string Value { get; }
public ColorAttribute(string value) => Value = value;
}
}

@ -0,0 +1,49 @@
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone;
public static unsafe class DocExtensions
{
private static Entity<TContext> Set<TContext, T>(Entity<TContext> entity, string? value)
{
var id = entity.World.Pair<flecs.doc.Description, T>();
if (value != null) {
var str = GlobalHeapAllocator.Instance.AllocateCString(value);
var desc = new flecs.doc.Description { Value = (void*)(nint)str };
entity.Set(id, desc);
} else {
entity.Remove(id);
}
return entity;
}
public static string? GetDocName<TContext>(this Entity<TContext> entity, bool fallbackToEntityName = true)
=> fallbackToEntityName || entity.Has<flecs.doc.Description, flecs.core.Name>()
? ecs_doc_get_name(entity.World, entity).FlecsToString() : null;
public static Entity<TContext> SetDocName<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, flecs.core.Name>(entity, value);
public static string? GetDocBrief<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_brief(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocBrief<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, flecs.doc.Brief>(entity, value);
public static string? GetDocDetail<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_detail(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocDetail<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, flecs.doc.Detail>(entity, value);
public static string? GetDocLink<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_link(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocLink<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, flecs.doc.Link>(entity, value);
public static string? GetDocColor<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_color(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocColor<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, flecs.doc.Color>(entity, value);
}

@ -1,86 +1,43 @@
using System;
using gaemstone.ECS;
using static gaemstone.Flecs.Core;
namespace gaemstone; namespace gaemstone;
[Module] [Module]
public partial class Doc public partial class Doc
{ {
[Tag]
public struct DisplayType { }
[Tag] [Tag]
public struct Relation { } public struct Relation { }
// TODO: These need to actually be read at some point.
/// <summary>
/// A display name for this entity.
/// Names in the entity hierarchy must be unique within the parent entity,
/// This doesn't apply to display names - they are mostly informational.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class Name : Attribute
{
public string Value { get; }
public Name(string value) => Value = value;
}
/// <summary>
/// A brief description of this entity.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class Brief : Attribute
{
public string Value { get; }
public Brief(string value) => Value = value;
}
/// <summary> /// <summary>
/// A detailed description, or full documentation, of this entity's purpose and behaviors. /// Tags are just <see cref="Flecs.Core.Component"/>s with 0 size.
/// It's encouraged to use multiple paragraphs and markdown formatting if necessary. /// This functions as a special entity that holds the appearance for tags
/// Displayed in the Entity Inspector. /// used by the entity inspector, and is not actually added to any entity.
/// Not related to <see cref="Flecs.Core.Tag"/> in any way.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] [Tag]
public class Detail : Attribute public struct Tag { }
{
public string Value { get; }
public Detail(string value) => Value = value;
}
/// <summary>
/// A link to a website relating to this entity, such as
/// a module's repository, or further documentation.
/// Displayed in the Entity Inspector.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class Link : Attribute
{
public string Value { get; }
public Link(string value) => Value = value;
}
/// <summary> /// <summary>
/// A custom color to represent this entity. /// Added to entities that represent a unique type of entity that should
/// Displayed in the Entity Inspector. /// be displayed uniquely in the entity inspector. If an entity is tagged
/// with such a display type entity, it too will be shown differently.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] /// <details>
public class Color : Attribute /// <p>
{ /// Examples of such display types include:
public float Red { get; } /// <list>
public float Green { get; } /// <item> <see cref="Flecs.Core.Module"/> </item>
public float Blue { get; } /// <item> <see cref="Flecs.Core.Component"/> </item>
/// <item> <see cref="Flecs.Core.Observer"/> </item>
public Color(float red, float green, float blue) /// <item> <see cref="Flecs.System.System"/> </item>
{ /// <item> <see cref="gaemstone.Doc.Relation"/> </item>
if ((red < 0.0f) || (red > 1.0f)) throw new ArgumentOutOfRangeException(nameof(red )); /// <item> <see cref="gaemstone.Doc.Tag"/> </item>
if ((green < 0.0f) || (green > 1.0f)) throw new ArgumentOutOfRangeException(nameof(green)); /// </list>
if ((blue < 0.0f) || (blue > 1.0f)) throw new ArgumentOutOfRangeException(nameof(blue )); /// </p>
Red = red; Green = green; Blue = blue; /// <p>
} /// Used in conjuction with components and relations such as
} /// (<see cref="Flecs.Doc.Description"/>, <see cref="Flecs.Doc.Color"/>)
/// to specify the appearance of this display type in the entity inspector.
/// </p>
/// </details>
[Tag]
public struct DisplayType { }
} }

@ -1,12 +1,19 @@
using gaemstone;
using gaemstone.ECS; using gaemstone.ECS;
namespace gaemstone.Flecs; namespace flecs;
[BuiltIn, Module, Path("/flecs/core")] [BuiltIn, Module, Path("/flecs/core")]
public partial class Core #pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public partial class core
: IModuleImport
{ {
// Entity Tags // Entity Tags
[Tag] public struct Observer { }
// TODO: Put back World?
[Tag] public struct Module { } [Tag] public struct Module { }
[Tag] public struct Private { } [Tag] public struct Private { }
[Tag] public struct Prefab { } [Tag] public struct Prefab { }
@ -105,4 +112,8 @@ public partial class Core
public static implicit operator string?(Identifier id) public static implicit operator string?(Identifier id)
=> id.ToString(); => id.ToString();
} }
static Entity<T> IModuleImport.Import<T>(World<T> world)
=> world.LookupPathOrThrow("/flecs/core");
} }

@ -0,0 +1,28 @@
using System.Runtime.InteropServices;
using gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace flecs;
[BuiltIn, Module, Path("/flecs/coredoc")]
[DependsOn<flecs.meta>]
[DependsOn<flecs.doc>]
#pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public unsafe partial class coredoc
: IModuleImport
{
static Entity<T> IModuleImport.Import<T>(World<T> world)
{
using var alloc = TempAllocator.Use();
return Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &CoreDocImport } },
alloc.AllocateCString("FlecsCoreDoc"))));
}
[UnmanagedCallersOnly]
private static void CoreDocImport(ecs_world_t* world)
=> FlecsCoreDocImport(world);
}

@ -1,12 +1,16 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using gaemstone;
using gaemstone.ECS; using gaemstone.ECS;
using gaemstone.ECS.Utility; using gaemstone.ECS.Utility;
using static flecs_hub.flecs; using static flecs_hub.flecs;
namespace gaemstone.Flecs; namespace flecs;
[BuiltIn, Module, Path("/flecs/doc")] [BuiltIn, Module, Path("/flecs/doc")]
public partial class Doc #pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public unsafe partial class doc
: IModuleImport
{ {
[Tag] public struct Brief { } [Tag] public struct Brief { }
[Tag] public struct Detail { } [Tag] public struct Detail { }
@ -23,48 +27,17 @@ public partial class Doc
public static implicit operator string?(Description desc) public static implicit operator string?(Description desc)
=> desc.ToString(); => desc.ToString();
} }
}
public static unsafe class DocExtensions
{
private static Entity<TContext> Set<TContext, T>(Entity<TContext> entity, string? value)
{
var id = entity.World.Pair<Doc.Description, T>();
if (value != null) {
var str = GlobalHeapAllocator.Instance.AllocateCString(value);
var desc = new Doc.Description { Value = (void*)(nint)str };
entity.Set(id, desc);
} else {
entity.Remove(id);
}
return entity; static Entity<T> IModuleImport.Import<T>(World<T> world)
{
using var alloc = TempAllocator.Use();
return Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &DocImport } },
alloc.AllocateCString("FlecsDoc"))));
} }
public static string? GetDocName<TContext>(this Entity<TContext> entity, bool fallbackToEntityName = true) [UnmanagedCallersOnly]
=> fallbackToEntityName || entity.Has<Doc.Description, Core.Name>() private static void DocImport(ecs_world_t* world)
? ecs_doc_get_name(entity.World, entity).FlecsToString() : null; => FlecsDocImport(world);
public static Entity<TContext> SetDocName<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Core.Name>(entity, value);
public static string? GetDocBrief<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_brief(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocBrief<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Doc.Brief>(entity, value);
public static string? GetDocDetail<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_detail(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocDetail<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Doc.Detail>(entity, value);
public static string? GetDocLink<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_link(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocLink<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Doc.Link>(entity, value);
public static string? GetDocColor<TContext>(this Entity<TContext> entity)
=> ecs_doc_get_color(entity.World, entity).FlecsToString()!;
public static Entity<TContext> SetDocColor<TContext>(this Entity<TContext> entity, string? value)
=> Set<TContext, Doc.Color>(entity, value);
} }

@ -0,0 +1,26 @@
using System.Runtime.InteropServices;
using gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace flecs;
[BuiltIn, Module, Path("/flecs/meta")]
#pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public unsafe partial class meta
: IModuleImport
{
static Entity<T> IModuleImport.Import<T>(World<T> world)
{
using var alloc = TempAllocator.Use();
return Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &MetaImport } },
alloc.AllocateCString("FlecsMeta"))));
}
[UnmanagedCallersOnly]
private static void MetaImport(ecs_world_t* world)
=> FlecsMetaImport(world);
}

@ -0,0 +1,29 @@
using System.Runtime.InteropServices;
using gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace flecs;
[BuiltIn, Module, Path("/flecs/timer")]
[DependsOn<flecs.pipeline>]
[DependsOn<flecs.meta>]
[DependsOn<flecs.units>]
#pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public unsafe partial class metrics
: IModuleImport
{
static Entity<T> IModuleImport.Import<T>(World<T> world)
{
using var alloc = TempAllocator.Use();
return Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &MetricsImport } },
alloc.AllocateCString("FlecsMetrics"))));
}
[UnmanagedCallersOnly]
private static void MetricsImport(ecs_world_t* world)
=> FlecsMetricsImport(world);
}

@ -0,0 +1,26 @@
using System.Runtime.InteropServices;
using gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace flecs;
[BuiltIn, Module, Path("/flecs/monitor")]
#pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public unsafe partial class monitor
: IModuleImport
{
static Entity<T> IModuleImport.Import<T>(World<T> world)
{
using var alloc = TempAllocator.Use();
return Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &MonitorImport } },
alloc.AllocateCString("FlecsMonitor"))));
}
[UnmanagedCallersOnly]
private static void MonitorImport(ecs_world_t* world)
=> FlecsMonitorImport(world);
}

@ -1,9 +1,17 @@
using System.Runtime.InteropServices;
using gaemstone;
using gaemstone.ECS; using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs; namespace flecs;
[BuiltIn, Module, Path("/flecs/pipeline")] [BuiltIn, Module, Path("/flecs/pipeline")]
public partial class Pipeline [DependsOn<flecs.system>]
#pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public unsafe partial class pipeline
: IModuleImport
{ {
[Entity] public struct Phase { } [Entity] public struct Phase { }
@ -90,4 +98,17 @@ public partial class Pipeline
[Entity, Add<Phase>] [Entity, Add<Phase>]
[DependsOn<OnStore>] [DependsOn<OnStore>]
public struct PostFrame { } public struct PostFrame { }
static Entity<T> IModuleImport.Import<T>(World<T> world)
{
using var alloc = TempAllocator.Use();
return Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &PipelineImport } },
alloc.AllocateCString("FlecsPipeline"))));
}
[UnmanagedCallersOnly]
private static void PipelineImport(ecs_world_t* world)
=> FlecsPipelineImport(world);
} }

@ -0,0 +1,34 @@
using System.Runtime.InteropServices;
using gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace flecs;
[BuiltIn, Module, Path("/flecs/rest")]
[DependsOn<flecs.pipeline>]
#pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public unsafe partial class rest
: IModuleImport
{
static Entity<T> IModuleImport.Import<T>(World<T> world)
{
using var alloc = TempAllocator.Use();
var module = Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &RestImport } },
alloc.AllocateCString("FlecsRest"))));
module.NewChild("Rest").Build()
.CreateLookup<EcsRest>()
.Set(new EcsRest { port = 27750 });
return module;
}
[UnmanagedCallersOnly]
private static void RestImport(ecs_world_t* world)
=> FlecsRestImport(world);
}

@ -0,0 +1,27 @@
using System.Runtime.InteropServices;
using gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace flecs;
[BuiltIn, Module, Path("/flecs/script")]
[DependsOn<flecs.meta>]
#pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public unsafe partial class script
: IModuleImport
{
static Entity<T> IModuleImport.Import<T>(World<T> world)
{
using var alloc = TempAllocator.Use();
return Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &ScriptImport } },
alloc.AllocateCString("FlecsScript"))));
}
[UnmanagedCallersOnly]
private static void ScriptImport(ecs_world_t* world)
=> FlecsScriptImport(world);
}

@ -0,0 +1,29 @@
using System.Runtime.InteropServices;
using gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace flecs;
[BuiltIn, Module, Path("/flecs/system")]
#pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public unsafe partial class system
: IModuleImport
{
[Tag]
public struct System { }
static Entity<T> IModuleImport.Import<T>(World<T> world)
{
using var alloc = TempAllocator.Use();
return Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &SystemImport } },
alloc.AllocateCString("FlecsSystem"))));
}
[UnmanagedCallersOnly]
private static void SystemImport(ecs_world_t* world)
=> FlecsSystemImport(world);
}

@ -1,23 +0,0 @@
using System.Runtime.InteropServices;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs.Systems;
[BuiltIn, Module, Path("/flecs/monitor")]
public unsafe partial class Monitor
: IModuleInitializer
{
public static void Initialize<T>(Entity<T> module)
{
using var alloc = TempAllocator.Use();
ecs_import_c(module.World,
new() { Data = new() { Pointer = &MonitorImport } },
alloc.AllocateCString("FlecsMonitor"));
}
[UnmanagedCallersOnly]
private static void MonitorImport(ecs_world_t* world)
=> FlecsMonitorImport(world);
}

@ -1,27 +0,0 @@
using System.Runtime.InteropServices;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs.Systems;
[BuiltIn, Module, Path("/flecs/rest")]
public unsafe partial class Rest
: IModuleInitializer
{
public static void Initialize<T>(Entity<T> module)
{
using (var alloc = TempAllocator.Use())
ecs_import_c(module.World,
new() { Data = new() { Pointer = &RestImport } },
alloc.AllocateCString("FlecsRest"));
module.NewChild("Rest").Build()
.CreateLookup<EcsRest>()
.Set(new EcsRest { port = 27750 });
}
[UnmanagedCallersOnly]
private static void RestImport(ecs_world_t* world)
=> FlecsRestImport(world);
}

@ -0,0 +1,27 @@
using System.Runtime.InteropServices;
using gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace flecs;
[BuiltIn, Module, Path("/flecs/timer")]
[DependsOn<flecs.pipeline>]
#pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public unsafe partial class timer
: IModuleImport
{
static Entity<T> IModuleImport.Import<T>(World<T> world)
{
using var alloc = TempAllocator.Use();
return Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &TimerImport } },
alloc.AllocateCString("FlecsTimer"))));
}
[UnmanagedCallersOnly]
private static void TimerImport(ecs_world_t* world)
=> FlecsTimerImport(world);
}

@ -0,0 +1,26 @@
using System.Runtime.InteropServices;
using gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace flecs;
[BuiltIn, Module, Path("/flecs/units")]
#pragma warning disable IDE1006 // Naming rule violation
#pragma warning disable CS8981 // Only contains lower-cased ascii characters
public unsafe partial class units
: IModuleImport
{
static Entity<T> IModuleImport.Import<T>(World<T> world)
{
using var alloc = TempAllocator.Use();
return Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &UnitsImport } },
alloc.AllocateCString("FlecsUnits"))));
}
[UnmanagedCallersOnly]
private static void UnitsImport(ecs_world_t* world)
=> FlecsUnitsImport(world);
}

@ -1,7 +1,6 @@
using System; using System;
using static gaemstone.Flecs.Core;
namespace gaemstone.ECS; namespace gaemstone;
/// <summary> /// <summary>
/// Entities marked with this attribute are automatically registered with a /// Entities marked with this attribute are automatically registered with a
@ -57,13 +56,22 @@ public class SetAttribute<TComponent> : Attribute
/// <seealso cref="IsA"/> /// <seealso cref="IsA"/>
public class IsAAttribute<TTarget> : AddAttribute<IsA, TTarget> { } public class IsAAttribute<TTarget>
: AddAttribute<flecs.core.IsA, TTarget> { }
/// <seealso cref="ChildOf"/> /// <seealso cref="ChildOf"/>
public class ChildOfAttribute<TTarget> : AddAttribute<ChildOf, TTarget> { } public class ChildOfAttribute<TTarget>
: AddAttribute<flecs.core.ChildOf, TTarget> { }
/// <seealso cref="DependsOn"/> /// <seealso cref="DependsOn"/>
public class DependsOnAttribute<TTarget> : AddAttribute<DependsOn, TTarget> { } public class DependsOnAttribute<TTarget>
: AddAttribute<flecs.core.DependsOn, TTarget> { }
/// <seealso cref="Exclusive"/> /// <seealso cref="Exclusive"/>
public class ExclusiveAttribute : AddAttribute<Exclusive> { } public class ExclusiveAttribute
: AddAttribute<flecs.core.Exclusive> { }
/// <seealso cref="With"/> /// <seealso cref="With"/>
public class WithAttribute<TTarget> : AddAttribute<With, TTarget> { } public class WithAttribute<TTarget>
: AddAttribute<flecs.core.With, TTarget> { }

@ -1,6 +1,6 @@
using System; using System;
namespace gaemstone.ECS; namespace gaemstone;
/// <summary> Use a custom name or path for this entity instead of the type's name. </summary> /// <summary> Use a custom name or path for this entity instead of the type's name. </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct

@ -1,18 +1,33 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using gaemstone.ECS;
namespace gaemstone.ECS; namespace gaemstone;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class ModuleAttribute : SingletonAttribute { } public class ModuleAttribute : SingletonAttribute { }
public interface IModuleLifetime
{
static abstract void OnEnable<TContext>(Entity<TContext> module);
static abstract void OnDisable<TContext>(Entity<TContext> module);
}
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class BuiltInAttribute : Attribute { } public class BuiltInAttribute : Attribute { }
public interface IModuleImport
{
static abstract Entity<TContext> Import<TContext>(World<TContext> world);
}
/// <summary> /// <summary>
/// A concrete implementation of this interface is generated by a source /// A concrete implementation of this interface is generated by a source
/// generator for each type marked as <see cref="ModuleAttribute"/>. /// generator for each type marked as <see cref="ModuleAttribute"/>.
/// (Do not implement this yourself.)
/// </summary> /// </summary>
public interface IModule public interface IModule
{ {
@ -22,10 +37,8 @@ public interface IModule
static abstract IReadOnlyList<string> Dependencies { get; } static abstract IReadOnlyList<string> Dependencies { get; }
static abstract void Initialize<TContext>(Entity<TContext> module);
}
public interface IModuleInitializer static abstract void OnEnable<TContext>(Entity<TContext> module);
{
static abstract void Initialize<TContext>(Entity<TContext> module); static abstract void OnDisable<TContext>(Entity<TContext> module);
} }

@ -1,7 +1,7 @@
using System; using System;
using gaemstone.Utility; using gaemstone.Utility;
namespace gaemstone.ECS; namespace gaemstone;
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
public class SourceAttribute<T> : Attribute { } public class SourceAttribute<T> : Attribute { }

@ -1,6 +1,6 @@
using System; using System;
namespace gaemstone.ECS; namespace gaemstone;
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class SystemAttribute : Attribute { } public class SystemAttribute : Attribute { }

@ -1,135 +1,107 @@
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using gaemstone.ECS; using gaemstone.ECS;
using static gaemstone.Flecs.Core;
using Module = gaemstone.Flecs.Core.Module;
namespace gaemstone; namespace gaemstone;
public class ModuleManager<TContext> public class ModuleManager<TContext>
: IEnumerable<ModuleManager<TContext>.IModuleInfo>
{ {
private readonly Dictionary<Entity<TContext>, IModuleInfo> _modules = new();
public Universe<TContext> Universe { get; } public Universe<TContext> Universe { get; }
public ModuleManager(Universe<TContext> universe) public World<TContext> World => Universe.World;
=> Universe = universe;
internal IModuleInfo? Lookup(Entity<TContext> entity)
=> _modules.GetValueOrDefault(entity);
public Entity<TContext> Register<T>() private readonly Rule<TContext> _findDisabledDeps;
where T : IModule private readonly Rule<TContext> _findDependents;
{
// if (!typeof(T).IsAssignableTo(typeof(IModule))) throw new ArgumentException(
// $"The specified type {typeof(T)} does not implement IModule", nameof(T));
var module = new ModuleInfo<T>(Universe); private readonly ECS.Variable _findDisabledDepsThisVar;
_modules.Add(module.Entity, module); private readonly ECS.Variable _findDependentsModuleVar;
TryEnableModule(module);
return module.Entity;
}
private void TryEnableModule(IModuleInfo module) public ModuleManager(Universe<TContext> universe)
{ {
if (!module.Dependencies.All(dep => dep.IsDependencyMet)) return; Universe = universe;
Console.WriteLine($"Enabling module {module.Entity.Path}");
module.Enable(); World.New("/gaemstone/ModuleInfo").Symbol("ModuleInfo")
.Build().InitComponent<IModuleInfo>();
// Find other modules that might be missing this module as a dependency.
foreach (var other in _modules.Values) {
if (other.IsInitialized) continue;
var dependency = other.Dependencies.FirstOrDefault(dep => dep.Entity == module.Entity);
if (dependency == null) continue;
dependency.Info = module; _findDisabledDeps = World.Rule(new("(DependsOn, $dep), Disabled($dep)"));
dependency.IsDependencyMet = true; _findDependents = World.Rule(new("ModuleInfo, Disabled, (DependsOn, $module)"));
TryEnableModule(other); _findDisabledDepsThisVar = _findDisabledDeps.ThisVar
} ?? throw new InvalidOperationException($"Could not find $this of {nameof(_findDisabledDeps)}");
_findDependentsModuleVar = _findDependents.Variables["module"]
?? throw new InvalidOperationException($"Could not find $module of {nameof(_findDependents)}");
} }
public interface IModuleInfo public Entity<TContext> Import<T>()
where T : IModule, IModuleImport
{ {
Entity<TContext> Entity { get; } foreach (var dep in T.Dependencies)
IReadOnlyCollection<ModuleDependency> Dependencies { get; } if (World.LookupPathOrNull(dep) == null) throw new InvalidOperationException(
bool IsInitialized { get; } $"Missing required dependency {dep} for built-in module {T.Path}");
void Enable();
}
// IEnumerable implementation Console.WriteLine($"Importing built-in module {T.Path}");
public IEnumerator<IModuleInfo> GetEnumerator() => _modules.Values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
var entity = T.Import(World);
public class ModuleDependency entity.Set<IModuleInfo>(new ModuleInfo<T>());
{ T.OnEnable(entity);
public Entity<TContext> Entity { get; } return entity;
public IModuleInfo? Info { get; internal set; }
public bool IsDependencyMet { get; internal set; }
public ModuleDependency(Entity<TContext> entity,
IModuleInfo? info = null, bool isDependencyMet = false)
{
Entity = entity;
Info = info;
IsDependencyMet = isDependencyMet;
}
} }
internal class ModuleInfo<T> : IModuleInfo public Entity<TContext> Register<T>()
where T : IModule where T : IModule
{ {
public Entity<TContext> Entity { get; } if (T.IsBuiltIn) throw new ArgumentException(
public IReadOnlyCollection<ModuleDependency> Dependencies { get; } $"Unexpected operation, {T.Path} is a built-in module");
public bool IsInitialized { get; private set; }
var builder = World.New(T.Path)
public ModuleInfo(Universe<TContext> universe) .Add<flecs.core.Module>()
{ .Add<flecs.core.Disabled>()
var world = universe.World; .Set<IModuleInfo>(new ModuleInfo<T>());
if (T.IsBuiltIn) foreach (var depPath in T.Dependencies) {
{ var dependency = World.LookupPathOrNull(depPath)
Entity = world.LookupPathOrThrow(T.Path); ?? World.New(depPath)
Dependencies = Array.Empty<ModuleDependency>(); .Add<flecs.core.Module>()
.Add<flecs.core.Disabled>()
.Build();
builder.Add<flecs.core.DependsOn>(dependency);
} }
else
{
var builder = world.New(T.Path);
var deps = new List<ModuleDependency>();
builder.Add<Module>(); var module = builder.Build().CreateLookup<T>();
foreach (var dependsPath in T.Dependencies) {
var dependency = world.LookupPathOrNull(dependsPath) ??
world.New(dependsPath).Add<Module>().Add<Disabled>().Build();
var depModule = universe.Modules.Lookup(dependency); // Ensure all parent entities have the Module tag set.
var isDepInit = (depModule?.IsInitialized == true); for (var p = module.Parent; p is Entity<TContext> parent; p = parent.Parent)
parent.Add<flecs.core.Module>();
deps.Add(new(dependency, depModule, isDepInit)); Console.WriteLine($"Registered module {module.Path}");
if (!isDepInit) builder.Add<Disabled>();
builder.Add<DependsOn>(dependency); TryEnableModule(module);
return module;
} }
Entity = builder.Build().CreateLookup<T>(); private void TryEnableModule(Entity<TContext> module)
Dependencies = deps.AsReadOnly(); {
// Skip if module is already enabled.
if (module.IsEnabled) return;
// Skip if module has any not-yet-enabled dependencies.
if (_findDisabledDeps.Iter().SetVar(_findDisabledDepsThisVar, module).Any()) return;
// Ensure all parent entities have the Module tag set. Console.WriteLine($"Enabling module {module.Path}");
for (var p = Entity.Parent; p is Entity<TContext> parent; p = parent.Parent) module.GetOrThrow<IModuleInfo>().OnEnable(module);
parent.Add<Module>(); module.Enable();
}
// Get all modules that depend on this one and try to enabled them if they now have their dependencies met.
foreach (var dependent in _findDependents.Iter().SetVar(_findDependentsModuleVar, module).GetAllEntities())
TryEnableModule(dependent);
} }
public void Enable() public interface IModuleInfo
{ {
Entity.Enable(); void OnEnable(Entity<TContext> entity);
T.Initialize(Entity);
IsInitialized = true;
} }
internal class ModuleInfo<T> : IModuleInfo
where T : IModule
{
public void OnEnable(Entity<TContext> entity)
=> T.OnEnable(entity);
} }
} }

@ -7,19 +7,24 @@ public class Universe<TContext>
public World<TContext> World { get; } public World<TContext> World { get; }
public ModuleManager<TContext> Modules { get; } public ModuleManager<TContext> Modules { get; }
public Universe(params string[] args) public Universe()
{ {
World = new(args); World = new(minimal: true);
Modules = new(this); Modules = new(this);
// Bootstrap [Relation] tag, since it will be added to some Flecs types. // Bootstrap [Relation] tag, since it will be added to some Flecs types.
World.New("/gaemstone/Doc/Relation").Build() World.New("/gaemstone/Doc/Relation").Build()
.CreateLookup<Doc.Relation>(); .CreateLookup<Doc.Relation>();
// Bootstrap built-in (static) modules from Flecs. // Bootstrap core module from Flecs.
Modules.Register<Flecs.Core>(); Modules.Import<flecs.core>();
Modules.Register<Flecs.Doc>();
Modules.Register<Flecs.Pipeline>(); // Import addon modules from Flecs we use for the engine.
Modules.Import<flecs.system>();
Modules.Import<flecs.pipeline>();
Modules.Import<flecs.meta>();
Modules.Import<flecs.doc>();
Modules.Import<flecs.coredoc>();
Modules.Register<Doc>(); Modules.Register<Doc>();
} }

Loading…
Cancel
Save