diff options
author | dan <dan.abramov@gmail.com> | 2023-12-08 21:57:00 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-08 13:57:00 -0800 |
commit | 7b686b5592a8cd01278c5c30e115385ffd676248 (patch) | |
tree | f8572822edf8c31586761ecf897f710f8e7c032a /src | |
parent | 61fa3d506cc8a9bf5154d9a2b1038023748af518 (diff) | |
download | voidsky-7b686b5592a8cd01278c5c30e115385ffd676248.tar.zst |
Add manual per-page memoization to post select (#2146)
Diffstat (limited to 'src')
-rw-r--r-- | src/state/queries/post-feed.ts | 177 |
1 files changed, 120 insertions, 57 deletions
diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 9bd1dacb3..14ffeb0da 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -1,4 +1,4 @@ -import {useCallback, useEffect} from 'react' +import React, {useCallback, useEffect} from 'react' import { AppBskyFeedDefs, AppBskyFeedPost, @@ -97,6 +97,22 @@ export function usePostFeedQuery( const feedTuners = useFeedTuners(feedDesc) const moderationOpts = useModerationOpts() const enabled = opts?.enabled !== false && Boolean(moderationOpts) + const lastRun = React.useRef<{ + data: InfiniteData<FeedPageUnselected> + args: typeof selectArgs + result: InfiniteData<FeedPage> + } | null>(null) + + // Make sure this doesn't invalidate unless really needed. + const selectArgs = React.useMemo( + () => ({ + feedTuners, + disableTuner: params?.disableTuner, + moderationOpts, + ignoreFilterFor: opts?.ignoreFilterFor, + }), + [feedTuners, params?.disableTuner, moderationOpts, opts?.ignoreFilterFor], + ) const query = useInfiniteQuery< FeedPageUnselected, @@ -147,69 +163,116 @@ export function usePostFeedQuery( : undefined, select: useCallback( (data: InfiniteData<FeedPageUnselected, RQPageParam>) => { - const tuner = params?.disableTuner + // If the selection depends on some data, that data should + // be included in the selectArgs object and read here. + const {feedTuners, disableTuner, moderationOpts, ignoreFilterFor} = + selectArgs + + const tuner = disableTuner ? new NoopFeedTuner() : new FeedTuner(feedTuners) - return { + + // Keep track of the last run and whether we can reuse + // some already selected pages from there. + let reusedPages = [] + if (lastRun.current) { + const { + data: lastData, + args: lastArgs, + result: lastResult, + } = lastRun.current + let canReuse = true + for (let key in selectArgs) { + if (selectArgs.hasOwnProperty(key)) { + if ((selectArgs as any)[key] !== (lastArgs as any)[key]) { + // Can't do reuse anything if any input has changed. + canReuse = false + break + } + } + } + if (canReuse) { + for (let i = 0; i < data.pages.length; i++) { + if (data.pages[i] && lastData.pages[i] === data.pages[i]) { + reusedPages.push(lastResult.pages[i]) + // Keep the tuner in sync so that the end result is deterministic. + tuner.tune(lastData.pages[i].feed) + continue + } + // Stop as soon as pages stop matching up. + break + } + } + } + + const result = { pageParams: data.pageParams, - pages: data.pages.map(page => ({ - api: page.api, - tuner, - cursor: page.cursor, - slices: tuner - .tune(page.feed) - .map(slice => { - const moderations = slice.items.map(item => - moderatePost(item.post, moderationOpts!), - ) - - // apply moderation filter - for (let i = 0; i < slice.items.length; i++) { - if ( - moderations[i]?.content.filter && - slice.items[i].post.author.did !== opts?.ignoreFilterFor - ) { - return undefined + pages: [ + ...reusedPages, + ...data.pages.slice(reusedPages.length).map(page => ({ + api: page.api, + tuner, + cursor: page.cursor, + slices: tuner + .tune(page.feed) + .map(slice => { + const moderations = slice.items.map(item => + moderatePost(item.post, moderationOpts!), + ) + + // apply moderation filter + for (let i = 0; i < slice.items.length; i++) { + if ( + moderations[i]?.content.filter && + slice.items[i].post.author.did !== ignoreFilterFor + ) { + return undefined + } } - } - - return { - _reactKey: slice._reactKey, - rootUri: slice.rootItem.post.uri, - isThread: - slice.items.length > 1 && - slice.items.every( - item => - item.post.author.did === slice.items[0].post.author.did, - ), - items: slice.items - .map((item, i) => { - if ( - AppBskyFeedPost.isRecord(item.post.record) && - AppBskyFeedPost.validateRecord(item.post.record).success - ) { - return { - _reactKey: `${slice._reactKey}-${i}`, - uri: item.post.uri, - post: item.post, - record: item.post.record, - reason: - i === 0 && slice.source - ? slice.source - : item.reason, - moderation: moderations[i], + + return { + _reactKey: slice._reactKey, + rootUri: slice.rootItem.post.uri, + isThread: + slice.items.length > 1 && + slice.items.every( + item => + item.post.author.did === + slice.items[0].post.author.did, + ), + items: slice.items + .map((item, i) => { + if ( + AppBskyFeedPost.isRecord(item.post.record) && + AppBskyFeedPost.validateRecord(item.post.record) + .success + ) { + return { + _reactKey: `${slice._reactKey}-${i}`, + uri: item.post.uri, + post: item.post, + record: item.post.record, + reason: + i === 0 && slice.source + ? slice.source + : item.reason, + moderation: moderations[i], + } } - } - return undefined - }) - .filter(Boolean) as FeedPostSliceItem[], - } - }) - .filter(Boolean) as FeedPostSlice[], - })), + return undefined + }) + .filter(Boolean) as FeedPostSliceItem[], + } + }) + .filter(Boolean) as FeedPostSlice[], + })), + ], } + // Save for memoization. + lastRun.current = {data, result, args: selectArgs} + return result }, - [feedTuners, params?.disableTuner, moderationOpts, opts?.ignoreFilterFor], + [selectArgs /* Don't change. Everything needs to go into selectArgs. */], ), }) |