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,
|
uploadThroughput: -1,
|
||||||
})
|
})
|
||||||
|
|
||||||
await expect(networkToggle).toContainText('Connected')
|
await expect(networkToggle).toContainText('Network health (Strong)')
|
||||||
|
|
||||||
await expect(page.getByText('building scene')).not.toBeVisible()
|
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',
|
'simulate network down and network little widget',
|
||||||
{ tag: '@skipLocalEngine' },
|
{ tag: '@skipLocalEngine' },
|
||||||
async ({ page, homePage }) => {
|
async ({ page, homePage }) => {
|
||||||
const networkToggleConnectedText = page.getByText('Connected')
|
const networkToggleConnectedText = page.getByText(
|
||||||
const networkToggleWeakText = page.getByText('Network health (Weak)')
|
'Network health (Strong)'
|
||||||
|
)
|
||||||
|
const networkToggleWeakText = page.getByText('Network health (Ok)')
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
@ -98,8 +100,10 @@ test.describe('Test network related behaviors', () => {
|
|||||||
{ tag: '@skipLocalEngine' },
|
{ tag: '@skipLocalEngine' },
|
||||||
async ({ page, homePage, toolbar, scene, cmdBar }) => {
|
async ({ page, homePage, toolbar, scene, cmdBar }) => {
|
||||||
const networkToggle = page.getByTestId('network-toggle')
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
const networkToggleConnectedText = page.getByText('Connected')
|
const networkToggleConnectedText = page.getByText(
|
||||||
const networkToggleWeakText = page.getByText('Network health (Weak)')
|
'Network health (Strong)'
|
||||||
|
)
|
||||||
|
const networkToggleWeakText = page.getByText('Network health (Ok)')
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
@ -282,8 +286,10 @@ profile001 = startProfile(sketch001, at = [12.34, -12.34])
|
|||||||
{ tag: ['@desktop', '@skipLocalEngine'] },
|
{ tag: ['@desktop', '@skipLocalEngine'] },
|
||||||
async ({ page, homePage, scene, cmdBar, toolbar, tronApp }) => {
|
async ({ page, homePage, scene, cmdBar, toolbar, tronApp }) => {
|
||||||
const networkToggle = page.getByTestId('network-toggle')
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
const networkToggleConnectedText = page.getByText('Connected')
|
const networkToggleConnectedText = page.getByText(
|
||||||
const networkToggleWeakText = page.getByText('Network health (Weak)')
|
'Network health (Strong)'
|
||||||
|
)
|
||||||
|
const networkToggleWeakText = page.getByText('Network health (Ok)')
|
||||||
|
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
|
|||||||
@ -10,8 +10,8 @@ import { reportRejection } from '@src/lib/trap'
|
|||||||
import { toSync } from '@src/lib/utils'
|
import { toSync } from '@src/lib/utils'
|
||||||
|
|
||||||
export const NETWORK_HEALTH_TEXT: Record<NetworkHealthState, string> = {
|
export const NETWORK_HEALTH_TEXT: Record<NetworkHealthState, string> = {
|
||||||
[NetworkHealthState.Ok]: 'Connected',
|
[NetworkHealthState.Ok]: 'Strong',
|
||||||
[NetworkHealthState.Weak]: 'Weak',
|
[NetworkHealthState.Weak]: 'Ok',
|
||||||
[NetworkHealthState.Issue]: 'Problem',
|
[NetworkHealthState.Issue]: 'Problem',
|
||||||
[NetworkHealthState.Disconnected]: 'Offline',
|
[NetworkHealthState.Disconnected]: 'Offline',
|
||||||
}
|
}
|
||||||
@ -53,8 +53,8 @@ const overallConnectionStateColor: Record<NetworkHealthState, IconColorConfig> =
|
|||||||
bg: 'bg-succeed-10/30 dark:bg-succeed-80/50',
|
bg: 'bg-succeed-10/30 dark:bg-succeed-80/50',
|
||||||
},
|
},
|
||||||
[NetworkHealthState.Weak]: {
|
[NetworkHealthState.Weak]: {
|
||||||
icon: 'text-warn-80 dark:text-warn-10',
|
icon: 'text-succeed-50 dark:text-succeed-30',
|
||||||
bg: 'bg-warn-10 dark:bg-warn-80/80',
|
bg: 'bg-lime-300/70 dark:bg-lime-300/30',
|
||||||
},
|
},
|
||||||
[NetworkHealthState.Issue]: {
|
[NetworkHealthState.Issue]: {
|
||||||
icon: 'text-destroy-80 dark:text-destroy-10',
|
icon: 'text-destroy-80 dark:text-destroy-10',
|
||||||
|
|||||||
@ -48,7 +48,8 @@ export function useNetworkStatus() {
|
|||||||
const [overallState, setOverallState] = useState<NetworkHealthState>(
|
const [overallState, setOverallState] = useState<NetworkHealthState>(
|
||||||
NetworkHealthState.Disconnected
|
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 [hasCopied, setHasCopied] = useState<boolean>(false)
|
||||||
|
|
||||||
const [error, setError] = useState<ErrorType | undefined>(undefined)
|
const [error, setError] = useState<ErrorType | undefined>(undefined)
|
||||||
@ -66,16 +67,54 @@ export function useNetworkStatus() {
|
|||||||
|
|
||||||
const [hasIssues, setHasIssues] = useState<boolean | undefined>(undefined)
|
const [hasIssues, setHasIssues] = useState<boolean | undefined>(undefined)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOverallState(
|
if (immediateState.type === EngineConnectionStateType.Disconnecting) {
|
||||||
!internetConnected
|
// Reset our running average.
|
||||||
? NetworkHealthState.Disconnected
|
setPingRaw(undefined)
|
||||||
: hasIssues || hasIssues === undefined
|
setPingEMA(undefined)
|
||||||
? NetworkHealthState.Issue
|
}
|
||||||
: (ping ?? 0) > 16.6 * 3 // we consider ping longer than 3 frames as weak
|
}, [immediateState])
|
||||||
? NetworkHealthState.Weak
|
|
||||||
: NetworkHealthState.Ok
|
useEffect(() => {
|
||||||
)
|
if (!pingRaw) return
|
||||||
}, [hasIssues, internetConnected, ping])
|
|
||||||
|
// 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(() => {
|
useEffect(() => {
|
||||||
const onlineCallback = () => {
|
const onlineCallback = () => {
|
||||||
@ -126,7 +165,7 @@ export function useNetworkStatus() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onPingPongChange = ({ detail: state }: CustomEvent) => {
|
const onPingPongChange = ({ detail: state }: CustomEvent) => {
|
||||||
setPing(state)
|
setPingRaw(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onConnectionStateChange = ({
|
const onConnectionStateChange = ({
|
||||||
@ -231,6 +270,6 @@ export function useNetworkStatus() {
|
|||||||
error,
|
error,
|
||||||
setHasCopied,
|
setHasCopied,
|
||||||
hasCopied,
|
hasCopied,
|
||||||
ping,
|
ping: pingEMA !== undefined ? Math.trunc(pingEMA) : pingEMA,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user