feat: GameEngineController
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user