Skip to content

Commit

Permalink
feat(core): Add fiber ID handling and block main thread
Browse files Browse the repository at this point in the history
  • Loading branch information
aidenybai committed Dec 27, 2024
1 parent a7ba740 commit 617b3d0
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 20 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ interface __react_DEVTOOLS_GLOBAL_HOOK__ {
// list of renderers (react-dom, react-native, etc.)
renderers: Map<RendererID, reactRenderer>;

// called when react has rendered everything for an update and is ready to
// called when react has rendered everything for an update and the fiber tree is fully built and ready to
// apply changes to the host tree (e.g. DOM mutations)
onCommitFiberRoot: (
rendererID: RendererID,
Expand All @@ -102,6 +102,12 @@ bippy works by monkey-patching `window.__react_DEVTOOLS_GLOBAL_HOOK__` with our
- _(instead of directly mutating `onCommitFiberRoot`, ...)_
- `secure` to wrap your handlers in a try/catch and determine if handlers are safe to run
- _(instead of rawdogging `window.__react_DEVTOOLS_GLOBAL_HOOK__` handlers, which may crash your app)_
- `createFiberVisitor` to traverse the fiber tree and determine which fibers have actually rendered
- _(instead of `child`, `sibling`, and `return` pointers)_
- `traverseFiber` to traverse the fiber tree, regardless of whether it has rendered
- _(instead of `child`, `sibling`, and `return` pointers)_
- `setFiberId` / `getFiberId` to set and get a fiber's id
- _(instead of anonymous fibers with no identity)_

## examples

Expand Down
26 changes: 22 additions & 4 deletions kitchen-sink/src/mini-react-scan.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export const App = () => {
<TooltipContext.Provider value={{ tooltip: "Hello" }}>
<div className="p-16 text-xs">
<div className="main-content">
<h1 id="btn-block-main-thread">Status: unblocked</h1>
<br />
<nav className="navbar">
<a href="/" className="navbar-brand">
<h3>
Expand Down Expand Up @@ -80,9 +82,10 @@ export const Text = ({ children }) => {
return <span>{children}</span>;
};

export const Button = ({ onClick, children }) => {
export const Button = ({ onClick, children, id }) => {
return (
<button
id={id}
type="button"
className="ml-2 border border-gray-300 bg-black text-white rounded-md p-2"
onClick={onClick}
Expand All @@ -99,20 +102,35 @@ export const AddTaskBar = ({ onCreate }) => {
<div className="add-task-container flex">
<Input
onChange={(value) => setValue(value)}
onEnter={(value) => {
onEnter={async (value) => {
onCreate(`${value} (${id})`);
setValue("");
setId(id + 1);
await new Promise((resolve) => setTimeout(resolve, 0));
for (let i = 0; i < 1000000000; i++) {}
}}
value={value}
/>
<Button
onClick={() => {
onClick={async () => {
onCreate(value);
setValue("");
document.getElementById("btn-block-main-thread").textContent =
"blocked";
document.getElementById(
"btn-block-main-thread",
).style.backgroundColor = "red";
await new Promise((resolve) => setTimeout(resolve, 0));
for (let i = 0; i < 1000000000; i++) {}
console.log("unblocking main thread");
document.getElementById("btn-block-main-thread").textContent =
"unblocked";
document.getElementById(
"btn-block-main-thread",
).style.backgroundColor = "lightgreen";
}}
>
Add Task
Add Task and block main thread
</Button>
</div>
);
Expand Down
25 changes: 25 additions & 0 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,28 @@ export type RenderHandler = <S>(
state?: S,
) => unknown;

let fiberId = 0;
export const fiberIdMap = new WeakMap<Fiber, number>();

export const setFiberId = (fiber: Fiber, id: number) => {
fiberIdMap.set(fiber, id);
};

// react fibers are double buffered, so the alternate fiber may
// be switched to the current fiber and vice versa.
// fiber === fiber.alternate.alternate
export const getFiberId = (fiber: Fiber) => {
let id = fiberIdMap.get(fiber);
if (!id && fiber.alternate) {
id = fiberIdMap.get(fiber.alternate);
}
if (!id) {
id = fiberId++;
setFiberId(fiber, id);
}
return id;
};

export const mountFiberRecursively = (
onRender: RenderHandler,
firstChild: Fiber,
Expand All @@ -743,6 +765,7 @@ export const mountFiberRecursively = (
let fiber: Fiber | null = firstChild;

while (fiber != null) {
getFiberId(fiber);
const shouldIncludeInTree = !shouldFilterFiber(fiber);
if (shouldIncludeInTree && didFiberRender(fiber)) {
onRender(fiber, "mount");
Expand Down Expand Up @@ -790,7 +813,9 @@ export const updateFiberRecursively = (
prevFiber: Fiber,
parentFiber: Fiber | null,
) => {
getFiberId(nextFiber);
if (!prevFiber) return;
getFiberId(prevFiber);

const isSuspense = nextFiber.tag === SuspenseComponentTag;

Expand Down
28 changes: 20 additions & 8 deletions src/scan/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
createFiberVisitor,
getDisplayName,
getFiberId,
getNearestHostFibers,
instrument,
isCompositeFiber,
Expand All @@ -14,6 +15,7 @@ import type {
const canvasHtmlStr = `<canvas style="position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:2147483646" aria-hidden="true"></canvas>`;
let worker: Worker;

const fiberIdMap = new WeakMap<Fiber, number>();
const fiberMap = new WeakMap<Fiber, FiberMetadata>();
const fiberMapKeys = new Set<Fiber>();

Expand Down Expand Up @@ -54,6 +56,7 @@ export const getRect = (
const entry = entries[i];
const element = entry.target;
const rect = entry.boundingClientRect;
console.log(entry.intersectionRatio);
if (entry.isIntersecting && rect.width && rect.height) {
rects.set(element, rect);
}
Expand Down Expand Up @@ -99,7 +102,15 @@ export const flushOutlines = async () => {
const { x, y, width, height } =
rects.length === 1 ? rects[0] : mergeRects(rects);

outlines.push([outline.name, outline.count, x, y, width, height]);
outlines.push([
getFiberId(fiber),
outline.name,
outline.count,
x,
y,
width,
height,
]);
}

const buffer = new (
Expand All @@ -108,18 +119,19 @@ export const flushOutlines = async () => {
const sharedView = new Float32Array(buffer);

for (let i = 0; i < outlines.length; i++) {
const [, count, x, y, width, height] = outlines[i];
sharedView[i * 6 + 0] = count;
sharedView[i * 6 + 1] = x;
sharedView[i * 6 + 2] = y;
sharedView[i * 6 + 3] = width;
sharedView[i * 6 + 4] = height;
const [id, _name, count, x, y, width, height] = outlines[i];
sharedView[i * 6 + 0] = id;
sharedView[i * 6 + 1] = count;
sharedView[i * 6 + 2] = x;
sharedView[i * 6 + 3] = y;
sharedView[i * 6 + 4] = width;
sharedView[i * 6 + 5] = height;
}

worker.postMessage({
type: "draw",
outlinesBuffer: buffer,
names: outlines.map(([name]) => name),
names: outlines.map(([_id, name]) => name),
});
};

Expand Down
5 changes: 5 additions & 0 deletions src/scan/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import type { Fiber as ReactFiber } from "../index.js";
export type Fiber = ReactFiber<Element>;

export type CompressedPendingOutline = [
/**
* id
*/
number,
/**
* name
*/
Expand Down Expand Up @@ -30,6 +34,7 @@ export type CompressedPendingOutline = [
];

export interface ActiveOutline {
id: number;
name: string;
count: number;
x: number;
Expand Down
16 changes: 9 additions & 7 deletions src/scan/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,13 @@ const TOTAL_FRAMES = 45;
// Convert compressed outline to an ActiveOutline
function toActiveOutline(c: CompressedPendingOutline): ActiveOutline {
return {
name: c[0],
count: c[1],
x: c[2],
y: c[3],
width: c[4],
height: c[5],
id: c[0],
name: c[1],
count: c[2],
x: c[3],
y: c[4],
width: c[5],
height: c[6],
frame: 0,
};
}
Expand Down Expand Up @@ -196,12 +197,13 @@ self.onmessage = (event) => {

for (let i = 0; i < floatView.length; i += 6) {
newOutlines.push([
names[i / 6],
floatView[i],
names[i / 6],
floatView[i + 1],
floatView[i + 2],
floatView[i + 3],
floatView[i + 4],
floatView[i + 5],
]);
}

Expand Down

0 comments on commit 617b3d0

Please sign in to comment.