Skip to content

Coalesce the assembly tree's selection and removal fan-out - #4157

Open
siegfriedpammer wants to merge 2 commits into
masterfrom
perf/tree-selection-fanout
Open

siegfriedpammer wants to merge 2 commits into
masterfrom
perf/tree-selection-fanout

Conversation

@siegfriedpammer

@siegfriedpammer siegfriedpammer commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

Selecting or deleting a large part of the assembly tree paid the cost of selection/list-change fan-out once per row.

This keeps the PR focused on the two highest-impact parts:

Removing a set of assemblies now happens in one logical list change. Deleting assemblies one at a time raised one collection change per row, and listeners reacted by pruning navigation history, restarting searches, re-querying commands and updating tabs once per event. The list now removes contiguous runs as ranged Remove notifications so consumers still receive the removed items and indices without a Reset.

Selection fan-out is coalesced. The tree selection binder now mirrors bulk selection changes into the model inside a selection batch. Membership checks use sets instead of scanning the model selection per node, and DockWorkspace no longer subscribes to both SelectedItems.CollectionChanged and SelectedItem for the same work.

The generic BatchedObservableCollection<T> helper was removed; the assembly list now owns the small range-remove operation it needs locally.

Verification

  • OPENSSL_ENABLE_SHA1_SIGNATURES=1 dotnet test --project ILSpy.Tests/ILSpy.Tests.csproj --filter SelectAllDeleteBatchingTests --report-trx: 2 passed.

This PR description was written by an AI agent (OpenCode gpt-5.5) working under @siegfriedpammer's direction.

Deleting assemblies one at a time is not equivalent to deleting them
together: each removal raises its own collection change, and the
listeners of the assembly list react to one of those by pruning
navigation history, restarting a running search, re-querying every bound
command and re-decompiling whatever is still selected. Selecting an
expanded list and pressing Delete therefore paid for all of that once per
assembly, and froze the UI.

Emptying the list still reports a Remove rather than the Reset that
Clear() raises, because consumers read Reset as "re-read everything" and
several short-circuit their per-removal cleanup on it, which would leave
tabs and navigation history pointing at unloaded assemblies.

The tree's removal is reordered for the same reason: it detached and
announced one node at a time, so a listener reacting to the first
notification still found the rest of the same removal attached and took
them for a selection the user had made.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
A selection change reaches a full application-wide command re-query, a
session-settings write, a message-bus broadcast and a decompile of the
new selection. The model already had a flag to hold that off during a
bulk edit, but only its own SelectNodes ever set it: the binder that
mirrors the tree's selection into the model added and removed one node at
a time, so selecting every row cost one fan-out per row.

The flag becomes a scope any bulk edit can enter, and the binder uses it.
Membership now comes from a set rather than a scan of the model selection
per node, which was quadratic once a large part of the tree was selected.

DockWorkspace subscribed to both the selection collection and the
SelectedItem property, so it showed the selection twice per change and
once per node during a bulk edit; the property already fires when the
selection settles, so the collection subscription goes.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
@siegfriedpammer
siegfriedpammer force-pushed the perf/tree-selection-fanout branch from 3535bb2 to 9fb1a5a Compare September 21, 2026 15:51

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant