feat: 전투 시뮬레이터를 브라우저 Worker로 이관
서버가 권위 환경과 반복 seed를 준비하고 공용 logic 프로세서를 브라우저와 기존 서버 fallback이 함께 사용하도록 변경한다. production Chromium에서 고정 seed와 1000회 Node 결과 동등성을 검증한다.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import type { BattleSimJobPayload, BattleSimResultPayload } from '@sammo-ts/logic';
|
||||
|
||||
import type { BattleSimulatorWorkerRequest, BattleSimulatorWorkerResponse } from './battleSimulatorWorkerProtocol';
|
||||
|
||||
type PendingRequest = {
|
||||
resolve: (result: BattleSimResultPayload) => void;
|
||||
reject: (error: Error) => void;
|
||||
};
|
||||
|
||||
export class BattleSimulatorWorkerClient {
|
||||
private worker: Worker | null = null;
|
||||
private nextRequestId = 1;
|
||||
private readonly pending = new Map<number, PendingRequest>();
|
||||
|
||||
private ensureWorker(): Worker {
|
||||
if (this.worker) {
|
||||
return this.worker;
|
||||
}
|
||||
|
||||
const worker = new Worker(new URL('../workers/battleSimulator.worker.ts', import.meta.url), {
|
||||
type: 'module',
|
||||
name: 'sammo-battle-simulator',
|
||||
});
|
||||
worker.addEventListener('message', this.handleMessage);
|
||||
worker.addEventListener('error', this.handleWorkerError);
|
||||
this.worker = worker;
|
||||
return worker;
|
||||
}
|
||||
|
||||
private readonly handleMessage = (event: MessageEvent<BattleSimulatorWorkerResponse>): void => {
|
||||
const pending = this.pending.get(event.data.requestId);
|
||||
if (!pending) {
|
||||
return;
|
||||
}
|
||||
this.pending.delete(event.data.requestId);
|
||||
if (event.data.ok) {
|
||||
pending.resolve(event.data.result);
|
||||
return;
|
||||
}
|
||||
pending.reject(new Error(event.data.error));
|
||||
};
|
||||
|
||||
private readonly handleWorkerError = (event: ErrorEvent): void => {
|
||||
const error = new Error(event.message || '전투 시뮬레이션 worker 오류');
|
||||
for (const pending of this.pending.values()) {
|
||||
pending.reject(error);
|
||||
}
|
||||
this.pending.clear();
|
||||
this.disposeWorker();
|
||||
};
|
||||
|
||||
public run(payload: BattleSimJobPayload): Promise<BattleSimResultPayload> {
|
||||
const requestId = this.nextRequestId;
|
||||
this.nextRequestId += 1;
|
||||
const request: BattleSimulatorWorkerRequest = { requestId, payload };
|
||||
const promise = new Promise<BattleSimResultPayload>((resolve, reject) => {
|
||||
this.pending.set(requestId, { resolve, reject });
|
||||
});
|
||||
this.ensureWorker().postMessage(request);
|
||||
return promise;
|
||||
}
|
||||
|
||||
private disposeWorker(): void {
|
||||
if (!this.worker) {
|
||||
return;
|
||||
}
|
||||
this.worker.removeEventListener('message', this.handleMessage);
|
||||
this.worker.removeEventListener('error', this.handleWorkerError);
|
||||
this.worker.terminate();
|
||||
this.worker = null;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
const error = new Error('전투 시뮬레이션이 취소되었습니다.');
|
||||
for (const pending of this.pending.values()) {
|
||||
pending.reject(error);
|
||||
}
|
||||
this.pending.clear();
|
||||
this.disposeWorker();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { BattleSimJobPayload, BattleSimResultPayload } from '@sammo-ts/logic';
|
||||
|
||||
export interface BattleSimulatorWorkerRequest {
|
||||
requestId: number;
|
||||
payload: BattleSimJobPayload;
|
||||
}
|
||||
|
||||
export type BattleSimulatorWorkerResponse =
|
||||
| {
|
||||
requestId: number;
|
||||
ok: true;
|
||||
result: BattleSimResultPayload;
|
||||
}
|
||||
| {
|
||||
requestId: number;
|
||||
ok: false;
|
||||
error: string;
|
||||
};
|
||||
Reference in New Issue
Block a user