mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Moved some things from ProfilerContext reducer into (root) Store
This commit is contained in:
+62
-2
@@ -25,6 +25,13 @@ const debug = (methodName, ...args) => {
|
||||
}
|
||||
};
|
||||
|
||||
type ProfilingSnapshotNode = {|
|
||||
id: number,
|
||||
children: Array<number>,
|
||||
displayName: string | null,
|
||||
key: number | string | null,
|
||||
|};
|
||||
|
||||
export type Capabilities = {|
|
||||
supportsProfiling: boolean,
|
||||
|};
|
||||
@@ -40,10 +47,24 @@ export default class Store extends EventEmitter {
|
||||
// Elements are mutable (for now) to avoid excessive cloning during tree updates.
|
||||
_idToElement: Map<number, Element> = new Map();
|
||||
|
||||
// When profiling is in progress, operations are stored so that we can later reconstruct past commit trees.
|
||||
_isProfiling: boolean = false;
|
||||
|
||||
// Total number of visible elements (within all roots).
|
||||
// Used for windowing purposes.
|
||||
_numElements: number = 0;
|
||||
|
||||
// List of tree mutation that occur during profiling.
|
||||
// Once profiling is finished, these mutations can be used, along with the initial tree snapshots,
|
||||
// to reconstruct the state of each root for each commit.
|
||||
_profilingOperations: Map<number, Array<Uint32Array>> = new Map();
|
||||
|
||||
// Snapshot of the state of the main Store (including all roots) when profiling started.
|
||||
// Once profiling is finished, this snapshot can be used along with "operations" messages emitted during profiling,
|
||||
// to reconstruct the state of each root for each commit.
|
||||
// It's okay to use a single root to store this information because node IDs are unique across all roots.
|
||||
_profilingSnapshot: Map<number, ProfilingSnapshotNode> = new Map();
|
||||
|
||||
// Incremented each time the store is mutated.
|
||||
// This enables a passive effect to detect a mutation between render and commit phase.
|
||||
_revision: number = 0;
|
||||
@@ -65,8 +86,13 @@ export default class Store extends EventEmitter {
|
||||
debug('constructor', 'subscribing to Bridge');
|
||||
|
||||
this._bridge = bridge;
|
||||
this._bridge.addListener('operations', this.onBridgeOperations);
|
||||
this._bridge.addListener('shutdown', this.onBridgeShutdown);
|
||||
bridge.addListener('operations', this.onBridgeOperations);
|
||||
bridge.addListener('profilingStatus', this.onProfilingStatus);
|
||||
bridge.addListener('shutdown', this.onBridgeShutdown);
|
||||
|
||||
// It's possible that profiling has already started (e.g. "reload and start profiling")
|
||||
// so the frontend needs to ask the backend for its status after mounting.
|
||||
bridge.send('getProfilingStatus');
|
||||
}
|
||||
|
||||
get numElements(): number {
|
||||
@@ -216,6 +242,20 @@ export default class Store extends EventEmitter {
|
||||
return null;
|
||||
}
|
||||
|
||||
_takeProfilingSnapshotRecursive = (id: number) => {
|
||||
const element = this.getElementByID(id);
|
||||
if (element !== null) {
|
||||
this._profilingSnapshot.set(id, {
|
||||
id,
|
||||
children: element.children.slice(0),
|
||||
displayName: element.displayName,
|
||||
key: element.key,
|
||||
});
|
||||
|
||||
element.children.forEach(this._takeProfilingSnapshotRecursive);
|
||||
}
|
||||
};
|
||||
|
||||
onBridgeOperations = (operations: Uint32Array) => {
|
||||
if (!(operations instanceof Uint32Array)) {
|
||||
// $FlowFixMe TODO HACK Temporary workaround for the fact that Chrome is not transferring the typed array.
|
||||
@@ -228,6 +268,15 @@ export default class Store extends EventEmitter {
|
||||
|
||||
const rendererID = operations[0];
|
||||
|
||||
if (this._isProfiling) {
|
||||
const profilingOperations = this._profilingOperations.get(rendererID);
|
||||
if (profilingOperations == null) {
|
||||
this._profilingOperations.set(rendererID, [operations]);
|
||||
} else {
|
||||
profilingOperations.push(operations);
|
||||
}
|
||||
}
|
||||
|
||||
let addedElementIDs: Uint32Array = new Uint32Array(0);
|
||||
let removedElementIDs: Uint32Array = new Uint32Array(0);
|
||||
|
||||
@@ -432,10 +481,21 @@ export default class Store extends EventEmitter {
|
||||
this.emit('mutated', [addedElementIDs, removedElementIDs]);
|
||||
};
|
||||
|
||||
onProfilingStatus = (isProfiling: boolean) => {
|
||||
this._isProfiling = isProfiling;
|
||||
|
||||
if (isProfiling) {
|
||||
this._profilingSnapshot = new Map();
|
||||
|
||||
this.roots.forEach(this._takeProfilingSnapshotRecursive);
|
||||
}
|
||||
};
|
||||
|
||||
onBridgeShutdown = () => {
|
||||
debug('onBridgeShutdown', 'unsubscribing from Bridge');
|
||||
|
||||
this._bridge.removeListener('operations', this.onBridgeOperations);
|
||||
this._bridge.removeListener('profilingStatus', this.onProfilingStatus);
|
||||
this._bridge.removeListener('shutdown', this.onBridgeShutdown);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,19 +4,12 @@ import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useReducer,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { BridgeContext, StoreContext } from '../context';
|
||||
import reducer from './reducer';
|
||||
|
||||
import type {
|
||||
HANDLE_OPERATIONS_ACTION,
|
||||
HANDLE_PROFILING_STATUS_CHANGE_ACTION,
|
||||
SEND_START_PROFILING_ACTION,
|
||||
SEND_STOP_PROFILING_ACTION,
|
||||
} from './reducer';
|
||||
// TODO (profiling) Connect to store and listen for new roots and load data.
|
||||
|
||||
type Context = {|
|
||||
hasProfilingData: boolean,
|
||||
@@ -36,65 +29,27 @@ function ProfilerContextController({ children }: Props) {
|
||||
const bridge = useContext(BridgeContext);
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
// Some of this reducer's actions require access to the store.
|
||||
// The store is mutable, but the Store itself is global and lives for the lifetime of the DevTools,
|
||||
// so we don't need to re-init the reducer in any special way.
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
hasProfilingData: false,
|
||||
isProfiling: false,
|
||||
_operations: [],
|
||||
_snapshot: new Map(),
|
||||
});
|
||||
const [isProfiling, setIsProfiling] = useState(false);
|
||||
|
||||
const startProfiling = useCallback(() => {
|
||||
bridge.send('startProfiling');
|
||||
dispatch(({ type: 'SEND_START_PROFILING' }: SEND_START_PROFILING_ACTION));
|
||||
}, [bridge, dispatch]);
|
||||
setIsProfiling(true);
|
||||
}, [bridge]);
|
||||
const stopProfiling = useCallback(() => {
|
||||
bridge.send('stopProfiling');
|
||||
dispatch(({ type: 'SEND_STOP_PROFILING' }: SEND_STOP_PROFILING_ACTION));
|
||||
}, [bridge, dispatch]);
|
||||
setIsProfiling(false);
|
||||
}, [bridge]);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
hasProfilingData: state.hasProfilingData,
|
||||
isProfiling: state.isProfiling,
|
||||
hasProfilingData: false, // TODO (profiling) Connect to store and listen for new roots and load data.
|
||||
isProfiling,
|
||||
startProfiling,
|
||||
stopProfiling,
|
||||
}),
|
||||
[state, startProfiling, stopProfiling]
|
||||
[isProfiling, startProfiling, stopProfiling]
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const handleOperations = (operations: Uint32Array) =>
|
||||
dispatch(
|
||||
({
|
||||
type: 'HANDLE_OPERATIONS',
|
||||
payload: operations,
|
||||
}: HANDLE_OPERATIONS_ACTION)
|
||||
);
|
||||
|
||||
const handleProfilingStatus = (isProfiling: boolean) =>
|
||||
dispatch(
|
||||
({
|
||||
type: 'HANDLE_PROFILING_STATUS_CHANGE',
|
||||
payload: { isProfiling, store },
|
||||
}: HANDLE_PROFILING_STATUS_CHANGE_ACTION)
|
||||
);
|
||||
|
||||
bridge.addListener('operations', handleOperations);
|
||||
bridge.addListener('profilingStatus', handleProfilingStatus);
|
||||
|
||||
// It's possible that profiling has already started (e.g. "reload and start profiling")
|
||||
// so the frontend needs to ask the backend for its status after mounting.
|
||||
bridge.send('getProfilingStatus');
|
||||
|
||||
return () => {
|
||||
bridge.removeListener('operations', handleOperations);
|
||||
bridge.removeListener('profilingStatus', handleProfilingStatus);
|
||||
};
|
||||
}, [bridge, dispatch, store]);
|
||||
|
||||
return (
|
||||
<ProfilerContext.Provider value={value}>
|
||||
{children}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import Store from '../../store';
|
||||
|
||||
type Node = {|
|
||||
id: number,
|
||||
children: Array<number>,
|
||||
displayName: string | null,
|
||||
key: number | string | null,
|
||||
|};
|
||||
|
||||
export type State = {|
|
||||
hasProfilingData: boolean,
|
||||
isProfiling: boolean,
|
||||
|
||||
// List of tree mutation that occur during profiling.
|
||||
// Once profiling is finished, these mutations can be used, along with the initial tree snapshots,
|
||||
// to reconstruct the state of each root for each commit.
|
||||
_operations: Array<Uint32Array>,
|
||||
|
||||
// Snapshot of the state of the main Store (including all roots) when profiling started.
|
||||
// Once profiling is finished, this snapshot can be used along with "operations" messages emitted during profiling,
|
||||
// to reconstruct the state of each root for each commit.
|
||||
// It's okay to use a single root to store this information because node IDs are unique across all roots.
|
||||
_snapshot: Map<number, Node>,
|
||||
|};
|
||||
|
||||
export type HANDLE_OPERATIONS_ACTION = {|
|
||||
type: 'HANDLE_OPERATIONS',
|
||||
payload: Uint32Array,
|
||||
|};
|
||||
|
||||
export type HANDLE_PROFILING_STATUS_CHANGE_ACTION = {|
|
||||
type: 'HANDLE_PROFILING_STATUS_CHANGE',
|
||||
payload: {
|
||||
isProfiling: boolean,
|
||||
store: Store,
|
||||
},
|
||||
|};
|
||||
|
||||
export type SEND_START_PROFILING_ACTION = {|
|
||||
type: 'SEND_START_PROFILING',
|
||||
|};
|
||||
|
||||
export type SEND_STOP_PROFILING_ACTION = {|
|
||||
type: 'SEND_STOP_PROFILING',
|
||||
|};
|
||||
|
||||
type Action =
|
||||
| HANDLE_OPERATIONS_ACTION
|
||||
| HANDLE_PROFILING_STATUS_CHANGE_ACTION
|
||||
| SEND_START_PROFILING_ACTION
|
||||
| SEND_STOP_PROFILING_ACTION;
|
||||
|
||||
// TODO (profiling) Lift this state up so it's shared between tabs.
|
||||
|
||||
export default function reducer(state: State, action: Action): State {
|
||||
const { type } = action;
|
||||
switch (type) {
|
||||
case 'HANDLE_OPERATIONS':
|
||||
if (state.isProfiling) {
|
||||
const operations = ((action: any): HANDLE_OPERATIONS_ACTION).payload;
|
||||
return {
|
||||
...state,
|
||||
hasProfilingData: true,
|
||||
_operations: state._operations.concat(operations),
|
||||
};
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
case 'HANDLE_PROFILING_STATUS_CHANGE':
|
||||
const {
|
||||
isProfiling,
|
||||
store,
|
||||
} = ((action: any): HANDLE_PROFILING_STATUS_CHANGE_ACTION).payload;
|
||||
|
||||
if (isProfiling) {
|
||||
const snapshot = new Map();
|
||||
|
||||
const recursiveSnapshot = id => {
|
||||
const element = store.getElementByID(id);
|
||||
if (element !== null) {
|
||||
snapshot.set(id, {
|
||||
id,
|
||||
children: element.children.slice(0),
|
||||
displayName: element.displayName,
|
||||
key: element.key,
|
||||
});
|
||||
element.children.forEach(id => recursiveSnapshot(id));
|
||||
}
|
||||
};
|
||||
|
||||
store.roots.forEach(rootID => recursiveSnapshot(rootID));
|
||||
|
||||
return {
|
||||
...state,
|
||||
isProfiling,
|
||||
_operations: [],
|
||||
_snapshot: snapshot,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...state,
|
||||
isProfiling,
|
||||
};
|
||||
}
|
||||
case 'SEND_START_PROFILING':
|
||||
return { ...state, hasProfilingData: false, isProfiling: true };
|
||||
case 'SEND_STOP_PROFILING':
|
||||
return { ...state, isProfiling: false };
|
||||
default:
|
||||
throw new Error(`Unrecognized action "${type}"`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user