@shopify/react-native-skia
Version:
High-performance React Native Graphics using Skia
266 lines (230 loc) • 6.4 kB
text/typescript
/*global NodeJS*/
import type { HostConfig } from "react-reconciler";
import { DefaultEventPriority } from "react-reconciler/constants";
import type { NodeType, Node } from "../dom/types";
import type { SkiaValue } from "../values";
import {
bindReanimatedProps,
extractReanimatedProps,
unbindReanimatedNode,
} from "../external/reanimated/renderHelpers";
import type { Container } from "./Container";
import { createNode } from "./HostComponents";
import type { AnimatedProps } from "./processors";
import { isSelector, isValue } from "./processors";
import { mapKeys, shallowEq } from "./typeddash";
const DEBUG = false;
export const debug = (...args: Parameters<typeof console.log>) => {
if (DEBUG) {
console.log(...args);
}
};
type Instance = Node<unknown>;
type Props = object;
type TextInstance = Node<unknown>;
type SuspenseInstance = Instance;
type HydratableInstance = Instance;
type PublicInstance = Instance;
type HostContext = null;
type UpdatePayload = Container;
type ChildSet = unknown;
type TimeoutHandle = NodeJS.Timeout;
type NoTimeout = -1;
type SkiaHostConfig = HostConfig<
NodeType,
Props,
Container,
Instance,
TextInstance,
SuspenseInstance,
HydratableInstance,
PublicInstance,
HostContext,
UpdatePayload,
ChildSet,
TimeoutHandle,
NoTimeout
>;
const appendNode = (parent: Node<unknown>, child: Node<unknown>) => {
parent.addChild(child);
};
const removeNode = (parent: Node<unknown>, child: Node<unknown>) => {
unbindReanimatedNode(child);
return parent.removeChild(child);
};
const insertBefore = (
parent: Node<unknown>,
child: Node<unknown>,
before: Node<unknown>
) => {
parent.insertChildBefore(child, before);
};
export const skHostConfig: SkiaHostConfig = {
/**
* This function is used by the reconciler in order to calculate current time for prioritising work.
*/
now: Date.now,
supportsMutation: true,
isPrimaryRenderer: false,
supportsPersistence: false,
supportsHydration: false,
//supportsMicrotask: true,
scheduleTimeout: setTimeout,
cancelTimeout: clearTimeout,
noTimeout: -1,
appendChildToContainer(container, child) {
debug("appendChildToContainer", container, child);
appendNode(container.root, child);
},
appendChild(parent, child) {
debug("appendChild", parent, child);
appendNode(parent, child);
},
getRootHostContext: (_rootContainerInstance: Container) => {
debug("getRootHostContext");
return null;
},
getChildHostContext(_parentHostContext, _type, _rootContainerInstance) {
debug("getChildHostContext");
return null;
},
shouldSetTextContent(_type, _props) {
return false;
},
createTextInstance(
_text,
_rootContainerInstance,
_hostContext,
_internalInstanceHandle
) {
debug("createTextInstance");
// return SpanNode({}, text) as SkNode;
throw new Error("Text nodes are not supported yet");
},
createInstance(
type,
pristineProps,
container,
_hostContext,
_internalInstanceHandle
) {
debug("createInstance", type);
const [props, reanimatedProps] = extractReanimatedProps(pristineProps);
const node = createNode(container, type, materialize(props));
bindReanimatedProps(container, node, reanimatedProps);
container.depMgr.subscribeNode(node, props);
return node;
},
appendInitialChild(parentInstance, child) {
debug("appendInitialChild");
appendNode(parentInstance, child);
},
finalizeInitialChildren(
parentInstance,
_type,
_props,
_rootContainerInstance,
_hostContext
) {
debug("finalizeInitialChildren", parentInstance);
return false;
},
commitMount() {
// if finalizeInitialChildren = true
debug("commitMount");
},
prepareForCommit(_containerInfo) {
debug("prepareForCommit");
return null;
},
resetAfterCommit(container) {
debug("resetAfterCommit");
container.depMgr.update();
container.redraw();
},
getPublicInstance(node: Instance) {
debug("getPublicInstance");
return node;
},
prepareUpdate: (
_instance,
type,
oldProps,
newProps,
rootContainerInstance,
_hostContext
) => {
debug("prepareUpdate");
const propsAreEqual = shallowEq(oldProps, newProps);
if (propsAreEqual) {
return null;
}
debug("update ", type);
return rootContainerInstance;
},
commitUpdate(
instance,
updatePayload,
type,
prevProps,
nextProps,
_internalHandle
) {
debug("commitUpdate: ", type);
if (shallowEq(prevProps, nextProps)) {
return;
}
const [props, reanimatedProps] = extractReanimatedProps(nextProps);
updatePayload.depMgr.unsubscribeNode(instance);
instance.setProps(materialize(props));
bindReanimatedProps(updatePayload, instance, reanimatedProps);
updatePayload.depMgr.subscribeNode(instance, props);
},
commitTextUpdate: (
_textInstance: TextInstance,
_oldText: string,
_newText: string
) => {
// textInstance.instance = newText;
},
clearContainer: (container) => {
debug("clearContainer");
container.root.children().forEach((child) => {
container.root.removeChild(child);
});
},
preparePortalMount: () => {
debug("preparePortalMount");
},
removeChild: (parent, child) => {
removeNode(parent, child);
},
removeChildFromContainer: (container, child) => {
removeNode(container.root, child);
},
insertInContainerBefore: (container, child, before) => {
insertBefore(container.root, child, before);
},
insertBefore: (parent, child, before) => {
insertBefore(parent, child, before);
},
// see https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r916356874
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
getCurrentEventPriority: () => DefaultEventPriority,
beforeActiveInstanceBlur: () => {},
afterActiveInstanceBlur: () => {},
detachDeletedInstance: () => {},
};
const materialize = <P>(props: AnimatedProps<P>) => {
const result = { ...props } as P;
mapKeys(props).forEach((key) => {
const prop = props[key];
if (isValue(prop)) {
result[key] = (prop as SkiaValue<P[typeof key]>).current;
} else if (isSelector(prop)) {
result[key] = prop.selector(prop.value.current) as P[typeof key];
}
});
return result;
};