Feature: Integrate project scheduling state tracking into EditorSimulationController

This commit is contained in:
2026-05-18 14:34:51 +02:00
parent 68950efaaa
commit b3c047a9eb

View File

@@ -1,5 +1,8 @@
import type { LoadedModelAsset } from "../assets/gltf-model-import"; import type { LoadedModelAsset } from "../assets/gltf-model-import";
import type { SceneDocument } from "../document/scene-document"; import type {
ProjectDocument,
SceneDocument
} from "../document/scene-document";
import { import {
advanceRuntimeClockState, advanceRuntimeClockState,
@@ -53,6 +56,7 @@ interface EditorSimulationControllerOptions {
export interface EditorSimulationControllerInputs { export interface EditorSimulationControllerInputs {
document: SceneDocument; document: SceneDocument;
projectDocument?: ProjectDocument;
loadedModelAssets: Record<string, LoadedModelAsset>; loadedModelAssets: Record<string, LoadedModelAsset>;
} }
@@ -103,8 +107,95 @@ function cancelBrowserAnimationFrame(handle: number): void {
window.cancelAnimationFrame(handle); window.cancelAnimationFrame(handle);
} }
interface EditorSimulationProjectSchedulingSignature {
scheduler: ProjectDocument["scheduler"];
sequences: ProjectDocument["sequences"];
assets: ProjectDocument["assets"];
targetRegistry: string;
}
function compareIds(left: { id: string }, right: { id: string }): number {
return left.id.localeCompare(right.id);
}
function createProjectSchedulingTargetRegistrySignature(
document: ProjectDocument
): string {
const parts: string[] = [];
for (const scene of Object.values(document.scenes).sort(compareIds)) {
parts.push(`scene:${scene.id}`);
for (const brush of Object.values(scene.brushes).sort(compareIds)) {
parts.push(`brush:${brush.id}`);
}
for (const entity of Object.values(scene.entities).sort(compareIds)) {
parts.push(`entity:${entity.id}:${entity.kind}`);
if (entity.kind === "npc") {
parts.push(
`actor:${entity.actorId}:${entity.id}:${entity.modelAssetId ?? ""}`
);
}
if (entity.kind === "soundEmitter") {
parts.push(
`sound:${entity.id}:${entity.audioAssetId === null ? "0" : "1"}`
);
}
}
for (const modelInstance of Object.values(scene.modelInstances).sort(
compareIds
)) {
parts.push(`model:${modelInstance.id}:${modelInstance.assetId}`);
}
for (const path of Object.values(scene.paths).sort(compareIds)) {
parts.push(`path:${path.id}:${path.enabled ? "1" : "0"}`);
}
}
return parts.join("|");
}
function createProjectSchedulingSignature(
document: ProjectDocument | null
): EditorSimulationProjectSchedulingSignature | null {
if (document === null) {
return null;
}
return {
scheduler: document.scheduler,
sequences: document.sequences,
assets: document.assets,
targetRegistry: createProjectSchedulingTargetRegistrySignature(document)
};
}
function areProjectSchedulingSignaturesEqual(
left: EditorSimulationProjectSchedulingSignature | null,
right: EditorSimulationProjectSchedulingSignature | null
): boolean {
if (left === null || right === null) {
return left === right;
}
return (
left.scheduler === right.scheduler &&
left.sequences === right.sequences &&
left.assets === right.assets &&
left.targetRegistry === right.targetRegistry
);
}
export class EditorSimulationController { export class EditorSimulationController {
private document: SceneDocument | null = null; private document: SceneDocument | null = null;
private projectDocument: ProjectDocument | null = null;
private projectSchedulingSignature: EditorSimulationProjectSchedulingSignature | null =
null;
private loadedModelAssets: Record<string, LoadedModelAsset> | null = null; private loadedModelAssets: Record<string, LoadedModelAsset> | null = null;
private runtimeScene: RuntimeSceneDefinition | null = null; private runtimeScene: RuntimeSceneDefinition | null = null;
private runtimeScheduleSyncContext: RuntimeScheduleSyncContext | null = null; private runtimeScheduleSyncContext: RuntimeScheduleSyncContext | null = null;
@@ -147,9 +238,18 @@ export class EditorSimulationController {
updateInputs(inputs: EditorSimulationControllerInputs) { updateInputs(inputs: EditorSimulationControllerInputs) {
const documentChanged = this.document !== inputs.document; const documentChanged = this.document !== inputs.document;
const projectDocument = inputs.projectDocument ?? null;
const nextProjectSchedulingSignature =
createProjectSchedulingSignature(projectDocument);
const projectSchedulingChanged = !areProjectSchedulingSignaturesEqual(
this.projectSchedulingSignature,
nextProjectSchedulingSignature
);
const assetsChanged = this.loadedModelAssets !== inputs.loadedModelAssets; const assetsChanged = this.loadedModelAssets !== inputs.loadedModelAssets;
this.document = inputs.document; this.document = inputs.document;
this.projectDocument = projectDocument;
this.projectSchedulingSignature = nextProjectSchedulingSignature;
this.loadedModelAssets = inputs.loadedModelAssets; this.loadedModelAssets = inputs.loadedModelAssets;
if (this.clockOverride === null) { if (this.clockOverride === null) {
@@ -162,7 +262,12 @@ export class EditorSimulationController {
this.currentClock = cloneRuntimeClockState(this.clockOverride); this.currentClock = cloneRuntimeClockState(this.clockOverride);
} }
if (documentChanged || assetsChanged || this.runtimeScene === null) { if (
documentChanged ||
projectSchedulingChanged ||
assetsChanged ||
this.runtimeScene === null
) {
this.rebuildCachedRuntimeScene(); this.rebuildCachedRuntimeScene();
return; return;
} }
@@ -326,7 +431,10 @@ export class EditorSimulationController {
try { try {
const runtimeScene = this.buildScene(this.document, { const runtimeScene = this.buildScene(this.document, {
loadedModelAssets: this.loadedModelAssets, loadedModelAssets: this.loadedModelAssets,
runtimeClock: this.currentClock runtimeClock: this.currentClock,
...(this.projectDocument === null
? {}
: { projectDocument: this.projectDocument })
}); });
const syncContext = createRuntimeScheduleSyncContext(runtimeScene); const syncContext = createRuntimeScheduleSyncContext(runtimeScene);