feat: 턴 데몬 명령 처리 및 결과 응답 기능 추가
This commit is contained in:
@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto';
|
||||
import type {
|
||||
TurnDaemonCommand,
|
||||
TurnDaemonCommandEnvelope,
|
||||
TurnDaemonCommandResult,
|
||||
TurnDaemonStatus,
|
||||
} from './types.js';
|
||||
import type { TurnDaemonTransport } from './transport.js';
|
||||
@@ -18,6 +19,7 @@ const buildDefaultStatus = (): TurnDaemonStatus => ({
|
||||
export class InMemoryTurnDaemonTransport implements TurnDaemonTransport {
|
||||
public readonly commands: TurnDaemonCommandEnvelope[] = [];
|
||||
private status: TurnDaemonStatus;
|
||||
private readonly results = new Map<string, TurnDaemonCommandResult>();
|
||||
|
||||
constructor(initialStatus: TurnDaemonStatus = buildDefaultStatus()) {
|
||||
this.status = initialStatus;
|
||||
@@ -34,6 +36,14 @@ export class InMemoryTurnDaemonTransport implements TurnDaemonTransport {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
async requestCommand(
|
||||
command: TurnDaemonCommand,
|
||||
_timeoutMs?: number
|
||||
): Promise<TurnDaemonCommandResult | null> {
|
||||
const requestId = await this.sendCommand(command);
|
||||
return this.results.get(requestId) ?? null;
|
||||
}
|
||||
|
||||
async requestStatus(): Promise<TurnDaemonStatus> {
|
||||
return this.status;
|
||||
}
|
||||
@@ -41,4 +51,8 @@ export class InMemoryTurnDaemonTransport implements TurnDaemonTransport {
|
||||
setStatus(status: TurnDaemonStatus): void {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
setCommandResult(requestId: string, result: TurnDaemonCommandResult): void {
|
||||
this.results.set(requestId, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { TurnDaemonTransport } from './transport.js';
|
||||
import type {
|
||||
TurnDaemonCommand,
|
||||
TurnDaemonCommandEnvelope,
|
||||
TurnDaemonCommandResult,
|
||||
TurnDaemonEventEnvelope,
|
||||
TurnDaemonStatus,
|
||||
} from './types.js';
|
||||
@@ -75,6 +76,50 @@ export class RedisTurnDaemonTransport implements TurnDaemonTransport {
|
||||
return envelope.requestId;
|
||||
}
|
||||
|
||||
async requestCommand(
|
||||
command: TurnDaemonCommand,
|
||||
timeoutMs?: number
|
||||
): Promise<TurnDaemonCommandResult | null> {
|
||||
const requestId = await this.sendCommand(command);
|
||||
|
||||
const deadline = Date.now() + (timeoutMs ?? this.requestTimeoutMs);
|
||||
let lastId = '$';
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
const remaining = Math.max(1, deadline - Date.now());
|
||||
const response = (await this.client.xRead(
|
||||
{ key: this.keys.eventStream, id: lastId },
|
||||
{ BLOCK: remaining, COUNT: 10 }
|
||||
)) as RedisStreamReadResponse | null;
|
||||
|
||||
if (!response) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const stream of response) {
|
||||
for (const message of stream.messages) {
|
||||
lastId = message.id;
|
||||
const payload = message.message.payload;
|
||||
if (!payload) {
|
||||
continue;
|
||||
}
|
||||
const envelope = parseEventEnvelope(payload);
|
||||
if (!envelope) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
envelope.event.type === 'commandResult' &&
|
||||
envelope.requestId === requestId
|
||||
) {
|
||||
return envelope.event.result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async requestStatus(timeoutMs?: number): Promise<TurnDaemonStatus | null> {
|
||||
const requestId = randomUUID();
|
||||
await this.sendCommand({ type: 'getStatus', requestId });
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import type { TurnDaemonCommand, TurnDaemonStatus } from './types.js';
|
||||
import type {
|
||||
TurnDaemonCommand,
|
||||
TurnDaemonCommandResult,
|
||||
TurnDaemonStatus,
|
||||
} from './types.js';
|
||||
|
||||
export interface TurnDaemonTransport {
|
||||
sendCommand(command: TurnDaemonCommand): Promise<string>;
|
||||
requestCommand(
|
||||
command: TurnDaemonCommand,
|
||||
timeoutMs?: number
|
||||
): Promise<TurnDaemonCommandResult | null>;
|
||||
requestStatus(timeoutMs?: number): Promise<TurnDaemonStatus | null>;
|
||||
}
|
||||
|
||||
@@ -38,19 +38,52 @@ export interface TurnDaemonStatus {
|
||||
checkpoint?: TurnCheckpoint;
|
||||
}
|
||||
|
||||
export type TurnDaemonMutationCommand =
|
||||
| { type: 'troopJoin'; generalId: number; troopId: number }
|
||||
| { type: 'troopExit'; generalId: number };
|
||||
|
||||
// 턴 데몬 제어 요청은 Redis 스트림으로 전달한다.
|
||||
export type TurnDaemonCommand =
|
||||
| { type: 'run'; reason: RunReason; targetTime?: string; budget?: TurnRunBudget }
|
||||
| { type: 'pause'; reason?: string }
|
||||
| { type: 'resume'; reason?: string }
|
||||
| { type: 'getStatus'; requestId: string };
|
||||
| { type: 'getStatus'; requestId: string }
|
||||
| TurnDaemonMutationCommand;
|
||||
|
||||
export type TurnDaemonCommandResult =
|
||||
| {
|
||||
type: 'troopJoin';
|
||||
ok: true;
|
||||
generalId: number;
|
||||
troopId: number;
|
||||
}
|
||||
| {
|
||||
type: 'troopJoin';
|
||||
ok: false;
|
||||
generalId: number;
|
||||
troopId: number;
|
||||
reason: string;
|
||||
}
|
||||
| {
|
||||
type: 'troopExit';
|
||||
ok: true;
|
||||
generalId: number;
|
||||
wasLeader: boolean;
|
||||
}
|
||||
| {
|
||||
type: 'troopExit';
|
||||
ok: false;
|
||||
generalId: number;
|
||||
reason: string;
|
||||
};
|
||||
|
||||
// 턴 데몬 이벤트는 상태/실행 결과를 API 서버에 알려준다.
|
||||
export type TurnDaemonEvent =
|
||||
| { type: 'status'; requestId?: string; status: TurnDaemonStatus }
|
||||
| { type: 'runStarted'; at: string; reason: RunReason }
|
||||
| { type: 'runCompleted'; at: string; result: TurnRunResult }
|
||||
| { type: 'runFailed'; at: string; error: string };
|
||||
| { type: 'runFailed'; at: string; error: string }
|
||||
| { type: 'commandResult'; result: TurnDaemonCommandResult };
|
||||
|
||||
export interface TurnDaemonCommandEnvelope {
|
||||
requestId: string;
|
||||
|
||||
Reference in New Issue
Block a user