diff --git a/src/hooks/useFlag.ts b/src/hooks/useFlag.ts index 7acd11b6c..3d8150cd6 100644 --- a/src/hooks/useFlag.ts +++ b/src/hooks/useFlag.ts @@ -1,7 +1,7 @@ import { useCallback, useState } from '../lib/teact/teact'; -const useFlag = (initial = false): [boolean, NoneToVoidFunction, NoneToVoidFunction] => { - const [value, setValue] = useState(initial); +const useFlag = (initial = false, debugKey?: string): [boolean, NoneToVoidFunction, NoneToVoidFunction] => { + const [value, setValue] = useState(initial, debugKey); const setTrue = useCallback(() => { setValue(true); diff --git a/src/lib/teact/teact.ts b/src/lib/teact/teact.ts index 47f7883a8..ff7cc24b8 100644 --- a/src/lib/teact/teact.ts +++ b/src/lib/teact/teact.ts @@ -6,13 +6,12 @@ import { import { orderBy } from '../../util/iteratees'; import { getUnequalProps } from '../../util/arePropsShallowEqual'; import { handleError } from '../../util/handleError'; +import { incrementOverlayCounter } from '../../util/debugOverlay'; export type Props = AnyLiteral; export type FC
= (props: P) => any;
// eslint-disable-next-line @typescript-eslint/naming-convention
-export type FC_withDebug = FC & {
- DEBUG_contentComponentName?: string;
-};
+export type FC_withDebug = FC & { DEBUG_contentComponentName?: string };
export enum VirtualElementTypesEnum {
Empty,
@@ -339,6 +338,11 @@ export function renderComponent(componentInstance: ComponentInstance) {
}
DEBUG_components[componentName].renderTimes.push(duration);
DEBUG_components[componentName].renderCount++;
+
+ if (DEBUG_MORE) {
+ incrementOverlayCounter(`${componentName} renders`);
+ incrementOverlayCounter(`${componentName} duration`, duration);
+ }
}
} catch (err: any) {
handleError(err);
diff --git a/src/util/debugOverlay.ts b/src/util/debugOverlay.ts
new file mode 100644
index 000000000..e30378c0e
--- /dev/null
+++ b/src/util/debugOverlay.ts
@@ -0,0 +1,109 @@
+import { throttle } from './schedulers';
+
+const KEYS_TO_IGNORE = new Set([
+ 'TeactMemoWrapper renders',
+ 'TeactNContainer renders',
+ 'Button renders',
+]);
+const MIN_RENDERS_TO_SHOW = 5;
+const MIN_DURATION_TO_SHOW = 2;
+const BG_GREEN = ' style="background: lightgreen"';
+
+let counters: Record
`;
+
+ if (wasAtBottom) {
+ loggerEl.scrollTop = loggerEl.scrollHeight;
+ }
+}
+
+export function incrementOverlayCounter(key: string, value = 1) {
+ const now = Date.now();
+ if (!counters[key]) {
+ counters[key] = { value, lastUpdateAt: now };
+ } else {
+ counters[key].value += value;
+ counters[key].lastUpdateAt = now;
+ }
+
+ renderCountersThrottled();
+}
+
+export function renderCounters() {
+ if (!loggerEl) {
+ setupOverlay();
+ }
+
+ const halfSecondAgo = Date.now() - 500;
+ const [maxRenders, maxDuration] = Object.entries(counters).reduce((acc, [key, { value }]) => {
+ if (KEYS_TO_IGNORE.has(key)) {
+ return acc;
+ }
+
+ if (key.includes('renders') && value > acc[0]) {
+ acc[0] = value;
+ }
+
+ if (key.includes('duration') && value > acc[1]) {
+ acc[1] = value;
+ }
+
+ return acc;
+ }, [0, 0]);
+
+ loggerEl.innerHTML = Object
+ .entries(counters)
+ .filter(([key, { value }]) => (
+ (!KEYS_TO_IGNORE.has(key)) && (
+ (key.includes('renders') && value > MIN_RENDERS_TO_SHOW)
+ || (key.includes('duration') && value > MIN_DURATION_TO_SHOW)
+ )
+ ))
+ .sort((a, b) => (
+ b[1].lastUpdateAt - a[1].lastUpdateAt
+ ))
+ .map(([key, { value, lastUpdateAt }]) => ([
+ `