diff --git a/app/game-api/src/daemon/inMemoryTransport.ts b/app/game-api/src/daemon/inMemoryTransport.ts index d41af63..9367061 100644 --- a/app/game-api/src/daemon/inMemoryTransport.ts +++ b/app/game-api/src/daemon/inMemoryTransport.ts @@ -27,7 +27,10 @@ export class InMemoryTurnDaemonTransport implements TurnDaemonTransport { // 테스트용: 메모리 큐에 명령을 저장하고 requestId를 반환한다. async sendCommand(command: TurnDaemonCommand): Promise { - const requestId = command.type === 'getStatus' ? command.requestId : randomUUID(); + const requestId = + command.type === 'getStatus' && command.requestId + ? command.requestId + : randomUUID(); this.commands.push({ requestId, sentAt: new Date().toISOString(), diff --git a/app/game-api/src/daemon/redisTransport.ts b/app/game-api/src/daemon/redisTransport.ts index e31ffb4..7e86e52 100644 --- a/app/game-api/src/daemon/redisTransport.ts +++ b/app/game-api/src/daemon/redisTransport.ts @@ -29,7 +29,10 @@ type RedisStreamReadResponse = Array<{ }>; const buildCommandEnvelope = (command: TurnDaemonCommand): TurnDaemonCommandEnvelope => { - const requestId = command.type === 'getStatus' ? command.requestId : randomUUID(); + const requestId = + command.type === 'getStatus' && command.requestId + ? command.requestId + : randomUUID(); return { requestId, sentAt: new Date().toISOString(), diff --git a/app/game-api/src/daemon/types.ts b/app/game-api/src/daemon/types.ts index 4aaa83d..43a4d11 100644 --- a/app/game-api/src/daemon/types.ts +++ b/app/game-api/src/daemon/types.ts @@ -1,89 +1,16 @@ -export type TurnDaemonState = 'idle' | 'running' | 'flushing' | 'paused' | 'stopping'; +import type { TurnDaemonCommand, TurnDaemonEvent } from '@sammo-ts/common'; -export type RunReason = 'schedule' | 'manual' | 'poke'; - -export interface TurnRunBudget { - budgetMs: number; - maxGenerals: number; - catchUpCap: number; -} - -export interface TurnCheckpoint { - turnTime: string; - generalId?: number; - year: number; - month: number; -} - -export interface TurnRunResult { - lastTurnTime: string; - processedGenerals: number; - processedTurns: number; - durationMs: number; - partial: boolean; - checkpoint?: TurnCheckpoint; -} - -export interface TurnDaemonStatus { - state: TurnDaemonState; - running: boolean; - paused: boolean; - lastError?: string; - lastRunAt?: string; - lastDurationMs?: number; - lastTurnTime?: string; - nextTurnTime?: string; - pendingReason?: RunReason; - queueDepth: number; - 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 } - | 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: 'commandResult'; result: TurnDaemonCommandResult }; +export type { + RunReason, + TurnCheckpoint, + TurnDaemonCommand, + TurnDaemonCommandResult, + TurnDaemonEvent, + TurnDaemonState, + TurnDaemonStatus, + TurnRunBudget, + TurnRunResult, +} from '@sammo-ts/common'; export interface TurnDaemonCommandEnvelope { requestId: string; diff --git a/app/game-engine/src/lifecycle/turnDaemonLifecycle.ts b/app/game-engine/src/lifecycle/turnDaemonLifecycle.ts index 6adb0c4..00e6f82 100644 --- a/app/game-engine/src/lifecycle/turnDaemonLifecycle.ts +++ b/app/game-engine/src/lifecycle/turnDaemonLifecycle.ts @@ -228,10 +228,12 @@ export class TurnDaemonLifecycle { this.stopping = true; return; case 'getStatus': { - await this.commandResponder?.publishStatus( - command.requestId, - this.getStatus() - ); + if (command.requestId) { + await this.commandResponder?.publishStatus( + command.requestId, + this.getStatus() + ); + } return; } case 'run': @@ -285,7 +287,7 @@ export class TurnDaemonLifecycle { } as TurnDaemonCommandResult; } - if (this.commandResponder) { + if (this.commandResponder && command.requestId) { await this.commandResponder.publishCommandResult( command.requestId, result diff --git a/app/game-engine/src/lifecycle/types.ts b/app/game-engine/src/lifecycle/types.ts index 6c7d164..0170493 100644 --- a/app/game-engine/src/lifecycle/types.ts +++ b/app/game-engine/src/lifecycle/types.ts @@ -1,78 +1,22 @@ -export type TurnDaemonState = 'idle' | 'running' | 'flushing' | 'paused' | 'stopping'; +import type { + TurnCheckpoint, + TurnDaemonCommand, + TurnDaemonCommandResult, + TurnDaemonStatus, + TurnRunBudget, + TurnRunResult, +} from '@sammo-ts/common'; -export type RunReason = 'schedule' | 'manual' | 'poke'; - -export interface TurnRunBudget { - budgetMs: number; - maxGenerals: number; - catchUpCap: number; -} - -export interface TurnCheckpoint { - turnTime: string; - generalId?: number; - year: number; - month: number; -} - -export interface TurnRunResult { - lastTurnTime: string; - processedGenerals: number; - processedTurns: number; - durationMs: number; - partial: boolean; - checkpoint?: TurnCheckpoint; -} - -export interface TurnDaemonStatus { - state: TurnDaemonState; - running: boolean; - paused: boolean; - lastError?: string; - lastRunAt?: string; - lastDurationMs?: number; - lastTurnTime?: string; - nextTurnTime?: string; - pendingReason?: RunReason; - queueDepth: number; - checkpoint?: TurnCheckpoint; -} - -export type TurnDaemonCommand = - | { type: 'run'; reason: RunReason; targetTime?: string; budget?: TurnRunBudget } - | { type: 'pause'; reason?: string } - | { type: 'resume'; reason?: string } - | { type: 'shutdown'; reason?: string } - | { type: 'getStatus'; requestId: string } - | { type: 'troopJoin'; requestId: string; generalId: number; troopId: number } - | { type: 'troopExit'; requestId: string; generalId: number }; - -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; - }; +export type { + RunReason, + TurnCheckpoint, + TurnDaemonCommand, + TurnDaemonCommandResult, + TurnDaemonState, + TurnDaemonStatus, + TurnRunBudget, + TurnRunResult, +} from '@sammo-ts/common'; export interface TurnDaemonCommandHandler { handle(command: TurnDaemonCommand): Promise; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 1eff8dd..739565b 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -10,3 +10,4 @@ export * from './util/RNG.js'; export * from './util/RandUtil.js'; export * from './util/TestRNG.js'; export * from './util/sha512.js'; +export * from './turnDaemon/types.js'; diff --git a/packages/common/src/turnDaemon/types.ts b/packages/common/src/turnDaemon/types.ts new file mode 100644 index 0000000..ef3b71b --- /dev/null +++ b/packages/common/src/turnDaemon/types.ts @@ -0,0 +1,92 @@ +export type TurnDaemonState = + | 'idle' + | 'running' + | 'flushing' + | 'paused' + | 'stopping'; + +export type RunReason = 'schedule' | 'manual' | 'poke'; + +export interface TurnRunBudget { + budgetMs: number; + maxGenerals: number; + catchUpCap: number; +} + +export interface TurnCheckpoint { + turnTime: string; + generalId?: number; + year: number; + month: number; +} + +export interface TurnRunResult { + lastTurnTime: string; + processedGenerals: number; + processedTurns: number; + durationMs: number; + partial: boolean; + checkpoint?: TurnCheckpoint; +} + +export interface TurnDaemonStatus { + state: TurnDaemonState; + running: boolean; + paused: boolean; + lastError?: string; + lastRunAt?: string; + lastDurationMs?: number; + lastTurnTime?: string; + nextTurnTime?: string; + pendingReason?: RunReason; + queueDepth: number; + checkpoint?: TurnCheckpoint; +} + +export type TurnDaemonCommand = + | { + type: 'run'; + reason: RunReason; + targetTime?: string; + budget?: TurnRunBudget; + } + | { type: 'pause'; reason?: string } + | { type: 'resume'; reason?: string } + | { type: 'shutdown'; reason?: string } + | { type: 'getStatus'; requestId?: string } + | { type: 'troopJoin'; requestId?: string; generalId: number; troopId: number } + | { type: 'troopExit'; requestId?: string; generalId: number }; + +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; + }; + +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: 'commandResult'; result: TurnDaemonCommandResult };