Refactor ModuleManager and built-in modules

- Create a minimal Flecs world (no default addon module imports)
- Wrap all the official Flecs addon modules as [BuiltIn] modules
- Add IModuleImport for [BuiltIn] modules
- Rename IModuleInitializer to IModuleLifetime
- Rename IModule.Initialize to .OnEnable
- Overhaul ModuleManager to use entities and rules
- Use ModuleManager.Import for built-in modules
- Add gaemstone.Doc.Tag to represent tag-like components
- Overhaul EntityInspector to use new features
main
copygirl 1 year ago
parent d80e69006c
commit 6b92e1ce8c
  1. 14
      src/Immersion/Program.cs
  2. 127
      src/gaemstone.Client/Systems/EntityInspector.cs
  3. 1
      src/gaemstone.Client/Systems/InputManager.cs
  4. 2
      src/gaemstone.Client/Systems/Renderer.cs
  5. 1622
      src/gaemstone.Client/Utility/ForkAwesome.cs
  6. 5
      src/gaemstone.Client/Utility/ImGuiUtility.cs
  7. 2
      src/gaemstone.ECS
  8. 23
      src/gaemstone.SourceGen/ModuleGenerator.cs
  9. 2
      src/gaemstone.SourceGen/Structure/MethodEntityInfo.cs
  10. 6
      src/gaemstone.SourceGen/Structure/ModuleEntityInfo.cs
  11. 8
      src/gaemstone/Doc.cs
  12. 22
      src/gaemstone/ECS/Module.cs
  13. 5
      src/gaemstone/Flecs/Core.cs
  14. 25
      src/gaemstone/Flecs/CoreDoc.cs
  15. 16
      src/gaemstone/Flecs/Doc.cs
  16. 23
      src/gaemstone/Flecs/Meta.cs
  17. 26
      src/gaemstone/Flecs/Metrics.cs
  18. 10
      src/gaemstone/Flecs/Monitor.cs
  19. 20
      src/gaemstone/Flecs/Pipeline.cs
  20. 18
      src/gaemstone/Flecs/Rest.cs
  21. 24
      src/gaemstone/Flecs/Script.cs
  22. 23
      src/gaemstone/Flecs/System.cs
  23. 24
      src/gaemstone/Flecs/Timer.cs
  24. 23
      src/gaemstone/Flecs/Units.cs
  25. 166
      src/gaemstone/Universe+Modules.cs
  26. 15
      src/gaemstone/Universe.cs

@ -23,8 +23,12 @@ 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<gaemstone.Flecs.Timer>();
universe.Modules.Register<gaemstone.Flecs.Systems.Rest>(); universe.Modules.Import<gaemstone.Flecs.Script>();
universe.Modules.Import<gaemstone.Flecs.Rest>();
universe.Modules.Import<gaemstone.Flecs.Monitor>();
universe.Modules.Import<gaemstone.Flecs.Units>();
universe.Modules.Import<gaemstone.Flecs.Metrics>();
var window = Window.Create(WindowOptions.Default with { var window = Window.Create(WindowOptions.Default with {
Title = "gæmstone", Title = "gæmstone",
@ -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)));

@ -7,7 +7,6 @@ using gaemstone.ECS;
using gaemstone.Flecs; 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,7 +14,6 @@ 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
{ {
[Tag] [Tag]
public struct InspectorWindow { } public struct InspectorWindow { }
@ -54,32 +52,49 @@ 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);
private const string DefaultWindowTitle = "Inspector Gadget"; [Path("/flecs/core/Module")]
// [Doc(Color = "#FFE4B2")]
[Add<Doc.DisplayType>, Set<Priority>(0), Set<Icon>(ForkAwesome.Archive)]
public struct Module { }
public static void Initialize<T>(Entity<T> module) [Path("/flecs/system/System")]
{ // [Doc(Color = "#FFB2B2")]
void SetDocInfo(string path, float priority, string icon, float r, float g, float b) [Add<Doc.DisplayType>, Set<Priority>(1), Set<Icon>(ForkAwesome.Cog)]
=> module.World.LookupPathOrThrow(path) public struct System { }
.Add<Doc.DisplayType>()
.Set(new DocPriority { Value = priority }) [Path("/flecs/core/Observer")]
.Set(new DocIcon { Value = icon[0] }) // [Doc(Color = "#FFCCCC")]
.SetDocColor(Color.FromRGB(r, g, b).ToHexString()); [Add<Doc.DisplayType>, Set<Priority>(2), Set<Icon>(ForkAwesome.Eye)]
public struct Observer { }
SetDocInfo("/flecs/core/Module" , 0 , Icon.Archive , 1.0f, 0.9f, 0.7f);
SetDocInfo("/flecs/system/System" , 1 , Icon.Cog , 1.0f, 0.7f, 0.7f); [Path("/gaemstone/Doc/Relation")]
SetDocInfo("/flecs/core/Observer" , 2 , Icon.Eye , 1.0f, 0.8f, 0.8f); // [Doc(Color = "#B2FFCC")]
SetDocInfo("/gaemstone/Doc/Relation" , 3 , Icon.ShareAlt , 0.7f, 1.0f, 0.8f); [Add<Doc.DisplayType>, Set<Priority>(3), Set<Icon>(ForkAwesome.ShareAlt)]
SetDocInfo("/flecs/core/Component" , 4 , Icon.PencilSquare , 0.6f, 0.6f, 1.0f); public struct Relation { }
// TODO: Handle tags like Flecs does.
SetDocInfo("/flecs/core/Tag" , 5 , Icon.Tag , 0.7f, 0.8f, 1.0f); [Path("/flecs/core/Component")]
SetDocInfo("/flecs/core/Prefab" , 6 , Icon.Cube , 0.9f, 0.8f, 1.0f); // [Doc(Color = "#9999FF")]
} [Add<Doc.DisplayType>, Set<Priority>(4), Set<Icon>(ForkAwesome.PencilSquare)]
public struct Component { }
[Path("/gaemstone/Doc/Tag")]
// [Doc(Color = "#B2CCFF")]
[Add<Doc.DisplayType>, Set<Priority>(5), Set<Icon>(ForkAwesome.Tag)]
public struct Tag { }
[Path("/flecs/core/Prefab")]
// [Doc(Color = "#E4CCFF")]
[Add<Doc.DisplayType>, Set<Priority>(6), Set<Icon>(ForkAwesome.Cube)]
public struct Prefab { }
private const string DefaultWindowTitle = "Inspector Gadget";
[System] [System]
@ -87,10 +102,10 @@ public partial class EntityInspector
{ {
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 +119,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 +147,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();
@ -157,9 +172,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);
@ -174,22 +189,22 @@ public partial class EntityInspector
ImGui.TableNextColumn(); ImGui.TableNextColumn();
var hasExpanded = window.Has<Expanded, Core.Wildcard>(); var hasExpanded = window.Has<Expanded, 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, 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 +212,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?
@ -361,9 +376,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()) {
@ -471,13 +486,13 @@ public partial class EntityInspector
var ChildOf = world.Entity<Core.ChildOf>(); var ChildOf = world.Entity<Core.ChildOf>();
var Wildcard = world.Entity<Core.Wildcard>(); var Wildcard = world.Entity<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 +504,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 +536,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 +547,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 +555,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 +571,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 +581,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);
@ -649,7 +664,7 @@ public partial class EntityInspector
var world = entity.World; var world = entity.World;
var component = world.Entity<Core.Component>(); var component = world.Entity<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"]!;
@ -660,7 +675,7 @@ public partial class EntityInspector
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<Core.Component>(component)?.Size == 0))
type = world.Entity<Core.Tag>(); type = world.Entity<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; }
} }
@ -705,8 +720,8 @@ public partial class EntityInspector
if (!ImGui.IsRectVisible(pos, pos + dummySize)) { ImGui.Dummy(dummySize); return; } if (!ImGui.IsRectVisible(pos, pos + dummySize)) { ImGui.Dummy(dummySize); return; }
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;

@ -1,7 +1,6 @@
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 gaemstone.Flecs;
using Silk.NET.Input; using Silk.NET.Input;

@ -95,7 +95,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)

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 c575046a61620434e7ee01679ce86bf0beb0f700 Subproject commit 063fb40c5c56adbd5bedd39045f1b896b4e420c3

@ -120,7 +120,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 +134,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\tOnEnabled(module);");
sb.AppendLine("\t}"); sb.AppendLine($$"""
}
static void IModule.OnDisable<T>(Entity<T> module)
{
""");
sb.AppendLine("}"); if (module.IsBuiltIn)
sb.AppendLine("\t\tthrow new global::System.InvalidOperationException();");
if (module.HasLifetimeInterface)
sb.AppendLine("\t\tOnDisabled(module);");
sb.AppendLine($$"""
}
}
""");
} }
private void AppendEntityRegistration( private void AppendEntityRegistration(

@ -88,7 +88,7 @@ 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<gaemstone.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.ECS.DependsOnAttribute");

@ -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.ECS.IModuleLifetime");
IsBuiltIn = Has("BuiltIn"); IsBuiltIn = Has("BuiltIn");
} }

@ -13,6 +13,14 @@ public partial class Doc
[Tag] [Tag]
public struct Relation { } public struct Relation { }
/// <summary>
/// Tags are just <see cref="Flecs.Core.Component"/>s with 0 size.
/// This functions as a special entity that holds the appearance for tags.
/// Not related to <see cref="Flecs.Core.Tag"/> in any way.
/// </summary>
[Tag]
public struct Tag { }
// TODO: These need to actually be read at some point. // TODO: These need to actually be read at some point.

@ -6,13 +6,27 @@ namespace gaemstone.ECS;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class ModuleAttribute : SingletonAttribute { } public class ModuleAttribute : SingletonAttribute { }
public interface IModuleLifetime
{
void OnEnable<TContext>(Entity<TContext> module);
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 +36,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);
} }

@ -4,6 +4,7 @@ namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/core")] [BuiltIn, Module, Path("/flecs/core")]
public partial class Core public partial class Core
: IModuleImport
{ {
// Entity Tags // Entity Tags
@ -105,4 +106,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,25 @@
using System.Runtime.InteropServices;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/coredoc")]
[DependsOn<gaemstone.Flecs.Meta>]
[DependsOn<gaemstone.Flecs.Doc>]
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);
}

@ -6,7 +6,8 @@ using static flecs_hub.flecs;
namespace gaemstone.Flecs; namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/doc")] [BuiltIn, Module, Path("/flecs/doc")]
public partial class Doc public unsafe partial class Doc
: IModuleImport
{ {
[Tag] public struct Brief { } [Tag] public struct Brief { }
[Tag] public struct Detail { } [Tag] public struct Detail { }
@ -23,6 +24,19 @@ public partial class Doc
public static implicit operator string?(Description desc) public static implicit operator string?(Description desc)
=> desc.ToString(); => desc.ToString();
} }
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"))));
}
[UnmanagedCallersOnly]
private static void DocImport(ecs_world_t* world)
=> FlecsDocImport(world);
} }
public static unsafe class DocExtensions public static unsafe class DocExtensions

@ -0,0 +1,23 @@
using System.Runtime.InteropServices;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/meta")]
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,26 @@
using System.Runtime.InteropServices;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/timer")]
[DependsOn<gaemstone.Flecs.Pipeline>]
[DependsOn<gaemstone.Flecs.Meta>]
[DependsOn<gaemstone.Flecs.Units>]
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);
}

@ -3,18 +3,18 @@ using gaemstone.ECS;
using gaemstone.ECS.Utility; using gaemstone.ECS.Utility;
using static flecs_hub.flecs; using static flecs_hub.flecs;
namespace gaemstone.Flecs.Systems; namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/monitor")] [BuiltIn, Module, Path("/flecs/monitor")]
public unsafe partial class Monitor public unsafe partial class Monitor
: IModuleInitializer : IModuleImport
{ {
public static void Initialize<T>(Entity<T> module) static Entity<T> IModuleImport.Import<T>(World<T> world)
{ {
using var alloc = TempAllocator.Use(); using var alloc = TempAllocator.Use();
ecs_import_c(module.World, return Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
new() { Data = new() { Pointer = &MonitorImport } }, new() { Data = new() { Pointer = &MonitorImport } },
alloc.AllocateCString("FlecsMonitor")); alloc.AllocateCString("FlecsMonitor"))));
} }
[UnmanagedCallersOnly] [UnmanagedCallersOnly]

@ -1,9 +1,14 @@
using System.Runtime.InteropServices;
using gaemstone.ECS; using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs; namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/pipeline")] [BuiltIn, Module, Path("/flecs/pipeline")]
public partial class Pipeline [DependsOn<gaemstone.Flecs.System>]
public unsafe partial class Pipeline
: IModuleImport
{ {
[Entity] public struct Phase { } [Entity] public struct Phase { }
@ -90,4 +95,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);
} }

@ -3,22 +3,26 @@ using gaemstone.ECS;
using gaemstone.ECS.Utility; using gaemstone.ECS.Utility;
using static flecs_hub.flecs; using static flecs_hub.flecs;
namespace gaemstone.Flecs.Systems; namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/rest")] [BuiltIn, Module, Path("/flecs/rest")]
[DependsOn<gaemstone.Flecs.Pipeline>]
public unsafe partial class Rest public unsafe partial class Rest
: IModuleInitializer : IModuleImport
{ {
public static void Initialize<T>(Entity<T> module) static Entity<T> IModuleImport.Import<T>(World<T> world)
{ {
using (var alloc = TempAllocator.Use()) using var alloc = TempAllocator.Use();
ecs_import_c(module.World,
new() { Data = new() { Pointer = &RestImport } }, var module = Entity<T>.GetOrThrow(world, new(ecs_import_c(world,
alloc.AllocateCString("FlecsRest")); new() { Data = new() { Pointer = &RestImport } },
alloc.AllocateCString("FlecsRest"))));
module.NewChild("Rest").Build() module.NewChild("Rest").Build()
.CreateLookup<EcsRest>() .CreateLookup<EcsRest>()
.Set(new EcsRest { port = 27750 }); .Set(new EcsRest { port = 27750 });
return module;
} }
[UnmanagedCallersOnly] [UnmanagedCallersOnly]

@ -0,0 +1,24 @@
using System.Runtime.InteropServices;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/script")]
[DependsOn<gaemstone.Flecs.Meta>]
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,23 @@
using System.Runtime.InteropServices;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/system")]
public unsafe partial class System
: 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 = &SystemImport } },
alloc.AllocateCString("FlecsSystem"))));
}
[UnmanagedCallersOnly]
private static void SystemImport(ecs_world_t* world)
=> FlecsSystemImport(world);
}

@ -0,0 +1,24 @@
using System.Runtime.InteropServices;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/timer")]
[DependsOn<gaemstone.Flecs.Pipeline>]
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,23 @@
using System.Runtime.InteropServices;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs;
[BuiltIn, Module, Path("/flecs/units")]
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,8 +1,4 @@
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 static gaemstone.Flecs.Core;
using Module = gaemstone.Flecs.Core.Module; using Module = gaemstone.Flecs.Core.Module;
@ -10,126 +6,100 @@ 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) private readonly Rule<TContext> _findDisabledDeps;
=> _modules.GetValueOrDefault(entity); private readonly Rule<TContext> _findDependents;
public Entity<TContext> Register<T>() private readonly ECS.Variable _findDisabledDepsThisVar;
where T : IModule private readonly ECS.Variable _findDependentsModuleVar;
public ModuleManager(Universe<TContext> universe)
{ {
// if (!typeof(T).IsAssignableTo(typeof(IModule))) throw new ArgumentException( Universe = universe;
// $"The specified type {typeof(T)} does not implement IModule", nameof(T));
var module = new ModuleInfo<T>(Universe); World.New("/gaemstone/ModuleInfo").Symbol("ModuleInfo")
_modules.Add(module.Entity, module); .Build().InitComponent<IModuleInfo>();
TryEnableModule(module);
return module.Entity; _findDisabledDeps = new(World, new("(DependsOn, $dep), Disabled($dep)"));
_findDependents = new(World, new("ModuleInfo, Disabled, (DependsOn, $module)"));
_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)}");
} }
private void TryEnableModule(IModuleInfo module) public Entity<TContext> Import<T>()
where T : IModule, IModuleImport
{ {
if (!module.Dependencies.All(dep => dep.IsDependencyMet)) return; foreach (var dep in T.Dependencies)
if (World.LookupPathOrNull(dep) == null) throw new InvalidOperationException(
$"Missing required dependency {dep} for built-in module {T.Path}");
Console.WriteLine($"Enabling module {module.Entity.Path}"); Console.WriteLine($"Importing built-in module {T.Path}");
module.Enable(); var entity = T.Import(World);
entity.Set<IModuleInfo>(new ModuleInfo<T>());
T.OnEnable(entity);
return entity;
}
// Find other modules that might be missing this module as a dependency. public Entity<TContext> Register<T>()
foreach (var other in _modules.Values) { where T : IModule
if (other.IsInitialized) continue; {
var dependency = other.Dependencies.FirstOrDefault(dep => dep.Entity == module.Entity); if (T.IsBuiltIn) throw new ArgumentException(
if (dependency == null) continue; $"Unexpected operation, {T.Path} is a built-in module");
dependency.Info = module; var builder = World.New(T.Path)
dependency.IsDependencyMet = true; .Add<Module>().Add<Disabled>()
.Set<IModuleInfo>(new ModuleInfo<T>());
TryEnableModule(other); foreach (var depPath in T.Dependencies) {
var dependency = World.LookupPathOrNull(depPath) ??
World.New(depPath).Add<Module>().Add<Disabled>().Build();
builder.Add<DependsOn>(dependency);
} }
var module = builder.Build().CreateLookup<T>();
// Ensure all parent entities have the Module tag set.
for (var p = module.Parent; p is Entity<TContext> parent; p = parent.Parent)
parent.Add<Module>();
Console.WriteLine($"Registered module {module.Path}");
TryEnableModule(module);
return module;
} }
public interface IModuleInfo private void TryEnableModule(Entity<TContext> module)
{ {
Entity<TContext> Entity { get; } // Skip if module is already enabled.
IReadOnlyCollection<ModuleDependency> Dependencies { get; } if (module.IsEnabled) return;
bool IsInitialized { get; } // Skip if module has any not-yet-enabled dependencies.
void Enable(); if (_findDisabledDeps.Iter().SetVar(_findDisabledDepsThisVar, module).Any()) return;
}
// IEnumerable implementation Console.WriteLine($"Enabling module {module.Path}");
public IEnumerator<IModuleInfo> GetEnumerator() => _modules.Values.GetEnumerator(); module.GetOrThrow<IModuleInfo>().OnEnable(module);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 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 class ModuleDependency public interface IModuleInfo
{ {
public Entity<TContext> Entity { get; } void OnEnable(Entity<TContext> 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 internal class ModuleInfo<T> : IModuleInfo
where T : IModule where T : IModule
{ {
public Entity<TContext> Entity { get; } public void OnEnable(Entity<TContext> entity)
public IReadOnlyCollection<ModuleDependency> Dependencies { get; } => T.OnEnable(entity);
public bool IsInitialized { get; private set; }
public ModuleInfo(Universe<TContext> universe)
{
var world = universe.World;
if (T.IsBuiltIn)
{
Entity = world.LookupPathOrThrow(T.Path);
Dependencies = Array.Empty<ModuleDependency>();
}
else
{
var builder = world.New(T.Path);
var deps = new List<ModuleDependency>();
builder.Add<Module>();
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);
var isDepInit = (depModule?.IsInitialized == true);
deps.Add(new(dependency, depModule, isDepInit));
if (!isDepInit) builder.Add<Disabled>();
builder.Add<DependsOn>(dependency);
}
Entity = builder.Build().CreateLookup<T>();
Dependencies = deps.AsReadOnly();
// Ensure all parent entities have the Module tag set.
for (var p = Entity.Parent; p is Entity<TContext> parent; p = parent.Parent)
parent.Add<Module>();
}
}
public void Enable()
{
Entity.Enable();
T.Initialize(Entity);
IsInitialized = true;
}
} }
} }

@ -9,17 +9,22 @@ public class Universe<TContext>
public Universe() public Universe()
{ {
World = new(); 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