feat: GameEngineController
This commit is contained in:
@@ -4,6 +4,7 @@ import { ResourceController } from "./ResourceController.js";
|
|||||||
import { entriesWithType } from "@sammo/util/converter";
|
import { entriesWithType } from "@sammo/util/converter";
|
||||||
import db from "./connectDB.js";
|
import db from "./connectDB.js";
|
||||||
import type { Mongoose } from "mongoose";
|
import type { Mongoose } from "mongoose";
|
||||||
|
import type { GameEngineMsg } from "./GameEngineDefs.js";
|
||||||
export interface ActionRequest {
|
export interface ActionRequest {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -45,7 +46,10 @@ export class GameEngine {
|
|||||||
|
|
||||||
private rsc: ResourceController
|
private rsc: ResourceController
|
||||||
|
|
||||||
private constructor(db: Mongoose) {
|
private constructor(
|
||||||
|
db: Mongoose,
|
||||||
|
private postStatus: (msg: GameEngineMsg) => void,
|
||||||
|
) {
|
||||||
this.rsc = new ResourceController(db);
|
this.rsc = new ResourceController(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +126,13 @@ export class GameEngine {
|
|||||||
|
|
||||||
if (processedGeneralCount > 0) {
|
if (processedGeneralCount > 0) {
|
||||||
await prevSaveWaiter;
|
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;
|
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) {
|
if (GameEngine.instance) {
|
||||||
throw new Error('GameEngine is already initialized');
|
throw new Error('GameEngine is already initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
GameEngine.instance = new GameEngine(await db);
|
GameEngine.instance = new GameEngine(await db, updateHandler);
|
||||||
return GameEngine.instance;
|
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