@samitouri / QOS-React / commits / c8f8943c9a

Transfer strings in a string table

Dan Abramov committed Apr 25, 2019 at 17:11 UTC c8f8943c9ad60a5416cf1a5c57f9c0e966a49b4e
4 files changed +116 -69
OVERVIEW.md
+34 -9
@@ -14,10 +14,39 @@ The old DevTools also rendered the entire application tree in the form of a larg
14
15 Every React commit that changes the tree in a way DevTools cares about results in an "_operations_" message being sent across the bridge. These messages are lightweight patches that describe the changes that were made. (We don't resend the full tree structure like in legacy DevTools.)
16
17 -The payload for each message is a typed array. The first two entries are numbers that identify which renderer and root the update belongs to (for multi-root support). The rest of the array depends on the operations being made to the tree.
17 +The payload for each message is a typed array. The first two entries are numbers that identify which renderer and root the update belongs to (for multi-root support). Then the strings are encoded in a string table. The rest of the array depends on the operations being made to the tree.
18
19 No updates are required for many commits because we only send the following bits of information: element type, id, parent id, owner id, name, and key. Additional information (e.g. props, state) requires a separate "_inspectElement_" message.
20
21 +#### String table
22 +
23 +The string table is encoded right after the first two numbers.
24 +
25 +It consists of:
26 +
27 +1. the total length of next items that belong to string table
28 +2. for each string in a table:
29 + 1. encoded size
30 + 2. a list of its codepoints
31 +
32 +For example, for `Foo` and `Bar` we would see:
33 +
34 +```
35 +[
36 + 8, // string table length
37 + 3, // encoded display name size
38 + 70, // "F"
39 + 111, // "o"
40 + 111, // "o"
41 + 3, // encoded display name size
42 + 66, // "B"
43 + 97, // "a"
44 + 114, // "r"
45 +]
46 +```
47 +
48 +Later operations will reference strings by a one-based index. For example, `1` would mean `"Foo"`, and `2` would mean `"Bar"`. The `0` string id always represents `null` and isn't explicitly encoded in the table.
49 +
50 #### Adding a root node
51
52 Adding a root to the tree requires sending 4 numbers:
@@ -46,10 +75,8 @@ Adding a leaf node takes a variable number of numbers since we need to decode th
75 1. element type constant (e.g. `1 === ElementTypeClass`)
76 1. parent fiber id
77 1. owner fiber id
49 -1. UTF encoded display name size
50 - * (followed by this number of encoded values)
51 -1. UTF encoded key size
52 - * (followed by this number of encoded values)
78 +1. string table id for `displayName`
79 +1. string table id for `key`
80
81 For example, adding a function component `<Foo>` with an id 2:
82 ```js
@@ -60,10 +87,8 @@ For example, adding a function component `<Foo>` with an id 2:
87 1, // parent id
88 0, // owner id
89 3, // encoded display name size
63 - 70, // "F"
64 - 111, // "o"
65 - 111, // "o"
66 - 0, // encoded key (null)
90 + 1, // id of "Foo" displayName in the string table
91 + 0, // id of null key in the string table (always zero for null)
92 ]
93 ```
94
src/backend/renderer.js
+40 -30
@@ -590,6 +590,9 @@ export function attach(
590 let pendingRealUnmountedIDs: Array<number> = [];
591 let pendingSimulatedUnmountedIDs: Array<number> = [];
592 let pendingOperationsQueue: Array<Uint32Array> | null = [];
593 + let pendingStringTable: Map<string, number> = new Map();
594 + let pendingStringTableLength = 0;
595 + let pendingStringTableCounter = 0;
596
597 function pushOperation(op: number): void {
598 if (__DEV__) {
@@ -620,6 +623,10 @@ export function attach(
623 const ops = new Uint32Array(
624 // Identify which renderer this update is coming from.
625 2 + // [rendererID, rootFiberID]
626 + // How big is the string table?
627 + 1 + // [stringTableLength]
628 + // Then goes the actual string table.
629 + pendingStringTableLength +
630 // All unmounts are batched in a single message.
631 2 + // [TREE_OPERATION_REMOVE, removedIDLength]
632 pendingRealUnmountedIDs.length +
@@ -635,6 +642,15 @@ export function attach(
642 ops[i++] = rendererID;
643 ops[i++] = getFiberID(getPrimaryFiber(root.current));
644
645 + // Now fill in the string table.
646 + // [stringTableLength, str1Length, ...str1, str2Length, ...str2, ...]
647 + ops[i++] = pendingStringTableLength;
648 + pendingStringTable.forEach((value, key) => {
649 + ops[i++] = key.length;
650 + ops.set(utfEncodeString(key), i);
651 + i += key.length;
652 + });
653 +
654 // All unmounts except roots are batched in a single message.
655 ops[i++] = TREE_OPERATION_REMOVE;
656 // The first number is how many unmounted IDs we're gonna send.
@@ -672,6 +688,26 @@ export function attach(
688 pendingOperations.length = 0;
689 pendingRealUnmountedIDs.length = 0;
690 pendingSimulatedUnmountedIDs.length = 0;
691 + pendingStringTable.clear();
692 + pendingStringTableLength = 0;
693 + pendingStringTableCounter = 0;
694 + }
695 +
696 + function getStringID(str: string | null): number {
697 + if (str === null) {
698 + return 0;
699 + }
700 + const existingID = pendingStringTable.get(str);
701 + if (existingID !== undefined) {
702 + return existingID;
703 + }
704 + let id = ++pendingStringTableCounter;
705 + pendingStringTable.set(str, id);
706 + // The string table total length needs to account
707 + // both for the string length, and for the array item
708 + // that contains the length itself. Hence + 1.
709 + pendingStringTableLength += str.length + 1;
710 + return id;
711 }
712
713 function recordMount(fiber: Fiber, parentFiber: Fiber | null) {
@@ -700,41 +736,15 @@ export function attach(
736 _debugOwner != null ? getFiberID(getPrimaryFiber(_debugOwner)) : 0;
737 const parentID = getFiberID(getPrimaryFiber(parentFiber));
738
703 - let encodedDisplayName = ((null: any): Uint8Array);
704 - let encodedKey = ((null: any): Uint8Array);
705 -
706 - if (displayName !== null) {
707 - encodedDisplayName = utfEncodeString(displayName);
708 - }
709 -
710 - if (key !== null) {
711 - // React$Key supports string and number types as inputs,
712 - // But React converts numeric keys to strings, so we only have to handle that type here.
713 - // https://github.com/facebook/react/blob/0e67969cb1ad8c27a72294662e68fa5d7c2c9783/packages/react/src/ReactElement.js#L187
714 - encodedKey = utfEncodeString(((key: any): string));
715 - }
716 -
717 - const encodedDisplayNameSize =
718 - displayName === null ? 0 : encodedDisplayName.length;
719 - const encodedKeySize = key === null ? 0 : encodedKey.length;
720 -
739 + let displayNameStringID = getStringID(displayName);
740 + let keyStringID = getStringID(key);
741 pushOperation(TREE_OPERATION_ADD);
742 pushOperation(id);
743 pushOperation(type);
744 pushOperation(parentID);
745 pushOperation(ownerID);
726 - pushOperation(encodedDisplayNameSize);
727 - if (displayName !== null) {
728 - for (let i = 0; i < encodedDisplayName.length; i++) {
729 - pushOperation(encodedDisplayName[i]);
730 - }
731 - }
732 - pushOperation(encodedKeySize);
733 - if (key !== null) {
734 - for (let i = 0; i < encodedKey.length; i++) {
735 - pushOperation(encodedKey[i]);
736 - }
737 - }
746 + pushOperation(displayNameStringID);
747 + pushOperation(keyStringID);
748 }
749
750 if (isProfiling) {
src/devtools/store.js
+21 -15
@@ -630,6 +630,22 @@ export default class Store extends EventEmitter {
630 // We'll use the parent ID to adjust selection if it gets deleted.
631
632 let i = 2;
633 +
634 + // Reassemble the string table.
635 + let stringTable = [
636 + null, // ID = 0 corresponds to the null string.
637 + ];
638 + const stringTableSize = operations[i++];
639 + const stringTableEnd = i + stringTableSize;
640 + while (i < stringTableEnd) {
641 + let nextLength = operations[i++];
642 + let nextString = utfDecodeString(
643 + (operations.slice(i, i + nextLength): any)
644 + );
645 + stringTable.push(nextString);
646 + i += nextLength;
647 + }
648 +
649 while (i < operations.length) {
650 const operation = operations[i];
651 switch (operation) {
@@ -686,23 +702,13 @@ export default class Store extends EventEmitter {
702 ownerID = ((operations[i]: any): number);
703 i++;
704
689 - const displayNameLength = operations[i];
705 + const displayNameStringID = operations[i];
706 + const displayName = stringTable[displayNameStringID];
707 i++;
691 - const displayName =
692 - displayNameLength === 0
693 - ? null
694 - : utfDecodeString(
695 - (operations.slice(i, i + displayNameLength): any)
696 - );
697 - i += displayNameLength;
698 -
699 - const keyLength = operations[i];
708 +
709 + const keyStringID = operations[i];
710 + const key = stringTable[keyStringID];
711 i++;
701 - const key =
702 - keyLength === 0
703 - ? null
704 - : utfDecodeString((operations.slice(i, i + keyLength): any));
705 - i += +keyLength;
712
713 if (__DEBUG__) {
714 debug(
src/devtools/views/Profiler/CommitTreeBuilder.js
+21 -15
@@ -167,6 +167,22 @@ function updateTree(
167 };
168
169 let i = 2;
170 +
171 + // Reassemble the string table.
172 + let stringTable = [
173 + null, // ID = 0 corresponds to the null string.
174 + ];
175 + const stringTableSize = operations[i++];
176 + const stringTableEnd = i + stringTableSize;
177 + while (i < stringTableEnd) {
178 + let nextLength = operations[i++];
179 + let nextString = utfDecodeString(
180 + (operations.slice(i, i + nextLength): any)
181 + );
182 + stringTable.push(nextString);
183 + i += nextLength;
184 + }
185 +
186 while (i < operations.length) {
187 const operation = operations[i];
188
@@ -209,23 +225,13 @@ function updateTree(
225
226 i++; // ownerID
227
212 - const displayNameLength = operations[i];
228 + const displayNameStringID = operations[i];
229 + const displayName = stringTable[displayNameStringID];
230 i++;
214 - const displayName =
215 - displayNameLength === 0
216 - ? null
217 - : utfDecodeString(
218 - (operations.slice(i, i + displayNameLength): any)
219 - );
220 - i += displayNameLength;
221 -
222 - const keyLength = operations[i];
231 +
232 + const keyStringID = operations[i];
233 + const key = stringTable[keyStringID];
234 i++;
224 - const key =
225 - keyLength === 0
226 - ? null
227 - : utfDecodeString((operations.slice(i, i + keyLength): any));
228 - i += +keyLength;
235
236 if (__DEBUG__) {
237 debug(