@keepsnappy/react-native 0.0.0-stage → 0.1.0-alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,257 @@
1
+ // A React DevTools profiler export (version 5, the shape TraceSift's
2
+ // parseReactExport reads) built from our own commit walk, one per window.
3
+ // Fibers get ids on first sight (shared with their alternate); a fiber is
4
+ // ADDed to the window's tree the first time it renders inside the window,
5
+ // under its nearest component ancestor already in the tree, else the root.
6
+ // snapshots stay empty: every rendered id is named by an ADD operation.
7
+
8
+ import {
9
+ CLASS_COMPONENT,
10
+ type Fiber,
11
+ FORWARD_REF,
12
+ isComponentTag,
13
+ SIMPLE_MEMO_COMPONENT,
14
+ } from './fiber';
15
+ import type { UpdateCause } from './profile';
16
+
17
+ const ROOT_ID = 1;
18
+ const RENDERER_ID = 1;
19
+ const OP_ADD = 1;
20
+ const TYPE_CLASS = 1;
21
+ const TYPE_FUNCTION = 5;
22
+ const TYPE_FORWARD_REF = 6;
23
+ const TYPE_MEMO = 8;
24
+ const TYPE_ROOT = 11;
25
+
26
+ const ids = new WeakMap<object, number>();
27
+ let nextId = ROOT_ID + 1;
28
+
29
+ export const fiberId = (fiber: Fiber): number => {
30
+ const known = ids.get(fiber) ?? (fiber.alternate ? ids.get(fiber.alternate) : undefined);
31
+ if (known !== undefined) {
32
+ ids.set(fiber, known);
33
+ return known;
34
+ }
35
+ const id = nextId++;
36
+ ids.set(fiber, id);
37
+ if (fiber.alternate) {
38
+ ids.set(fiber.alternate, id);
39
+ }
40
+ return id;
41
+ };
42
+
43
+ const elementType = (tag: number) =>
44
+ tag === CLASS_COMPONENT
45
+ ? TYPE_CLASS
46
+ : tag === FORWARD_REF
47
+ ? TYPE_FORWARD_REF
48
+ : tag === SIMPLE_MEMO_COMPONENT
49
+ ? TYPE_MEMO
50
+ : TYPE_FUNCTION;
51
+
52
+ export interface ExportFiber {
53
+ id: number;
54
+ name: string;
55
+ type: number;
56
+ // Component ancestors, nearest first.
57
+ ancestors: number[];
58
+ actualMs: number;
59
+ selfMs: number;
60
+ cause: UpdateCause;
61
+ }
62
+
63
+ export interface ExportCommit {
64
+ t: number;
65
+ durationMs: number;
66
+ updaters: string[] | null;
67
+ // What caused the commit (causes.ts `describeCauses`).
68
+ causes: string[];
69
+ // Timer-carried (`timer`, `timer-net`) or null; the backend compares attribution approaches from it.
70
+ carrier: 'timer' | 'timer-net' | null;
71
+ // A store write a carried tick made, moved to a newer action (`touch#3>touch#9`), or null.
72
+ store: string | null;
73
+ // The commit without the store move, when it had one: what the registering code's causes and carrier were.
74
+ task: { causes: string[]; carrier: 'timer' | 'timer-net' | null } | null;
75
+ // Commit-stream facts (stream.ts): the commit's update parts, and the render time with null kept.
76
+ facts?: {
77
+ seq: number;
78
+ renderMs: number | null;
79
+ unknown: boolean;
80
+ // [root ids, timer run id, store write id] per part.
81
+ parts: [number[], number | null, number | null][];
82
+ more: number;
83
+ fibers: boolean;
84
+ };
85
+ fibers: ExportFiber[];
86
+ }
87
+
88
+ const MAX_ANCESTORS = 64;
89
+
90
+ // A fiber's component ancestors, nearest first, up to MAX_ANCESTORS: the walk from its parent up.
91
+ export const ancestorsOf = (fiber: Fiber): number[] => {
92
+ const ancestors: number[] = [];
93
+ for (
94
+ let f = fiber.return;
95
+ f !== null && ancestors.length < MAX_ANCESTORS;
96
+ f = f.return
97
+ ) {
98
+ if (isComponentTag(f.tag)) {
99
+ ancestors.push(fiberId(f));
100
+ }
101
+ }
102
+ return ancestors;
103
+ };
104
+
105
+ // The same lists for every fiber of one commit, without walking the whole path each time: the component fibers at and
106
+ // above a fiber are its own (when it is a component) in front of its parent's, so each is built once from its parent's.
107
+ // Ids are taken nearest first, the order ancestorsOf gives them out (fiberId numbers a fiber when it first sees it), and
108
+ // a list's ids are kept for the fibers that share it. Lists are shared, so they are read-only.
109
+ export const createAncestorPaths = () => {
110
+ const known = new Map<Fiber, Fiber[]>();
111
+ const ids = new Map<Fiber[], number[]>();
112
+ const through = (fiber: Fiber | null): Fiber[] => {
113
+ const pending: Fiber[] = [];
114
+ let above: Fiber[] = [];
115
+ for (let f = fiber; f !== null; f = f.return) {
116
+ const hit = known.get(f);
117
+ if (hit) {
118
+ above = hit;
119
+ break;
120
+ }
121
+ pending.push(f);
122
+ }
123
+ for (let i = pending.length - 1; i >= 0; i--) {
124
+ const f = pending[i] as Fiber;
125
+ if (isComponentTag(f.tag)) {
126
+ above = [f, ...(above.length < MAX_ANCESTORS ? above : above.slice(0, MAX_ANCESTORS - 1))];
127
+ }
128
+ known.set(f, above);
129
+ }
130
+ return above;
131
+ };
132
+ return (fiber: Fiber): number[] => {
133
+ const list = through(fiber.return);
134
+ let out = ids.get(list);
135
+ if (!out) {
136
+ out = list.map(fiberId);
137
+ ids.set(list, out);
138
+ }
139
+ return out;
140
+ };
141
+ };
142
+
143
+ export const exportFiber = (
144
+ fiber: Fiber,
145
+ name: string,
146
+ cause: UpdateCause,
147
+ actualMs: number | null,
148
+ selfMs: number | null,
149
+ ancestors: number[] = ancestorsOf(fiber),
150
+ ): ExportFiber => {
151
+ return {
152
+ id: fiberId(fiber),
153
+ name,
154
+ type: elementType(fiber.tag),
155
+ ancestors,
156
+ actualMs: actualMs ?? 0,
157
+ selfMs: selfMs ?? 0,
158
+ cause,
159
+ };
160
+ };
161
+
162
+ const encodeString = (value: string) => {
163
+ const codes: number[] = [];
164
+ for (const char of value) {
165
+ codes.push(char.codePointAt(0) as number);
166
+ }
167
+ return [codes.length, ...codes];
168
+ };
169
+
170
+ const changeDescription = (cause: UpdateCause) => ({
171
+ context: cause.context,
172
+ didHooksChange: (cause.hooks?.length ?? 0) > 0,
173
+ hooks: cause.mount ? null : (cause.hooks ?? []),
174
+ isFirstMount: cause.mount,
175
+ props: cause.mount ? null : (cause.props ?? []),
176
+ state: cause.mount ? null : cause.state,
177
+ });
178
+
179
+ export interface ReactExport {
180
+ version: 5;
181
+ source: 'perflab';
182
+ captureStart: { clock: 'perf-now-ms'; t0: number };
183
+ dataForRoots: {
184
+ rootID: number;
185
+ displayName: string;
186
+ snapshots: never[];
187
+ initialTreeBaseDurations: never[];
188
+ operations: number[][];
189
+ commitData: Record<string, unknown>[];
190
+ }[];
191
+ }
192
+
193
+ // Builds the export for one window from the commits that fell into it.
194
+ export const buildReactExport = (t0: number, commits: ExportCommit[]): ReactExport => {
195
+ const added = new Set<number>([ROOT_ID]);
196
+ const operations: number[][] = [];
197
+ const commitData: Record<string, unknown>[] = [];
198
+ commits.forEach((commit, index) => {
199
+ const strings: string[] = [];
200
+ const ops: number[] = [];
201
+ if (index === 0) {
202
+ ops.push(OP_ADD, ROOT_ID, TYPE_ROOT, 0, 0, 0, 0);
203
+ }
204
+ for (const fiber of commit.fibers) {
205
+ if (added.has(fiber.id)) {
206
+ continue;
207
+ }
208
+ const parent = fiber.ancestors.find(id => added.has(id)) ?? ROOT_ID;
209
+ let nameIndex = strings.indexOf(fiber.name) + 1;
210
+ if (nameIndex === 0) {
211
+ strings.push(fiber.name);
212
+ nameIndex = strings.length;
213
+ }
214
+ ops.push(OP_ADD, fiber.id, fiber.type, parent, 0, nameIndex, 0);
215
+ added.add(fiber.id);
216
+ }
217
+ const table = strings.flatMap(encodeString);
218
+ operations.push([RENDERER_ID, ROOT_ID, table.length, ...table, ...ops]);
219
+ commitData.push({
220
+ changeDescriptions: commit.fibers.map(f => [f.id, changeDescription(f.cause)]),
221
+ duration: commit.durationMs,
222
+ effectDuration: null,
223
+ fiberActualDurations: commit.fibers.map(f => [f.id, f.actualMs]),
224
+ fiberSelfDurations: commit.fibers.map(f => [f.id, f.selfMs]),
225
+ passiveEffectDuration: null,
226
+ priorityLevel: null,
227
+ timestamp: Math.max(0, commit.t - t0),
228
+ perflabCauses: commit.causes,
229
+ perflabCarrier: commit.carrier,
230
+ perflabStore: commit.store,
231
+ perflabTask: commit.task,
232
+ ...(commit.facts ? { perflabFacts: commit.facts } : {}),
233
+ updaters: (commit.updaters ?? []).map(displayName => ({
234
+ displayName,
235
+ hocDisplayNames: null,
236
+ compiledWithForget: false,
237
+ key: null,
238
+ type: TYPE_FUNCTION,
239
+ })),
240
+ });
241
+ });
242
+ return {
243
+ version: 5,
244
+ source: 'perflab',
245
+ captureStart: { clock: 'perf-now-ms', t0 },
246
+ dataForRoots: [
247
+ {
248
+ rootID: ROOT_ID,
249
+ displayName: 'Root',
250
+ snapshots: [],
251
+ initialTreeBaseDurations: [],
252
+ operations,
253
+ commitData,
254
+ },
255
+ ],
256
+ };
257
+ };