refactor: 턴 경계와 12턴 묶음을 보존하는 2배속 복구 구현
This commit is contained in:
@@ -145,6 +145,7 @@ model TurnDaemonLease {
|
||||
ownerId String @map("owner_id")
|
||||
leaseUntil DateTime @map("lease_until")
|
||||
fencingEpoch BigInt @default(1) @map("fencing_epoch")
|
||||
clockReady Boolean @default(false) @map("clock_ready")
|
||||
heartbeatAt DateTime @default(now()) @map("heartbeat_at")
|
||||
|
||||
@@map("turn_daemon_lease")
|
||||
@@ -160,6 +161,9 @@ model WorldState {
|
||||
clockTick BigInt? @map("clock_tick")
|
||||
clockMode String @default("realtime") @map("clock_mode")
|
||||
clockWallAnchor DateTime? @map("clock_wall_anchor")
|
||||
clockRecoveryStartTick BigInt? @map("clock_recovery_start_tick")
|
||||
clockRecoveryEndTick BigInt? @map("clock_recovery_end_tick")
|
||||
clockRecoveryStartWallAt DateTime? @map("clock_recovery_start_wall_at")
|
||||
lastTurnTick BigInt? @map("last_turn_tick")
|
||||
clockPhase String @default("RUNNING") @map("clock_phase")
|
||||
clockRevision BigInt @default(1) @map("clock_revision")
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
ALTER TABLE "turn_daemon_lease" ADD COLUMN "clock_ready" BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
ALTER TABLE "world_state"
|
||||
ADD COLUMN "clock_recovery_start_tick" BIGINT,
|
||||
ADD COLUMN "clock_recovery_end_tick" BIGINT,
|
||||
ADD COLUMN "clock_recovery_start_wall_at" TIMESTAMP(3),
|
||||
ADD CONSTRAINT "world_state_turn_recovery_window_check" CHECK (
|
||||
("clock_recovery_start_tick" IS NULL AND "clock_recovery_end_tick" IS NULL AND "clock_recovery_start_wall_at" IS NULL)
|
||||
OR (
|
||||
"clock_recovery_start_tick" IS NOT NULL AND "clock_recovery_end_tick" IS NOT NULL AND "clock_recovery_start_wall_at" IS NOT NULL
|
||||
AND "clock_recovery_start_tick" BETWEEN -9007199254740991 AND 9007199254740991
|
||||
AND "clock_recovery_end_tick" BETWEEN -9007199254740991 AND 9007199254740991
|
||||
AND "clock_recovery_start_tick" % 36000000 = 0
|
||||
AND "clock_recovery_end_tick" % 36000000 = 0
|
||||
AND "clock_recovery_end_tick" - "clock_recovery_start_tick" BETWEEN 72000000 AND 792000000
|
||||
AND ("clock_recovery_end_tick" - "clock_recovery_start_tick") % 72000000 = 0
|
||||
)
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GameClock, inferClockPhase, parseGameClockPhase } from '@sammo-ts/common';
|
||||
import { GameClock, readTurnRecovery, inferClockPhase, parseGameClockPhase } from '@sammo-ts/common';
|
||||
|
||||
import { GamePrisma, type GamePrismaClient } from './gamePrisma.js';
|
||||
import { acquireGameSchemaAdvisoryXactLock, CLOCK_OPERATION_PERSISTENCE_LOCK } from './gameSchemaAdvisoryLock.js';
|
||||
@@ -9,6 +9,21 @@ interface DbWallRow {
|
||||
wallNow: Date;
|
||||
}
|
||||
|
||||
/** 새 daemon의 시계 복구가 끝나기 전에는 독립 worker가 시간을 진행하지 않는다. */
|
||||
export const readTurnRuntimeReady = async (
|
||||
db: Pick<GamePrismaClient, '$queryRaw'>,
|
||||
revision: bigint
|
||||
): Promise<boolean> => {
|
||||
const [row] = await db.$queryRaw<Array<{ ready: boolean }>>(GamePrisma.sql`
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM turn_daemon_lease, world_state
|
||||
WHERE clock_ready = TRUE AND lease_until > timezone('UTC', clock_timestamp())
|
||||
AND clock_revision = ${revision}
|
||||
) AS ready
|
||||
`);
|
||||
return row?.ready === true;
|
||||
};
|
||||
|
||||
export interface InputEventClockCoordinate {
|
||||
wallAt: Date;
|
||||
gameAt: Date;
|
||||
@@ -38,6 +53,9 @@ export const readInputEventClockCoordinate = async (
|
||||
clockTick: true,
|
||||
clockMode: true,
|
||||
clockWallAnchor: true,
|
||||
clockRecoveryStartTick: true,
|
||||
clockRecoveryEndTick: true,
|
||||
clockRecoveryStartWallAt: true,
|
||||
tickSeconds: true,
|
||||
clockPhase: true,
|
||||
clockRevision: true,
|
||||
@@ -60,11 +78,17 @@ export const readInputEventClockCoordinate = async (
|
||||
tick,
|
||||
mode,
|
||||
wallAnchor: world.clockWallAnchor,
|
||||
recovery: readTurnRecovery(world),
|
||||
turnSeconds: world.tickSeconds,
|
||||
phase,
|
||||
revision,
|
||||
});
|
||||
const observedTick = clock.nowTick(wall.wallNow);
|
||||
const ready =
|
||||
phase !== 'RUNNING' ||
|
||||
mode !== 'realtime' ||
|
||||
world.clockRecoveryStartTick === undefined ||
|
||||
(await readTurnRuntimeReady(db, world.clockRevision));
|
||||
const observedTick = ready ? clock.nowTick(wall.wallNow) : clock.tick;
|
||||
return {
|
||||
wallAt: wall.wallNow,
|
||||
gameAt: clock.tickToDate(observedTick),
|
||||
|
||||
@@ -15,6 +15,9 @@ export interface TurnEngineWorldStateRow {
|
||||
clockTick: bigint | null;
|
||||
clockMode: string;
|
||||
clockWallAnchor: Date | null;
|
||||
clockRecoveryStartTick?: bigint | null;
|
||||
clockRecoveryEndTick?: bigint | null;
|
||||
clockRecoveryStartWallAt?: Date | null;
|
||||
lastTurnTick: bigint | null;
|
||||
clockPhase: string;
|
||||
clockRevision: bigint;
|
||||
@@ -183,6 +186,9 @@ export interface TurnEngineWorldStateUpdateInput {
|
||||
clockTick: bigint;
|
||||
clockMode: string;
|
||||
clockWallAnchor: Date;
|
||||
clockRecoveryStartTick?: bigint | null;
|
||||
clockRecoveryEndTick?: bigint | null;
|
||||
clockRecoveryStartWallAt?: Date | null;
|
||||
lastTurnTick: bigint;
|
||||
clockPhase: string;
|
||||
clockRevision: bigint;
|
||||
@@ -200,6 +206,9 @@ export interface TurnEngineWorldStateCreateInput {
|
||||
clockTick: bigint;
|
||||
clockMode: string;
|
||||
clockWallAnchor: Date;
|
||||
clockRecoveryStartTick?: bigint | null;
|
||||
clockRecoveryEndTick?: bigint | null;
|
||||
clockRecoveryStartWallAt?: Date | null;
|
||||
lastTurnTick: bigint;
|
||||
clockPhase: string;
|
||||
clockRevision: bigint;
|
||||
|
||||
Reference in New Issue
Block a user