185 lines
4.6 KiB
TypeScript
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 { useWithAsyncCache } from "#/engine/lib/cache.ts";
|
|
import MapProxy from "#/engine/lib/MapProxy.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, MapProxy<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] = useWithAsyncCache(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, MapProxy<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)!.map.set(id, boxedValue);
|
|
} else {
|
|
componentsMap.set(key, new MapProxy([[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): MapProxy<string, T> {
|
|
return currentScene.components.get(name) as MapProxy<string, T>;
|
|
}
|