Files
nage/engine/Scene.ts
T
2026-08-08 14:00:36 +02:00

185 lines
4.6 KiB
TypeScript

import { box } from "#/engine/Box.ts";
import {
type Process as EntityProcess,
entityBlueprintMap,
entityProcessMap,
} from "#/engine/Entity.ts";
import { type Graph, getNeighbours } from "#/engine/Graph.ts";
import { type Cache, 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<T> = {
name: string;
init: T;
};
export type SceneData = {
name: string;
width: number;
height: number;
entities: Array<EntityData<unknown>>;
};
export type ComponentsMap = Map<string, MapArray<string, unknown>>;
export type Scene = {
data: SceneData;
components: ComponentsMap;
process: Process;
};
export type SceneChange = (to: SceneData, from: SceneData) => void;
export const sceneDataMap = new Map<string, SceneData>();
export const sceneProcessMap = new Map<string, Process>();
export const sceneComponentsMap = new Map<string, ComponentsMap>();
export const sceneChangeSet = new Set<SceneChange>();
export const sceneGraph: Graph<string> = new Map();
const sceneEntityProcessMap = new Map<string, Map<string, EntityProcess>>();
const scenePreProcessMap = new Map<string, PreProcess>();
const scenePostProcessMap = new Map<string, PostProcess>();
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: <T extends readonly string[]>(
names: T,
) => (name: T[number]) => Promise<void>;
} {
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<string, EntityProcess>();
sceneEntityProcessMap.set(name, localEntityProcessMap);
const componentsMap = new Map<string, MapArray<string, unknown>>();
sceneComponentsMap.set(name, componentsMap);
await Promise.all(
data.entities.map(
async (entityData: EntityData<unknown>, 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<string, unknown>)[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<void> {
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 <T>(name: string): MapArray<string, T> {
return currentScene.components.get(name) as MapArray<string, T>;
}