diff --git a/.github/workflows/push-docs.yml b/.github/workflows/push-docs.yml new file mode 100644 index 0000000..1493b04 --- /dev/null +++ b/.github/workflows/push-docs.yml @@ -0,0 +1,49 @@ +name: synchronize-docs + +on: + push: + branches: + - main + paths: + - "docs/**" + - "packages/ecs-lib/docs/**" + - "packages/ecs-lib/wasm/**" + workflow_dispatch: + +jobs: + synchronize: + runs-on: ubuntu-latest + steps: + - name: Checkout engine + uses: actions/checkout@v5 + with: + path: engine + + - name: Checkout docs + uses: actions/checkout@v5 + with: + path: docs + repository: nanoforge-dev/docs + token: ${{ secrets.ACTIONS_KEY }} + + - name: setup git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "username@users.noreply.github.com" + + - name: setup registry code documentation + run: | + apt install doxygen -y + cd engine/packages/ecs-lib/docs + doxygen Doxyfile + cd - + cp engine/packages/ecs-lib/docs engine/docs/registry/api + + - name: synchronize docs + run: | + mkdir -p docs/engine + cp engine/docs/. -r docs/engine + cd docs + git add . + git commit -m "chore(engine): updating docs" + git push origin main diff --git a/README.md b/README.md index 83a9597..05b4065 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Nanoforge Documentation](https://github.com/NanoForge-dev/docs/actions/workflows/deploy.yml/badge.svg)](https://github.com/NanoForge-dev/docs/actions/workflows/deploy.yml) + # Engine This repository contains the full engine for NanoForge. @@ -20,6 +22,10 @@ This is the full nanoforge engine including all the default libraries. In order to manage this project we use (pnpm)[https://pnpm.io/] +## Documentation + +The full documentation can be found at: [https://nanoforge-dev.github.io/docs/engine](https://nanoforge-dev.github.io/docs/engine) + ## Installing dependencies To install dependencies run: diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..cef6e7b --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +Doxyfile \ No newline at end of file diff --git a/docs/Introduction.md b/docs/Introduction.md deleted file mode 100644 index aa34ac7..0000000 --- a/docs/Introduction.md +++ /dev/null @@ -1,14 +0,0 @@ -# Introduction to using the Engine - -Whether you work on this engine as a devlopper or you wanna use this engine you gonna want to have a test project. -This is a walkthrough on how to setup a basic project - -## As a devlopper on the engine - -As a devlopper you want to be able to use your changes in your project. -Therefore it is recommended to use the provided template in the [example](https://github.com/NanoForge-dev/Engine/tree/main/example/template) - -## As a user - -As a user you can either use the template and change the dependencies location. -Or you can create a project and add the nanoforge dependencies. Note that it is recommended to use bun as a package manager. diff --git a/docs/documentation.rst b/docs/documentation.rst new file mode 100644 index 0000000..db4f625 --- /dev/null +++ b/docs/documentation.rst @@ -0,0 +1,7 @@ +Documentation +============= + +The documentation for the engine is seperated between the different libraries of the engine. +Then everything is packed up and put into this site. The documentation is handled on the engine repository and then automatically pushed. + +This documentation is written in restructured text in order for it to be easier to operate with. We use sphinx to generate the relevant generated documentation. diff --git a/docs/how_to_use.rst b/docs/how_to_use.rst new file mode 100644 index 0000000..68beebb --- /dev/null +++ b/docs/how_to_use.rst @@ -0,0 +1,20 @@ +Introduction to using the Engine +================================ + +Whether you work on this engine as a devlopper or you wanna use this +engine you gonna want to have a test project. This is a walkthrough on +how to setup a basic project + +As a devlopper on the engine +---------------------------- + +As a devlopper you want to be able to use your changes in your project. +Therefore it is recommended to use the provided `cli `__ + +As a user +--------- + +As a user you can either use the template and change the dependencies +location. Or you can create a project and add the nanoforge +dependencies. Note that it is recommended to use bun as a package +manager. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..12e4713 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,12 @@ +Engine +====== + +.. toctree:: + :maxdepth: 2 + + registry + documentation.rst + how_to_use.rst + +In this doc you will find both the how to use and why use this engine as well as its library. +To understand how to use this engine please refer to :doc:`/how_to_use` diff --git a/docs/registry/index.rst b/docs/registry/index.rst new file mode 100644 index 0000000..e642ee1 --- /dev/null +++ b/docs/registry/index.rst @@ -0,0 +1,44 @@ +Registry +======== + +.. toctree:: + :maxdepth: 2 + + writing_web_assembly.rst + +The registry class is written in C++ and exposed to WebAssembly using +Emscripten. This choice was made to leverage the performance benefits of +C++ for managing entities and components in an ECS architecture. +Emscripten allows us to compile C++ code into WebAssembly, which can +then be used in web applications, providing a bridge between +high-performance C++ code and JavaScript. + +Design Choices +-------------- + +This design makes some trade-offs between performance and ease of use. A +pure C++ ECS would have been easier to use but having to bind it as us +make choices that impact usability. + +Const Correctness +~~~~~~~~~~~~~~~~~ + +In a regular C++ ECS, const correctness is a given, but when exposing +C++ to WebAssembly, as in C++ we can force constant of return values. +But in WebAssembly, the concept of const correctness does not directly +translate to JavaScript. Therefore, methods that would typically return +const references in C++ may return non-const references or copies when +exposed to WebAssembly. We deciced to keep the const correctness in the +C++ code to maintain clarity and intent within the C++ domain, even if +it doesn't fully carry over to the WebAssembly interface. + +Error Handling +~~~~~~~~~~~~~~ + +Any thrown exceptions in C++ will result in a runtime error in +JavaScript. The problem with this approach is that the error messages +may not be as descriptive or user-friendly as native JavaScript errors. +As every error thrown in C++ will be caught as a generic runtime error +in JavaScript, it can make debugging more challenging. To mitigate this, +we recommend thorough testing and validation within the C++ code to +catch potential issues before they propagate to the WebAssembly layer. diff --git a/docs/registry/writing_web_assembly.rst b/docs/registry/writing_web_assembly.rst new file mode 100644 index 0000000..7ad7583 --- /dev/null +++ b/docs/registry/writing_web_assembly.rst @@ -0,0 +1,35 @@ +Writing Web Assembly +==================== + +Code Splitting +-------------- + +When writing Web Assembly modules, it's important to consider code +splitting for readability and maintainability. We try to split binding +and code logic into separate files where possible. This helps keep the +codebase organized and makes it easier to navigate. + +For example, in the ECS package, the binding logic is in ``.cpp`` files, +while the core logic resides in ``.hpp`` files. This separation allows +developers to focus on either the binding or the logic without being +overwhelmed by both at the same time. + +Documentation +------------- + +When documenting Web Assembly modules, we follow a similar approach to +C++ documentation. We use Doxygen-style comments to provide clear and +concise explanations of classes, methods, and parameters. This +documentation is crucial for developers who will be using or maintaining +the Web Assembly modules, as it provides necessary context and usage +information. + +Logging +------- + +Logging in Web Assembly is costly memory-wise due to the interaction +between C++ and JavaScript. Therefore, we recommend to log from the +JavaScript side whenever possible. If logging from C++ is necessary, be +aware of the potential performance implications and memory leaks. Web +assembly vm is memory limited, so excessive logging will lead to +out-of-memory errors. diff --git a/example/template/.gitignore b/example/template/.gitignore deleted file mode 100644 index 6162e42..0000000 --- a/example/template/.gitignore +++ /dev/null @@ -1,225 +0,0 @@ -### 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.* - diff --git a/example/template/.nvmrc b/example/template/.nvmrc deleted file mode 100644 index c519bf5..0000000 --- a/example/template/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v24.11.0 diff --git a/example/template/.prettierignore b/example/template/.prettierignore deleted file mode 100644 index e77da01..0000000 --- a/example/template/.prettierignore +++ /dev/null @@ -1,5 +0,0 @@ -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock -bun.lock diff --git a/example/template/.prettierrc b/example/template/.prettierrc deleted file mode 100644 index d5f635c..0000000 --- a/example/template/.prettierrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "plugins": ["@trivago/prettier-plugin-sort-imports"], - "importOrderSeparation": true, - "importOrderSortSpecifiers": true, - "importOrderParserPlugins": ["typescript", "decorators-legacy"], - "importOrder": ["^~/(.*)$", "^[./]"], - "useTabs": false, - "singleQuote": false, - "trailingComma": "all", - "printWidth": 100 -} diff --git a/example/template/README.md b/example/template/README.md deleted file mode 100644 index abd9ad3..0000000 --- a/example/template/README.md +++ /dev/null @@ -1,31 +0,0 @@ -## setup.sh - -Allow you init your game - -```shell -./setup.sh -``` - -## update.sh - -Allow you to update your lib - -```shell -./update.sh -``` - -## build.sh - -Allow you to build project and copy it to the loader - -```shell -./build.sh Loader -``` - -## run.sh - -Allow you to build project and copy it to the loader, and then run the loader (Ctrl + C to stop it) - -```shell -./run.sh Loader -``` \ No newline at end of file diff --git a/example/template/build.sh b/example/template/build.sh deleted file mode 100755 index b886e83..0000000 --- a/example/template/build.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -bun run build -mkdir -p ../../../${1}/apps/server/public/game -cp dist/* ../../../${1}/apps/server/public/game diff --git a/example/template/eslint.config.js b/example/template/eslint.config.js deleted file mode 100644 index efc80f1..0000000 --- a/example/template/eslint.config.js +++ /dev/null @@ -1,44 +0,0 @@ -import pluginJs from "@eslint/js"; -import eslintConfigPrettier from "eslint-config-prettier"; -import globals from "globals"; -import tseslint from "typescript-eslint"; - -export default [ - { files: ["src/**/*.{js,mjs,cjs,ts}"] }, - { languageOptions: { globals: globals.node } }, - - pluginJs.configs.recommended, - ...tseslint.configs.recommended, - ...tseslint.configs.strict, - eslintConfigPrettier, - { - rules: { - "@typescript-eslint/consistent-type-imports": [ - "error", - { - disallowTypeAnnotations: true, - fixStyle: "inline-type-imports", - prefer: "type-imports", - }, - ], - "@typescript-eslint/no-extraneous-class": "off", - "@typescript-eslint/no-empty-object-type": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/member-ordering": [ - "error", - { - default: [ - "static-field", - "field", - "public-static-method", - "constructor", - "method", - "protected-method", - "private-method", - ], - }, - ], - }, - }, -]; diff --git a/example/template/package.json b/example/template/package.json deleted file mode 100644 index 927cde9..0000000 --- a/example/template/package.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "name": "nanoforge-example-template", - "version": "0.0.0", - "description": "NanoForge Loader - Web", - "homepage": "https://github.com/NanoForge-dev/Loader#readme", - "license": "MIT", - "contributors": [ - "Bill", - "Exelo", - "Fexkoser", - "Tchips" - ], - "funding": { - "type": "individual", - "url": "" - }, - "type": "module", - "module": "src/index.ts", - "browser": true, - "repository": { - "type": "git", - "url": "git+https://github.com/NanoForge-dev/Loader.git", - "directory": "apps/web" - }, - "private": true, - "scripts": { - "setup": "bun i", - "build": "bun run clean && bun run build:raw", - "build:raw": "bun build src/index.ts --outdir dist --asset-naming \"[name].[ext]\"", - "clean": "rm -rf dist", - "lint": "eslint . && prettier --check . '!.cloud/**'", - "format": "eslint . --fix && prettier --write . '!.cloud/**'", - "lint-staged": "lint-staged" - }, - "dependencies": { - "@nanoforge-dev/asset-manager": "workspace:^", - "@nanoforge-dev/common": "workspace:^", - "@nanoforge-dev/config": "workspace:^", - "@nanoforge-dev/core": "workspace:^", - "@nanoforge-dev/ecs-client": "workspace:^", - "@nanoforge-dev/graphics-2d": "workspace:^", - "@nanoforge-dev/input": "workspace:^", - "@nanoforge-dev/sound": "workspace:^" - }, - "devDependencies": { - "@eslint/js": "^9.39.1", - "@trivago/prettier-plugin-sort-imports": "^6.0.0", - "@types/bun": "latest", - "eslint": "^9.39.1", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-format": "^1.0.2", - "eslint-plugin-prettier": "^5.5.4", - "globals": "^16.5.0", - "prettier": "^3.6.2", - "typescript-eslint": "^8.47.0" - }, - "peerDependencies": { - "typescript": "^5" - }, - "engines": { - "node": "24.11.0", - "bun": "1.3.2" - }, - "packageManager": "bun@1.3.2", - "lint-staged": { - "**/*.{js,ts,html,css}": [ - "eslint --fix", - "prettier --write" - ] - }, - "workspaces": [ - ".", - "../../packages/*", - "../../libs/*", - "../../utils/*" - ] -} diff --git a/example/template/run.sh b/example/template/run.sh deleted file mode 100755 index 73745c1..0000000 --- a/example/template/run.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -./build.sh ${1} -cd ../../../${1}/apps - -bun run dev diff --git a/example/template/src/index.ts b/example/template/src/index.ts deleted file mode 100644 index 39433bc..0000000 --- a/example/template/src/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AssetManagerLibrary } from "@nanoforge-dev/asset-manager"; -import { type IRunOptions } from "@nanoforge-dev/common"; -import { NanoforgeFactory } from "@nanoforge-dev/core"; -import { ECSClientLibrary } from "@nanoforge-dev/ecs-client"; -import { Graphics2DLibrary } from "@nanoforge-dev/graphics-2d"; - -export const app = NanoforgeFactory.createClient(); - -export const main = async (options: IRunOptions) => { - app.useGraphics(new Graphics2DLibrary()); - app.useAssetManager(new AssetManagerLibrary()); - app.useComponentSystem(new ECSClientLibrary()); - - await app.init(options); - app.run(); -}; diff --git a/example/template/tsconfig.json b/example/template/tsconfig.json deleted file mode 100644 index d84e1b8..0000000 --- a/example/template/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "es2023", - "module": "commonjs", - "rootDir": "./src", - "outDir": "./dist", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true, - "paths": { - } - } -} diff --git a/packages/ecs-lib/.gitignore b/packages/ecs-lib/.gitignore index 6542710..82ecd49 100644 --- a/packages/ecs-lib/.gitignore +++ b/packages/ecs-lib/.gitignore @@ -274,3 +274,4 @@ lib/index.js lib/libecs.js compile_commands.json emsdk/ +docs/xml diff --git a/packages/ecs-lib/docs/Doxyfile b/packages/ecs-lib/docs/Doxyfile new file mode 100644 index 0000000..ac94351 --- /dev/null +++ b/packages/ecs-lib/docs/Doxyfile @@ -0,0 +1,7 @@ +GENERATE_XML = YES +GENERATE_HTML = NO +GENERATE_LATEX = NO + +INPUT = ../wasm + +OUTPUT_DIRECTORY = . diff --git a/packages/ecs-lib/docs/index.rst b/packages/ecs-lib/docs/index.rst new file mode 100644 index 0000000..264e899 --- /dev/null +++ b/packages/ecs-lib/docs/index.rst @@ -0,0 +1,8 @@ +ECS Documentation +================= + +.. toctree:: + :maxdepth: 2 + + wasm + typescript diff --git a/packages/ecs-lib/docs/typescript.rst b/packages/ecs-lib/docs/typescript.rst new file mode 100644 index 0000000..0405804 --- /dev/null +++ b/packages/ecs-lib/docs/typescript.rst @@ -0,0 +1,8 @@ +Typescript +========== + +.. .. js:autoclass:: AbstractECSLibrary +.. :members: + +.. .. js::typealias:: ECSElementDefaults +.. :members: diff --git a/packages/ecs-lib/docs/wasm.rst b/packages/ecs-lib/docs/wasm.rst new file mode 100644 index 0000000..bd9ea6b --- /dev/null +++ b/packages/ecs-lib/docs/wasm.rst @@ -0,0 +1,11 @@ +C++ classes +----------- + +.. doxygenclass:: nfo::Registry + :members: + +.. doxygenclass:: nfo::Entity + :members: + +.. doxygenclass:: nfo::SparseArray + :members: diff --git a/packages/ecs-lib/src/ecs-library.abstract.ts b/packages/ecs-lib/src/ecs-library.abstract.ts index eed8e97..fa1ebec 100644 --- a/packages/ecs-lib/src/ecs-library.abstract.ts +++ b/packages/ecs-lib/src/ecs-library.abstract.ts @@ -7,6 +7,14 @@ import { import { type MainModule, type Registry } from "../lib/libecs"; +/** + * Abstract class representing an ECS (Entity Component System) library. + * Extends the BaseComponentSystemLibrary to provide ECS-specific functionality. + * Manages a registry of systems and ensures proper initialization before use. + * @abstract + * @class AbstractECSLibrary + * @extends {BaseComponentSystemLibrary} + */ export abstract class AbstractECSLibrary extends BaseComponentSystemLibrary { protected module?: MainModule; protected _registry?: Registry; @@ -22,11 +30,21 @@ export abstract class AbstractECSLibrary extends BaseComponentSystemLibrary { abstract override get __name(): string; + /** + * Runs the ECS systems using the provided context. + * @param ctx - The context to be used for running the systems. + * @returns A promise that resolves when the systems have been run. + */ async __run(ctx: Context): Promise { if (!this._registry) this.throwNotInitializedError(); this._registry.runSystems(ctx); } + /** + * Gets the registry. + * @throws Will throw an error if the library is not initialized. + * @returns The registry. + */ get registry(): Registry { if (!this._registry) this.throwNotInitializedError(); return this._registry; diff --git a/packages/ecs-lib/wasm/Entity.hpp b/packages/ecs-lib/wasm/Entity.hpp index ddc0381..27e2f46 100644 --- a/packages/ecs-lib/wasm/Entity.hpp +++ b/packages/ecs-lib/wasm/Entity.hpp @@ -16,15 +16,37 @@ #include namespace nfo { + /** + ** @brief Represents an entity in the ECS (Entity-Component-System) architecture. + ** + ** This class encapsulates the concept of an entity, which is identified by a unique ID. + ** Entities are used to group components together in the ECS paradigm. + */ class Entity { public: + /** + * Create an entity from an ID. + * + * @param id The ID of the entity. + * @return An Entity instance. + */ explicit Entity(const std::size_t id) : _id(id) {} + /** + * Convert the entity to its ID. + * + * @return The ID of the entity. + */ operator std::size_t() const { return _id; } + /** + * Get the ID of the entity. + * + * @return The ID of the entity. + */ [[nodiscard]] std::size_t get_id() const { return _id; diff --git a/packages/ecs-lib/wasm/Registry.hpp b/packages/ecs-lib/wasm/Registry.hpp index 566df82..c118465 100644 --- a/packages/ecs-lib/wasm/Registry.hpp +++ b/packages/ecs-lib/wasm/Registry.hpp @@ -26,22 +26,36 @@ #include "Utils.hpp" namespace nfo { + /** + ** @brief The main registry class for managing entities and components. + ** + ** This class provides methods to register components, manage entities, + ** add and remove components from entities, and run systems. + */ class Registry { public: - SparseArray ®ister_component(const Component &c) + /** + * Register a component type in the registry. + * + * @param conponent An instance of the component to register. + * @return A reference to the SparseArray that will hold all components of this type. + * @throws std::runtime_error if the component type is "entity", "id", + * or UNKNOWN_COMPONENT_TYPE. + */ + SparseArray ®ister_component(const Component &component) { - std::string component_type(get_js_class_name(c)); + std::string component_type(get_js_class_name(component)); if (component_type == "entity" || component_type == "id") throw std::runtime_error("Component type '" + component_type + "' not supported : you can't use : id, entity, " + UNKNOWN_COMPONENT_TYPE); if (!_components_arrays.contains(component_type)) _components_arrays.emplace(component_type, SparseArray()); if (!_remove_functions.contains(component_type)) { - _remove_functions.emplace(component_type, [c](Registry ®, Entity const &ent) { - SparseArray &array = reg.get_components(c); + _remove_functions.emplace(component_type, [component](Registry ®, Entity const &ent) { + SparseArray &array = reg.get_components(component); array.erase(ent); }); } - // TODO: rework logger + // TODO: rework logger https://github.com/NanoForge-dev/Engine/issues/104 // if (!_loggers.contains(component_type)) { // _loggers.emplace(component_type, [](Registry const ®, Entity const &ent) { // const auto &array = reg.get_components(); @@ -54,42 +68,77 @@ namespace nfo { return std::any_cast &>(_components_arrays[component_type]); } - SparseArray &get_components(const Component &c) + /** + * Get the SparseArray of a given component type. + * + * @param component An instance of the component type to get. + * @return A reference to the SparseArray holding all components of this type. + * @throws std::runtime_error if the component type is not registered. + */ + SparseArray &get_components(const Component &component) { - const std::string component_type(get_js_class_name(c)); + const std::string component_type(get_js_class_name(component)); if (!_components_arrays.contains(component_type)) - register_component(c); + register_component(component); std::any &components = _components_arrays[component_type]; return std::any_cast &>(components); } - [[nodiscard]] SparseArray const &get_components(const Component &c) const + /** + * Get the SparseArray of a given component type (const version). + * + * @param component An instance of the component type to get. + * @return A const reference to the SparseArray holding all components of this type. + * @throws std::runtime_error if the component type is not registered. + */ + [[nodiscard]] SparseArray const &get_components(const Component &component) const { - const std::string component_type(get_js_class_name(c)); + const std::string component_type(get_js_class_name(component)); if (!_components_arrays.contains(component_type)) throw std::runtime_error(component_type + " array not registered"); const std::any &components = _components_arrays.find(component_type)->second; return std::any_cast &>(components); } - std::optional &get_entity_component(const Entity e, const Component &c) + /** + * Get the component of a given entity. + * + * @param entity The entity to get the component from. + * @param component An instance of the component type to get. + * @return A reference to an optional containing the component if it exists, or std::nullopt otherwise. + * @throws std::runtime_error if the component type is not registered. + */ + std::optional &get_entity_component(const Entity entity, const Component &component) { - const std::string component_type(get_js_class_name(c)); + const std::string component_type(get_js_class_name(component)); if (!_components_arrays.contains(component_type)) - register_component(c); + register_component(component); std::any &components = _components_arrays[component_type]; - return std::any_cast &>(components)[e]; + return std::any_cast &>(components)[entity]; } - [[nodiscard]] std::optional const &get_entity_component(const Entity e, const Component &c) const + /** + * Get the component of a given entity (const version). + * + * @param entity The entity to get the component from. + * @param component An instance of the component type to get. + * @return A const reference to an optional containing the component if it exists, or std::nullopt otherwise. + * @throws std::runtime_error if the component type is not registered. + */ + [[nodiscard]] std::optional const &get_entity_component(const Entity entity, const Component &component) const { - const std::string component_type(get_js_class_name(c)); + const std::string component_type(get_js_class_name(component)); if (!_components_arrays.contains(component_type)) throw std::runtime_error(component_type + " array not registered"); const std::any &components = _components_arrays.find(component_type)->second; - return std::any_cast &>(components)[e]; + return std::any_cast &>(components)[entity]; } + /** + * Spawn a new entity. + * + * @return The newly spawned entity. + */ [[nodiscard]] Entity spawn_entity() { if (!_dead_entities.empty()) { @@ -101,6 +150,13 @@ namespace nfo { return Entity(_next_entity - 1); } + /** + * Get an entity from its index. + * + * @param component_type The index of the entity. + * @return The entity corresponding to the given index. + * @throws std::runtime_error if the index is out of range. + */ Entity entity_from_index(const std::size_t component_type) { if (std::ranges::find(_dead_entities, component_type) != _dead_entities.end() || component_type >= _next_entity) @@ -108,13 +164,21 @@ namespace nfo { return Entity(component_type); } - void kill_entity(Entity const &e) + /** + * Kill an entity, marking it for removal. + * + * @param entity The entity to kill. + */ + void kill_entity(Entity const &entity) { - _dead_entities.push_back(e); + _dead_entities.push_back(entity); for (const std::function &remove_function : std::views::values(_remove_functions)) - remove_function(*this, e); + remove_function(*this, entity); } + /** + * Clear all entities and reset the registry. + */ void clear_entities() { _next_entity = 0; @@ -124,30 +188,53 @@ namespace nfo { _components_arrays.clear(); } - SparseArray::reference_type add_component(Entity const &to, Component &&c) + /** + * Add a component to an entity. + * + * @param entity The entity to add the component to. + * @param component The component to add. + * @return A reference to the added component. + */ + SparseArray::reference_type add_component(Entity const &entity, Component &&component) { - const std::string component_type(get_js_class_name(c)); + const std::string component_type(get_js_class_name(component)); if (!_components_arrays.contains(component_type)) { - register_component(c); + register_component(component); } - return get_components(c).insert_at(to, c); + return get_components(component).insert_at(entity, component); } - void remove_component(Entity const &from, Component &&c) + /** + * Remove a component from an entity. + * + * @param entity The entity to remove the component from. + * @param component The component to remove. + */ + void remove_component(Entity const &entity, Component &&component) { - const std::string component_type(get_js_class_name(c)); + const std::string component_type(get_js_class_name(component)); if (!_components_arrays.contains(component_type)) - register_component(c); + register_component(component); if (_remove_functions.contains(component_type)) - _remove_functions[component_type](*this, from); + _remove_functions[component_type](*this, entity); } + /** + * Add a system to the registry. + * + * @param f The system function to add. + */ template void add_system(Function &&f) { _systems.emplace_back(std::forward(f)); } + /** + * Remove a system from the registry by its index. + * + * @param system_idx The index of the system to remove. + */ void remove_system(const std::size_t system_idx) { if (system_idx >= _systems.size()) @@ -155,11 +242,19 @@ namespace nfo { _systems.erase(_systems.begin() + static_cast(system_idx)); } + /** + * Clear all systems from the registry. + */ void clear_systems() { _systems.clear(); } + /** + * Run all systems with the given context. + * + * @param ctx The context to pass to each system. + */ void run_systems(const emscripten::val &ctx) { std::vector> systems_copy = _systems; @@ -167,6 +262,11 @@ namespace nfo { system(*this, ctx); } + /** + * Log information about an entity. (Costly operation as logging in JS is not memory safe) + * + * @param entity The entity to log information about. + */ void log(const Entity &entity) const { for (const auto &logger : std::views::values(_loggers)) { @@ -175,11 +275,23 @@ namespace nfo { } } + /** + * Get the maximum number of entities that have been spawned. + * + * @return The maximum number of entities. + */ [[nodiscard]] std::size_t max_entities() const { return _next_entity; } + /** + * Get the zipper output for the given components. + * + * @param comps An array of component types to zip. + * @return A ZipperOutput containing the zipped components. + * @throws std::runtime_error if the input is not an array. + */ ZipperOutput get_zipper(const ZipperInput &comps) { if (!comps.isArray()) diff --git a/packages/ecs-lib/wasm/SparseArray.hpp b/packages/ecs-lib/wasm/SparseArray.hpp index 95c57ce..d6d87cc 100644 --- a/packages/ecs-lib/wasm/SparseArray.hpp +++ b/packages/ecs-lib/wasm/SparseArray.hpp @@ -19,6 +19,14 @@ #include namespace nfo { + /** + ** @brief A sparse array implementation that allows for optional components. + ** @tparam Component The type of component to be stored in the sparse array. + ** + ** This class provides a way to store components in a sparse manner, allowing for efficient + ** storage and retrieval of components by their indices. It uses std::optional to represent + ** the presence or absence of a component at a given index. + */ template class SparseArray { public: @@ -31,12 +39,28 @@ namespace nfo { using iterator = typename container::iterator; using const_iterator = typename container::const_iterator; + /** + * @brief Default constructor for SparseArray. + */ SparseArray() = default; + /** + * @brief Copy constructor for SparseArray. + * @param other The SparseArray to copy from. + */ SparseArray(SparseArray const &other) : _data(other._data) {} + /** + * @brief Move constructor for SparseArray. + * @param other The SparseArray to move from. + */ SparseArray(SparseArray &&other) noexcept : _data(std::move(other._data)) {} + /** + * @brief Copy assignment operator for SparseArray. + * @param other The SparseArray to copy from. + * @return Reference to this SparseArray. + */ SparseArray &operator=(SparseArray const &other) { if (this != &other) { @@ -45,6 +69,11 @@ namespace nfo { return *this; } + /** + * @brief Move assignment operator for SparseArray. + * @param other The SparseArray to move from. + * @return Reference to this SparseArray. + */ SparseArray &operator=(SparseArray &&other) noexcept { if (this != &other) { @@ -53,6 +82,11 @@ namespace nfo { return *this; } + /** + * @brief Erase the component at the given index. + * + * @param idx The index of the component to erase. + */ void erase(const size_type &idx) { if (idx < _data.size()) { @@ -60,36 +94,68 @@ namespace nfo { } } + /** + * @brief Get an iterator to the beginning of the sparse array. + * @return An iterator to the beginning of the sparse array. + */ iterator begin() { return _data.begin(); } + /** + * @brief Get a const iterator to the beginning of the sparse array. + * @return A const iterator to the beginning of the sparse array. + */ [[nodiscard]] const_iterator begin() const { return _data.begin(); } + /** + * @brief Get a const iterator to the beginning of the sparse array. + * @return A const iterator to the beginning of the sparse array. + */ [[nodiscard]] const_iterator cbegin() const { return _data.cbegin(); } + /** + * @brief Get an iterator to the end of the sparse array. + * @return An iterator to the end of the sparse array. + */ iterator end() { return _data.end(); } + /** + * @brief Get a const iterator to the end of the sparse array. + * @return A const iterator to the end of the sparse array. + */ [[nodiscard]] const_iterator end() const { return _data.end(); } + /** + * @brief Get a const iterator to the end of the sparse array. + * @return A const iterator to the end of the sparse array. + */ [[nodiscard]] const_iterator cend() const { return _data.cend(); } + /** + * @brief Access the component at the given index. + * + * If the index is out of bounds, the sparse array is resized to accommodate the index. + * + * @param idx The index of the component to access. + * @return A reference to the optional component at the given index. + */ reference_type operator[](size_type idx) { if (idx >= _data.size()) { @@ -98,6 +164,14 @@ namespace nfo { return _data[idx]; } + /** + * @brief Access the component at the given index (const version). + * + * If the index is out of bounds, returns a reference to a null optional. + * + * @param idx The index of the component to access. + * @return A const reference to the optional component at the given index. + */ const_reference_type operator[](size_type idx) const { if (idx >= _data.size()) { @@ -106,31 +180,64 @@ namespace nfo { return _data[idx]; } + /** + * @brief Set the component at the given index. + * + * @param idx The index of the component to set. + * @param value The component value to set. + */ void set(size_type idx, value_type value) { (*this)[idx] = std::move(value); } + /** + * @brief Get the size of the sparse array. + * + * @return The size of the sparse array. + */ [[nodiscard]] size_type size() const { return _data.size(); } + /** + * @brief Resize the sparse array to the given size. + * + * @param size The new size of the sparse array. + */ void resize(size_type size) { _data.resize(size); } + /** + * @brief Clear the sparse array. + */ void clear() { _data.clear(); } + /** + * @brief Check if the sparse array is empty. + * + * @return True if the sparse array is empty, false otherwise. + */ [[nodiscard]] bool empty() const { return _data.empty() || std::ranges::all_of(_data, [](const auto &v) { return !v.has_value(); }); } + /** + * @brief Insert a component at the given index. + * + * If the index is out of bounds, the sparse array is resized to accommodate the index. + * + * @param idx The index at which to insert the component. + * @param value The component value to insert. + * @return A reference to the inserted optional component. + */ reference_type insert_at(size_type idx, const_reference_type value) { if (idx >= _data.size()) { @@ -140,6 +247,15 @@ namespace nfo { return _data[idx]; } + /** + * @brief Insert a component at the given index (move version). + * + * If the index is out of bounds, the sparse array is resized to accommodate the index. + * + * @param idx The index at which to insert the component. + * @param value The component value to insert. + * @return A reference to the inserted optional component. + */ reference_type insert_at(size_type idx, move_reference_type value) { if (idx >= _data.size()) { @@ -149,6 +265,16 @@ namespace nfo { return _data[idx]; } + /** + * @brief Emplace a component at the given index. + * + * If the index is out of bounds, the sparse array is resized to accommodate the index. + * + * @tparam Params The types of the parameters to construct the component. + * @param idx The index at which to emplace the component. + * @param params The parameters to construct the component. + * @return A reference to the emplaced optional component. + */ template reference_type emplace_at(size_type idx, Params &&...params) { @@ -159,6 +285,12 @@ namespace nfo { return _data[idx]; } + /** + * @brief Get the index of the given component value. + * + * @param value The component value to find. + * @return The index of the component value, or size() if not found. + */ [[nodiscard]] size_type get_index(const_reference_type value) const { auto it = std::find(_data.begin(), _data.end(), value);