feat: 턴 다이아몬드 CLI 추가 및 상태 관리 개선
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import type { TurnSchedule } from '@sammo-ts/logic';
|
||||
|
||||
import type { TurnRunBudget } from '../lifecycle/types.js';
|
||||
import { resolveDatabaseUrl } from '../scenario/databaseUrl.js';
|
||||
import { createTurnDaemonRuntime } from './turnDaemon.js';
|
||||
|
||||
export interface TurnDaemonCliOptions {
|
||||
profile?: string;
|
||||
databaseUrl?: string;
|
||||
tickMinutes?: number;
|
||||
schedule?: TurnSchedule;
|
||||
budget?: Partial<TurnRunBudget>;
|
||||
enableDatabaseFlush?: boolean;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}
|
||||
|
||||
const DEFAULT_BUDGET: TurnRunBudget = {
|
||||
budgetMs: 5000,
|
||||
maxGenerals: 200,
|
||||
catchUpCap: 1,
|
||||
};
|
||||
|
||||
const parseNumber = (value: string | undefined): number | undefined => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return undefined;
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
const parseBoolean = (value: string | undefined): boolean | undefined => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const buildBudgetOverride = (
|
||||
env: NodeJS.ProcessEnv,
|
||||
override?: Partial<TurnRunBudget>
|
||||
): TurnRunBudget | undefined => {
|
||||
const budgetOverride: Partial<TurnRunBudget> = {
|
||||
budgetMs: parseNumber(env.TURN_BUDGET_MS),
|
||||
maxGenerals: parseNumber(env.TURN_MAX_GENERALS),
|
||||
catchUpCap: parseNumber(env.TURN_CATCH_UP_CAP),
|
||||
...override,
|
||||
};
|
||||
|
||||
const hasOverride = Object.values(budgetOverride).some(
|
||||
(value) => value !== undefined
|
||||
);
|
||||
if (!hasOverride) {
|
||||
return undefined;
|
||||
}
|
||||
return { ...DEFAULT_BUDGET, ...budgetOverride };
|
||||
};
|
||||
|
||||
export const runTurnDaemonCli = async (
|
||||
options: TurnDaemonCliOptions = {}
|
||||
): Promise<void> => {
|
||||
const env = options.env ?? process.env;
|
||||
const profile =
|
||||
options.profile ?? env.TURN_PROFILE ?? env.PROFILE ?? 'che';
|
||||
const databaseUrl =
|
||||
options.databaseUrl ?? (await resolveDatabaseUrl({ env }));
|
||||
const budget = buildBudgetOverride(env, options.budget);
|
||||
const tickMinutes =
|
||||
options.tickMinutes ?? parseNumber(env.TURN_TICK_MINUTES);
|
||||
const enableDatabaseFlush =
|
||||
options.enableDatabaseFlush ??
|
||||
parseBoolean(env.TURN_FLUSH_DB) ??
|
||||
true;
|
||||
|
||||
const runtime = await createTurnDaemonRuntime({
|
||||
profile,
|
||||
databaseUrl,
|
||||
defaultBudget: budget,
|
||||
tickMinutes,
|
||||
schedule: options.schedule,
|
||||
enableDatabaseFlush,
|
||||
});
|
||||
|
||||
let closed = false;
|
||||
const closeOnce = async (): Promise<void> => {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
await runtime.close();
|
||||
};
|
||||
|
||||
let stopping = false;
|
||||
const stop = async (reason: string): Promise<void> => {
|
||||
if (stopping) {
|
||||
return;
|
||||
}
|
||||
stopping = true;
|
||||
console.info(`[turn-daemon] stopping: ${reason}`);
|
||||
await runtime.lifecycle.stop(reason);
|
||||
await closeOnce();
|
||||
};
|
||||
|
||||
process.on('SIGINT', () => void stop('SIGINT'));
|
||||
process.on('SIGTERM', () => void stop('SIGTERM'));
|
||||
|
||||
const activeTickMinutes =
|
||||
tickMinutes ??
|
||||
Math.max(1, Math.round(runtime.world.getState().tickSeconds / 60));
|
||||
console.info(
|
||||
`[turn-daemon] started profile=${profile} tickMinutes=${activeTickMinutes}`
|
||||
);
|
||||
|
||||
try {
|
||||
await runtime.lifecycle.start();
|
||||
} finally {
|
||||
await closeOnce();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user