-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
feat: use linked lists for each blocks #11107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
This replaces the current implementation of
{#each ...}
blocks with a linked list implementation.What this means is that rather than constantly replacing
state.items
(which is prone to race conditions that are tricky to accommodate), we update the linked list in place as the value changes. As such, it's easy to handle things like aborted outros in the middle of the list without them being re-appended.The hard part of list reconciliation algorithms is minimising moves in a direction-agnostic way. For example, if a list changes from
ABCDE
toBCDEA
, this can easily be accomplished by stashing the existingA
, skipping overBCDE
(because everything matches), then grabbingA
from the stash and moving it to the end.By the same logic, if we changed from
ABCDE
toEABCD
, we would stashABCD
, matchE
, then appendA
thenB
thenC
thenD
— four moves. Ideally, we'd figure out that we can accomplish the same thing by just movingE
instead.Previously, this was accomplished with the help of a longest increasing subsequence algorithm. In this PR, we take a different tack. As we progress through the list, we keep track of consecutive
stashed
andmatched
items, and when we encounter a changed item that we previously saw, we decide whether to move items forward or backward based on which list is longer.So in the
ABCDE -> EABCD
case:ABCD
then matchE
, and proceed to the next item in the listA
, which we've previously seen, so we comparestashed
(ABCD
) andmatched
(E
) and, becausematched
is shorter, moveE
beforeABCD
A === A
), so we proceed to the next itemB === B
, proceedC === C
, proceedD === D
, finishIn this way we're able to reconcile the new data with the existing linked list in what I think is an optimal way. Honestly though, the whole thing is a complete brainfuck and there may be some pathological cases that this struggles with. If anyone with a CS degree has encountered this algorithm before and can speak to its merits/flaws I'd love to hear about them. I haven't benchmarked this against the current implementation (though there are probably some opportunities for further optimisations).
Another change I made: for the sake of my own sanity while working on this, I eliminated the distinction between keyed and indexed each blocks (by turning indexed each blocks into blocks whose key is the index). It's possible that we will need to reinstate that separation to avoid the overhead of creating a useless lookup for indexed blocks.
Before submitting the PR, please make sure you do the following
feat:
,fix:
,chore:
, ordocs:
.Tests and linting
pnpm test
and lint the project withpnpm lint