Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions src/api/useDebounceIfVisible.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { getDefaultStore } from "jotai";
import { useCallback, useMemo, useRef } from "react";
import { useUnmount } from "react-use";
import { useThrottledCallback, useDebouncedCallback } from "use-debounce";
import { isDocumentVisibleAtom } from "../atoms";

const store = getDefaultStore();

/**
* Only use the throttled callback while visible.
* Otherwise, clear the timers and use requestAnimationFrame to call
* the callback on the next frame (rAF is paused while backgrounded, so no timers
* accumulate).
*/
export function useThrottledCallbackIfVisible<
Comment thread
asuzuki-jumptrading marked this conversation as resolved.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends (...args: any) => ReturnType<T>,
>(...args: Parameters<typeof useThrottledCallback<T>>) {
const throttledFn = useThrottledCallback(...args);
const rafRef = useRef<number | null>(null);

const argFunc = args[0];
const funcRef = useRef(argFunc);
funcRef.current = argFunc;

useUnmount(() => {
if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
});

const fn = useCallback(
(...args: Parameters<T>) => {
if (store.get(isDocumentVisibleAtom)) {
throttledFn(...args);
return;
}

// hidden, don't accumulate timers from throttledFn
throttledFn.cancel();
if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
rafRef.current = requestAnimationFrame(() => {
return funcRef.current(...args);
});
},
[throttledFn],
);

const cancel = useCallback(() => {
throttledFn.cancel();
if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
rafRef.current = null;
}, [throttledFn]);

return useMemo(() => Object.assign(fn, { cancel }), [fn, cancel]);
}

/**
* Only use the debounced callback while visible.
* Otherwise, clear the timers and use requestAnimationFrame to call
* the callback on the next frame (rAF is paused while backgrounded, so no timers
* accumulate).
*/
export function useDebouncedCallbackIfVisible<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T extends (...args: any) => ReturnType<T>,
>(...args: Parameters<typeof useDebouncedCallback<T>>) {
const debouncedFn = useDebouncedCallback(...args);
const rafRef = useRef<number | null>(null);

const argFunc = args[0];
const funcRef = useRef(argFunc);
funcRef.current = argFunc;

useUnmount(() => {
if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
});

const fn = useCallback(
(...args: Parameters<T>) => {
if (store.get(isDocumentVisibleAtom)) {
debouncedFn(...args);
return;
}

// hidden, don't accumulate timers from debouncedFn
debouncedFn.cancel();
if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
rafRef.current = requestAnimationFrame(() => {
return funcRef.current(...args);
});
},
[debouncedFn],
);

const cancel = useCallback(() => {
debouncedFn.cancel();
if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
rafRef.current = null;
}, [debouncedFn]);

return useMemo(() => Object.assign(fn, { cancel }), [fn, cancel]);
}
43 changes: 27 additions & 16 deletions src/api/useSetAtomWsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import type {
import { isEmaObjectKey } from "./worker/types";
import { DateTime } from "luxon";
import { useInterval } from "react-use";
import { useThrottledCallback, useDebouncedCallback } from "use-debounce";
import {
useThrottledCallbackIfVisible,
useDebouncedCallbackIfVisible,
} from "./useDebounceIfVisible";
import type z from "zod";
import {
skipRateAtom,
Expand Down Expand Up @@ -241,31 +244,37 @@ function useUpdateAtoms() {
useInterval(updateSlotDurationDbMs, 1_000);

const setEstimatedSlotDuration = useSetAtom(estimatedSlotDurationAtom);
const setDbEstimatedSlotDuration = useThrottledCallback(
const setDbEstimatedSlotDuration = useThrottledCallbackIfVisible(
(value?: EstimatedSlotDuration) => setEstimatedSlotDuration(value),
slotDurationDbMs,
);

const setEstimatedTps = useSetAtom(estimatedTpsAtom);
const setDbEstimatedTps = useThrottledCallback((value?: EstimatedTps) => {
setEstimatedTps(value);
}, estimatedTpsDebounceMs);
const setDbEstimatedTps = useThrottledCallbackIfVisible(
(value?: EstimatedTps) => {
setEstimatedTps(value);
},
estimatedTpsDebounceMs,
);

const setLiveNetworkMetrics = useSetAtom(liveNetworkMetricsAtom);
const setDbLiveNetworkMetrics = useThrottledCallback(
const setDbLiveNetworkMetrics = useThrottledCallbackIfVisible(
(value?: LiveNetworkMetrics) => {
setLiveNetworkMetrics(value);
},
liveNetworkMetricsDebounceMs,
);

const setLiveTileMetrics = useSetAtom(liveTileMetricsAtom);
const setDbLiveTileMetrics = useThrottledCallback((value?: TileMetrics) => {
setLiveTileMetrics(value);
}, liveTileMetricsDebounceMs);
const setDbLiveTileMetrics = useThrottledCallbackIfVisible(
(value?: TileMetrics) => {
setLiveTileMetrics(value);
},
liveTileMetricsDebounceMs,
);

const setLivePrimaryMetrics = useSetAtom(liveTilePrimaryMetricAtom);
const setDbLivePrimaryMetrics = useThrottledCallback(
const setDbLivePrimaryMetrics = useThrottledCallbackIfVisible(
(value?: LiveTilePrimaryMetric) => {
setLivePrimaryMetrics(value);
},
Expand All @@ -274,7 +283,7 @@ function useUpdateAtoms() {

const setRateLiveTxnWaterfall = useSetAtom(rateLiveWaterfallAtom);
const setLiveTxnWaterfall = useSetAtom(liveTxnWaterfallAtom);
const setDbLiveTxnWaterfall = useThrottledCallback(
const setDbLiveTxnWaterfall = useThrottledCallbackIfVisible(
(value?: LiveTxnWaterfall) => {
setLiveTxnWaterfall(value);
setRateLiveTxnWaterfall(value?.waterfall);
Expand All @@ -283,7 +292,7 @@ function useUpdateAtoms() {
);

const setTileTimer = useSetAtom(tileTimerAtom);
const setDbTileTimer = useThrottledCallback((value?: number[]) => {
const setDbTileTimer = useThrottledCallbackIfVisible((value?: number[]) => {
setTileTimer(value);
}, tileTimerDebounceMs);

Expand All @@ -307,15 +316,15 @@ function useUpdateAtoms() {
const setSlotStatus = useSetAtom(setSlotStatusAtom);

const setGossipNetworkStats = useSetAtom(gossipNetworkStatsAtom);
const setDbGossipNetworkStats = useThrottledCallback(
const setDbGossipNetworkStats = useThrottledCallbackIfVisible(
(value?: GossipNetworkStats) => {
setGossipNetworkStats(value);
},
gossipNetworkDebounceMs,
);

const setGossipPeersSize = useSetAtom(gossipPeersSizeAtom);
const setDbGossipPeersSize = useThrottledCallback(
const setDbGossipPeersSize = useThrottledCallbackIfVisible(
(value?: GossipPeersSize) => {
setGossipPeersSize(value);
},
Expand Down Expand Up @@ -427,7 +436,7 @@ function useUpdateAtoms() {
const peersBuffer = useRef(new Map<string, Peer>());
const removePeersBuffer = useRef(new Map<string, PeerRemove>());

const dbFlushBuffer = useDebouncedCallback(
const dbFlushBuffer = useDebouncedCallbackIfVisible(
() => {
updatePeers([...peersBuffer.current.values()]);
removePeers([...removePeersBuffer.current.values()]);
Expand Down Expand Up @@ -459,6 +468,7 @@ function useUpdateAtoms() {
}
}

// only triggers when document is visible
dbFlushBuffer();
},
[dbFlushBuffer],
Expand All @@ -472,7 +482,7 @@ function useUpdateAtoms() {
toRemove: new Set<string>(),
});

const dbFlushSupermajorityPeersBuffers = useDebouncedCallback(
const dbFlushSupermajorityPeersBuffers = useDebouncedCallbackIfVisible(
() => {
updateSupermajorityOnlinePeers(
[...supermajorityPeersBuffers.current.toAdd],
Expand All @@ -499,6 +509,7 @@ function useUpdateAtoms() {
}
}

// only triggers when document is visible
dbFlushSupermajorityPeersBuffers();
},
[dbFlushSupermajorityPeersBuffers],
Expand Down