diff options
author | dan <dan.abramov@gmail.com> | 2024-11-24 19:10:54 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-24 19:10:54 +0000 |
commit | b9406aa011db2db7d3bcdf7d91ef929b17a07c02 (patch) | |
tree | fe590626f573c2e78e4d9f7df73f33c523e62172 /src | |
parent | 371e6ad333e00cdfb7f4713f7fef816668011049 (diff) | |
download | voidsky-b9406aa011db2db7d3bcdf7d91ef929b17a07c02.tar.zst |
Don't re-sort already fetched post thread items (#6698)
* Don't reorder already seen posts in PostThread * Add sorting by generation * Rip out stable order cache It doesn't make sense because sort() doesn't call the callback for all A/B pairs, and the server returning a different ordering will cause cache misses which means there'll be no stability anyway. * Make hotness deterministic per fetched at * Cache random scores while in thread * Reorder for clarity
Diffstat (limited to 'src')
-rw-r--r-- | src/state/queries/post-thread.ts | 52 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThread.tsx | 9 |
2 files changed, 52 insertions, 9 deletions
diff --git a/src/state/queries/post-thread.ts b/src/state/queries/post-thread.ts index 4784a9d75..2ebaf4f7d 100644 --- a/src/state/queries/post-thread.ts +++ b/src/state/queries/post-thread.ts @@ -150,6 +150,9 @@ export function sortThread( currentDid: string | undefined, justPostedUris: Set<string>, threadgateRecordHiddenReplies: Set<string>, + fetchedAtCache: Map<string, number>, + fetchedAt: number, + randomCache: Map<string, number>, ): ThreadNode { if (node.type !== 'post') { return node @@ -237,9 +240,23 @@ export function sortThread( } } - if (opts.sort === 'hotness') { - const aHotness = getHotness(a.post) - const bHotness = getHotness(b.post) + // Split items from different fetches into separate generations. + let aFetchedAt = fetchedAtCache.get(a.uri) + if (aFetchedAt === undefined) { + fetchedAtCache.set(a.uri, fetchedAt) + aFetchedAt = fetchedAt + } + let bFetchedAt = fetchedAtCache.get(b.uri) + if (bFetchedAt === undefined) { + fetchedAtCache.set(b.uri, fetchedAt) + bFetchedAt = fetchedAt + } + + if (aFetchedAt !== bFetchedAt) { + return aFetchedAt - bFetchedAt // older fetches first + } else if (opts.sort === 'hotness') { + const aHotness = getHotness(a.post, aFetchedAt) + const bHotness = getHotness(b.post, bFetchedAt /* same as aFetchedAt */) return bHotness - aHotness } else if (opts.sort === 'oldest') { return a.post.indexedAt.localeCompare(b.post.indexedAt) @@ -252,9 +269,21 @@ export function sortThread( return (b.post.likeCount || 0) - (a.post.likeCount || 0) // most likes } } else if (opts.sort === 'random') { - return 0.5 - Math.random() // this is vaguely criminal but we can get away with it + let aRandomScore = randomCache.get(a.uri) + if (aRandomScore === undefined) { + aRandomScore = Math.random() + randomCache.set(a.uri, aRandomScore) + } + let bRandomScore = randomCache.get(b.uri) + if (bRandomScore === undefined) { + bRandomScore = Math.random() + randomCache.set(b.uri, bRandomScore) + } + // this is vaguely criminal but we can get away with it + return aRandomScore - bRandomScore + } else { + return b.post.indexedAt.localeCompare(a.post.indexedAt) } - return b.post.indexedAt.localeCompare(a.post.indexedAt) }) node.replies.forEach(reply => sortThread( @@ -264,6 +293,9 @@ export function sortThread( currentDid, justPostedUris, threadgateRecordHiddenReplies, + fetchedAtCache, + fetchedAt, + randomCache, ), ) } @@ -277,10 +309,12 @@ export function sortThread( // We want to give recent comments a real chance (and not bury them deep below the fold) // while also surfacing well-liked comments from the past. In the future, we can explore // something more sophisticated, but we don't have much data on the client right now. -function getHotness(post: AppBskyFeedDefs.PostView) { - const hoursAgo = - (new Date().getTime() - new Date(post.indexedAt).getTime()) / - (1000 * 60 * 60) +function getHotness(post: AppBskyFeedDefs.PostView, fetchedAt: number) { + const hoursAgo = Math.max( + 0, + (new Date(fetchedAt).getTime() - new Date(post.indexedAt).getTime()) / + (1000 * 60 * 60), + ) const likeCount = post.likeCount ?? 0 const likeOrder = Math.log(3 + likeCount) const timePenaltyExponent = 1.5 + 1.5 / (1 + Math.log(1 + likeCount)) diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index 7ce6deaa1..315800fe5 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -104,6 +104,7 @@ export function PostThread({uri}: {uri: string | undefined}) { error: threadError, refetch, data: {thread, threadgate} = {}, + dataUpdatedAt: fetchedAt, } = usePostThreadQuery(uri) const treeView = React.useMemo( @@ -171,6 +172,8 @@ export function PostThread({uri}: {uri: string | undefined}) { () => new Set<string>(), ) + const [fetchedAtCache] = React.useState(() => new Map<string, number>()) + const [randomCache] = React.useState(() => new Map<string, number>()) const skeleton = React.useMemo(() => { const threadViewPrefs = preferences?.threadViewPrefs if (!threadViewPrefs || !thread) return null @@ -183,6 +186,9 @@ export function PostThread({uri}: {uri: string | undefined}) { currentDid, justPostedUris, threadgateHiddenReplies, + fetchedAtCache, + fetchedAt, + randomCache, ), currentDid, treeView, @@ -199,6 +205,9 @@ export function PostThread({uri}: {uri: string | undefined}) { hiddenRepliesState, justPostedUris, threadgateHiddenReplies, + fetchedAtCache, + fetchedAt, + randomCache, ]) const error = React.useMemo(() => { |