From 41df88e625b3be99f057cf4f35c647f9b3890d66 Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 7 Jan 2023 20:26:23 +0800 Subject: [PATCH] Perf fixes Turns out, adding an object to states.statuses proxyMap object, re-render ALL statuses --- src/app.jsx | 43 +++++++++++---------------- src/components/status.jsx | 59 +++++++++++++++++++------------------ src/pages/home.jsx | 6 ++-- src/pages/notifications.jsx | 7 +++-- src/pages/status.jsx | 20 ++++++------- src/utils/states.js | 9 +++--- 6 files changed, 71 insertions(+), 73 deletions(-) diff --git a/src/app.jsx b/src/app.jsx index 903f8d3c..8b73c6c3 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -302,23 +302,23 @@ async function startStream() { }); } - states.statuses.set(status.id, status); + states.statuses[status.id] = status; if (status.reblog) { - states.statuses.set(status.reblog.id, status.reblog); + states.statuses[status.reblog.id] = status.reblog; } }, 5000); stream.on('update', handleNewStatus); stream.on('status.update', (status) => { console.log('STATUS.UPDATE', status); - states.statuses.set(status.id, status); + states.statuses[status.id] = status; if (status.reblog) { - states.statuses.set(status.reblog.id, status.reblog); + states.statuses[status.reblog.id] = status.reblog; } }); stream.on('delete', (statusID) => { console.log('DELETE', statusID); - // states.statuses.delete(statusID); - const s = states.statuses.get(statusID); + // delete states.statuses[statusID]; + const s = states.statuses[statusID]; if (s) s._deleted = true; }); stream.on('notification', (notification) => { @@ -334,16 +334,14 @@ async function startStream() { states.notificationsNew.unshift(notification); } - if (notification.status && !states.statuses.has(notification.status.id)) { - states.statuses.set(notification.status.id, notification.status); + if (notification.status && !states.statuses[notification.status.id]) { + states.statuses[notification.status.id] = notification.status; if ( notification.status.reblog && - !states.statuses.has(notification.status.reblog.id) + !states.statuses[notification.status.reblog.id] ) { - states.statuses.set( - notification.status.reblog.id, - notification.status.reblog, - ); + states.statuses[notification.status.reblog.id] = + notification.status.reblog; } } }); @@ -397,9 +395,9 @@ function startVisibility() { newStatuses[0].id !== states.home[0].id ) { states.homeNew = newStatuses.map((status) => { - states.statuses.set(status.id, status); + states.statuses[status.id] = status; if (status.reblog) { - states.statuses.set(status.reblog.id, status.reblog); + states.statuses[status.reblog.id] = status.reblog; } return { id: status.id, @@ -424,20 +422,15 @@ function startVisibility() { if ( notification.status && - !states.statuses.has(notification.status.id) + !states.statuses[notification.status.id] ) { - states.statuses.set( - notification.status.id, - notification.status, - ); + states.statuses[notification.status.id] = notification.status; if ( notification.status.reblog && - !states.statuses.has(notification.status.reblog.id) + !states.statuses[notification.status.reblog.id] ) { - states.statuses.set( - notification.status.reblog.id, - notification.status.reblog, - ); + states.statuses[notification.status.reblog.id] = + notification.status.reblog; } } } diff --git a/src/components/status.jsx b/src/components/status.jsx index 3cabe4fb..e21347eb 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -2,6 +2,7 @@ import './status.css'; import { getBlurHashAverageColor } from 'fast-blurhash'; import mem from 'mem'; +import { memo } from 'preact/compat'; import { useEffect, useLayoutEffect, @@ -61,7 +62,7 @@ function Status({ const snapStates = useSnapshot(states); if (!status) { - status = snapStates.statuses.get(statusID); + status = snapStates.statuses[statusID]; } if (!status) { return null; @@ -106,6 +107,8 @@ function Status({ _deleted, } = status; + console.debug('RENDER Status', id, status?.account.displayName); + const createdAtDate = new Date(createdAt); const editedAtDate = new Date(editedAt); @@ -122,20 +125,20 @@ function Status({ } const [inReplyToAccount, setInReplyToAccount] = useState(inReplyToAccountRef); if (!withinContext && !inReplyToAccount && inReplyToAccountId) { - const account = states.accounts.get(inReplyToAccountId); + const account = states.accounts[inReplyToAccountId]; if (account) { setInReplyToAccount(account); } else { memFetchAccount(inReplyToAccountId) .then((account) => { setInReplyToAccount(account); - states.accounts.set(account.id, account); + states.accounts[account.id] = account; }) .catch((e) => {}); } } - const showSpoiler = snapStates.spoilers.has(id) || false; + const showSpoiler = !!snapStates.spoilers[id] || false; const debugHover = (e) => { if (e.shiftKey) { @@ -321,9 +324,9 @@ function Status({ e.preventDefault(); e.stopPropagation(); if (showSpoiler) { - states.spoilers.delete(id); + delete states.spoilers[id]; } else { - states.spoilers.set(id, true); + states.spoilers[id] = true; } }} > @@ -388,7 +391,7 @@ function Status({ poll={poll} readOnly={readOnly} onUpdate={(newPoll) => { - states.statuses.get(id).poll = newPoll; + states.statuses[id].poll = newPoll; }} /> )} @@ -400,9 +403,9 @@ function Status({ e.preventDefault(); e.stopPropagation(); if (showSpoiler) { - states.spoilers.delete(id); + delete states.spoilers[id]; } else { - states.spoilers.set(id, true); + states.spoilers[id] = true; } }} > @@ -519,28 +522,26 @@ function Status({ } } // Optimistic - states.statuses.set(id, { + states.statuses[id] = { ...status, reblogged: !reblogged, reblogsCount: reblogsCount + (reblogged ? -1 : 1), - }); + }; if (reblogged) { const newStatus = await masto.v1.statuses.unreblog( id, ); - states.statuses.set(newStatus.id, newStatus); + states.statuses[newStatus.id] = newStatus; } else { const newStatus = await masto.v1.statuses.reblog(id); - states.statuses.set(newStatus.id, newStatus); - states.statuses.set( - newStatus.reblog.id, - newStatus.reblog, - ); + states.statuses[newStatus.id] = newStatus; + states.statuses[newStatus.reblog.id] = + newStatus.reblog; } } catch (e) { console.error(e); // Revert optimistism - states.statuses.set(id, status); + states.statuses[id] = status; } }} /> @@ -557,25 +558,25 @@ function Status({ onClick={async () => { try { // Optimistic - states.statuses.set(statusID, { + states.statuses[statusID] = { ...status, favourited: !favourited, favouritesCount: favouritesCount + (favourited ? -1 : 1), - }); + }; if (favourited) { const newStatus = await masto.v1.statuses.unfavourite( id, ); - states.statuses.set(newStatus.id, newStatus); + states.statuses[newStatus.id] = newStatus; } else { const newStatus = await masto.v1.statuses.favourite(id); - states.statuses.set(newStatus.id, newStatus); + states.statuses[newStatus.id] = newStatus; } } catch (e) { console.error(e); // Revert optimistism - states.statuses.set(statusID, status); + states.statuses[statusID] = status; } }} /> @@ -590,23 +591,23 @@ function Status({ onClick={async () => { try { // Optimistic - states.statuses.set(statusID, { + states.statuses[statusID] = { ...status, bookmarked: !bookmarked, - }); + }; if (bookmarked) { const newStatus = await masto.v1.statuses.unbookmark( id, ); - states.statuses.set(newStatus.id, newStatus); + states.statuses[newStatus.id] = newStatus; } else { const newStatus = await masto.v1.statuses.bookmark(id); - states.statuses.set(newStatus.id, newStatus); + states.statuses[newStatus.id] = newStatus; } } catch (e) { console.error(e); // Revert optimistism - states.statuses.set(statusID, status); + states.statuses[statusID] = status; } }} /> @@ -1437,4 +1438,4 @@ function formatDuration(time) { } } -export default Status; +export default memo(Status); diff --git a/src/pages/home.jsx b/src/pages/home.jsx index a4d94985..e198dfc2 100644 --- a/src/pages/home.jsx +++ b/src/pages/home.jsx @@ -18,6 +18,8 @@ function Home({ hidden }) { const [uiState, setUIState] = useState('default'); const [showMore, setShowMore] = useState(false); + console.debug('RENDER Home'); + const homeIterator = useRef( masto.v1.timelines.listHome({ limit: LIMIT, @@ -36,9 +38,9 @@ function Home({ hidden }) { return { done: true }; } const homeValues = allStatuses.value.map((status) => { - states.statuses.set(status.id, status); + states.statuses[status.id] = status; if (status.reblog) { - states.statuses.set(status.reblog.id, status.reblog); + states.statuses[status.reblog.id] = status.reblog; } return { id: status.id, diff --git a/src/pages/notifications.jsx b/src/pages/notifications.jsx index cf425c83..d99bdde0 100644 --- a/src/pages/notifications.jsx +++ b/src/pages/notifications.jsx @@ -1,6 +1,7 @@ import './notifications.css'; import { Link } from 'preact-router/match'; +import { memo } from 'preact/compat'; import { useEffect, useRef, useState } from 'preact/hooks'; import { useSnapshot } from 'valtio'; @@ -205,6 +206,8 @@ function Notifications() { const [showMore, setShowMore] = useState(false); const [onlyMentions, setOnlyMentions] = useState(false); + console.debug('RENDER Notifications'); + const notificationsIterator = useRef( masto.v1.notifications.list({ limit: LIMIT, @@ -224,7 +227,7 @@ function Notifications() { } const notificationsValues = allNotifications.value.map((notification) => { if (notification.status) { - states.statuses.set(notification.status.id, notification.status); + states.statuses[notification.status.id] = notification.status; } return notification; }); @@ -411,4 +414,4 @@ function Notifications() { ); } -export default Notifications; +export default memo(Notifications); diff --git a/src/pages/status.jsx b/src/pages/status.jsx index d6d065ed..ea1ab474 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -44,7 +44,7 @@ function StatusPage({ id }) { // console.log('onScroll'); if (!scrollableRef.current) return; const { scrollTop } = scrollableRef.current; - states.scrollPositions.set(id, scrollTop); + states.scrollPositions[id] = scrollTop; }, 100); scrollableRef.current.addEventListener('scroll', onScroll, { passive: true, @@ -63,7 +63,7 @@ function StatusPage({ id }) { if (cachedStatuses) { // Case 1: It's cached, let's restore them to make it snappy const reallyCachedStatuses = cachedStatuses.filter( - (s) => states.statuses.has(s.id), + (s) => states.statuses[s.id], // Some are not cached in the global state, so we need to filter them out ); setStatuses(reallyCachedStatuses); @@ -83,15 +83,15 @@ function StatusPage({ id }) { const heroFetch = () => masto.v1.statuses.fetch(id); const contextFetch = masto.v1.statuses.fetchContext(id); - const hasStatus = snapStates.statuses.has(id); - let heroStatus = snapStates.statuses.get(id); + const hasStatus = !!snapStates.statuses[id]; + let heroStatus = snapStates.statuses[id]; if (hasStatus) { console.log('Hero status is cached'); // NOTE: This might conflict if the user interacts with the status before the fetch is done, e.g. favouriting it // heroTimer = setTimeout(async () => { // try { // heroStatus = await heroFetch(); - // states.statuses.set(id, heroStatus); + // states.statuses[id] = heroStatus; // } catch (e) { // // Silent fail if status is cached // console.error(e); @@ -100,7 +100,7 @@ function StatusPage({ id }) { } else { try { heroStatus = await heroFetch(); - states.statuses.set(id, heroStatus); + states.statuses[id] = heroStatus; } catch (e) { console.error(e); setUIState('error'); @@ -113,11 +113,11 @@ function StatusPage({ id }) { const { ancestors, descendants } = context; ancestors.forEach((status) => { - states.statuses.set(status.id, status); + states.statuses[status.id] = status; }); const nestedDescendants = []; descendants.forEach((status) => { - states.statuses.set(status.id, status); + states.statuses[status.id] = status; if (status.inReplyToAccountId === status.account.id) { // If replying to self, it's part of the thread, level 1 nestedDescendants.push(status); @@ -225,7 +225,7 @@ function StatusPage({ id }) { } } } else { - const scrollPosition = states.scrollPositions.get(id); + const scrollPosition = states.scrollPositions[id]; if (scrollPosition && scrollableRef.current) { // Case 3: Not user initiated (e.g. back/forward button), restore to saved scroll position console.log('Case 3'); @@ -247,7 +247,7 @@ function StatusPage({ id }) { } }, [statuses, uiState]); - const heroStatus = snapStates.statuses.get(id); + const heroStatus = snapStates.statuses[id]; const heroDisplayName = useMemo(() => { // Remove shortcodes from display name if (!heroStatus) return ''; diff --git a/src/utils/states.js b/src/utils/states.js index de3f3146..c65344bb 100644 --- a/src/utils/states.js +++ b/src/utils/states.js @@ -1,19 +1,18 @@ import { proxy } from 'valtio'; -import { proxyMap } from 'valtio/utils'; export default proxy({ history: [], - statuses: proxyMap([]), + statuses: {}, home: [], homeNew: [], homeLastFetchTime: null, notifications: [], notificationsNew: [], notificationsLastFetchTime: null, - accounts: new Map(), + accounts: {}, reloadStatusPage: 0, - spoilers: proxyMap([]), - scrollPositions: new Map(), + spoilers: {}, + scrollPositions: {}, // Modals showCompose: false, showSettings: false,