diff options
Diffstat (limited to 'static/src/engine/game.ts')
| -rw-r--r-- | static/src/engine/game.ts | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/static/src/engine/game.ts b/static/src/engine/game.ts new file mode 100644 index 0000000..64f63b5 --- /dev/null +++ b/static/src/engine/game.ts @@ -0,0 +1,112 @@ +import { System, SystemType } from "./system"; +import { Entity } from "./entity"; +import { ComponentType } from "./component"; + +export class Game { + private running: boolean; + private last_update: number; + + private readonly entities: Map<string, Entity> = new Map(); + private readonly component_entities: Map<ComponentType, Set<string>> = + new Map(); + private readonly systems: Map<SystemType, System> = new Map(); + private readonly system_order: SystemType[]; + + constructor( + public readonly client_id: string, + systems: System[], + ) { + this.last_update = performance.now(); + this.running = false; + + systems.forEach((system) => this.systems.set(system.system_type, system)); + this.system_order = systems.map(({ system_type }) => system_type); + } + + public start() { + if (this.running) return; + + console.log("starting game"); + this.running = true; + this.last_update = performance.now(); + + const game_loop = (timestamp: number) => { + if (!this.running) return; + + // rebuild component -> { entity } map + this.component_entities.clear(); + Array.from(this.entities.values()).forEach((entity) => + Object.values(entity.components).forEach((component) => { + const set = + this.component_entities.get(component.name) ?? new Set<string>(); + set.add(entity.id); + this.component_entities.set(component.name, set); + }), + ); + + const dt = timestamp - this.last_update; + + this.system_order.forEach((system_type) => + this.systems.get(system_type)!.update(dt, this), + ); + + this.last_update = timestamp; + requestAnimationFrame(game_loop); // tail call recursion! /s + }; + requestAnimationFrame(game_loop); + } + + public stop() { + if (!this.running) return; + this.running = false; + } + + public for_each_entity_with_component( + component: ComponentType, + callback: (entity: Entity) => void, + ) { + this.component_entities.get(component)?.forEach((entity_id) => { + const entity = this.entities.get(entity_id); + if (!entity) return; + + callback(entity); + }); + } + + public get_entity(id: string) { + return this.entities.get(id); + } + + public put_entity(entity: Entity) { + const old_entity = this.entities.get(entity.id); + if (old_entity) this.clear_entity_components(old_entity); + + Object.values(entity.components).forEach((component) => { + const set = + this.component_entities.get(component.name) ?? new Set<string>(); + set.add(entity.id); + this.component_entities.set(component.name, set); + }); + this.entities.set(entity.id, entity); + } + + public remove_entity(id: string) { + const entity = this.entities.get(id); + if (typeof entity === "undefined") return; + + this.clear_entity_components(entity); + this.entities.delete(id); + } + + private clear_entity_components(entity: Entity) { + Object.values(entity.components).forEach((component) => { + const set = this.component_entities.get(component.name); + if (typeof set === "undefined") return; + set.delete(entity.id); + }); + } + + public get_system<T extends System>(system_type: SystemType): T | undefined { + return this.systems.get(system_type) as T; + } +} |
