refac: wip GameEngine

This commit is contained in:
2024-03-09 09:19:43 +00:00
parent ff23cd27b8
commit d87c177d50
+66 -26
View File
@@ -3,41 +3,54 @@ import Denque from "denque";
import { ResourceController } from "./ResourceController.js"; import { ResourceController } from "./ResourceController.js";
import { entriesWithType } from "@sammo/util/converter"; import { entriesWithType } from "@sammo/util/converter";
export interface ActionQueueItem { export interface ActionRequest {
waiterSeq: number;
} }
export interface ActionQueueItemResult { export interface ActionRequestContainer {
success: boolean; waiterSeq: number;
reason?: string; action: ActionRequest;
}
export type ActionSuccess = {
success: true;
message?: string; message?: string;
} }
export type ActionFailure = {
success: false;
reason: string;
}
export type ActionResult = ActionSuccess | ActionFailure;
type QueueType = 'server' | 'api';
//type QueueType = 'server' | 'npc' | 'api';
export class GameEngine { export class GameEngine {
private static instance: GameEngine | null = null; private static instance: GameEngine | null = null;
private actionSeq = 0; private actionSeq = 0;
private queueWaiters = new Map<number, [resolve: (result: unknown) => void, reject: (reason: unknown) => void]>(); private queueWaiters = new Map<number, [resolve: (result: ActionSuccess) => void, reject: (reason: ActionFailure) => void]>();
private signal = new Signal(30000); private signal = new Signal(30000);
private actionQueue = { private actionQueue = {
server: new Denque<ActionQueueItem>(), server: new Denque<ActionRequestContainer>(),
npc: new Denque<ActionQueueItem>(), //npc: new Denque<ActionRequestContainer>(),
api: new Denque<ActionQueueItem>(), api: new Denque<ActionRequestContainer>(),
} as const; } as const satisfies Record<QueueType, Denque<ActionRequestContainer>>;
private rsc: ResourceController private rsc: ResourceController
private constructor() { private constructor() {
//TODO MongoDB를 넘겨줘야함
this.rsc = new ResourceController(); this.rsc = new ResourceController();
} }
private continueRun = true; private continueRun = true;
private async gameLoop(): Promise<void> { private async gameLoop(): Promise<void> {
let generalWaitTimer: ReturnType<typeof setTimeout> | null = null; let generalWaitTimer: ReturnType<typeof setTimeout> | null = null;
let prevSaveWaiter: Promise<void> | null = null; let prevSaveWaiter: Promise<void> | null = null;
@@ -46,7 +59,7 @@ export class GameEngine {
//가장 빠른 장수 //가장 빠른 장수
do { do {
const now = new Date(); const now = new Date();
const upcomingGeneral = this.rsc.upcomingGeneral(); const upcomingGeneral = this.rsc.peekGeneralTurntimeQueue();
if (!upcomingGeneral) { if (!upcomingGeneral) {
break; break;
} }
@@ -64,6 +77,13 @@ export class GameEngine {
} }
} while (0); } while (0);
for(const queue of Object.values(this.actionQueue)){
if(!queue.isEmpty()){
this.signal.notify();
break;
}
}
//이벤트 대기 //이벤트 대기
await this.signal.wait(); await this.signal.wait();
@@ -80,18 +100,23 @@ export class GameEngine {
while (true) { while (true) {
//장수턴 부터 처리 //장수턴 부터 처리
const upcomingGeneral = this.rsc.upcomingGeneral(); const upcomingGeneral = this.rsc.popGeneralTurntimeQueue();
if (!upcomingGeneral) { if (!upcomingGeneral) {
break; break;
} }
if (upcomingGeneral.raw.turntime > now) { const generalTurntime = upcomingGeneral.raw.turntime;
if (generalTurntime > now) {
break; break;
} }
//TODO: 턴 수행해야지! //TODO: 턴 수행해야지!
upcomingGeneral.updateTurntime(); upcomingGeneral.updateTurntime();
this.rsc.insertGeneralTurntimeQueue(upcomingGeneral, true);
this.rsc.gameEnv().update((env)=>{
env.lastExecuted = generalTurntime;
})
processedGeneralCount++; processedGeneralCount++;
} }
@@ -136,7 +161,7 @@ export class GameEngine {
await prevSaveWaiter; await prevSaveWaiter;
prevSaveWaiter = null; prevSaveWaiter = null;
await this.rsc.saveAll(); await this.rsc.saveAll();
for(const response of responseQueue){ for (const response of responseQueue) {
response(); response();
} }
} }
@@ -147,9 +172,18 @@ export class GameEngine {
private gameLoopPromise: Promise<void> | null = null; private gameLoopPromise: Promise<void> | null = null;
public static async initInstance(): Promise<GameEngine> {
if (GameEngine.instance) {
throw new Error('GameEngine is already initialized');
}
GameEngine.instance = new GameEngine();
return GameEngine.instance;
}
public static getInstance() { public static getInstance() {
if (!GameEngine.instance) { if (!GameEngine.instance) {
GameEngine.instance = new GameEngine(); throw new Error('GameEngine is not initialized');
} }
return GameEngine.instance; return GameEngine.instance;
} }
@@ -182,7 +216,8 @@ export class GameEngine {
} }
private async pushAction(queneName: string, queue: Denque<ActionQueueItem>, action: ActionQueueItem): Promise<ActionQueueItemResult> { private async pushAction(queueName: QueueType, action: ActionRequest): Promise<ActionResult> {
const queue = this.actionQueue[queueName];
if (this.actionSeq >= Number.MAX_SAFE_INTEGER) { if (this.actionSeq >= Number.MAX_SAFE_INTEGER) {
this.actionSeq = 0; this.actionSeq = 0;
} }
@@ -197,29 +232,34 @@ export class GameEngine {
const waiter = new Promise((resolve, reject) => { const waiter = new Promise((resolve, reject) => {
this.queueWaiters.set(seq, [resolve, reject]); this.queueWaiters.set(seq, [resolve, reject]);
done = true; done = true;
queue.push(action); queue.push({
waiterSeq: seq,
action
});
}); });
await delay(0); await delay(0);
this.signal.notify(); this.signal.notify();
if (!done) { if (!done) {
throw new Error(`GameEngine: push${queneName}Action failed`); throw new Error(`GameEngine: pushAction(${queueName}) failed`);
} }
return waiter as Promise<ActionQueueItemResult>; return waiter as Promise<ActionResult>;
} }
public async pushServerAction(action: ActionQueueItem): Promise<ActionQueueItemResult> { public async pushServerAction(action: ActionRequest): Promise<ActionResult> {
return this.pushAction('Server', this.actionQueue.server, action); return this.pushAction('server', action);
} }
public async pushNPCAction(action: ActionQueueItem): Promise<ActionQueueItemResult> { /*
return this.pushAction('NPC', this.actionQueue.npc, action); public async pushNPCAction(action: ActionRequest): Promise<ActionResult> {
return this.pushAction('npc', action);
} }
*/
public async pushAPIAction(action: ActionQueueItem): Promise<ActionQueueItemResult> { public async pushAPIAction(action: ActionRequest): Promise<ActionResult> {
return this.pushAction('API', this.actionQueue.api, action); return this.pushAction('api', action);
} }
} }