import { entityMap, entityProcessMap, type Process as ProcessEntity, } from "#/engine/Entity.ts"; import { type Graph, getNeighbours } from "#/engine/Graph.ts"; import { useWithAsyncCache } from "#/engine/lib/cache.ts"; import { playAnimation } from "./Sprite"; export type EntityData = { name: string; init: object; }; export type SceneData = { name: string; width: number; height: number; entities: Array; }; export type EntityMap = Map< string, { process: ProcessEntity; instances: Array; } >; export type Scene = { data: SceneData; entityMap: EntityMap; process: Process; }; export type Process = (ctx: CanvasRenderingContext2D, delta: number) => void; export type PreProcess = (entityMap: EntityMap) => void; export type PostProcess = (entityMap: EntityMap) => void; export type SceneChangeHandler = (to: SceneData, from: SceneData) => void; export const sceneProcessMap = new Map(); export const scenePreProcessMap = new Map(); export const scenePostProcessMap = new Map(); export const sceneChangedHandlers = new Set(); export const sceneDataMap = new Map(); export const sceneEntiyMap = new Map(); export const sceneGraph: Graph = new Map(); const sceneInstancesDrawMap = new Map>(); export const [loadScene, sceneCache] = useWithAsyncCache( async (name: string) => { await import(`#/game/scenes/${name}/scene.ts`); const data = sceneDataMap.get(name)!; const entityInstanceMap = sceneEntiyMap.get(name)!; entityInstanceMap.clear(); await Promise.all( data.entities.map(async (entityData: EntityData) => { await import(`#/game/entities/${entityData.name}/entity.ts`); if (!entityInstanceMap.has(entityData.name)) { entityInstanceMap.set(entityData.name, { process: entityProcessMap.get(entityData.name) ?? (() => {}), instances: [], }); } try { entityInstanceMap.get(entityData.name)!.instances.push( structuredClone({ ...entityMap.get(entityData.name)!, ...entityData, }), ); } catch (error) { console.error(entityData.name, error); } }), ); }, ); export const currentScene: Scene = { data: { name: "", width: 0, height: 0, entities: [], }, process: () => {}, entityMap: new Map(), }; function processEntities(delta: number): void { for (const entry of currentScene.entityMap.values()) { for (const instance of entry.instances) { entry.process(instance, delta); } } } // TODO: type stuff and fixme WIP function processSystems(ctx: CanvasRenderingContext2D, delta: number): void { const drawables = sceneInstancesDrawMap.get(currentScene.data.name)!; for (const drawable of drawables.sort( (a, b) => a.position.y - b.position.y, )) { playAnimation( ctx, drawable.animation, drawable.position.x, drawable.position.y, delta, ); } } function linkScenes(a: string, b: string): void { if (sceneGraph.has(a)) { sceneGraph.get(a)!.push(b); } else { sceneGraph.set(a, [b]); } } export function defineScene(data: SceneData): { linkScenes: ( names: T, ) => (name: T[number]) => Promise; process: (fn: Process) => void; preProcess: (fn: PreProcess) => void; postProcess: (fn: PostProcess) => void; } { sceneDataMap.set(data.name, data); sceneEntiyMap.set(data.name, new Map()); return { linkScenes(ids) { for (const id of ids) { linkScenes(data.name, id); } return async (id) => { return setScene(id); }; }, process(fn) { sceneProcessMap.set(data.name, (ctx, delta) => { fn(ctx, delta); processEntities(delta); processSystems(ctx, delta); }); }, preProcess(fn) { scenePreProcessMap.set(data.name, fn); }, postProcess(fn) { scenePostProcessMap.set(data.name, fn); }, }; } export async function setScene(name: string): Promise { await loadScene(name); for (const neighbour of getNeighbours(sceneGraph, name)) { if (!sceneCache.has(neighbour)) { sceneCache.set(neighbour, loadScene(neighbour)); } } const data = sceneDataMap.get(name)!; const process = sceneProcessMap.get(name)!; const entityInstanceMap = sceneEntiyMap.get(name)!; // TODO: test for drawable first sceneInstancesDrawMap.set( data.name, entityInstanceMap.values().reduce( (acc, cur) => { for (const instance of cur.instances) { acc.push(instance); } return acc; }, [] as Array, ), ); const preProcess = scenePreProcessMap.get(name); if (preProcess) { preProcess(sceneEntiyMap.get(name)!); } const postProcess = scenePostProcessMap.get(currentScene.data.name); if (postProcess) { postProcess(sceneEntiyMap.get(name)!); } currentScene.data = data; self.dispatchEvent(new Event("resize")); currentScene.process = process; currentScene.entityMap = entityInstanceMap; for (const handler of sceneChangedHandlers.values()) { handler(data, currentScene.data); } }