Add hysteresis and EMA to ping to avoid flickering network badge (#7197)
* Don't use WEAK and yellow * fmt && lint && tsc * Fix up the rebase & dark mode colors * Update src/hooks/useNetworkStatus.tsx Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> * Change Weak to Ok * Change Connected to Strong * fmt * Sync selectors for start sketch * Remove unused test-util brought back in a rebase * Align the other OKs * Add an else statement to overallState --------- Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
This commit is contained in:
@ -823,7 +823,7 @@ test('theme persists', async ({ page, context, homePage }) => {
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
await expect(networkToggle).toContainText('Network health (Strong)')
|
||||
|
||||
await expect(page.getByText('building scene')).not.toBeVisible()
|
||||
|
||||
|
||||
@ -14,8 +14,10 @@ test.describe('Test network related behaviors', () => {
|
||||
'simulate network down and network little widget',
|
||||
{ tag: '@skipLocalEngine' },
|
||||
async ({ page, homePage }) => {
|
||||
const networkToggleConnectedText = page.getByText('Connected')
|
||||
const networkToggleWeakText = page.getByText('Network health (Weak)')
|
||||
const networkToggleConnectedText = page.getByText(
|
||||
'Network health (Strong)'
|
||||
)
|
||||
const networkToggleWeakText = page.getByText('Network health (Ok)')
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
@ -98,8 +100,10 @@ test.describe('Test network related behaviors', () => {
|
||||
{ tag: '@skipLocalEngine' },
|
||||
async ({ page, homePage, toolbar, scene, cmdBar }) => {
|
||||
const networkToggle = page.getByTestId('network-toggle')
|
||||
const networkToggleConnectedText = page.getByText('Connected')
|
||||
const networkToggleWeakText = page.getByText('Network health (Weak)')
|
||||
const networkToggleConnectedText = page.getByText(
|
||||
'Network health (Strong)'
|
||||
)
|
||||
const networkToggleWeakText = page.getByText('Network health (Ok)')
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
@ -282,8 +286,10 @@ profile001 = startProfile(sketch001, at = [12.34, -12.34])
|
||||
{ tag: ['@desktop', '@skipLocalEngine'] },
|
||||
async ({ page, homePage, scene, cmdBar, toolbar, tronApp }) => {
|
||||
const networkToggle = page.getByTestId('network-toggle')
|
||||
const networkToggleConnectedText = page.getByText('Connected')
|
||||
const networkToggleWeakText = page.getByText('Network health (Weak)')
|
||||
const networkToggleConnectedText = page.getByText(
|
||||
'Network health (Strong)'
|
||||
)
|
||||
const networkToggleWeakText = page.getByText('Network health (Ok)')
|
||||
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
|
||||
@ -10,8 +10,8 @@ import { reportRejection } from '@src/lib/trap'
|
||||
import { toSync } from '@src/lib/utils'
|
||||
|
||||
export const NETWORK_HEALTH_TEXT: Record<NetworkHealthState, string> = {
|
||||
[NetworkHealthState.Ok]: 'Connected',
|
||||
[NetworkHealthState.Weak]: 'Weak',
|
||||
[NetworkHealthState.Ok]: 'Strong',
|
||||
[NetworkHealthState.Weak]: 'Ok',
|
||||
[NetworkHealthState.Issue]: 'Problem',
|
||||
[NetworkHealthState.Disconnected]: 'Offline',
|
||||
}
|
||||
@ -53,8 +53,8 @@ const overallConnectionStateColor: Record<NetworkHealthState, IconColorConfig> =
|
||||
bg: 'bg-succeed-10/30 dark:bg-succeed-80/50',
|
||||
},
|
||||
[NetworkHealthState.Weak]: {
|
||||
icon: 'text-warn-80 dark:text-warn-10',
|
||||
bg: 'bg-warn-10 dark:bg-warn-80/80',
|
||||
icon: 'text-succeed-50 dark:text-succeed-30',
|
||||
bg: 'bg-lime-300/70 dark:bg-lime-300/30',
|
||||
},
|
||||
[NetworkHealthState.Issue]: {
|
||||
icon: 'text-destroy-80 dark:text-destroy-10',
|
||||
|
||||
@ -48,7 +48,8 @@ export function useNetworkStatus() {
|
||||
const [overallState, setOverallState] = useState<NetworkHealthState>(
|
||||
NetworkHealthState.Disconnected
|
||||
)
|
||||
const [ping, setPing] = useState<undefined | number>(undefined)
|
||||
const [pingRaw, setPingRaw] = useState<undefined | number>(undefined)
|
||||
const [pingEMA, setPingEMA] = useState<undefined | number>(undefined)
|
||||
const [hasCopied, setHasCopied] = useState<boolean>(false)
|
||||
|
||||
const [error, setError] = useState<ErrorType | undefined>(undefined)
|
||||
@ -66,16 +67,54 @@ export function useNetworkStatus() {
|
||||
|
||||
const [hasIssues, setHasIssues] = useState<boolean | undefined>(undefined)
|
||||
useEffect(() => {
|
||||
setOverallState(
|
||||
!internetConnected
|
||||
? NetworkHealthState.Disconnected
|
||||
: hasIssues || hasIssues === undefined
|
||||
? NetworkHealthState.Issue
|
||||
: (ping ?? 0) > 16.6 * 3 // we consider ping longer than 3 frames as weak
|
||||
? NetworkHealthState.Weak
|
||||
: NetworkHealthState.Ok
|
||||
)
|
||||
}, [hasIssues, internetConnected, ping])
|
||||
if (immediateState.type === EngineConnectionStateType.Disconnecting) {
|
||||
// Reset our running average.
|
||||
setPingRaw(undefined)
|
||||
setPingEMA(undefined)
|
||||
}
|
||||
}, [immediateState])
|
||||
|
||||
useEffect(() => {
|
||||
if (!pingRaw) return
|
||||
|
||||
// We use an exponential running average to smooth out ping values.
|
||||
const pingDataPointsToConsider = 10
|
||||
const multiplier = 2 / (pingDataPointsToConsider + 1)
|
||||
let pingEMANext = ((pingEMA ?? 0) + pingRaw) / 2
|
||||
pingEMANext = pingEMANext * multiplier + (pingEMA ?? 0) * (1 - multiplier)
|
||||
setPingEMA(pingEMANext)
|
||||
}, [pingRaw])
|
||||
|
||||
useEffect(() => {
|
||||
// We consider ping longer than 3 frames as weak
|
||||
const WEAK_PING = 16.6 * 3
|
||||
const OK_PING = 16.6 * 2
|
||||
|
||||
// A is used in the literature to specify the "window" of switching
|
||||
const A = 1.25
|
||||
|
||||
const CENTER = (WEAK_PING + OK_PING) / 2
|
||||
const THRESHOLD_GOOD = CENTER / A // Lower bound
|
||||
const THRESHOLD_WEAK = CENTER * A // Upper bound
|
||||
|
||||
let nextOverallState = overallState
|
||||
|
||||
if (!internetConnected) {
|
||||
nextOverallState = NetworkHealthState.Disconnected
|
||||
} else if (hasIssues || hasIssues === undefined) {
|
||||
nextOverallState = NetworkHealthState.Issue
|
||||
} else if (pingEMA && pingEMA < THRESHOLD_GOOD) {
|
||||
nextOverallState = NetworkHealthState.Ok
|
||||
} else if (pingEMA && pingEMA > THRESHOLD_WEAK) {
|
||||
nextOverallState = NetworkHealthState.Weak
|
||||
} else {
|
||||
nextOverallState = NetworkHealthState.Ok
|
||||
}
|
||||
|
||||
if (nextOverallState === overallState) return
|
||||
|
||||
setOverallState(nextOverallState)
|
||||
}, [hasIssues, internetConnected, pingEMA, overallState])
|
||||
|
||||
useEffect(() => {
|
||||
const onlineCallback = () => {
|
||||
@ -126,7 +165,7 @@ export function useNetworkStatus() {
|
||||
|
||||
useEffect(() => {
|
||||
const onPingPongChange = ({ detail: state }: CustomEvent) => {
|
||||
setPing(state)
|
||||
setPingRaw(state)
|
||||
}
|
||||
|
||||
const onConnectionStateChange = ({
|
||||
@ -231,6 +270,6 @@ export function useNetworkStatus() {
|
||||
error,
|
||||
setHasCopied,
|
||||
hasCopied,
|
||||
ping,
|
||||
ping: pingEMA !== undefined ? Math.trunc(pingEMA) : pingEMA,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user