mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #227 from gaearon/string-table
Transfer strings in a string table
This commit is contained in:
+35
-10
@@ -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
@@ -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
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user