actions reactored. multi-tick action support

This commit is contained in:
azykov@mail.ru
2026-06-12 11:46:42 +03:00
parent 36f76efdd7
commit 20a4edad5a
35 changed files with 1047 additions and 373 deletions
+57 -65
View File
@@ -1,39 +1,18 @@
import type { Game, RuntimeGameScene, RuntimeScene, World } from "../types";
import type { GameEventContext, GameEventHandler, GeneratorEventHandler, InternalGameEventBus } from "../types/runtime/gameBus";
import { toJS } from "mobx";
import type { Game, RuntimeGameObjectInstance, RuntimeGameScene, RuntimeScene, ScriptAction, ScriptCoroutineContext, World } from "../types";
import { clone } from "../utils";
import { populateRuntimeScene } from "../utils/runtime";
import { ObjectApi } from "../utils/runtime/objectApi";
import type { GameScheduler, YieldCommand } from "./gameScheduler";
import { ScriptGameEventBus } from "../scripts/eventBus";
import * as ScriptActions from "../scripts/actions";
import type { ScriptCoroutines } from "../scripts/coroutines";
export class GameEventBus {
private handlers = new Map<string, GameEventHandler[]>();
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// type ScriptActionHelperFunction<T extends abstract new (...args: any[]) => ScriptAction> =
// (...args: ConstructorParameters<T>) => InstanceType<T>;
on(event: string, handler: GameEventHandler): void {
console.log('subscribing to ' + event, handler);
let set = this.handlers.get(event);
if (!set) {
set = [];
this.handlers.set(event, set);
}
if (!set.includes(handler))
set.push(handler);
}
off(event: string, handler: GameEventHandler): void {
console.log('unubscribing from ' + event, handler);
const handlers = this.handlers.get(event);
if (!handlers)
return;
this.handlers.set(event, handlers.filter((h) => h !== handler));
}
emit(event: string, context: GameEventContext): void {
console.log('emitting ' + event);
this.handlers.get(event)
?.forEach((handler) => handler(context));
}
}
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// const scriptActionHelperFunction = <T extends new (...args: any[]) => ScriptAction>(Cls: T): ScriptActionHelperFunction<T> =>
// (...args) => new Cls(...args) as InstanceType<T>;
export class GameFactory {
@@ -66,47 +45,60 @@ export class GameFactory {
public static evalGameScripts(
world: World,
scene: RuntimeGameScene,
eventBus: GameEventBus,
scheduler: GameScheduler,
): void {
coroutines: ScriptCoroutines,
): ScriptGameEventBus {
const handlerWrappers = new WeakMap<GeneratorEventHandler, GameEventHandler>();
const eventBus = new ScriptGameEventBus(coroutines);
const wait = (seconds: number): YieldCommand => ({ type: 'waitSeconds', seconds });
const waitFrames = (frames: number): YieldCommand => ({ type: 'waitFrames', frames });
const waitUntil = (condition: () => boolean): YieldCommand => ({ type: 'waitUntil', condition });
const internalBus: InternalGameEventBus = {
onGameEvent(event, genFn) {
const wrapper: GameEventHandler = (ctx) => scheduler.startCoroutine(genFn(ctx) as Generator<YieldCommand, void, void>);
handlerWrappers.set(genFn, wrapper);
eventBus.on(event, wrapper);
},
offGameEvent(event, genFn) {
const wrapper = handlerWrappers.get(genFn);
if (wrapper) {
eventBus.off(event, wrapper);
handlerWrappers.delete(genFn);
}
},
const scriptHelpers = {
// convert all ScriptActions imported to a structure factory(coroutine):
// {
// WaitSeconds: (seconds) => new WaitSecondsScriptAction(coroutine, seconds),
// ...
// }
actions: (context: ScriptCoroutineContext) => Object.fromEntries(
Object.entries(ScriptActions)
.map(([name, Cls]) => [
name.replace(/ScriptAction$/, ''),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(...args: any[]) => new (Cls as any)(context, ...args),
])
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as Record<string, (...args: any[]) => ScriptAction>,
};
try {
Object.values(world.objectTypes)
.filter((ot) => ot.javascript)
.forEach((ot) => {
const gameScript = eval(`(function gameScript({onGameEvent, offGameEvent, wait, waitFrames, waitUntil}, {api, object, objectType}) {${ot.javascript}})`);
Object.values(world.objectTypes)
.filter((ot) => ot.javascript)
.forEach((ot) => {
try {
const javascript = `(function gameScript(eventBus, helpers, thisObject, thisObjectType) {${ot.javascript}})`;
const gameScript = eval(javascript);
Object.values(scene.objects)
.filter((o) => o.typeId == ot.id)
.map((liveObj) => {
const snapshot = toJS(liveObj) as RuntimeGameObjectInstance;
snapshot.rb = () => liveObj.rb();
return snapshot;
})
.forEach((o) => {
const api = new ObjectApi(o, ot, world, scene);
gameScript({ ...internalBus, wait, waitFrames, waitUntil }, { api, object: o, objectType: ot });
try {
const localScriptHelpers = {
...scriptHelpers,
actions: scriptHelpers.actions({ thisObject: o, thisObjectType: ot }),
};
gameScript(eventBus, localScriptHelpers, o, ot);
}
catch (err) {
console.log('Error running object instance script:\n' + err);
}
});
});
}
catch (err) {
console.log('Error running game script:\n' + err);
}
}
catch (err) {
console.log('Error evaluating object type script:\n' + err);
}
});
return eventBus;
}
}