Holding a frame
Hold a frame while a texture, font or shader loads, and carry the frame context into another React reconciler.
Hosts and renderers draw synchronously, but some of what they draw arrives later: a texture,
a font, a shader compile. useHold(label, when) from @motionactor/renderers/react holds the
frame while when is true, and the host waits for it before showing or capturing the frame:
import { function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.useEffect, function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)Returns a stateful value, and a function to update it.useState } from "react";
import { function useHold(label: string, when: boolean): voiduseHold } from "@motionactor/renderers/react";
export function function useTexture(url: string): ImageBitmap | nulluseTexture(url: stringurl: string) {
const [const bitmap: ImageBitmap | nullbitmap, const setBitmap: React.Dispatch<React.SetStateAction<ImageBitmap | null>>setBitmap] = useState<ImageBitmap | null>(initialState: ImageBitmap | (() => ImageBitmap | null) | null): [ImageBitmap | null, React.Dispatch<React.SetStateAction<ImageBitmap | null>>] (+1 overload)Returns a stateful value, and a function to update it.useState<ImageBitmap | null>(null);
function useHold(label: string, when: boolean): voiduseHold(`texture ${url: stringurl}`, const bitmap: ImageBitmap | nullbitmap === null);
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.useEffect(() => {
let let live: booleanlive = true;
void function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)fetch(url: stringurl)
.Promise<Response>.then<Blob, never>(onfulfilled?: ((value: Response) => Blob | PromiseLike<Blob>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<Blob>Attaches callbacks for the resolution and/or rejection of the Promise.then((response: Responseresponse) => response: Responseresponse.Body.blob(): Promise<Blob>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob)blob())
.Promise<Blob>.then<ImageBitmap, never>(onfulfilled?: ((value: Blob) => ImageBitmap | PromiseLike<ImageBitmap>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<ImageBitmap>Attaches callbacks for the resolution and/or rejection of the Promise.then(function createImageBitmap(image: ImageBitmapSource, options?: ImageBitmapOptions): Promise<ImageBitmap> (+1 overload)[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/createImageBitmap)createImageBitmap)
.Promise<ImageBitmap>.then<false | void, never>(onfulfilled?: ((value: ImageBitmap) => false | void | PromiseLike<false | void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<false | void>Attaches callbacks for the resolution and/or rejection of the Promise.then((next: ImageBitmapnext) => let live: booleanlive && const setBitmap: (value: React.SetStateAction<ImageBitmap | null>) => voidsetBitmap(next: ImageBitmapnext));
return () => {
let live: booleanlive = false;
};
}, [url: stringurl]);
return const bitmap: ImageBitmap | nullbitmap;
}
The label is what a capture timeout reports, so name the thing being waited for.
Another React tree
The frame source and the gate reach components through React context. A surface with its own React reconciler, such as a React Three Fiber or a Skia canvas, starts a new tree where that context is gone. Read both outside the canvas and bind them again inside it:
import {
function FrameGateBinding({ gate, children }: FrameGateBindingProps): JSX.ElementFrameGateBinding,
function FrameSourceBinding({ source, children }: FrameSourceBindingProps): JSX.ElementFrameSourceBinding,
function useFrameGate(): FrameGateuseFrameGate,
function useFrameSource(): FrameSourceuseFrameSource,
} from "@motionactor/renderers/react";
function function CanvasHost({ children }: {
children: ReactNode;
}): JSX.Element
CanvasHost({ children: ReactNodechildren }: { children: ReactNodechildren: type ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<ReactNode> | React.ReactPortal | Promise<AwaitedReactNode> | null | undefinedRepresents all of the things React can render.
Where
{@link
ReactElement
}
only represents JSX, `ReactNode` represents everything that can be rendered.ReactNode }) {
const const source: FrameSourcesource = function useFrameSource(): FrameSourceuseFrameSource();
const const gate: FrameGategate = function useFrameGate(): FrameGateuseFrameGate();
return (
<function Canvas(props: {
children: ReactNode;
}): ReactNode
Canvas>
<function FrameSourceBinding({ source, children }: FrameSourceBindingProps): JSX.ElementFrameSourceBinding FrameSourceBindingProps.source: FrameSourcesource={const source: FrameSourcesource}>
<function FrameGateBinding({ gate, children }: FrameGateBindingProps): JSX.ElementFrameGateBinding FrameGateBindingProps.gate: FrameGategate={const gate: FrameGategate}>{children: ReactNodechildren}</function FrameGateBinding({ gate, children }: FrameGateBindingProps): JSX.ElementFrameGateBinding>
</function FrameSourceBinding({ source, children }: FrameSourceBindingProps): JSX.ElementFrameSourceBinding>
</function Canvas(props: {
children: ReactNode;
}): ReactNode
Canvas>
);
}
The Three.js and Skia backends both do this.