Compare commits
9 Commits
wip/source
...
main
Author | SHA1 | Date |
---|---|---|
copygirl | 6b78cb2985 | 1 year ago |
copygirl | 47530de27e | 1 year ago |
copygirl | 902ff21e48 | 1 year ago |
copygirl | a27800150a | 1 year ago |
copygirl | 330c11f217 | 1 year ago |
copygirl | 50c3ccd014 | 1 year ago |
copygirl | 5da1f9c937 | 1 year ago |
copygirl | 6b92e1ce8c | 1 year ago |
copygirl | d80e69006c | 1 year ago |
46 changed files with 1627 additions and 1259 deletions
@ -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 |
After Width: | Height: | Size: 751 KiB |
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@ |
||||
Subproject commit eec968d6361930eebad180f6300d126c4bac70f3 |
||||
Subproject commit 1607506e323b0c1513733d93dea619c22edd5cc7 |
@ -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. |
||||
/// </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. |
||||
/// 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 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 { } |
||||
} |
||||
|
@ -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); |
||||
} |
@ -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); |
||||
} |
@ -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,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,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; |
||||
|
||||
internal IModuleInfo? Lookup(Entity<TContext> entity) |
||||
=> _modules.GetValueOrDefault(entity); |
||||
public World<TContext> World => Universe.World; |
||||
|
||||
public Entity<TContext> Register<T>() |
||||
where T : IModule |
||||
{ |
||||
// if (!typeof(T).IsAssignableTo(typeof(IModule))) throw new ArgumentException( |
||||
// $"The specified type {typeof(T)} does not implement IModule", nameof(T)); |
||||
private readonly Rule<TContext> _findDisabledDeps; |
||||
private readonly Rule<TContext> _findDependents; |
||||
|
||||
var module = new ModuleInfo<T>(Universe); |
||||
_modules.Add(module.Entity, module); |
||||
TryEnableModule(module); |
||||
return module.Entity; |
||||
} |
||||
private readonly ECS.Variable _findDisabledDepsThisVar; |
||||
private readonly ECS.Variable _findDependentsModuleVar; |
||||
|
||||
private void TryEnableModule(IModuleInfo module) |
||||
public ModuleManager(Universe<TContext> universe) |
||||
{ |
||||
if (!module.Dependencies.All(dep => dep.IsDependencyMet)) return; |
||||
|
||||
Console.WriteLine($"Enabling module {module.Entity.Path}"); |
||||
Universe = universe; |
||||
|
||||
module.Enable(); |
||||
|
||||
// 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; |
||||
World.New("/gaemstone/ModuleInfo").Symbol("ModuleInfo") |
||||
.Build().InitComponent<IModuleInfo>(); |
||||
|
||||
dependency.Info = module; |
||||
dependency.IsDependencyMet = true; |
||||
_findDisabledDeps = World.Rule(new("(DependsOn, $dep), Disabled($dep)")); |
||||
_findDependents = World.Rule(new("ModuleInfo, Disabled, (DependsOn, $module)")); |
||||
|
||||
TryEnableModule(other); |
||||
} |
||||
_findDisabledDepsThisVar = _findDisabledDeps.ThisVar |
||||
?? throw new InvalidOperationException($"Could not find $this of {nameof(_findDisabledDeps)}"); |
||||
_findDependentsModuleVar = _findDependents.Variables["module"] |
||||
?? throw new InvalidOperationException($"Could not find $module of {nameof(_findDependents)}"); |
||||
} |
||||
|
||||
public interface IModuleInfo |
||||
public Entity<TContext> Import<T>() |
||||
where T : IModule, IModuleImport |
||||
{ |
||||
Entity<TContext> Entity { get; } |
||||
IReadOnlyCollection<ModuleDependency> Dependencies { get; } |
||||
bool IsInitialized { get; } |
||||
void Enable(); |
||||
} |
||||
foreach (var dep in T.Dependencies) |
||||
if (World.LookupPathOrNull(dep) == null) throw new InvalidOperationException( |
||||
$"Missing required dependency {dep} for built-in module {T.Path}"); |
||||
|
||||
// IEnumerable implementation |
||||
public IEnumerator<IModuleInfo> GetEnumerator() => _modules.Values.GetEnumerator(); |
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
||||
Console.WriteLine($"Importing built-in module {T.Path}"); |
||||
|
||||
|
||||
public class ModuleDependency |
||||
{ |
||||
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; |
||||
} |
||||
var entity = T.Import(World); |
||||
entity.Set<IModuleInfo>(new ModuleInfo<T>()); |
||||
T.OnEnable(entity); |
||||
return entity; |
||||
} |
||||
|
||||
internal class ModuleInfo<T> : IModuleInfo |
||||
public Entity<TContext> Register<T>() |
||||
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>(); |
||||
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); |
||||
} |
||||
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 module = builder.Build().CreateLookup<T>(); |
||||
|
||||
var depModule = universe.Modules.Lookup(dependency); |
||||
var isDepInit = (depModule?.IsInitialized == 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>(); |
||||
|
||||
deps.Add(new(dependency, depModule, isDepInit)); |
||||
if (!isDepInit) builder.Add<Disabled>(); |
||||
builder.Add<DependsOn>(dependency); |
||||
Console.WriteLine($"Registered module {module.Path}"); |
||||
|
||||
TryEnableModule(module); |
||||
return module; |
||||
} |
||||
|
||||
Entity = builder.Build().CreateLookup<T>(); |
||||
Dependencies = deps.AsReadOnly(); |
||||
private void TryEnableModule(Entity<TContext> module) |
||||
{ |
||||
// Skip if module is already enabled. |
||||
if (module.IsEnabled) return; |
||||
// Skip if module has any not-yet-enabled dependencies. |
||||
if (_findDisabledDeps.Iter().SetVar(_findDisabledDepsThisVar, module).Any()) return; |
||||
|
||||
// Ensure all parent entities have the Module tag set. |
||||
for (var p = Entity.Parent; p is Entity<TContext> parent; p = parent.Parent) |
||||
parent.Add<Module>(); |
||||
} |
||||
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 void Enable() |
||||
public interface IModuleInfo |
||||
{ |
||||
Entity.Enable(); |
||||
T.Initialize(Entity); |
||||
IsInitialized = true; |
||||
void OnEnable(Entity<TContext> entity); |
||||
} |
||||
|
||||
internal class ModuleInfo<T> : IModuleInfo |
||||
where T : IModule |
||||
{ |
||||
public void OnEnable(Entity<TContext> entity) |
||||
=> T.OnEnable(entity); |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue