From 1bac088437dc2b6e1da1124673aee0c5e90eee17 Mon Sep 17 00:00:00 2001 From: Marco Date: Wed, 2 Dec 2020 00:01:51 +0100 Subject: [PATCH] make PDA work with constructors --- README.md | 6 +-- .../pong/src/app/frame-transition-handlers.ts | 10 ++--- examples/pong/src/app/game-store.ts | 4 +- examples/pong/src/main.ts | 43 +++++++++---------- examples/pong/src/systems/menu.ts | 2 +- examples/pong/src/systems/pause.ts | 2 +- examples/pong/src/systems/score.ts | 2 +- src/pda.spec.ts | 8 ++-- src/pda.ts | 9 ++-- src/world.spec.ts | 6 +-- src/world.ts | 19 ++++---- 11 files changed, 57 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index c1b204e..cb2bdb1 100644 --- a/README.md +++ b/README.md @@ -103,11 +103,9 @@ using the handler function. Single calls to `dispatch()` do not offer the benefi class InitState extends State { _systems = [InitSystem] } class RunState extends State { _systems = [GravitySystem] } class PauseState extends State { _systems = [PauseSystem] } -const initState = new InitState(); -const runState = new RunState(); -world.dispatch(initState); -while (true) world.dispatch(runState); +world.dispatch(InitState); +world.run({ initialState: RunState }); ``` ## Update loop diff --git a/examples/pong/src/app/frame-transition-handlers.ts b/examples/pong/src/app/frame-transition-handlers.ts index 80d8f51..6c16020 100644 --- a/examples/pong/src/app/frame-transition-handlers.ts +++ b/examples/pong/src/app/frame-transition-handlers.ts @@ -4,7 +4,7 @@ import {MenuState} from "../states/menu"; let lastTransition = Date.now(); -export async function afterFrameStep(actions: ITransitionActions) { +export async function afterFrameHandler(actions: ITransitionActions) { const gameStore = actions.getResource(GameStore); if (gameStore.exit) { @@ -17,9 +17,9 @@ export async function afterFrameStep(actions: ITransitionActions) { gameStore.popState = false; } - if (gameStore.pushState) { - await actions.pushState(gameStore.pushState); - gameStore.pushState = undefined; + if (gameStore.PushState) { + await actions.pushState(gameStore.PushState); + gameStore.PushState = undefined; } if (gameStore.backToMenu) { @@ -31,7 +31,7 @@ export async function afterFrameStep(actions: ITransitionActions) { } } -export async function beforeFrameStep(actions: ITransitionActions) { +export async function beforeFrameHandler(actions: ITransitionActions) { const gameStore = actions.getResource(GameStore); { // Update info diff --git a/examples/pong/src/app/game-store.ts b/examples/pong/src/app/game-store.ts index 5ece82e..b6783f7 100644 --- a/examples/pong/src/app/game-store.ts +++ b/examples/pong/src/app/game-store.ts @@ -1,5 +1,5 @@ import { EKeyState } from '../systems/input' -import {IState} from "../ecs"; +import {IState, TStateProto} from "../ecs"; export enum EMovement { up, @@ -39,5 +39,5 @@ export class GameStore { pointsLeft = 0 pointsRight = 0 popState = false - pushState?: IState + PushState?: TStateProto } diff --git a/examples/pong/src/main.ts b/examples/pong/src/main.ts index faf86c7..81373ec 100644 --- a/examples/pong/src/main.ts +++ b/examples/pong/src/main.ts @@ -1,6 +1,6 @@ import {ECS, IWorld} from "./ecs"; import {PaddleSystem} from "./systems/paddle"; -import {afterFrameStep, beforeFrameStep} from "./app/frame-transition-handlers"; +import {afterFrameHandler, beforeFrameHandler} from "./app/frame-transition-handlers"; import {InputSystem} from "./systems/input"; import {GameStore} from "./app/game-store"; import {MenuState} from "./states/menu"; @@ -17,7 +17,7 @@ import {GameItem} from "./components/game-item"; import {ScoreBoardSystem} from "./systems/score-board"; -const cleanup = async (_world: IWorld) => { +const cleanup = () => { const canvasEle = document.querySelector('canvas'); if (!canvasEle) throw new Error('Could not find canvas element!'); @@ -30,8 +30,7 @@ const cleanup = async (_world: IWorld) => { }; const createWorld = () => { - const ecs = new ECS(); - return ecs + return new ECS() .buildWorld() .withSystem(BallSystem, [InputSystem]) .withSystem(InputSystem) @@ -40,18 +39,20 @@ const createWorld = () => { .withSystem(PauseSystem, [InputSystem]) .withSystem(ScoreBoardSystem) // todo: throw if a system was not registered but is supposed to run .withSystem(ScoreSystem, [InputSystem]) - .withComponent(Ball) - .withComponent(GameItem) - .withComponent(MenuItem) - .withComponent(Paddle) - .withComponent(PauseItem) - .withComponent(Position) - .withComponent(ScoreItem) - .withComponent(UIItem) + .withComponents([ + Ball, + GameItem, + MenuItem, + Paddle, + PauseItem, + Position, + ScoreItem, + UIItem, + ]) .build(); }; -const initGame = async (world: IWorld) => { +const initGame = (world: IWorld) => { const canvasEle = document.querySelector('canvas'); if (!canvasEle) throw new Error('Could not find canvas element!'); @@ -71,21 +72,19 @@ const initGame = async (world: IWorld) => { world.addResource(gameStore); }; -const runGame = async (world: IWorld) => { +const runGame = (world: IWorld) => { return world.run({ - afterStepHandler: afterFrameStep, - beforeStepHandler: beforeFrameStep, + afterStepHandler: afterFrameHandler, + beforeStepHandler: beforeFrameHandler, initialState: MenuState, - }).catch(console.error); + }); } // main function (async () => { const world = createWorld(); - await initGame(world); + initGame(world); await runGame(world); - await cleanup(world); -})(); - -// todo: find a way to fix the problem of optimizers destroying constructor names + cleanup(); +})().catch(console.error); diff --git a/examples/pong/src/systems/menu.ts b/examples/pong/src/systems/menu.ts index 52c7209..399e01f 100644 --- a/examples/pong/src/systems/menu.ts +++ b/examples/pong/src/systems/menu.ts @@ -32,7 +32,7 @@ export class MenuSystem extends System { if (this.gameStore.input.actions.menuConfirm) { if (this.menuAction == EActions.Play) { - this.gameStore.pushState = new GameState(); + this.gameStore.PushState = GameState; } else { this.gameStore.exit = true; diff --git a/examples/pong/src/systems/pause.ts b/examples/pong/src/systems/pause.ts index 49bc607..157b1d7 100644 --- a/examples/pong/src/systems/pause.ts +++ b/examples/pong/src/systems/pause.ts @@ -34,7 +34,7 @@ export class PauseSystem extends System { if (this.gameStore.input.actions.togglePause) { if (isGameState) { - this.gameStore.pushState = new PauseState(); + this.gameStore.PushState = PauseState; } else { this.gameStore.popState = true; diff --git a/examples/pong/src/systems/score.ts b/examples/pong/src/systems/score.ts index ba4e19f..49e550f 100644 --- a/examples/pong/src/systems/score.ts +++ b/examples/pong/src/systems/score.ts @@ -29,7 +29,7 @@ export class ScoreSystem extends System { scorePoint(scoreItem: GameItem, score: number, ball: Ball, ballPos: Position, ballDir: Direction) { scoreItem.score = score; if (score == this.gameStore.pointLimit) { - this.gameStore.pushState = new ScoreBoardState(); + this.gameStore.PushState = ScoreBoardState; } ballPos.x = this.canvasEle.width / 2 - ball.size / 2; diff --git a/src/pda.spec.ts b/src/pda.spec.ts index 73a87ed..e6ebbfb 100644 --- a/src/pda.spec.ts +++ b/src/pda.spec.ts @@ -1,10 +1,12 @@ // todo: this PushDownAutomaton could get its own package on npm -export interface IPushDownAutomaton { +import {TTypeProto} from "./_.spec"; + +export interface IPushDownAutomaton> { readonly state?: T clear(): void pop(): T | undefined - push(state: T): void + push(state: P): void } -export type TPushDownAutomatonProto = { new(): IPushDownAutomaton }; +export type TPushDownAutomatonProto = { new(): IPushDownAutomaton> }; export default IPushDownAutomaton; diff --git a/src/pda.ts b/src/pda.ts index e42fe5e..5594fcf 100644 --- a/src/pda.ts +++ b/src/pda.ts @@ -1,11 +1,12 @@ import IPushDownAutomaton from "./pda.spec"; +import {TTypeProto} from "./_.spec"; type TStateNode = { state: T, prevNode?: TStateNode, }; -export class PushDownAutomaton implements IPushDownAutomaton { +export class PushDownAutomaton> implements IPushDownAutomaton { protected currentState?: T; protected statesTail?: TStateNode; @@ -29,11 +30,11 @@ export class PushDownAutomaton implements IPushDownAutomaton { return oldTail.state; } - push(state: T): void { - this.currentState = state; + push(State: P): void { + this.currentState = new State(); this.statesTail = { prevNode: this.statesTail, - state, + state: this.currentState, }; } } diff --git a/src/world.spec.ts b/src/world.spec.ts index 3485aee..62622dd 100644 --- a/src/world.spec.ts +++ b/src/world.spec.ts @@ -24,7 +24,7 @@ export interface IStaticRunConfiguration { afterStepHandler: (actions: ITransitionActions) => Promise | void beforeStepHandler: (actions: ITransitionActions) => Promise | void executionFunction: TExecutionFunction - initialState: IState, + initialState: TStateProto, } export type TSystemInfo = { dataPrototype: TTypeProto @@ -170,9 +170,9 @@ export interface ITransitionActions extends IPartialWorld { /** * Change the running world to a new state - * @param newState + * @param NewState */ - pushState(newState: IState): Promise + pushState(NewState: TStateProto): Promise } export interface IWorld extends IPartialWorld { diff --git a/src/world.ts b/src/world.ts index 09084e3..487904c 100644 --- a/src/world.ts +++ b/src/world.ts @@ -30,14 +30,14 @@ export class World implements IWorld { protected dirty = false; protected entityInfos: Map = new Map(); protected entityWorld: IEntityWorld; - protected pda = new PushDownAutomaton(); + protected pda = new PushDownAutomaton(); protected prefabs = { nextHandle: 0, entityLinks: new Map(), }; protected resources = new Map<{ new(): Object }, Object>(); protected runExecutionPipeline: Set>[] = []; - protected runExecutionPipelineCache: Map>[]> = new Map(); + protected runExecutionPipelineCache: Map>[]> = new Map(); protected runPromise?: Promise = undefined; protected _saveFormat?: ISaveFormat; protected shouldRunSystems = false; @@ -316,7 +316,7 @@ export class World implements IWorld { } await newState.activate(this.transitionWorld); - this.runExecutionPipeline = this.runExecutionPipelineCache.get(newState) ?? []; + this.runExecutionPipeline = this.runExecutionPipelineCache.get(newState.constructor as TStateProto) ?? []; } // todo: improve logic which sets up the groups (tracked by #13) @@ -356,15 +356,18 @@ export class World implements IWorld { return result; } - protected async pushState(newState: IState): Promise { + protected async pushState(NewState: TStateProto): Promise { await this.pda.state?.deactivate(this.transitionWorld); - this.pda.push(newState); - if (this.runExecutionPipelineCache.has(newState)) { - this.runExecutionPipeline = this.runExecutionPipelineCache.get(newState) ?? []; + this.pda.push(NewState); + + const newState = this.pda.state!; + + if (this.runExecutionPipelineCache.has(NewState)) { + this.runExecutionPipeline = this.runExecutionPipelineCache.get(NewState) ?? []; } else { newState.create(this.transitionWorld); this.runExecutionPipeline = this.prepareExecutionPipeline(newState); - this.runExecutionPipelineCache.set(newState, this.runExecutionPipeline); + this.runExecutionPipelineCache.set(NewState, this.runExecutionPipeline); } await newState.activate(this.transitionWorld);