diff --git a/example/pong/src/collisions.ts b/example/pong/src/collisions.ts index 224b59a..3ba113c 100644 --- a/example/pong/src/collisions.ts +++ b/example/pong/src/collisions.ts @@ -1,8 +1,9 @@ +import { type ECSRegistry } from "@nanoforge/ecs"; + import { Hitbox, Position } from "./components"; -import { ecsLibrary } from "./index"; -export function checkCollisions(entity: any) { - const entities = ecsLibrary.getZipper([Hitbox, Position]); +export function checkCollisions(registry: ECSRegistry, entity: any) { + const entities = registry.getZipper([Hitbox, Position]); const { x, y } = entity.Position; const { width, height } = entity.Hitbox; diff --git a/example/pong/src/index.ts b/example/pong/src/index.ts index 19d315d..c07de0b 100644 --- a/example/pong/src/index.ts +++ b/example/pong/src/index.ts @@ -1,9 +1,8 @@ -import { AssetManagerLibrary } from "@nanoforge/asset-manager"; import { type IRunOptions } from "@nanoforge/common"; import { NanoforgeFactory } from "@nanoforge/core"; import { ECSLibrary } from "@nanoforge/ecs"; import { Graphics, Graphics2DLibrary } from "@nanoforge/graphics-2d"; -import { InputEnum, InputLibrary } from "@nanoforge/input"; +import { InputEnum } from "@nanoforge/input"; import { SoundLibrary } from "@nanoforge/sound"; import { @@ -17,38 +16,36 @@ import { } from "./components"; import { bounce, controlPlayer, drawCircle, move, moveRectangle } from "./systems"; -export const ecsLibrary = new ECSLibrary(); - export const app = NanoforgeFactory.createClient(); -export const graphics = new Graphics2DLibrary(); -export const inputs = new InputLibrary(); -export const sounds = new SoundLibrary(); -export const assetManager = new AssetManagerLibrary(); export const layer = new Graphics.Layer(); export const main = async (options: IRunOptions) => { + const graphics = new Graphics2DLibrary(); + const ecsLibrary = new ECSLibrary(); + const sounds = new SoundLibrary(); + app.useGraphics(graphics); app.useComponentSystem(ecsLibrary); - app.useAssetManager(assetManager); - app.useInput(inputs); app.useSound(sounds); await app.init(options); + const registry = ecsLibrary.registry; + graphics.stage.add(layer); console.log(graphics.stage.width()); sounds.load("test", "https://universal-soundbank.com/sounds/18782.mp3"); - const ball = ecsLibrary.spawnEntity(); - ecsLibrary.addComponent(ball, new Velocity(10, 0)); - ecsLibrary.addComponent( + const ball = registry.spawnEntity(); + registry.addComponent(ball, new Velocity(10, 0)); + registry.addComponent( ball, new Position(graphics.stage.width() / 2, graphics.stage.height() / 2), ); - ecsLibrary.addComponent(ball, new Bounce()); - ecsLibrary.addComponent( + registry.addComponent(ball, new Bounce()); + registry.addComponent( ball, new CircleComponent( new Graphics.Circle({ @@ -58,31 +55,31 @@ export const main = async (options: IRunOptions) => { ), ); - const player1 = ecsLibrary.spawnEntity(); - ecsLibrary.addComponent(player1, new Position(20, 100)); - ecsLibrary.addComponent(player1, new Velocity(0, 5)); - ecsLibrary.addComponent(player1, new Hitbox(50, 500)); - ecsLibrary.addComponent(player1, new Controller(InputEnum.KeyW, InputEnum.KeyS)); - ecsLibrary.addComponent( + const player1 = registry.spawnEntity(); + registry.addComponent(player1, new Position(20, 100)); + registry.addComponent(player1, new Velocity(0, 5)); + registry.addComponent(player1, new Hitbox(50, 500)); + registry.addComponent(player1, new Controller(InputEnum.KeyW, InputEnum.KeyS)); + registry.addComponent( player1, new RectangleComponent(new Graphics.Rect({ fill: "blue", width: 50, height: 500 })), ); - const player2 = ecsLibrary.spawnEntity(); - ecsLibrary.addComponent(player2, new Position(1850, 100)); - ecsLibrary.addComponent(player2, new Velocity(0, 5)); - ecsLibrary.addComponent(player2, new Hitbox(50, 500)); - ecsLibrary.addComponent(player2, new Controller(InputEnum.ArrowUp, InputEnum.ArrowDown)); - ecsLibrary.addComponent( + const player2 = registry.spawnEntity(); + registry.addComponent(player2, new Position(1850, 100)); + registry.addComponent(player2, new Velocity(0, 5)); + registry.addComponent(player2, new Hitbox(50, 500)); + registry.addComponent(player2, new Controller(InputEnum.ArrowUp, InputEnum.ArrowDown)); + registry.addComponent( player2, new RectangleComponent(new Graphics.Rect({ fill: "blue", width: 50, height: 500 })), ); - ecsLibrary.addSystem(move); - ecsLibrary.addSystem(controlPlayer); - ecsLibrary.addSystem(moveRectangle); - ecsLibrary.addSystem(drawCircle); - ecsLibrary.addSystem(bounce); + registry.addSystem(move); + registry.addSystem(controlPlayer); + registry.addSystem(moveRectangle); + registry.addSystem(drawCircle); + registry.addSystem(bounce); app.run(); }; diff --git a/example/pong/src/systems.ts b/example/pong/src/systems.ts index 657c39d..c90faff 100644 --- a/example/pong/src/systems.ts +++ b/example/pong/src/systems.ts @@ -1,3 +1,7 @@ +import { type ECSContext, type ECSRegistry } from "@nanoforge/ecs"; +import { type InputLibrary } from "@nanoforge/input"; +import { type SoundLibrary } from "@nanoforge/sound"; + import { checkCollisions } from "./collisions"; import { Bounce, @@ -8,10 +12,9 @@ import { RectangleComponent, Velocity, } from "./components"; -import { ecsLibrary, inputs, sounds } from "./index"; -export function move() { - const entities = ecsLibrary.getZipper([Bounce, Position, Velocity]); +export function move(registry: ECSRegistry) { + const entities = registry.getZipper([Bounce, Position, Velocity]); entities.forEach((entity) => { entity.Position.x += entity.Velocity.x; @@ -19,33 +22,39 @@ export function move() { }); } -export function bounce() { - const entities = ecsLibrary.getZipper([Bounce, Position, Velocity]); +export function bounce(registry: ECSRegistry, ctx: ECSContext) { + const entities = registry.getZipper([Bounce, Position, Velocity]); entities.forEach((entity) => { if (entity.Position.x >= 1800 || entity.Position.x <= 100) { entity.Velocity.x = -entity.Velocity.x; - sounds.play("test"); + ctx.libraries.getSound().library.play("test"); } if (entity.Position.y >= 1000 || entity.Position.y <= 100) { entity.Velocity.y = -entity.Velocity.y; - sounds.play("test"); + ctx.libraries.getSound().library.play("test"); } }); } -export function controlPlayer() { - const entities = ecsLibrary.getZipper([Controller, Position, Hitbox, Velocity]); +export function controlPlayer(registry: ECSRegistry, ctx: ECSContext) { + const entities = registry.getZipper([Controller, Position, Hitbox, Velocity]); entities.forEach((entity) => { - if (inputs.isKeyPressed(entity.Controller.up) && !checkCollisions(entity)) { + if ( + ctx.libraries.getInput().library.isKeyPressed(entity.Controller.up) && + !checkCollisions(registry, entity) + ) { entity.Position.y -= entity.Velocity.y; } else { entity.Position.y += entity.Velocity.y; } - if (inputs.isKeyPressed(entity.Controller.down) && !checkCollisions(entity)) { + if ( + ctx.libraries.getInput().library.isKeyPressed(entity.Controller.down) && + !checkCollisions(registry, entity) + ) { entity.Position.y += entity.Velocity.y; } else { entity.Position.y -= entity.Velocity.y; @@ -53,8 +62,8 @@ export function controlPlayer() { }); } -export function drawCircle() { - const entities = ecsLibrary.getZipper([CircleComponent, Position]); +export function drawCircle(registry: ECSRegistry) { + const entities = registry.getZipper([CircleComponent, Position]); entities.forEach((entity) => { const pos = entity.Position; @@ -62,10 +71,9 @@ export function drawCircle() { }); } -export function moveRectangle() { - const entities = ecsLibrary.getZipper([RectangleComponent, Position, Hitbox]); +export function moveRectangle(registry: ECSRegistry) { + const entities = registry.getZipper([RectangleComponent, Position, Hitbox]); - console.log(entities); entities.forEach((entity) => { const pos = entity.Position; (entity.RectangleComponent as RectangleComponent).component.setPosition(pos); diff --git a/packages/ecs/src/ecs-library.ts b/packages/ecs/src/ecs-library.ts index db735ee..4d81634 100644 --- a/packages/ecs/src/ecs-library.ts +++ b/packages/ecs/src/ecs-library.ts @@ -6,16 +6,15 @@ import { type InitContext, } from "@nanoforge/common"; -import type { Entity, MainModule, Registry, SparseArray } from "../lib"; +import type { MainModule } from "../lib"; import { Module } from "../lib"; import { type ECSContext } from "./ecs-context.type"; - -export type Component = { name: string; [key: string]: any }; -export type System = (registry: Registry) => void; +import { ECSRegistry } from "./ecs-registry"; export class ECSLibrary extends BaseComponentSystemLibrary { private module: MainModule; - private registry: Registry; + private _registry: ECSRegistry; + private readonly path: string = "libecs.wasm"; constructor() { @@ -34,74 +33,14 @@ export class ECSLibrary extends BaseComponentSystemLibrary { .getAssetManager() .library.getWasm(this.path); this.module = await Module({ locateFile: () => wasmFile.path }); - this.registry = new this.module.Registry(); + this._registry = new ECSRegistry(new this.module.Registry()); } async run(ctx: ECSContext): Promise { - this.runSystems(ctx); - } - - addComponent(entity: Entity, component: Component): void { - this.registry.addComponent(entity, component); - } - - spawnEntity(): Entity { - return this.registry.spawnEntity(); - } - - getComponents(component: Component): SparseArray { - return this.registry.getComponents(component); - } - - removeComponent(entity: Entity, component: Component): void { - this.registry.removeComponent(entity, component); - } - - getEntityComponent(entity: Entity, component: Component): Component | undefined { - return this.registry.getEntityComponent(entity, component); - } - - getEntityComponentConst(entity: Entity, component: Component): Component | undefined { - return this.registry.getEntityComponentConst(entity, component); - } - - clearEntities(): void { - this.registry.clearEntities(); - } - - runSystems(ctx: ECSContext): void { - this.registry.runSystems(ctx); - } - - clearSystems(): void { - this.registry.clearSystems(); - } - - removeSystem(system: any): void { - this.registry.removeSystem(system); - } - - registerComponent(component: any): SparseArray { - return this.registry.registerComponent(component); - } - - entityFromIndex(index: number): Entity { - return this.registry.entityFromIndex(index); - } - - killEntity(entity: Entity): void { - this.registry.killEntity(entity); - } - - maxEntities(): number { - return this.registry.maxEntities(); - } - - addSystem(system: System): void { - this.registry.addSystem(system); + this._registry.runSystems(ctx); } - getZipper(types: [Component, ...Component[]]): [any, ...any[]] { - return this.registry.getZipper(types); + get registry(): ECSRegistry { + return this._registry; } } diff --git a/packages/ecs/src/ecs-registry.ts b/packages/ecs/src/ecs-registry.ts new file mode 100644 index 0000000..6141b53 --- /dev/null +++ b/packages/ecs/src/ecs-registry.ts @@ -0,0 +1,77 @@ +import type { Entity, Registry, SparseArray } from "../lib"; +import { type ECSContext } from "./ecs-context.type"; + +export type Component = { name: string; [key: string]: any }; +export type System = (registry: ECSRegistry, ctx: ECSContext) => void; + +export class ECSRegistry { + private _registry: Registry; + + constructor(registry: Registry) { + this._registry = registry; + } + + addComponent(entity: Entity, component: Component): void { + return this._registry.addComponent(entity, component); + } + + spawnEntity(): Entity { + return this._registry.spawnEntity(); + } + + getComponents(component: Component): SparseArray { + return this._registry.getComponents(component); + } + + removeComponent(entity: Entity, component: Component): void { + return this._registry.removeComponent(entity, component); + } + + getEntityComponent(entity: Entity, component: Component): Component | undefined { + return this._registry.getEntityComponent(entity, component); + } + + getEntityComponentConst(entity: Entity, component: Component): Component | undefined { + return this._registry.getEntityComponentConst(entity, component); + } + + clearEntities(): void { + return this._registry.clearEntities(); + } + + runSystems(ctx: ECSContext): void { + return this._registry.runSystems(ctx); + } + + clearSystems(): void { + return this._registry.clearSystems(); + } + + removeSystem(system: any): void { + return this._registry.removeSystem(system); + } + + registerComponent(component: any): SparseArray { + return this._registry.registerComponent(component); + } + + entityFromIndex(index: number): Entity { + return this._registry.entityFromIndex(index); + } + + killEntity(entity: Entity): void { + return this._registry.killEntity(entity); + } + + maxEntities(): number { + return this._registry.maxEntities(); + } + + addSystem(system: System): void { + return this._registry.addSystem(system as unknown as (registry: Registry, ctx: any) => void); + } + + getZipper(types: [Component, ...Component[]]): [any, ...any[]] { + return this._registry.getZipper(types); + } +} diff --git a/packages/ecs/src/index.ts b/packages/ecs/src/index.ts index 739ae3b..da11674 100644 --- a/packages/ecs/src/index.ts +++ b/packages/ecs/src/index.ts @@ -1,4 +1,5 @@ import "../lib/libecs.wasm"; -export * from "./ecs-library"; +export { ECSLibrary } from "./ecs-library"; export type { ECSContext } from "./ecs-context.type"; +export type { ECSRegistry, Component, System } from "./ecs-registry"; diff --git a/packages/ecs/test/ecs-library.spec.ts b/packages/ecs/test/ecs-library.spec.ts index dfe552b..f94ed59 100644 --- a/packages/ecs/test/ecs-library.spec.ts +++ b/packages/ecs/test/ecs-library.spec.ts @@ -6,6 +6,7 @@ import { InitContext, } from "@nanoforge/common"; import { EditableLibraryManager } from "@nanoforge/core/src/common/library/manager/library.manager"; +import { type ECSRegistry } from "@nanoforge/ecs"; import { ECSLibrary } from "@nanoforge/ecs/src/ecs-library"; class Position { @@ -21,6 +22,7 @@ class Position { describe("ECSLibrary", () => { let ecs: ECSLibrary; + let registry: ECSRegistry; const assetManager = new AssetManagerLibrary(); const appContext = new ApplicationContext(); const libraryManager = new EditableLibraryManager(); @@ -45,19 +47,20 @@ describe("ECSLibrary", () => { beforeEach(async () => { ecs = new ECSLibrary(); await ecs.init(initContext); + registry = ecs.registry; }); test("init and spawn entity", async () => { - const entity = ecs.spawnEntity(); + const entity = registry.spawnEntity(); expect(entity).toBeDefined(); expect(entity.getId()).toBe(0); }); test("add component to entity", async () => { - const entity = ecs.spawnEntity(); + const entity = registry.spawnEntity(); const pos = new Position(1, 2); - ecs.addComponent(entity, pos); - const components = ecs.getComponents(Position); + registry.addComponent(entity, pos); + const components = registry.getComponents(Position); expect(components.get(entity.getId())).toStrictEqual(new Position(1, 2)); expect(components.size()).toBe(1); });