fix: game audio persistence and editor perf
- Keep game/campaign audio assets referenced (no prune) - Flush pending project save on quit/switch/export to avoid losing campaignAudios - Control: prevent game music restarts on scene changes; allow always-on controls; handle autoplay-after-scene-audio - Editor: reduce ReactFlow churn with stable scene card map; lazy/async image decode - Add contract/unit tests and update test script Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import type { Project } from '../../../shared/types';
|
||||
import type { SceneId } from '../../../shared/types/ids';
|
||||
|
||||
import type { SceneGraphSceneAudioSummary, SceneGraphSceneCard } from './SceneGraph';
|
||||
|
||||
export function stableSceneGraphAudios(
|
||||
prevCard: SceneGraphSceneCard | undefined,
|
||||
nextRaw: SceneGraphSceneAudioSummary[],
|
||||
): readonly SceneGraphSceneAudioSummary[] {
|
||||
if (!prevCard) return nextRaw;
|
||||
const pa = prevCard.audios;
|
||||
if (pa.length !== nextRaw.length) return nextRaw;
|
||||
for (let i = 0; i < nextRaw.length; i++) {
|
||||
const p = pa[i];
|
||||
const n = nextRaw[i];
|
||||
if (p?.assetId !== n?.assetId || p?.loop !== n?.loop || p?.autoplay !== n?.autoplay) return nextRaw;
|
||||
}
|
||||
return pa;
|
||||
}
|
||||
|
||||
export function buildNextSceneCardById(
|
||||
prevRecord: Record<SceneId, SceneGraphSceneCard>,
|
||||
project: Project,
|
||||
): Record<SceneId, SceneGraphSceneCard> {
|
||||
const nextMap: Record<SceneId, SceneGraphSceneCard> = {};
|
||||
|
||||
for (const id of Object.keys(project.scenes) as SceneId[]) {
|
||||
const s = project.scenes[id];
|
||||
if (!s) continue;
|
||||
const prevCard = prevRecord[id];
|
||||
const nextAudiosRaw: SceneGraphSceneAudioSummary[] = s.media.audios.map((a) => ({
|
||||
assetId: a.assetId,
|
||||
loop: a.loop,
|
||||
autoplay: a.autoplay,
|
||||
}));
|
||||
const audios = stableSceneGraphAudios(prevCard, nextAudiosRaw);
|
||||
const loopVideo = s.settings.loopVideo;
|
||||
if (
|
||||
prevCard?.title === s.title &&
|
||||
prevCard.previewAssetId === s.previewAssetId &&
|
||||
prevCard.previewAssetType === s.previewAssetType &&
|
||||
prevCard.previewVideoAutostart === s.previewVideoAutostart &&
|
||||
prevCard.previewRotationDeg === s.previewRotationDeg &&
|
||||
prevCard.loopVideo === loopVideo &&
|
||||
prevCard.audios === audios
|
||||
) {
|
||||
nextMap[id] = prevCard;
|
||||
} else {
|
||||
nextMap[id] = {
|
||||
title: s.title,
|
||||
previewAssetId: s.previewAssetId,
|
||||
previewAssetType: s.previewAssetType,
|
||||
previewVideoAutostart: s.previewVideoAutostart,
|
||||
previewRotationDeg: s.previewRotationDeg,
|
||||
loopVideo,
|
||||
audios,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const prevKeys = Object.keys(prevRecord);
|
||||
const nextKeys = Object.keys(nextMap);
|
||||
const reuseRecord =
|
||||
prevKeys.length === nextKeys.length &&
|
||||
nextKeys.every((k) => prevRecord[k as SceneId] === nextMap[k as SceneId]);
|
||||
|
||||
return reuseRecord ? prevRecord : nextMap;
|
||||
}
|
||||
Reference in New Issue
Block a user