feat: 메인 현재 시각을 최근 통신 기준으로 갱신
완료 턴 시각 대신 lobby serverTime을 분 경계에 맞춰 표시한다. 게임 API 응답과 SSE 및 같은 계정 탭 전달을 최근 통신으로 기록하고, 45초 공백에는 시계를 멈춘다. 공통 시계 투영과 단위 및 Chromium 회귀 검증을 추가한다.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
export type ServerClockProjectionInput = {
|
||||
serverTime?: string;
|
||||
serverWallTime?: string;
|
||||
clockMode?: 'realtime' | 'manual';
|
||||
clockRunning?: boolean;
|
||||
clockStartsAt?: string | null;
|
||||
};
|
||||
|
||||
export type SampledServerClock = {
|
||||
serverTimeMs: number;
|
||||
sampledClientTimeMs: number;
|
||||
clockMode: 'realtime' | 'manual';
|
||||
startDelayMs: number | null;
|
||||
};
|
||||
|
||||
const parseInstant = (value?: string | null): number | null => {
|
||||
if (!value) return null;
|
||||
const parsed = new Date(value).getTime();
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
|
||||
export const sampleServerClock = (
|
||||
input: ServerClockProjectionInput,
|
||||
sampledClientTimeMs = Date.now()
|
||||
): SampledServerClock | null => {
|
||||
const serverTimeMs = parseInstant(input.serverTime);
|
||||
if (serverTimeMs === null) return null;
|
||||
|
||||
let startDelayMs: number | null;
|
||||
if (input.clockMode === 'manual') {
|
||||
startDelayMs = null;
|
||||
} else if (input.clockRunning !== false) {
|
||||
startDelayMs = 0;
|
||||
} else {
|
||||
const serverWallTimeMs = parseInstant(input.serverWallTime);
|
||||
const clockStartsAtMs = parseInstant(input.clockStartsAt);
|
||||
startDelayMs =
|
||||
serverWallTimeMs !== null && clockStartsAtMs !== null
|
||||
? Math.max(0, clockStartsAtMs - serverWallTimeMs)
|
||||
: null;
|
||||
}
|
||||
|
||||
return {
|
||||
serverTimeMs,
|
||||
sampledClientTimeMs,
|
||||
clockMode: input.clockMode ?? 'realtime',
|
||||
startDelayMs,
|
||||
};
|
||||
};
|
||||
|
||||
export const projectServerClock = (sample: SampledServerClock, clientTimeMs = Date.now()) => {
|
||||
const clientElapsedMs = Math.max(0, clientTimeMs - sample.sampledClientTimeMs);
|
||||
const elapsedGameMs =
|
||||
sample.clockMode === 'manual' || sample.startDelayMs === null
|
||||
? 0
|
||||
: Math.max(0, clientElapsedMs - sample.startDelayMs);
|
||||
|
||||
return {
|
||||
clientElapsedMs,
|
||||
time: new Date(sample.serverTimeMs + elapsedGameMs),
|
||||
};
|
||||
};
|
||||
|
||||
export const millisecondsUntilNextMinute = (time: Date): number => {
|
||||
const remainder = ((time.getTime() % 60_000) + 60_000) % 60_000;
|
||||
return remainder === 0 ? 60_000 : 60_000 - remainder;
|
||||
};
|
||||
Reference in New Issue
Block a user