Skip to content

Commit

Permalink
fix(🐎): minor improvements to the new reconciler (#2871)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcandillon authored Jan 9, 2025
1 parent d1debdd commit c5f48cc
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 103 deletions.
47 changes: 37 additions & 10 deletions packages/skia/scripts/skia-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export type Platform = {
options?: Arg[];
};

const appleMinTarget = GRAPHITE ? '15.1' : '13.0';
const appleMinTarget = GRAPHITE ? "15.1" : "13.0";
const iosMinTarget = `"${appleMinTarget}"`;

export const configurations = {
Expand Down Expand Up @@ -175,29 +175,56 @@ export const configurations = {
cpu: "arm64",
platform: "tvos",
args: [
["extra_cflags", `["-target", "arm64-apple-tvos", "-mappletvos-version-min=${appleMinTarget}"]`],
["extra_asmflags", `["-target", "arm64-apple-tvos", "-mappletvos-version-min=${appleMinTarget}"]`],
["extra_ldflags", `["-target", "arm64-apple-tvos", "-mappletvos-version-min=${appleMinTarget}"]`],
[
"extra_cflags",
`["-target", "arm64-apple-tvos", "-mappletvos-version-min=${appleMinTarget}"]`,
],
[
"extra_asmflags",
`["-target", "arm64-apple-tvos", "-mappletvos-version-min=${appleMinTarget}"]`,
],
[
"extra_ldflags",
`["-target", "arm64-apple-tvos", "-mappletvos-version-min=${appleMinTarget}"]`,
],
],
},
"arm64-tvsimulator": {
cpu: "arm64",
platform: "tvos",
args: [
["ios_use_simulator", true],
["extra_cflags", `["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`],
["extra_asmflags", `["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`],
["extra_ldflags", `["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`],
[
"extra_cflags",
`["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`,
],
[
"extra_asmflags",
`["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`,
],
[
"extra_ldflags",
`["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`,
],
],
},
"x64-tvsimulator": {
cpu: "x64",
platform: "tvos",
args: [
["ios_use_simulator", true],
["extra_cflags", `["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`],
["extra_asmflags", `["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`],
["extra_ldflags", `["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`],
[
"extra_cflags",
`["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`,
],
[
"extra_asmflags",
`["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`,
],
[
"extra_ldflags",
`["-target", "arm64-apple-tvos-simulator", "-mappletvsimulator-version-min=${appleMinTarget}"]`,
],
],
},
"arm64-macosx": {
Expand Down
3 changes: 2 additions & 1 deletion packages/skia/src/skia/types/Picture/PictureRecorder.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { SkCanvas } from "../Canvas";
import type { SkJSIInstance } from "../JsiInstance";
import type { SkRect } from "../Rect";

import type { SkPicture } from "./Picture";

export interface SkPictureRecorder {
export interface SkPictureRecorder extends SkJSIInstance<"PictureRecorder"> {
/**
* Returns a canvas on which to draw. When done drawing, call finishRecordingAsPicture()
*
Expand Down
131 changes: 64 additions & 67 deletions packages/skia/src/sksg/Container.ts
Original file line number Diff line number Diff line change
@@ -1,108 +1,105 @@
import { type SharedValue } from "react-native-reanimated";

import Rea from "../external/reanimated/ReanimatedProxy";
import type { Skia, SkCanvas } from "../skia/types";
import { HAS_REANIMATED_3 } from "../external/reanimated/renderHelpers";

import type { Node } from "./Node";
import { isSharedValue } from "./utils";
import type { Recording } from "./Recorder/Recorder";
import { Recorder } from "./Recorder/Recorder";
import { visit } from "./Recorder/Visitor";
import { replay } from "./Recorder/Player";
import { createDrawingContext } from "./Recorder/DrawingContext";
import { createRecording, type Recording } from "./Recorder/Recording";

const drawOnscreen = (Skia: Skia, nativeId: number, recording: Recording) => {
"worklet";

const rec = Skia.PictureRecorder();
const canvas = rec.beginRecording();
// const start = performance.now();

// TODO: because the pool is not a shared value here, it is copied on every frame
const ctx = createDrawingContext(Skia, recording.paintPool, canvas);
//console.log(recording.commands);
replay(ctx, recording.commands);
const picture = rec.finishRecordingAsPicture();
//const end = performance.now();
//console.log("Recording time: ", end - start);
SkiaViewApi.setJsiProperty(nativeId, "picture", picture);
rec.dispose();
picture.dispose();
};

export class Container {
private _root: Node[] = [];
private _recording: Recording | null = null;
public unmounted = false;

private values = new Set<SharedValue<unknown>>();
private mapperId: number | null = null;

constructor(public Skia: Skia, private nativeId: number) {}
export abstract class Container {
public root: Node[] = [];
protected recording: Recording | null = null;

get root() {
return this._root;
}
constructor(protected Skia: Skia, protected nativeId: number) {}

set root(root: Node[]) {
const isOnscreen = this.nativeId !== -1;
if (isOnscreen) {
if (this.mapperId !== null) {
Rea.stopMapper(this.mapperId);
}
const { nativeId, Skia, _recording } = this;
this.mapperId = Rea.startMapper(() => {
"worklet";
drawOnscreen(Skia, nativeId, _recording!);
}, Array.from(this.values));
drawOnCanvas(canvas: SkCanvas) {
if (!this.recording) {
throw new Error("No recording to draw");
}
this._root = root;
const recorder = new Recorder();
visit(recorder, root);
this._recording = createRecording(recorder.commands);
const ctx = createDrawingContext(
this.Skia,
this.recording.paintPool,
canvas
);
//console.log(this._recording);
replay(ctx, this.recording.commands);
}

clear() {
console.log("clear container");
abstract redraw(): void;
}

class StaticContainer extends Container {
constructor(Skia: Skia, nativeId: number) {
super(Skia, nativeId);
}

redraw() {
const isOnscreen = this.nativeId !== -1;
if (isOnscreen) {
const { nativeId, Skia, _recording } = this;
Rea.runOnUI(() => {
drawOnscreen(Skia, nativeId, _recording!);
})();
const recorder = new Recorder();
visit(recorder, this.root);
this.recording = recorder.getRecording();
const isOnScreen = this.nativeId !== -1;
if (isOnScreen) {
const rec = this.Skia.PictureRecorder();
const canvas = rec.beginRecording();
this.drawOnCanvas(canvas);
const picture = rec.finishRecordingAsPicture();
SkiaViewApi.setJsiProperty(this.nativeId, "picture", picture);
}
}
}

getNativeId() {
return this.nativeId;
}

unregisterValues(values: object) {
Object.values(values)
.filter(isSharedValue)
.forEach((value) => {
this.values.delete(value);
});
}
class ReanimatedContainer extends Container {
private mapperId: number | null = null;

registerValues(values: object) {
Object.values(values)
.filter(isSharedValue)
.forEach((value) => {
this.values.add(value);
});
constructor(Skia: Skia, nativeId: number) {
super(Skia, nativeId);
}

drawOnCanvas(canvas: SkCanvas) {
if (!this._recording) {
throw new Error("No recording to draw");
redraw() {
if (this.mapperId !== null) {
Rea.stopMapper(this.mapperId);
}
const recorder = new Recorder();
visit(recorder, this.root);
const record = recorder.getRecording();
const { animationValues } = record;
this.recording = {
commands: record.commands,
paintPool: record.paintPool,
};
if (animationValues.size > 0) {
const { nativeId, Skia, recording } = this;
this.mapperId = Rea.startMapper(() => {
"worklet";
drawOnscreen(Skia, nativeId, recording!);
}, Array.from(animationValues));
}
const ctx = createDrawingContext(
this.Skia,
this._recording.paintPool,
canvas
);
//console.log(this._recording);
replay(ctx, this._recording.commands);
}
}

export const createContainer = (Skia: Skia, nativeId: number) => {
return HAS_REANIMATED_3 && nativeId !== -1
? new ReanimatedContainer(Skia, nativeId)
: new StaticContainer(Skia, nativeId);
};
13 changes: 4 additions & 9 deletions packages/skia/src/sksg/HostConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,13 @@ export const sksgHostConfig: SkiaHostConfig = {
createInstance(
type,
propsWithChildren,
container,
_container,
_hostContext,
_internalInstanceHandle
) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { children, ...props } = propsWithChildren as any;
debug("createInstance", type);
container.registerValues(props);
const instance = {
type,
props,
Expand Down Expand Up @@ -121,7 +120,7 @@ export const sksgHostConfig: SkiaHostConfig = {
debug("commitMount");
},

prepareForCommit(_containerInfo) {
prepareForCommit(_container: Container) {
debug("prepareForCommit");
return null;
},
Expand All @@ -144,9 +143,8 @@ export const sksgHostConfig: SkiaHostConfig = {
// textInstance.instance = newText;
},

clearContainer: (container) => {
clearContainer: (_container) => {
debug("clearContainer");
container.clear();
},

prepareUpdate(
Expand All @@ -162,8 +160,6 @@ export const sksgHostConfig: SkiaHostConfig = {
if (propsAreEqual) {
return null;
}
container.unregisterValues(oldProps);
container.registerValues(newProps);
return container;
},

Expand Down Expand Up @@ -208,7 +204,6 @@ export const sksgHostConfig: SkiaHostConfig = {
},

replaceContainerChildren(container: Container, newChildren: ChildSet) {
debug("replaceContainerChildren");
container.root = newChildren;
},

Expand All @@ -229,7 +224,7 @@ export const sksgHostConfig: SkiaHostConfig = {
getCurrentEventPriority: () => DefaultEventPriority,
beforeActiveInstanceBlur: () => {},
afterActiveInstanceBlur: () => {},
detachDeletedInstance: () => {},
detachDeletedInstance: (_node: Instance) => {},
getInstanceFromNode: function (_node): Fiber | null | undefined {
throw new Error("Function not implemented.");
},
Expand Down
6 changes: 3 additions & 3 deletions packages/skia/src/sksg/Reconciler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import type { SkCanvas, Skia } from "../skia/types";
import { NodeType } from "../dom/types";

import { debug, sksgHostConfig } from "./HostConfig";
import { Container } from "./Container";
import type { Container } from "./Container";
import { createContainer } from "./Container";

const skiaReconciler = ReactReconciler(sksgHostConfig);

Expand All @@ -21,7 +22,7 @@ export class SkiaSGRoot {
private container: Container;

constructor(public Skia: Skia, nativeId = -1) {
this.container = new Container(Skia, nativeId);
this.container = createContainer(Skia, nativeId);
this.root = skiaReconciler.createContainer(
this.container,
0,
Expand Down Expand Up @@ -58,7 +59,6 @@ export class SkiaSGRoot {
}

unmount() {
this.container.unmounted = true;
skiaReconciler.updateContainer(null, this.root, null, () => {
debug("unmountContainer");
});
Expand Down
20 changes: 20 additions & 0 deletions packages/skia/src/sksg/Recorder/Recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,31 @@ import type {
import type { AnimatedProps } from "../../renderer";
import { isSharedValue } from "../utils";
import { isColorFilter, isImageFilter, isPathEffect, isShader } from "../Node";
import type { SkPaint } from "../../skia/types";

import { CommandType } from "./Core";
import type { Command } from "./Core";

export interface Recording {
commands: Command[];
paintPool: SkPaint[];
}

interface AnimationValues {
animationValues: Set<SharedValue<unknown>>;
}

export class Recorder {
commands: Command[] = [];
animationValues: Set<SharedValue<unknown>> = new Set();

getRecording(): Recording & AnimationValues {
return {
commands: this.commands,
paintPool: [],
animationValues: this.animationValues,
};
}

private processProps(props: Record<string, unknown>) {
const animatedProps: Record<string, SharedValue<unknown>> = {};
Expand All @@ -44,6 +63,7 @@ export class Recorder {
for (const key in props) {
const prop = props[key];
if (isSharedValue(prop)) {
this.animationValues.add(prop);
props[key] = prop.value;
animatedProps[key] = prop;
hasAnimatedProps = true;
Expand Down
Loading

0 comments on commit c5f48cc

Please sign in to comment.