refactor engine state transactions
This commit is contained in:
@@ -17,6 +17,7 @@ export * from './scenario/scenarioSeeder.js';
|
||||
export * from './turn/types.js';
|
||||
export * from './turn/worldLoader.js';
|
||||
export * from './turn/inMemoryWorld.js';
|
||||
export * from './turn/engineStateManager.js';
|
||||
export * from './turn/inMemoryStateStore.js';
|
||||
export * from './turn/inMemoryTurnProcessor.js';
|
||||
export * from './turn/databaseHooks.js';
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
TurnDaemonCommandResult,
|
||||
TurnDaemonCommandExecutionContext,
|
||||
} from './types.js';
|
||||
import type { EngineStateManager } from '../turn/engineStateManager.js';
|
||||
|
||||
type PendingRun = {
|
||||
reason: RunReason;
|
||||
@@ -43,6 +44,7 @@ export interface TurnDaemonLifecycleDeps {
|
||||
commandHandler?: TurnDaemonCommandHandler;
|
||||
commandResponder?: TurnDaemonCommandResponder;
|
||||
pauseGate?: () => Promise<boolean>;
|
||||
stateManager?: Pick<EngineStateManager, 'transaction'>;
|
||||
}
|
||||
|
||||
export class TurnDaemonLifecycle {
|
||||
@@ -56,6 +58,7 @@ export class TurnDaemonLifecycle {
|
||||
private readonly commandHandler?: TurnDaemonCommandHandler;
|
||||
private readonly commandResponder?: TurnDaemonCommandResponder;
|
||||
private readonly pauseGate?: () => Promise<boolean>;
|
||||
private readonly stateManager?: Pick<EngineStateManager, 'transaction'>;
|
||||
private readonly options: TurnDaemonLifecycleOptions;
|
||||
|
||||
private status: TurnDaemonStatus;
|
||||
@@ -75,6 +78,7 @@ export class TurnDaemonLifecycle {
|
||||
this.commandHandler = deps.commandHandler;
|
||||
this.commandResponder = deps.commandResponder;
|
||||
this.pauseGate = deps.pauseGate;
|
||||
this.stateManager = deps.stateManager;
|
||||
this.options = options;
|
||||
this.status = {
|
||||
state: 'idle',
|
||||
@@ -272,15 +276,18 @@ export class TurnDaemonLifecycle {
|
||||
const executeHandler = async (
|
||||
context?: TurnDaemonCommandExecutionContext
|
||||
): Promise<TurnDaemonCommandResult> => {
|
||||
const handled = this.commandHandler ? await this.commandHandler.handle(command, context) : null;
|
||||
return (
|
||||
handled ?? {
|
||||
type: 'commandRejected',
|
||||
ok: false,
|
||||
commandType: command.type,
|
||||
reason: '턴 데몬이 명령을 처리할 수 없습니다.',
|
||||
}
|
||||
);
|
||||
const execute = async (): Promise<TurnDaemonCommandResult> => {
|
||||
const handled = this.commandHandler ? await this.commandHandler.handle(command, context) : null;
|
||||
return (
|
||||
handled ?? {
|
||||
type: 'commandRejected',
|
||||
ok: false,
|
||||
commandType: command.type,
|
||||
reason: '턴 데몬이 명령을 처리할 수 없습니다.',
|
||||
}
|
||||
);
|
||||
};
|
||||
return this.stateManager ? this.stateManager.transaction(execute) : execute();
|
||||
};
|
||||
try {
|
||||
if (command.requestId && this.hooks?.executeCommand) {
|
||||
@@ -331,7 +338,8 @@ export class TurnDaemonLifecycle {
|
||||
let result: TurnRunResult;
|
||||
|
||||
try {
|
||||
result = await this.processor.run(targetTime, budget, checkpoint);
|
||||
const runProcessor = () => this.processor.run(targetTime, budget, checkpoint);
|
||||
result = this.stateManager ? await this.stateManager.transaction(runProcessor) : await runProcessor();
|
||||
} catch (error) {
|
||||
this.status.running = false;
|
||||
this.status.state = 'paused';
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
export interface EngineStateParticipant<T> {
|
||||
capture(): T;
|
||||
restore(snapshot: T): void;
|
||||
inspect?(): unknown;
|
||||
}
|
||||
|
||||
type RegisteredParticipant = {
|
||||
capture(): unknown;
|
||||
restore(snapshot: unknown): void;
|
||||
inspect?(): unknown;
|
||||
};
|
||||
|
||||
type CapturedParticipant = {
|
||||
name: string;
|
||||
snapshot: unknown;
|
||||
};
|
||||
|
||||
export interface EngineStateInspection {
|
||||
revision: number;
|
||||
transactionActive: boolean;
|
||||
participants: Readonly<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
export class EngineStateSavepoint {
|
||||
readonly revision: number;
|
||||
readonly participants: readonly CapturedParticipant[];
|
||||
readonly owner: symbol;
|
||||
|
||||
constructor(owner: symbol, revision: number, participants: CapturedParticipant[]) {
|
||||
this.owner = owner;
|
||||
this.revision = revision;
|
||||
this.participants = participants;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinates the mutable state owned by the game-engine worker.
|
||||
*
|
||||
* The manager deliberately knows nothing about PostgreSQL or Redis. Database
|
||||
* transactions remain in databaseHooks; this boundary only prevents a failed
|
||||
* calculation from leaving a partially-mutated in-memory world behind.
|
||||
*/
|
||||
export class EngineStateManager {
|
||||
private readonly owner = Symbol('EngineStateManager');
|
||||
private readonly participants = new Map<string, RegisteredParticipant>();
|
||||
private revision = 0;
|
||||
private transactionActive = false;
|
||||
|
||||
register<T>(name: string, participant: EngineStateParticipant<T>): void {
|
||||
if (this.transactionActive) {
|
||||
throw new Error('Cannot register engine state while a transaction is active.');
|
||||
}
|
||||
if (!name) {
|
||||
throw new Error('Engine state participant name is required.');
|
||||
}
|
||||
if (this.participants.has(name)) {
|
||||
throw new Error(`Engine state participant is already registered: ${name}`);
|
||||
}
|
||||
this.participants.set(name, {
|
||||
capture: participant.capture,
|
||||
restore: (snapshot) => participant.restore(snapshot as T),
|
||||
inspect: participant.inspect,
|
||||
});
|
||||
}
|
||||
|
||||
getRevision(): number {
|
||||
return this.revision;
|
||||
}
|
||||
|
||||
isTransactionActive(): boolean {
|
||||
return this.transactionActive;
|
||||
}
|
||||
|
||||
capture(): EngineStateSavepoint {
|
||||
const captured: CapturedParticipant[] = [];
|
||||
for (const [name, participant] of this.participants) {
|
||||
captured.push({ name, snapshot: participant.capture() });
|
||||
}
|
||||
return new EngineStateSavepoint(this.owner, this.revision, captured);
|
||||
}
|
||||
|
||||
restore(savepoint: EngineStateSavepoint): void {
|
||||
if (this.transactionActive) {
|
||||
throw new Error('Cannot restore engine state while a transaction is active.');
|
||||
}
|
||||
this.assertOwnedSavepoint(savepoint);
|
||||
this.restoreParticipants(savepoint);
|
||||
this.revision += 1;
|
||||
}
|
||||
|
||||
inspect(): EngineStateInspection {
|
||||
const participants: Record<string, unknown> = {};
|
||||
for (const [name, participant] of this.participants) {
|
||||
participants[name] = structuredClone(participant.inspect ? participant.inspect() : participant.capture());
|
||||
}
|
||||
return {
|
||||
revision: this.revision,
|
||||
transactionActive: this.transactionActive,
|
||||
participants,
|
||||
};
|
||||
}
|
||||
|
||||
async transaction<T>(operation: () => Promise<T> | T): Promise<T> {
|
||||
if (this.transactionActive) {
|
||||
throw new Error('Nested engine state transactions are not supported.');
|
||||
}
|
||||
const savepoint = this.capture();
|
||||
this.transactionActive = true;
|
||||
try {
|
||||
const result = await operation();
|
||||
this.revision += 1;
|
||||
return result;
|
||||
} catch (error) {
|
||||
try {
|
||||
this.restoreParticipants(savepoint);
|
||||
} catch (restoreError) {
|
||||
throw new AggregateError(
|
||||
[error, restoreError],
|
||||
'Engine state operation failed and its in-memory state could not be restored.',
|
||||
{ cause: restoreError }
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
this.transactionActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
private assertOwnedSavepoint(savepoint: EngineStateSavepoint): void {
|
||||
if (savepoint.owner !== this.owner) {
|
||||
throw new Error('Engine state savepoint belongs to a different manager.');
|
||||
}
|
||||
}
|
||||
|
||||
private restoreParticipants(savepoint: EngineStateSavepoint): void {
|
||||
this.assertOwnedSavepoint(savepoint);
|
||||
const currentNames = Array.from(this.participants.keys());
|
||||
const capturedNames = savepoint.participants.map(({ name }) => name);
|
||||
if (
|
||||
currentNames.length !== capturedNames.length ||
|
||||
currentNames.some((name, index) => name !== capturedNames[index])
|
||||
) {
|
||||
throw new Error('Engine state participant set changed after the savepoint was captured.');
|
||||
}
|
||||
for (let index = savepoint.participants.length - 1; index >= 0; index -= 1) {
|
||||
const captured = savepoint.participants[index];
|
||||
if (!captured) {
|
||||
continue;
|
||||
}
|
||||
this.participants.get(captured.name)?.restore(captured.snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,6 +119,52 @@ export interface TurnWorldChanges {
|
||||
pendingNationBettingFinishes: PendingNationBettingFinish[];
|
||||
}
|
||||
|
||||
export interface InMemoryTurnWorldStateSnapshot {
|
||||
schedule: TurnSchedule;
|
||||
state: TurnWorldState;
|
||||
checkpoint?: TurnCheckpoint;
|
||||
generals: Array<[number, TurnGeneral]>;
|
||||
cities: Array<[number, City]>;
|
||||
nations: Array<[number, Nation]>;
|
||||
troops: Array<[number, Troop]>;
|
||||
diplomacy: Array<[string, TurnDiplomacy]>;
|
||||
events: Array<[number, TurnEvent]>;
|
||||
dirtyGeneralIds: number[];
|
||||
dirtyCityIds: number[];
|
||||
dirtyNationIds: number[];
|
||||
dirtyTroopIds: number[];
|
||||
dirtyDiplomacyKeys: string[];
|
||||
createdGeneralIds: number[];
|
||||
createdNationIds: number[];
|
||||
createdTroopIds: number[];
|
||||
createdDiplomacyKeys: string[];
|
||||
createdEventIds: number[];
|
||||
deletedTroopIds: number[];
|
||||
deletedGeneralIds: number[];
|
||||
deletedNationIds: number[];
|
||||
deletedEventIds: number[];
|
||||
deletedNationSnapshots: Array<{ nation: Nation; generalIds: number[]; removedAt: Date }>;
|
||||
logs: LogEntryDraft[];
|
||||
messages: MessageDraft[];
|
||||
lifecycleEvents: GeneralLifecycleEvent[];
|
||||
pendingNeutralAuctions: PendingNeutralAuction[];
|
||||
inheritancePointAdjustments: Array<{ userId: string; key: string; amount: number }>;
|
||||
pendingNationBettingOpens: PendingNationBettingOpen[];
|
||||
pendingNationBettingFinishes: PendingNationBettingFinish[];
|
||||
}
|
||||
|
||||
export interface InMemoryTurnWorldInspection {
|
||||
state: TurnWorldState;
|
||||
checkpoint?: TurnCheckpoint;
|
||||
generals: TurnGeneral[];
|
||||
cities: City[];
|
||||
nations: Nation[];
|
||||
troops: Troop[];
|
||||
diplomacy: TurnDiplomacy[];
|
||||
events: TurnEvent[];
|
||||
changes: TurnWorldChanges;
|
||||
}
|
||||
|
||||
const compareTurnOrder = (left: TurnGeneral, right: TurnGeneral): number => {
|
||||
const timeDiff = left.turnTime.getTime() - right.turnTime.getTime();
|
||||
if (timeDiff !== 0) {
|
||||
@@ -335,6 +381,91 @@ export class InMemoryTurnWorld {
|
||||
this.ensureDiplomacyMatrix();
|
||||
}
|
||||
|
||||
captureState(): InMemoryTurnWorldStateSnapshot {
|
||||
return structuredClone({
|
||||
schedule: this.schedule,
|
||||
state: this.state,
|
||||
checkpoint: this.checkpoint,
|
||||
generals: Array.from(this.generals.entries()),
|
||||
cities: Array.from(this.cities.entries()),
|
||||
nations: Array.from(this.nations.entries()),
|
||||
troops: Array.from(this.troops.entries()),
|
||||
diplomacy: Array.from(this.diplomacy.entries()),
|
||||
events: Array.from(this.events.entries()),
|
||||
dirtyGeneralIds: Array.from(this.dirtyGeneralIds),
|
||||
dirtyCityIds: Array.from(this.dirtyCityIds),
|
||||
dirtyNationIds: Array.from(this.dirtyNationIds),
|
||||
dirtyTroopIds: Array.from(this.dirtyTroopIds),
|
||||
dirtyDiplomacyKeys: Array.from(this.dirtyDiplomacyKeys),
|
||||
createdGeneralIds: Array.from(this.createdGeneralIds),
|
||||
createdNationIds: Array.from(this.createdNationIds),
|
||||
createdTroopIds: Array.from(this.createdTroopIds),
|
||||
createdDiplomacyKeys: Array.from(this.createdDiplomacyKeys),
|
||||
createdEventIds: Array.from(this.createdEventIds),
|
||||
deletedTroopIds: Array.from(this.deletedTroopIds),
|
||||
deletedGeneralIds: Array.from(this.deletedGeneralIds),
|
||||
deletedNationIds: Array.from(this.deletedNationIds),
|
||||
deletedEventIds: Array.from(this.deletedEventIds),
|
||||
deletedNationSnapshots: this.deletedNationSnapshots,
|
||||
logs: this.logs,
|
||||
messages: this.messages,
|
||||
lifecycleEvents: this.lifecycleEvents,
|
||||
pendingNeutralAuctions: this.pendingNeutralAuctions,
|
||||
inheritancePointAdjustments: this.inheritancePointAdjustments,
|
||||
pendingNationBettingOpens: this.pendingNationBettingOpens,
|
||||
pendingNationBettingFinishes: this.pendingNationBettingFinishes,
|
||||
} satisfies InMemoryTurnWorldStateSnapshot);
|
||||
}
|
||||
|
||||
restoreState(snapshot: InMemoryTurnWorldStateSnapshot): void {
|
||||
const restored = structuredClone(snapshot);
|
||||
this.schedule = restored.schedule;
|
||||
this.state = restored.state;
|
||||
this.checkpoint = restored.checkpoint;
|
||||
this.replaceMap(this.generals, restored.generals);
|
||||
this.replaceMap(this.cities, restored.cities);
|
||||
this.replaceMap(this.nations, restored.nations);
|
||||
this.replaceMap(this.troops, restored.troops);
|
||||
this.replaceMap(this.diplomacy, restored.diplomacy);
|
||||
this.replaceMap(this.events, restored.events);
|
||||
this.replaceSet(this.dirtyGeneralIds, restored.dirtyGeneralIds);
|
||||
this.replaceSet(this.dirtyCityIds, restored.dirtyCityIds);
|
||||
this.replaceSet(this.dirtyNationIds, restored.dirtyNationIds);
|
||||
this.replaceSet(this.dirtyTroopIds, restored.dirtyTroopIds);
|
||||
this.replaceSet(this.dirtyDiplomacyKeys, restored.dirtyDiplomacyKeys);
|
||||
this.replaceSet(this.createdGeneralIds, restored.createdGeneralIds);
|
||||
this.replaceSet(this.createdNationIds, restored.createdNationIds);
|
||||
this.replaceSet(this.createdTroopIds, restored.createdTroopIds);
|
||||
this.replaceSet(this.createdDiplomacyKeys, restored.createdDiplomacyKeys);
|
||||
this.replaceSet(this.createdEventIds, restored.createdEventIds);
|
||||
this.replaceSet(this.deletedTroopIds, restored.deletedTroopIds);
|
||||
this.replaceSet(this.deletedGeneralIds, restored.deletedGeneralIds);
|
||||
this.replaceSet(this.deletedNationIds, restored.deletedNationIds);
|
||||
this.replaceSet(this.deletedEventIds, restored.deletedEventIds);
|
||||
this.replaceArray(this.deletedNationSnapshots, restored.deletedNationSnapshots);
|
||||
this.replaceArray(this.logs, restored.logs);
|
||||
this.replaceArray(this.messages, restored.messages);
|
||||
this.replaceArray(this.lifecycleEvents, restored.lifecycleEvents);
|
||||
this.replaceArray(this.pendingNeutralAuctions, restored.pendingNeutralAuctions);
|
||||
this.replaceArray(this.inheritancePointAdjustments, restored.inheritancePointAdjustments);
|
||||
this.replaceArray(this.pendingNationBettingOpens, restored.pendingNationBettingOpens);
|
||||
this.replaceArray(this.pendingNationBettingFinishes, restored.pendingNationBettingFinishes);
|
||||
}
|
||||
|
||||
inspectState(): InMemoryTurnWorldInspection {
|
||||
return structuredClone({
|
||||
state: this.state,
|
||||
checkpoint: this.checkpoint,
|
||||
generals: Array.from(this.generals.values()),
|
||||
cities: Array.from(this.cities.values()),
|
||||
nations: Array.from(this.nations.values()),
|
||||
troops: Array.from(this.troops.values()),
|
||||
diplomacy: Array.from(this.diplomacy.values()),
|
||||
events: Array.from(this.events.values()),
|
||||
changes: this.peekDirtyState(),
|
||||
} satisfies InMemoryTurnWorldInspection);
|
||||
}
|
||||
|
||||
getState(): TurnWorldState {
|
||||
return { ...this.state };
|
||||
}
|
||||
@@ -1166,4 +1297,22 @@ export class InMemoryTurnWorld {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private replaceMap<K, V>(target: Map<K, V>, entries: Array<[K, V]>): void {
|
||||
target.clear();
|
||||
for (const [key, value] of entries) {
|
||||
target.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private replaceSet<T>(target: Set<T>, values: T[]): void {
|
||||
target.clear();
|
||||
for (const value of values) {
|
||||
target.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
private replaceArray<T>(target: T[], values: T[]): void {
|
||||
target.splice(0, target.length, ...values);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,17 @@ export interface ReservedTurnChanges {
|
||||
nationLeaseKeys: string[];
|
||||
}
|
||||
|
||||
export interface InMemoryReservedTurnStateSnapshot {
|
||||
generalTurns: Array<[number, ReservedTurnEntry[]]>;
|
||||
nationTurns: Array<[string, ReservedTurnEntry[]]>;
|
||||
dirtyGeneralIds: number[];
|
||||
dirtyNationKeys: string[];
|
||||
pendingGeneralInitializationIds: number[];
|
||||
pendingNationInitializationKeys: string[];
|
||||
leasedGeneralIds: number[];
|
||||
leasedNationKeys: string[];
|
||||
}
|
||||
|
||||
export class ReservedTurnLeaseConflictError extends Error {
|
||||
constructor(readonly queueKey: string) {
|
||||
super(`Reserved turn queue lease conflict: ${queueKey}.`);
|
||||
@@ -130,6 +141,35 @@ export class InMemoryReservedTurnStore {
|
||||
this.leaseDurationMs = options.leaseDurationMs ?? DEFAULT_LEASE_DURATION_MS;
|
||||
}
|
||||
|
||||
captureState(): InMemoryReservedTurnStateSnapshot {
|
||||
return structuredClone({
|
||||
generalTurns: Array.from(this.generalTurns.entries()),
|
||||
nationTurns: Array.from(this.nationTurns.entries()),
|
||||
dirtyGeneralIds: Array.from(this.dirtyGeneralIds),
|
||||
dirtyNationKeys: Array.from(this.dirtyNationKeys),
|
||||
pendingGeneralInitializationIds: Array.from(this.pendingGeneralInitializationIds),
|
||||
pendingNationInitializationKeys: Array.from(this.pendingNationInitializationKeys),
|
||||
leasedGeneralIds: Array.from(this.leasedGeneralIds),
|
||||
leasedNationKeys: Array.from(this.leasedNationKeys),
|
||||
} satisfies InMemoryReservedTurnStateSnapshot);
|
||||
}
|
||||
|
||||
restoreState(snapshot: InMemoryReservedTurnStateSnapshot): void {
|
||||
const restored = structuredClone(snapshot);
|
||||
this.replaceMap(this.generalTurns, restored.generalTurns);
|
||||
this.replaceMap(this.nationTurns, restored.nationTurns);
|
||||
this.replaceSet(this.dirtyGeneralIds, restored.dirtyGeneralIds);
|
||||
this.replaceSet(this.dirtyNationKeys, restored.dirtyNationKeys);
|
||||
this.replaceSet(this.pendingGeneralInitializationIds, restored.pendingGeneralInitializationIds);
|
||||
this.replaceSet(this.pendingNationInitializationKeys, restored.pendingNationInitializationKeys);
|
||||
this.replaceSet(this.leasedGeneralIds, restored.leasedGeneralIds);
|
||||
this.replaceSet(this.leasedNationKeys, restored.leasedNationKeys);
|
||||
}
|
||||
|
||||
inspectState(): InMemoryReservedTurnStateSnapshot {
|
||||
return this.captureState();
|
||||
}
|
||||
|
||||
async loadAll(): Promise<void> {
|
||||
const [generalRows, nationRows] = await Promise.all([
|
||||
this.prisma.generalTurn.findMany(),
|
||||
@@ -164,6 +204,20 @@ export class InMemoryReservedTurnStore {
|
||||
}
|
||||
}
|
||||
|
||||
private replaceMap<K, V>(target: Map<K, V>, entries: Array<[K, V]>): void {
|
||||
target.clear();
|
||||
for (const [key, value] of entries) {
|
||||
target.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private replaceSet<T>(target: Set<T>, values: T[]): void {
|
||||
target.clear();
|
||||
for (const value of values) {
|
||||
target.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
private getLeaseExpiresAt(): Date {
|
||||
return new Date(Date.now() + this.leaseDurationMs);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ import {
|
||||
} from './monthlyCoreEventAction.js';
|
||||
import { buildCommandEnv } from './reservedTurnCommands.js';
|
||||
import { DatabaseTurnDaemonLease, TurnDaemonLeaseUnavailableError } from '../lifecycle/databaseTurnDaemonLease.js';
|
||||
import { EngineStateManager } from './engineStateManager.js';
|
||||
|
||||
export interface TurnDaemonRuntimeOptions {
|
||||
profile: string;
|
||||
@@ -106,6 +107,7 @@ export interface TurnDaemonRuntime {
|
||||
world: InMemoryTurnWorld;
|
||||
controlQueue: TurnDaemonControlQueue;
|
||||
stateStore: InMemoryTurnStateStore;
|
||||
stateManager: EngineStateManager;
|
||||
processor: InMemoryTurnProcessor;
|
||||
hooks?: TurnDaemonHooks;
|
||||
close(): Promise<void>;
|
||||
@@ -475,6 +477,7 @@ const createTurnDaemonRuntimeWithLease = async (
|
||||
);
|
||||
let occupiedAuctionUniqueItemKeys: string[] = [];
|
||||
let refreshOccupiedAuctionUniqueItemKeys = async (): Promise<void> => {};
|
||||
const prefetchedNationTurns = new Set<string>();
|
||||
const worldOptions: InMemoryTurnWorldOptions = {
|
||||
schedule,
|
||||
generalTurnHandler:
|
||||
@@ -496,8 +499,38 @@ const createTurnDaemonRuntimeWithLease = async (
|
||||
const world = new InMemoryTurnWorld(resolvedState, snapshot, worldOptions);
|
||||
worldRef = world;
|
||||
|
||||
const stateManager = new EngineStateManager();
|
||||
stateManager.register('world', {
|
||||
capture: () => world.captureState(),
|
||||
restore: (captured) => world.restoreState(captured),
|
||||
inspect: () => world.inspectState(),
|
||||
});
|
||||
if (reservedTurnStoreHandle) {
|
||||
stateManager.register('reservedTurns', {
|
||||
capture: () => reservedTurnStoreHandle.store.captureState(),
|
||||
restore: (captured) => reservedTurnStoreHandle.store.restoreState(captured),
|
||||
inspect: () => reservedTurnStoreHandle.store.inspectState(),
|
||||
});
|
||||
}
|
||||
stateManager.register('runtimeCaches', {
|
||||
capture: () => ({
|
||||
monthlyNationPowerRollCount,
|
||||
monthlyTournamentRollConsumed,
|
||||
occupiedAuctionUniqueItemKeys: [...occupiedAuctionUniqueItemKeys],
|
||||
prefetchedNationTurns: Array.from(prefetchedNationTurns),
|
||||
}),
|
||||
restore: (captured) => {
|
||||
monthlyNationPowerRollCount = captured.monthlyNationPowerRollCount;
|
||||
monthlyTournamentRollConsumed = captured.monthlyTournamentRollConsumed;
|
||||
occupiedAuctionUniqueItemKeys = [...captured.occupiedAuctionUniqueItemKeys];
|
||||
prefetchedNationTurns.clear();
|
||||
for (const key of captured.prefetchedNationTurns) {
|
||||
prefetchedNationTurns.add(key);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const stateStore = new InMemoryTurnStateStore(world);
|
||||
const prefetchedNationTurns = new Set<string>();
|
||||
const processor = new InMemoryTurnProcessor(world, {
|
||||
tickMinutes,
|
||||
beforeExecuteGeneral: reservedTurnStoreHandle
|
||||
@@ -709,6 +742,7 @@ const createTurnDaemonRuntimeWithLease = async (
|
||||
pauseGate: async () => turnDaemonLease?.isLost() || ((await pauseGate?.()) ?? false),
|
||||
commandHandler,
|
||||
commandResponder: options.controlQueue ? undefined : (databaseCommandQueue ?? undefined),
|
||||
stateManager,
|
||||
},
|
||||
{ profile: options.profile, defaultBudget }
|
||||
);
|
||||
@@ -749,6 +783,7 @@ const createTurnDaemonRuntimeWithLease = async (
|
||||
world,
|
||||
controlQueue: resolvedControlQueue,
|
||||
stateStore,
|
||||
stateManager,
|
||||
processor,
|
||||
hooks,
|
||||
close,
|
||||
|
||||
Reference in New Issue
Block a user