import { type Process as EntityProcess, entityBlueprintMap, entityProcessMap, } from "#/engine/Entity.ts"; import { type Graph, getNeighbours } from "#/engine/Graph.ts"; import { box } from "#/engine/lib/box"; import { memoize } from "#/engine/lib/cache.ts"; import MapArray from "#/engine/lib/MapArray.ts"; export type Process = (ctx: CanvasRenderingContext2D, delta: number) => void; export type PreProcess = () => void; export type PostProcess = () => void; export type EntityData = { name: string; init: T; }; export type SceneData = { name: string; width: number; height: number; entities: Array>; }; export type ComponentsMap = Map>; export type Scene = { data: SceneData; components: ComponentsMap; process: Process; }; export type SceneChange = (to: SceneData, from: SceneData) => void; export const sceneDataMap = new Map(); export const sceneProcessMap = new Map(); export const sceneComponentsMap = new Map(); export const sceneChangeSet = new Set(); export const sceneGraph: Graph = new Map(); const sceneEntityProcessMap = new Map>(); const scenePreProcessMap = new Map(); const scenePostProcessMap = new Map(); export const currentScene: Scene = { data: { name: "", width: 0, height: 0, entities: [], }, components: new Map(), process: () => {}, }; function processEntities(delta: number): void { for (const [id, process] of sceneEntityProcessMap.get( currentScene.data.name, )!) { process(id, 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): { process: (fn: Process) => void; preProcess: (fn: PreProcess) => void; postProcess: (fn: PostProcess) => void; linkScenes: ( names: T, ) => (name: T[number]) => Promise; } { sceneDataMap.set(data.name, data); return { process(fn) { sceneProcessMap.set(data.name, (ctx, delta) => { fn(ctx, delta); processEntities(delta); }); }, preProcess(fn) { scenePreProcessMap.set(data.name, fn); }, postProcess(fn) { scenePostProcessMap.set(data.name, fn); }, linkScenes(ids) { for (const id of ids) { linkScenes(data.name, id); } return async (id) => { return setScene(id); }; }, }; } const [loadScene, sceneCache] = memoize(async (name: string) => { await import(`#/game/scenes/${name}/scene.ts`); const data = sceneDataMap.get(name)!; const localEntityProcessMap = new Map(); sceneEntityProcessMap.set(name, localEntityProcessMap); const componentsMap = new Map>(); sceneComponentsMap.set(name, componentsMap); await Promise.all( data.entities.map( async (entityData: EntityData, index: number) => { await import(`#/game/entities/${entityData.name}/entity.ts`); const id = `${index}_${entityData.name}`; const entityProcess = entityProcessMap.get(entityData.name); if (entityProcess !== undefined) { localEntityProcessMap.set(id, entityProcess); } for (const [key, value] of entityBlueprintMap .get(entityData.name)! .entries()) { const boxedValue = box( structuredClone( (entityData.init as Record)[key] ?? value, ), ); if (componentsMap.has(key)) { componentsMap.get(key)!.set(id, boxedValue); } else { componentsMap.set(key, new MapArray([[id, boxedValue]])); } } }, ), ); }); 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 components = sceneComponentsMap.get(name)!; const process = sceneProcessMap.get(name)!; const preProcess = scenePreProcessMap.get(name); if (preProcess) { preProcess(); } const postProcess = scenePostProcessMap.get(currentScene.data.name); if (postProcess) { postProcess(); } currentScene.data = data; self.dispatchEvent(new Event("resize")); currentScene.components = components; currentScene.process = process; for (const callback of sceneChangeSet.values()) { callback(data, currentScene.data); } } export default function (name: string): MapArray { return currentScene.components.get(name) as MapArray; }