Merge pull request #227 from gaearon/string-table

Transfer strings in a string table
This commit is contained in:
Dan Abramov
2019-04-25 17:54:11 +01:00
committed by GitHub
4 changed files with 113 additions and 68 deletions
+35 -10
View File
@@ -14,10 +14,39 @@ The old DevTools also rendered the entire application tree in the form of a larg
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.)
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.
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.
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.
#### String table
The string table is encoded right after the first two numbers.
It consists of:
1. the total length of next items that belong to string table
2. for each string in a table:
1. encoded size
2. a list of its UTF encoded codepoints
For example, for `Foo` and `Bar` we would see:
```
[
8, // string table length
3, // encoded display name size
70, // "F"
111, // "o"
111, // "o"
3, // encoded display name size
66, // "B"
97, // "a"
114, // "r"
]
```
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.
#### Adding a root node
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
1. element type constant (e.g. `1 === ElementTypeClass`)
1. parent fiber id
1. owner fiber id
1. UTF encoded display name size
* (followed by this number of encoded values)
1. UTF encoded key size
* (followed by this number of encoded values)
1. string table id for `displayName`
1. string table id for `key`
For example, adding a function component `<Foo>` with an id 2:
```js
@@ -60,10 +87,8 @@ For example, adding a function component `<Foo>` with an id 2:
1, // parent id
0, // owner id
3, // encoded display name size
70, // "F"
111, // "o"
111, // "o"
0, // encoded key (null)
1, // id of "Foo" displayName in the string table
0, // id of null key in the string table (always zero for null)
]
```
@@ -358,4 +383,4 @@ Here is an example of a profiling session consisting of two interactions:
}
```
The backend does not need to resend the timestamp for each of the commits because that was already sent as part of the "_profilingSummary_" message.
The backend does not need to resend the timestamp for each of the commits because that was already sent as part of the "_profilingSummary_" message.
+38 -30
View File
@@ -590,6 +590,8 @@ export function attach(
let pendingRealUnmountedIDs: Array<number> = [];
let pendingSimulatedUnmountedIDs: Array<number> = [];
let pendingOperationsQueue: Array<Uint32Array> | null = [];
let pendingStringTable: Map<string, number> = new Map();
let pendingStringTableLength = 0;
function pushOperation(op: number): void {
if (__DEV__) {
@@ -620,6 +622,10 @@ export function attach(
const ops = new Uint32Array(
// Identify which renderer this update is coming from.
2 + // [rendererID, rootFiberID]
// How big is the string table?
1 + // [stringTableLength]
// Then goes the actual string table.
pendingStringTableLength +
// All unmounts are batched in a single message.
2 + // [TREE_OPERATION_REMOVE, removedIDLength]
pendingRealUnmountedIDs.length +
@@ -635,6 +641,15 @@ export function attach(
ops[i++] = rendererID;
ops[i++] = getFiberID(getPrimaryFiber(root.current));
// Now fill in the string table.
// [stringTableLength, str1Length, ...str1, str2Length, ...str2, ...]
ops[i++] = pendingStringTableLength;
pendingStringTable.forEach((value, key) => {
ops[i++] = key.length;
ops.set(utfEncodeString(key), i);
i += key.length;
});
// All unmounts except roots are batched in a single message.
ops[i++] = TREE_OPERATION_REMOVE;
// The first number is how many unmounted IDs we're gonna send.
@@ -672,6 +687,25 @@ export function attach(
pendingOperations.length = 0;
pendingRealUnmountedIDs.length = 0;
pendingSimulatedUnmountedIDs.length = 0;
pendingStringTable.clear();
pendingStringTableLength = 0;
}
function getStringID(str: string | null): number {
if (str === null) {
return 0;
}
const existingID = pendingStringTable.get(str);
if (existingID !== undefined) {
return existingID;
}
const id = pendingStringTable.size + 1;
pendingStringTable.set(str, id);
// The string table total length needs to account
// both for the string length, and for the array item
// that contains the length itself. Hence + 1.
pendingStringTableLength += str.length + 1;
return id;
}
function recordMount(fiber: Fiber, parentFiber: Fiber | null) {
@@ -700,41 +734,15 @@ export function attach(
_debugOwner != null ? getFiberID(getPrimaryFiber(_debugOwner)) : 0;
const parentID = getFiberID(getPrimaryFiber(parentFiber));
let encodedDisplayName = ((null: any): Uint8Array);
let encodedKey = ((null: any): Uint8Array);
if (displayName !== null) {
encodedDisplayName = utfEncodeString(displayName);
}
if (key !== null) {
// React$Key supports string and number types as inputs,
// But React converts numeric keys to strings, so we only have to handle that type here.
// https://github.com/facebook/react/blob/0e67969cb1ad8c27a72294662e68fa5d7c2c9783/packages/react/src/ReactElement.js#L187
encodedKey = utfEncodeString(((key: any): string));
}
const encodedDisplayNameSize =
displayName === null ? 0 : encodedDisplayName.length;
const encodedKeySize = key === null ? 0 : encodedKey.length;
let displayNameStringID = getStringID(displayName);
let keyStringID = getStringID(key);
pushOperation(TREE_OPERATION_ADD);
pushOperation(id);
pushOperation(type);
pushOperation(parentID);
pushOperation(ownerID);
pushOperation(encodedDisplayNameSize);
if (displayName !== null) {
for (let i = 0; i < encodedDisplayName.length; i++) {
pushOperation(encodedDisplayName[i]);
}
}
pushOperation(encodedKeySize);
if (key !== null) {
for (let i = 0; i < encodedKey.length; i++) {
pushOperation(encodedKey[i]);
}
}
pushOperation(displayNameStringID);
pushOperation(keyStringID);
}
if (isProfiling) {
+20 -14
View File
@@ -630,6 +630,22 @@ export default class Store extends EventEmitter {
// We'll use the parent ID to adjust selection if it gets deleted.
let i = 2;
// Reassemble the string table.
const stringTable = [
null, // ID = 0 corresponds to the null string.
];
const stringTableSize = operations[i++];
const stringTableEnd = i + stringTableSize;
while (i < stringTableEnd) {
const nextLength = operations[i++];
const nextString = utfDecodeString(
(operations.slice(i, i + nextLength): any)
);
stringTable.push(nextString);
i += nextLength;
}
while (i < operations.length) {
const operation = operations[i];
switch (operation) {
@@ -686,23 +702,13 @@ export default class Store extends EventEmitter {
ownerID = ((operations[i]: any): number);
i++;
const displayNameLength = operations[i];
const displayNameStringID = operations[i];
const displayName = stringTable[displayNameStringID];
i++;
const displayName =
displayNameLength === 0
? null
: utfDecodeString(
(operations.slice(i, i + displayNameLength): any)
);
i += displayNameLength;
const keyLength = operations[i];
const keyStringID = operations[i];
const key = stringTable[keyStringID];
i++;
const key =
keyLength === 0
? null
: utfDecodeString((operations.slice(i, i + keyLength): any));
i += +keyLength;
if (__DEBUG__) {
debug(
@@ -167,6 +167,22 @@ function updateTree(
};
let i = 2;
// Reassemble the string table.
const stringTable = [
null, // ID = 0 corresponds to the null string.
];
const stringTableSize = operations[i++];
const stringTableEnd = i + stringTableSize;
while (i < stringTableEnd) {
const nextLength = operations[i++];
const nextString = utfDecodeString(
(operations.slice(i, i + nextLength): any)
);
stringTable.push(nextString);
i += nextLength;
}
while (i < operations.length) {
const operation = operations[i];
@@ -209,23 +225,13 @@ function updateTree(
i++; // ownerID
const displayNameLength = operations[i];
const displayNameStringID = operations[i];
const displayName = stringTable[displayNameStringID];
i++;
const displayName =
displayNameLength === 0
? null
: utfDecodeString(
(operations.slice(i, i + displayNameLength): any)
);
i += displayNameLength;
const keyLength = operations[i];
const keyStringID = operations[i];
const key = stringTable[keyStringID];
i++;
const key =
keyLength === 0
? null
: utfDecodeString((operations.slice(i, i + keyLength): any));
i += +keyLength;
if (__DEBUG__) {
debug(