perf: NPC 생명주기 메모리 프로파일과 큐 정리를 추가한다
This commit is contained in:
@@ -96,10 +96,17 @@ export const runTurnDaemonCli = async (options: TurnDaemonCliOptions = {}): Prom
|
||||
intervalMs: memoryReportIntervalMs,
|
||||
getContext: () => {
|
||||
const state = runtime.world.getState();
|
||||
const queueCounts = runtime.reservedTurns?.getQueueCounts();
|
||||
return {
|
||||
year: state.currentYear,
|
||||
month: state.currentMonth,
|
||||
...runtime.world.getEntityCounts(),
|
||||
...(queueCounts
|
||||
? {
|
||||
generalTurnQueues: queueCounts.generalQueues,
|
||||
nationTurnQueues: queueCounts.nationQueues,
|
||||
}
|
||||
: {}),
|
||||
lifecycleState: runtime.lifecycle.getStatus().state,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1787,6 +1787,7 @@ export const createDatabaseTurnHooks = async (
|
||||
world.acknowledgeDirtyState(changes);
|
||||
if (options?.reservedTurns && reservedTurnChanges) {
|
||||
options.reservedTurns.acknowledgeDirtyState(reservedTurnChanges);
|
||||
options.reservedTurns.pruneDeletedEntityQueues(deletedGenerals, deletedNations);
|
||||
}
|
||||
applyRealtimeReadModelBaseline(readModelBaseline, changes);
|
||||
worldReadModelBaseline = persisted.worldReadModelSignature;
|
||||
|
||||
@@ -105,6 +105,17 @@ export interface InMemoryReservedTurnStateSnapshot {
|
||||
leasedNationKeys: string[];
|
||||
}
|
||||
|
||||
export interface ReservedTurnQueueCounts {
|
||||
generalQueues: number;
|
||||
nationQueues: number;
|
||||
dirtyGeneralQueues: number;
|
||||
dirtyNationQueues: number;
|
||||
pendingGeneralInitializations: number;
|
||||
pendingNationInitializations: number;
|
||||
leasedGeneralQueues: number;
|
||||
leasedNationQueues: number;
|
||||
}
|
||||
|
||||
export class ReservedTurnLeaseConflictError extends Error {
|
||||
constructor(readonly queueKey: string) {
|
||||
super(`Reserved turn queue lease conflict: ${queueKey}.`);
|
||||
@@ -189,6 +200,64 @@ export class InMemoryReservedTurnStore {
|
||||
return this.captureState();
|
||||
}
|
||||
|
||||
getQueueCounts(): ReservedTurnQueueCounts {
|
||||
return {
|
||||
generalQueues: this.generalTurns.size,
|
||||
nationQueues: this.nationTurns.size,
|
||||
dirtyGeneralQueues: this.dirtyGeneralIds.size,
|
||||
dirtyNationQueues: this.dirtyNationKeys.size,
|
||||
pendingGeneralInitializations: this.pendingGeneralInitializationIds.size,
|
||||
pendingNationInitializations: this.pendingNationInitializationKeys.size,
|
||||
leasedGeneralQueues: this.leasedGeneralIds.size,
|
||||
leasedNationQueues: this.leasedNationKeys.size,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops queues whose owning rows were deleted by a successful world flush.
|
||||
* The daemon calls this inside EngineStateManager.transaction(), so a later
|
||||
* failure still restores these maps and journals from the transaction savepoint.
|
||||
*/
|
||||
pruneDeletedEntityQueues(
|
||||
generalIds: readonly number[],
|
||||
nationIds: readonly number[]
|
||||
): { generalQueues: number; nationQueues: number } {
|
||||
let generalQueues = 0;
|
||||
for (const generalId of new Set(generalIds)) {
|
||||
if (this.generalTurns.delete(generalId)) {
|
||||
generalQueues += 1;
|
||||
}
|
||||
this.dirtyGeneralIds.delete(generalId);
|
||||
this.pendingGeneralInitializationIds.delete(generalId);
|
||||
this.leasedGeneralIds.delete(generalId);
|
||||
}
|
||||
|
||||
const deletedNations = new Set(nationIds);
|
||||
let nationQueues = 0;
|
||||
const pruneNationKeys = (keys: Iterable<string>, remove: (key: string) => boolean | void): void => {
|
||||
for (const key of keys) {
|
||||
const nationId = Number(key.split(':', 1)[0]);
|
||||
if (deletedNations.has(nationId) && remove(key) !== false) {
|
||||
nationQueues += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
pruneNationKeys(Array.from(this.nationTurns.keys()), (key) => this.nationTurns.delete(key));
|
||||
for (const keys of [
|
||||
this.dirtyNationKeys,
|
||||
this.pendingNationInitializationKeys,
|
||||
this.leasedNationKeys,
|
||||
]) {
|
||||
for (const key of Array.from(keys)) {
|
||||
const nationId = Number(key.split(':', 1)[0]);
|
||||
if (deletedNations.has(nationId)) {
|
||||
keys.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { generalQueues, nationQueues };
|
||||
}
|
||||
|
||||
inspectGeneralTurnActivity(): Array<[number, boolean]> {
|
||||
return Array.from(this.generalTurns, ([generalId, turns]) => [
|
||||
generalId,
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
} from './monthlyNationStatsHandler.js';
|
||||
import { createFrontStateHandler } from './frontStateHandler.js';
|
||||
import { createReservedTurnHandler } from './reservedTurnHandler.js';
|
||||
import { createReservedTurnStore } from './reservedTurnStore.js';
|
||||
import { createReservedTurnStore, type InMemoryReservedTurnStore } from './reservedTurnStore.js';
|
||||
import { createTurnDaemonCommandHandler } from './worldCommandHandler.js';
|
||||
import { loadTurnCommandProfile } from './turnCommandProfile.js';
|
||||
import { loadTurnWorldFromDatabase } from './worldLoader.js';
|
||||
@@ -132,6 +132,7 @@ export interface TurnDaemonRuntime {
|
||||
stateStore: InMemoryTurnStateStore;
|
||||
stateManager: EngineStateManager;
|
||||
processor: InMemoryTurnProcessor;
|
||||
reservedTurns: InMemoryReservedTurnStore | null;
|
||||
hooks?: TurnDaemonHooks;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
@@ -1012,6 +1013,7 @@ const createTurnDaemonRuntimeWithLease = async (
|
||||
stateStore,
|
||||
stateManager,
|
||||
processor,
|
||||
reservedTurns: reservedTurnStoreHandle?.store ?? null,
|
||||
hooks,
|
||||
close,
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@ export interface TurnDaemonMemoryContext {
|
||||
nations: number;
|
||||
troops: number;
|
||||
events: number;
|
||||
generalTurnQueues?: number;
|
||||
nationTurnQueues?: number;
|
||||
lifecycleState: string;
|
||||
}
|
||||
|
||||
@@ -48,6 +50,10 @@ export const buildTurnDaemonMemoryReport = (
|
||||
`nations=${context.nations}`,
|
||||
`troops=${context.troops}`,
|
||||
`events=${context.events}`,
|
||||
...(context.generalTurnQueues === undefined
|
||||
? []
|
||||
: [`generalTurnQueues=${context.generalTurnQueues}`]),
|
||||
...(context.nationTurnQueues === undefined ? [] : [`nationTurnQueues=${context.nationTurnQueues}`]),
|
||||
`lifecycle=${context.lifecycleState}`,
|
||||
].join(' ');
|
||||
return { message, warning: heapRatio >= HEAP_WARNING_RATIO };
|
||||
|
||||
Reference in New Issue
Block a user