@hyperframes/core 0.8.207 → 0.8.209
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atomicFile.js +18 -19
- package/dist/compiler/compositionScoping.d.ts +2 -0
- package/dist/compiler/compositionScoping.js +9 -0
- package/dist/compiler/htmlBundler.js +2 -8
- package/dist/compiler/htmlDocument.d.ts +4 -0
- package/dist/compiler/htmlDocument.js +22 -0
- package/dist/compiler/index.d.ts +1 -1
- package/dist/compiler/index.js +1 -1
- package/dist/generated/runtime-inline.js +1 -1
- package/dist/hyperframe.runtime.iife.js +67 -67
- package/dist/removeScratch.d.ts +5 -0
- package/dist/removeScratch.js +23 -0
- package/dist/renameWithRetry.d.ts +16 -0
- package/dist/renameWithRetry.js +81 -0
- package/dist/runtime/applyVariableBindings.d.ts +6 -4
- package/dist/runtime/applyVariableBindings.js +23 -11
- package/dist/runtime/clock.js +1 -1
- package/dist/runtime/getVariables.js +5 -4
- package/dist/runtime/mediaSteer.d.ts +1 -0
- package/dist/runtime/mediaSteer.js +1 -0
- package/dist/runtime/state.d.ts +1 -2
- package/dist/runtime/timeline.js +4 -4
- package/dist/runtime/types.d.ts +21 -0
- package/dist/runtime/variableScope.d.ts +2 -0
- package/dist/runtime/variableScope.js +9 -3
- package/package.json +12 -4
- package/dist/runtime/adapters/seek-dispatch.d.ts +0 -37
- package/dist/runtime/adapters/seek-dispatch.js +0 -143
- package/dist/runtime/media.d.ts +0 -131
- package/dist/runtime/media.js +0 -586
- package/dist/runtime/preloadMedia.d.ts +0 -12
- package/dist/runtime/preloadMedia.js +0 -108
- package/dist/runtime/timedClipHide.d.ts +0 -12
- package/dist/runtime/timedClipHide.js +0 -93
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Removes a scratch folder or file the mix is already done with. A refusal (Windows temp folders that deny
|
|
2
|
+
* delete) leaves it for the render's own cleanup and never fails the audio it served. */
|
|
3
|
+
export declare function removeScratch(path: string): void;
|
|
4
|
+
/** Runs `run`; whatever removeScratch left behind inside it (this run's only) goes to `report` once it ends. */
|
|
5
|
+
export declare function collectScratchLeftovers<T>(run: () => Promise<T>, report: (left: string[]) => void): Promise<T>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
const collecting = new AsyncLocalStorage();
|
|
4
|
+
/** Removes a scratch folder or file the mix is already done with. A refusal (Windows temp folders that deny
|
|
5
|
+
* delete) leaves it for the render's own cleanup and never fails the audio it served. */
|
|
6
|
+
export function removeScratch(path) {
|
|
7
|
+
try {
|
|
8
|
+
rmSync(path, { recursive: true, force: true });
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
collecting.getStore()?.push(path);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Runs `run`; whatever removeScratch left behind inside it (this run's only) goes to `report` once it ends. */
|
|
15
|
+
export async function collectScratchLeftovers(run, report) {
|
|
16
|
+
const left = [];
|
|
17
|
+
try {
|
|
18
|
+
return await collecting.run(left, run);
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
report(left);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface Options {
|
|
2
|
+
platform?: NodeJS.Platform;
|
|
3
|
+
/** Copy the bytes when Windows keeps refusing (a TEMP that denies delete). Not atomic: scratch files only. */
|
|
4
|
+
copyFallback?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** `renameSync` that rides out Windows' transient file locks; other platforms and errors throw at once. */
|
|
7
|
+
export declare function renameWithRetry(from: string, to: string, options?: Options & {
|
|
8
|
+
rename?: (from: string, to: string) => void;
|
|
9
|
+
copy?: (from: string, to: string) => void;
|
|
10
|
+
}): void;
|
|
11
|
+
/** Async twin of {@link renameWithRetry}. */
|
|
12
|
+
export declare function renameWithRetryAsync(from: string, to: string, options?: Options & {
|
|
13
|
+
rename?: (from: string, to: string) => Promise<void>;
|
|
14
|
+
copy?: (from: string, to: string) => Promise<void>;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { copyFileSync, renameSync } from "node:fs";
|
|
2
|
+
import { copyFile, rename } from "node:fs/promises";
|
|
3
|
+
import { removeScratch } from "./removeScratch.js";
|
|
4
|
+
// Windows reports EPERM/EACCES/EBUSY while antivirus or a just-exited process still holds the
|
|
5
|
+
// file; the hold clears within milliseconds. Total wait before giving up: about 1.3 s.
|
|
6
|
+
const RETRYABLE = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
7
|
+
const BACKOFF_MS = [10, 20, 40, 80, 160, 320, 640];
|
|
8
|
+
function isRetryable(error, platform) {
|
|
9
|
+
if (platform !== "win32")
|
|
10
|
+
return false;
|
|
11
|
+
return RETRYABLE.has(error?.code ?? "");
|
|
12
|
+
}
|
|
13
|
+
/** What a refused rename does next: wait and try again, fall back to a copy, or give up with the error. */
|
|
14
|
+
function nextStep(error, attempt, options, platform) {
|
|
15
|
+
if (!isRetryable(error, platform))
|
|
16
|
+
return "throw";
|
|
17
|
+
if (attempt < BACKOFF_MS.length)
|
|
18
|
+
return "wait";
|
|
19
|
+
return options.copyFallback ? "copy" : "throw";
|
|
20
|
+
}
|
|
21
|
+
function sleepSync(ms) {
|
|
22
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
23
|
+
}
|
|
24
|
+
/** `renameSync` that rides out Windows' transient file locks; other platforms and errors throw at once. */
|
|
25
|
+
export function renameWithRetry(from, to, options = {}) {
|
|
26
|
+
const doRename = options.rename ?? renameSync;
|
|
27
|
+
const doCopy = options.copy ?? copyFileSync;
|
|
28
|
+
const platform = options.platform ?? process.platform;
|
|
29
|
+
for (let attempt = 0;; attempt++) {
|
|
30
|
+
try {
|
|
31
|
+
doRename(from, to);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const step = nextStep(error, attempt, options, platform);
|
|
36
|
+
if (step === "throw")
|
|
37
|
+
throw error;
|
|
38
|
+
if (step === "wait") {
|
|
39
|
+
sleepSync(BACKOFF_MS[attempt]);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
doCopy(from, to);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
removeScratch(from);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Async twin of {@link renameWithRetry}. */
|
|
54
|
+
export async function renameWithRetryAsync(from, to, options = {}) {
|
|
55
|
+
const doRename = options.rename ?? rename;
|
|
56
|
+
const doCopy = options.copy ?? copyFile;
|
|
57
|
+
const platform = options.platform ?? process.platform;
|
|
58
|
+
for (let attempt = 0;; attempt++) {
|
|
59
|
+
try {
|
|
60
|
+
await doRename(from, to);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const step = nextStep(error, attempt, options, platform);
|
|
65
|
+
if (step === "throw")
|
|
66
|
+
throw error;
|
|
67
|
+
if (step === "wait") {
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, BACKOFF_MS[attempt]));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
await doCopy(from, to);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
removeScratch(from);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
* chain the color-grading runtime uses: `__hfVariablesByComp[compId]` for
|
|
22
22
|
* inlined sub-compositions, then the top-level merged `getVariables()`.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
* external / template sub-compositions (their
|
|
26
|
-
* values don't exist at init). Idempotent:
|
|
27
|
-
* values.
|
|
24
|
+
* Text is bound before composition scripts run; all of it at init and after
|
|
25
|
+
* the composition loader inlines external / template sub-compositions (their
|
|
26
|
+
* DOM and per-instance scoped values don't exist at init). Idempotent:
|
|
27
|
+
* re-applying writes the same values.
|
|
28
28
|
*/
|
|
29
29
|
/**
|
|
30
30
|
* Strip characters that could smuggle additional declarations or markup out of
|
|
@@ -45,3 +45,5 @@ export declare function sanitizeCssValue(value: string): string;
|
|
|
45
45
|
export declare function unproxiedMediaSrc(el: Element): string | null;
|
|
46
46
|
/** Applies the bindings in `doc`, or only those inside `within` and `within` itself. */
|
|
47
47
|
export declare function applyVariableBindings(doc: Document, within?: Element): void;
|
|
48
|
+
/** Binds only text, in `doc` or inside `within`: what composition scripts read before they run. */
|
|
49
|
+
export declare function applyTextBindings(doc: Document, within?: Element): void;
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
* chain the color-grading runtime uses: `__hfVariablesByComp[compId]` for
|
|
22
22
|
* inlined sub-compositions, then the top-level merged `getVariables()`.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
* external / template sub-compositions (their
|
|
26
|
-
* values don't exist at init). Idempotent:
|
|
27
|
-
* values.
|
|
24
|
+
* Text is bound before composition scripts run; all of it at init and after
|
|
25
|
+
* the composition loader inlines external / template sub-compositions (their
|
|
26
|
+
* DOM and per-instance scoped values don't exist at init). Idempotent:
|
|
27
|
+
* re-applying writes the same values.
|
|
28
28
|
*/
|
|
29
29
|
import { readVariablesForElement } from "./variableScope.js";
|
|
30
30
|
import { isScalarVariableValue as isScalar, isSafeMediaUrl, } from "@hyperframes/parsers/composition";
|
|
@@ -154,12 +154,13 @@ function variableSrcFor(el, cache, warn = true) {
|
|
|
154
154
|
export function unproxiedMediaSrc(el) {
|
|
155
155
|
return variableSrcFor(el, new Map(), false) ?? unproxiedSrc(el);
|
|
156
156
|
}
|
|
157
|
+
const matching = (doc, within, selector) => within
|
|
158
|
+
? [...(within.matches(selector) ? [within] : []), ...within.querySelectorAll(selector)]
|
|
159
|
+
: Array.from(doc.querySelectorAll(selector));
|
|
157
160
|
/** Applies the bindings in `doc`, or only those inside `within` and `within` itself. */
|
|
158
161
|
export function applyVariableBindings(doc, within) {
|
|
159
162
|
const cache = new Map();
|
|
160
|
-
const all = (selector) => within
|
|
161
|
-
? [...(within.matches(selector) ? [within] : []), ...within.querySelectorAll(selector)]
|
|
162
|
-
: Array.from(doc.querySelectorAll(selector));
|
|
163
|
+
const all = (selector) => matching(doc, within, selector);
|
|
163
164
|
const topRoot = within ? null : findTopRoot(doc);
|
|
164
165
|
applyCssCustomProperties([...(topRoot ? [topRoot] : []), ...all("[data-composition-id]")], cache);
|
|
165
166
|
for (const el of all("[data-var-src]")) {
|
|
@@ -167,12 +168,23 @@ export function applyVariableBindings(doc, within) {
|
|
|
167
168
|
if (url !== null && unproxiedSrc(el) !== url)
|
|
168
169
|
el.setAttribute("src", url);
|
|
169
170
|
}
|
|
170
|
-
|
|
171
|
+
bindText(all("[data-var-text]"), cache);
|
|
172
|
+
}
|
|
173
|
+
/** Binds only text, in `doc` or inside `within`: what composition scripts read before they run. */
|
|
174
|
+
export function applyTextBindings(doc, within) {
|
|
175
|
+
bindText(matching(doc, within, "[data-var-text]"), new Map());
|
|
176
|
+
}
|
|
177
|
+
const textWrittenOnce = new WeakSet();
|
|
178
|
+
/** Writes each binding once: a script may then reshape the text, which a second write would duplicate. */
|
|
179
|
+
function bindText(elements, cache) {
|
|
180
|
+
for (const el of elements) {
|
|
171
181
|
const id = el.getAttribute("data-var-text")?.trim();
|
|
172
|
-
if (!id)
|
|
182
|
+
if (!id || textWrittenOnce.has(el))
|
|
173
183
|
continue;
|
|
174
184
|
const value = valuesForElement(el, cache)[id];
|
|
175
|
-
if (isScalar(value))
|
|
176
|
-
|
|
185
|
+
if (!isScalar(value))
|
|
186
|
+
continue;
|
|
187
|
+
setOwnTextPreservingChildren(el, String(value));
|
|
188
|
+
textWrittenOnce.add(el);
|
|
177
189
|
}
|
|
178
190
|
}
|
package/dist/runtime/clock.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { timeAtSourceTime } from "../speedRamp.js";
|
|
2
|
-
import { MEDIA_HARD_SYNC_SECONDS } from "./
|
|
2
|
+
import { MEDIA_HARD_SYNC_SECONDS } from "./mediaSteer.js";
|
|
3
3
|
import { playRangeHoldTime } from "./protocol.js";
|
|
4
4
|
/** GSAP's own `lagSmoothing` default — see the PR body for why this clock needs its own copy. */
|
|
5
5
|
const STALL_THRESHOLD_MS = 500;
|
|
@@ -20,18 +20,19 @@
|
|
|
20
20
|
* const { title = "Untitled", theme = "light" } = getVariables<MyVars>();
|
|
21
21
|
*/
|
|
22
22
|
import { cssVariableName, detectSlugCollisions } from "../tokenSlug.js";
|
|
23
|
+
import { scopedVariablesFor } from "./variableScope.js";
|
|
23
24
|
export function getVariables() {
|
|
24
25
|
if (typeof document === "undefined")
|
|
25
26
|
return {};
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// render-time overrides.
|
|
27
|
+
// <html> first, then any element carrying the attribute (later declarers win), then render-time
|
|
28
|
+
// overrides. A sub-composition's declaration is its own scope's, read from its scoped table.
|
|
29
29
|
const declarers = new Set();
|
|
30
30
|
if (document.documentElement?.hasAttribute("data-composition-variables")) {
|
|
31
31
|
declarers.add(document.documentElement);
|
|
32
32
|
}
|
|
33
33
|
for (const el of Array.from(document.querySelectorAll("[data-composition-variables]"))) {
|
|
34
|
-
|
|
34
|
+
if (!scopedVariablesFor(el))
|
|
35
|
+
declarers.add(el);
|
|
35
36
|
}
|
|
36
37
|
const declaredDefaults = {};
|
|
37
38
|
for (const el of declarers) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Drift a playing element may carry before sync pulls it back onto the playhead. */
|
|
2
2
|
export declare const MEDIA_SYNC_TOLERANCE_SECONDS = 0.04;
|
|
3
|
+
export declare const MEDIA_HARD_SYNC_SECONDS = 0.5;
|
|
3
4
|
/** Rate or seek for media `offset` s behind (+) or ahead (-); a lag still growing (`settled` false) never seeks. */
|
|
4
5
|
export declare function steerMedia(el: HTMLMediaElement, offset: number, baseRate: number, settled: boolean, audioOnly?: boolean): {
|
|
5
6
|
rate: number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Drift a playing element may carry before sync pulls it back onto the playhead. */
|
|
2
2
|
export const MEDIA_SYNC_TOLERANCE_SECONDS = 0.04;
|
|
3
|
+
export const MEDIA_HARD_SYNC_SECONDS = 0.5;
|
|
3
4
|
// Steered by rate, not seeked (a seek resets a video's decoder and clicks in audio); steps keep rate writes rare.
|
|
4
5
|
const MEDIA_STEER = 0.03;
|
|
5
6
|
const MEDIA_STEER_STEPS = [
|
package/dist/runtime/state.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { RuntimeDeterministicAdapter, RuntimeTimelineLike } from "./types.js";
|
|
2
|
-
import type { RuntimeMediaClip } from "./media.js";
|
|
1
|
+
import type { RuntimeDeterministicAdapter, RuntimeMediaClip, RuntimeTimelineLike } from "./types.js";
|
|
3
2
|
import type { TransportClock } from "./clock.js";
|
|
4
3
|
export type RuntimeState = {
|
|
5
4
|
capturedTimeline: RuntimeTimelineLike | null;
|
package/dist/runtime/timeline.js
CHANGED
|
@@ -3,7 +3,7 @@ import { findRootCompositionElement, parseCompositionDimension } from "./composi
|
|
|
3
3
|
import { AUTHORED_DURATION_ATTR, AUTHORED_END_ATTR, resolveAuthoredTimingWindow, } from "./authoredTiming.js";
|
|
4
4
|
import { swallow } from "./diagnostics.js";
|
|
5
5
|
import { readTimelineDurationSeconds } from "./timelineDuration.js";
|
|
6
|
-
import { readElementPlaybackRate,
|
|
6
|
+
import { readElementPlaybackRate, readMediaStart } from "./playbackRate.js";
|
|
7
7
|
import { parseStrictFiniteTimingNumber, resolveMediaElementDurationSeconds, resolveNaturalMediaTimelineDuration, resolveTimedImageDurationSeconds, } from "./playbackRate.js";
|
|
8
8
|
import { resolveCssStackingContextId } from "./stackingContext.js";
|
|
9
9
|
import { createRuntimeStartTimeResolver } from "./startResolver.js";
|
|
@@ -426,7 +426,7 @@ export function collectRuntimeTimelinePayload(params) {
|
|
|
426
426
|
parentCompositionId: compositionContext.parentCompositionId,
|
|
427
427
|
nodePath: null,
|
|
428
428
|
compositionSrc: toAbsoluteAssetUrl(node.getAttribute("data-composition-src")),
|
|
429
|
-
playbackStart:
|
|
429
|
+
playbackStart: readMediaStart(node),
|
|
430
430
|
playbackRate: readElementPlaybackRate(node),
|
|
431
431
|
assetUrl: resolveNodeAssetUrl(node),
|
|
432
432
|
timelineRole: node.getAttribute("data-timeline-role"),
|
|
@@ -560,7 +560,7 @@ export function collectRuntimeTimelinePayload(params) {
|
|
|
560
560
|
parentCompositionId: rootCompositionIdForGsap,
|
|
561
561
|
nodePath: null,
|
|
562
562
|
compositionSrc: null,
|
|
563
|
-
playbackStart:
|
|
563
|
+
playbackStart: readMediaStart(el),
|
|
564
564
|
playbackRate: readElementPlaybackRate(el),
|
|
565
565
|
assetUrl: null,
|
|
566
566
|
timelineRole: el.getAttribute("data-timeline-role"),
|
|
@@ -622,7 +622,7 @@ export function collectRuntimeTimelinePayload(params) {
|
|
|
622
622
|
parentCompositionId: rootCompositionIdForGsap,
|
|
623
623
|
nodePath: null,
|
|
624
624
|
compositionSrc: null,
|
|
625
|
-
playbackStart:
|
|
625
|
+
playbackStart: readMediaStart(el),
|
|
626
626
|
playbackRate: readElementPlaybackRate(el),
|
|
627
627
|
assetUrl: null,
|
|
628
628
|
timelineRole,
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { HfColorGradingTarget } from "../colorGrading.js";
|
|
2
2
|
import type { RuntimeAnalyticsEvent } from "./analytics.js";
|
|
3
|
+
import type { AudioFades } from "../audioFade.js";
|
|
4
|
+
import type { RateSpec } from "../speedRamp.js";
|
|
5
|
+
import type { VolumeKeyframe } from "./mediaVolumeEnvelope.js";
|
|
3
6
|
export type RuntimeJson = string | number | boolean | null | RuntimeJson[] | {
|
|
4
7
|
[key: string]: RuntimeJson;
|
|
5
8
|
};
|
|
@@ -378,4 +381,22 @@ type RuntimeDataControlFields = {
|
|
|
378
381
|
};
|
|
379
382
|
type RuntimeBridgeControlAction = RuntimeBridgeControlActionBase | "set-runtime-data" | "clear-runtime-data";
|
|
380
383
|
export type RuntimeBridgeControlMessage = RuntimeBridgeControlMessageBase & RuntimeDataControlFields;
|
|
384
|
+
export type RuntimeMediaClip = {
|
|
385
|
+
el: HTMLVideoElement | HTMLAudioElement;
|
|
386
|
+
start: number;
|
|
387
|
+
/** The clip's own t=0 when a host's in-point cut its head; source time, lanes and fades run from it. */
|
|
388
|
+
origin?: number;
|
|
389
|
+
mediaStart: number;
|
|
390
|
+
duration: number;
|
|
391
|
+
end: number;
|
|
392
|
+
volume: number | null;
|
|
393
|
+
playbackRate: number;
|
|
394
|
+
/** The rate lane when the clip has one; otherwise `playbackRate`. */
|
|
395
|
+
rate?: RateSpec;
|
|
396
|
+
loop: boolean;
|
|
397
|
+
sourceDuration: number | null;
|
|
398
|
+
/** Probed from the GSAP timeline as the renderer does; when set they drive volume, not `data-volume`. */
|
|
399
|
+
volumeKeyframes?: VolumeKeyframe[];
|
|
400
|
+
fades?: AudioFades;
|
|
401
|
+
};
|
|
381
402
|
export {};
|
|
@@ -5,4 +5,6 @@
|
|
|
5
5
|
* runtime consumer of variables (color grading, declarative bindings) so the
|
|
6
6
|
* scope chain can never diverge between channels.
|
|
7
7
|
*/
|
|
8
|
+
/** The per-instance values of the inlined sub-composition an element belongs to, if any. */
|
|
9
|
+
export declare function scopedVariablesFor(element: Element): Record<string, unknown> | undefined;
|
|
8
10
|
export declare function readVariablesForElement(element: Element): Record<string, unknown>;
|
|
@@ -5,11 +5,17 @@
|
|
|
5
5
|
* runtime consumer of variables (color grading, declarative bindings) so the
|
|
6
6
|
* scope chain can never diverge between channels.
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/** The per-instance values of the inlined sub-composition an element belongs to, if any. */
|
|
9
|
+
export function scopedVariablesFor(element) {
|
|
10
10
|
const scope = element.closest("[data-composition-id]");
|
|
11
11
|
const compositionId = scope?.getAttribute("data-composition-id")?.trim() ?? "";
|
|
12
|
-
|
|
12
|
+
return compositionId
|
|
13
|
+
? window.__hfVariablesByComp?.[compositionId]
|
|
14
|
+
: undefined;
|
|
15
|
+
}
|
|
16
|
+
export function readVariablesForElement(element) {
|
|
17
|
+
const win = window;
|
|
18
|
+
const scoped = scopedVariablesFor(element);
|
|
13
19
|
if (scoped)
|
|
14
20
|
return scoped;
|
|
15
21
|
const fromHelper = win.__hyperframes?.getVariables?.();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.209",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -64,6 +64,14 @@
|
|
|
64
64
|
"import": "./dist/removeTree.js",
|
|
65
65
|
"types": "./dist/removeTree.d.ts"
|
|
66
66
|
},
|
|
67
|
+
"./rename-retry": {
|
|
68
|
+
"import": "./dist/renameWithRetry.js",
|
|
69
|
+
"types": "./dist/renameWithRetry.d.ts"
|
|
70
|
+
},
|
|
71
|
+
"./remove-scratch": {
|
|
72
|
+
"import": "./dist/removeScratch.js",
|
|
73
|
+
"types": "./dist/removeScratch.d.ts"
|
|
74
|
+
},
|
|
67
75
|
"./project-folders": {
|
|
68
76
|
"import": "./dist/projectFolders.js",
|
|
69
77
|
"types": "./dist/projectFolders.d.ts"
|
|
@@ -417,9 +425,9 @@
|
|
|
417
425
|
"linkedom": "^0.18.12",
|
|
418
426
|
"postcss": "^8.5.8",
|
|
419
427
|
"wawoff2": "^2.0.1",
|
|
420
|
-
"@hyperframes/lint": "0.8.
|
|
421
|
-
"@hyperframes/
|
|
422
|
-
"@hyperframes/
|
|
428
|
+
"@hyperframes/lint": "0.8.209",
|
|
429
|
+
"@hyperframes/parsers": "0.8.209",
|
|
430
|
+
"@hyperframes/studio-server": "0.8.209"
|
|
423
431
|
},
|
|
424
432
|
"devDependencies": {
|
|
425
433
|
"@fontsource/archivo-black": "^5.2.8",
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
export interface HfSeekEventDetail {
|
|
2
|
-
time: number;
|
|
3
|
-
waitUntil: (promise: PromiseLike<unknown>) => void;
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* Join `promise` to the seek-completion barrier: `waitForSeekCompletion` will
|
|
7
|
-
* not resolve until it settles, and its rejection (the first one) becomes the
|
|
8
|
-
* barrier's.
|
|
9
|
-
*
|
|
10
|
-
* Exported because the `"hf-seek"` event's synchronous `waitUntil()` is not
|
|
11
|
-
* the only asynchronous seek work a frame can owe. `runtime/vfx.ts` registers
|
|
12
|
-
* its preview-side capture here, so the CLI seek paths that already await this
|
|
13
|
-
* barrier wait for a `self` chain's paint instead of racing it.
|
|
14
|
-
*/
|
|
15
|
-
export declare function registerSeekCompletion(promise: Promise<unknown>, kind?: "video-frame"): void;
|
|
16
|
-
export declare function dispatchSeekEvent(time: number): void;
|
|
17
|
-
/**
|
|
18
|
-
* Force-dispatch a `"hf-seek"` event even if `time` equals the last dispatched
|
|
19
|
-
* time, bypassing the dedup guard.
|
|
20
|
-
*
|
|
21
|
-
* Needed for the post-video-injection GPU re-render: the engine seeks to time
|
|
22
|
-
* T (GPU adapters render once, before video frames are injected), then injects
|
|
23
|
-
* the decoded `__render_frame__` images, then must re-render GPU compositions
|
|
24
|
-
* at the *same* T so they re-upload textures from the now-present frames. The
|
|
25
|
-
* normal dedup would swallow that second dispatch.
|
|
26
|
-
*/
|
|
27
|
-
export declare function forceDispatchSeekEvent(time: number): void;
|
|
28
|
-
export declare function isSeekCompletionBarrierActive(): boolean;
|
|
29
|
-
/** Settles with every pending completion, without consuming a failure the barrier reports. */
|
|
30
|
-
export declare function seekCompletionsSettled(): Promise<void>;
|
|
31
|
-
export declare function hasPendingSeekCompletion(): boolean;
|
|
32
|
-
/** `videoFrames: false` skips only the video-frame holds: a geometry read needs every other completion. */
|
|
33
|
-
export declare function waitForSeekCompletion(options?: {
|
|
34
|
-
videoFrames?: boolean;
|
|
35
|
-
}): Promise<void>;
|
|
36
|
-
/** Reset internal state — used in tests to prevent cross-test contamination. */
|
|
37
|
-
export declare function resetSeekDispatchState(): void;
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { swallow } from "../diagnostics.js";
|
|
2
|
-
/**
|
|
3
|
-
* Shared, deduplicated `"hf-seek"` CustomEvent dispatcher for GPU adapters.
|
|
4
|
-
*
|
|
5
|
-
* Both the Three.js and TypeGPU adapters dispatch the same `"hf-seek"` event
|
|
6
|
-
* so that compositions need not know which GPU library they're paired with.
|
|
7
|
-
* Without deduplication, a seek to time T would fire two events (one from each
|
|
8
|
-
* adapter), doubling per-scrub work in any composition that has both present.
|
|
9
|
-
*
|
|
10
|
-
* This module deduplicates by tracking the last dispatched time. If the same
|
|
11
|
-
* time value is dispatched twice in the same synchronous call stack (e.g. two
|
|
12
|
-
* adapters both calling `dispatchSeekEvent(5.0)` without yielding), only the
|
|
13
|
-
* first call fires the event.
|
|
14
|
-
*
|
|
15
|
-
* The deduplication is intentionally coarse (exact float equality). Adapter
|
|
16
|
-
* seek paths clamp and normalise time before calling this function, so the
|
|
17
|
-
* values that arrive here are already stable.
|
|
18
|
-
*/
|
|
19
|
-
let _lastDispatchedTime = -1;
|
|
20
|
-
let _pendingCompletions = new Set();
|
|
21
|
-
let _videoFrameCompletions = new WeakSet();
|
|
22
|
-
let _pendingFailure;
|
|
23
|
-
let _activeCompletionBarriers = 0;
|
|
24
|
-
function dispatch(time) {
|
|
25
|
-
const pending = [];
|
|
26
|
-
let accepting = true;
|
|
27
|
-
const detail = {
|
|
28
|
-
time,
|
|
29
|
-
waitUntil: (promise) => {
|
|
30
|
-
if (!accepting) {
|
|
31
|
-
throw new Error("hf-seek waitUntil() must be called synchronously from the event listener");
|
|
32
|
-
}
|
|
33
|
-
pending.push(promise);
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
try {
|
|
37
|
-
window.dispatchEvent(new CustomEvent("hf-seek", { detail }));
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
swallow("runtime.adapters.seek-dispatch.site1", err);
|
|
41
|
-
}
|
|
42
|
-
finally {
|
|
43
|
-
accepting = false;
|
|
44
|
-
}
|
|
45
|
-
if (pending.length === 0)
|
|
46
|
-
return;
|
|
47
|
-
registerSeekCompletion(Promise.all(pending));
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Join `promise` to the seek-completion barrier: `waitForSeekCompletion` will
|
|
51
|
-
* not resolve until it settles, and its rejection (the first one) becomes the
|
|
52
|
-
* barrier's.
|
|
53
|
-
*
|
|
54
|
-
* Exported because the `"hf-seek"` event's synchronous `waitUntil()` is not
|
|
55
|
-
* the only asynchronous seek work a frame can owe. `runtime/vfx.ts` registers
|
|
56
|
-
* its preview-side capture here, so the CLI seek paths that already await this
|
|
57
|
-
* barrier wait for a `self` chain's paint instead of racing it.
|
|
58
|
-
*/
|
|
59
|
-
export function registerSeekCompletion(promise, kind) {
|
|
60
|
-
const completion = promise
|
|
61
|
-
.then(() => ({ status: "fulfilled" }))
|
|
62
|
-
.catch((reason) => ({ status: "rejected", reason }));
|
|
63
|
-
_pendingCompletions.add(completion);
|
|
64
|
-
if (kind === "video-frame")
|
|
65
|
-
_videoFrameCompletions.add(completion);
|
|
66
|
-
void completion.then((result) => {
|
|
67
|
-
if (!_pendingCompletions.delete(completion))
|
|
68
|
-
return;
|
|
69
|
-
if (result.status === "rejected" && _pendingFailure === undefined) {
|
|
70
|
-
_pendingFailure = { reason: result.reason };
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
export function dispatchSeekEvent(time) {
|
|
75
|
-
if (time === _lastDispatchedTime)
|
|
76
|
-
return;
|
|
77
|
-
_lastDispatchedTime = time;
|
|
78
|
-
dispatch(time);
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Force-dispatch a `"hf-seek"` event even if `time` equals the last dispatched
|
|
82
|
-
* time, bypassing the dedup guard.
|
|
83
|
-
*
|
|
84
|
-
* Needed for the post-video-injection GPU re-render: the engine seeks to time
|
|
85
|
-
* T (GPU adapters render once, before video frames are injected), then injects
|
|
86
|
-
* the decoded `__render_frame__` images, then must re-render GPU compositions
|
|
87
|
-
* at the *same* T so they re-upload textures from the now-present frames. The
|
|
88
|
-
* normal dedup would swallow that second dispatch.
|
|
89
|
-
*/
|
|
90
|
-
export function forceDispatchSeekEvent(time) {
|
|
91
|
-
_lastDispatchedTime = time;
|
|
92
|
-
dispatch(time);
|
|
93
|
-
}
|
|
94
|
-
export function isSeekCompletionBarrierActive() {
|
|
95
|
-
return _activeCompletionBarriers > 0;
|
|
96
|
-
}
|
|
97
|
-
/** Settles with every pending completion, without consuming a failure the barrier reports. */
|
|
98
|
-
export async function seekCompletionsSettled() {
|
|
99
|
-
for (let pending = [..._pendingCompletions]; pending.length > 0; pending = [..._pendingCompletions]) {
|
|
100
|
-
await Promise.all(pending);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
export function hasPendingSeekCompletion() {
|
|
104
|
-
return _pendingCompletions.size > 0;
|
|
105
|
-
}
|
|
106
|
-
/** `videoFrames: false` skips only the video-frame holds: a geometry read needs every other completion. */
|
|
107
|
-
export async function waitForSeekCompletion(options) {
|
|
108
|
-
const awaited = () => [..._pendingCompletions].filter((completion) => options?.videoFrames !== false || !_videoFrameCompletions.has(completion));
|
|
109
|
-
_activeCompletionBarriers += 1;
|
|
110
|
-
let failed = _pendingFailure;
|
|
111
|
-
try {
|
|
112
|
-
// Let concurrently started barriers snapshot the same retained failure
|
|
113
|
-
// before either one consumes it.
|
|
114
|
-
await Promise.resolve();
|
|
115
|
-
for (let pending = awaited(); pending.length > 0; pending = awaited()) {
|
|
116
|
-
const results = await Promise.all(pending);
|
|
117
|
-
const rejected = results.find((result) => result.status === "rejected");
|
|
118
|
-
if (failed === undefined && rejected?.status === "rejected") {
|
|
119
|
-
failed = { reason: rejected.reason };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
const retainedFailure = _pendingFailure;
|
|
123
|
-
if (failed === undefined) {
|
|
124
|
-
failed = retainedFailure;
|
|
125
|
-
}
|
|
126
|
-
if (_pendingFailure === retainedFailure) {
|
|
127
|
-
_pendingFailure = undefined;
|
|
128
|
-
}
|
|
129
|
-
if (failed)
|
|
130
|
-
throw failed.reason;
|
|
131
|
-
}
|
|
132
|
-
finally {
|
|
133
|
-
_activeCompletionBarriers -= 1;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
/** Reset internal state — used in tests to prevent cross-test contamination. */
|
|
137
|
-
export function resetSeekDispatchState() {
|
|
138
|
-
_lastDispatchedTime = -1;
|
|
139
|
-
_pendingCompletions = new Set();
|
|
140
|
-
_videoFrameCompletions = new WeakSet();
|
|
141
|
-
_pendingFailure = undefined;
|
|
142
|
-
_activeCompletionBarriers = 0;
|
|
143
|
-
}
|