diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 0bd387270ba13..66e68b88be56c 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -308,6 +308,28 @@ class PlatformDispatcher { _invoke(onMetricsChanged, _onMetricsChangedZone); } + // A debug-only variable that stores the [FlutterView]s for which + // [FlutterView.render] has already been called during the current + // [onBeginFrame]/[onDrawFrame] callback sequence. + // + // It is null outside the scope of those callbacks indicating that calls to + // [FlutterView.render] must be ignored. Furthermore, if a given [FlutterView] + // is already present in this set when its [FlutterView.render] is called + // again, that call must be ignored as a duplicate. + // + // Between [onBeginFrame] and [onDrawFrame] the properties value is + // temporarily stored in `_renderedViewsBetweenCallbacks` so that it survives + // the gap between the two callbacks. + // + // In release build, this variable is null, and therefore the calling rule is + // not enforced. This is because the check might hurt cold startup delay; + // see https://github.com/flutter/engine/pull/46919. + Set? _debugRenderedViews; + // A debug-only variable that temporarily stores the `_renderedViews` value + // between `_beginFrame` and `_drawFrame`. + // + // In release build, this variable is null. + Set? _debugRenderedViewsBetweenCallbacks; /// A callback invoked when any view begins a frame. /// @@ -329,11 +351,26 @@ class PlatformDispatcher { // Called from the engine, via hooks.dart void _beginFrame(int microseconds) { + assert(_debugRenderedViews == null); + assert(_debugRenderedViewsBetweenCallbacks == null); + assert(() { + _debugRenderedViews = {}; + return true; + }()); + _invoke1( onBeginFrame, _onBeginFrameZone, Duration(microseconds: microseconds), ); + + assert(_debugRenderedViews != null); + assert(_debugRenderedViewsBetweenCallbacks == null); + assert(() { + _debugRenderedViewsBetweenCallbacks = _debugRenderedViews; + _debugRenderedViews = null; + return true; + }()); } /// A callback that is invoked for each frame after [onBeginFrame] has @@ -351,7 +388,22 @@ class PlatformDispatcher { // Called from the engine, via hooks.dart void _drawFrame() { + assert(_debugRenderedViews == null); + assert(_debugRenderedViewsBetweenCallbacks != null); + assert(() { + _debugRenderedViews = _debugRenderedViewsBetweenCallbacks; + _debugRenderedViewsBetweenCallbacks = null; + return true; + }()); + _invoke(onDrawFrame, _onDrawFrameZone); + + assert(_debugRenderedViews != null); + assert(_debugRenderedViewsBetweenCallbacks == null); + assert(() { + _debugRenderedViews = null; + return true; + }()); } /// A callback that is invoked when pointer data is available. diff --git a/lib/ui/window.dart b/lib/ui/window.dart index 26a258cfa96c1..be761e548bb12 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -353,7 +353,18 @@ class FlutterView { /// scheduling of frames. /// * [RendererBinding], the Flutter framework class which manages layout and /// painting. - void render(Scene scene) => _render(scene as _NativeScene); + void render(Scene scene) { + // Duplicated calls or calls outside of onBeginFrame/onDrawFrame (indicated + // by _debugRenderedViews being null) are ignored. See _debugRenderedViews. + bool validRender = true; + assert(() { + validRender = platformDispatcher._debugRenderedViews?.add(this) ?? false; + return true; + }()); + if (validRender) { + _render(scene as _NativeScene); + } + } @Native)>(symbol: 'PlatformConfigurationNativeApi::Render') external static void _render(_NativeScene scene);