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. 165
      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. 174
      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 gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;

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

@ -23,11 +23,15 @@ var universe = new Universe<Program>();
var world = universe.World;
// TODO: Figure out a nice way to get rid of "compile errors" here.
// FIXME: universe.Modules.Register<gaemstone.Flecs.Systems.Monitor>();
universe.Modules.Register<gaemstone.Flecs.Systems.Rest>();
universe.Modules.Import<flecs.timer>();
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 {
Title = "gæmstone",
Title = "gæmstone - Immersion",
Size = new(1280, 720),
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.Systems.FreeCameraController>();
foreach (var module in universe.Modules)
if (!module.IsInitialized) throw new InvalidOperationException(
$"Module '{module.Entity.Path}' is not initialized");
using (var disabledModules = world.Filter(new("ModuleInfo, Disabled")))
foreach (var module in disabledModules.Iter().GetAllEntities())
throw new InvalidOperationException($"Module '{module.Path}' is not ednbled");
// Initialize Canvas and GameWindow singletons with actual values.
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>(
World<T> world, Canvas canvas,
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);
if (result is MeshHandle handle) {

@ -4,10 +4,8 @@ using System.Linq;
using System.Numerics;
using gaemstone.Client.Utility;
using gaemstone.ECS;
using gaemstone.Flecs;
using ImGuiNET;
using static gaemstone.Client.Systems.ImGuiManager;
using Icon = gaemstone.Client.Utility.ForkAwesome;
using ImGuiInternal = ImGuiNET.Internal.ImGui;
namespace gaemstone.Client.Systems;
@ -15,13 +13,13 @@ namespace gaemstone.Client.Systems;
[Module]
[DependsOn<gaemstone.Client.Systems.ImGuiManager>]
public partial class EntityInspector
: IModuleInitializer
: IModuleLifetime
{
[Tag]
public struct InspectorWindow { }
[Relation, Exclusive]
[Add<Core.OnDeleteTarget, Core.Delete>]
[Add<flecs.core.OnDeleteTarget, flecs.core.Delete>]
public struct Selected { }
[Tag]
@ -54,43 +52,68 @@ public partial class EntityInspector
}
[Component]
public struct DocPriority { public float Value; }
public record struct Priority(float Value);
[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";
public static void Initialize<T>(Entity<T> module)
public static void OnEnable<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);
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);
SetDocInfo("/gaemstone/Doc/Relation" , 3 , Icon.ShareAlt , 0.7f, 1.0f, 0.8f);
SetDocInfo("/flecs/core/Component" , 4 , Icon.PencilSquare , 0.6f, 0.6f, 1.0f);
// TODO: Handle tags like Flecs does.
SetDocInfo("/flecs/core/Tag" , 5 , Icon.Tag , 0.7f, 0.8f, 1.0f);
SetDocInfo("/flecs/core/Prefab" , 6 , Icon.Cube , 0.9f, 0.8f, 1.0f);
var world = module.World;
world.LookupPathOrNull("/flecs/core/Module")?.SetDocColor("#FFE4B2");
world.LookupPathOrNull("/flecs/system/System")?.SetDocColor("#FFB2B2");
world.LookupPathOrNull("/flecs/core/Observer")?.SetDocColor("#FFCCCC");
world.LookupPathOrNull("/gaemstone/Doc/Relation")?.SetDocColor("#B2FFCC");
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]
public static void ShowUIButton<T>(World<T> world, ImGuiData _)
{
var hasAnyInspector = false;
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; }
if (ImGuiUtility.UIButton(0, Icon.Search, DefaultWindowTitle, hasAnyInspector))
if (ImGuiUtility.UIButton(0, ForkAwesome.Search, DefaultWindowTitle, hasAnyInspector))
NewEntityInspectorWindow(world);
}
@ -104,7 +127,7 @@ public partial class EntityInspector
ImGui.SetNextWindowSize(new(fontSize * 40, fontSize * 25), ImGuiCond.Appearing);
ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[1]);
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)) {
ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[0]);
@ -132,9 +155,9 @@ public partial class EntityInspector
ImGui.TableNextColumn();
ImGui.BeginChild("EntityView", new(-float.Epsilon, -float.Epsilon));
if (!ImGui.BeginTabBar("Tabs")) return;
Tab($"{Icon.PencilSquare} Components", ComponentsTab);
Tab($"{Icon.ShareAlt} References", ReferencesTab);
Tab($"{Icon.InfoCircle} Documentation", DocumentationTab);
Tab($"{ForkAwesome.PencilSquare} Components", ComponentsTab);
Tab($"{ForkAwesome.ShareAlt} References", ReferencesTab);
Tab($"{ForkAwesome.InfoCircle} Documentation", DocumentationTab);
ImGui.EndTabBar();
ImGui.EndChild();
@ -149,7 +172,7 @@ public partial class EntityInspector
if (!isOpen) window.Delete();
}
[Observer<Core.OnRemove>]
[Observer<flecs.core.OnRemove>]
public static void ClearStorageOnRemove<T>(Entity<T> _1, InspectorWindow _2)
{
// 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)
{
static bool IconButtonWithToolTip(string icon, string tooltip, bool enabled = true) {
static bool IconButtonWithToolTip(char icon, string tooltip, bool enabled = true) {
if (!enabled) ImGui.BeginDisabled();
var clicked = ImGui.Button(icon);
var clicked = ImGui.Button(icon.ToString());
if (!enabled) ImGui.EndDisabled();
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip(tooltip);
@ -173,23 +196,23 @@ public partial class EntityInspector
ImGui.TableSetupColumn("Entity", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableNextColumn();
var hasExpanded = window.Has<Expanded, Core.Wildcard>();
if (IconButtonWithToolTip(Icon.Outdent, "Collapse all items in the Explorer View", hasExpanded))
window.Remove<Expanded, Core.Wildcard>();
var hasExpanded = window.Has<Expanded, flecs.core.Wildcard>();
if (IconButtonWithToolTip(ForkAwesome.Outdent, "Collapse all items in the Explorer View", hasExpanded))
window.Remove<Expanded, flecs.core.Wildcard>();
if (history != null) {
var hasPrev = ((selected != null) ? history.Current?.Prev : history.Current) != null;
var hasNext = history.Current?.Next != null;
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);
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);
}
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>();
ImGui.TableNextColumn();
@ -197,22 +220,22 @@ public partial class EntityInspector
PathInput(window, history, selected, availableWidth);
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());
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!
ImGui.SameLine();
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";
if (IconButtonWithToolTip(icon, tooltip, (selected != null)))
{ if (isDisabled) selected?.Enable(); else selected?.Disable(); }
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?
SetSelected(window, history, selected?.Parent);
selected?.Delete(); // TODO: Confirmation dialog?
@ -350,10 +373,10 @@ public partial class EntityInspector
// nullable, so let's be explicit about the type here.
var world = window.World;
var Wildcard = world.Entity<Core.Wildcard>().Value;
var Any = world.Entity<Core.Any>().Value;
var This = world.Entity<Core.This>().Value;
var Variable = world.Entity<Core.Variable>().Value;
var Wildcard = world.Entity<flecs.core.Wildcard>().Value;
var Any = world.Entity<flecs.core.Any>().Value;
var This = world.Entity<flecs.core.This>().Value;
var Variable = world.Entity<flecs.core.Variable>().Value;
bool IsSpecialEntity(Entity entity)
=> (entity == Wildcard) || (entity == Any)
|| (entity == This) || (entity == Variable);
@ -361,9 +384,9 @@ public partial class EntityInspector
var expId = world.Entity<Expanded>().NumericId;
List<IExplorerEntry> GetEntries(Entity? parent) {
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.
+ $",?{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.
));
foreach (var iter in rule.Iter()) {
@ -372,7 +395,7 @@ public partial class EntityInspector
for (var i = 0; i < iter.Count; i++) {
var entity = iter.Entity(i);
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));
}
}
@ -456,7 +479,7 @@ public partial class EntityInspector
private static void ComponentsTab<T>(Entity<T> window, History? history, Entity<T>? sel)
{
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) {
// Hide ChildOf relations, as they are visible in the explorer.
if (id.IsPair && (id.Value.RelationUnsafe == ChildOf)) continue;
@ -468,16 +491,16 @@ public partial class EntityInspector
{
if (sel is not Entity<T> selected) return;
var world = window.World;
var ChildOf = world.Entity<Core.ChildOf>();
var Wildcard = world.Entity<Core.Wildcard>();
var ChildOf = world.Entity<flecs.core.ChildOf>();
var Wildcard = world.Entity<flecs.core.Wildcard>();
if (ImGui.CollapsingHeader($"As {Icon.Tag} Component", ImGuiTreeNodeFlags.DefaultOpen))
foreach (var iter in Iterator<T>.FromTerm(world, new(selected)))
if (ImGui.CollapsingHeader($"As {ForkAwesome.Tag} Component", ImGuiTreeNodeFlags.DefaultOpen))
foreach (var iter in world.Term(new(selected)))
for (var i = 0; i < iter.Count; i++)
RenderEntity(window, history, iter.Entity(i));
if (ImGui.CollapsingHeader($"As {Icon.ShareAlt} Relation", ImGuiTreeNodeFlags.DefaultOpen))
foreach (var iter in Iterator<T>.FromTerm(world, new(selected, Wildcard))) {
if (ImGui.CollapsingHeader($"As {ForkAwesome.ShareAlt} Relation", ImGuiTreeNodeFlags.DefaultOpen))
foreach (var iter in world.Term(new(selected, Wildcard))) {
var id = iter.FieldId(1);
if (id.AsPair() is not (Entity<T> relation, Entity<T> target)) throw new InvalidOperationException();
if (relation == ChildOf) continue; // Hide ChildOf relations.
@ -489,8 +512,8 @@ public partial class EntityInspector
}
}
if (ImGui.CollapsingHeader($"As {Icon.Bullseye} Target", ImGuiTreeNodeFlags.DefaultOpen))
foreach (var iter in Iterator<T>.FromTerm(world, new(Wildcard, selected))) {
if (ImGui.CollapsingHeader($"As {ForkAwesome.Bullseye} Target", ImGuiTreeNodeFlags.DefaultOpen))
foreach (var iter in world.Term(new(Wildcard, selected))) {
var id = iter.FieldId(1);
if (id.AsPair() is not (Entity<T> relation, Entity<T> target)) throw new InvalidOperationException();
if (relation == ChildOf) continue; // Hide ChildOf relations.
@ -521,7 +544,7 @@ public partial class EntityInspector
if (fill) ImGui.SetNextItemWidth(-float.Epsilon);
}
Column($"{Icon.Tag} Display Name", """
Column($"{ForkAwesome.Tag} Display Name", """
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.
@ -532,7 +555,7 @@ public partial class EntityInspector
selected?.SetDocName((name.Length > 0) ? name : null);
if (!hasSelected) ImGui.EndDisabled();
Column($"{Icon.Comment} Description",
Column($"{ForkAwesome.Comment} Description",
"A brief description of this entity.");
if (!hasSelected) ImGui.BeginDisabled();
var brief = selected?.GetDocBrief() ?? "";
@ -540,7 +563,7 @@ public partial class EntityInspector
selected?.SetDocBrief((brief.Length > 0) ? brief : null);
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.
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);
if (!hasSelected) ImGui.EndDisabled();
Column($"{Icon.Link} Link", """
Column($"{ForkAwesome.Link} Link", """
A link to a website relating to this entity, such as
a module's repository, or further documentation.
""");
@ -566,7 +589,7 @@ public partial class EntityInspector
selected?.SetDocLink((link.Length > 0) ? link : null);
if (!hasSelected) ImGui.EndDisabled();
Column($"{Icon.PaintBrush} Color", """
Column($"{ForkAwesome.PaintBrush} Color", """
A custom color to represent this entity.
Used in the entity inspector's explorer view.
""", false);
@ -607,7 +630,7 @@ public partial class EntityInspector
bool scrollTo = true) // Should entity be scrolled to in the explorer view?
{
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)
window.Add<Expanded>(parent);
@ -647,9 +670,9 @@ public partial class EntityInspector
private static (Entity<T>? DisplayType, float Priority) FindDisplayType<T>(Entity<T> entity)
{
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)")));
var typeVar = rule.Variables["Type"]!;
@ -658,9 +681,9 @@ public partial class EntityInspector
foreach (var iter in rule.Iter().SetVar(rule.ThisVar!, entity))
for (var i = 0; i < iter.Count; i++) {
var type = iter.GetVar(typeVar);
if ((type == component) && (entity.GetOrNull<Core.Component>(component)?.Size == 0))
type = world.Entity<Core.Tag>();
var priority = type?.GetOrNull<DocPriority>()?.Value ?? float.MaxValue;
if ((type == component) && (entity.GetOrNull<flecs.core.Component>(component)?.Size == 0))
type = world.Entity<flecs.core.Tag>();
var priority = type?.GetOrNull<Priority>()?.Value ?? float.MaxValue;
if (priority <= curPriority) { curType = type; curPriority = priority; }
}
@ -705,8 +728,8 @@ public partial class EntityInspector
if (!ImGui.IsRectVisible(pos, pos + dummySize)) { ImGui.Dummy(dummySize); return; }
var (displayType, _) = FindDisplayType(entity);
var docColor = Color.TryParseHex(entity.GetDocColor()) ?? Color.TryParseHex(displayType?.GetDocColor());
var docIcon = entity.GetOrNull<DocIcon>()?.Value.ToString() ?? displayType?.GetOrNull<DocIcon>()?.Value.ToString();
var docColor = Color.TryParseHex(entity.GetDocColor()) ?? Color.TryParseHex(displayType?.GetDocColor());
var docIcon = entity.GetOrNull<Icon>()?.Value.ToString() ?? displayType?.GetOrNull<Icon>()?.Value.ToString();
var docName = entity.GetDocName(false);
var isDisabled = entity.IsDisabled;
@ -749,7 +772,7 @@ public partial class EntityInspector
if (isHeaderLike) pos.X += ImGui.GetStyle().FramePadding.X;
drawList.AddText(ImGui.GetFont(), ImGui.GetFontSize(), pos, color.RGBA, displayName);
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 p2 = pos + new Vector2(size.X, size.Y - 1.75f);
if (docIcon != null) p1.X += ImGui.CalcTextSize($"{docIcon} ").X;

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

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

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

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

@ -1,6 +1,4 @@
using System.Drawing;
using gaemstone.ECS;
using gaemstone.Flecs;
using Silk.NET.OpenGL;
using Silk.NET.Windowing;
@ -29,7 +27,7 @@ public partial class Windowing
}
[System]
[DependsOn<Pipeline.PreFrame>]
[DependsOn<flecs.pipeline.PreFrame>]
public static void ProcessWindow(GameWindow window, Canvas canvas)
{
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 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)
{ 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)
{
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)
=> sb.AppendLine($$"""
// <auto-generated/>
#pragma warning disable
using System.Collections.Generic;
using System.Collections.Immutable;
using gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
@ -120,7 +122,7 @@ public class ModuleGenerator
sb.AppendLine();
sb.AppendLine($$"""
static void IModule.Initialize<T>(Entity<T> module)
static void IModule.OnEnable<T>(Entity<T> module)
{
var world = module.World;
""");
@ -134,12 +136,25 @@ public class ModuleGenerator
// TODO: Can BuiltIn modules have systems and such?
if (module.HasInitializer)
sb.AppendLine("\t\tInitialize(module);");
if (module.HasLifetimeInterface)
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(
@ -175,8 +190,8 @@ public class ModuleGenerator
sb.AppendLine($"\t\t\t.Symbol({e.EntitySymbol.ToStringLiteral()})");
// 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<gaemstone.Flecs.Core.Tag>()");
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<flecs.core.Tag>()");
sb.Append( "\t\t\t");
if (!e.IsBuiltIn) sb.Append(".Build()");
@ -184,6 +199,12 @@ public class ModuleGenerator
else sb.Append($".CreateLookup<{e.FullName}>()");
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.
// Especially for singletons that are reference types, which would default to null.
// 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 (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
: ISyntaxContextReceiver
{
// Attributes from gaemstone and gaemstone.Doc are considered.
private static readonly HashSet<string> RelevantAttributeNames = new(){
// Base entity attributes
"Module", // Can also be [Singleton]
@ -24,7 +25,7 @@ public class RelevantSymbolReceiver
"Observer",
// Entity properties that specify additional info / behavior
"Path", // TODO: When referring to a pre-existing entity, only [Path] should be necessary, right?
"Path",
"Symbol",
"Add",
"Set",
@ -37,6 +38,13 @@ public class RelevantSymbolReceiver
"Has",
"Not",
"Or",
// Documentation attributes
"Name",
"Brief",
"Detail",
"Link",
"Color",
};
public Dictionary<ISymbol, BaseInfo> Symbols { get; } = new(SymbolEqualityComparer.Default);
@ -60,7 +68,7 @@ public class RelevantSymbolReceiver
Symbols.Add(symbol, symbol switch {
INamedTypeSymbol typeSymbol =>
typeSymbol.GetAttributes().Any(attr => attr.AttributeClass!
.GetFullName() == "gaemstone.ECS.ModuleAttribute")
.GetFullName() == "gaemstone.ModuleAttribute")
? new ModuleEntityInfo(typeSymbol)
: new TypeEntityInfo(typeSymbol),
IMethodSymbol methodSymbol => new MethodEntityInfo(methodSymbol),
@ -75,8 +83,13 @@ public class RelevantSymbolReceiver
public static string? ToRelevantAttributeName(INamedTypeSymbol symbol)
{
if (symbol.GetNamespace() != "gaemstone.ECS") return null;
var name = symbol.MetadataName.Split('`')[0];
return name.EndsWith("Attribute") ? name[..^"Attribute".Length] : null;
var name = symbol.GetFullName(FullNameStyle.NoGeneric);
if (!name.EndsWith("Attribute")) return 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? 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 Relation, INamedTypeSymbol Target)> RelationsToAdd { 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], ..
?? Name // .. or just use the default: The symbol's name.
: 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()

@ -88,9 +88,9 @@ public class MethodEntityInfo : BaseEntityInfo
param.TermIndex = termIndex++;
// 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
.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.ECS.DependsOnAttribute");
.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.DependsOnAttribute");
// TODO: Handle systems with [Source].
// TODO: Validate ObserverEvents.

@ -10,7 +10,7 @@ namespace gaemstone.SourceGen.Structure;
public class ModuleEntityInfo : TypeEntityInfo
{
public bool IsPartial { get; }
public bool HasInitializer { get; }
public bool HasLifetimeInterface { get; }
public ModuleEntityInfo(ISymbol symbol)
: base(symbol)
@ -18,8 +18,8 @@ public class ModuleEntityInfo : TypeEntityInfo
var classDecl = (TypeDeclarationSyntax)Symbol.DeclaringSyntaxReferences.First().GetSyntax();
IsPartial = classDecl.Modifiers.Any(t => t.IsKind(SyntaxKind.PartialKeyword));
HasInitializer = Symbol.AllInterfaces.Any(i =>
i.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.ECS.IModuleInitializer");
HasLifetimeInterface = Symbol.AllInterfaces.Any(i =>
i.GetFullName(FullNameStyle.NoGeneric) == "gaemstone.IModuleLifetime");
IsBuiltIn = Has("BuiltIn");
}
@ -38,7 +38,7 @@ public class ModuleEntityInfo : TypeEntityInfo
public IEnumerable<string> GetDependencies()
{
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);
}

@ -54,9 +54,9 @@ public class ParameterInfo : BaseInfo
}
else
{
IsOr = typeFullName.StartsWith("gaemstone.ECS.Or");
var isHas = typeFullName.StartsWith("gaemstone.ECS.Has");
var isNot = typeFullName.StartsWith("gaemstone.ECS.Not");
IsOr = typeFullName.StartsWith("gaemstone.Or");
var isHas = typeFullName.StartsWith("gaemstone.Has");
var isNot = typeFullName.StartsWith("gaemstone.Not");
if (IsGeneric)
{
@ -66,7 +66,7 @@ public class ParameterInfo : BaseInfo
// 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.
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();
FieldType = null;
@ -91,13 +91,13 @@ public class ParameterInfo : BaseInfo
// If the type of the parameter has the [Tag] attribute,
// 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.)
if (Symbol.Type.HasAttribute("gaemstone.ECS.TagAttribute")) isHas = true;
if (Symbol.Type.HasAttribute("gaemstone.TagAttribute")) isHas = true;
// TODO: Make sure [Tag] is used appropriately.
}
Source = Get("Source")?.AttributeClass!.TypeArguments[0]
// 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
: isNot ? ParameterKind.Not

@ -96,23 +96,3 @@ public enum FullNameStyle
Metadata, // Namespace.Foo+Bar`1
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;
[Module]
public partial class Doc
{
[Tag]
public struct DisplayType { }
[Tag]
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.
/// Tags are just <see cref="Flecs.Core.Component"/>s with 0 size.
/// This functions as a special entity that holds the appearance for tags
/// 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>
[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>
/// 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 Detail : Attribute
{
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;
}
[Tag]
public struct Tag { }
/// <summary>
/// A custom color to represent this entity.
/// Displayed in the Entity Inspector.
/// Added to entities that represent a unique type of entity that should
/// 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>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
public class Color : Attribute
{
public float Red { get; }
public float Green { get; }
public float Blue { get; }
public Color(float red, float green, float blue)
{
if ((red < 0.0f) || (red > 1.0f)) throw new ArgumentOutOfRangeException(nameof(red ));
if ((green < 0.0f) || (green > 1.0f)) throw new ArgumentOutOfRangeException(nameof(green));
if ((blue < 0.0f) || (blue > 1.0f)) throw new ArgumentOutOfRangeException(nameof(blue ));
Red = red; Green = green; Blue = blue;
}
}
/// <details>
/// <p>
/// Examples of such display types include:
/// <list>
/// <item> <see cref="Flecs.Core.Module"/> </item>
/// <item> <see cref="Flecs.Core.Component"/> </item>
/// <item> <see cref="Flecs.Core.Observer"/> </item>
/// <item> <see cref="Flecs.System.System"/> </item>
/// <item> <see cref="gaemstone.Doc.Relation"/> </item>
/// <item> <see cref="gaemstone.Doc.Tag"/> </item>
/// </list>
/// </p>
/// <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;
namespace gaemstone.Flecs;
namespace flecs;
[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
[Tag] public struct Observer { }
// TODO: Put back World?
[Tag] public struct Module { }
[Tag] public struct Private { }
[Tag] public struct Prefab { }
@ -105,4 +112,8 @@ public partial class Core
public static implicit operator string?(Identifier id)
=> 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 gaemstone;
using gaemstone.ECS;
using gaemstone.ECS.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs;
namespace flecs;
[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 Detail { }
@ -23,48 +27,17 @@ public partial class Doc
public static implicit operator string?(Description desc)
=> 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)
=> fallbackToEntityName || entity.Has<Doc.Description, 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, 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);
[UnmanagedCallersOnly]
private static void DocImport(ecs_world_t* world)
=> FlecsDocImport(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/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.Utility;
using static flecs_hub.flecs;
namespace gaemstone.Flecs;
namespace flecs;
[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 { }
@ -90,4 +98,17 @@ public partial class Pipeline
[Entity, Add<Phase>]
[DependsOn<OnStore>]
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 static gaemstone.Flecs.Core;
namespace gaemstone.ECS;
namespace gaemstone;
/// <summary>
/// Entities marked with this attribute are automatically registered with a
@ -57,13 +56,22 @@ public class SetAttribute<TComponent> : Attribute
/// <seealso cref="IsA"/>
public class IsAAttribute<TTarget> : AddAttribute<IsA, TTarget> { }
public class IsAAttribute<TTarget>
: AddAttribute<flecs.core.IsA, TTarget> { }
/// <seealso cref="ChildOf"/>
public class ChildOfAttribute<TTarget> : AddAttribute<ChildOf, TTarget> { }
public class ChildOfAttribute<TTarget>
: AddAttribute<flecs.core.ChildOf, TTarget> { }
/// <seealso cref="DependsOn"/>
public class DependsOnAttribute<TTarget> : AddAttribute<DependsOn, TTarget> { }
public class DependsOnAttribute<TTarget>
: AddAttribute<flecs.core.DependsOn, TTarget> { }
/// <seealso cref="Exclusive"/>
public class ExclusiveAttribute : AddAttribute<Exclusive> { }
public class ExclusiveAttribute
: AddAttribute<flecs.core.Exclusive> { }
/// <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;
namespace gaemstone.ECS;
namespace gaemstone;
/// <summary> Use a custom name or path for this entity instead of the type's name. </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct

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

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

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

@ -1,135 +1,107 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using gaemstone.ECS;
using static gaemstone.Flecs.Core;
using Module = gaemstone.Flecs.Core.Module;
namespace gaemstone;
public class ModuleManager<TContext>
: IEnumerable<ModuleManager<TContext>.IModuleInfo>
{
private readonly Dictionary<Entity<TContext>, IModuleInfo> _modules = new();
public Universe<TContext> Universe { get; }
public ModuleManager(Universe<TContext> universe)
=> Universe = universe;
public World<TContext> World => Universe.World;
internal IModuleInfo? Lookup(Entity<TContext> entity)
=> _modules.GetValueOrDefault(entity);
private readonly Rule<TContext> _findDisabledDeps;
private readonly Rule<TContext> _findDependents;
public Entity<TContext> Register<T>()
where T : IModule
private readonly ECS.Variable _findDisabledDepsThisVar;
private readonly ECS.Variable _findDependentsModuleVar;
public ModuleManager(Universe<TContext> universe)
{
// if (!typeof(T).IsAssignableTo(typeof(IModule))) throw new ArgumentException(
// $"The specified type {typeof(T)} does not implement IModule", nameof(T));
Universe = universe;
var module = new ModuleInfo<T>(Universe);
_modules.Add(module.Entity, module);
TryEnableModule(module);
return module.Entity;
World.New("/gaemstone/ModuleInfo").Symbol("ModuleInfo")
.Build().InitComponent<IModuleInfo>();
_findDisabledDeps = World.Rule(new("(DependsOn, $dep), Disabled($dep)"));
_findDependents = World.Rule(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;
}
public Entity<TContext> Register<T>()
where T : IModule
{
if (T.IsBuiltIn) throw new ArgumentException(
$"Unexpected operation, {T.Path} is a built-in module");
var builder = World.New(T.Path)
.Add<flecs.core.Module>()
.Add<flecs.core.Disabled>()
.Set<IModuleInfo>(new ModuleInfo<T>());
foreach (var depPath in T.Dependencies) {
var dependency = World.LookupPathOrNull(depPath)
?? World.New(depPath)
.Add<flecs.core.Module>()
.Add<flecs.core.Disabled>()
.Build();
builder.Add<flecs.core.DependsOn>(dependency);
}
// 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;
var module = builder.Build().CreateLookup<T>();
dependency.Info = module;
dependency.IsDependencyMet = true;
// Ensure all parent entities have the Module tag set.
for (var p = module.Parent; p is Entity<TContext> parent; p = parent.Parent)
parent.Add<flecs.core.Module>();
TryEnableModule(other);
}
Console.WriteLine($"Registered module {module.Path}");
TryEnableModule(module);
return module;
}
public interface IModuleInfo
private void TryEnableModule(Entity<TContext> module)
{
Entity<TContext> Entity { get; }
IReadOnlyCollection<ModuleDependency> Dependencies { get; }
bool IsInitialized { get; }
void Enable();
}
// 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;
// IEnumerable implementation
public IEnumerator<IModuleInfo> GetEnumerator() => _modules.Values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
Console.WriteLine($"Enabling module {module.Path}");
module.GetOrThrow<IModuleInfo>().OnEnable(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 class ModuleDependency
public interface IModuleInfo
{
public Entity<TContext> Entity { get; }
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;
}
void OnEnable(Entity<TContext> entity);
}
internal class ModuleInfo<T> : IModuleInfo
where T : IModule
{
public Entity<TContext> Entity { get; }
public IReadOnlyCollection<ModuleDependency> Dependencies { get; }
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;
}
public void OnEnable(Entity<TContext> entity)
=> T.OnEnable(entity);
}
}

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

Loading…
Cancel
Save