refactor: 턴 경계와 12턴 묶음을 보존하는 2배속 복구 구현
This commit is contained in:
@@ -29,6 +29,7 @@ const props = defineProps<{
|
||||
clockMode?: 'realtime' | 'manual';
|
||||
clockRunning?: boolean;
|
||||
clockStartsAt?: string | null;
|
||||
clockRecovery?: { startsAt: string; endsAt: string } | null;
|
||||
autorunLimit?: number | null;
|
||||
storageKey?: string;
|
||||
mapData?: CommandMapData | null;
|
||||
@@ -60,11 +61,7 @@ const labelMap = computed(() => {
|
||||
});
|
||||
|
||||
const firstReservedMonth = computed(
|
||||
() =>
|
||||
(props.currentYear ?? 0) * 12 +
|
||||
(props.currentMonth ?? 1) -
|
||||
1 +
|
||||
(props.general?.nextTurnMonthOffset ?? 0)
|
||||
() => (props.currentYear ?? 0) * 12 + (props.currentMonth ?? 1) - 1 + (props.general?.nextTurnMonthOffset ?? 0)
|
||||
);
|
||||
|
||||
const rows = computed<ReservedCommandRow[]>(() => {
|
||||
@@ -119,7 +116,7 @@ const updateServerClock = () => {
|
||||
currentServerTime.value = '--:--:--';
|
||||
return;
|
||||
}
|
||||
const { clientElapsedMs, time: projectedTime } = projectServerClock(serverClockSample);
|
||||
const { clientElapsedMs, time: projectedTime, rate } = projectServerClock(serverClockSample);
|
||||
currentServerTime.value = formatLocalTimeSeconds(projectedTime);
|
||||
if (serverClockSample.clockMode !== 'manual' && serverClockSample.startDelayMs !== null) {
|
||||
const untilStartMs = serverClockSample.startDelayMs - clientElapsedMs;
|
||||
@@ -127,15 +124,30 @@ const updateServerClock = () => {
|
||||
updateServerClock,
|
||||
untilStartMs > 0
|
||||
? Math.min(untilStartMs, MAX_SERVER_CLOCK_TIMER_DELAY_MS)
|
||||
: 1_000 - projectedTime.getMilliseconds()
|
||||
: (1_000 - projectedTime.getMilliseconds()) / rate
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [props.serverTime, props.serverWallTime, props.clockMode, props.clockRunning, props.clockStartsAt] as const,
|
||||
([serverTime, serverWallTime, clockMode, clockRunning, clockStartsAt]) => {
|
||||
serverClockSample = sampleServerClock({ serverTime, serverWallTime, clockMode, clockRunning, clockStartsAt });
|
||||
() =>
|
||||
[
|
||||
props.serverTime,
|
||||
props.serverWallTime,
|
||||
props.clockMode,
|
||||
props.clockRunning,
|
||||
props.clockStartsAt,
|
||||
props.clockRecovery,
|
||||
] as const,
|
||||
([serverTime, serverWallTime, clockMode, clockRunning, clockStartsAt, clockRecovery]) => {
|
||||
serverClockSample = sampleServerClock({
|
||||
serverTime,
|
||||
serverWallTime,
|
||||
clockMode,
|
||||
clockRunning,
|
||||
clockStartsAt,
|
||||
clockRecovery,
|
||||
});
|
||||
updateServerClock();
|
||||
},
|
||||
{ immediate: true }
|
||||
|
||||
@@ -16,6 +16,7 @@ const props = defineProps<{
|
||||
clockMode?: 'realtime' | 'manual';
|
||||
clockRunning?: boolean;
|
||||
clockStartsAt?: string | null;
|
||||
clockRecovery?: { startsAt: string; endsAt: string } | null;
|
||||
turnEngineRunning?: boolean | null;
|
||||
status: {
|
||||
onlineUserCount: number;
|
||||
@@ -34,6 +35,7 @@ const props = defineProps<{
|
||||
const tournamentStatus = computed(() => resolveTournamentStageName(props.tournamentStage));
|
||||
const currentServerTime = ref('기록 없음');
|
||||
const hasServerClock = ref(false);
|
||||
const recovering = ref(false);
|
||||
const turnEngineStopped = computed(() => props.turnEngineRunning === false);
|
||||
const turnEngineStatusUnknown = computed(() => typeof props.turnEngineRunning !== 'boolean');
|
||||
const serverClockTitle = computed(() => {
|
||||
@@ -57,6 +59,7 @@ const updateServerClock = () => {
|
||||
|
||||
const now = Date.now();
|
||||
const projection = projectServerClock(serverClockSample, now);
|
||||
recovering.value = projection.rate === 2;
|
||||
currentServerTime.value = formatServerDateTime(projection.time, {
|
||||
format: 'monthDayTime',
|
||||
fallback: '기록 없음',
|
||||
@@ -67,16 +70,37 @@ const updateServerClock = () => {
|
||||
const nextDelays: number[] = [];
|
||||
if (serverClockSample.clockMode !== 'manual' && serverClockSample.startDelayMs !== null) {
|
||||
const untilStartMs = serverClockSample.startDelayMs - projection.clientElapsedMs;
|
||||
nextDelays.push(untilStartMs > 0 ? untilStartMs : millisecondsUntilNextMinute(projection.time));
|
||||
nextDelays.push(
|
||||
untilStartMs > 0 ? untilStartMs : millisecondsUntilNextMinute(projection.time) / projection.rate
|
||||
);
|
||||
}
|
||||
for (const boundary of [serverClockSample.recoveryStartDelayMs, serverClockSample.recoveryEndDelayMs]) {
|
||||
if (boundary !== undefined && boundary > projection.clientElapsedMs)
|
||||
nextDelays.push(boundary - projection.clientElapsedMs);
|
||||
}
|
||||
if (nextDelays.length === 0) return;
|
||||
serverClockTimer = setTimeout(updateServerClock, Math.max(1, Math.min(...nextDelays)));
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [props.serverTime, props.serverWallTime, props.clockMode, props.clockRunning, props.clockStartsAt] as const,
|
||||
([serverTime, serverWallTime, clockMode, clockRunning, clockStartsAt]) => {
|
||||
serverClockSample = sampleServerClock({ serverTime, serverWallTime, clockMode, clockRunning, clockStartsAt });
|
||||
() =>
|
||||
[
|
||||
props.serverTime,
|
||||
props.serverWallTime,
|
||||
props.clockMode,
|
||||
props.clockRunning,
|
||||
props.clockStartsAt,
|
||||
props.clockRecovery,
|
||||
] as const,
|
||||
([serverTime, serverWallTime, clockMode, clockRunning, clockStartsAt, clockRecovery]) => {
|
||||
serverClockSample = sampleServerClock({
|
||||
serverTime,
|
||||
serverWallTime,
|
||||
clockMode,
|
||||
clockRunning,
|
||||
clockStartsAt,
|
||||
clockRecovery,
|
||||
});
|
||||
updateServerClock();
|
||||
},
|
||||
{ immediate: true }
|
||||
@@ -100,7 +124,7 @@ onUnmounted(() => {
|
||||
}"
|
||||
:title="serverClockTitle"
|
||||
>
|
||||
현재 시각: {{ currentServerTime }}
|
||||
현재 시각: {{ currentServerTime }}<span v-if="recovering"> · 복구 2배속</span>
|
||||
</div>
|
||||
<div class="status-row tournament-status">
|
||||
<RouterLink to="/tournament">
|
||||
|
||||
@@ -4,6 +4,7 @@ export type ServerClockProjectionInput = {
|
||||
clockMode?: 'realtime' | 'manual';
|
||||
clockRunning?: boolean;
|
||||
clockStartsAt?: string | null;
|
||||
clockRecovery?: { startsAt: string; endsAt: string } | null;
|
||||
};
|
||||
|
||||
export type SampledServerClock = {
|
||||
@@ -11,6 +12,8 @@ export type SampledServerClock = {
|
||||
sampledClientTimeMs: number;
|
||||
clockMode: 'realtime' | 'manual';
|
||||
startDelayMs: number | null;
|
||||
recoveryStartDelayMs?: number;
|
||||
recoveryEndDelayMs?: number;
|
||||
};
|
||||
|
||||
const parseInstant = (value?: string | null): number | null => {
|
||||
@@ -40,11 +43,20 @@ export const sampleServerClock = (
|
||||
: null;
|
||||
}
|
||||
|
||||
const wallSample = parseInstant(input.serverWallTime);
|
||||
const recoveryStart = parseInstant(input.clockRecovery?.startsAt);
|
||||
const recoveryEnd = parseInstant(input.clockRecovery?.endsAt);
|
||||
return {
|
||||
serverTimeMs,
|
||||
sampledClientTimeMs,
|
||||
clockMode: input.clockMode ?? 'realtime',
|
||||
startDelayMs,
|
||||
...(wallSample !== null && recoveryStart !== null && recoveryEnd !== null && recoveryEnd > recoveryStart
|
||||
? {
|
||||
recoveryStartDelayMs: recoveryStart - wallSample,
|
||||
recoveryEndDelayMs: recoveryEnd - wallSample,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -55,9 +67,25 @@ export const projectServerClock = (sample: SampledServerClock, clientTimeMs = Da
|
||||
? 0
|
||||
: Math.max(0, clientElapsedMs - sample.startDelayMs);
|
||||
|
||||
const accelerationMs =
|
||||
sample.startDelayMs === null || sample.clockMode === 'manual'
|
||||
? 0
|
||||
: Math.max(
|
||||
0,
|
||||
Math.min(clientElapsedMs, sample.recoveryEndDelayMs ?? 0) -
|
||||
Math.max(0, sample.recoveryStartDelayMs ?? 0)
|
||||
);
|
||||
const rate =
|
||||
sample.startDelayMs !== null &&
|
||||
sample.clockMode !== 'manual' &&
|
||||
clientElapsedMs >= (sample.recoveryStartDelayMs ?? Infinity) &&
|
||||
clientElapsedMs < (sample.recoveryEndDelayMs ?? -Infinity)
|
||||
? 2
|
||||
: 1;
|
||||
return {
|
||||
clientElapsedMs,
|
||||
time: new Date(sample.serverTimeMs + elapsedGameMs),
|
||||
rate,
|
||||
time: new Date(sample.serverTimeMs + elapsedGameMs + accelerationMs),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -311,6 +311,7 @@ watch(
|
||||
:clock-mode="lobbyInfo?.clockMode"
|
||||
:clock-running="lobbyInfo?.clockRunning"
|
||||
:clock-starts-at="lobbyInfo?.clockStartsAt"
|
||||
:clock-recovery="lobbyInfo?.clockRecovery"
|
||||
:turn-engine-running="lobbyInfo?.turnEngineRunning"
|
||||
/>
|
||||
</div>
|
||||
@@ -365,6 +366,7 @@ watch(
|
||||
:clock-mode="lobbyInfo?.clockMode"
|
||||
:clock-running="lobbyInfo?.clockRunning"
|
||||
:clock-starts-at="lobbyInfo?.clockStartsAt"
|
||||
:clock-recovery="lobbyInfo?.clockRecovery"
|
||||
:autorun-limit="reservedGeneralAutorunLimit"
|
||||
:map-data="worldMap"
|
||||
:map-layout="mapLayout"
|
||||
@@ -547,6 +549,7 @@ watch(
|
||||
:clock-mode="lobbyInfo?.clockMode"
|
||||
:clock-running="lobbyInfo?.clockRunning"
|
||||
:clock-starts-at="lobbyInfo?.clockStartsAt"
|
||||
:clock-recovery="lobbyInfo?.clockRecovery"
|
||||
:autorun-limit="reservedGeneralAutorunLimit"
|
||||
:map-data="worldMap"
|
||||
:map-layout="mapLayout"
|
||||
|
||||
Reference in New Issue
Block a user