feat: GameEngineController

This commit is contained in:
2024-03-09 16:15:43 +00:00
parent 554588fb42
commit ddfff44c12
4 changed files with 135 additions and 4 deletions
+14 -4
View File
@@ -4,6 +4,7 @@ import { ResourceController } from "./ResourceController.js";
import { entriesWithType } from "@sammo/util/converter";
import db from "./connectDB.js";
import type { Mongoose } from "mongoose";
import type { GameEngineMsg } from "./GameEngineDefs.js";
export interface ActionRequest {
}
@@ -45,7 +46,10 @@ export class GameEngine {
private rsc: ResourceController
private constructor(db: Mongoose) {
private constructor(
db: Mongoose,
private postStatus: (msg: GameEngineMsg) => void,
) {
this.rsc = new ResourceController(db);
}
@@ -122,7 +126,13 @@ export class GameEngine {
if (processedGeneralCount > 0) {
await prevSaveWaiter;
prevSaveWaiter = this.rsc.saveAll();
const lastExecuted = this.rsc.gameEnv().raw.lastExecuted;
prevSaveWaiter = this.rsc.saveAll().then(()=>{
this.postStatus({
type: 'update',
lastExecuted: lastExecuted.toISOString()
});
});
}
//서버 이벤트 처리
@@ -172,12 +182,12 @@ export class GameEngine {
private gameLoopPromise: Promise<void> | null = null;
public static async initInstance(): Promise<GameEngine> {
public static async initInstance(updateHandler: (msg: GameEngineMsg)=>void): Promise<GameEngine> {
if (GameEngine.instance) {
throw new Error('GameEngine is already initialized');
}
GameEngine.instance = new GameEngine(await db);
GameEngine.instance = new GameEngine(await db, updateHandler);
return GameEngine.instance;
}
+103
View File
@@ -0,0 +1,103 @@
import { randomUUID } from "node:crypto";
import { fileURLToPath } from "node:url";
import { MessageChannel, Worker } from "node:worker_threads";
import * as nodePath from "node:path";
import { delay, must } from "@sammo/util";
import type { GameEngineMsg, GameEngineRPCDefs } from "./GameEngineDefs.js";
import { RPCClient } from "@sammo/server_util";
const __filename = fileURLToPath(new URL(import.meta.url));
const __dirname = nodePath.dirname(__filename);
const workerPath = nodePath.resolve(__dirname, 'GameEngineWorker');
class GameEngineController {
private constructor() { }
private static _instance: GameEngineController;
static get instance(): GameEngineController {
if (!GameEngineController._instance) {
GameEngineController._instance = new GameEngineController();
}
return GameEngineController._instance;
}
private engineGUID?: string = undefined;
private _lastExecuted?: Date = undefined;
private stopWaiter: Promise<void> = Promise.resolve();
private stopWaiterResolve: () => void = () => { };
private rpcClient?: RPCClient<GameEngineRPCDefs> = undefined;
get isRunning(): boolean {
return this.engineGUID !== undefined;
}
get lastExecuted(): Date | undefined {
return this._lastExecuted;
}
async start(): Promise<void> {
if (this.engineGUID) {
throw new Error('GameEngine is already running');
}
const guid = randomUUID();
const { port1: rpcServer, port2: rpcClient } = new MessageChannel();
const worker = new Worker(workerPath, {
workerData: {
guid
}
});
this.stopWaiter = new Promise((resolve) => {
this.stopWaiterResolve = resolve;
});
worker.postMessage({
type: 'init',
port: rpcServer
}, [rpcServer]);
await delay(0);
this.engineGUID = guid;
worker.once('exit', () => {
this.engineGUID = undefined;
this._lastExecuted = undefined;
this.rpcClient = undefined;
this.stopWaiterResolve();
});
worker.on('message', (msg: GameEngineMsg) => {
if (msg.type === 'update') {
this._lastExecuted = new Date(msg.lastExecuted);
}
});
this.rpcClient = new RPCClient(rpcClient, 'GameEngineController')
}
async stop(): Promise<boolean> {
if (!this.isRunning) {
return false;
}
await must(this.rpcClient).callFunction('stop', undefined);
await this.stopWaiter;
return true;
}
async restart(): Promise<void> {
await this.stop();
await this.start();
}
async call<T extends keyof GameEngineRPCDefs & string>(name: T, arg: Parameters<GameEngineRPCDefs[T]>[0]): Promise<ReturnType<GameEngineRPCDefs[T]>> {
return must(this.rpcClient).callFunction(name, arg);
}
}
export function getGameEngineController(): GameEngineController {
return GameEngineController.instance;
}
+18
View File
@@ -0,0 +1,18 @@
import type { RPCLists } from "@sammo/server_util";
import type { ActionRequest, ActionResult } from "./GameEngine.js";
export type GameEngineRPCDefs = {
stop: ()=>Promise<void>,
pushAPIAction: (action: ActionRequest)=>Promise<ActionResult>,
pushServerAction: (action: ActionRequest)=>Promise<ActionResult>,
};
type WorkerUpdate = {
type: 'update',
lastExecuted: string,
}
export type GameEngineMsg = WorkerUpdate;