From cbea41ffbc89b467063e679959ff40d2c3490357 Mon Sep 17 00:00:00 2001 From: Hide_D Date: Sun, 28 Dec 2025 12:48:13 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EC=97=94=EC=A7=84?= =?UTF-8?q?=EC=97=90=20Clock=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EB=B0=8F=20=EA=B4=80=EB=A0=A8=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80,=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/game-engine/package.json | 3 + app/game-engine/src/lifecycle/clock.ts | 77 +---------------- app/game-engine/src/lifecycle/types.ts | 5 +- packages/common/package.json | 6 +- packages/common/src/index.ts | 1 + packages/common/src/time/Clock.ts | 86 +++++++++++++++++++ .../common}/test/clock.test.ts | 9 +- packages/common/vitest.config.ts | 9 ++ pnpm-lock.yaml | 8 ++ 9 files changed, 121 insertions(+), 83 deletions(-) create mode 100644 packages/common/src/time/Clock.ts rename {app/game-engine => packages/common}/test/clock.test.ts (85%) create mode 100644 packages/common/vitest.config.ts diff --git a/app/game-engine/package.json b/app/game-engine/package.json index 7dc04d5..f45acb5 100644 --- a/app/game-engine/package.json +++ b/app/game-engine/package.json @@ -9,6 +9,9 @@ "lint": "node -e \"console.log('lint not configured')\"", "test": "vitest run --config vitest.config.ts" }, + "dependencies": { + "@sammo-ts/common": "workspace:*" + }, "devDependencies": { "vitest": "^4.0.16" } diff --git a/app/game-engine/src/lifecycle/clock.ts b/app/game-engine/src/lifecycle/clock.ts index ce2c6b1..e29f259 100644 --- a/app/game-engine/src/lifecycle/clock.ts +++ b/app/game-engine/src/lifecycle/clock.ts @@ -1,76 +1 @@ -import type { Clock } from './types.js'; - -export class SystemClock implements Clock { - // 시스템 시간을 기준으로 동작하는 기본 시계. - nowMs(): number { - return Date.now(); - } - - async sleepMs(ms: number): Promise { - if (ms <= 0) { - return; - } - await new Promise((resolve) => setTimeout(resolve, ms)); - } -} - -export class ManualClock implements Clock { - // 테스트에서 시간을 직접 이동시키는 수동 시계. - private currentMs: number; - - constructor(initialMs = 0) { - this.currentMs = initialMs; - } - - nowMs(): number { - return this.currentMs; - } - - async sleepMs(ms: number): Promise { - if (ms <= 0) { - return; - } - this.currentMs += ms; - } - - advanceMs(ms: number): void { - if (ms <= 0) { - return; - } - this.currentMs += ms; - } - - setMs(ms: number): void { - this.currentMs = ms; - } -} - -export class StepClock implements Clock { - // 호출마다 일정 간격씩 시간이 진행되는 시계. - private currentMs: number; - private readonly stepMs: number; - - constructor(stepMs: number, initialMs = 0) { - if (stepMs <= 0) { - throw new Error('stepMs must be positive'); - } - this.stepMs = stepMs; - this.currentMs = initialMs; - } - - nowMs(): number { - this.currentMs += this.stepMs; - return this.currentMs; - } - - async sleepMs(ms: number): Promise { - if (ms <= 0) { - return; - } - this.currentMs += ms; - } - - setMs(ms: number): void { - this.currentMs = ms; - } -} +export { ManualClock, StepClock, SystemClock } from '@sammo-ts/common'; diff --git a/app/game-engine/src/lifecycle/types.ts b/app/game-engine/src/lifecycle/types.ts index 1f6b91c..b564d88 100644 --- a/app/game-engine/src/lifecycle/types.ts +++ b/app/game-engine/src/lifecycle/types.ts @@ -43,10 +43,7 @@ export type TurnDaemonCommand = | { type: 'resume'; reason?: string } | { type: 'shutdown'; reason?: string }; -export interface Clock { - nowMs(): number; - sleepMs(ms: number): Promise; -} +export type { Clock } from '@sammo-ts/common'; export interface TurnSchedule { getNextTurnTime(lastTurnTime: Date): Date; diff --git a/packages/common/package.json b/packages/common/package.json index c3f65ec..72cae07 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -9,10 +9,12 @@ "build": "tsc -p tsconfig.json", "dev": "node -e \"console.log('dev not configured')\"", "lint": "node -e \"console.log('lint not configured')\"", - "test": "node -e \"console.log('test not configured')\"" + "test": "vitest run --config vitest.config.ts" }, "dependencies": { "js-sha512": "^0.9.0" }, - "devDependencies": {} + "devDependencies": { + "vitest": "^4.0.16" + } } diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 4b4a672..bd23b9e 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,4 +1,5 @@ export * from './rng.js'; +export * from './time/Clock.js'; export * from './util/BytesLike.js'; export * from './util/convertBytesLikeToArrayBuffer.js'; export * from './util/convertBytesLikeToUint8Array.js'; diff --git a/packages/common/src/time/Clock.ts b/packages/common/src/time/Clock.ts new file mode 100644 index 0000000..d285bab --- /dev/null +++ b/packages/common/src/time/Clock.ts @@ -0,0 +1,86 @@ +export interface Clock { + nowMs(): number; + sleepMs(ms: number): Promise; +} + +export class SystemClock implements Clock { + // 시스템 시간을 기준으로 동작하는 기본 시계. + nowMs(): number { + return Date.now(); + } + + async sleepMs(ms: number): Promise { + if (ms <= 0) { + return; + } + await new Promise((resolve) => setTimeout(resolve, ms)); + } +} + +export class ManualClock implements Clock { + // 테스트에서 시간을 직접 이동시키는 수동 시계. + private currentMs: number; + + constructor(initialMs = 0) { + this.currentMs = initialMs; + } + + nowMs(): number { + return this.currentMs; + } + + async sleepMs(ms: number): Promise { + if (ms <= 0) { + return; + } + this.currentMs += ms; + } + + advanceMs(ms: number): void { + if (ms <= 0) { + return; + } + this.currentMs += ms; + } + + setMs(ms: number): void { + this.currentMs = ms; + } +} + +export class StepClock implements Clock { + // 호출마다 일정 간격씩 시간이 진행되는 시계. + private currentMs: number; + private readonly stepMs: number; + + constructor(stepMs: number, initialMs = 0) { + if (stepMs <= 0) { + throw new Error('stepMs must be positive'); + } + this.stepMs = stepMs; + this.currentMs = initialMs; + } + + nowMs(): number { + this.currentMs += this.stepMs; + return this.currentMs; + } + + async sleepMs(ms: number): Promise { + if (ms <= 0) { + return; + } + this.currentMs += ms; + } + + advanceMs(ms: number): void { + if (ms <= 0) { + return; + } + this.currentMs += ms; + } + + setMs(ms: number): void { + this.currentMs = ms; + } +} diff --git a/app/game-engine/test/clock.test.ts b/packages/common/test/clock.test.ts similarity index 85% rename from app/game-engine/test/clock.test.ts rename to packages/common/test/clock.test.ts index 0df36ec..7a7a8c5 100644 --- a/app/game-engine/test/clock.test.ts +++ b/packages/common/test/clock.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { ManualClock, StepClock } from '../src/lifecycle/clock.js'; + +import { ManualClock, StepClock } from '../src/time/Clock.js'; describe('ManualClock', () => { it('returns current time without advancing', () => { @@ -37,6 +38,12 @@ describe('StepClock', () => { expect(clock.nowMs()).toBe(1250); }); + it('advances manually', () => { + const clock = new StepClock(10, 0); + clock.advanceMs(50); + expect(clock.nowMs()).toBe(60); + }); + it('rejects non-positive step', () => { expect(() => new StepClock(0)).toThrow('stepMs must be positive'); expect(() => new StepClock(-5)).toThrow('stepMs must be positive'); diff --git a/packages/common/vitest.config.ts b/packages/common/vitest.config.ts new file mode 100644 index 0000000..5c63f1e --- /dev/null +++ b/packages/common/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + globals: true, + include: ['test/**/*.test.ts'], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3d322a..d912c28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,10 @@ importers: app/game-api: {} app/game-engine: + dependencies: + '@sammo-ts/common': + specifier: workspace:* + version: link:../../packages/common devDependencies: vitest: specifier: ^4.0.16 @@ -34,6 +38,10 @@ importers: js-sha512: specifier: ^0.9.0 version: 0.9.0 + devDependencies: + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@20.19.27) packages/infra: dependencies: