Skip to content

Commit

Permalink
Merge pull request #530 from Shopify/decouple-reconciller
Browse files Browse the repository at this point in the history
Decouple Skia from Reconciller
  • Loading branch information
chrfalch authored Jun 1, 2022
2 parents 9726c4a + 1b52680 commit 0d8454e
Show file tree
Hide file tree
Showing 134 changed files with 714 additions and 539 deletions.
23 changes: 6 additions & 17 deletions docs/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2735,9 +2735,8 @@
integrity sha512-W/J0fNYVO01tioHjvYWQ9m6RgndVtbElzYozBq1ZPrHO/iCzlqoySHl4gO/fpCl9QEFjvJfjPgtPMTMlsoq5DQ==

"@shopify/react-native-skia@link:../package":
version "0.1.100"
dependencies:
react-reconciler "^0.26.2"
version "0.0.0"
uid ""

"@sideway/address@^4.1.3":
version "4.1.3"
Expand Down Expand Up @@ -4089,20 +4088,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001272, caniuse-lite@^1.0.30001280:
version "1.0.30001283"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001283.tgz#8573685bdae4d733ef18f78d44ba0ca5fe9e896b"
integrity sha512-9RoKo841j1GQFSJz/nCXOj0sD7tHBtlowjYlrqIUS812x9/emfBLBt6IyMz1zIaYc/eRL8Cs6HPUVi2Hzq4sIg==

caniuse-lite@^1.0.30001312:
version "1.0.30001312"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz#e11eba4b87e24d22697dae05455d5aea28550d5f"
integrity sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==

caniuse-lite@^1.0.30001317:
version "1.0.30001327"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001327.tgz#c1546d7d7bb66506f0ccdad6a7d07fc6d668c858"
integrity sha512-1/Cg4jlD9qjZzhbzkzEaAC2JHsP0WrOc8Rd/3a3LuajGzGWR/hD7TVyvq99VqmTy99eVh8Zkmdq213OgvgXx7w==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001272, caniuse-lite@^1.0.30001280, caniuse-lite@^1.0.30001312, caniuse-lite@^1.0.30001317:
version "1.0.30001344"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001344.tgz"
integrity sha512-0ZFjnlCaXNOAYcV7i+TtdKBp0L/3XEU2MF/x6Du1lrh+SRX4IfzIVL4HNJg5pB2PmFb8rszIGyOvsZnqqRoc2g==

ccount@^1.0.0, ccount@^1.0.3:
version "1.1.0"
Expand Down
6 changes: 3 additions & 3 deletions example/src/Examples/API/Gradients.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from "react";
import { StyleSheet, Dimensions, ScrollView } from "react-native";
import {
bottomRight,
center,
topLeft,
Skia,
rect,
Canvas,
Rect,
LinearGradient,
topLeft,
bottomRight,
center,
RadialGradient,
TwoPointConicalGradient,
SweepGradient,
Expand Down
4 changes: 3 additions & 1 deletion package/cpp/api/JsiSkPaint.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,11 @@ Returns the underlying object from a host object of this type
static const jsi::HostFunctionType
createCtor(std::shared_ptr<RNSkPlatformContext> context) {
return JSI_HOST_FUNCTION_LAMBDA {
auto paint = SkPaint();
paint.setAntiAlias(true);
// Return the newly constructed object
return jsi::Object::createFromHostObject(
runtime, std::make_shared<JsiSkPaint>(std::move(context), SkPaint()));
runtime, std::make_shared<JsiSkPaint>(std::move(context), paint));
};
}
};
Expand Down
8 changes: 8 additions & 0 deletions package/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,12 @@ module.exports = {
// Ignore lib folder - contains build artifacts and should
// not be probed for tests
modulePathIgnorePatterns: ["<rootDir>/lib/typescript", "setup.ts$"],
transform: {
"^.+\\.(js|jsx)$": "ts-jest",
},
globals: {
"ts-jest": {
tsconfig: "./tsconfig.test.json",
},
},
};
24 changes: 24 additions & 0 deletions package/src/__tests__/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import path from "path";
import fs from "fs";

import type { Surface } from "canvaskit-wasm";

import type { SkSurface } from "../skia";
import { toValue } from "../skia/web/api/Host";

export const processResult = (
surface: SkSurface,
relPath: string,
overwrite = false
) => {
toValue<Surface>(surface).flush();
const image = surface.makeImageSnapshot();
const png = image.encodeToBytes();
const p = path.resolve(__dirname, relPath);
if (fs.existsSync(p) && !overwrite) {
const ref = fs.readFileSync(p);
expect(ref.equals(png)).toBe(true);
} else {
fs.writeFileSync(p, png);
}
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions package/src/animation/functions/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { interpolate } from "./interpolate";
export { interpolateColors, mixColors } from "./interpolateColors";
export * from "./interpolate";
export * from "./interpolateColors";
export * from "./interpolateVector";
26 changes: 26 additions & 0 deletions package/src/animation/functions/interpolateVector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Vector } from "../../skia/types";

import { interpolate } from "./interpolate";

export const interpolateVector = (
value: number,
inputRange: readonly number[],
outputRange: readonly Vector[],
options?: Parameters<typeof interpolate>[3]
) => ({
x: interpolate(
value,
inputRange,
outputRange.map((v) => v.x),
options
),
y: interpolate(
value,
inputRange,
outputRange.map((v) => v.y),
options
),
});

export const mixVector = (value: number, from: Vector, to: Vector) =>
interpolateVector(value, [0, 1], [from, to]);
1 change: 1 addition & 0 deletions package/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import "./skia/NativeSetup";
export * from "./renderer";
export * from "./renderer/Canvas";
export * from "./views";
export * from "./skia";
export * from "./external";
Expand Down
46 changes: 12 additions & 34 deletions package/src/renderer/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import React, {
useState,
useCallback,
useMemo,
useContext,
forwardRef,
useRef,
} from "react";
Expand All @@ -19,36 +18,15 @@ import ReactReconciler from "react-reconciler";

import { SkiaView, useDrawCallback } from "../views";
import type { TouchHandler } from "../views";
import { Skia, SkiaPaint } from "../skia";
import type { FontMgr } from "../skia";
import type { FontMgr } from "../skia/types";
import { useValue } from "../values/hooks/useValue";
import type { SkiaValue } from "../values/types";
import { Skia } from "../skia/Skia";

import { debug as hostDebug, skHostConfig } from "./HostConfig";
// import { debugTree } from "./nodes";
import { vec } from "./processors";
import { Container } from "./nodes";
import { DependencyManager } from "./DependencyManager";

const CanvasContext = React.createContext<SkiaValue<{
width: number;
height: number;
}> | null>(null);

export const useCanvas = () => {
const size = useContext(CanvasContext);
if (!size) {
throw new Error("Canvas context is not available");
}
return { size };
};

export const useCanvasSize = () => {
console.warn(
"useCanvasSize is deprecated, use the size member of useCanvas() instead."
);
return useCanvas().size;
};
import { CanvasProvider } from "./useCanvas";

export const skiaReconciler = ReactReconciler(skHostConfig);

Expand Down Expand Up @@ -79,7 +57,8 @@ const defaultFontMgr = Skia.FontMgr.RefDefault();

export const Canvas = forwardRef<SkiaView, CanvasProps>(
({ children, style, debug, mode, onTouch, fontMgr }, forwardedRef) => {
const canvasCtx = useValue({ width: 0, height: 0 });
const size = useValue({ width: 0, height: 0 });
const canvasCtx = useMemo(() => ({ Skia, size }), [size]);
const innerRef = useCanvasRef();
const ref = useCombinedRefs(forwardedRef, innerRef);
const [tick, setTick] = useState(0);
Expand All @@ -97,9 +76,7 @@ export const Canvas = forwardRef<SkiaView, CanvasProps>(
// Render effect
useEffect(() => {
render(
<CanvasContext.Provider value={canvasCtx}>
{children}
</CanvasContext.Provider>,
<CanvasProvider value={canvasCtx}>{children}</CanvasProvider>,
root,
container
);
Expand All @@ -114,12 +91,12 @@ export const Canvas = forwardRef<SkiaView, CanvasProps>(
onTouch(info.touches);
}
if (
width !== canvasCtx.current.width ||
height !== canvasCtx.current.height
width !== canvasCtx.size.current.width ||
height !== canvasCtx.size.current.height
) {
canvasCtx.current = { width, height };
canvasCtx.size.current = { width, height };
}
const paint = SkiaPaint();
const paint = Skia.Paint();
const ctx = {
width,
height,
Expand All @@ -128,8 +105,9 @@ export const Canvas = forwardRef<SkiaView, CanvasProps>(
paint,
opacity: 1,
ref,
center: vec(width / 2, height / 2),
center: Skia.Point(width / 2, height / 2),
fontMgr: fontMgr ?? defaultFontMgr,
Skia,
};
container.draw(ctx);
},
Expand Down
5 changes: 2 additions & 3 deletions package/src/renderer/DrawingContext.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { RefObject } from "react";

import type { DrawingInfo, SkiaView } from "../views";
import type { FontMgr, SkCanvas, SkPaint } from "../skia";

import type { Vector } from "./processors/math/Vector";
import type { FontMgr, SkCanvas, SkPaint, Skia, Vector } from "../skia/types";

export interface DrawingContext extends Omit<DrawingInfo, "touches"> {
canvas: SkCanvas;
Expand All @@ -12,4 +10,5 @@ export interface DrawingContext extends Omit<DrawingInfo, "touches"> {
center: Vector;
ref: RefObject<SkiaView>;
fontMgr: FontMgr;
Skia: Skia;
}
4 changes: 2 additions & 2 deletions package/src/renderer/HostConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*global NodeJS, performance*/
/*global NodeJS*/
import type { HostConfig } from "react-reconciler";

import type { Node, Container, DeclarationProps, DrawingProps } from "./nodes";
Expand Down Expand Up @@ -137,7 +137,7 @@ export const skHostConfig: SkiaHostConfig = {
/**
* This function is used by the reconciler in order to calculate current time for prioritising work.
*/
now: performance.now,
now: Date.now,

supportsMutation: true,
isPrimaryRenderer: false,
Expand Down
43 changes: 43 additions & 0 deletions package/src/renderer/__tests__/Simple.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";

import { processResult } from "../../__tests__/setup";
import { Group, Rect } from "../components";
import * as SkiaRenderer from "../index";

import { center, drawOnNode, width } from "./setup";

const size = 128;

describe("Renderer", () => {
it("Loads renderer without Skia", () => {
expect(SkiaRenderer).toBeDefined();
});
it("Light blue rectangle", () => {
const surface = drawOnNode(
<Rect
x={(width - size) / 2}
y={(width - size) / 2}
width={size}
height={size}
color="lightblue"
/>
);
processResult(surface, "snapshots/drawings/lightblue-rect.png");
});
it("Scaled light blue rectangle", () => {
const scale = 2;
const scaled = size / scale;
const surface = drawOnNode(
<Group transform={[{ scale }]} origin={center}>
<Rect
x={(width - scaled) / 2}
y={(width - scaled) / 2}
width={scaled}
height={scaled}
color="lightblue"
/>
</Group>
);
processResult(surface, "snapshots/drawings/lightblue-rect.png");
});
});
59 changes: 59 additions & 0 deletions package/src/renderer/__tests__/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import CanvasKitInit from "canvaskit-wasm";
import type { ReactNode } from "react";
import ReactReconciler from "react-reconciler";

import { JsiSkApi } from "../../skia/web";
import { DependencyManager } from "../DependencyManager";
import { skHostConfig } from "../HostConfig";
import { Container } from "../nodes";
import type { DrawingContext } from "../DrawingContext";

let Skia: ReturnType<typeof JsiSkApi>;

beforeAll(async () => {
const CanvasKit = await CanvasKitInit();
Skia = JsiSkApi(CanvasKit);
});

export const width = 256;
export const height = 256;
export const center = { x: width / 2, y: height / 2 };
const redraw = () => {};
const ref = { current: null };

const skiaReconciler = ReactReconciler(skHostConfig);

skiaReconciler.injectIntoDevTools({
bundleType: 1,
version: "0.0.1",
rendererPackageName: "react-native-skia",
});

export const drawOnNode = (element: ReactNode) => {
expect(Skia).toBeDefined();
const surface = Skia.Surface.Make(width, height)!;
expect(surface).toBeDefined();
const canvas = surface.getCanvas();
expect(canvas).toBeDefined();
expect(element).toBeDefined();
const container = new Container(new DependencyManager(ref), redraw);
skiaReconciler.createContainer(container, 0, false, null);
const root = skiaReconciler.createContainer(container, 0, false, null);
skiaReconciler.updateContainer(element, root, null, () => {});
const ctx: DrawingContext = {
width,
height,
timestamp: 0,
canvas,
paint: Skia.Paint(),
opacity: 1,
ref,
center: Skia.Point(width / 2, height / 2),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
fontMgr: null,
Skia,
};
container.draw(ctx);
return surface;
};
Loading

0 comments on commit 0d8454e

Please sign in to comment.