The feed stays light at thousands of posts
A Perf test (tests/e2e/Perf/FeedPerfTests, Category=Perf) scrolls load0's home, thousands of posts from SocialPub's load runs, to 2000 posts. Before: 100 cards took 12 s to reach, 1000 took 437 s, 2000 took 29 minutes; the main thread was busy almost all of that time, and the page held 224 000 DOM nodes and 920 MB of .NET heap. Every card re-rendered whenever the list did. - A card re-renders only when its own events run or its render key changes (Content.RenderKey: the post, its status, notification, conversation, place and grouping, where it is shown, the persona and the connection). 2000 cards then took 122 s. - The list is a <Virtualize>: only the cards near the view are rendered. The page now holds about 3 000 DOM nodes and 145 MB however long the feed is, and each page of older posts costs the same (about 1.9 s in the test's scroll loop) at the 100th post as at the 1700th. The feed tells how many posts it holds in data-feed-count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
f9de55ffd5
commit
a5178a67b1
4 files changed
+138
-5
No files matched your search
@@ -1,4 +1,5 @@
|
||||
@inherits LocalizableComponentBase
|
||||
@implements IHandleEvent
|
||||
|
||||
@* One feed item: a post (a boost shows the original and its booster), a notification, or a conversation's last post.
|
||||
The card does its own Mastodon actions (favourite, boost, bookmark, reply, mute, block, delete) and tells the page
|
||||
@@ -404,6 +405,45 @@
|
||||
|
||||
protected override void OnInitialized() => IsLoading = false;
|
||||
|
||||
// A card re-renders when something it shows may have changed: its own actions and toggles (its events), a new copy of
|
||||
// its post (an edit, a grouped notification, a place found), where it is shown, or the persona and connection. Without
|
||||
// this every card re-rendered whenever its list did: a feed of a thousand cards took seven minutes to scroll to
|
||||
// (tests/e2e/Perf/FeedPerfTests).
|
||||
object renderedWith;
|
||||
int handlingEvents;
|
||||
|
||||
object RenderKey() => (Post, Post?.Status, Post?.Notification, Post?.Conversation, Post?.Location, Post?.GroupCount, Post?.GroupAccounts,
|
||||
(FilterIn, Highlighted, Compact, IncludeExpand, CssContainer), (CascadingState?.PersonaId, CascadingState?.IsOnline));
|
||||
|
||||
protected override bool ShouldRender()
|
||||
{
|
||||
var key = RenderKey();
|
||||
if (handlingEvents == 0 && Equals(key, renderedWith))
|
||||
return false;
|
||||
renderedWith = key;
|
||||
return true;
|
||||
}
|
||||
|
||||
// as ComponentBase handles an event, rendering after it and again when its task ends, with the card's own renders allowed
|
||||
async Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object arg)
|
||||
{
|
||||
handlingEvents++;
|
||||
try
|
||||
{
|
||||
var task = callback.InvokeAsync(arg);
|
||||
var pending = task.Status is not (TaskStatus.RanToCompletion or TaskStatus.Canceled);
|
||||
StateHasChanged();
|
||||
if (!pending)
|
||||
return;
|
||||
await task;
|
||||
StateHasChanged();
|
||||
}
|
||||
finally
|
||||
{
|
||||
handlingEvents--;
|
||||
}
|
||||
}
|
||||
|
||||
RenderFragment Body(Status status) => @<div class="flex flex-col gap-3">
|
||||
@if (status.Content is { Length: > 0 } content)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user