mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(core): auto-detect three.js asset readiness via adapter contract (#1543)
Replaces the original `window.__hyperframesReady` authored API with an internal adapter contract: `RuntimeDeterministicAdapter.getReadyPromise?: () => PromiseLike | null`. The Three.js adapter implements it by hooking `THREE.DefaultLoadingManager.onStart/onLoad`; the runtime collects promises from every adapter and gates `window.__renderReady = true` on them. Zero authoring burden — composition authors write plain Three.js, framework handles async asset gating automatically. Also keeps the orthogonal `htmlDocument.ts` script-stripping refactor (substring → regex for simple flag assignments), which fixes the bug where authored scripts referencing readiness flags were stripped despite never assigning them. Stamped by Magi and Miguel; CI green; tests 33/33 pass.
This commit is contained in:
@@ -1643,6 +1643,65 @@ export function initSandboxRuntimeModular(): void {
|
||||
let maybePublishRenderReady = () => {
|
||||
window.__renderReady = false;
|
||||
};
|
||||
// Internal adapter-readiness tracking. Adapters with outstanding async work
|
||||
// (Three.js `DefaultLoadingManager`, future fetch/font/image detectors) expose
|
||||
// a `getReadyPromise()` method; the runtime waits for whatever they return
|
||||
// before publishing render-ready. This is purely internal — there is no
|
||||
// authored-code-facing flag (LLMs should not need to know about render
|
||||
// readiness, the framework handles async asset gating automatically).
|
||||
let trackedAdapterReadyPromise: PromiseLike<unknown> | null = null;
|
||||
let trackedAdapterReadySettled = true;
|
||||
|
||||
const collectAdapterReadyPromises = (): PromiseLike<unknown>[] => {
|
||||
const promises: PromiseLike<unknown>[] = [];
|
||||
for (const adapter of state.deterministicAdapters) {
|
||||
const getter = adapter.getReadyPromise;
|
||||
if (typeof getter !== "function") continue;
|
||||
try {
|
||||
const p = getter();
|
||||
if (p) promises.push(p);
|
||||
} catch (err) {
|
||||
// A throwing readiness gate must not permanently block render; swallow
|
||||
// and continue, matching the rest of the runtime's adapter-resilience
|
||||
// pattern.
|
||||
swallow("runtime.init.adapterReady", err);
|
||||
}
|
||||
}
|
||||
return promises;
|
||||
};
|
||||
|
||||
const isAdapterReadinessSettled = (): boolean => {
|
||||
const promises = collectAdapterReadyPromises();
|
||||
if (promises.length === 0) {
|
||||
trackedAdapterReadyPromise = null;
|
||||
trackedAdapterReadySettled = true;
|
||||
return true;
|
||||
}
|
||||
// Combine multiple adapter promises so we only attach a single resume
|
||||
// handler. Identity is stable as long as the inputs are stable (each
|
||||
// adapter is expected to return the same promise on repeat calls while
|
||||
// its work is in flight).
|
||||
const combined: PromiseLike<unknown> =
|
||||
promises.length === 1 ? promises[0] : Promise.all(promises);
|
||||
if (combined !== trackedAdapterReadyPromise) {
|
||||
trackedAdapterReadyPromise = combined;
|
||||
trackedAdapterReadySettled = false;
|
||||
void Promise.resolve(combined).then(
|
||||
() => {
|
||||
if (trackedAdapterReadyPromise !== combined) return;
|
||||
trackedAdapterReadySettled = true;
|
||||
maybePublishRenderReady();
|
||||
},
|
||||
(err) => {
|
||||
if (trackedAdapterReadyPromise !== combined) return;
|
||||
trackedAdapterReadySettled = true;
|
||||
swallow("runtime.init.adapterReady", err);
|
||||
maybePublishRenderReady();
|
||||
},
|
||||
);
|
||||
}
|
||||
return trackedAdapterReadySettled;
|
||||
};
|
||||
|
||||
if (!externalCompositionsReady) {
|
||||
const compositionLoaderParams = {
|
||||
@@ -1910,6 +1969,16 @@ export function initSandboxRuntimeModular(): void {
|
||||
window.__renderReady = false;
|
||||
return;
|
||||
}
|
||||
// Re-run discover so adapters can refresh their state from the current
|
||||
// DOM — e.g. the Three.js adapter only hooks `DefaultLoadingManager` once
|
||||
// it sees `window.THREE`, which may have loaded AFTER the initial
|
||||
// bootstrap discover. Discover is idempotent in every adapter, so a
|
||||
// second call here is cheap.
|
||||
runAdapters("discover", state.currentTime);
|
||||
if (!isAdapterReadinessSettled()) {
|
||||
window.__renderReady = false;
|
||||
return;
|
||||
}
|
||||
publishRenderReadyAfterTimelineBinding();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user