Skip to content

Defer document and tool content materialization to the next dispatcher frame#1086

Merged
wieslawsoltes merged 18 commits intomasterfrom
feature/deferred-content-materialization
Apr 7, 2026
Merged

Defer document and tool content materialization to the next dispatcher frame#1086
wieslawsoltes merged 18 commits intomasterfrom
feature/deferred-content-materialization

Conversation

@wieslawsoltes
Copy link
Copy Markdown
Owner

@wieslawsoltes wieslawsoltes commented Mar 30, 2026

PR Summary: Deferred Dock Content Materialization

Branch

feature/deferred-content-materialization

Pull Request

Commit Series

  1. c54c8c01c Add deferred dock content host
  2. ba153af47 Add deferred content headless tests
  3. f83a4a0bb Ignore local report artifacts
  4. 8e80a6707 Extract deferred content hosts into standalone package
  5. 6ecdbc499 Document deferred content control package
  6. 8944d52d5 Support standard content presenter templates
  7. 1ec4ac487 Batch deferred content realization per frame
  8. f580fabf3 Prevent deferred queue starvation
  9. 1fd6dc8bb Add configurable deferred presentation budgets
  10. a719c5c71 Document deferred presentation budget settings
  11. 16b5eca4a Bound deferred flushes to one queue scan
  12. f9cbbd27b Detach removed windows from owner graph
  13. 9f4ef42a3 Add scoped deferred presentation timelines
  14. 8473c5985 Add deferred content timeline sample
  15. 6d1a0d902 Document deferred presentation timelines
  16. 9e0680eb3 Refine deferred content reveal behavior
  17. 99ed42cbf Document deferred content reveal behavior

Problem

Large Dock layouts were still paying too much work during initial measure because heavy content hosts were eagerly triggering presenter realization, logical-tree attachment, and style/theme activation.

The first pass of this branch introduced deferred hosts and bounded batching, but two practical gaps remained:

  • there was no way to scope deferred work the way Dock scopes control caching,
  • there was no end-to-end sample or documentation for ordering, delay, timeline budgets, and presenter-contract hosts.

In addition, the sample work exposed a presenter-contract edge case: time-budgeted standalone presenter paths needed more reliable delayed scheduling, and the sample needed to use the same template-binding pattern as real Dock presenter hosts.

After that, reveal smoothing exposed two more runtime issues:

  • a first-paint fade could hide startup or structural dock hosts,
  • aggressive replacement fading could make tool tab switches look blank even though the new content was already realized.

Final Scope

This branch now covers four layers:

1. Built-in theme deferral

The built-in Fluent and Simple themes defer the main expensive content hosts:

  • DocumentContentControl
  • ToolContentControl
  • DocumentControl
  • ToolControl
  • MdiDocumentWindow
  • SplitViewDockControl
  • PinnedDockControl
  • DockControl
  • RootDockControl
  • ToolChromeControl
  • HostWindow

Paths that intentionally stay eager:

  • cached document tab content in DocumentControl
  • drag preview content
  • managed window layer item templates

Managed floating-window content still has an explicit synchronous opt-out because deferring hosted live dock controls caused regressions.

2. Deferred timeline scoping

The standalone Dock.Controls.DeferredContentControl package now supports scoped deferred scheduling instead of only one global queue.

New API surface:

  • DeferredContentPresentationTimeline
  • DeferredContentScheduling.Timeline
  • DeferredContentScheduling.Delay
  • DeferredContentScheduling.Order
  • DeferredContentPresentationSettings.DefaultTimeline
  • DeferredContentPresentationSettings.InitialDelay
  • DeferredContentPresentationSettings.FollowUpDelay

This allows:

  • one shared default timeline for existing behavior,
  • subtree-scoped timelines with independent queues,
  • per-host ordering inside a shared scope,
  • per-host delay on top of timeline delay,
  • both item-count and realization-time budgets in any scope.

3. Sample and documentation

The branch adds a focused sample app and full documentation for:

  • default timeline behavior,
  • scoped timelines,
  • per-host order and delay,
  • count-based and time-based budgets,
  • DeferredContentControl,
  • presenter-contract usage through DeferredContentPresenter.

4. Deferred reveal smoothing

The deferred package now includes a bounded reveal transition for deferred replacements:

  • RevealDuration is configurable on both the shared default timeline and scoped timelines,
  • first realization from a blank host stays immediate,
  • replacement content fades from a near-opaque state instead of from fully transparent,
  • existing caller-supplied opacity transitions are preserved.

That keeps the UI refresh smoother without blanking startup content or making tool/document tab switches look unloaded.

Approach

The deferred package now has two host types:

  • DeferredContentControl for templates that can use a ContentControl
  • DeferredContentPresenter for templates that must preserve a ContentPresenter contract

Both hosts keep requested Content and ContentTemplate, enqueue themselves onto a deferred timeline, and apply the latest coalesced state only when the timeline grants them a turn.

The queue implementation now supports:

  • count-based batching,
  • elapsed-time batching,
  • configurable initial and follow-up delay,
  • scoped independent timelines,
  • order-aware processing,
  • starvation prevention,
  • bounded single-pass scans,
  • reliable delayed reposting for follow-up work.

On top of scheduling, deferred hosts now support a short reveal transition that is applied only when replacing already-presented content.

Key Implementation Details

New package and public API

Files:

  • src/Dock.Controls.DeferredContentControl/Dock.Controls.DeferredContentControl.csproj
  • src/Dock.Controls.DeferredContentControl/DeferredContentControl.cs
  • src/Dock.Controls.DeferredContentControl/Properties/AssemblyInfo.cs

Key points:

  • Deferred hosts live in a standalone package alongside the other Dock control packages.
  • AssemblyInfo.cs preserves the Avalonia XML namespace mapping so theme XAML did not need a contract change.
  • IDeferredContentPresentation remains the public opt-out contract for content that must stay synchronous.

Queue behavior

File:

  • src/Dock.Controls.DeferredContentControl/DeferredContentControl.cs

Final queue behavior:

  • BudgetMode supports ItemCount and RealizationTime.
  • MaxPresentationsPerPass and MaxRealizationTimePerPass bound per-pass work.
  • InitialDelay controls when a newly queued target first becomes due.
  • FollowUpDelay controls later passes while pending work remains.
  • Lower Order values run first, and FIFO order is preserved for ties.
  • Targets that are not ready stay queued instead of being dropped.
  • One flush only scans each currently pending target once.
  • Follow-up delayed passes use Task.Delay plus UI-thread reposting, which made delayed presenter/time-budget paths reliable in both tests and the desktop sample.

The queue hot path was also tightened to avoid the LINQ/array allocation regression introduced during the first scoped-timeline implementation.

Presenter-contract path

File:

  • src/Dock.Controls.DeferredContentControl/DeferredContentControl.cs

DeferredContentPresenter now:

  • keeps requested state separate from applied state,
  • restores the last applied base presenter state until the deferred batch runs,
  • avoids measure-time realization before its queue turn,
  • still applies content/template atomically when presentation occurs.

This was necessary so presenter-contract templates actually defer the same way control-host templates do.

Reveal behavior

File:

  • src/Dock.Controls.DeferredContentControl/DeferredContentControl.cs

Final reveal behavior:

  • RevealDuration lives on DeferredContentPresentationTimeline and DeferredContentPresentationSettings.
  • First paint from a blank presenter does not animate.
  • Deferred replacements use a short opacity transition starting from a visible state rather than 0.
  • The helper adds or updates its own reveal transition without overwriting unrelated transitions already set on the control.
  • Reveal completion reposts onto a normal UI pass instead of depending on an extra render-only tick.

Sample app

Files:

  • samples/DockDeferredContentSample/DockDeferredContentSample.csproj
  • samples/DockDeferredContentSample/MainWindow.axaml
  • samples/DockDeferredContentSample/ViewModels/MainWindowViewModel.cs
  • samples/DockDeferredContentSample/Controls/PresenterCardHost.cs
  • samples/DockDeferredContentSample/README.md

Key points:

  • The sample shows the shared default timeline, a scoped ordered timeline, and a scoped time-budgeted presenter-contract timeline.
  • The presenter-contract section now uses a small templated host (PresenterCardHost) that feeds an inner DeferredContentPresenter through template bindings.
  • That mirrors the actual Dock presenter-contract use case and avoids the direct data-template binding issue that was leaving presenter content empty in the sample.

Tests

Files:

  • tests/Dock.Avalonia.HeadlessTests/DeferredContentControlTests.cs
  • tests/Dock.Avalonia.HeadlessTests/HostWindowThemeChangeTests.cs
  • tests/Dock.Avalonia.HeadlessTests/FactoryWindowManagementTests.cs

Coverage now includes:

  • deferred first materialization,
  • coalesced content updates,
  • detach/reattach behavior,
  • item-count batching,
  • time-budget batching,
  • starvation prevention,
  • bounded flush scans,
  • standard ContentPresenter fallback templates,
  • scoped independent timelines,
  • inherited scoped order and delay,
  • scoped time-budget ordering,
  • presenter-contract scoped hosts inside ItemsControl,
  • auto-scheduled delayed presenter/time-budget realization,
  • first-paint reveal suppression,
  • visible replacement reveal behavior,
  • tool-tab active content switching under deferred reveal,
  • preservation of existing opacity transitions,
  • host-window theme-change behavior under real deferred first paint,
  • removed-window graph cleanup.

Documentation

Files:

  • docfx/articles/dock-deferred-content.md
  • docfx/articles/dock-deferred-content-sample.md
  • docfx/articles/toc.yml
  • docfx/index.md
  • README.md

The documentation now explains:

  • when to use DeferredContentControl versus DeferredContentPresenter,
  • how to configure the default shared timeline,
  • how to create scoped timelines,
  • how Delay and Order interact with both budget modes,
  • how RevealDuration works,
  • why the first realization stays immediate while later swaps can animate,
  • why delayed scopes can legitimately show blank hosts until their turn,
  • where to find and run the sample app.

Review and Follow-up Fixes Included

This branch also includes the fixes discovered during review and validation:

  1. Atomic presenter application to avoid mismatched Content/ContentTemplate state.
  2. Reattach-safe queue behavior.
  3. Standard ContentPresenter template compatibility.
  4. Per-pass bounded queue batching.
  5. Starvation prevention when blocked targets appear ahead of ready targets.
  6. Configurable item-count and time-based budgets.
  7. Bounded single-pass scans to avoid revisiting blocked targets indefinitely.
  8. Floating-window graph detachment to fix the CI leak regression.
  9. Scoped timelines, inherited delay/order, and sample coverage.
  10. Presenter-contract sample fix using a templated host plus reliable delayed follow-up scheduling.
  11. Deferred reveal smoothing that avoids hidden startup hosts.
  12. Tool/document replacement reveal tuning so tab switches stay visibly loaded.

Validation

Executed:

dotnet test tests/Dock.Avalonia.HeadlessTests/Dock.Avalonia.HeadlessTests.csproj --filter DeferredContentControlTests
dotnet test tests/Dock.Avalonia.HeadlessTests/Dock.Avalonia.HeadlessTests.csproj --filter "FullyQualifiedName~DeferredContentControlTests|FullyQualifiedName~GeneratedItemContainerThemeTests|FullyQualifiedName~HostWindowThemeChangeTests|FullyQualifiedName~HostWindowOwnerModeTests|FullyQualifiedName~DockControlsTests|FullyQualifiedName~DockControlMainWindowTests|FullyQualifiedName~FactoryWindowManagementTests|FullyQualifiedName~ManagedWindowParityTests"
dotnet build samples/DockDeferredContentSample/DockDeferredContentSample.csproj
dotnet build samples/DockMvvmSample/DockMvvmSample.csproj
dotnet test tests/Dock.Avalonia.LeakTests/Dock.Avalonia.LeakTests.csproj -c Release --filter "DocumentTabStripItem_DoubleTap_Floats_DoesNotLeak|ToolTabStripItem_DoubleTap_Floats_DoesNotLeak|DockControl_FloatDockable_NativeHost_DoesNotLeak"

The deferred-content suite, the broader touched headless suite, the deferred-content sample build, the MVVM sample build, and the targeted leak validation passed.

Risk and Compatibility Notes

  • Deferred presentation changes when content becomes visible, not the eventual rendered result.
  • Any code assuming synchronous materialization before the next dispatcher turn may now observe deferred first paint on the covered hosts.
  • Scoped timelines make that behavior configurable rather than fixed.
  • Large InitialDelay or per-host Delay values can intentionally leave a host blank until its scheduled turn.
  • Cached document tab content remains eager by design.
  • Managed hosted-window content keeps its explicit synchronous opt-out.

Suggested PR Description

This PR finishes Dock’s deferred content materialization work by moving the hosts into a standalone Dock.Controls.DeferredContentControl package, wiring the built-in themes to defer the main expensive content hosts, and adding configurable deferred timelines that can be scoped to any subtree. The package now supports shared or scoped queues, per-host delay and order, item-count or realization-time budgets, and presenter-contract hosts through DeferredContentPresenter.

The branch also adds a focused sample app and full docs for timeline scoping, budgets, and presenter-contract usage, plus follow-up fixes for queue starvation, bounded scans, delayed follow-up scheduling, leak cleanup, and presenter-sample reliability.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f83a4a0bbc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a719c5c71b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99ed42cbff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +579 to +583
internal void ApplyDeferredState(object? content, IDataTemplate? contentTemplate, TimeSpan revealDuration)
{
var hadPresentedChild = Child is not null;
_suppressDeferredUpdates = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve applied presenter state in templated-parent path

When DeferredContentPresenter is used as PART_ContentPresenter inside a DeferredContentControl template (especially if the template binds Content/ContentTemplate), this method applies values to the base presenter but never updates _appliedContent, _appliedContentTemplate, or _appliedVersion. Because QueueDeferredPresentation() short-circuits for TemplatedParent is DeferredContentControl, ApplyDeferredPresentation() is never called to refresh those fields, so the next OnPropertyChanged/RestoreAppliedState() can roll the presenter back to stale values (often null), causing visible blanking/flicker before the deferred batch runs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec927e874f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1114 to +1115
control.Opacity = RevealStartingOpacity;
Dispatcher.UIThread.Post(() => control.SetCurrentValue(Visual.OpacityProperty, 1D), DispatcherPriority.Background);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve bound opacity during deferred reveal staging

The reveal path writes control.Opacity directly before posting the final value, which introduces a local value on Opacity and can override an existing style/binding-driven opacity on the host. In templates that intentionally set non-default opacity (for dimming, transitions, or state-based styling), a deferred content swap will force the presenter toward 1.0, causing visible regressions and potentially breaking the original binding source for later updates.

Useful? React with 👍 / 👎.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant