diff --git a/e2e/playwright/snapshot-tests.spec.ts b/e2e/playwright/snapshot-tests.spec.ts index f78e8a1c2..a8fc23eed 100644 --- a/e2e/playwright/snapshot-tests.spec.ts +++ b/e2e/playwright/snapshot-tests.spec.ts @@ -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() diff --git a/e2e/playwright/test-network-and-connection-issues.spec.ts b/e2e/playwright/test-network-and-connection-issues.spec.ts index 40fde6537..c05e764ef 100644 --- a/e2e/playwright/test-network-and-connection-issues.spec.ts +++ b/e2e/playwright/test-network-and-connection-issues.spec.ts @@ -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() diff --git a/src/components/NetworkHealthIndicator.tsx b/src/components/NetworkHealthIndicator.tsx index fa754ac05..ad6987a39 100644 --- a/src/components/NetworkHealthIndicator.tsx +++ b/src/components/NetworkHealthIndicator.tsx @@ -10,8 +10,8 @@ import { reportRejection } from '@src/lib/trap' import { toSync } from '@src/lib/utils' export const NETWORK_HEALTH_TEXT: Record = { - [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 = 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', diff --git a/src/hooks/useNetworkStatus.tsx b/src/hooks/useNetworkStatus.tsx index 581d3276e..ace10488c 100644 --- a/src/hooks/useNetworkStatus.tsx +++ b/src/hooks/useNetworkStatus.tsx @@ -48,7 +48,8 @@ export function useNetworkStatus() { const [overallState, setOverallState] = useState( NetworkHealthState.Disconnected ) - const [ping, setPing] = useState(undefined) + const [pingRaw, setPingRaw] = useState(undefined) + const [pingEMA, setPingEMA] = useState(undefined) const [hasCopied, setHasCopied] = useState(false) const [error, setError] = useState(undefined) @@ -66,16 +67,54 @@ export function useNetworkStatus() { const [hasIssues, setHasIssues] = useState(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, } }