Perf fixes

Turns out, adding an object to states.statuses proxyMap object, re-render ALL statuses
This commit is contained in:
Lim Chee Aun 2023-01-07 20:26:23 +08:00
parent 862107f2e6
commit 41df88e625
6 changed files with 71 additions and 73 deletions

View file

@ -302,23 +302,23 @@ async function startStream() {
}); });
} }
states.statuses.set(status.id, status); states.statuses[status.id] = status;
if (status.reblog) { if (status.reblog) {
states.statuses.set(status.reblog.id, status.reblog); states.statuses[status.reblog.id] = status.reblog;
} }
}, 5000); }, 5000);
stream.on('update', handleNewStatus); stream.on('update', handleNewStatus);
stream.on('status.update', (status) => { stream.on('status.update', (status) => {
console.log('STATUS.UPDATE', status); console.log('STATUS.UPDATE', status);
states.statuses.set(status.id, status); states.statuses[status.id] = status;
if (status.reblog) { if (status.reblog) {
states.statuses.set(status.reblog.id, status.reblog); states.statuses[status.reblog.id] = status.reblog;
} }
}); });
stream.on('delete', (statusID) => { stream.on('delete', (statusID) => {
console.log('DELETE', statusID); console.log('DELETE', statusID);
// states.statuses.delete(statusID); // delete states.statuses[statusID];
const s = states.statuses.get(statusID); const s = states.statuses[statusID];
if (s) s._deleted = true; if (s) s._deleted = true;
}); });
stream.on('notification', (notification) => { stream.on('notification', (notification) => {
@ -334,16 +334,14 @@ async function startStream() {
states.notificationsNew.unshift(notification); states.notificationsNew.unshift(notification);
} }
if (notification.status && !states.statuses.has(notification.status.id)) { if (notification.status && !states.statuses[notification.status.id]) {
states.statuses.set(notification.status.id, notification.status); states.statuses[notification.status.id] = notification.status;
if ( if (
notification.status.reblog && notification.status.reblog &&
!states.statuses.has(notification.status.reblog.id) !states.statuses[notification.status.reblog.id]
) { ) {
states.statuses.set( states.statuses[notification.status.reblog.id] =
notification.status.reblog.id, notification.status.reblog;
notification.status.reblog,
);
} }
} }
}); });
@ -397,9 +395,9 @@ function startVisibility() {
newStatuses[0].id !== states.home[0].id newStatuses[0].id !== states.home[0].id
) { ) {
states.homeNew = newStatuses.map((status) => { states.homeNew = newStatuses.map((status) => {
states.statuses.set(status.id, status); states.statuses[status.id] = status;
if (status.reblog) { if (status.reblog) {
states.statuses.set(status.reblog.id, status.reblog); states.statuses[status.reblog.id] = status.reblog;
} }
return { return {
id: status.id, id: status.id,
@ -424,20 +422,15 @@ function startVisibility() {
if ( if (
notification.status && notification.status &&
!states.statuses.has(notification.status.id) !states.statuses[notification.status.id]
) { ) {
states.statuses.set( states.statuses[notification.status.id] = notification.status;
notification.status.id,
notification.status,
);
if ( if (
notification.status.reblog && notification.status.reblog &&
!states.statuses.has(notification.status.reblog.id) !states.statuses[notification.status.reblog.id]
) { ) {
states.statuses.set( states.statuses[notification.status.reblog.id] =
notification.status.reblog.id, notification.status.reblog;
notification.status.reblog,
);
} }
} }
} }

View file

@ -2,6 +2,7 @@ import './status.css';
import { getBlurHashAverageColor } from 'fast-blurhash'; import { getBlurHashAverageColor } from 'fast-blurhash';
import mem from 'mem'; import mem from 'mem';
import { memo } from 'preact/compat';
import { import {
useEffect, useEffect,
useLayoutEffect, useLayoutEffect,
@ -61,7 +62,7 @@ function Status({
const snapStates = useSnapshot(states); const snapStates = useSnapshot(states);
if (!status) { if (!status) {
status = snapStates.statuses.get(statusID); status = snapStates.statuses[statusID];
} }
if (!status) { if (!status) {
return null; return null;
@ -106,6 +107,8 @@ function Status({
_deleted, _deleted,
} = status; } = status;
console.debug('RENDER Status', id, status?.account.displayName);
const createdAtDate = new Date(createdAt); const createdAtDate = new Date(createdAt);
const editedAtDate = new Date(editedAt); const editedAtDate = new Date(editedAt);
@ -122,20 +125,20 @@ function Status({
} }
const [inReplyToAccount, setInReplyToAccount] = useState(inReplyToAccountRef); const [inReplyToAccount, setInReplyToAccount] = useState(inReplyToAccountRef);
if (!withinContext && !inReplyToAccount && inReplyToAccountId) { if (!withinContext && !inReplyToAccount && inReplyToAccountId) {
const account = states.accounts.get(inReplyToAccountId); const account = states.accounts[inReplyToAccountId];
if (account) { if (account) {
setInReplyToAccount(account); setInReplyToAccount(account);
} else { } else {
memFetchAccount(inReplyToAccountId) memFetchAccount(inReplyToAccountId)
.then((account) => { .then((account) => {
setInReplyToAccount(account); setInReplyToAccount(account);
states.accounts.set(account.id, account); states.accounts[account.id] = account;
}) })
.catch((e) => {}); .catch((e) => {});
} }
} }
const showSpoiler = snapStates.spoilers.has(id) || false; const showSpoiler = !!snapStates.spoilers[id] || false;
const debugHover = (e) => { const debugHover = (e) => {
if (e.shiftKey) { if (e.shiftKey) {
@ -321,9 +324,9 @@ function Status({
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (showSpoiler) { if (showSpoiler) {
states.spoilers.delete(id); delete states.spoilers[id];
} else { } else {
states.spoilers.set(id, true); states.spoilers[id] = true;
} }
}} }}
> >
@ -388,7 +391,7 @@ function Status({
poll={poll} poll={poll}
readOnly={readOnly} readOnly={readOnly}
onUpdate={(newPoll) => { onUpdate={(newPoll) => {
states.statuses.get(id).poll = newPoll; states.statuses[id].poll = newPoll;
}} }}
/> />
)} )}
@ -400,9 +403,9 @@ function Status({
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (showSpoiler) { if (showSpoiler) {
states.spoilers.delete(id); delete states.spoilers[id];
} else { } else {
states.spoilers.set(id, true); states.spoilers[id] = true;
} }
}} }}
> >
@ -519,28 +522,26 @@ function Status({
} }
} }
// Optimistic // Optimistic
states.statuses.set(id, { states.statuses[id] = {
...status, ...status,
reblogged: !reblogged, reblogged: !reblogged,
reblogsCount: reblogsCount + (reblogged ? -1 : 1), reblogsCount: reblogsCount + (reblogged ? -1 : 1),
}); };
if (reblogged) { if (reblogged) {
const newStatus = await masto.v1.statuses.unreblog( const newStatus = await masto.v1.statuses.unreblog(
id, id,
); );
states.statuses.set(newStatus.id, newStatus); states.statuses[newStatus.id] = newStatus;
} else { } else {
const newStatus = await masto.v1.statuses.reblog(id); const newStatus = await masto.v1.statuses.reblog(id);
states.statuses.set(newStatus.id, newStatus); states.statuses[newStatus.id] = newStatus;
states.statuses.set( states.statuses[newStatus.reblog.id] =
newStatus.reblog.id, newStatus.reblog;
newStatus.reblog,
);
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
// Revert optimistism // Revert optimistism
states.statuses.set(id, status); states.statuses[id] = status;
} }
}} }}
/> />
@ -557,25 +558,25 @@ function Status({
onClick={async () => { onClick={async () => {
try { try {
// Optimistic // Optimistic
states.statuses.set(statusID, { states.statuses[statusID] = {
...status, ...status,
favourited: !favourited, favourited: !favourited,
favouritesCount: favouritesCount:
favouritesCount + (favourited ? -1 : 1), favouritesCount + (favourited ? -1 : 1),
}); };
if (favourited) { if (favourited) {
const newStatus = await masto.v1.statuses.unfavourite( const newStatus = await masto.v1.statuses.unfavourite(
id, id,
); );
states.statuses.set(newStatus.id, newStatus); states.statuses[newStatus.id] = newStatus;
} else { } else {
const newStatus = await masto.v1.statuses.favourite(id); const newStatus = await masto.v1.statuses.favourite(id);
states.statuses.set(newStatus.id, newStatus); states.statuses[newStatus.id] = newStatus;
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
// Revert optimistism // Revert optimistism
states.statuses.set(statusID, status); states.statuses[statusID] = status;
} }
}} }}
/> />
@ -590,23 +591,23 @@ function Status({
onClick={async () => { onClick={async () => {
try { try {
// Optimistic // Optimistic
states.statuses.set(statusID, { states.statuses[statusID] = {
...status, ...status,
bookmarked: !bookmarked, bookmarked: !bookmarked,
}); };
if (bookmarked) { if (bookmarked) {
const newStatus = await masto.v1.statuses.unbookmark( const newStatus = await masto.v1.statuses.unbookmark(
id, id,
); );
states.statuses.set(newStatus.id, newStatus); states.statuses[newStatus.id] = newStatus;
} else { } else {
const newStatus = await masto.v1.statuses.bookmark(id); const newStatus = await masto.v1.statuses.bookmark(id);
states.statuses.set(newStatus.id, newStatus); states.statuses[newStatus.id] = newStatus;
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
// Revert optimistism // 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);

View file

@ -18,6 +18,8 @@ function Home({ hidden }) {
const [uiState, setUIState] = useState('default'); const [uiState, setUIState] = useState('default');
const [showMore, setShowMore] = useState(false); const [showMore, setShowMore] = useState(false);
console.debug('RENDER Home');
const homeIterator = useRef( const homeIterator = useRef(
masto.v1.timelines.listHome({ masto.v1.timelines.listHome({
limit: LIMIT, limit: LIMIT,
@ -36,9 +38,9 @@ function Home({ hidden }) {
return { done: true }; return { done: true };
} }
const homeValues = allStatuses.value.map((status) => { const homeValues = allStatuses.value.map((status) => {
states.statuses.set(status.id, status); states.statuses[status.id] = status;
if (status.reblog) { if (status.reblog) {
states.statuses.set(status.reblog.id, status.reblog); states.statuses[status.reblog.id] = status.reblog;
} }
return { return {
id: status.id, id: status.id,

View file

@ -1,6 +1,7 @@
import './notifications.css'; import './notifications.css';
import { Link } from 'preact-router/match'; import { Link } from 'preact-router/match';
import { memo } from 'preact/compat';
import { useEffect, useRef, useState } from 'preact/hooks'; import { useEffect, useRef, useState } from 'preact/hooks';
import { useSnapshot } from 'valtio'; import { useSnapshot } from 'valtio';
@ -205,6 +206,8 @@ function Notifications() {
const [showMore, setShowMore] = useState(false); const [showMore, setShowMore] = useState(false);
const [onlyMentions, setOnlyMentions] = useState(false); const [onlyMentions, setOnlyMentions] = useState(false);
console.debug('RENDER Notifications');
const notificationsIterator = useRef( const notificationsIterator = useRef(
masto.v1.notifications.list({ masto.v1.notifications.list({
limit: LIMIT, limit: LIMIT,
@ -224,7 +227,7 @@ function Notifications() {
} }
const notificationsValues = allNotifications.value.map((notification) => { const notificationsValues = allNotifications.value.map((notification) => {
if (notification.status) { if (notification.status) {
states.statuses.set(notification.status.id, notification.status); states.statuses[notification.status.id] = notification.status;
} }
return notification; return notification;
}); });
@ -411,4 +414,4 @@ function Notifications() {
); );
} }
export default Notifications; export default memo(Notifications);

View file

@ -44,7 +44,7 @@ function StatusPage({ id }) {
// console.log('onScroll'); // console.log('onScroll');
if (!scrollableRef.current) return; if (!scrollableRef.current) return;
const { scrollTop } = scrollableRef.current; const { scrollTop } = scrollableRef.current;
states.scrollPositions.set(id, scrollTop); states.scrollPositions[id] = scrollTop;
}, 100); }, 100);
scrollableRef.current.addEventListener('scroll', onScroll, { scrollableRef.current.addEventListener('scroll', onScroll, {
passive: true, passive: true,
@ -63,7 +63,7 @@ function StatusPage({ id }) {
if (cachedStatuses) { if (cachedStatuses) {
// Case 1: It's cached, let's restore them to make it snappy // Case 1: It's cached, let's restore them to make it snappy
const reallyCachedStatuses = cachedStatuses.filter( 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 // Some are not cached in the global state, so we need to filter them out
); );
setStatuses(reallyCachedStatuses); setStatuses(reallyCachedStatuses);
@ -83,15 +83,15 @@ function StatusPage({ id }) {
const heroFetch = () => masto.v1.statuses.fetch(id); const heroFetch = () => masto.v1.statuses.fetch(id);
const contextFetch = masto.v1.statuses.fetchContext(id); const contextFetch = masto.v1.statuses.fetchContext(id);
const hasStatus = snapStates.statuses.has(id); const hasStatus = !!snapStates.statuses[id];
let heroStatus = snapStates.statuses.get(id); let heroStatus = snapStates.statuses[id];
if (hasStatus) { if (hasStatus) {
console.log('Hero status is cached'); 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 // NOTE: This might conflict if the user interacts with the status before the fetch is done, e.g. favouriting it
// heroTimer = setTimeout(async () => { // heroTimer = setTimeout(async () => {
// try { // try {
// heroStatus = await heroFetch(); // heroStatus = await heroFetch();
// states.statuses.set(id, heroStatus); // states.statuses[id] = heroStatus;
// } catch (e) { // } catch (e) {
// // Silent fail if status is cached // // Silent fail if status is cached
// console.error(e); // console.error(e);
@ -100,7 +100,7 @@ function StatusPage({ id }) {
} else { } else {
try { try {
heroStatus = await heroFetch(); heroStatus = await heroFetch();
states.statuses.set(id, heroStatus); states.statuses[id] = heroStatus;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
setUIState('error'); setUIState('error');
@ -113,11 +113,11 @@ function StatusPage({ id }) {
const { ancestors, descendants } = context; const { ancestors, descendants } = context;
ancestors.forEach((status) => { ancestors.forEach((status) => {
states.statuses.set(status.id, status); states.statuses[status.id] = status;
}); });
const nestedDescendants = []; const nestedDescendants = [];
descendants.forEach((status) => { descendants.forEach((status) => {
states.statuses.set(status.id, status); states.statuses[status.id] = status;
if (status.inReplyToAccountId === status.account.id) { if (status.inReplyToAccountId === status.account.id) {
// If replying to self, it's part of the thread, level 1 // If replying to self, it's part of the thread, level 1
nestedDescendants.push(status); nestedDescendants.push(status);
@ -225,7 +225,7 @@ function StatusPage({ id }) {
} }
} }
} else { } else {
const scrollPosition = states.scrollPositions.get(id); const scrollPosition = states.scrollPositions[id];
if (scrollPosition && scrollableRef.current) { if (scrollPosition && scrollableRef.current) {
// Case 3: Not user initiated (e.g. back/forward button), restore to saved scroll position // Case 3: Not user initiated (e.g. back/forward button), restore to saved scroll position
console.log('Case 3'); console.log('Case 3');
@ -247,7 +247,7 @@ function StatusPage({ id }) {
} }
}, [statuses, uiState]); }, [statuses, uiState]);
const heroStatus = snapStates.statuses.get(id); const heroStatus = snapStates.statuses[id];
const heroDisplayName = useMemo(() => { const heroDisplayName = useMemo(() => {
// Remove shortcodes from display name // Remove shortcodes from display name
if (!heroStatus) return ''; if (!heroStatus) return '';

View file

@ -1,19 +1,18 @@
import { proxy } from 'valtio'; import { proxy } from 'valtio';
import { proxyMap } from 'valtio/utils';
export default proxy({ export default proxy({
history: [], history: [],
statuses: proxyMap([]), statuses: {},
home: [], home: [],
homeNew: [], homeNew: [],
homeLastFetchTime: null, homeLastFetchTime: null,
notifications: [], notifications: [],
notificationsNew: [], notificationsNew: [],
notificationsLastFetchTime: null, notificationsLastFetchTime: null,
accounts: new Map(), accounts: {},
reloadStatusPage: 0, reloadStatusPage: 0,
spoilers: proxyMap([]), spoilers: {},
scrollPositions: new Map(), scrollPositions: {},
// Modals // Modals
showCompose: false, showCompose: false,
showSettings: false, showSettings: false,