From a0034c862a0f7f26165c6d797a98c9a6b7c7ea2d Mon Sep 17 00:00:00 2001 From: Ruggero Visintin Date: Tue, 5 Dec 2023 19:39:45 +0100 Subject: [PATCH 1/6] test(renderer): improve existing tests --- test/unit/renderer/Renderer.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/unit/renderer/Renderer.test.ts b/test/unit/renderer/Renderer.test.ts index 12e83508..fc481c47 100644 --- a/test/unit/renderer/Renderer.test.ts +++ b/test/unit/renderer/Renderer.test.ts @@ -26,11 +26,12 @@ describe('renderer/Renderer', () => { [0, 0] ); - // TODO: implement execute interface in renderCommand with gfx as Dependency - // To abstract the logic of how A command is executed from the renderer + const spy = jest.spyOn(renderCommand, 'execute'); renderer.pushRenderCommand(renderCommand); renderer.endFrame(ctx); + + expect(spy).toHaveBeenCalled(); }) it('Should clear the command buffer', () => { From 5fb7cef5be2350e853be7df1044a1b853ed876b4 Mon Sep 17 00:00:00 2001 From: Ruggero Visintin Date: Tue, 5 Dec 2023 20:19:25 +0100 Subject: [PATCH 2/6] feat(rendering): implement rendering system add rendering system implementation + ShapeComponent draw logic --- src/ecs/components/BaseComponent.ts | 4 ++++ src/ecs/components/IComponent.ts | 1 + src/ecs/components/ShapeComponent.ts | 19 ++++++++++++++- src/ecs/entities/BaseEntity.ts | 8 +++---- src/ecs/entities/IEntity.ts | 2 +- src/ecs/systems/RenderSystem.ts | 9 ++++++-- .../unit/ecs/components/BaseComponent.test.ts | 11 +++++---- .../ecs/components/ShapeComponent.test.ts | 18 +++++++++++++++ test/unit/ecs/systems/RenderSystem.test.ts | 23 +++++++++++++++++-- 9 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 test/unit/ecs/components/ShapeComponent.test.ts diff --git a/src/ecs/components/BaseComponent.ts b/src/ecs/components/BaseComponent.ts index 1bc9d996..31466390 100644 --- a/src/ecs/components/BaseComponent.ts +++ b/src/ecs/components/BaseComponent.ts @@ -5,4 +5,8 @@ export class BaseComponent implements IComponent { public getContainer(): IEntity { throw new Error("Method not implemented."); } + + public setContainer(): IEntity { + throw new Error("Method not implemented."); + } } \ No newline at end of file diff --git a/src/ecs/components/IComponent.ts b/src/ecs/components/IComponent.ts index 6e73fc2a..c9e390e4 100644 --- a/src/ecs/components/IComponent.ts +++ b/src/ecs/components/IComponent.ts @@ -2,4 +2,5 @@ import { IEntity } from "../entities"; export interface IComponent { getContainer(): IEntity; + setContainer(container: IEntity): void; } \ No newline at end of file diff --git a/src/ecs/components/ShapeComponent.ts b/src/ecs/components/ShapeComponent.ts index 0327a8d6..fc1d6e84 100644 --- a/src/ecs/components/ShapeComponent.ts +++ b/src/ecs/components/ShapeComponent.ts @@ -1,9 +1,26 @@ -import { PrimitiveType } from "../../renderer/RenderCommand"; +import { Renderer } from "../../renderer"; +import { DrawPrimitiveCommand, PrimitiveType } from "../../renderer/RenderCommand"; import { BaseComponent } from "./BaseComponent"; +import { TransformComponent } from "./TransformComponent"; /** * Represents a primitive Shape like rectangle, circle, etc */ export class ShapeComponent extends BaseComponent { public shapeType: PrimitiveType = PrimitiveType.Rectangle; + + private get transform(): TransformComponent { + return new TransformComponent(); + } + + public draw(renderer: Renderer): void { + const position = this.transform.position; + const size = this.transform.size; + + renderer.pushRenderCommand(new DrawPrimitiveCommand( + PrimitiveType.Rectangle, + [position.x, position.y], + [size.width, size.height] + )); + } } \ No newline at end of file diff --git a/src/ecs/entities/BaseEntity.ts b/src/ecs/entities/BaseEntity.ts index 2d07eb80..64e28f11 100644 --- a/src/ecs/entities/BaseEntity.ts +++ b/src/ecs/entities/BaseEntity.ts @@ -1,14 +1,14 @@ -import { BaseComponent } from "../components"; +import { BaseComponent, IComponent } from "../components"; import { IEntity } from "./IEntity"; export class BaseEntity implements IEntity { - private components: Map = new Map(); + private components: Map = new Map(); - public addComponent(key: string, component: Component): void { + public addComponent(key: string, component: IComponent): void { this.components.set(key, component); } - public getComponent(key: string): Component | undefined { + public getComponent(key: string): Component | undefined { return this.components.get(key) as Component; } } \ No newline at end of file diff --git a/src/ecs/entities/IEntity.ts b/src/ecs/entities/IEntity.ts index ca47f8dc..d62fe7ec 100644 --- a/src/ecs/entities/IEntity.ts +++ b/src/ecs/entities/IEntity.ts @@ -2,5 +2,5 @@ import { IComponent } from "../components"; export interface IEntity { addComponent(key: string, component: IComponent): void; - getComponent(key: string): IComponent | undefined; + getComponent(key: string): T | undefined; } \ No newline at end of file diff --git a/src/ecs/systems/RenderSystem.ts b/src/ecs/systems/RenderSystem.ts index 2af0fca3..753498f0 100644 --- a/src/ecs/systems/RenderSystem.ts +++ b/src/ecs/systems/RenderSystem.ts @@ -1,14 +1,19 @@ +import { Renderer } from "../../renderer"; import { ShapeComponent } from "../components"; import { ISystem } from "./ISystem"; export class RenderSystem implements ISystem { - public readonly components: ShapeComponent[] = [] + public readonly components: ShapeComponent[] = []; + + constructor( + private readonly renderer: Renderer + ) {} public registerComponent(component: ShapeComponent): void { this.components.push(component); } public update(): void { - throw new Error('NotImplemented'); + this.components.forEach(component => component.draw(this.renderer)); } } \ No newline at end of file diff --git a/test/unit/ecs/components/BaseComponent.test.ts b/test/unit/ecs/components/BaseComponent.test.ts index 37254db3..2d3638da 100644 --- a/test/unit/ecs/components/BaseComponent.test.ts +++ b/test/unit/ecs/components/BaseComponent.test.ts @@ -1,7 +1,10 @@ -import { BaseComponent } from "../../../../src"; - describe('ecs/components/BaseComponent', () => { - it('Should construct', () => { - new BaseComponent(); + describe('.getContainer()', () => { + it.todo('Should get the container entity of the component'); + it.todo('Should return null if the component is not attached to an entity'); + }) + + describe('.setConatiner', () => { + it.todo('Should set the container entity to the component'); }) }) \ No newline at end of file diff --git a/test/unit/ecs/components/ShapeComponent.test.ts b/test/unit/ecs/components/ShapeComponent.test.ts new file mode 100644 index 00000000..d9c64ae0 --- /dev/null +++ b/test/unit/ecs/components/ShapeComponent.test.ts @@ -0,0 +1,18 @@ +import { CanvasDevice, DrawPrimitiveCommand, PrimitiveType, Renderer, ShapeComponent } from "../../../../src"; + +describe('ecs/components/ShapeComponent', () => { + describe('.draw()', () => { + const shapeComponent = new ShapeComponent(); + const renderer = new Renderer(new CanvasDevice()); + + it('Should push the right draw command to the renderer', () => { + shapeComponent.draw(renderer); + + expect(renderer.commandBuffer).toEqual([new DrawPrimitiveCommand( + PrimitiveType.Rectangle, + [0, 0], + [0, 0], + )]); + }) + }) +}) \ No newline at end of file diff --git a/test/unit/ecs/systems/RenderSystem.test.ts b/test/unit/ecs/systems/RenderSystem.test.ts index ca129d8c..28653793 100644 --- a/test/unit/ecs/systems/RenderSystem.test.ts +++ b/test/unit/ecs/systems/RenderSystem.test.ts @@ -1,8 +1,8 @@ -import { RenderSystem, ShapeComponent } from "../../../../src" +import { CanvasDevice, RenderSystem, Renderer, ShapeComponent } from "../../../../src"; describe('systems/RenderSystem', () => { describe('.registerComponent()', () => { - const renderSystem = new RenderSystem(); + const renderSystem = new RenderSystem(new Renderer(new CanvasDevice())); const myTestShape = new ShapeComponent(); it('Should register the component into the RenderSystem components list', () => { @@ -11,4 +11,23 @@ describe('systems/RenderSystem', () => { expect(renderSystem.components).toContain(myTestShape); }) }) + + describe('.update()', () => { + const renderSystem = new RenderSystem(new Renderer(new CanvasDevice())); + + it('Should draw renderable object', () => { + const drawSpy = jest.spyOn(ShapeComponent.prototype, 'draw'); + + renderSystem.registerComponent(new ShapeComponent()); + renderSystem.registerComponent(new ShapeComponent()); + + renderSystem.update(); + + expect(drawSpy).toHaveBeenCalledTimes(2); + }); + + it.todo('Should render objects based on their depthIndex in reverse order (0 rendered last)') + + it.todo('Should render object based on their transparency (transparent last)') + }) }) \ No newline at end of file From e6440b0ac5801d38af31b10af4b8c4b01c987bdc Mon Sep 17 00:00:00 2001 From: Ruggero Visintin Date: Tue, 5 Dec 2023 20:29:42 +0100 Subject: [PATCH 3/6] feat: make transform getter public --- src/ecs/components/ShapeComponent.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ecs/components/ShapeComponent.ts b/src/ecs/components/ShapeComponent.ts index fc1d6e84..9585bcf0 100644 --- a/src/ecs/components/ShapeComponent.ts +++ b/src/ecs/components/ShapeComponent.ts @@ -9,8 +9,10 @@ import { TransformComponent } from "./TransformComponent"; export class ShapeComponent extends BaseComponent { public shapeType: PrimitiveType = PrimitiveType.Rectangle; - private get transform(): TransformComponent { - return new TransformComponent(); + private defaultTransform = new TransformComponent(); + + public get transform(): TransformComponent { + return this.defaultTransform; } public draw(renderer: Renderer): void { From ba3acfe7c9fedea7c7258191420e5713d71f0538 Mon Sep 17 00:00:00 2001 From: Ruggero Visintin Date: Tue, 5 Dec 2023 20:29:53 +0100 Subject: [PATCH 4/6] docs(examples): update examples --- examples/simpleRect/index.html | 2 +- examples/simpleShapeComponent/index.html | 34 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 examples/simpleShapeComponent/index.html diff --git a/examples/simpleRect/index.html b/examples/simpleRect/index.html index 732f9d6d..a375f78a 100644 --- a/examples/simpleRect/index.html +++ b/examples/simpleRect/index.html @@ -1,7 +1,7 @@ - + + + + + \ No newline at end of file From f757ccbd71f41734c14b233a42943ad571160eb4 Mon Sep 17 00:00:00 2001 From: Ruggero Visintin Date: Tue, 5 Dec 2023 20:34:13 +0100 Subject: [PATCH 5/6] test(BaseEntity): fix broken test --- test/unit/ecs/entities/BaseEntity.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/ecs/entities/BaseEntity.test.ts b/test/unit/ecs/entities/BaseEntity.test.ts index 26d3ed70..a4d406b1 100644 --- a/test/unit/ecs/entities/BaseEntity.test.ts +++ b/test/unit/ecs/entities/BaseEntity.test.ts @@ -7,7 +7,7 @@ describe('ecs/entities/BaseEntity', () => { it('Should add component to entity', () => { const testComponent = new BaseComponent(); - baseEntity.addComponent('testComponent', testComponent); + baseEntity.addComponent('testComponent', testComponent); expect(baseEntity.getComponent('testComponent')).toEqual(testComponent); }); }) @@ -15,7 +15,7 @@ describe('ecs/entities/BaseEntity', () => { describe('.getComponent()', () => { it('Should retrieve a specific component given a specific key', () => { const testComponent = new BaseComponent(); - baseEntity.addComponent('testComponent', testComponent); + baseEntity.addComponent('testComponent', testComponent); expect(baseEntity.getComponent('testComponent')).toEqual(testComponent); }) From 325ecb398e6b221b7a396e2b4e9e654d216240b6 Mon Sep 17 00:00:00 2001 From: Ruggero Visintin Date: Tue, 5 Dec 2023 20:34:51 +0100 Subject: [PATCH 6/6] ci: remove unit test from build step --- .github/workflows/ci.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a9dee9f..6949bb3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,10 +16,7 @@ jobs: - name: install run: npm ci - - - name: test - run: npm run test - + - name: build run: npm run build test_unit: