From 0e71a7434a1e3c887d566abed147dde3905c95f3 Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Wed, 18 Mar 2026 16:17:25 +0900
Subject: [PATCH 01/14] feat(common): editor core types
---
packages/common/src/options/index.ts | 9 ++++-
.../common/src/options/types/options.type.ts | 21 ++++++++++
.../common/src/options/types/save.type.ts | 40 +++++++++++++++++++
3 files changed, 69 insertions(+), 1 deletion(-)
create mode 100644 packages/common/src/options/types/save.type.ts
diff --git a/packages/common/src/options/index.ts b/packages/common/src/options/index.ts
index 32ab2502..1bd44e31 100644
--- a/packages/common/src/options/index.ts
+++ b/packages/common/src/options/index.ts
@@ -1 +1,8 @@
-export type { IRunClientOptions, IRunOptions, IRunServerOptions } from "./types/options.type";
+export type {
+ IRunClientOptions,
+ IRunOptions,
+ IRunServerOptions,
+ IEditorRunClientOptions,
+ IEditorRunOptions,
+ IEditorRunServerOptions,
+} from "./types/options.type";
diff --git a/packages/common/src/options/types/options.type.ts b/packages/common/src/options/types/options.type.ts
index 3ed47674..5cab0ac1 100644
--- a/packages/common/src/options/types/options.type.ts
+++ b/packages/common/src/options/types/options.type.ts
@@ -1,3 +1,5 @@
+import { type Save } from "./save.type";
+
export type IRunOptions = IRunClientOptions | IRunServerOptions;
export interface IRunClientOptions {
@@ -10,3 +12,22 @@ export interface IRunServerOptions {
files: Map;
env: Record;
}
+
+export type IEditorRunOptions = IEditorRunClientOptions | IEditorRunServerOptions;
+
+export interface IEditorRunClientOptions {
+ canvas: HTMLCanvasElement;
+ files: Map;
+ env: Record;
+ editor: {
+ save: Save;
+ };
+}
+export interface IEditorRunServerOptions {
+ canvas: HTMLCanvasElement;
+ files: Map;
+ env: Record;
+ editor: {
+ save: Save;
+ };
+}
diff --git a/packages/common/src/options/types/save.type.ts b/packages/common/src/options/types/save.type.ts
new file mode 100644
index 00000000..94371ad1
--- /dev/null
+++ b/packages/common/src/options/types/save.type.ts
@@ -0,0 +1,40 @@
+export enum SaveLibraryTypeEnum {
+ COMPONENT_SYSTEM = "component-system",
+ GRAPHICS = "graphics",
+ ASSET_MANAGER = "asset-manager",
+ NETWORK = "network",
+ INPUT = "input",
+ SOUND = "sound",
+}
+
+export interface SaveLibrary {
+ id: string;
+ type: SaveLibraryTypeEnum | string;
+ name: string;
+ path: string;
+}
+
+export interface SaveComponent {
+ name: string;
+ path: string;
+}
+
+export interface SaveSystem {
+ name: string;
+ path: string;
+}
+
+export interface SaveEntity {
+ id: string;
+ components: {
+ name: string;
+ params: string[];
+ }[];
+}
+
+export interface Save {
+ libraries: SaveLibrary[];
+ components: SaveComponent[];
+ systems: SaveSystem[];
+ entities: SaveEntity[];
+}
From 151811457a3a88f64ade2141acfbcaa074b176cf Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Wed, 18 Mar 2026 16:17:48 +0900
Subject: [PATCH 02/14] feat(common): editor core types
---
packages/core-editor/.cliff-jumperrc.json | 7 +
packages/core-editor/.gitignore | 231 ++++++++++++++++++
packages/core-editor/.idea/.name | 1 +
.../.idea/[NanoForge] Engine Core.iml | 12 +
.../core-editor/.idea/codeStyles/Project.xml | 57 +++++
.../.idea/codeStyles/codeStyleConfig.xml | 5 +
.../core-editor/.idea/git_toolbox_blame.xml | 6 +
.../core-editor/.idea/git_toolbox_prj.xml | 15 ++
.../inspectionProfiles/Project_Default.xml | 6 +
.../core-editor/.idea/jsLinters/eslint.xml | 6 +
packages/core-editor/.idea/modules.xml | 8 +
packages/core-editor/.idea/prettier.xml | 7 +
packages/core-editor/.idea/vcs.xml | 6 +
packages/core-editor/.nvmrc | 1 +
packages/core-editor/.prettierignore | 11 +
packages/core-editor/CHANGELOG.md | 59 +++++
packages/core-editor/LICENSE | 21 ++
packages/core-editor/README.md | 68 ++++++
packages/core-editor/cliff.toml | 79 ++++++
packages/core-editor/eslint.config.js | 3 +
packages/core-editor/package.json | 91 +++++++
packages/core-editor/prettier.config.js | 3 +
.../src/application/application-config.ts | 89 +++++++
.../application/application-options.type.ts | 3 +
.../src/application/nanoforge-application.ts | 57 +++++
.../src/application/nanoforge-client.ts | 21 ++
.../src/application/nanoforge-factory.ts | 15 ++
.../src/application/nanoforge-server.ts | 3 +
.../contexts/application.editable-context.ts | 20 ++
.../executions/clear.editable-context.ts | 3 +
.../executions/execution.editable-context.ts | 3 +
.../executions/init.editable-context.ts | 3 +
.../contexts/library.editable-context.ts | 7 +
.../common/library/manager/library.manager.ts | 106 ++++++++
.../common/library/relationship-functions.ts | 122 +++++++++
.../core-editor/src/config/config-registry.ts | 19 ++
packages/core-editor/src/core/core.ts | 104 ++++++++
packages/core-editor/src/index.ts | 4 +
.../core-editor/test/config-registry.spec.ts | 54 ++++
.../test/editable-library-manager.spec.ts | 182 ++++++++++++++
.../core-editor/test/relationship.spec.ts | 135 ++++++++++
packages/core-editor/tsconfig.json | 6 +
packages/core-editor/tsconfig.spec.json | 10 +
packages/core-editor/tsup.config.ts | 3 +
packages/core-editor/typedoc.json | 5 +
45 files changed, 1677 insertions(+)
create mode 100644 packages/core-editor/.cliff-jumperrc.json
create mode 100644 packages/core-editor/.gitignore
create mode 100644 packages/core-editor/.idea/.name
create mode 100644 packages/core-editor/.idea/[NanoForge] Engine Core.iml
create mode 100644 packages/core-editor/.idea/codeStyles/Project.xml
create mode 100644 packages/core-editor/.idea/codeStyles/codeStyleConfig.xml
create mode 100644 packages/core-editor/.idea/git_toolbox_blame.xml
create mode 100644 packages/core-editor/.idea/git_toolbox_prj.xml
create mode 100644 packages/core-editor/.idea/inspectionProfiles/Project_Default.xml
create mode 100644 packages/core-editor/.idea/jsLinters/eslint.xml
create mode 100644 packages/core-editor/.idea/modules.xml
create mode 100644 packages/core-editor/.idea/prettier.xml
create mode 100644 packages/core-editor/.idea/vcs.xml
create mode 100644 packages/core-editor/.nvmrc
create mode 100644 packages/core-editor/.prettierignore
create mode 100644 packages/core-editor/CHANGELOG.md
create mode 100644 packages/core-editor/LICENSE
create mode 100644 packages/core-editor/README.md
create mode 100644 packages/core-editor/cliff.toml
create mode 100644 packages/core-editor/eslint.config.js
create mode 100644 packages/core-editor/package.json
create mode 100644 packages/core-editor/prettier.config.js
create mode 100644 packages/core-editor/src/application/application-config.ts
create mode 100644 packages/core-editor/src/application/application-options.type.ts
create mode 100644 packages/core-editor/src/application/nanoforge-application.ts
create mode 100644 packages/core-editor/src/application/nanoforge-client.ts
create mode 100644 packages/core-editor/src/application/nanoforge-factory.ts
create mode 100644 packages/core-editor/src/application/nanoforge-server.ts
create mode 100644 packages/core-editor/src/common/context/contexts/application.editable-context.ts
create mode 100644 packages/core-editor/src/common/context/contexts/executions/clear.editable-context.ts
create mode 100644 packages/core-editor/src/common/context/contexts/executions/execution.editable-context.ts
create mode 100644 packages/core-editor/src/common/context/contexts/executions/init.editable-context.ts
create mode 100644 packages/core-editor/src/common/context/contexts/library.editable-context.ts
create mode 100644 packages/core-editor/src/common/library/manager/library.manager.ts
create mode 100644 packages/core-editor/src/common/library/relationship-functions.ts
create mode 100644 packages/core-editor/src/config/config-registry.ts
create mode 100644 packages/core-editor/src/core/core.ts
create mode 100644 packages/core-editor/src/index.ts
create mode 100644 packages/core-editor/test/config-registry.spec.ts
create mode 100644 packages/core-editor/test/editable-library-manager.spec.ts
create mode 100644 packages/core-editor/test/relationship.spec.ts
create mode 100644 packages/core-editor/tsconfig.json
create mode 100644 packages/core-editor/tsconfig.spec.json
create mode 100644 packages/core-editor/tsup.config.ts
create mode 100644 packages/core-editor/typedoc.json
diff --git a/packages/core-editor/.cliff-jumperrc.json b/packages/core-editor/.cliff-jumperrc.json
new file mode 100644
index 00000000..fe6d9d4a
--- /dev/null
+++ b/packages/core-editor/.cliff-jumperrc.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "https://raw.githubusercontent.com/favware/cliff-jumper/main/assets/cliff-jumper.schema.json",
+ "name": "core",
+ "org": "nanoforge-dev",
+ "packagePath": "packages/core",
+ "identifierBase": false
+}
diff --git a/packages/core-editor/.gitignore b/packages/core-editor/.gitignore
new file mode 100644
index 00000000..f7652f89
--- /dev/null
+++ b/packages/core-editor/.gitignore
@@ -0,0 +1,231 @@
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+# Turbo
+.turbo/
+
+# Compiled files
+src/**/*.js
+src/**/*.d.ts
diff --git a/packages/core-editor/.idea/.name b/packages/core-editor/.idea/.name
new file mode 100644
index 00000000..3d3e7caa
--- /dev/null
+++ b/packages/core-editor/.idea/.name
@@ -0,0 +1 @@
+[NanoForge] Engine Core
\ No newline at end of file
diff --git a/packages/core-editor/.idea/[NanoForge] Engine Core.iml b/packages/core-editor/.idea/[NanoForge] Engine Core.iml
new file mode 100644
index 00000000..24643cc3
--- /dev/null
+++ b/packages/core-editor/.idea/[NanoForge] Engine Core.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core-editor/.idea/codeStyles/Project.xml b/packages/core-editor/.idea/codeStyles/Project.xml
new file mode 100644
index 00000000..b70d7533
--- /dev/null
+++ b/packages/core-editor/.idea/codeStyles/Project.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core-editor/.idea/codeStyles/codeStyleConfig.xml b/packages/core-editor/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 00000000..79ee123c
--- /dev/null
+++ b/packages/core-editor/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core-editor/.idea/git_toolbox_blame.xml b/packages/core-editor/.idea/git_toolbox_blame.xml
new file mode 100644
index 00000000..7dc12496
--- /dev/null
+++ b/packages/core-editor/.idea/git_toolbox_blame.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core-editor/.idea/git_toolbox_prj.xml b/packages/core-editor/.idea/git_toolbox_prj.xml
new file mode 100644
index 00000000..02b915b8
--- /dev/null
+++ b/packages/core-editor/.idea/git_toolbox_prj.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core-editor/.idea/inspectionProfiles/Project_Default.xml b/packages/core-editor/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000..03d9549e
--- /dev/null
+++ b/packages/core-editor/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core-editor/.idea/jsLinters/eslint.xml b/packages/core-editor/.idea/jsLinters/eslint.xml
new file mode 100644
index 00000000..541945bb
--- /dev/null
+++ b/packages/core-editor/.idea/jsLinters/eslint.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core-editor/.idea/modules.xml b/packages/core-editor/.idea/modules.xml
new file mode 100644
index 00000000..99922e22
--- /dev/null
+++ b/packages/core-editor/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core-editor/.idea/prettier.xml b/packages/core-editor/.idea/prettier.xml
new file mode 100644
index 00000000..0c83ac4e
--- /dev/null
+++ b/packages/core-editor/.idea/prettier.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core-editor/.idea/vcs.xml b/packages/core-editor/.idea/vcs.xml
new file mode 100644
index 00000000..b2bdec2d
--- /dev/null
+++ b/packages/core-editor/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core-editor/.nvmrc b/packages/core-editor/.nvmrc
new file mode 100644
index 00000000..c519bf5b
--- /dev/null
+++ b/packages/core-editor/.nvmrc
@@ -0,0 +1 @@
+v24.11.0
diff --git a/packages/core-editor/.prettierignore b/packages/core-editor/.prettierignore
new file mode 100644
index 00000000..64b127c9
--- /dev/null
+++ b/packages/core-editor/.prettierignore
@@ -0,0 +1,11 @@
+# Ignore files for PNPM, NPM and YARN
+pnpm-lock.yaml
+package-lock.json
+yarn.lock
+bun.lock
+
+.turbo/
+node_modules/
+dist/
+coverage/
+CHANGELOG.md
diff --git a/packages/core-editor/CHANGELOG.md b/packages/core-editor/CHANGELOG.md
new file mode 100644
index 00000000..1d7fb605
--- /dev/null
+++ b/packages/core-editor/CHANGELOG.md
@@ -0,0 +1,59 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+# [@nanoforge-dev/core@1.0.1](https://github.com/NanoForge-dev/Engine/compare/@nanoforge-dev/core@1.0.0...@nanoforge-dev/core@1.0.1) - (2026-02-16)
+
+## Documentation
+
+- Setup typedoc (#192) ([fa908e7](https://github.com/NanoForge-dev/Engine/commit/fa908e7e268fa1770be58fc62a0257f3760480b2)) by @MartinFillon
+- Fix readme badges (#186) ([fd8d93d](https://github.com/NanoForge-dev/Engine/commit/fd8d93d13a0fbad95ef9952acd10faad9e112c78)) by @Exeloo
+
+# [@nanoforge-dev/core@1.0.0](https://github.com/NanoForge-dev/Engine/tree/@nanoforge-dev/core@1.0.0) - (2026-01-09)
+
+## Bug Fixes
+
+- **graphics:** Game loop ([53329d2](https://github.com/NanoForge-dev/Engine/commit/53329d28c47bfac9fe86259e9fc6f42b206062a8)) by @Exeloo
+- **graphics:** Fix display ([d8522e5](https://github.com/NanoForge-dev/Engine/commit/d8522e56678f3bd136733f7941c1d917c18b1400)) by @Exeloo
+- **ecs:** Fix tests ([d33ada5](https://github.com/NanoForge-dev/Engine/commit/d33ada5d9c37e331b8178aa1fc0daee88b07131c)) by @Exeloo
+- **ecs:** Change type handling on lib ecs ([580192d](https://github.com/NanoForge-dev/Engine/commit/580192d5038f386c965434f78aacdf3d1e399ff8)) by @Exeloo
+
+## Documentation
+
+- Update README files with new structure and detailed usage examples for all packages (#157) ([63fab73](https://github.com/NanoForge-dev/Engine/commit/63fab7326bd9c7e6b00f950694ab16c9d9190c53)) by @Exeloo
+- Add funding (#147) ([7301fad](https://github.com/NanoForge-dev/Engine/commit/7301fad10f59b7e1f7fa788f8a2f6fc81d0db72e)) by @Exeloo
+- Add a basic introduction readme ([b240964](https://github.com/NanoForge-dev/Engine/commit/b240964a265b31769a8c5422e23e20156ba56192)) by @MartinFillon
+- Add building and dependency docs to every readme ([2d4785b](https://github.com/NanoForge-dev/Engine/commit/2d4785bdcb455e83337b37540f9ab6b3394c0850)) by @MartinFillon
+
+## Features
+
+- **packages/network:** Client and server for tcp/udp and networked pong as example (#156) ([839fb95](https://github.com/NanoForge-dev/Engine/commit/839fb95449f6ae0ee66d7f7e279374268b743f65)) by @Tchips46
+- **core:** Add client/server distinction and update rendering logic (#119) ([5271432](https://github.com/NanoForge-dev/Engine/commit/5271432710031396d7e433bfdfb015e3871f69d0)) by @Exeloo
+- Add schematics used types (#102) ([b992306](https://github.com/NanoForge-dev/Engine/commit/b9923064ba1da3164b1739fcdec5a819734c4ba2)) by @Exeloo
+- **core:** Introduce `EditableApplicationContext` for managing sound libraries ([6c7bac2](https://github.com/NanoForge-dev/Engine/commit/6c7bac261eeb7ad79203d5695d5ad76dc9e9e9f5)) by @Exeloo
+- **core:** Add Context that admit a ClientLibraryManager ([3835bc8](https://github.com/NanoForge-dev/Engine/commit/3835bc8a6e6d039f11a513b7fe54c353f90e9fe1)) by @Exeloo
+- **music:** Finish music library and add an interface for mutable libraries ([8e00c5d](https://github.com/NanoForge-dev/Engine/commit/8e00c5d00f2901ada86f59667eff7e5d3446076b)) by @MartinFillon
+- **core:** Add `class-transformer` and `class-validator` dependencies for validation utilities ([fd94fe7](https://github.com/NanoForge-dev/Engine/commit/fd94fe7755999c5529335666720899792a691a36)) by @Exeloo
+- **common, core, config:** Introduce configuration registry and validation system ([4fafb82](https://github.com/NanoForge-dev/Engine/commit/4fafb82576fec6866fc281ad5b10321d2ac430df)) by @Exeloo
+- **core:** Enhance type safety and execution context handling ([d986030](https://github.com/NanoForge-dev/Engine/commit/d986030a333bc08d2e37291d1a023cf8d7a6e1d6)) by @Exeloo
+- **app:** Add the ability to mute and unmute sounds ([947bdc0](https://github.com/NanoForge-dev/Engine/commit/947bdc00784a4c3313fe08feb4f91fc91b3ac7b7)) by @MartinFillon
+- **sound:** Add basic sound playing to example ([7335814](https://github.com/NanoForge-dev/Engine/commit/7335814fc532ee92a5f9d776f409c5faa4d56423)) by @MartinFillon
+- **core:** Add default libraries to constructor ([7d9da69](https://github.com/NanoForge-dev/Engine/commit/7d9da69be4301875020176656276236b88b737f1)) by @Exeloo
+- Add dependencies handling ([e51dd3b](https://github.com/NanoForge-dev/Engine/commit/e51dd3bdb5e2e3de21339bf6218e85f935efb9d5)) by @Exeloo
+- **common:** Add dependencies handler ([edb098a](https://github.com/NanoForge-dev/Engine/commit/edb098a65fb932ba9a9532a9b1eee7d64a7a8f0d)) by @Exeloo
+- **core:** Add tickrate and fix runner ([1dba5bd](https://github.com/NanoForge-dev/Engine/commit/1dba5bd89ffa20dfd29b079f93c3eb923ffbdbbc)) by @Exeloo
+- **input:** Add input library ([387e97d](https://github.com/NanoForge-dev/Engine/commit/387e97d7c3015a869947af4acecf48e8e1b0e2b8)) by @Exeloo
+- **game:** Create pong example game ([4b66674](https://github.com/NanoForge-dev/Engine/commit/4b66674c750f345e860d225384054423433beb07)) by @bill-h4rper
+- **game:** Add width and height ([c93c985](https://github.com/NanoForge-dev/Engine/commit/c93c985665bd99c09bc410f1499d11aeaffe3c4c)) by @Exeloo
+- **game:** Add graphics factory ([0f4453c](https://github.com/NanoForge-dev/Engine/commit/0f4453ced908b39e953a672324e97eba82bfeaa3)) by @Exeloo
+- **asset-manager:** Add asset manager ([1774a26](https://github.com/NanoForge-dev/Engine/commit/1774a26593099b4faa0a2527d1684de35211d5d2)) by @Exeloo
+- Add asset manager default in core ([26cc5a9](https://github.com/NanoForge-dev/Engine/commit/26cc5a99e014fbc8669a43cc4aa4d78ecc1dee14)) by @Exeloo
+- Add core and common ([1755c79](https://github.com/NanoForge-dev/Engine/commit/1755c799c143513d72b28edaac875267d484a44f)) by @Exeloo
+- Initial commit ([c9bb59e](https://github.com/NanoForge-dev/Engine/commit/c9bb59ee963e7b444e8668db55597915e9ef0e4b)) by @Exeloo
+
+## Refactor
+
+- **core:** Remove default libs in factory (#118) ([fa893c7](https://github.com/NanoForge-dev/Engine/commit/fa893c71616f151343c2f52a4723a64cca65814a)) by @Exeloo
+- Migrate namespaces to `@nanoforge-dev` and update related imports ([c84c927](https://github.com/NanoForge-dev/Engine/commit/c84c927ead941d914e5a9fd752fd3a5ac969f981)) by @Exeloo
+- **libraries:** Implement initialization validation and standardize nullable fields ([8b04575](https://github.com/NanoForge-dev/Engine/commit/8b04575cf7f649a440b8f40ad6114414406b0c1a)) by @Exeloo
+
diff --git a/packages/core-editor/LICENSE b/packages/core-editor/LICENSE
new file mode 100644
index 00000000..62c6400b
--- /dev/null
+++ b/packages/core-editor/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright © 2025 NanoForge
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/core-editor/README.md b/packages/core-editor/README.md
new file mode 100644
index 00000000..f114fe2e
--- /dev/null
+++ b/packages/core-editor/README.md
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## About
+
+`@nanoforge-dev/core` is a core package that contains game main loop. It is used to initialize the game and run it.
+
+## Installation
+
+**Node.js 24.11.0 or newer is required.**
+
+```sh
+npm install @nanoforge-dev/core
+yarn add @nanoforge-dev/core
+pnpm add @nanoforge-dev/core
+bun add @nanoforge-dev/core
+```
+
+## Example usage
+
+Initialize the game in your main file.
+
+```ts
+import { type IRunOptions } from "@nanoforge-dev/common";
+import { NanoforgeFactory } from "@nanoforge-dev/core";
+
+export async function main(options: IRunClientOptions) {
+ const app = NanoforgeFactory.createClient();
+
+ await app.init(options);
+
+ await app.run();
+}
+```
+
+## Links
+
+- [GitHub][source]
+- [npm][npm]
+
+## Contributing
+
+Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
+[documentation][documentation].
+See [the contribution guide][contributing] if you'd like to submit a PR.
+
+## Help
+
+If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle nudge in the right direction, please don't hesitate to ask questions in [discussions][discussions].
+
+[documentation]: https://github.com/NanoForge-dev/Engine
+[discussions]: https://github.com/NanoForge-dev/Engine/discussions
+[source]: https://github.com/NanoForge-dev/Engine/tree/main/packages/core
+[npm]: https://www.npmjs.com/package/@nanoforge-dev/core
+[contributing]: https://github.com/NanoForge-dev/Engine/blob/main/.github/CONTRIBUTING.md
diff --git a/packages/core-editor/cliff.toml b/packages/core-editor/cliff.toml
new file mode 100644
index 00000000..59978d4c
--- /dev/null
+++ b/packages/core-editor/cliff.toml
@@ -0,0 +1,79 @@
+[changelog]
+header = """
+# Changelog
+
+All notable changes to this project will be documented in this file.\n
+"""
+body = """
+{%- macro remote_url() -%}
+ https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
+{%- endmacro -%}
+{% if version %}\
+ # [{{ version | trim_start_matches(pat="v") }}]\
+ {% if previous %}\
+ {% if previous.version %}\
+ ({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }})\
+ {% else %}\
+ ({{ self::remote_url() }}/tree/{{ version }})\
+ {% endif %}\
+ {% endif %} \
+ - ({{ timestamp | date(format="%Y-%m-%d") }})
+{% else %}\
+ # [unreleased]
+{% endif %}\
+{% for group, commits in commits | group_by(attribute="group") %}
+ ## {{ group | upper_first }}
+ {% for commit in commits %}
+ - {% if commit.scope %}\
+ **{{commit.scope}}:** \
+ {% endif %}\
+ {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\
+ {% if commit.github.username %} by @{{ commit.github.username }}{%- endif %}\
+ {% if commit.breaking %}\
+ {% for footer in commit.footers %}\
+ {% if footer.breaking %}\
+ \n{% raw %} {% endraw %}- **{{ footer.token }}{{ footer.separator }}** {{ footer.value }}\
+ {% endif %}\
+ {% endfor %}\
+ {% endif %}\
+ {% endfor %}
+{% endfor %}\
+{% if github.contributors | filter(attribute="is_first_time", value=true) | length %}\
+ \n### New Contributors\n
+ {% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}\
+ - @{{ contributor.username }} made their first contribution in #{{ contributor.pr_number }}
+ {% endfor %}\
+{% endif %}\n
+"""
+trim = true
+footer = ""
+
+[git]
+conventional_commits = true
+filter_unconventional = true
+commit_parsers = [
+ { message = "^feat", group = "Features"},
+ { message = "^fix", group = "Bug Fixes"},
+ { message = "^docs", group = "Documentation"},
+ { message = "^perf", group = "Performance"},
+ { message = "^refactor", group = "Refactor"},
+ { message = "^types", group = "Typings"},
+ { message = ".*deprecated", body = ".*deprecated", group = "Deprecation"},
+ { message = "^revert", skip = true},
+ { message = "^style", group = "Styling"},
+ { message = "^test", group = "Testing"},
+ { message = "^chore", skip = true},
+ { message = "^ci", skip = true},
+ { message = "^build", skip = true},
+ { body = ".*security", group = "Security"},
+]
+filter_commits = true
+protect_breaking_commits = true
+tag_pattern = "@nanoforge-dev/core@[0-9]*"
+ignore_tags = ""
+topo_order = false
+sort_commits = "newest"
+
+[remote.github]
+owner = "NanoForge-dev"
+repo = "Engine"
diff --git a/packages/core-editor/eslint.config.js b/packages/core-editor/eslint.config.js
new file mode 100644
index 00000000..62ec06dc
--- /dev/null
+++ b/packages/core-editor/eslint.config.js
@@ -0,0 +1,3 @@
+import config from "@nanoforge-dev/utils-eslint-config";
+
+export default config;
diff --git a/packages/core-editor/package.json b/packages/core-editor/package.json
new file mode 100644
index 00000000..90e0540a
--- /dev/null
+++ b/packages/core-editor/package.json
@@ -0,0 +1,91 @@
+{
+ "$schema": "https://json.schemastore.org/package.json",
+ "name": "@nanoforge-dev/core-editor",
+ "version": "1.0",
+ "description": "NanoForge Engine - Core Editor",
+ "keywords": [
+ "nanoforge",
+ "game",
+ "engine"
+ ],
+ "homepage": "https://github.com/NanoForge-dev/Engine#readme",
+ "bugs": "https://github.com/NanoForge-dev/Engine/issues",
+ "license": "MIT",
+ "contributors": [
+ "Bill ",
+ "Exelo ",
+ "Fexkoser ",
+ "Tchips "
+ ],
+ "files": [
+ "dist"
+ ],
+ "main": "./dist/index.cjs",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.cts",
+ "exports": {
+ ".": {
+ "require": {
+ "types": "./dist/index.d.cts",
+ "default": "./dist/index.cjs"
+ },
+ "import": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ }
+ },
+ "./package.json": "./package.json"
+ },
+ "type": "module",
+ "directories": {
+ "lib": "src"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/NanoForge-dev/Engine.git",
+ "directory": "packages/core-editor"
+ },
+ "funding": "https://github.com/NanoForge-dev/Engine?sponsor",
+ "scripts": {
+ "build": "tsc --noEmit && tsup",
+ "lint": "prettier --check . && eslint --format=pretty src",
+ "format": "prettier --write . && eslint --fix --format=pretty src",
+ "test:unit": "vitest run --config ../../vitest.config.ts",
+ "prepack": "pnpm run build && pnpm run lint",
+ "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/core-editor/*'",
+ "release": "cliff-jumper"
+ },
+ "dependencies": {
+ "@nanoforge-dev/asset-manager": "workspace:*",
+ "@nanoforge-dev/common": "workspace:*",
+ "@nanoforge-dev/input": "workspace:*",
+ "class-transformer": "catalog:config",
+ "class-validator": "catalog:config"
+ },
+ "devDependencies": {
+ "@favware/cliff-jumper": "catalog:ci",
+ "@nanoforge-dev/utils-eslint-config": "workspace:*",
+ "@nanoforge-dev/utils-prettier-config": "workspace:*",
+ "@trivago/prettier-plugin-sort-imports": "catalog:lint",
+ "eslint": "catalog:lint",
+ "prettier": "catalog:lint",
+ "tsup": "catalog:build",
+ "typescript": "catalog:core",
+ "vitest": "catalog:test"
+ },
+ "packageManager": "pnpm@10.29.3",
+ "engines": {
+ "node": "25"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "lint-staged": {
+ "**": [
+ "prettier --ignore-unknown --write"
+ ],
+ "src/**/*.ts": [
+ "eslint --fix"
+ ]
+ }
+}
diff --git a/packages/core-editor/prettier.config.js b/packages/core-editor/prettier.config.js
new file mode 100644
index 00000000..27d0e269
--- /dev/null
+++ b/packages/core-editor/prettier.config.js
@@ -0,0 +1,3 @@
+import config from "@nanoforge-dev/utils-prettier-config";
+
+export default config;
diff --git a/packages/core-editor/src/application/application-config.ts b/packages/core-editor/src/application/application-config.ts
new file mode 100644
index 00000000..82d28bed
--- /dev/null
+++ b/packages/core-editor/src/application/application-config.ts
@@ -0,0 +1,89 @@
+import {
+ type IAssetManagerLibrary,
+ type IComponentSystemLibrary,
+ type IGraphicsLibrary,
+ type IInputLibrary,
+ type ILibrary,
+ type IMusicLibrary,
+ type INetworkLibrary,
+ type ISoundLibrary,
+ type LibraryHandle,
+} from "@nanoforge-dev/common";
+
+import { EditableLibraryManager } from "../common/library/manager/library.manager";
+
+export class ApplicationConfig {
+ private readonly _libraryManager: EditableLibraryManager;
+
+ constructor() {
+ this._libraryManager = new EditableLibraryManager();
+ }
+
+ get libraryManager(): EditableLibraryManager {
+ return this._libraryManager;
+ }
+
+ public getLibrary(sym: symbol): LibraryHandle {
+ return this._libraryManager.get(sym);
+ }
+
+ public useLibrary(sym: symbol, library: ILibrary): void {
+ this._libraryManager.set(sym, library);
+ }
+
+ public getComponentSystemLibrary() {
+ return this._libraryManager.getComponentSystem();
+ }
+
+ public useComponentSystemLibrary(library: IComponentSystemLibrary) {
+ this._libraryManager.setComponentSystem(library);
+ }
+
+ public getGraphicsLibrary() {
+ return this._libraryManager.getGraphics();
+ }
+
+ public useGraphicsLibrary(library: IGraphicsLibrary) {
+ this._libraryManager.setGraphics(library);
+ }
+
+ public getNetworkLibrary() {
+ return this._libraryManager.getNetwork();
+ }
+
+ public useNetworkLibrary(library: INetworkLibrary) {
+ this._libraryManager.setNetwork(library);
+ }
+
+ public getAssetManagerLibrary() {
+ return this._libraryManager.getAssetManager();
+ }
+
+ public useAssetManagerLibrary(library: IAssetManagerLibrary) {
+ this._libraryManager.setAssetManager(library);
+ }
+
+ public getInputLibrary() {
+ return this._libraryManager.getInput();
+ }
+
+ public useInputLibrary(library: IInputLibrary) {
+ this._libraryManager.setInput(library);
+ }
+
+ public getSoundLibrary() {
+ return this._libraryManager.getSound();
+ }
+
+ public useSoundLibrary(library: ISoundLibrary) {
+ this._libraryManager.setSound(library);
+ }
+
+ public getMusicLibrary() {
+ return this._libraryManager.getMusic();
+ }
+
+ public useMusicLibrary(library: IMusicLibrary) {
+ this._libraryManager.setMusic(library);
+ }
+}
diff --git a/packages/core-editor/src/application/application-options.type.ts b/packages/core-editor/src/application/application-options.type.ts
new file mode 100644
index 00000000..57ecc833
--- /dev/null
+++ b/packages/core-editor/src/application/application-options.type.ts
@@ -0,0 +1,3 @@
+export interface IApplicationOptions {
+ tickRate: number;
+}
diff --git a/packages/core-editor/src/application/nanoforge-application.ts b/packages/core-editor/src/application/nanoforge-application.ts
new file mode 100644
index 00000000..b35db5ae
--- /dev/null
+++ b/packages/core-editor/src/application/nanoforge-application.ts
@@ -0,0 +1,57 @@
+import {
+ type IAssetManagerLibrary,
+ type IComponentSystemLibrary,
+ type IEditorRunOptions,
+ type ILibrary,
+ type INetworkLibrary,
+ NfNotInitializedException,
+} from "@nanoforge-dev/common";
+
+import { EditableApplicationContext } from "../common/context/contexts/application.editable-context";
+import { Core } from "../core/core";
+import { ApplicationConfig } from "./application-config";
+import type { IApplicationOptions } from "./application-options.type";
+
+export abstract class NanoforgeApplication {
+ protected applicationConfig: ApplicationConfig;
+ private _core?: Core;
+ private readonly _options: IApplicationOptions;
+
+ constructor(options?: Partial) {
+ this.applicationConfig = new ApplicationConfig();
+
+ this._options = {
+ tickRate: 60,
+ ...(options ?? {}),
+ };
+ }
+
+ public use(sym: symbol, library: ILibrary): void {
+ this.applicationConfig.useLibrary(sym, library);
+ }
+
+ public useComponentSystem(library: IComponentSystemLibrary) {
+ this.applicationConfig.useComponentSystemLibrary(library);
+ }
+
+ public useNetwork(library: INetworkLibrary) {
+ this.applicationConfig.useNetworkLibrary(library);
+ }
+
+ public useAssetManager(library: IAssetManagerLibrary) {
+ this.applicationConfig.useAssetManagerLibrary(library);
+ }
+
+ public init(options: IEditorRunOptions): Promise {
+ this._core = new Core(
+ this.applicationConfig,
+ new EditableApplicationContext(this.applicationConfig.libraryManager),
+ );
+ return this._core.init(options, this._options);
+ }
+
+ public run() {
+ if (!this._core) throw new NfNotInitializedException("Core");
+ return this._core?.run();
+ }
+}
diff --git a/packages/core-editor/src/application/nanoforge-client.ts b/packages/core-editor/src/application/nanoforge-client.ts
new file mode 100644
index 00000000..1ddfe4ac
--- /dev/null
+++ b/packages/core-editor/src/application/nanoforge-client.ts
@@ -0,0 +1,21 @@
+import {
+ type IGraphicsLibrary,
+ type IInputLibrary,
+ type ISoundLibrary,
+} from "@nanoforge-dev/common";
+
+import { NanoforgeApplication } from "./nanoforge-application";
+
+export class NanoforgeClient extends NanoforgeApplication {
+ public useGraphics(library: IGraphicsLibrary) {
+ this.applicationConfig.useGraphicsLibrary(library);
+ }
+
+ public useInput(library: IInputLibrary) {
+ this.applicationConfig.useInputLibrary(library);
+ }
+
+ public useSound(library: ISoundLibrary) {
+ this.applicationConfig.useSoundLibrary(library);
+ }
+}
diff --git a/packages/core-editor/src/application/nanoforge-factory.ts b/packages/core-editor/src/application/nanoforge-factory.ts
new file mode 100644
index 00000000..84711a6d
--- /dev/null
+++ b/packages/core-editor/src/application/nanoforge-factory.ts
@@ -0,0 +1,15 @@
+import { type IApplicationOptions } from "./application-options.type";
+import { NanoforgeClient } from "./nanoforge-client";
+import { NanoforgeServer } from "./nanoforge-server";
+
+class NanoforgeFactoryStatic {
+ createClient(options?: Partial): NanoforgeClient {
+ return new NanoforgeClient(options);
+ }
+
+ createServer(options?: Partial): NanoforgeServer {
+ return new NanoforgeServer(options);
+ }
+}
+
+export const NanoforgeFactory = new NanoforgeFactoryStatic();
diff --git a/packages/core-editor/src/application/nanoforge-server.ts b/packages/core-editor/src/application/nanoforge-server.ts
new file mode 100644
index 00000000..74cca8a8
--- /dev/null
+++ b/packages/core-editor/src/application/nanoforge-server.ts
@@ -0,0 +1,3 @@
+import { NanoforgeApplication } from "./nanoforge-application";
+
+export class NanoforgeServer extends NanoforgeApplication {}
diff --git a/packages/core-editor/src/common/context/contexts/application.editable-context.ts b/packages/core-editor/src/common/context/contexts/application.editable-context.ts
new file mode 100644
index 00000000..492b797e
--- /dev/null
+++ b/packages/core-editor/src/common/context/contexts/application.editable-context.ts
@@ -0,0 +1,20 @@
+import { ApplicationContext } from "@nanoforge-dev/common";
+
+import { type EditableLibraryManager } from "../../library/manager/library.manager";
+
+export class EditableApplicationContext extends ApplicationContext {
+ private _libraryManager: EditableLibraryManager;
+
+ constructor(libraryManager: EditableLibraryManager) {
+ super();
+ this._libraryManager = libraryManager;
+ }
+
+ setDelta(delta: number) {
+ this._delta = delta;
+ }
+
+ muteSoundLibraries(): void {
+ this._libraryManager.getMutableLibraries().forEach((lib) => lib.library.mute());
+ }
+}
diff --git a/packages/core-editor/src/common/context/contexts/executions/clear.editable-context.ts b/packages/core-editor/src/common/context/contexts/executions/clear.editable-context.ts
new file mode 100644
index 00000000..1081686d
--- /dev/null
+++ b/packages/core-editor/src/common/context/contexts/executions/clear.editable-context.ts
@@ -0,0 +1,3 @@
+import { ClearContext } from "@nanoforge-dev/common";
+
+export class EditableClearContext extends ClearContext {}
diff --git a/packages/core-editor/src/common/context/contexts/executions/execution.editable-context.ts b/packages/core-editor/src/common/context/contexts/executions/execution.editable-context.ts
new file mode 100644
index 00000000..e9b5b3de
--- /dev/null
+++ b/packages/core-editor/src/common/context/contexts/executions/execution.editable-context.ts
@@ -0,0 +1,3 @@
+import { ExecutionContext } from "@nanoforge-dev/common";
+
+export class EditableExecutionContext extends ExecutionContext {}
diff --git a/packages/core-editor/src/common/context/contexts/executions/init.editable-context.ts b/packages/core-editor/src/common/context/contexts/executions/init.editable-context.ts
new file mode 100644
index 00000000..7ce44e10
--- /dev/null
+++ b/packages/core-editor/src/common/context/contexts/executions/init.editable-context.ts
@@ -0,0 +1,3 @@
+import { InitContext } from "@nanoforge-dev/common";
+
+export class EditableInitContext extends InitContext {}
diff --git a/packages/core-editor/src/common/context/contexts/library.editable-context.ts b/packages/core-editor/src/common/context/contexts/library.editable-context.ts
new file mode 100644
index 00000000..48f942e8
--- /dev/null
+++ b/packages/core-editor/src/common/context/contexts/library.editable-context.ts
@@ -0,0 +1,7 @@
+import { LibraryContext, type LibraryStatusEnum } from "@nanoforge-dev/common";
+
+export class EditableLibraryContext extends LibraryContext {
+ setStatus(status: LibraryStatusEnum) {
+ this._status = status;
+ }
+}
diff --git a/packages/core-editor/src/common/library/manager/library.manager.ts b/packages/core-editor/src/common/library/manager/library.manager.ts
new file mode 100644
index 00000000..6e17b321
--- /dev/null
+++ b/packages/core-editor/src/common/library/manager/library.manager.ts
@@ -0,0 +1,106 @@
+import {
+ ASSET_MANAGER_LIBRARY,
+ COMPONENT_SYSTEM_LIBRARY,
+ DefaultLibrariesEnum,
+ GRAPHICS_LIBRARY,
+ type IAssetManagerLibrary,
+ type IComponentSystemLibrary,
+ type IGraphicsLibrary,
+ type IInputLibrary,
+ type ILibrary,
+ type IMusicLibrary,
+ type IMutableLibrary,
+ INPUT_LIBRARY,
+ type INetworkLibrary,
+ type IRunnerLibrary,
+ type ISoundLibrary,
+ type LibraryHandle,
+ LibraryManager,
+ MUSIC_LIBRARY,
+ NETWORK_LIBRARY,
+ SOUND_LIBRARY,
+} from "@nanoforge-dev/common";
+
+import { EditableLibraryContext } from "../../context/contexts/library.editable-context";
+import { Relationship } from "../relationship-functions";
+
+const hasMethod = (obj: any, method: string) => {
+ return typeof obj[method] === "function";
+};
+
+export class EditableLibraryManager extends LibraryManager {
+ public set(sym: symbol, library: ILibrary) {
+ this.setNewLibrary(sym, library, new EditableLibraryContext());
+ }
+
+ public setComponentSystem(library: IComponentSystemLibrary): void {
+ this._set(
+ DefaultLibrariesEnum.COMPONENT_SYSTEM,
+ COMPONENT_SYSTEM_LIBRARY,
+ library,
+ new EditableLibraryContext(),
+ );
+ }
+
+ public setGraphics(library: IGraphicsLibrary): void {
+ this._set(
+ DefaultLibrariesEnum.GRAPHICS,
+ GRAPHICS_LIBRARY,
+ library,
+ new EditableLibraryContext(),
+ );
+ }
+
+ public setAssetManager(library: IAssetManagerLibrary): void {
+ this._set(
+ DefaultLibrariesEnum.ASSET_MANAGER,
+ ASSET_MANAGER_LIBRARY,
+ library,
+ new EditableLibraryContext(),
+ );
+ }
+
+ public setNetwork(library: INetworkLibrary): void {
+ this._set(DefaultLibrariesEnum.NETWORK, NETWORK_LIBRARY, library, new EditableLibraryContext());
+ }
+
+ public setInput(library: IInputLibrary): void {
+ this._set(DefaultLibrariesEnum.INPUT, INPUT_LIBRARY, library, new EditableLibraryContext());
+ }
+
+ public setSound(library: ISoundLibrary): void {
+ this._set(DefaultLibrariesEnum.SOUND, SOUND_LIBRARY, library, new EditableLibraryContext());
+ }
+
+ public setMusic(library: IMusicLibrary): void {
+ this._set(DefaultLibrariesEnum.MUSIC, MUSIC_LIBRARY, library, new EditableLibraryContext());
+ }
+
+ public getLibraries(): LibraryHandle[] {
+ return this._libraries;
+ }
+
+ public getInitLibraries(): LibraryHandle[] {
+ return Relationship.getLibrariesByDependencies(this._libraries);
+ }
+
+ public getExecutionLibraries(): LibraryHandle[] {
+ return Relationship.getLibrariesByRun(this._getRunnerLibraries());
+ }
+
+ public getClearLibraries(): LibraryHandle[] {
+ return Relationship.getLibrariesByDependencies(this._libraries, true);
+ }
+
+ public getMutableLibraries(): LibraryHandle[] {
+ return this._libraries.filter(
+ (handle) => handle && hasMethod(handle.library, "mute"),
+ ) as LibraryHandle[];
+ }
+
+ private _getRunnerLibraries(): LibraryHandle[] {
+ return this._libraries.filter(
+ (handle) => handle && hasMethod(handle.library, "__run"),
+ ) as LibraryHandle[];
+ }
+}
diff --git a/packages/core-editor/src/common/library/relationship-functions.ts b/packages/core-editor/src/common/library/relationship-functions.ts
new file mode 100644
index 00000000..d9ff0f2b
--- /dev/null
+++ b/packages/core-editor/src/common/library/relationship-functions.ts
@@ -0,0 +1,122 @@
+import { type ILibrary, type LibraryHandle } from "@nanoforge-dev/common";
+
+class RelationshipStatic {
+ getLibrariesByDependencies(libraries: LibraryHandle[], reverse: boolean = false) {
+ let response: LibraryHandle[] = [];
+ for (const library of libraries) {
+ if (!library) continue;
+ response = this._pushLibraryWithDependencies(library, response, [], libraries);
+ }
+
+ if (reverse) return response.reverse();
+ return response;
+ }
+
+ getLibrariesByRun(libraries: LibraryHandle[]) {
+ let response: LibraryHandle[] = [];
+ const dependencies = new Map>(
+ libraries.map((library) => [library.symbol, new Set()]),
+ );
+
+ for (const handle of libraries) {
+ const key = handle.symbol;
+
+ for (const before of handle.library.__relationship.runBefore) {
+ this._pushToDependencies(key, before, dependencies);
+ }
+ for (const after of handle.library.__relationship.runAfter) {
+ this._pushToDependencies(after, key, dependencies);
+ }
+ }
+
+ for (const library of libraries) {
+ response = this._pushLibraryWithDependenciesRun(
+ library,
+ dependencies,
+ response,
+ [],
+ libraries,
+ );
+ }
+ return response;
+ }
+
+ private _pushToDependencies(
+ key: symbol,
+ value: symbol,
+ dependencies: Map>,
+ ): void {
+ let curr = dependencies.get(key);
+ if (!curr) curr = new Set();
+ curr.add(value);
+ dependencies.set(key, curr);
+ }
+
+ private _pushLibraryWithDependenciesRun(
+ handle: LibraryHandle,
+ dependencies: Map>,
+ response: LibraryHandle[],
+ cache: symbol[],
+ libraries: LibraryHandle[],
+ ): LibraryHandle[] {
+ const key = handle.symbol;
+ if (this._symbolIsInList(key, response)) return response;
+
+ if (cache.includes(key)) throw new Error("Circular dependencies !");
+
+ cache.push(key);
+
+ const deps = dependencies.get(key);
+ if (!deps) throw new Error("Dependencies not found");
+
+ for (const dep of deps) {
+ if (this._symbolIsInList(dep, response)) continue;
+
+ const depHandle = libraries.find((lib) => lib?.symbol === dep) as LibraryHandle;
+ if (!depHandle) throw new Error(`Cannot find library ${dep.toString()}`);
+
+ response = this._pushLibraryWithDependenciesRun(
+ depHandle,
+ dependencies,
+ response,
+ cache,
+ libraries,
+ );
+ }
+ cache.pop();
+
+ response.push(handle);
+ return response;
+ }
+
+ private _pushLibraryWithDependencies(
+ handle: LibraryHandle,
+ response: LibraryHandle[],
+ cache: symbol[],
+ libraries: LibraryHandle[],
+ ): LibraryHandle[] {
+ if (this._symbolIsInList(handle.symbol, response)) return response;
+
+ if (cache.includes(handle.symbol)) throw new Error("Circular dependencies !");
+
+ cache.push(handle.symbol);
+ for (const dep of handle.library.__relationship.dependencies) {
+ if (this._symbolIsInList(dep, response)) continue;
+
+ const depHandle = libraries.find((lib) => lib?.symbol === dep) as LibraryHandle;
+ if (!depHandle) throw new Error(`Cannot find library ${dep.toString()}`);
+
+ response = this._pushLibraryWithDependencies(depHandle, response, cache, libraries);
+ }
+ cache.pop();
+
+ response.push(handle);
+ return response;
+ }
+
+ private _symbolIsInList(sym: symbol, libraries: LibraryHandle[]): boolean {
+ return libraries.some((lib) => lib.symbol === sym);
+ }
+}
+
+export const Relationship = new RelationshipStatic();
diff --git a/packages/core-editor/src/config/config-registry.ts b/packages/core-editor/src/config/config-registry.ts
new file mode 100644
index 00000000..946ed1dc
--- /dev/null
+++ b/packages/core-editor/src/config/config-registry.ts
@@ -0,0 +1,19 @@
+import { plainToInstance } from "class-transformer";
+import { validate } from "class-validator";
+
+export class ConfigRegistry {
+ private readonly _env: Record;
+
+ constructor(env: Record) {
+ this._env = env;
+ }
+
+ async registerConfig(config: new () => T): Promise {
+ const data = plainToInstance(config, this._env, { excludeExtraneousValues: true });
+ const errors = await validate(data);
+ if (errors.length > 0) {
+ throw new Error(errors.toString());
+ }
+ return data;
+ }
+}
diff --git a/packages/core-editor/src/core/core.ts b/packages/core-editor/src/core/core.ts
new file mode 100644
index 00000000..3d9d15d1
--- /dev/null
+++ b/packages/core-editor/src/core/core.ts
@@ -0,0 +1,104 @@
+import {
+ ClearContext,
+ ClientLibraryManager,
+ Context,
+ type IEditorRunOptions,
+ type IRunnerLibrary,
+ InitContext,
+ type LibraryHandle,
+ LibraryStatusEnum,
+ NfNotInitializedException,
+} from "@nanoforge-dev/common";
+
+import { type ApplicationConfig } from "../application/application-config";
+import type { IApplicationOptions } from "../application/application-options.type";
+import { type EditableApplicationContext } from "../common/context/contexts/application.editable-context";
+import { EditableExecutionContext } from "../common/context/contexts/executions/execution.editable-context";
+import { type EditableLibraryContext } from "../common/context/contexts/library.editable-context";
+import { ConfigRegistry } from "../config/config-registry";
+
+export class Core {
+ private readonly config: ApplicationConfig;
+ private readonly context: EditableApplicationContext;
+ private options?: IApplicationOptions;
+ private _configRegistry?: ConfigRegistry;
+
+ constructor(config: ApplicationConfig, context: EditableApplicationContext) {
+ this.config = config;
+ this.context = context;
+ }
+
+ public async init(options: IEditorRunOptions, appOptions: IApplicationOptions): Promise {
+ this.options = appOptions;
+ this._configRegistry = new ConfigRegistry(options.env);
+ await this.runInit(this.getInitContext(options));
+ }
+
+ public async run(): Promise {
+ if (!this.options) throw new NfNotInitializedException("Core");
+
+ const context = this.getExecutionContext();
+ const clientContext = this.getClientContext();
+ const libraries = this.config.libraryManager.getExecutionLibraries();
+
+ const runner = async (delta: number) => {
+ this.context.setDelta(delta);
+ await this.runExecute(clientContext, libraries);
+ };
+
+ const tickLengthMs = 1000 / this.options.tickRate;
+ let previousTick = Date.now();
+
+ const render = async () => {
+ if (!context.application.isRunning) {
+ await this.runClear(this.getClearContext());
+ return;
+ }
+ const tickStart = Date.now();
+ await runner(tickStart - previousTick);
+ previousTick = tickStart;
+ setTimeout(render, tickLengthMs + tickStart - Date.now());
+ };
+
+ context.application.setIsRunning(true);
+ setTimeout(render);
+ }
+
+ private getInitContext(options: IEditorRunOptions): InitContext {
+ if (!this._configRegistry) throw new NfNotInitializedException("Core");
+
+ return new InitContext(this.context, this.config.libraryManager, this._configRegistry, options);
+ }
+
+ private getExecutionContext(): EditableExecutionContext {
+ return new EditableExecutionContext(this.context, this.config.libraryManager);
+ }
+
+ private getClearContext(): ClearContext {
+ return new ClearContext(this.context, this.config.libraryManager);
+ }
+
+ private getClientContext(): Context {
+ return new Context(this.context, new ClientLibraryManager(this.config.libraryManager));
+ }
+
+ private async runInit(context: InitContext): Promise {
+ for (const handle of this.config.libraryManager.getInitLibraries()) {
+ await handle.library.__init(context);
+ (handle.context as EditableLibraryContext).setStatus(LibraryStatusEnum.LOADED);
+ }
+ }
+
+ private async runExecute(context: Context, libraries: LibraryHandle[]) {
+ for (const handle of libraries) {
+ await handle.library.__run(context);
+ }
+ }
+
+ private async runClear(context: ClearContext) {
+ for (const handle of this.config.libraryManager.getClearLibraries()) {
+ await handle.library.__clear(context);
+ (handle.context as EditableLibraryContext).setStatus(LibraryStatusEnum.CLEAR);
+ }
+ }
+}
diff --git a/packages/core-editor/src/index.ts b/packages/core-editor/src/index.ts
new file mode 100644
index 00000000..4273d62e
--- /dev/null
+++ b/packages/core-editor/src/index.ts
@@ -0,0 +1,4 @@
+export * from "./application/nanoforge-factory";
+
+export type { NanoforgeClient } from "./application/nanoforge-client";
+export type { NanoforgeServer } from "./application/nanoforge-server";
diff --git a/packages/core-editor/test/config-registry.spec.ts b/packages/core-editor/test/config-registry.spec.ts
new file mode 100644
index 00000000..0a1e2844
--- /dev/null
+++ b/packages/core-editor/test/config-registry.spec.ts
@@ -0,0 +1,54 @@
+import { Expose } from "class-transformer";
+import { IsString } from "class-validator";
+import { describe, expect, it } from "vitest";
+
+import { ConfigRegistry } from "../src/config/config-registry";
+
+class ValidConfig {
+ @Expose()
+ @IsString()
+ name!: string;
+}
+
+class OptionalConfig {
+ @Expose()
+ @IsString()
+ name!: string;
+
+ @Expose()
+ host?: string;
+}
+
+describe("ConfigRegistry", () => {
+ describe("registerConfig", () => {
+ it("should return a transformed config instance when env is valid", async () => {
+ const registry = new ConfigRegistry({ name: "hello" });
+ const config = await registry.registerConfig(ValidConfig);
+ expect(config).toBeInstanceOf(ValidConfig);
+ expect(config.name).toBe("hello");
+ });
+
+ it("should exclude values not decorated with @Expose", async () => {
+ const registry = new ConfigRegistry({ name: "hello", extra: "ignored" });
+ const config = await registry.registerConfig(ValidConfig);
+ expect((config as any)["extra"]).toBeUndefined();
+ });
+
+ it("should throw when a required field is missing", async () => {
+ const registry = new ConfigRegistry({});
+ await expect(registry.registerConfig(ValidConfig)).rejects.toThrow();
+ });
+
+ it("should throw when a field has the wrong type", async () => {
+ const registry = new ConfigRegistry({ name: 42 });
+ await expect(registry.registerConfig(ValidConfig)).rejects.toThrow();
+ });
+
+ it("should map multiple env fields correctly", async () => {
+ const registry = new ConfigRegistry({ name: "world", host: "localhost" });
+ const config = await registry.registerConfig(OptionalConfig);
+ expect(config.name).toBe("world");
+ expect(config.host).toBe("localhost");
+ });
+ });
+});
diff --git a/packages/core-editor/test/editable-library-manager.spec.ts b/packages/core-editor/test/editable-library-manager.spec.ts
new file mode 100644
index 00000000..0e808baa
--- /dev/null
+++ b/packages/core-editor/test/editable-library-manager.spec.ts
@@ -0,0 +1,182 @@
+import { COMPONENT_SYSTEM_LIBRARY, type ILibrary, LibraryStatusEnum } from "@nanoforge-dev/common";
+import { beforeEach, describe, expect, it } from "vitest";
+
+import { Library } from "../../common/src/library/libraries/library";
+import { EditableLibraryManager } from "../src/common/library/manager/library.manager";
+
+class StubLibrary extends Library {
+ private readonly _name: string;
+
+ constructor(name: string, options?: ConstructorParameters[0]) {
+ super(options);
+ this._name = name;
+ }
+
+ get __name(): string {
+ return this._name;
+ }
+}
+
+class StubRunnerLibrary extends StubLibrary {
+ async __run(): Promise {}
+}
+
+class StubMutableLibrary extends StubLibrary {
+ mute(): void {}
+}
+
+describe("EditableLibraryManager", () => {
+ let manager: EditableLibraryManager;
+
+ beforeEach(() => {
+ manager = new EditableLibraryManager();
+ });
+
+ describe("typed setters and getters", () => {
+ it("should store and retrieve a component system library", () => {
+ const lib = new StubLibrary("ComponentSystem");
+ manager.setComponentSystem(lib as any);
+ expect(manager.getComponentSystem().library).toBe(lib);
+ });
+
+ it("should store and retrieve a graphics library", () => {
+ const lib = new StubLibrary("Graphics");
+ manager.setGraphics(lib as any);
+ expect(manager.getGraphics().library).toBe(lib);
+ });
+
+ it("should store and retrieve an asset manager library", () => {
+ const lib = new StubLibrary("AssetManager");
+ manager.setAssetManager(lib as any);
+ expect(manager.getAssetManager().library).toBe(lib);
+ });
+
+ it("should store and retrieve a network library", () => {
+ const lib = new StubLibrary("Network");
+ manager.setNetwork(lib as any);
+ expect(manager.getNetwork().library).toBe(lib);
+ });
+
+ it("should store and retrieve an input library", () => {
+ const lib = new StubLibrary("Input");
+ manager.setInput(lib as any);
+ expect(manager.getInput().library).toBe(lib);
+ });
+
+ it("should store and retrieve a sound library", () => {
+ const lib = new StubLibrary("Sound");
+ manager.setSound(lib as any);
+ expect(manager.getSound().library).toBe(lib);
+ });
+
+ it("should store and retrieve a music library", () => {
+ const lib = new StubLibrary("Music");
+ manager.setMusic(lib as any);
+ expect(manager.getMusic().library).toBe(lib);
+ });
+
+ it("should throw when getting a typed library that was not set", () => {
+ expect(() => manager.getComponentSystem()).toThrow();
+ });
+ });
+
+ describe("set and get (custom symbol)", () => {
+ it("should store and retrieve a library by Symbol.for key", () => {
+ const sym = Symbol.for("customLib");
+ const lib = new StubLibrary("Custom");
+
+ manager.setAssetManager(new StubLibrary("Asset") as any);
+ manager.set(sym, lib as unknown as ILibrary);
+
+ expect(manager.get(sym).library).toBe(lib);
+ });
+ });
+
+ describe("getLibraries", () => {
+ it("should return the list of all set libraries", () => {
+ const lib = new StubLibrary("ComponentSystem");
+ manager.setComponentSystem(lib as any);
+ const libs = manager.getLibraries().filter(Boolean);
+ expect(libs.some((h) => h.library === (lib as unknown as ILibrary))).toBe(true);
+ });
+ });
+
+ describe("getInitLibraries", () => {
+ it("should return libraries in dependency order", () => {
+ const libA = new StubLibrary("A", { dependencies: [COMPONENT_SYSTEM_LIBRARY] });
+ const libB = new StubLibrary("B");
+
+ manager.setComponentSystem(libB as any);
+ manager.setGraphics(libA as any);
+
+ const order = manager.getInitLibraries().map((h) => h.library.__name);
+ const idxB = order.indexOf("B");
+ const idxA = order.indexOf("A");
+
+ expect(idxB).toBeLessThan(idxA);
+ });
+
+ it("should return all set libraries", () => {
+ manager.setAssetManager(new StubLibrary("Asset") as any);
+ manager.setGraphics(new StubLibrary("Graphics") as any);
+
+ expect(manager.getInitLibraries().length).toBe(2);
+ });
+ });
+
+ describe("getClearLibraries", () => {
+ it("should return libraries in reverse dependency order", () => {
+ const libA = new StubLibrary("A", { dependencies: [COMPONENT_SYSTEM_LIBRARY] });
+ const libB = new StubLibrary("B");
+
+ manager.setComponentSystem(libB as any);
+ manager.setGraphics(libA as any);
+
+ const order = manager.getClearLibraries().map((h) => h.library.__name);
+ const idxA = order.indexOf("A");
+ const idxB = order.indexOf("B");
+
+ expect(idxA).toBeLessThan(idxB);
+ });
+ });
+
+ describe("getExecutionLibraries", () => {
+ it("should only return libraries that implement __run", () => {
+ manager.setComponentSystem(new StubLibrary("NotARunner") as any);
+ manager.setGraphics(new StubRunnerLibrary("Runner") as any);
+
+ const runners = manager.getExecutionLibraries();
+ expect(runners.every((h) => typeof (h.library as any).__run === "function")).toBe(true);
+ expect(runners.some((h) => h.library.__name === "Runner")).toBe(true);
+ expect(runners.some((h) => h.library.__name === "NotARunner")).toBe(false);
+ });
+
+ it("should return empty when no runner libraries are set", () => {
+ manager.setComponentSystem(new StubLibrary("Static") as any);
+ expect(manager.getExecutionLibraries()).toHaveLength(0);
+ });
+ });
+
+ describe("getMutableLibraries", () => {
+ it("should only return libraries that implement mute", () => {
+ manager.setSound(new StubMutableLibrary("MutableSound") as any);
+ manager.setGraphics(new StubLibrary("NonMutableGraphics") as any);
+
+ const mutable = manager.getMutableLibraries();
+ expect(mutable.some((h) => h.library.__name === "MutableSound")).toBe(true);
+ expect(mutable.some((h) => h.library.__name === "NonMutableGraphics")).toBe(false);
+ });
+
+ it("should return empty when no mutable libraries are set", () => {
+ manager.setGraphics(new StubLibrary("Graphics") as any);
+ expect(manager.getMutableLibraries()).toHaveLength(0);
+ });
+ });
+
+ describe("library context status", () => {
+ it("should start with UNLOADED status", () => {
+ manager.setComponentSystem(new StubLibrary("Comp") as any);
+ expect(manager.getComponentSystem().context.status).toBe(LibraryStatusEnum.UNLOADED);
+ });
+ });
+});
diff --git a/packages/core-editor/test/relationship.spec.ts b/packages/core-editor/test/relationship.spec.ts
new file mode 100644
index 00000000..776fa493
--- /dev/null
+++ b/packages/core-editor/test/relationship.spec.ts
@@ -0,0 +1,135 @@
+import { type ILibrary, LibraryContext, LibraryHandle } from "@nanoforge-dev/common";
+import { describe, expect, it } from "vitest";
+
+import { Library } from "../../common/src/library/libraries/library";
+import { Relationship } from "../src/common/library/relationship-functions";
+
+class StubLibrary extends Library {
+ private readonly _name: string;
+
+ constructor(name: string, options?: ConstructorParameters[0]) {
+ super(options);
+ this._name = name;
+ }
+
+ get __name(): string {
+ return this._name;
+ }
+}
+
+const makeHandle = (
+ sym: symbol,
+ name: string,
+ options?: ConstructorParameters[0],
+): LibraryHandle => {
+ return new LibraryHandle(
+ sym,
+ new StubLibrary(name, options) as unknown as ILibrary,
+ new LibraryContext(),
+ );
+};
+
+describe("Relationship.getLibrariesByDependencies", () => {
+ it("should return libraries in same order when no dependencies are declared", () => {
+ const symA = Symbol("A");
+ const symB = Symbol("B");
+ const handleA = makeHandle(symA, "A");
+ const handleB = makeHandle(symB, "B");
+
+ const result = Relationship.getLibrariesByDependencies([handleA, handleB]);
+ expect(result.map((h) => h.library.__name)).toEqual(["A", "B"]);
+ });
+
+ it("should put a dependency before the library that depends on it", () => {
+ const symA = Symbol("A");
+ const symB = Symbol("B");
+ const handleB = makeHandle(symB, "B");
+ const handleA = makeHandle(symA, "A", { dependencies: [symB] });
+
+ const result = Relationship.getLibrariesByDependencies([handleA, handleB]);
+ const names = result.map((h) => h.library.__name);
+ expect(names.indexOf("B")).toBeLessThan(names.indexOf("A"));
+ });
+
+ it("should not duplicate a shared dependency", () => {
+ const symDep = Symbol("Dep");
+ const symA = Symbol("A");
+ const symB = Symbol("B");
+ const handleDep = makeHandle(symDep, "Dep");
+ const handleA = makeHandle(symA, "A", { dependencies: [symDep] });
+ const handleB = makeHandle(symB, "B", { dependencies: [symDep] });
+
+ const result = Relationship.getLibrariesByDependencies([handleA, handleB, handleDep]);
+ const names = result.map((h) => h.library.__name);
+ expect(names.filter((n) => n === "Dep")).toHaveLength(1);
+ });
+
+ it("should return libraries in reverse dependency order when reverse=true", () => {
+ const symA = Symbol("A");
+ const symB = Symbol("B");
+ const handleB = makeHandle(symB, "B");
+ const handleA = makeHandle(symA, "A", { dependencies: [symB] });
+
+ const result = Relationship.getLibrariesByDependencies([handleA, handleB], true);
+ const names = result.map((h) => h.library.__name);
+ expect(names.indexOf("A")).toBeLessThan(names.indexOf("B"));
+ });
+
+ it("should throw on circular dependencies", () => {
+ const symA = Symbol("A");
+ const symB = Symbol("B");
+ const handleA = makeHandle(symA, "A", { dependencies: [symB] });
+ const handleB = makeHandle(symB, "B", { dependencies: [symA] });
+
+ expect(() => Relationship.getLibrariesByDependencies([handleA, handleB])).toThrow(
+ /[Cc]ircular/,
+ );
+ });
+});
+
+describe("Relationship.getLibrariesByRun", () => {
+ it("should return all runner libraries when no ordering is specified", () => {
+ const symA = Symbol("A");
+ const symB = Symbol("B");
+ const handleA = makeHandle(symA, "A");
+ const handleB = makeHandle(symB, "B");
+
+ const result = Relationship.getLibrariesByRun([handleA, handleB]);
+ expect(result).toHaveLength(2);
+ });
+
+ it("should place a library before another when runBefore is set", () => {
+ const symA = Symbol("A");
+ const symB = Symbol("B");
+ const handleA = makeHandle(symA, "A", { runBefore: [symB] });
+ const handleB = makeHandle(symB, "B");
+
+ const result = Relationship.getLibrariesByRun([handleA, handleB]);
+ const names = result.map((h) => h.library.__name);
+ expect(names.indexOf("B")).toBeLessThan(names.indexOf("A"));
+ });
+
+ it("should place a library after another when runAfter is set", () => {
+ const symA = Symbol("A");
+ const symB = Symbol("B");
+ const handleA = makeHandle(symA, "A");
+ const handleB = makeHandle(symB, "B", { runAfter: [symA] });
+
+ const result = Relationship.getLibrariesByRun([handleA, handleB]);
+ const names = result.map((h) => h.library.__name);
+ expect(names.indexOf("B")).toBeLessThan(names.indexOf("A"));
+ });
+
+ it("should throw on circular run dependencies", () => {
+ const symA = Symbol("A");
+ const symB = Symbol("B");
+ const handleA = makeHandle(symA, "A", { runBefore: [symB] });
+ const handleB = makeHandle(symB, "B", { runBefore: [symA] });
+
+ expect(() => Relationship.getLibrariesByRun([handleA, handleB])).toThrow(/[Cc]ircular/);
+ });
+
+ it("should return empty array for empty input", () => {
+ expect(Relationship.getLibrariesByRun([])).toHaveLength(0);
+ });
+});
diff --git a/packages/core-editor/tsconfig.json b/packages/core-editor/tsconfig.json
new file mode 100644
index 00000000..9e6d724b
--- /dev/null
+++ b/packages/core-editor/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig.json",
+ "extends": "../../tsconfig.json",
+ "include": ["src/**/*.ts"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/core-editor/tsconfig.spec.json b/packages/core-editor/tsconfig.spec.json
new file mode 100644
index 00000000..8270caba
--- /dev/null
+++ b/packages/core-editor/tsconfig.spec.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig.json",
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "noEmit": true,
+ "skipLibCheck": true
+ },
+ "include": ["test/**/*.spec.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/core-editor/tsup.config.ts b/packages/core-editor/tsup.config.ts
new file mode 100644
index 00000000..f3b6e6ce
--- /dev/null
+++ b/packages/core-editor/tsup.config.ts
@@ -0,0 +1,3 @@
+import { createTsupConfig } from "../../tsup.config.js";
+
+export default [createTsupConfig()];
diff --git a/packages/core-editor/typedoc.json b/packages/core-editor/typedoc.json
new file mode 100644
index 00000000..7e2ec1a3
--- /dev/null
+++ b/packages/core-editor/typedoc.json
@@ -0,0 +1,5 @@
+{
+ "extends": ["../../typedoc.base.json"],
+ "entryPoints": ["src/index.ts"],
+ "skipErrorChecking": true
+}
From 8e21bc9c01f7ba53cf0f3557b0a7d1ebc3e02945 Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Wed, 25 Mar 2026 15:43:47 +0900
Subject: [PATCH 03/14] feat(core-editor): hot reload from save
---
packages/common/src/options/index.ts | 9 +---
.../common/src/options/types/options.type.ts | 21 --------
packages/core-editor/package.json | 2 +
.../src/application/application-config.ts | 4 +-
.../src/application/nanoforge-application.ts | 2 +-
.../src/common/context/options.type.ts | 20 +++++++
.../src/common/context}/save.type.ts | 6 +--
packages/core-editor/src/core/core.ts | 10 +++-
.../core-editor/src/editor/core-editor.ts | 37 +++++++++++++
.../core-editor/test/editor-feature.spec.ts | 54 +++++++++++++++++++
pnpm-lock.yaml | 52 ++++++++++++++++++
11 files changed, 180 insertions(+), 37 deletions(-)
create mode 100644 packages/core-editor/src/common/context/options.type.ts
rename packages/{common/src/options/types => core-editor/src/common/context}/save.type.ts (89%)
create mode 100644 packages/core-editor/src/editor/core-editor.ts
create mode 100644 packages/core-editor/test/editor-feature.spec.ts
diff --git a/packages/common/src/options/index.ts b/packages/common/src/options/index.ts
index 1bd44e31..32ab2502 100644
--- a/packages/common/src/options/index.ts
+++ b/packages/common/src/options/index.ts
@@ -1,8 +1 @@
-export type {
- IRunClientOptions,
- IRunOptions,
- IRunServerOptions,
- IEditorRunClientOptions,
- IEditorRunOptions,
- IEditorRunServerOptions,
-} from "./types/options.type";
+export type { IRunClientOptions, IRunOptions, IRunServerOptions } from "./types/options.type";
diff --git a/packages/common/src/options/types/options.type.ts b/packages/common/src/options/types/options.type.ts
index 5cab0ac1..3ed47674 100644
--- a/packages/common/src/options/types/options.type.ts
+++ b/packages/common/src/options/types/options.type.ts
@@ -1,5 +1,3 @@
-import { type Save } from "./save.type";
-
export type IRunOptions = IRunClientOptions | IRunServerOptions;
export interface IRunClientOptions {
@@ -12,22 +10,3 @@ export interface IRunServerOptions {
files: Map;
env: Record;
}
-
-export type IEditorRunOptions = IEditorRunClientOptions | IEditorRunServerOptions;
-
-export interface IEditorRunClientOptions {
- canvas: HTMLCanvasElement;
- files: Map;
- env: Record;
- editor: {
- save: Save;
- };
-}
-export interface IEditorRunServerOptions {
- canvas: HTMLCanvasElement;
- files: Map;
- env: Record;
- editor: {
- save: Save;
- };
-}
diff --git a/packages/core-editor/package.json b/packages/core-editor/package.json
index 90e0540a..cf532b87 100644
--- a/packages/core-editor/package.json
+++ b/packages/core-editor/package.json
@@ -58,6 +58,8 @@
"dependencies": {
"@nanoforge-dev/asset-manager": "workspace:*",
"@nanoforge-dev/common": "workspace:*",
+ "@nanoforge-dev/ecs-client": "workspace:*",
+ "@nanoforge-dev/ecs-server": "workspace:*",
"@nanoforge-dev/input": "workspace:*",
"class-transformer": "catalog:config",
"class-validator": "catalog:config"
diff --git a/packages/core-editor/src/application/application-config.ts b/packages/core-editor/src/application/application-config.ts
index 82d28bed..7eeede0c 100644
--- a/packages/core-editor/src/application/application-config.ts
+++ b/packages/core-editor/src/application/application-config.ts
@@ -31,8 +31,8 @@ export class ApplicationConfig {
this._libraryManager.set(sym, library);
}
- public getComponentSystemLibrary() {
- return this._libraryManager.getComponentSystem();
+ public getComponentSystemLibrary() {
+ return this._libraryManager.getComponentSystem();
}
public useComponentSystemLibrary(library: IComponentSystemLibrary) {
diff --git a/packages/core-editor/src/application/nanoforge-application.ts b/packages/core-editor/src/application/nanoforge-application.ts
index b35db5ae..81f6ea6e 100644
--- a/packages/core-editor/src/application/nanoforge-application.ts
+++ b/packages/core-editor/src/application/nanoforge-application.ts
@@ -1,13 +1,13 @@
import {
type IAssetManagerLibrary,
type IComponentSystemLibrary,
- type IEditorRunOptions,
type ILibrary,
type INetworkLibrary,
NfNotInitializedException,
} from "@nanoforge-dev/common";
import { EditableApplicationContext } from "../common/context/contexts/application.editable-context";
+import { type IEditorRunOptions } from "../common/context/options.type";
import { Core } from "../core/core";
import { ApplicationConfig } from "./application-config";
import type { IApplicationOptions } from "./application-options.type";
diff --git a/packages/core-editor/src/common/context/options.type.ts b/packages/core-editor/src/common/context/options.type.ts
new file mode 100644
index 00000000..4aa66560
--- /dev/null
+++ b/packages/core-editor/src/common/context/options.type.ts
@@ -0,0 +1,20 @@
+import { type Save } from "./save.type";
+
+export type IEditorRunOptions = IEditorRunClientOptions | IEditorRunServerOptions;
+
+export interface IEditorRunClientOptions {
+ canvas: HTMLCanvasElement;
+ files: Map;
+ env: Record;
+ editor: {
+ save: Save;
+ };
+}
+export interface IEditorRunServerOptions {
+ canvas: HTMLCanvasElement;
+ files: Map;
+ env: Record;
+ editor: {
+ save: Save;
+ };
+}
diff --git a/packages/common/src/options/types/save.type.ts b/packages/core-editor/src/common/context/save.type.ts
similarity index 89%
rename from packages/common/src/options/types/save.type.ts
rename to packages/core-editor/src/common/context/save.type.ts
index 94371ad1..36372a33 100644
--- a/packages/common/src/options/types/save.type.ts
+++ b/packages/core-editor/src/common/context/save.type.ts
@@ -17,6 +17,7 @@ export interface SaveLibrary {
export interface SaveComponent {
name: string;
path: string;
+ paramsNames: string[];
}
export interface SaveSystem {
@@ -26,10 +27,7 @@ export interface SaveSystem {
export interface SaveEntity {
id: string;
- components: {
- name: string;
- params: string[];
- }[];
+ components: Record>;
}
export interface Save {
diff --git a/packages/core-editor/src/core/core.ts b/packages/core-editor/src/core/core.ts
index 3d9d15d1..437da6b7 100644
--- a/packages/core-editor/src/core/core.ts
+++ b/packages/core-editor/src/core/core.ts
@@ -2,25 +2,29 @@ import {
ClearContext,
ClientLibraryManager,
Context,
- type IEditorRunOptions,
type IRunnerLibrary,
InitContext,
type LibraryHandle,
LibraryStatusEnum,
NfNotInitializedException,
} from "@nanoforge-dev/common";
+import { type ECSClientLibrary } from "@nanoforge-dev/ecs-client";
+import { type ECSServerLibrary } from "@nanoforge-dev/ecs-server";
import { type ApplicationConfig } from "../application/application-config";
import type { IApplicationOptions } from "../application/application-options.type";
import { type EditableApplicationContext } from "../common/context/contexts/application.editable-context";
import { EditableExecutionContext } from "../common/context/contexts/executions/execution.editable-context";
import { type EditableLibraryContext } from "../common/context/contexts/library.editable-context";
+import { type IEditorRunOptions } from "../common/context/options.type";
import { ConfigRegistry } from "../config/config-registry";
+import { CoreEditor } from "../editor/core-editor";
export class Core {
private readonly config: ApplicationConfig;
private readonly context: EditableApplicationContext;
private options?: IApplicationOptions;
+ public editor?: CoreEditor;
private _configRegistry?: ConfigRegistry;
constructor(config: ApplicationConfig, context: EditableApplicationContext) {
@@ -30,6 +34,10 @@ export class Core {
public async init(options: IEditorRunOptions, appOptions: IApplicationOptions): Promise {
this.options = appOptions;
+ this.editor = new CoreEditor(
+ options.editor,
+ this.config.getComponentSystemLibrary().library,
+ );
this._configRegistry = new ConfigRegistry(options.env);
await this.runInit(this.getInitContext(options));
}
diff --git a/packages/core-editor/src/editor/core-editor.ts b/packages/core-editor/src/editor/core-editor.ts
new file mode 100644
index 00000000..23fed3b8
--- /dev/null
+++ b/packages/core-editor/src/editor/core-editor.ts
@@ -0,0 +1,37 @@
+import { NfNotFound } from "@nanoforge-dev/common";
+import { type ECSClientLibrary } from "@nanoforge-dev/ecs-client";
+import { type ECSServerLibrary } from "@nanoforge-dev/ecs-server";
+
+import type { IEditorRunOptions } from "../common/context/options.type";
+import type { SaveComponent, SaveEntity } from "../common/context/save.type";
+
+export class CoreEditor {
+ private editor: IEditorRunOptions["editor"];
+ private ecsLibrary: ECSClientLibrary | ECSServerLibrary;
+ constructor(
+ editor: IEditorRunOptions["editor"],
+ ecsLibrary: ECSClientLibrary | ECSServerLibrary,
+ ) {
+ this.editor = editor;
+ this.ecsLibrary = ecsLibrary;
+ }
+
+ public askEntityHotReload(saveComponents: SaveComponent[], entityToReload: SaveEntity[]): void {
+ const reg = this.ecsLibrary.registry;
+ entityToReload.forEach(({ id, components }) => {
+ Object.entries(components).forEach(([componentName, params]) => {
+ const ogComponent = saveComponents.find(({ name: paramName }) => {
+ if (!ogComponent) {
+ throw new NfNotFound("Component: " + componentName + " not found in saved components");
+ }
+ return paramName == componentName;
+ });
+ const ecsComponent = reg.getComponents({ name: componentName }).get(Number(id));
+ Object.entries(params).forEach(([paramName, paramValue]) => {
+ ecsComponent[paramName] = paramValue;
+ });
+ reg.getComponents({ name: componentName }).set(Number(id), ecsComponent);
+ });
+ });
+ }
+}
diff --git a/packages/core-editor/test/editor-feature.spec.ts b/packages/core-editor/test/editor-feature.spec.ts
new file mode 100644
index 00000000..0a1e2844
--- /dev/null
+++ b/packages/core-editor/test/editor-feature.spec.ts
@@ -0,0 +1,54 @@
+import { Expose } from "class-transformer";
+import { IsString } from "class-validator";
+import { describe, expect, it } from "vitest";
+
+import { ConfigRegistry } from "../src/config/config-registry";
+
+class ValidConfig {
+ @Expose()
+ @IsString()
+ name!: string;
+}
+
+class OptionalConfig {
+ @Expose()
+ @IsString()
+ name!: string;
+
+ @Expose()
+ host?: string;
+}
+
+describe("ConfigRegistry", () => {
+ describe("registerConfig", () => {
+ it("should return a transformed config instance when env is valid", async () => {
+ const registry = new ConfigRegistry({ name: "hello" });
+ const config = await registry.registerConfig(ValidConfig);
+ expect(config).toBeInstanceOf(ValidConfig);
+ expect(config.name).toBe("hello");
+ });
+
+ it("should exclude values not decorated with @Expose", async () => {
+ const registry = new ConfigRegistry({ name: "hello", extra: "ignored" });
+ const config = await registry.registerConfig(ValidConfig);
+ expect((config as any)["extra"]).toBeUndefined();
+ });
+
+ it("should throw when a required field is missing", async () => {
+ const registry = new ConfigRegistry({});
+ await expect(registry.registerConfig(ValidConfig)).rejects.toThrow();
+ });
+
+ it("should throw when a field has the wrong type", async () => {
+ const registry = new ConfigRegistry({ name: 42 });
+ await expect(registry.registerConfig(ValidConfig)).rejects.toThrow();
+ });
+
+ it("should map multiple env fields correctly", async () => {
+ const registry = new ConfigRegistry({ name: "world", host: "localhost" });
+ const config = await registry.registerConfig(OptionalConfig);
+ expect(config.name).toBe("world");
+ expect(config.host).toBe("localhost");
+ });
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0ed1b563..2913b6c7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -420,6 +420,58 @@ importers:
specifier: catalog:test
version: 4.0.18(@types/node@25.3.5)(jiti@2.6.1)(yaml@2.8.2)
+ packages/core-editor:
+ dependencies:
+ '@nanoforge-dev/asset-manager':
+ specifier: workspace:*
+ version: link:../asset-manager
+ '@nanoforge-dev/common':
+ specifier: workspace:*
+ version: link:../common
+ '@nanoforge-dev/ecs-client':
+ specifier: workspace:*
+ version: link:../ecs-client
+ '@nanoforge-dev/ecs-server':
+ specifier: workspace:*
+ version: link:../ecs-server
+ '@nanoforge-dev/input':
+ specifier: workspace:*
+ version: link:../input
+ class-transformer:
+ specifier: catalog:config
+ version: 0.5.1
+ class-validator:
+ specifier: catalog:config
+ version: 0.14.4
+ devDependencies:
+ '@favware/cliff-jumper':
+ specifier: catalog:ci
+ version: 6.0.0
+ '@nanoforge-dev/utils-eslint-config':
+ specifier: workspace:*
+ version: link:../../utils/eslint-config
+ '@nanoforge-dev/utils-prettier-config':
+ specifier: workspace:*
+ version: link:../../utils/prettier-config
+ '@trivago/prettier-plugin-sort-imports':
+ specifier: catalog:lint
+ version: 6.0.2(prettier@3.8.1)
+ eslint:
+ specifier: catalog:lint
+ version: 10.0.2(jiti@2.6.1)
+ prettier:
+ specifier: catalog:lint
+ version: 3.8.1
+ tsup:
+ specifier: catalog:build
+ version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.2)
+ typescript:
+ specifier: catalog:core
+ version: 5.9.3
+ vitest:
+ specifier: catalog:test
+ version: 4.0.18(@types/node@25.3.5)(jiti@2.6.1)(yaml@2.8.2)
+
packages/ecs-client:
dependencies:
'@nanoforge-dev/common':
From 9096b78a43b05fe7a5ce98674c9788d3779ce52c Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Wed, 25 Mar 2026 16:01:28 +0900
Subject: [PATCH 04/14] feat(core-editor): hot reload from save
---
packages/core-editor/src/core/core.ts | 1 -
packages/core-editor/src/editor/core-editor.ts | 8 +-------
2 files changed, 1 insertion(+), 8 deletions(-)
diff --git a/packages/core-editor/src/core/core.ts b/packages/core-editor/src/core/core.ts
index 437da6b7..9ea80077 100644
--- a/packages/core-editor/src/core/core.ts
+++ b/packages/core-editor/src/core/core.ts
@@ -35,7 +35,6 @@ export class Core {
public async init(options: IEditorRunOptions, appOptions: IApplicationOptions): Promise {
this.options = appOptions;
this.editor = new CoreEditor(
- options.editor,
this.config.getComponentSystemLibrary().library,
);
this._configRegistry = new ConfigRegistry(options.env);
diff --git a/packages/core-editor/src/editor/core-editor.ts b/packages/core-editor/src/editor/core-editor.ts
index 23fed3b8..c739ee34 100644
--- a/packages/core-editor/src/editor/core-editor.ts
+++ b/packages/core-editor/src/editor/core-editor.ts
@@ -2,17 +2,11 @@ import { NfNotFound } from "@nanoforge-dev/common";
import { type ECSClientLibrary } from "@nanoforge-dev/ecs-client";
import { type ECSServerLibrary } from "@nanoforge-dev/ecs-server";
-import type { IEditorRunOptions } from "../common/context/options.type";
import type { SaveComponent, SaveEntity } from "../common/context/save.type";
export class CoreEditor {
- private editor: IEditorRunOptions["editor"];
private ecsLibrary: ECSClientLibrary | ECSServerLibrary;
- constructor(
- editor: IEditorRunOptions["editor"],
- ecsLibrary: ECSClientLibrary | ECSServerLibrary,
- ) {
- this.editor = editor;
+ constructor(ecsLibrary: ECSClientLibrary | ECSServerLibrary) {
this.ecsLibrary = ecsLibrary;
}
From ec6af58068d2a10b8e9138c598c39df88e78d4f4 Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Wed, 25 Mar 2026 16:08:47 +0900
Subject: [PATCH 05/14] feat(core-editor): hot reload from save
---
packages/core-editor/src/editor/core-editor.ts | 6 +++---
packages/ecs-lib/lib/libecs.d.ts | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/core-editor/src/editor/core-editor.ts b/packages/core-editor/src/editor/core-editor.ts
index c739ee34..1acf374c 100644
--- a/packages/core-editor/src/editor/core-editor.ts
+++ b/packages/core-editor/src/editor/core-editor.ts
@@ -15,11 +15,11 @@ export class CoreEditor {
entityToReload.forEach(({ id, components }) => {
Object.entries(components).forEach(([componentName, params]) => {
const ogComponent = saveComponents.find(({ name: paramName }) => {
- if (!ogComponent) {
- throw new NfNotFound("Component: " + componentName + " not found in saved components");
- }
return paramName == componentName;
});
+ if (!ogComponent) {
+ throw new NfNotFound("Component: " + componentName + " not found in saved components");
+ }
const ecsComponent = reg.getComponents({ name: componentName }).get(Number(id));
Object.entries(params).forEach(([paramName, paramValue]) => {
ecsComponent[paramName] = paramValue;
diff --git a/packages/ecs-lib/lib/libecs.d.ts b/packages/ecs-lib/lib/libecs.d.ts
index 6ff945f1..d4d8cf0b 100644
--- a/packages/ecs-lib/lib/libecs.d.ts
+++ b/packages/ecs-lib/lib/libecs.d.ts
@@ -16,7 +16,7 @@ export interface ClassHandle {
[Symbol.dispose](): void;
clone(): this;
}
-export interface container extends ClassHandle {
+export interface container extends ClassHandle, Iterable {
size(): number;
get(_0: number): any | undefined | undefined;
push_back(_0?: any): void;
From 24a8782b08644fe19f7a0e1a33658979a98908fe Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Thu, 26 Mar 2026 10:19:26 +0900
Subject: [PATCH 06/14] feat(core-editor): ask hot reload
---
packages/core-editor/package.json | 1 -
packages/core-editor/src/core/core.ts | 5 +-
.../core-editor/src/editor/core-editor.ts | 25 ++-
.../core-editor/test/editor-feature.spec.ts | 157 +++++++++++++-----
packages/ecs-client/src/index.ts | 2 +
packages/ecs-lib/src/index.ts | 2 +-
packages/ecs-server/src/index.ts | 2 +
pnpm-lock.yaml | 3 -
8 files changed, 136 insertions(+), 61 deletions(-)
diff --git a/packages/core-editor/package.json b/packages/core-editor/package.json
index cf532b87..8f9080bf 100644
--- a/packages/core-editor/package.json
+++ b/packages/core-editor/package.json
@@ -59,7 +59,6 @@
"@nanoforge-dev/asset-manager": "workspace:*",
"@nanoforge-dev/common": "workspace:*",
"@nanoforge-dev/ecs-client": "workspace:*",
- "@nanoforge-dev/ecs-server": "workspace:*",
"@nanoforge-dev/input": "workspace:*",
"class-transformer": "catalog:config",
"class-validator": "catalog:config"
diff --git a/packages/core-editor/src/core/core.ts b/packages/core-editor/src/core/core.ts
index 9ea80077..657d810d 100644
--- a/packages/core-editor/src/core/core.ts
+++ b/packages/core-editor/src/core/core.ts
@@ -9,7 +9,6 @@ import {
NfNotInitializedException,
} from "@nanoforge-dev/common";
import { type ECSClientLibrary } from "@nanoforge-dev/ecs-client";
-import { type ECSServerLibrary } from "@nanoforge-dev/ecs-server";
import { type ApplicationConfig } from "../application/application-config";
import type { IApplicationOptions } from "../application/application-options.type";
@@ -34,9 +33,7 @@ export class Core {
public async init(options: IEditorRunOptions, appOptions: IApplicationOptions): Promise {
this.options = appOptions;
- this.editor = new CoreEditor(
- this.config.getComponentSystemLibrary().library,
- );
+ this.editor = new CoreEditor(this.config.getComponentSystemLibrary().library);
this._configRegistry = new ConfigRegistry(options.env);
await this.runInit(this.getInitContext(options));
}
diff --git a/packages/core-editor/src/editor/core-editor.ts b/packages/core-editor/src/editor/core-editor.ts
index 1acf374c..5b0bc74c 100644
--- a/packages/core-editor/src/editor/core-editor.ts
+++ b/packages/core-editor/src/editor/core-editor.ts
@@ -1,16 +1,15 @@
import { NfNotFound } from "@nanoforge-dev/common";
-import { type ECSClientLibrary } from "@nanoforge-dev/ecs-client";
-import { type ECSServerLibrary } from "@nanoforge-dev/ecs-server";
+import { type ECSClientLibrary, type Entity } from "@nanoforge-dev/ecs-client";
import type { SaveComponent, SaveEntity } from "../common/context/save.type";
export class CoreEditor {
- private ecsLibrary: ECSClientLibrary | ECSServerLibrary;
- constructor(ecsLibrary: ECSClientLibrary | ECSServerLibrary) {
+ private ecsLibrary: ECSClientLibrary;
+ constructor(ecsLibrary: ECSClientLibrary) {
this.ecsLibrary = ecsLibrary;
}
- public askEntityHotReload(saveComponents: SaveComponent[], entityToReload: SaveEntity[]): void {
+ public askEntitiesHotReload(saveComponents: SaveComponent[], entityToReload: SaveEntity[]): void {
const reg = this.ecsLibrary.registry;
entityToReload.forEach(({ id, components }) => {
Object.entries(components).forEach(([componentName, params]) => {
@@ -20,12 +19,24 @@ export class CoreEditor {
if (!ogComponent) {
throw new NfNotFound("Component: " + componentName + " not found in saved components");
}
- const ecsComponent = reg.getComponents({ name: componentName }).get(Number(id));
+ const ecsEntity: Entity = this.getEntityFromEntityId(id);
+ const ecsComponent = reg.getEntityComponent(ecsEntity, {
+ name: componentName,
+ });
Object.entries(params).forEach(([paramName, paramValue]) => {
ecsComponent[paramName] = paramValue;
});
- reg.getComponents({ name: componentName }).set(Number(id), ecsComponent);
+ reg.addComponent(ecsEntity, ecsComponent);
});
});
}
+
+ private getEntityFromEntityId(entityId: string): Entity {
+ const reg = this.ecsLibrary.registry;
+ return reg.entityFromIndex(
+ reg
+ .getComponentsConst({ name: "__RESERVED_ENTITY_ID" })
+ .getIndex({ name: "__RESERVED_ENTITY_ID", entityId: entityId }),
+ );
+ }
}
diff --git a/packages/core-editor/test/editor-feature.spec.ts b/packages/core-editor/test/editor-feature.spec.ts
index 0a1e2844..b5674dcf 100644
--- a/packages/core-editor/test/editor-feature.spec.ts
+++ b/packages/core-editor/test/editor-feature.spec.ts
@@ -1,54 +1,121 @@
-import { Expose } from "class-transformer";
-import { IsString } from "class-validator";
-import { describe, expect, it } from "vitest";
+import { type ECSClientLibrary } from "@nanoforge-dev/ecs-client";
+import { describe, expect, it, vi } from "vitest";
-import { ConfigRegistry } from "../src/config/config-registry";
+import { type SaveComponent, type SaveEntity } from "../src/common/context/save.type";
+import { CoreEditor } from "../src/editor/core-editor";
-class ValidConfig {
- @Expose()
- @IsString()
- name!: string;
-}
-
-class OptionalConfig {
- @Expose()
- @IsString()
- name!: string;
-
- @Expose()
- host?: string;
-}
-
-describe("ConfigRegistry", () => {
- describe("registerConfig", () => {
- it("should return a transformed config instance when env is valid", async () => {
- const registry = new ConfigRegistry({ name: "hello" });
- const config = await registry.registerConfig(ValidConfig);
- expect(config).toBeInstanceOf(ValidConfig);
- expect(config.name).toBe("hello");
- });
-
- it("should exclude values not decorated with @Expose", async () => {
- const registry = new ConfigRegistry({ name: "hello", extra: "ignored" });
- const config = await registry.registerConfig(ValidConfig);
- expect((config as any)["extra"]).toBeUndefined();
- });
+const getIndex = vi.fn((component) => {
+ return Number(component.entityId.slice(-1));
+});
- it("should throw when a required field is missing", async () => {
- const registry = new ConfigRegistry({});
- await expect(registry.registerConfig(ValidConfig)).rejects.toThrow();
+const FakeRegistry = vi.fn(
+ class {
+ addComponent = vi.fn();
+ getComponentsConst = vi.fn(() => ({ getIndex }));
+ getEntityComponent = vi.fn((entity: number, component) => {
+ return (
+ {
+ 2: {
+ Position: {
+ name: "Position",
+ x: 3,
+ y: 4,
+ },
+ Bullets: {
+ name: "Bullets",
+ number: 4,
+ bulletTypes: ["9mm"],
+ },
+ __RESERVED_ENTITY_ID: {
+ entityId: "ent2",
+ },
+ },
+ 3: {
+ Position: {
+ name: "Position",
+ x: 7,
+ y: 8,
+ },
+ __RESERVED_ENTITY_ID: {
+ entityId: "ent3",
+ },
+ },
+ } as Record>
+ )[entity]?.[component.name];
});
-
- it("should throw when a field has the wrong type", async () => {
- const registry = new ConfigRegistry({ name: 42 });
- await expect(registry.registerConfig(ValidConfig)).rejects.toThrow();
+ entityFromIndex = vi.fn((index) => {
+ return index;
});
+ },
+);
- it("should map multiple env fields correctly", async () => {
- const registry = new ConfigRegistry({ name: "world", host: "localhost" });
- const config = await registry.registerConfig(OptionalConfig);
- expect(config.name).toBe("world");
- expect(config.host).toBe("localhost");
+describe("EditorFeatures", () => {
+ describe("askEntitiesHotReload", () => {
+ it("should reload entities with new save variables", async () => {
+ const components: SaveComponent[] = [
+ {
+ name: "Position",
+ path: "/tmp/pos",
+ paramsNames: ["x", "y"],
+ },
+ {
+ name: "Bullets",
+ path: "/tmp/pos",
+ paramsNames: ["bulletTypes", "number"],
+ },
+ ];
+ const entities: SaveEntity[] = [
+ {
+ id: "ent2",
+ components: {
+ Position: {
+ x: 1,
+ y: 2,
+ },
+ Bullets: {
+ bulletTypes: ["fire", "water", "rocket"],
+ number: 1000,
+ },
+ },
+ },
+ {
+ id: "ent3",
+ components: {
+ Position: {
+ x: 5,
+ y: 6,
+ },
+ },
+ },
+ ];
+ const fakeReg = new FakeRegistry();
+ new CoreEditor({ registry: fakeReg } as any as ECSClientLibrary).askEntitiesHotReload(
+ components,
+ entities,
+ );
+ expect(fakeReg.getComponentsConst).toHaveBeenCalledWith({ name: "__RESERVED_ENTITY_ID" });
+ expect(getIndex).toHaveBeenNthCalledWith(1, {
+ entityId: "ent2",
+ name: "__RESERVED_ENTITY_ID",
+ });
+ expect(getIndex).toHaveBeenNthCalledWith(2, {
+ entityId: "ent2",
+ name: "__RESERVED_ENTITY_ID",
+ });
+ expect(getIndex).toHaveBeenNthCalledWith(3, {
+ entityId: "ent3",
+ name: "__RESERVED_ENTITY_ID",
+ });
+ expect(fakeReg.getEntityComponent).toHaveBeenNthCalledWith(1, 2, { name: "Position" });
+ expect(fakeReg.getEntityComponent).toHaveBeenNthCalledWith(2, 2, { name: "Bullets" });
+ expect(fakeReg.getEntityComponent).toHaveBeenNthCalledWith(3, 3, { name: "Position" });
+ expect(fakeReg.addComponent).toHaveBeenNthCalledWith(1, 2, { name: "Position", x: 1, y: 2 });
+ expect(fakeReg.addComponent).toHaveBeenNthCalledWith(2, 2, {
+ name: "Bullets",
+ bulletTypes: ["fire", "water", "rocket"],
+ number: 1000,
+ });
+ expect(fakeReg.addComponent).toHaveBeenNthCalledWith(3, 3, { name: "Position", x: 5, y: 6 });
});
});
});
diff --git a/packages/ecs-client/src/index.ts b/packages/ecs-client/src/index.ts
index 5e93dd98..8759c383 100644
--- a/packages/ecs-client/src/index.ts
+++ b/packages/ecs-client/src/index.ts
@@ -6,6 +6,8 @@ export type {
EditorSystemManifest,
System,
Registry,
+ Entity,
+ SparseArray,
} from "@nanoforge-dev/ecs-lib";
export { ECSClientLibrary } from "./ecs-client-library";
diff --git a/packages/ecs-lib/src/index.ts b/packages/ecs-lib/src/index.ts
index ab84137a..a0d20643 100644
--- a/packages/ecs-lib/src/index.ts
+++ b/packages/ecs-lib/src/index.ts
@@ -1,3 +1,3 @@
export { AbstractECSLibrary } from "./ecs-library.abstract";
-export type { Component, System, Registry } from "../lib/libecs";
+export type { Component, System, Registry, SparseArray, Entity } from "../lib/libecs";
export type * from "./editor-manifest.type";
diff --git a/packages/ecs-server/src/index.ts b/packages/ecs-server/src/index.ts
index 0c14ef42..045ba79b 100644
--- a/packages/ecs-server/src/index.ts
+++ b/packages/ecs-server/src/index.ts
@@ -6,6 +6,8 @@ export type {
EditorSystemManifest,
System,
Registry,
+ Entity,
+ SparseArray,
} from "@nanoforge-dev/ecs-lib";
export { ECSServerLibrary } from "./ecs-server-library";
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2913b6c7..0abe178c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -431,9 +431,6 @@ importers:
'@nanoforge-dev/ecs-client':
specifier: workspace:*
version: link:../ecs-client
- '@nanoforge-dev/ecs-server':
- specifier: workspace:*
- version: link:../ecs-server
'@nanoforge-dev/input':
specifier: workspace:*
version: link:../input
From 7cc1bc5349a3fd6d2a4d94e79e96652909e6a579 Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Thu, 26 Mar 2026 10:23:49 +0900
Subject: [PATCH 07/14] feat: merge main
---
pnpm-lock.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 61290906..35a1e681 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -455,7 +455,7 @@ importers:
version: 6.0.2(prettier@3.8.1)
eslint:
specifier: catalog:lint
- version: 10.0.2(jiti@2.6.1)
+ version: 10.0.3(jiti@2.6.1)
prettier:
specifier: catalog:lint
version: 3.8.1
From c1dbfd6a18f70e89133540068cb9bda7fe0162ac Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Thu, 26 Mar 2026 11:26:35 +0900
Subject: [PATCH 08/14] feat(core-editor): event executor
---
.../src/common/context/event-emitter.type.ts | 8 ++
.../src/common/context/options.type.ts | 3 +
packages/core-editor/src/core/core.ts | 6 +-
.../core-editor/src/editor/core-editor.ts | 28 +++-
.../core-editor/test/editor-feature.spec.ts | 123 +++++++++++-------
5 files changed, 112 insertions(+), 56 deletions(-)
create mode 100644 packages/core-editor/src/common/context/event-emitter.type.ts
diff --git a/packages/core-editor/src/common/context/event-emitter.type.ts b/packages/core-editor/src/common/context/event-emitter.type.ts
new file mode 100644
index 00000000..c9c040d0
--- /dev/null
+++ b/packages/core-editor/src/common/context/event-emitter.type.ts
@@ -0,0 +1,8 @@
+export enum EventTypeEnum {
+ HOT_RELOAD = "hot-reload",
+ HARD_RELOAD = "hard-reload",
+}
+
+export interface EventEmitter {
+ eventQueue: (EventTypeEnum | string)[];
+}
diff --git a/packages/core-editor/src/common/context/options.type.ts b/packages/core-editor/src/common/context/options.type.ts
index 4aa66560..3a06c151 100644
--- a/packages/core-editor/src/common/context/options.type.ts
+++ b/packages/core-editor/src/common/context/options.type.ts
@@ -1,3 +1,4 @@
+import { type EventEmitter } from "./event-emitter.type";
import { type Save } from "./save.type";
export type IEditorRunOptions = IEditorRunClientOptions | IEditorRunServerOptions;
@@ -8,6 +9,7 @@ export interface IEditorRunClientOptions {
env: Record;
editor: {
save: Save;
+ events: EventEmitter;
};
}
export interface IEditorRunServerOptions {
@@ -16,5 +18,6 @@ export interface IEditorRunServerOptions {
env: Record;
editor: {
save: Save;
+ events: EventEmitter;
};
}
diff --git a/packages/core-editor/src/core/core.ts b/packages/core-editor/src/core/core.ts
index 657d810d..9857164b 100644
--- a/packages/core-editor/src/core/core.ts
+++ b/packages/core-editor/src/core/core.ts
@@ -33,9 +33,12 @@ export class Core {
public async init(options: IEditorRunOptions, appOptions: IApplicationOptions): Promise {
this.options = appOptions;
- this.editor = new CoreEditor(this.config.getComponentSystemLibrary().library);
this._configRegistry = new ConfigRegistry(options.env);
await this.runInit(this.getInitContext(options));
+ this.editor = new CoreEditor(
+ options.editor,
+ this.config.getComponentSystemLibrary().library,
+ );
}
public async run(): Promise {
@@ -47,6 +50,7 @@ export class Core {
const runner = async (delta: number) => {
this.context.setDelta(delta);
+ this.editor?.runEvents();
await this.runExecute(clientContext, libraries);
};
diff --git a/packages/core-editor/src/editor/core-editor.ts b/packages/core-editor/src/editor/core-editor.ts
index 5b0bc74c..2f454410 100644
--- a/packages/core-editor/src/editor/core-editor.ts
+++ b/packages/core-editor/src/editor/core-editor.ts
@@ -1,19 +1,37 @@
import { NfNotFound } from "@nanoforge-dev/common";
import { type ECSClientLibrary, type Entity } from "@nanoforge-dev/ecs-client";
-import type { SaveComponent, SaveEntity } from "../common/context/save.type";
+import { EventTypeEnum } from "../common/context/event-emitter.type";
+import { type IEditorRunOptions } from "../common/context/options.type";
export class CoreEditor {
+ private editor: IEditorRunOptions["editor"];
private ecsLibrary: ECSClientLibrary;
- constructor(ecsLibrary: ECSClientLibrary) {
+ constructor(editor: IEditorRunOptions["editor"], ecsLibrary: ECSClientLibrary) {
+ this.editor = editor;
this.ecsLibrary = ecsLibrary;
}
- public askEntitiesHotReload(saveComponents: SaveComponent[], entityToReload: SaveEntity[]): void {
+ public runEvents() {
+ const events: (EventTypeEnum | string)[] = this.editor.events.eventQueue;
+ while (events.length > 0) {
+ const event = events.shift();
+ switch (event) {
+ case EventTypeEnum.HOT_RELOAD:
+ this.askEntitiesHotReload();
+ break;
+ default:
+ console.warn(`Unknown event type ${event}`);
+ }
+ }
+ }
+
+ public askEntitiesHotReload(): void {
const reg = this.ecsLibrary.registry;
- entityToReload.forEach(({ id, components }) => {
+ const save = this.editor.save;
+ save.entities.forEach(({ id, components }) => {
Object.entries(components).forEach(([componentName, params]) => {
- const ogComponent = saveComponents.find(({ name: paramName }) => {
+ const ogComponent = save.components.find(({ name: paramName }) => {
return paramName == componentName;
});
if (!ogComponent) {
diff --git a/packages/core-editor/test/editor-feature.spec.ts b/packages/core-editor/test/editor-feature.spec.ts
index b5674dcf..2c67193c 100644
--- a/packages/core-editor/test/editor-feature.spec.ts
+++ b/packages/core-editor/test/editor-feature.spec.ts
@@ -1,57 +1,75 @@
import { type ECSClientLibrary } from "@nanoforge-dev/ecs-client";
-import { describe, expect, it, vi } from "vitest";
+import { afterEach, describe, expect, it, vi } from "vitest";
-import { type SaveComponent, type SaveEntity } from "../src/common/context/save.type";
+import { type EventEmitter, EventTypeEnum } from "../src/common/context/event-emitter.type";
+import type { IEditorRunOptions } from "../src/common/context/options.type";
+import { type Save, type SaveComponent, type SaveEntity } from "../src/common/context/save.type";
import { CoreEditor } from "../src/editor/core-editor";
-const getIndex = vi.fn((component) => {
- return Number(component.entityId.slice(-1));
-});
-
-const FakeRegistry = vi.fn(
- class {
- addComponent = vi.fn();
- getComponentsConst = vi.fn(() => ({ getIndex }));
- getEntityComponent = vi.fn((entity: number, component) => {
- return (
- {
- 2: {
- Position: {
- name: "Position",
- x: 3,
- y: 4,
- },
- Bullets: {
- name: "Bullets",
- number: 4,
- bulletTypes: ["9mm"],
- },
- __RESERVED_ENTITY_ID: {
- entityId: "ent2",
- },
- },
- 3: {
- Position: {
- name: "Position",
- x: 7,
- y: 8,
- },
- __RESERVED_ENTITY_ID: {
- entityId: "ent3",
- },
- },
- } as Record>
- )[entity]?.[component.name];
- });
- entityFromIndex = vi.fn((index) => {
- return index;
+describe("EditorFeatures", () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+ describe("eventEmitter", () => {
+ it("should execute eventQueue once", async () => {
+ const events: EventEmitter = {
+ eventQueue: [EventTypeEnum.HOT_RELOAD, EventTypeEnum.HOT_RELOAD],
+ };
+ const spyHotReload = vi
+ .spyOn(CoreEditor.prototype, "askEntitiesHotReload")
+ .mockImplementation(() => {});
+ new CoreEditor({ events } as IEditorRunOptions["editor"], {} as ECSClientLibrary).runEvents();
+ expect(spyHotReload).toHaveBeenCalledTimes(2);
});
- },
-);
+ });
-describe("EditorFeatures", () => {
describe("askEntitiesHotReload", () => {
it("should reload entities with new save variables", async () => {
+ const getIndex = vi.fn((component) => {
+ return Number(component.entityId.slice(-1));
+ });
+
+ const FakeRegistry = vi.fn(
+ class {
+ addComponent = vi.fn();
+ getComponentsConst = vi.fn(() => ({ getIndex }));
+ getEntityComponent = vi.fn((entity: number, component) => {
+ return (
+ {
+ 2: {
+ Position: {
+ name: "Position",
+ x: 3,
+ y: 4,
+ },
+ Bullets: {
+ name: "Bullets",
+ number: 4,
+ bulletTypes: ["9mm"],
+ },
+ __RESERVED_ENTITY_ID: {
+ entityId: "ent2",
+ },
+ },
+ 3: {
+ Position: {
+ name: "Position",
+ x: 7,
+ y: 8,
+ },
+ __RESERVED_ENTITY_ID: {
+ entityId: "ent3",
+ },
+ },
+ } as Record>
+ )[entity]?.[component.name];
+ });
+ entityFromIndex = vi.fn((index) => {
+ return index;
+ });
+ },
+ );
+
const components: SaveComponent[] = [
{
name: "Position",
@@ -89,10 +107,15 @@ describe("EditorFeatures", () => {
},
];
const fakeReg = new FakeRegistry();
- new CoreEditor({ registry: fakeReg } as any as ECSClientLibrary).askEntitiesHotReload(
- components,
- entities,
- );
+ new CoreEditor(
+ {
+ save: {
+ components,
+ entities,
+ } as any as Save,
+ } as any as IEditorRunOptions["editor"],
+ { registry: fakeReg } as any as ECSClientLibrary,
+ ).askEntitiesHotReload();
expect(fakeReg.getComponentsConst).toHaveBeenCalledWith({ name: "__RESERVED_ENTITY_ID" });
expect(getIndex).toHaveBeenNthCalledWith(1, {
entityId: "ent2",
From 565446fa4b92d15049cdb2dccf70b57fc49b7a4e Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Tue, 31 Mar 2026 00:55:20 +0900
Subject: [PATCH 09/14] feat(core-editor): cleaner event manager
---
packages/core-editor/.cliff-jumperrc.json | 2 +-
packages/core-editor/.idea/.name | 2 +-
...iml => [NanoForge] Engine Core Editor.iml} | 0
packages/core-editor/.idea/modules.xml | 2 +-
packages/core-editor/CHANGELOG.md | 45 -----------------
packages/core-editor/README.md | 19 ++++---
packages/core-editor/cliff.toml | 2 +-
packages/core-editor/package.json | 2 +-
.../src/common/context/event-emitter.type.ts | 20 +++++++-
.../src/common/context/options.type.ts | 9 ++--
.../core-editor/src/editor/core-editor.ts | 13 +----
.../src/editor/event-emitter.manager.ts | 50 +++++++++++++++++++
.../core-editor/test/editor-feature.spec.ts | 15 ++++--
13 files changed, 99 insertions(+), 82 deletions(-)
rename packages/core-editor/.idea/{[NanoForge] Engine Core.iml => [NanoForge] Engine Core Editor.iml} (100%)
create mode 100644 packages/core-editor/src/editor/event-emitter.manager.ts
diff --git a/packages/core-editor/.cliff-jumperrc.json b/packages/core-editor/.cliff-jumperrc.json
index fe6d9d4a..05f3e85e 100644
--- a/packages/core-editor/.cliff-jumperrc.json
+++ b/packages/core-editor/.cliff-jumperrc.json
@@ -2,6 +2,6 @@
"$schema": "https://raw.githubusercontent.com/favware/cliff-jumper/main/assets/cliff-jumper.schema.json",
"name": "core",
"org": "nanoforge-dev",
- "packagePath": "packages/core",
+ "packagePath": "packages/core-editor",
"identifierBase": false
}
diff --git a/packages/core-editor/.idea/.name b/packages/core-editor/.idea/.name
index 3d3e7caa..b5da13ff 100644
--- a/packages/core-editor/.idea/.name
+++ b/packages/core-editor/.idea/.name
@@ -1 +1 @@
-[NanoForge] Engine Core
\ No newline at end of file
+[NanoForge] Engine Core Editor
\ No newline at end of file
diff --git a/packages/core-editor/.idea/[NanoForge] Engine Core.iml b/packages/core-editor/.idea/[NanoForge] Engine Core Editor.iml
similarity index 100%
rename from packages/core-editor/.idea/[NanoForge] Engine Core.iml
rename to packages/core-editor/.idea/[NanoForge] Engine Core Editor.iml
diff --git a/packages/core-editor/.idea/modules.xml b/packages/core-editor/.idea/modules.xml
index 99922e22..529602fb 100644
--- a/packages/core-editor/.idea/modules.xml
+++ b/packages/core-editor/.idea/modules.xml
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/packages/core-editor/CHANGELOG.md b/packages/core-editor/CHANGELOG.md
index 1d7fb605..53aaa9de 100644
--- a/packages/core-editor/CHANGELOG.md
+++ b/packages/core-editor/CHANGELOG.md
@@ -2,58 +2,13 @@
All notable changes to this project will be documented in this file.
-# [@nanoforge-dev/core@1.0.1](https://github.com/NanoForge-dev/Engine/compare/@nanoforge-dev/core@1.0.0...@nanoforge-dev/core@1.0.1) - (2026-02-16)
-
## Documentation
-- Setup typedoc (#192) ([fa908e7](https://github.com/NanoForge-dev/Engine/commit/fa908e7e268fa1770be58fc62a0257f3760480b2)) by @MartinFillon
-- Fix readme badges (#186) ([fd8d93d](https://github.com/NanoForge-dev/Engine/commit/fd8d93d13a0fbad95ef9952acd10faad9e112c78)) by @Exeloo
-
-# [@nanoforge-dev/core@1.0.0](https://github.com/NanoForge-dev/Engine/tree/@nanoforge-dev/core@1.0.0) - (2026-01-09)
-
## Bug Fixes
-- **graphics:** Game loop ([53329d2](https://github.com/NanoForge-dev/Engine/commit/53329d28c47bfac9fe86259e9fc6f42b206062a8)) by @Exeloo
-- **graphics:** Fix display ([d8522e5](https://github.com/NanoForge-dev/Engine/commit/d8522e56678f3bd136733f7941c1d917c18b1400)) by @Exeloo
-- **ecs:** Fix tests ([d33ada5](https://github.com/NanoForge-dev/Engine/commit/d33ada5d9c37e331b8178aa1fc0daee88b07131c)) by @Exeloo
-- **ecs:** Change type handling on lib ecs ([580192d](https://github.com/NanoForge-dev/Engine/commit/580192d5038f386c965434f78aacdf3d1e399ff8)) by @Exeloo
-
## Documentation
-- Update README files with new structure and detailed usage examples for all packages (#157) ([63fab73](https://github.com/NanoForge-dev/Engine/commit/63fab7326bd9c7e6b00f950694ab16c9d9190c53)) by @Exeloo
-- Add funding (#147) ([7301fad](https://github.com/NanoForge-dev/Engine/commit/7301fad10f59b7e1f7fa788f8a2f6fc81d0db72e)) by @Exeloo
-- Add a basic introduction readme ([b240964](https://github.com/NanoForge-dev/Engine/commit/b240964a265b31769a8c5422e23e20156ba56192)) by @MartinFillon
-- Add building and dependency docs to every readme ([2d4785b](https://github.com/NanoForge-dev/Engine/commit/2d4785bdcb455e83337b37540f9ab6b3394c0850)) by @MartinFillon
-
## Features
-- **packages/network:** Client and server for tcp/udp and networked pong as example (#156) ([839fb95](https://github.com/NanoForge-dev/Engine/commit/839fb95449f6ae0ee66d7f7e279374268b743f65)) by @Tchips46
-- **core:** Add client/server distinction and update rendering logic (#119) ([5271432](https://github.com/NanoForge-dev/Engine/commit/5271432710031396d7e433bfdfb015e3871f69d0)) by @Exeloo
-- Add schematics used types (#102) ([b992306](https://github.com/NanoForge-dev/Engine/commit/b9923064ba1da3164b1739fcdec5a819734c4ba2)) by @Exeloo
-- **core:** Introduce `EditableApplicationContext` for managing sound libraries ([6c7bac2](https://github.com/NanoForge-dev/Engine/commit/6c7bac261eeb7ad79203d5695d5ad76dc9e9e9f5)) by @Exeloo
-- **core:** Add Context that admit a ClientLibraryManager ([3835bc8](https://github.com/NanoForge-dev/Engine/commit/3835bc8a6e6d039f11a513b7fe54c353f90e9fe1)) by @Exeloo
-- **music:** Finish music library and add an interface for mutable libraries ([8e00c5d](https://github.com/NanoForge-dev/Engine/commit/8e00c5d00f2901ada86f59667eff7e5d3446076b)) by @MartinFillon
-- **core:** Add `class-transformer` and `class-validator` dependencies for validation utilities ([fd94fe7](https://github.com/NanoForge-dev/Engine/commit/fd94fe7755999c5529335666720899792a691a36)) by @Exeloo
-- **common, core, config:** Introduce configuration registry and validation system ([4fafb82](https://github.com/NanoForge-dev/Engine/commit/4fafb82576fec6866fc281ad5b10321d2ac430df)) by @Exeloo
-- **core:** Enhance type safety and execution context handling ([d986030](https://github.com/NanoForge-dev/Engine/commit/d986030a333bc08d2e37291d1a023cf8d7a6e1d6)) by @Exeloo
-- **app:** Add the ability to mute and unmute sounds ([947bdc0](https://github.com/NanoForge-dev/Engine/commit/947bdc00784a4c3313fe08feb4f91fc91b3ac7b7)) by @MartinFillon
-- **sound:** Add basic sound playing to example ([7335814](https://github.com/NanoForge-dev/Engine/commit/7335814fc532ee92a5f9d776f409c5faa4d56423)) by @MartinFillon
-- **core:** Add default libraries to constructor ([7d9da69](https://github.com/NanoForge-dev/Engine/commit/7d9da69be4301875020176656276236b88b737f1)) by @Exeloo
-- Add dependencies handling ([e51dd3b](https://github.com/NanoForge-dev/Engine/commit/e51dd3bdb5e2e3de21339bf6218e85f935efb9d5)) by @Exeloo
-- **common:** Add dependencies handler ([edb098a](https://github.com/NanoForge-dev/Engine/commit/edb098a65fb932ba9a9532a9b1eee7d64a7a8f0d)) by @Exeloo
-- **core:** Add tickrate and fix runner ([1dba5bd](https://github.com/NanoForge-dev/Engine/commit/1dba5bd89ffa20dfd29b079f93c3eb923ffbdbbc)) by @Exeloo
-- **input:** Add input library ([387e97d](https://github.com/NanoForge-dev/Engine/commit/387e97d7c3015a869947af4acecf48e8e1b0e2b8)) by @Exeloo
-- **game:** Create pong example game ([4b66674](https://github.com/NanoForge-dev/Engine/commit/4b66674c750f345e860d225384054423433beb07)) by @bill-h4rper
-- **game:** Add width and height ([c93c985](https://github.com/NanoForge-dev/Engine/commit/c93c985665bd99c09bc410f1499d11aeaffe3c4c)) by @Exeloo
-- **game:** Add graphics factory ([0f4453c](https://github.com/NanoForge-dev/Engine/commit/0f4453ced908b39e953a672324e97eba82bfeaa3)) by @Exeloo
-- **asset-manager:** Add asset manager ([1774a26](https://github.com/NanoForge-dev/Engine/commit/1774a26593099b4faa0a2527d1684de35211d5d2)) by @Exeloo
-- Add asset manager default in core ([26cc5a9](https://github.com/NanoForge-dev/Engine/commit/26cc5a99e014fbc8669a43cc4aa4d78ecc1dee14)) by @Exeloo
-- Add core and common ([1755c79](https://github.com/NanoForge-dev/Engine/commit/1755c799c143513d72b28edaac875267d484a44f)) by @Exeloo
-- Initial commit ([c9bb59e](https://github.com/NanoForge-dev/Engine/commit/c9bb59ee963e7b444e8668db55597915e9ef0e4b)) by @Exeloo
-
## Refactor
-- **core:** Remove default libs in factory (#118) ([fa893c7](https://github.com/NanoForge-dev/Engine/commit/fa893c71616f151343c2f52a4723a64cca65814a)) by @Exeloo
-- Migrate namespaces to `@nanoforge-dev` and update related imports ([c84c927](https://github.com/NanoForge-dev/Engine/commit/c84c927ead941d914e5a9fd752fd3a5ac969f981)) by @Exeloo
-- **libraries:** Implement initialization validation and standardize nullable fields ([8b04575](https://github.com/NanoForge-dev/Engine/commit/8b04575cf7f649a440b8f40ad6114414406b0c1a)) by @Exeloo
-
diff --git a/packages/core-editor/README.md b/packages/core-editor/README.md
index f114fe2e..af648541 100644
--- a/packages/core-editor/README.md
+++ b/packages/core-editor/README.md
@@ -16,17 +16,17 @@
## About
-`@nanoforge-dev/core` is a core package that contains game main loop. It is used to initialize the game and run it.
+`@nanoforge-dev/core-editor` is a core package that contains game main loop. It is used to initialize the game and run it.
## Installation
**Node.js 24.11.0 or newer is required.**
```sh
-npm install @nanoforge-dev/core
-yarn add @nanoforge-dev/core
-pnpm add @nanoforge-dev/core
-bun add @nanoforge-dev/core
+npm install @nanoforge-dev/core-editor
+yarn add @nanoforge-dev/core-editor
+pnpm add @nanoforge-dev/core-editor
+bun add @nanoforge-dev/core-editor
```
## Example usage
@@ -34,10 +34,9 @@ bun add @nanoforge-dev/core
Initialize the game in your main file.
```ts
-import { type IRunOptions } from "@nanoforge-dev/common";
-import { NanoforgeFactory } from "@nanoforge-dev/core";
+import { IEditorRunOptions, NanoforgeFactory } from "@nanoforge-dev/core-editor";
-export async function main(options: IRunClientOptions) {
+export async function main(options: IEditorRunOptions) {
const app = NanoforgeFactory.createClient();
await app.init(options);
@@ -63,6 +62,6 @@ If you don't understand something in the documentation, you are experiencing pro
[documentation]: https://github.com/NanoForge-dev/Engine
[discussions]: https://github.com/NanoForge-dev/Engine/discussions
-[source]: https://github.com/NanoForge-dev/Engine/tree/main/packages/core
-[npm]: https://www.npmjs.com/package/@nanoforge-dev/core
+[source]: https://github.com/NanoForge-dev/Engine/tree/main/packages/core-editor
+[npm]: https://www.npmjs.com/package/@nanoforge-dev/core-editor
[contributing]: https://github.com/NanoForge-dev/Engine/blob/main/.github/CONTRIBUTING.md
diff --git a/packages/core-editor/cliff.toml b/packages/core-editor/cliff.toml
index 59978d4c..a9abf0b7 100644
--- a/packages/core-editor/cliff.toml
+++ b/packages/core-editor/cliff.toml
@@ -69,7 +69,7 @@ commit_parsers = [
]
filter_commits = true
protect_breaking_commits = true
-tag_pattern = "@nanoforge-dev/core@[0-9]*"
+tag_pattern = "@nanoforge-dev/core-editor@[0-9]*"
ignore_tags = ""
topo_order = false
sort_commits = "newest"
diff --git a/packages/core-editor/package.json b/packages/core-editor/package.json
index 8f9080bf..c2fe4a6a 100644
--- a/packages/core-editor/package.json
+++ b/packages/core-editor/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@nanoforge-dev/core-editor",
- "version": "1.0",
+ "version": "0.0",
"description": "NanoForge Engine - Core Editor",
"keywords": [
"nanoforge",
diff --git a/packages/core-editor/src/common/context/event-emitter.type.ts b/packages/core-editor/src/common/context/event-emitter.type.ts
index c9c040d0..c90caba3 100644
--- a/packages/core-editor/src/common/context/event-emitter.type.ts
+++ b/packages/core-editor/src/common/context/event-emitter.type.ts
@@ -3,6 +3,22 @@ export enum EventTypeEnum {
HARD_RELOAD = "hard-reload",
}
-export interface EventEmitter {
- eventQueue: (EventTypeEnum | string)[];
+export type ListenerType = (...args: any[]) => void;
+
+export interface IEventEmitter {
+ listeners: Record;
+ eventQueue: { event: EventTypeEnum | string; args: any[] }[];
+
+ runEvents: () => void;
+
+ emitEvent: (event: EventTypeEnum, ...args: any) => void;
+
+ addListener: (event: EventTypeEnum | string, listener: ListenerType) => void;
+ on: (event: EventTypeEnum | string, listener: ListenerType) => void;
+
+ removeListener: (event: EventTypeEnum | string, listener: ListenerType) => void;
+ off: (event: EventTypeEnum | string, listener: ListenerType) => void;
+
+ removeListenersForEvent: (event: EventTypeEnum | string) => void;
+ removeAllListeners: () => void;
}
diff --git a/packages/core-editor/src/common/context/options.type.ts b/packages/core-editor/src/common/context/options.type.ts
index 3a06c151..1fc49839 100644
--- a/packages/core-editor/src/common/context/options.type.ts
+++ b/packages/core-editor/src/common/context/options.type.ts
@@ -1,4 +1,4 @@
-import { type EventEmitter } from "./event-emitter.type";
+import { type EventEmitter } from "../../editor/event-emitter.manager";
import { type Save } from "./save.type";
export type IEditorRunOptions = IEditorRunClientOptions | IEditorRunServerOptions;
@@ -9,15 +9,16 @@ export interface IEditorRunClientOptions {
env: Record;
editor: {
save: Save;
- events: EventEmitter;
+ coreEvents: EventEmitter;
+ editorEvents: EventEmitter;
};
}
export interface IEditorRunServerOptions {
- canvas: HTMLCanvasElement;
files: Map;
env: Record;
editor: {
save: Save;
- events: EventEmitter;
+ coreEvents: EventEmitter;
+ editorEvents: EventEmitter;
};
}
diff --git a/packages/core-editor/src/editor/core-editor.ts b/packages/core-editor/src/editor/core-editor.ts
index 2f454410..553638d5 100644
--- a/packages/core-editor/src/editor/core-editor.ts
+++ b/packages/core-editor/src/editor/core-editor.ts
@@ -10,20 +10,11 @@ export class CoreEditor {
constructor(editor: IEditorRunOptions["editor"], ecsLibrary: ECSClientLibrary) {
this.editor = editor;
this.ecsLibrary = ecsLibrary;
+ this.editor.coreEvents?.addListener(EventTypeEnum.HOT_RELOAD, this.askEntitiesHotReload);
}
public runEvents() {
- const events: (EventTypeEnum | string)[] = this.editor.events.eventQueue;
- while (events.length > 0) {
- const event = events.shift();
- switch (event) {
- case EventTypeEnum.HOT_RELOAD:
- this.askEntitiesHotReload();
- break;
- default:
- console.warn(`Unknown event type ${event}`);
- }
- }
+ this.editor.coreEvents?.runEvents();
}
public askEntitiesHotReload(): void {
diff --git a/packages/core-editor/src/editor/event-emitter.manager.ts b/packages/core-editor/src/editor/event-emitter.manager.ts
new file mode 100644
index 00000000..3498d826
--- /dev/null
+++ b/packages/core-editor/src/editor/event-emitter.manager.ts
@@ -0,0 +1,50 @@
+import {
+ type EventTypeEnum,
+ type IEventEmitter,
+ type ListenerType,
+} from "../common/context/event-emitter.type";
+
+export class EventEmitter implements IEventEmitter {
+ public listeners: Record = {};
+ public eventQueue: { event: EventTypeEnum | string; args: any[] }[] = [];
+
+ public runEvents = () => {
+ this.eventQueue.forEach(({ event, args }) => {
+ this.listeners[event]?.forEach((listener) => {
+ listener(...args);
+ });
+ });
+ this.eventQueue = [];
+ };
+
+ public emitEvent(event: EventTypeEnum | string, ...args: any[]) {
+ this.eventQueue.push({ event, args });
+ }
+
+ public addListener(event: EventTypeEnum | string, listener: ListenerType): void {
+ if (!this.listeners[event]) this.listeners[event] = [];
+ this.listeners[event].push(listener);
+ }
+ public on(event: EventTypeEnum | string, listener: ListenerType): void {
+ this.addListener(event, listener);
+ }
+
+ public removeListener(event: EventTypeEnum | string, listener: ListenerType): void {
+ if (!this.listeners[event]) return;
+ const index = this.listeners[event].indexOf(listener);
+ if (index >= 0) {
+ this.listeners[event].splice(index, 1);
+ }
+ }
+ public off(event: EventTypeEnum | string, listener: ListenerType): void {
+ this.removeListener(event, listener);
+ }
+
+ public removeListenersForEvent(event: EventTypeEnum | string): void {
+ if (!this.listeners[event]) return;
+ this.listeners[event] = [];
+ }
+ public removeAllListeners(): void {
+ this.listeners = {};
+ }
+}
diff --git a/packages/core-editor/test/editor-feature.spec.ts b/packages/core-editor/test/editor-feature.spec.ts
index 2c67193c..bd442f78 100644
--- a/packages/core-editor/test/editor-feature.spec.ts
+++ b/packages/core-editor/test/editor-feature.spec.ts
@@ -1,24 +1,29 @@
import { type ECSClientLibrary } from "@nanoforge-dev/ecs-client";
import { afterEach, describe, expect, it, vi } from "vitest";
-import { type EventEmitter, EventTypeEnum } from "../src/common/context/event-emitter.type";
+import { EventTypeEnum } from "../src/common/context/event-emitter.type";
import type { IEditorRunOptions } from "../src/common/context/options.type";
import { type Save, type SaveComponent, type SaveEntity } from "../src/common/context/save.type";
import { CoreEditor } from "../src/editor/core-editor";
+import { EventEmitter } from "../src/editor/event-emitter.manager";
describe("EditorFeatures", () => {
afterEach(() => {
vi.restoreAllMocks();
});
+
describe("eventEmitter", () => {
it("should execute eventQueue once", async () => {
- const events: EventEmitter = {
- eventQueue: [EventTypeEnum.HOT_RELOAD, EventTypeEnum.HOT_RELOAD],
- };
+ const events = new EventEmitter();
+ events.emitEvent(EventTypeEnum.HOT_RELOAD);
+ events.emitEvent(EventTypeEnum.HOT_RELOAD);
const spyHotReload = vi
.spyOn(CoreEditor.prototype, "askEntitiesHotReload")
.mockImplementation(() => {});
- new CoreEditor({ events } as IEditorRunOptions["editor"], {} as ECSClientLibrary).runEvents();
+ new CoreEditor(
+ { coreEvents: events } as IEditorRunOptions["editor"],
+ {} as ECSClientLibrary,
+ ).runEvents();
expect(spyHotReload).toHaveBeenCalledTimes(2);
});
});
From c8e9be799e84b7e697a08f4cd0dcc01cf856b38d Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Tue, 31 Mar 2026 00:55:58 +0900
Subject: [PATCH 10/14] fix(core-editor): cliffer name
---
packages/core-editor/.cliff-jumperrc.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/core-editor/.cliff-jumperrc.json b/packages/core-editor/.cliff-jumperrc.json
index 05f3e85e..c760745b 100644
--- a/packages/core-editor/.cliff-jumperrc.json
+++ b/packages/core-editor/.cliff-jumperrc.json
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/favware/cliff-jumper/main/assets/cliff-jumper.schema.json",
- "name": "core",
+ "name": "core-editor",
"org": "nanoforge-dev",
"packagePath": "packages/core-editor",
"identifierBase": false
From b94f0546182ad1449f7a658ac5deb5f47d31dfbd Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Tue, 31 Mar 2026 10:47:47 +0900
Subject: [PATCH 11/14] feat(core-editor): use core fonctions
---
.../src/application/application-config.ts | 89 ---------
.../application/application-options.type.ts | 3 -
.../src/application/nanoforge-application.ts | 6 +-
.../src/application/nanoforge-factory.ts | 2 +-
.../contexts/application.editable-context.ts | 20 --
.../executions/clear.editable-context.ts | 3 -
.../executions/execution.editable-context.ts | 3 -
.../executions/init.editable-context.ts | 3 -
.../contexts/library.editable-context.ts | 7 -
.../common/library/manager/library.manager.ts | 106 ----------
.../common/library/relationship-functions.ts | 122 ------------
.../core-editor/src/config/config-registry.ts | 19 --
packages/core-editor/src/core/core.ts | 12 +-
.../core-editor/test/config-registry.spec.ts | 54 ------
.../test/editable-library-manager.spec.ts | 182 ------------------
.../core-editor/test/relationship.spec.ts | 135 -------------
packages/core-editor/tsconfig.json | 7 +-
.../src/application/application-config.ts | 4 +-
18 files changed, 18 insertions(+), 759 deletions(-)
delete mode 100644 packages/core-editor/src/application/application-config.ts
delete mode 100644 packages/core-editor/src/application/application-options.type.ts
delete mode 100644 packages/core-editor/src/common/context/contexts/application.editable-context.ts
delete mode 100644 packages/core-editor/src/common/context/contexts/executions/clear.editable-context.ts
delete mode 100644 packages/core-editor/src/common/context/contexts/executions/execution.editable-context.ts
delete mode 100644 packages/core-editor/src/common/context/contexts/executions/init.editable-context.ts
delete mode 100644 packages/core-editor/src/common/context/contexts/library.editable-context.ts
delete mode 100644 packages/core-editor/src/common/library/manager/library.manager.ts
delete mode 100644 packages/core-editor/src/common/library/relationship-functions.ts
delete mode 100644 packages/core-editor/src/config/config-registry.ts
delete mode 100644 packages/core-editor/test/config-registry.spec.ts
delete mode 100644 packages/core-editor/test/editable-library-manager.spec.ts
delete mode 100644 packages/core-editor/test/relationship.spec.ts
diff --git a/packages/core-editor/src/application/application-config.ts b/packages/core-editor/src/application/application-config.ts
deleted file mode 100644
index 7eeede0c..00000000
--- a/packages/core-editor/src/application/application-config.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import {
- type IAssetManagerLibrary,
- type IComponentSystemLibrary,
- type IGraphicsLibrary,
- type IInputLibrary,
- type ILibrary,
- type IMusicLibrary,
- type INetworkLibrary,
- type ISoundLibrary,
- type LibraryHandle,
-} from "@nanoforge-dev/common";
-
-import { EditableLibraryManager } from "../common/library/manager/library.manager";
-
-export class ApplicationConfig {
- private readonly _libraryManager: EditableLibraryManager;
-
- constructor() {
- this._libraryManager = new EditableLibraryManager();
- }
-
- get libraryManager(): EditableLibraryManager {
- return this._libraryManager;
- }
-
- public getLibrary(sym: symbol): LibraryHandle {
- return this._libraryManager.get(sym);
- }
-
- public useLibrary(sym: symbol, library: ILibrary): void {
- this._libraryManager.set(sym, library);
- }
-
- public getComponentSystemLibrary() {
- return this._libraryManager.getComponentSystem();
- }
-
- public useComponentSystemLibrary(library: IComponentSystemLibrary) {
- this._libraryManager.setComponentSystem(library);
- }
-
- public getGraphicsLibrary() {
- return this._libraryManager.getGraphics();
- }
-
- public useGraphicsLibrary(library: IGraphicsLibrary) {
- this._libraryManager.setGraphics(library);
- }
-
- public getNetworkLibrary() {
- return this._libraryManager.getNetwork();
- }
-
- public useNetworkLibrary(library: INetworkLibrary) {
- this._libraryManager.setNetwork(library);
- }
-
- public getAssetManagerLibrary() {
- return this._libraryManager.getAssetManager();
- }
-
- public useAssetManagerLibrary(library: IAssetManagerLibrary) {
- this._libraryManager.setAssetManager(library);
- }
-
- public getInputLibrary() {
- return this._libraryManager.getInput();
- }
-
- public useInputLibrary(library: IInputLibrary) {
- this._libraryManager.setInput(library);
- }
-
- public getSoundLibrary() {
- return this._libraryManager.getSound();
- }
-
- public useSoundLibrary(library: ISoundLibrary) {
- this._libraryManager.setSound(library);
- }
-
- public getMusicLibrary() {
- return this._libraryManager.getMusic();
- }
-
- public useMusicLibrary(library: IMusicLibrary) {
- this._libraryManager.setMusic(library);
- }
-}
diff --git a/packages/core-editor/src/application/application-options.type.ts b/packages/core-editor/src/application/application-options.type.ts
deleted file mode 100644
index 57ecc833..00000000
--- a/packages/core-editor/src/application/application-options.type.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export interface IApplicationOptions {
- tickRate: number;
-}
diff --git a/packages/core-editor/src/application/nanoforge-application.ts b/packages/core-editor/src/application/nanoforge-application.ts
index 81f6ea6e..66bb18eb 100644
--- a/packages/core-editor/src/application/nanoforge-application.ts
+++ b/packages/core-editor/src/application/nanoforge-application.ts
@@ -6,11 +6,11 @@ import {
NfNotInitializedException,
} from "@nanoforge-dev/common";
-import { EditableApplicationContext } from "../common/context/contexts/application.editable-context";
+import { ApplicationConfig } from "../../../core/src/application/application-config";
+import type { IApplicationOptions } from "../../../core/src/application/application-options.type";
+import { EditableApplicationContext } from "../../../core/src/common/context/contexts/application.editable-context";
import { type IEditorRunOptions } from "../common/context/options.type";
import { Core } from "../core/core";
-import { ApplicationConfig } from "./application-config";
-import type { IApplicationOptions } from "./application-options.type";
export abstract class NanoforgeApplication {
protected applicationConfig: ApplicationConfig;
diff --git a/packages/core-editor/src/application/nanoforge-factory.ts b/packages/core-editor/src/application/nanoforge-factory.ts
index 84711a6d..98ec685e 100644
--- a/packages/core-editor/src/application/nanoforge-factory.ts
+++ b/packages/core-editor/src/application/nanoforge-factory.ts
@@ -1,4 +1,4 @@
-import { type IApplicationOptions } from "./application-options.type";
+import { type IApplicationOptions } from "../../../core/src/application/application-options.type";
import { NanoforgeClient } from "./nanoforge-client";
import { NanoforgeServer } from "./nanoforge-server";
diff --git a/packages/core-editor/src/common/context/contexts/application.editable-context.ts b/packages/core-editor/src/common/context/contexts/application.editable-context.ts
deleted file mode 100644
index 492b797e..00000000
--- a/packages/core-editor/src/common/context/contexts/application.editable-context.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { ApplicationContext } from "@nanoforge-dev/common";
-
-import { type EditableLibraryManager } from "../../library/manager/library.manager";
-
-export class EditableApplicationContext extends ApplicationContext {
- private _libraryManager: EditableLibraryManager;
-
- constructor(libraryManager: EditableLibraryManager) {
- super();
- this._libraryManager = libraryManager;
- }
-
- setDelta(delta: number) {
- this._delta = delta;
- }
-
- muteSoundLibraries(): void {
- this._libraryManager.getMutableLibraries().forEach((lib) => lib.library.mute());
- }
-}
diff --git a/packages/core-editor/src/common/context/contexts/executions/clear.editable-context.ts b/packages/core-editor/src/common/context/contexts/executions/clear.editable-context.ts
deleted file mode 100644
index 1081686d..00000000
--- a/packages/core-editor/src/common/context/contexts/executions/clear.editable-context.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { ClearContext } from "@nanoforge-dev/common";
-
-export class EditableClearContext extends ClearContext {}
diff --git a/packages/core-editor/src/common/context/contexts/executions/execution.editable-context.ts b/packages/core-editor/src/common/context/contexts/executions/execution.editable-context.ts
deleted file mode 100644
index e9b5b3de..00000000
--- a/packages/core-editor/src/common/context/contexts/executions/execution.editable-context.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { ExecutionContext } from "@nanoforge-dev/common";
-
-export class EditableExecutionContext extends ExecutionContext {}
diff --git a/packages/core-editor/src/common/context/contexts/executions/init.editable-context.ts b/packages/core-editor/src/common/context/contexts/executions/init.editable-context.ts
deleted file mode 100644
index 7ce44e10..00000000
--- a/packages/core-editor/src/common/context/contexts/executions/init.editable-context.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { InitContext } from "@nanoforge-dev/common";
-
-export class EditableInitContext extends InitContext {}
diff --git a/packages/core-editor/src/common/context/contexts/library.editable-context.ts b/packages/core-editor/src/common/context/contexts/library.editable-context.ts
deleted file mode 100644
index 48f942e8..00000000
--- a/packages/core-editor/src/common/context/contexts/library.editable-context.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { LibraryContext, type LibraryStatusEnum } from "@nanoforge-dev/common";
-
-export class EditableLibraryContext extends LibraryContext {
- setStatus(status: LibraryStatusEnum) {
- this._status = status;
- }
-}
diff --git a/packages/core-editor/src/common/library/manager/library.manager.ts b/packages/core-editor/src/common/library/manager/library.manager.ts
deleted file mode 100644
index 6e17b321..00000000
--- a/packages/core-editor/src/common/library/manager/library.manager.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import {
- ASSET_MANAGER_LIBRARY,
- COMPONENT_SYSTEM_LIBRARY,
- DefaultLibrariesEnum,
- GRAPHICS_LIBRARY,
- type IAssetManagerLibrary,
- type IComponentSystemLibrary,
- type IGraphicsLibrary,
- type IInputLibrary,
- type ILibrary,
- type IMusicLibrary,
- type IMutableLibrary,
- INPUT_LIBRARY,
- type INetworkLibrary,
- type IRunnerLibrary,
- type ISoundLibrary,
- type LibraryHandle,
- LibraryManager,
- MUSIC_LIBRARY,
- NETWORK_LIBRARY,
- SOUND_LIBRARY,
-} from "@nanoforge-dev/common";
-
-import { EditableLibraryContext } from "../../context/contexts/library.editable-context";
-import { Relationship } from "../relationship-functions";
-
-const hasMethod = (obj: any, method: string) => {
- return typeof obj[method] === "function";
-};
-
-export class EditableLibraryManager extends LibraryManager {
- public set(sym: symbol, library: ILibrary) {
- this.setNewLibrary(sym, library, new EditableLibraryContext());
- }
-
- public setComponentSystem(library: IComponentSystemLibrary): void {
- this._set(
- DefaultLibrariesEnum.COMPONENT_SYSTEM,
- COMPONENT_SYSTEM_LIBRARY,
- library,
- new EditableLibraryContext(),
- );
- }
-
- public setGraphics(library: IGraphicsLibrary): void {
- this._set(
- DefaultLibrariesEnum.GRAPHICS,
- GRAPHICS_LIBRARY,
- library,
- new EditableLibraryContext(),
- );
- }
-
- public setAssetManager(library: IAssetManagerLibrary): void {
- this._set(
- DefaultLibrariesEnum.ASSET_MANAGER,
- ASSET_MANAGER_LIBRARY,
- library,
- new EditableLibraryContext(),
- );
- }
-
- public setNetwork(library: INetworkLibrary): void {
- this._set(DefaultLibrariesEnum.NETWORK, NETWORK_LIBRARY, library, new EditableLibraryContext());
- }
-
- public setInput(library: IInputLibrary): void {
- this._set(DefaultLibrariesEnum.INPUT, INPUT_LIBRARY, library, new EditableLibraryContext());
- }
-
- public setSound(library: ISoundLibrary): void {
- this._set(DefaultLibrariesEnum.SOUND, SOUND_LIBRARY, library, new EditableLibraryContext());
- }
-
- public setMusic(library: IMusicLibrary): void {
- this._set(DefaultLibrariesEnum.MUSIC, MUSIC_LIBRARY, library, new EditableLibraryContext());
- }
-
- public getLibraries(): LibraryHandle[] {
- return this._libraries;
- }
-
- public getInitLibraries(): LibraryHandle[] {
- return Relationship.getLibrariesByDependencies(this._libraries);
- }
-
- public getExecutionLibraries(): LibraryHandle[] {
- return Relationship.getLibrariesByRun(this._getRunnerLibraries());
- }
-
- public getClearLibraries(): LibraryHandle[] {
- return Relationship.getLibrariesByDependencies(this._libraries, true);
- }
-
- public getMutableLibraries(): LibraryHandle[] {
- return this._libraries.filter(
- (handle) => handle && hasMethod(handle.library, "mute"),
- ) as LibraryHandle[];
- }
-
- private _getRunnerLibraries(): LibraryHandle[] {
- return this._libraries.filter(
- (handle) => handle && hasMethod(handle.library, "__run"),
- ) as LibraryHandle[];
- }
-}
diff --git a/packages/core-editor/src/common/library/relationship-functions.ts b/packages/core-editor/src/common/library/relationship-functions.ts
deleted file mode 100644
index d9ff0f2b..00000000
--- a/packages/core-editor/src/common/library/relationship-functions.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import { type ILibrary, type LibraryHandle } from "@nanoforge-dev/common";
-
-class RelationshipStatic {
- getLibrariesByDependencies(libraries: LibraryHandle[], reverse: boolean = false) {
- let response: LibraryHandle[] = [];
- for (const library of libraries) {
- if (!library) continue;
- response = this._pushLibraryWithDependencies(library, response, [], libraries);
- }
-
- if (reverse) return response.reverse();
- return response;
- }
-
- getLibrariesByRun(libraries: LibraryHandle[]) {
- let response: LibraryHandle[] = [];
- const dependencies = new Map>(
- libraries.map((library) => [library.symbol, new Set()]),
- );
-
- for (const handle of libraries) {
- const key = handle.symbol;
-
- for (const before of handle.library.__relationship.runBefore) {
- this._pushToDependencies(key, before, dependencies);
- }
- for (const after of handle.library.__relationship.runAfter) {
- this._pushToDependencies(after, key, dependencies);
- }
- }
-
- for (const library of libraries) {
- response = this._pushLibraryWithDependenciesRun(
- library,
- dependencies,
- response,
- [],
- libraries,
- );
- }
- return response;
- }
-
- private _pushToDependencies(
- key: symbol,
- value: symbol,
- dependencies: Map>,
- ): void {
- let curr = dependencies.get(key);
- if (!curr) curr = new Set();
- curr.add(value);
- dependencies.set(key, curr);
- }
-
- private _pushLibraryWithDependenciesRun(
- handle: LibraryHandle,
- dependencies: Map>,
- response: LibraryHandle[],
- cache: symbol[],
- libraries: LibraryHandle[],
- ): LibraryHandle[] {
- const key = handle.symbol;
- if (this._symbolIsInList(key, response)) return response;
-
- if (cache.includes(key)) throw new Error("Circular dependencies !");
-
- cache.push(key);
-
- const deps = dependencies.get(key);
- if (!deps) throw new Error("Dependencies not found");
-
- for (const dep of deps) {
- if (this._symbolIsInList(dep, response)) continue;
-
- const depHandle = libraries.find((lib) => lib?.symbol === dep) as LibraryHandle;
- if (!depHandle) throw new Error(`Cannot find library ${dep.toString()}`);
-
- response = this._pushLibraryWithDependenciesRun(
- depHandle,
- dependencies,
- response,
- cache,
- libraries,
- );
- }
- cache.pop();
-
- response.push(handle);
- return response;
- }
-
- private _pushLibraryWithDependencies(
- handle: LibraryHandle,
- response: LibraryHandle[],
- cache: symbol[],
- libraries: LibraryHandle[],
- ): LibraryHandle[] {
- if (this._symbolIsInList(handle.symbol, response)) return response;
-
- if (cache.includes(handle.symbol)) throw new Error("Circular dependencies !");
-
- cache.push(handle.symbol);
- for (const dep of handle.library.__relationship.dependencies) {
- if (this._symbolIsInList(dep, response)) continue;
-
- const depHandle = libraries.find((lib) => lib?.symbol === dep) as LibraryHandle;
- if (!depHandle) throw new Error(`Cannot find library ${dep.toString()}`);
-
- response = this._pushLibraryWithDependencies(depHandle, response, cache, libraries);
- }
- cache.pop();
-
- response.push(handle);
- return response;
- }
-
- private _symbolIsInList(sym: symbol, libraries: LibraryHandle[]): boolean {
- return libraries.some((lib) => lib.symbol === sym);
- }
-}
-
-export const Relationship = new RelationshipStatic();
diff --git a/packages/core-editor/src/config/config-registry.ts b/packages/core-editor/src/config/config-registry.ts
deleted file mode 100644
index 946ed1dc..00000000
--- a/packages/core-editor/src/config/config-registry.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { plainToInstance } from "class-transformer";
-import { validate } from "class-validator";
-
-export class ConfigRegistry {
- private readonly _env: Record;
-
- constructor(env: Record) {
- this._env = env;
- }
-
- async registerConfig(config: new () => T): Promise {
- const data = plainToInstance(config, this._env, { excludeExtraneousValues: true });
- const errors = await validate(data);
- if (errors.length > 0) {
- throw new Error(errors.toString());
- }
- return data;
- }
-}
diff --git a/packages/core-editor/src/core/core.ts b/packages/core-editor/src/core/core.ts
index 9857164b..b917ce2e 100644
--- a/packages/core-editor/src/core/core.ts
+++ b/packages/core-editor/src/core/core.ts
@@ -10,13 +10,13 @@ import {
} from "@nanoforge-dev/common";
import { type ECSClientLibrary } from "@nanoforge-dev/ecs-client";
-import { type ApplicationConfig } from "../application/application-config";
-import type { IApplicationOptions } from "../application/application-options.type";
-import { type EditableApplicationContext } from "../common/context/contexts/application.editable-context";
-import { EditableExecutionContext } from "../common/context/contexts/executions/execution.editable-context";
-import { type EditableLibraryContext } from "../common/context/contexts/library.editable-context";
+import { type ApplicationConfig } from "../../../core/src/application/application-config";
+import type { IApplicationOptions } from "../../../core/src/application/application-options.type";
+import { type EditableApplicationContext } from "../../../core/src/common/context/contexts/application.editable-context";
+import { EditableExecutionContext } from "../../../core/src/common/context/contexts/executions/execution.editable-context";
+import { type EditableLibraryContext } from "../../../core/src/common/context/contexts/library.editable-context";
+import { ConfigRegistry } from "../../../core/src/config/config-registry";
import { type IEditorRunOptions } from "../common/context/options.type";
-import { ConfigRegistry } from "../config/config-registry";
import { CoreEditor } from "../editor/core-editor";
export class Core {
diff --git a/packages/core-editor/test/config-registry.spec.ts b/packages/core-editor/test/config-registry.spec.ts
deleted file mode 100644
index 0a1e2844..00000000
--- a/packages/core-editor/test/config-registry.spec.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { Expose } from "class-transformer";
-import { IsString } from "class-validator";
-import { describe, expect, it } from "vitest";
-
-import { ConfigRegistry } from "../src/config/config-registry";
-
-class ValidConfig {
- @Expose()
- @IsString()
- name!: string;
-}
-
-class OptionalConfig {
- @Expose()
- @IsString()
- name!: string;
-
- @Expose()
- host?: string;
-}
-
-describe("ConfigRegistry", () => {
- describe("registerConfig", () => {
- it("should return a transformed config instance when env is valid", async () => {
- const registry = new ConfigRegistry({ name: "hello" });
- const config = await registry.registerConfig(ValidConfig);
- expect(config).toBeInstanceOf(ValidConfig);
- expect(config.name).toBe("hello");
- });
-
- it("should exclude values not decorated with @Expose", async () => {
- const registry = new ConfigRegistry({ name: "hello", extra: "ignored" });
- const config = await registry.registerConfig(ValidConfig);
- expect((config as any)["extra"]).toBeUndefined();
- });
-
- it("should throw when a required field is missing", async () => {
- const registry = new ConfigRegistry({});
- await expect(registry.registerConfig(ValidConfig)).rejects.toThrow();
- });
-
- it("should throw when a field has the wrong type", async () => {
- const registry = new ConfigRegistry({ name: 42 });
- await expect(registry.registerConfig(ValidConfig)).rejects.toThrow();
- });
-
- it("should map multiple env fields correctly", async () => {
- const registry = new ConfigRegistry({ name: "world", host: "localhost" });
- const config = await registry.registerConfig(OptionalConfig);
- expect(config.name).toBe("world");
- expect(config.host).toBe("localhost");
- });
- });
-});
diff --git a/packages/core-editor/test/editable-library-manager.spec.ts b/packages/core-editor/test/editable-library-manager.spec.ts
deleted file mode 100644
index 0e808baa..00000000
--- a/packages/core-editor/test/editable-library-manager.spec.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import { COMPONENT_SYSTEM_LIBRARY, type ILibrary, LibraryStatusEnum } from "@nanoforge-dev/common";
-import { beforeEach, describe, expect, it } from "vitest";
-
-import { Library } from "../../common/src/library/libraries/library";
-import { EditableLibraryManager } from "../src/common/library/manager/library.manager";
-
-class StubLibrary extends Library {
- private readonly _name: string;
-
- constructor(name: string, options?: ConstructorParameters[0]) {
- super(options);
- this._name = name;
- }
-
- get __name(): string {
- return this._name;
- }
-}
-
-class StubRunnerLibrary extends StubLibrary {
- async __run(): Promise {}
-}
-
-class StubMutableLibrary extends StubLibrary {
- mute(): void {}
-}
-
-describe("EditableLibraryManager", () => {
- let manager: EditableLibraryManager;
-
- beforeEach(() => {
- manager = new EditableLibraryManager();
- });
-
- describe("typed setters and getters", () => {
- it("should store and retrieve a component system library", () => {
- const lib = new StubLibrary("ComponentSystem");
- manager.setComponentSystem(lib as any);
- expect(manager.getComponentSystem().library).toBe(lib);
- });
-
- it("should store and retrieve a graphics library", () => {
- const lib = new StubLibrary("Graphics");
- manager.setGraphics(lib as any);
- expect(manager.getGraphics().library).toBe(lib);
- });
-
- it("should store and retrieve an asset manager library", () => {
- const lib = new StubLibrary("AssetManager");
- manager.setAssetManager(lib as any);
- expect(manager.getAssetManager().library).toBe(lib);
- });
-
- it("should store and retrieve a network library", () => {
- const lib = new StubLibrary("Network");
- manager.setNetwork(lib as any);
- expect(manager.getNetwork().library).toBe(lib);
- });
-
- it("should store and retrieve an input library", () => {
- const lib = new StubLibrary("Input");
- manager.setInput(lib as any);
- expect(manager.getInput().library).toBe(lib);
- });
-
- it("should store and retrieve a sound library", () => {
- const lib = new StubLibrary("Sound");
- manager.setSound(lib as any);
- expect(manager.getSound().library).toBe(lib);
- });
-
- it("should store and retrieve a music library", () => {
- const lib = new StubLibrary("Music");
- manager.setMusic(lib as any);
- expect(manager.getMusic().library).toBe(lib);
- });
-
- it("should throw when getting a typed library that was not set", () => {
- expect(() => manager.getComponentSystem()).toThrow();
- });
- });
-
- describe("set and get (custom symbol)", () => {
- it("should store and retrieve a library by Symbol.for key", () => {
- const sym = Symbol.for("customLib");
- const lib = new StubLibrary("Custom");
-
- manager.setAssetManager(new StubLibrary("Asset") as any);
- manager.set(sym, lib as unknown as ILibrary);
-
- expect(manager.get(sym).library).toBe(lib);
- });
- });
-
- describe("getLibraries", () => {
- it("should return the list of all set libraries", () => {
- const lib = new StubLibrary("ComponentSystem");
- manager.setComponentSystem(lib as any);
- const libs = manager.getLibraries().filter(Boolean);
- expect(libs.some((h) => h.library === (lib as unknown as ILibrary))).toBe(true);
- });
- });
-
- describe("getInitLibraries", () => {
- it("should return libraries in dependency order", () => {
- const libA = new StubLibrary("A", { dependencies: [COMPONENT_SYSTEM_LIBRARY] });
- const libB = new StubLibrary("B");
-
- manager.setComponentSystem(libB as any);
- manager.setGraphics(libA as any);
-
- const order = manager.getInitLibraries().map((h) => h.library.__name);
- const idxB = order.indexOf("B");
- const idxA = order.indexOf("A");
-
- expect(idxB).toBeLessThan(idxA);
- });
-
- it("should return all set libraries", () => {
- manager.setAssetManager(new StubLibrary("Asset") as any);
- manager.setGraphics(new StubLibrary("Graphics") as any);
-
- expect(manager.getInitLibraries().length).toBe(2);
- });
- });
-
- describe("getClearLibraries", () => {
- it("should return libraries in reverse dependency order", () => {
- const libA = new StubLibrary("A", { dependencies: [COMPONENT_SYSTEM_LIBRARY] });
- const libB = new StubLibrary("B");
-
- manager.setComponentSystem(libB as any);
- manager.setGraphics(libA as any);
-
- const order = manager.getClearLibraries().map((h) => h.library.__name);
- const idxA = order.indexOf("A");
- const idxB = order.indexOf("B");
-
- expect(idxA).toBeLessThan(idxB);
- });
- });
-
- describe("getExecutionLibraries", () => {
- it("should only return libraries that implement __run", () => {
- manager.setComponentSystem(new StubLibrary("NotARunner") as any);
- manager.setGraphics(new StubRunnerLibrary("Runner") as any);
-
- const runners = manager.getExecutionLibraries();
- expect(runners.every((h) => typeof (h.library as any).__run === "function")).toBe(true);
- expect(runners.some((h) => h.library.__name === "Runner")).toBe(true);
- expect(runners.some((h) => h.library.__name === "NotARunner")).toBe(false);
- });
-
- it("should return empty when no runner libraries are set", () => {
- manager.setComponentSystem(new StubLibrary("Static") as any);
- expect(manager.getExecutionLibraries()).toHaveLength(0);
- });
- });
-
- describe("getMutableLibraries", () => {
- it("should only return libraries that implement mute", () => {
- manager.setSound(new StubMutableLibrary("MutableSound") as any);
- manager.setGraphics(new StubLibrary("NonMutableGraphics") as any);
-
- const mutable = manager.getMutableLibraries();
- expect(mutable.some((h) => h.library.__name === "MutableSound")).toBe(true);
- expect(mutable.some((h) => h.library.__name === "NonMutableGraphics")).toBe(false);
- });
-
- it("should return empty when no mutable libraries are set", () => {
- manager.setGraphics(new StubLibrary("Graphics") as any);
- expect(manager.getMutableLibraries()).toHaveLength(0);
- });
- });
-
- describe("library context status", () => {
- it("should start with UNLOADED status", () => {
- manager.setComponentSystem(new StubLibrary("Comp") as any);
- expect(manager.getComponentSystem().context.status).toBe(LibraryStatusEnum.UNLOADED);
- });
- });
-});
diff --git a/packages/core-editor/test/relationship.spec.ts b/packages/core-editor/test/relationship.spec.ts
deleted file mode 100644
index 776fa493..00000000
--- a/packages/core-editor/test/relationship.spec.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-import { type ILibrary, LibraryContext, LibraryHandle } from "@nanoforge-dev/common";
-import { describe, expect, it } from "vitest";
-
-import { Library } from "../../common/src/library/libraries/library";
-import { Relationship } from "../src/common/library/relationship-functions";
-
-class StubLibrary extends Library {
- private readonly _name: string;
-
- constructor(name: string, options?: ConstructorParameters[0]) {
- super(options);
- this._name = name;
- }
-
- get __name(): string {
- return this._name;
- }
-}
-
-const makeHandle = (
- sym: symbol,
- name: string,
- options?: ConstructorParameters[0],
-): LibraryHandle => {
- return new LibraryHandle(
- sym,
- new StubLibrary(name, options) as unknown as ILibrary,
- new LibraryContext(),
- );
-};
-
-describe("Relationship.getLibrariesByDependencies", () => {
- it("should return libraries in same order when no dependencies are declared", () => {
- const symA = Symbol("A");
- const symB = Symbol("B");
- const handleA = makeHandle(symA, "A");
- const handleB = makeHandle(symB, "B");
-
- const result = Relationship.getLibrariesByDependencies([handleA, handleB]);
- expect(result.map((h) => h.library.__name)).toEqual(["A", "B"]);
- });
-
- it("should put a dependency before the library that depends on it", () => {
- const symA = Symbol("A");
- const symB = Symbol("B");
- const handleB = makeHandle(symB, "B");
- const handleA = makeHandle(symA, "A", { dependencies: [symB] });
-
- const result = Relationship.getLibrariesByDependencies([handleA, handleB]);
- const names = result.map((h) => h.library.__name);
- expect(names.indexOf("B")).toBeLessThan(names.indexOf("A"));
- });
-
- it("should not duplicate a shared dependency", () => {
- const symDep = Symbol("Dep");
- const symA = Symbol("A");
- const symB = Symbol("B");
- const handleDep = makeHandle(symDep, "Dep");
- const handleA = makeHandle(symA, "A", { dependencies: [symDep] });
- const handleB = makeHandle(symB, "B", { dependencies: [symDep] });
-
- const result = Relationship.getLibrariesByDependencies([handleA, handleB, handleDep]);
- const names = result.map((h) => h.library.__name);
- expect(names.filter((n) => n === "Dep")).toHaveLength(1);
- });
-
- it("should return libraries in reverse dependency order when reverse=true", () => {
- const symA = Symbol("A");
- const symB = Symbol("B");
- const handleB = makeHandle(symB, "B");
- const handleA = makeHandle(symA, "A", { dependencies: [symB] });
-
- const result = Relationship.getLibrariesByDependencies([handleA, handleB], true);
- const names = result.map((h) => h.library.__name);
- expect(names.indexOf("A")).toBeLessThan(names.indexOf("B"));
- });
-
- it("should throw on circular dependencies", () => {
- const symA = Symbol("A");
- const symB = Symbol("B");
- const handleA = makeHandle(symA, "A", { dependencies: [symB] });
- const handleB = makeHandle(symB, "B", { dependencies: [symA] });
-
- expect(() => Relationship.getLibrariesByDependencies([handleA, handleB])).toThrow(
- /[Cc]ircular/,
- );
- });
-});
-
-describe("Relationship.getLibrariesByRun", () => {
- it("should return all runner libraries when no ordering is specified", () => {
- const symA = Symbol("A");
- const symB = Symbol("B");
- const handleA = makeHandle(symA, "A");
- const handleB = makeHandle(symB, "B");
-
- const result = Relationship.getLibrariesByRun([handleA, handleB]);
- expect(result).toHaveLength(2);
- });
-
- it("should place a library before another when runBefore is set", () => {
- const symA = Symbol("A");
- const symB = Symbol("B");
- const handleA = makeHandle(symA, "A", { runBefore: [symB] });
- const handleB = makeHandle(symB, "B");
-
- const result = Relationship.getLibrariesByRun([handleA, handleB]);
- const names = result.map((h) => h.library.__name);
- expect(names.indexOf("B")).toBeLessThan(names.indexOf("A"));
- });
-
- it("should place a library after another when runAfter is set", () => {
- const symA = Symbol("A");
- const symB = Symbol("B");
- const handleA = makeHandle(symA, "A");
- const handleB = makeHandle(symB, "B", { runAfter: [symA] });
-
- const result = Relationship.getLibrariesByRun([handleA, handleB]);
- const names = result.map((h) => h.library.__name);
- expect(names.indexOf("B")).toBeLessThan(names.indexOf("A"));
- });
-
- it("should throw on circular run dependencies", () => {
- const symA = Symbol("A");
- const symB = Symbol("B");
- const handleA = makeHandle(symA, "A", { runBefore: [symB] });
- const handleB = makeHandle(symB, "B", { runBefore: [symA] });
-
- expect(() => Relationship.getLibrariesByRun([handleA, handleB])).toThrow(/[Cc]ircular/);
- });
-
- it("should return empty array for empty input", () => {
- expect(Relationship.getLibrariesByRun([])).toHaveLength(0);
- });
-});
diff --git a/packages/core-editor/tsconfig.json b/packages/core-editor/tsconfig.json
index 9e6d724b..2a9635c8 100644
--- a/packages/core-editor/tsconfig.json
+++ b/packages/core-editor/tsconfig.json
@@ -2,5 +2,10 @@
"$schema": "https://json.schemastore.org/tsconfig.json",
"extends": "../../tsconfig.json",
"include": ["src/**/*.ts"],
- "exclude": ["node_modules", "dist"]
+ "exclude": ["node_modules", "dist"],
+ "compilerOptions": {
+ "paths": {
+ "@core/*": ["../core/src"]
+ }
+ }
}
diff --git a/packages/core/src/application/application-config.ts b/packages/core/src/application/application-config.ts
index 82d28bed..7eeede0c 100644
--- a/packages/core/src/application/application-config.ts
+++ b/packages/core/src/application/application-config.ts
@@ -31,8 +31,8 @@ export class ApplicationConfig {
this._libraryManager.set(sym, library);
}
- public getComponentSystemLibrary() {
- return this._libraryManager.getComponentSystem();
+ public getComponentSystemLibrary() {
+ return this._libraryManager.getComponentSystem();
}
public useComponentSystemLibrary(library: IComponentSystemLibrary) {
From e985ead67fa301750d5b51fdee09e4ef993e269e Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Tue, 31 Mar 2026 11:47:43 +0900
Subject: [PATCH 12/14] fix(core-editor): version as str
---
packages/core-editor/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/core-editor/package.json b/packages/core-editor/package.json
index c2fe4a6a..74bd0ece 100644
--- a/packages/core-editor/package.json
+++ b/packages/core-editor/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@nanoforge-dev/core-editor",
- "version": "0.0",
+ "version": "0.0.0",
"description": "NanoForge Engine - Core Editor",
"keywords": [
"nanoforge",
From be0ce854e3cb60d051b7c0c36ec535f61623e4c7 Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Tue, 31 Mar 2026 15:04:26 +0900
Subject: [PATCH 13/14] fix(core-editor): review
---
packages/core-editor/CHANGELOG.md | 14 --------------
packages/core-editor/README.md | 26 ++++++--------------------
packages/core-editor/tsconfig.json | 7 +------
3 files changed, 7 insertions(+), 40 deletions(-)
diff --git a/packages/core-editor/CHANGELOG.md b/packages/core-editor/CHANGELOG.md
index 53aaa9de..e69de29b 100644
--- a/packages/core-editor/CHANGELOG.md
+++ b/packages/core-editor/CHANGELOG.md
@@ -1,14 +0,0 @@
-# Changelog
-
-All notable changes to this project will be documented in this file.
-
-## Documentation
-
-## Bug Fixes
-
-## Documentation
-
-## Features
-
-## Refactor
-
diff --git a/packages/core-editor/README.md b/packages/core-editor/README.md
index af648541..7daa7a82 100644
--- a/packages/core-editor/README.md
+++ b/packages/core-editor/README.md
@@ -5,18 +5,20 @@
-
-
+
+
-
+
## About
-`@nanoforge-dev/core-editor` is a core package that contains game main loop. It is used to initialize the game and run it.
+`@nanoforge-dev/core-editor` is a package made to allow the editor to make change to a game even if it's already compiled and running.
+
+If you want a core to run your game use the `@nanoforge-dev/core`.
## Installation
@@ -29,22 +31,6 @@ pnpm add @nanoforge-dev/core-editor
bun add @nanoforge-dev/core-editor
```
-## Example usage
-
-Initialize the game in your main file.
-
-```ts
-import { IEditorRunOptions, NanoforgeFactory } from "@nanoforge-dev/core-editor";
-
-export async function main(options: IEditorRunOptions) {
- const app = NanoforgeFactory.createClient();
-
- await app.init(options);
-
- await app.run();
-}
-```
-
## Links
- [GitHub][source]
diff --git a/packages/core-editor/tsconfig.json b/packages/core-editor/tsconfig.json
index 2a9635c8..9e6d724b 100644
--- a/packages/core-editor/tsconfig.json
+++ b/packages/core-editor/tsconfig.json
@@ -2,10 +2,5 @@
"$schema": "https://json.schemastore.org/tsconfig.json",
"extends": "../../tsconfig.json",
"include": ["src/**/*.ts"],
- "exclude": ["node_modules", "dist"],
- "compilerOptions": {
- "paths": {
- "@core/*": ["../core/src"]
- }
- }
+ "exclude": ["node_modules", "dist"]
}
From 767a2f1de4998366aa62f2fa656d842e0f51596e Mon Sep 17 00:00:00 2001
From: Tchips46
Date: Tue, 31 Mar 2026 16:14:00 +0900
Subject: [PATCH 14/14] fix(core-editor): review
---
.../core-editor/src/common/context/options.type.ts | 10 +++++-----
packages/core-editor/test/editor-feature.spec.ts | 2 +-
.../helpers/event-emitter.ts} | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
rename packages/core-editor/{src/editor/event-emitter.manager.ts => test/helpers/event-emitter.ts} (96%)
diff --git a/packages/core-editor/src/common/context/options.type.ts b/packages/core-editor/src/common/context/options.type.ts
index 1fc49839..eabf7e62 100644
--- a/packages/core-editor/src/common/context/options.type.ts
+++ b/packages/core-editor/src/common/context/options.type.ts
@@ -1,4 +1,4 @@
-import { type EventEmitter } from "../../editor/event-emitter.manager";
+import { type IEventEmitter } from "./event-emitter.type";
import { type Save } from "./save.type";
export type IEditorRunOptions = IEditorRunClientOptions | IEditorRunServerOptions;
@@ -9,8 +9,8 @@ export interface IEditorRunClientOptions {
env: Record;
editor: {
save: Save;
- coreEvents: EventEmitter;
- editorEvents: EventEmitter;
+ coreEvents: IEventEmitter;
+ editorEvents: IEventEmitter;
};
}
export interface IEditorRunServerOptions {
@@ -18,7 +18,7 @@ export interface IEditorRunServerOptions {
env: Record;
editor: {
save: Save;
- coreEvents: EventEmitter;
- editorEvents: EventEmitter;
+ coreEvents: IEventEmitter;
+ editorEvents: IEventEmitter;
};
}
diff --git a/packages/core-editor/test/editor-feature.spec.ts b/packages/core-editor/test/editor-feature.spec.ts
index bd442f78..c02f1a03 100644
--- a/packages/core-editor/test/editor-feature.spec.ts
+++ b/packages/core-editor/test/editor-feature.spec.ts
@@ -5,7 +5,7 @@ import { EventTypeEnum } from "../src/common/context/event-emitter.type";
import type { IEditorRunOptions } from "../src/common/context/options.type";
import { type Save, type SaveComponent, type SaveEntity } from "../src/common/context/save.type";
import { CoreEditor } from "../src/editor/core-editor";
-import { EventEmitter } from "../src/editor/event-emitter.manager";
+import { EventEmitter } from "./helpers/event-emitter";
describe("EditorFeatures", () => {
afterEach(() => {
diff --git a/packages/core-editor/src/editor/event-emitter.manager.ts b/packages/core-editor/test/helpers/event-emitter.ts
similarity index 96%
rename from packages/core-editor/src/editor/event-emitter.manager.ts
rename to packages/core-editor/test/helpers/event-emitter.ts
index 3498d826..95c6eb62 100644
--- a/packages/core-editor/src/editor/event-emitter.manager.ts
+++ b/packages/core-editor/test/helpers/event-emitter.ts
@@ -2,7 +2,7 @@ import {
type EventTypeEnum,
type IEventEmitter,
type ListenerType,
-} from "../common/context/event-emitter.type";
+} from "../../src/common/context/event-emitter.type";
export class EventEmitter implements IEventEmitter {
public listeners: Record = {};