feat: 턴 데몬 명령 처리 및 결과 응답 기능 추가

This commit is contained in:
2026-01-04 16:05:22 +00:00
parent b2a625d4e7
commit 2753b85a26
12 changed files with 716 additions and 63 deletions
@@ -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);
}
}
+45
View File
@@ -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 });
+9 -1
View File
@@ -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>;
}
+35 -2
View File
@@ -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;