diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs index 3665fc4475..6d698ae5ce 100644 --- a/src/Avalonia.Base/Layout/LayoutManager.cs +++ b/src/Avalonia.Base/Layout/LayoutManager.cs @@ -28,10 +28,12 @@ namespace Avalonia.Layout private bool _queued; private bool _running; private int _totalPassCount; + private Action _invokeOnRender; public LayoutManager(ILayoutRoot owner) { _owner = owner as Layoutable ?? throw new ArgumentNullException(nameof(owner)); + _invokeOnRender = ExecuteQueuedLayoutPass; } public virtual event EventHandler? LayoutUpdated; @@ -345,7 +347,7 @@ namespace Avalonia.Layout if (!_queued && !_running) { _queued = true; - MediaContext.Instance.QueueLayoutPass(this); + MediaContext.Instance.BeginInvokeOnRender(_invokeOnRender); } } diff --git a/src/Avalonia.Base/Media/MediaContext.cs b/src/Avalonia.Base/Media/MediaContext.cs index 5bd4a51102..023b2f728c 100644 --- a/src/Avalonia.Base/Media/MediaContext.cs +++ b/src/Avalonia.Base/Media/MediaContext.cs @@ -16,6 +16,7 @@ internal partial class MediaContext : ICompositorScheduler private DispatcherOperation? _nextRenderOp; private DispatcherOperation? _inputMarkerOp; private TimeSpan _inputMarkerAddedAt; + private bool _isRendering; private bool _animationsAreWaitingForComposition; private const double MaxSecondsWithoutInput = 1; private readonly Action _render; @@ -23,7 +24,9 @@ internal partial class MediaContext : ICompositorScheduler private readonly HashSet _requestedCommits = new(); private readonly Dictionary _pendingCompositionBatches = new(); private record TopLevelInfo(Compositor Compositor, CompositingRenderer Renderer, ILayoutManager LayoutManager); - private readonly HashSet _queuedLayoutManagers = new(); + + private List? _invokeOnRenderCallbacks; + private readonly Stack> _invokeOnRenderCallbackListPool = new(); private Dictionary _topLevels = new(); @@ -95,11 +98,13 @@ internal partial class MediaContext : ICompositorScheduler { try { + _isRendering = true; RenderCore(); } finally { _nextRenderOp = null; + _isRendering = false; } } @@ -114,10 +119,7 @@ internal partial class MediaContext : ICompositorScheduler for (var c = 0; c < 10; c++) { _clock.HasNewSubscriptions = false; - //TODO: Integrate LayoutManager's attempt limit here - foreach (var layout in _queuedLayoutManagers.ToArray()) - layout.ExecuteQueuedLayoutPass(); - _queuedLayoutManagers.Clear(); + FireInvokeOnRenderCallbacks(); if (_clock.HasNewSubscriptions) { @@ -160,9 +162,55 @@ internal partial class MediaContext : ICompositorScheduler } } - public void QueueLayoutPass(LayoutManager layoutManager) + /// + /// Calls all _invokeOnRenderCallbacks until no more are added + /// + private void FireInvokeOnRenderCallbacks() { - _queuedLayoutManagers.Add(layoutManager); - ScheduleRender(true); + int callbackLoopCount = 0; + int count = _invokeOnRenderCallbacks?.Count ?? 0; + + // This outer loop is to re-run layout in case the app causes a layout to get enqueued in response + // to a Loaded event. In this case we would like to re-run layout before we allow render. + do + { + while (count > 0) + { + callbackLoopCount++; + if (callbackLoopCount > 153) + throw new InvalidOperationException("Infinite layout loop detected"); + + var callbacks = _invokeOnRenderCallbacks!; + _invokeOnRenderCallbacks = null; + + for (int i = 0; i < count; i++) + callbacks[i].Invoke(); + + callbacks.Clear(); + _invokeOnRenderCallbackListPool.Push(callbacks); + + count = _invokeOnRenderCallbacks?.Count ?? 0; + } + + // TODO: port the rest of the Loaded logic later + // Fire all the pending Loaded events before Render happens + // but after the layout storm has subsided + // FireLoadedPendingCallbacks(); + + count = _invokeOnRenderCallbacks?.Count ?? 0; + } + while (count > 0); + } + + public void BeginInvokeOnRender(Action callback) + { + if (_invokeOnRenderCallbacks == null) + _invokeOnRenderCallbacks = + _invokeOnRenderCallbackListPool.Count > 0 ? _invokeOnRenderCallbackListPool.Pop() : new(); + + _invokeOnRenderCallbacks.Add(callback); + + if (!_isRendering) + ScheduleRender(true); } } \ No newline at end of file