// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_RUNTIME_RUNTIME_CONTROLLER_H_
#define FLUTTER_RUNTIME_RUNTIME_CONTROLLER_H_

#include <memory>
#include <vector>

#include "assets/native_assets.h"
#include "flutter/assets/asset_manager.h"
#include "flutter/common/task_runners.h"
#include "flutter/fml/macros.h"
#include "flutter/fml/mapping.h"
#include "flutter/lib/ui/io_manager.h"
#include "flutter/lib/ui/painting/image_generator_registry.h"
#include "flutter/lib/ui/text/font_collection.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "flutter/lib/ui/window/platform_configuration.h"
#include "flutter/lib/ui/window/pointer_data_packet.h"
#include "flutter/lib/ui/window/pointer_data_packet_converter.h"
#include "flutter/lib/ui/window/view_focus.h"
#include "flutter/runtime/dart_vm.h"
#include "flutter/runtime/platform_data.h"
#include "flutter/runtime/platform_isolate_manager.h"

namespace flutter {

class Scene;
class RuntimeDelegate;
class View;
class Window;

//------------------------------------------------------------------------------
/// Represents an instance of a running root isolate with window bindings. In
/// normal operation, a single instance of this object is owned by the engine
/// per shell. This object may only be created, used, and collected on the UI
/// task runner. Window state queried by the root isolate is stored by this
/// object. In cold-restart scenarios, the engine may collect this before
/// installing a new runtime controller in its place. The Clone method may be
/// used by the engine to copy the currently accumulated window state so it can
/// be referenced by the new runtime controller.
///
/// When `RuntimeController` is created, it takes some time before the root
/// isolate becomes ready. Operation during this gap is stored by
/// `RuntimeController` and flushed to the Dart VM when the isolate becomes
/// ready before the entrypoint function. See `PlatformData`.
///
class RuntimeController : public PlatformConfigurationClient,
                          PointerDataPacketConverter::Delegate {
 public:
  /// A callback that's invoked after this `RuntimeController` attempts to
  /// add a view to the Dart isolate.
  ///
  /// If the Dart isolate is not launched yet, this callback will be stored
  /// and invoked after the isolate is launched.
  ///
  /// The `added` parameter is false if the add operation fails or was
  /// cancelled while pending using `RemoveView`.
  using AddViewCallback = std::function<void(bool added)>;

  //----------------------------------------------------------------------------
  /// @brief      Creates a new instance of a runtime controller. This is
  ///             usually only done by the engine instance associated with the
  ///             shell.
  ///
  /// @param      client                      The runtime delegate. This is
  ///                                         usually the `Engine` instance.
  /// @param      vm                          A reference to a running Dart VM.
  ///                                         The runtime controller must be
  ///                                         collected before the VM is
  ///                                         destroyed (this order is
  ///                                         guaranteed by the shell).
  /// @param[in]  idle_notification_callback  The idle notification callback.
  ///                                         This allows callers to run native
  ///                                         code in isolate scope when the VM
  ///                                         is about to be notified that the
  ///                                         engine is going to be idle.
  /// @param[in]  platform_data               The window data (if exists).
  /// @param[in]  isolate_create_callback     The isolate create callback. This
  ///                                         allows callers to run native code
  ///                                         in isolate scope on the UI task
  ///                                         runner as soon as the root isolate
  ///                                         has been created.
  /// @param[in]  isolate_shutdown_callback   The isolate shutdown callback.
  ///                                         This allows callers to run native
  ///                                         code in isolate scoped on the UI
  ///                                         task runner just as the root
  ///                                         isolate is about to be torn down.
  /// @param[in]  persistent_isolate_data     Unstructured persistent read-only
  ///                                         data that the root isolate can
  ///                                         access in a synchronous manner.
  /// @param[in]  context              Engine-owned state which is
  ///                                         accessed by the root dart isolate.
  ///
  RuntimeController(
      RuntimeDelegate& p_client,
      DartVM* vm,
      fml::RefPtr<const DartSnapshot> p_isolate_snapshot,
      const std::function<void(int64_t)>& idle_notification_callback,
      const PlatformData& platform_data,
      const fml::closure& isolate_create_callback,
      const fml::closure& isolate_shutdown_callback,
      std::shared_ptr<const fml::Mapping> p_persistent_isolate_data,
      const UIDartState::Context& context);

  //----------------------------------------------------------------------------
  /// @brief      Create a RuntimeController that shares as many resources as
  ///             possible with the calling RuntimeController such that together
  ///             they occupy less memory.
  /// @return     A RuntimeController with a running isolate.
  /// @see        RuntimeController::RuntimeController
  ///
  std::unique_ptr<RuntimeController> Spawn(
      RuntimeDelegate& p_client,
      const std::string& advisory_script_uri,
      const std::string& advisory_script_entrypoint,
      const std::function<void(int64_t)>& idle_notification_callback,
      const fml::closure& isolate_create_callback,
      const fml::closure& isolate_shutdown_callback,
      const std::shared_ptr<const fml::Mapping>& persistent_isolate_data,
      fml::WeakPtr<IOManager> io_manager,
      fml::WeakPtr<ImageDecoder> image_decoder,
      fml::WeakPtr<ImageGeneratorRegistry> image_generator_registry,
      fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate) const;

  // |PlatformConfigurationClient|
  ~RuntimeController() override;

  //----------------------------------------------------------------------------
  /// @brief      Launches the isolate using the window data associated with
  ///             this runtime controller. Before this call, the Dart isolate
  ///             has not been initialized. On successful return, the caller can
  ///             assume that the isolate is in the
  ///             `DartIsolate::Phase::Running` phase.
  ///
  ///             This call will fail if a root isolate is already running. To
  ///             re-create an isolate with the window data associated with this
  ///             runtime controller, `Clone`  this runtime controller and
  ///             Launch an isolate in that runtime controller instead.
  ///
  /// @param[in]  settings                 The per engine instance settings.
  /// @param[in]  root_isolate_create_callback  A callback invoked before the
  ///                                      root isolate has launched the Dart
  ///                                      program, but after it has been
  ///                                      created. This is called without
  ///                                      isolate scope, and after any root
  ///                                      isolate callback in the settings.
  /// @param[in]  dart_entrypoint          The dart entrypoint. If
  ///                                      `std::nullopt` or empty, `main` will
  ///                                      be attempted.
  /// @param[in]  dart_entrypoint_library  The dart entrypoint library. If
  ///                                      `std::nullopt` or empty, the core
  ///                                      library will be attempted.
  /// @param[in]  dart_entrypoint_args     Arguments passed as a List<String>
  ///                                      to Dart's entrypoint function.
  /// @param[in]  isolate_configuration    The isolate configuration
  /// @param[in]  engine_id.               Engine identifier to be passed to the
  ///                                      platform dispatcher.
  ///
  /// @return     If the isolate could be launched and guided to the
  ///             `DartIsolate::Phase::Running` phase.
  ///
  [[nodiscard]] bool LaunchRootIsolate(
      const Settings& settings,
      const fml::closure& root_isolate_create_callback,
      std::optional<std::string> dart_entrypoint,
      std::optional<std::string> dart_entrypoint_library,
      const std::vector<std::string>& dart_entrypoint_args,
      std::unique_ptr<IsolateConfiguration> isolate_configuration,
      std::shared_ptr<NativeAssetsManager> native_assets_manager,
      std::optional<int64_t> engine_id);

  //----------------------------------------------------------------------------
  /// @brief      Clone the runtime controller. Launching an isolate with a
  ///             cloned runtime controller will use the same snapshots and
  ///             copies all window data to the new instance. This is usually
  ///             only used in the debug runtime mode to support the
  ///             cold-restart scenario.
  ///
  /// @return     A clone of the existing runtime controller.
  ///
  std::unique_ptr<RuntimeController> Clone() const;

  //----------------------------------------------------------------------------
  /// @brief      Notify the isolate that a new view is available.
  ///
  ///             A view must be added before other methods can refer to it,
  ///             including the implicit view. Adding a view that already exists
  ///             is an error.
  ///
  ///             The `callback` is invoked when the add operation is attempted,
  ///             failed, or is cancelled.
  ///
  ///             If the isolate is not running, the view add will be queued and
  ///             flushed to the isolate when it starts. Calling `RemoveView`
  ///             before the isolate is launched cancels the add operation.
  ///
  ///             If the isolate is running, a frame will be scheduled.
  ///
  /// @param[in]  view_id           The ID of the new view.
  /// @param[in]  viewport_metrics  The initial viewport metrics for the view.
  /// @param[in]  callback          Callback that will be invoked after the add
  ///                               operation is attempted or cancelled.
  ///
  void AddView(int64_t view_id,
               const ViewportMetrics& view_metrics,
               AddViewCallback callback);

  //----------------------------------------------------------------------------
  /// @brief      Notify the isolate that a view is no longer available.
  ///
  ///             Views that are added before the isolate is started are
  ///             queued until the isolate is launched. If one of these
  ///             "pending" views are removed, the view add is cancelled:
  ///             the `AddViewCallback` will be invoked with an `added` of
  ///             false and `RemoveView` will return false.
  ///
  ///             The implicit view (kFlutterImplicitViewId) should never be
  ///             removed. Doing so triggers an assertion.
  ///
  /// @param[in]  view_id  The ID of the view.
  ///
  /// @return     If the remove view operation was forwarded to the running
  ///             isolate. False if the view does not exist. If the Dart isolate
  ///             is not running, then the pending view creation (if any) is
  ///             cancelled and the return value is always false.
  bool RemoveView(int64_t view_id);

  //----------------------------------------------------------------------------
  /// @brief      Notify the isolate that the focus state of a native view has
  ///             changed.
  ///
  /// @param[in]  event  The focus event describing the change.
  bool SendViewFocusEvent(const ViewFocusEvent& event);

  //----------------------------------------------------------------------------
  /// @brief      Forward the specified viewport metrics to the running isolate.
  ///             If the isolate is not running, these metrics will be saved and
  ///             flushed to the isolate when it starts.
  ///
  /// @param[in]  view_id  The ID for the view that `metrics` describes.
  /// @param[in]  metrics  The window's viewport metrics.
  ///
  /// @return     If the window metrics were forwarded to the running isolate.
  ///
  bool SetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics);

  //----------------------------------------------------------------------------
  /// @brief      Forward the specified display metrics to the running isolate.
  ///             If the isolate is not running, these metrics will be saved and
  ///             flushed to the isolate when it starts.
  ///
  /// @param[in]  displays  The available displays.
  bool SetDisplays(const std::vector<DisplayData>& displays);

  //----------------------------------------------------------------------------
  /// @brief      Forward the specified locale data to the running isolate. If
  ///             the isolate is not running, this data will be saved and
  ///             flushed to the isolate when it starts running.
  ///
  /// @deprecated The persistent isolate data must be used for this purpose
  ///             instead.
  ///
  /// @param[in]  locale_data  The locale data. This should consist of groups of
  ///             4 strings, each group representing a single locale.
  ///
  /// @return     If the locale data was forwarded to the running isolate.
  ///
  bool SetLocales(const std::vector<std::string>& locale_data);

  //----------------------------------------------------------------------------
  /// @brief      Forward the user settings data to the running isolate. If the
  ///             isolate is not running, this data will be saved and flushed to
  ///             the isolate when it starts running.
  ///
  /// @deprecated The persistent isolate data must be used for this purpose
  ///             instead.
  ///
  /// @param[in]  data  The user settings data.
  ///
  /// @return     If the user settings data was forwarded to the running
  ///             isolate.
  ///
  bool SetUserSettingsData(const std::string& data);

  //----------------------------------------------------------------------------
  /// @brief      Forward the initial lifecycle state data to the running
  ///             isolate. If the isolate is not running, this data will be
  ///             saved and flushed to the isolate when it starts running.
  ///             After the isolate starts running, the current lifecycle
  ///             state is pushed to it via the "flutter/lifecycle" channel.
  ///
  /// @deprecated The persistent isolate data must be used for this purpose
  ///             instead.
  ///
  /// @param[in]  data  The lifecycle state data.
  ///
  /// @return     If the lifecycle state data was forwarded to the running
  ///             isolate.
  ///
  bool SetInitialLifecycleState(const std::string& data);

  //----------------------------------------------------------------------------
  /// @brief      Notifies the running isolate about whether the semantics tree
  ///             should be generated or not. If the isolate is not running,
  ///             this preference will be saved and flushed to the isolate when
  ///             it starts running.
  ///
  /// @param[in]  enabled  Indicates whether to generate the semantics tree.
  ///
  /// @return     If the semantics tree generation preference was forwarded to
  ///             the running isolate.
  ///
  bool SetSemanticsEnabled(bool enabled);

  //----------------------------------------------------------------------------
  /// @brief      Forward the preference of accessibility features that must be
  ///             enabled in the semantics tree to the running isolate. If the
  ///             isolate is not running, this data will be saved and flushed to
  ///             the isolate when it starts running.
  ///
  /// @param[in]  flags  The accessibility features that must be generated in
  ///             the semantics tree.
  ///
  /// @return     If the preference of accessibility features was forwarded to
  ///             the running isolate.
  ///
  bool SetAccessibilityFeatures(int32_t flags);

  //----------------------------------------------------------------------------
  /// @brief      Notifies the running isolate that it should start generating a
  ///             new frame.
  ///
  /// @see        `Engine::BeginFrame` for more context.
  ///
  /// @param[in]  frame_time  The point at which the current frame interval
  ///                         began. May be used by animation interpolators,
  ///                         physics simulations, etc.
  ///
  /// @return     If notification to begin frame rendering was delivered to the
  ///             running isolate.
  ///
  bool BeginFrame(fml::TimePoint frame_time, uint64_t frame_number);

  //----------------------------------------------------------------------------
  /// @brief      Dart code cannot fully measure the time it takes for a
  ///             specific frame to be rendered. This is because Dart code only
  ///             runs on the UI task runner. That is only a small part of the
  ///             overall frame workload. The raster task runner frame workload
  ///             is executed on a thread where Dart code cannot run (and hence
  ///             instrument). Besides, due to the pipelined nature of rendering
  ///             in Flutter, there may be multiple frame workloads being
  ///             processed at any given time. However, for non-Timeline based
  ///             profiling, it is useful for trace collection and processing to
  ///             happen in Dart. To do this, the raster task runner frame
  ///             workloads need to be instrumented separately. After a set
  ///             number of these profiles have been gathered, they need to be
  ///             reported back to Dart code. The engine reports this extra
  ///             instrumentation information back to Dart code running on the
  ///             engine by invoking this method at predefined intervals.
  ///
  /// @see        `Engine::ReportTimings`, `FrameTiming`
  ///
  /// @param[in]  timings  Collection of `FrameTiming::kCount` * `n` timestamps
  ///                      for `n` frames whose timings have not been reported
  ///                      yet. A collection of integers is reported here for
  ///                      easier conversions to Dart objects. The timestamps
  ///                      are measured against the system monotonic clock
  ///                      measured in microseconds.
  ///
  bool ReportTimings(std::vector<int64_t> timings);

  //----------------------------------------------------------------------------
  /// @brief      Notify the Dart VM that no frame workloads are expected on the
  ///             UI task runner till the specified deadline. The VM uses this
  ///             opportunity to perform garbage collection operations is a
  ///             manner that interferes as little as possible with frame
  ///             rendering.
  ///
  /// NotifyIdle is advisory. The VM may or may not run a garbage collection
  /// when this is called, and will eventually perform garbage collections even
  /// if it is not called or it is called with insufficient deadlines.
  ///
  /// The garbage collection mechanism and its thresholds are internal
  /// implementation details and absolutely no guarantees are made about the
  /// threshold discussed below. This discussion is also an oversimplification
  /// but hopefully serves to calibrate expectations about GC behavior:
  /// * When the Dart VM and its root isolate are initialized, the memory
  ///   consumed upto that point are treated as a baseline.
  /// * A fixed percentage of the memory consumed (~20%) over the baseline is
  ///   treated as the hard threshold.
  /// * The memory in play is divided into old space and new space. The new
  ///   space is typically very small and fills up rapidly.
  /// * The baseline plus the threshold is considered the old space while the
  ///   small new space is a separate region (typically a few pages).
  /// * The total old space size minus the max new space size is treated as the
  ///   soft threshold.
  /// * In a world where there is no call to NotifyIdle, when the total
  ///   allocation exceeds the soft threshold, a concurrent mark is initiated in
  ///   the VM. There is a “small” pause that occurs when the concurrent mark is
  ///   initiated and another pause when the mark concludes and a sweep is
  ///   initiated.
  /// * If the total allocations exceeds the hard threshold, a “big”
  ///   stop-the-world pause is initiated.
  /// * If after either the sweep after the concurrent mark, or, the
  ///   stop-the-world pause, the consumption returns to be below the soft
  ///   threshold, the dance begins anew.
  /// * If after both the “small” and “big” pauses, memory usage is still over
  ///   the hard threshold, i.e, the objects are still reachable, that amount of
  ///   memory is treated as the new baseline and a fixed percentage of the new
  ///   baseline over the new baseline is now the new hard threshold.
  /// * Updating the baseline will continue till memory for the updated old
  ///   space can be allocated from the operating system. These allocations will
  ///   typically fail due to address space exhaustion on 32-bit systems and
  ///   page table exhaustion on 64-bit systems.
  /// * NotifyIdle initiates the concurrent mark preemptively. The deadline is
  ///   used by the VM to determine if the corresponding sweep can be performed
  ///   within the deadline. This way, jank due to “small” pauses can be
  ///   ameliorated.
  /// * There is no ability to stop a “big” pause on reaching the hard threshold
  ///   in the old space. The best you can do is release (by making them
  ///   unreachable) objects eagerly so that the are marked as unreachable in
  ///   the concurrent mark initiated by either reaching the soft threshold or
  ///   an explicit NotifyIdle.
  /// * If you are running out of memory, its because too many large objects
  ///   were allocation and remained reachable such that the old space kept
  ///   growing till it could grow no more.
  /// * At the edges of allocation thresholds, failures can occur gracefully if
  ///   the instigating allocation was made in the Dart VM or rather gracelessly
  ///   if the allocation is made by some native component.
  ///
  /// @see        `Dart_TimelineGetMicros`
  ///
  /// @bug        The `deadline` argument must be converted to `std::chrono`
  ///             instead of a raw integer.
  ///
  /// @param[in]  deadline  The deadline is used by the VM to determine if the
  ///             corresponding sweep can be performed within the deadline.
  ///
  /// @return     If the idle notification was forwarded to the running isolate.
  ///
  virtual bool NotifyIdle(fml::TimeDelta deadline);

  //----------------------------------------------------------------------------
  /// @brief      Notify the Dart VM that the attached flutter view has been
  ///             destroyed. This gives the Dart VM to perform some cleanup
  ///             activities e.g: perform garbage collection to free up any
  ///             unused memory.
  ///
  /// NotifyDestroyed is advisory. The VM may or may not perform any clean up
  /// activities.
  ///
  virtual bool NotifyDestroyed();

  //----------------------------------------------------------------------------
  /// @brief      Returns if the root isolate is running. The isolate must be
  ///             transitioned to the running phase manually. The isolate can
  ///             stop running if it terminates execution on its own.
  ///
  /// @return     True if root isolate running, False otherwise.
  ///
  virtual bool IsRootIsolateRunning() const;

  //----------------------------------------------------------------------------
  /// @brief      Dispatch the specified platform message to running root
  ///             isolate.
  ///
  /// @param[in]  message  The message to dispatch to the isolate.
  ///
  /// @return     If the message was dispatched to the running root isolate.
  ///             This may fail is an isolate is not running.
  ///
  virtual bool DispatchPlatformMessage(
      std::unique_ptr<PlatformMessage> message);

  //----------------------------------------------------------------------------
  /// @brief      Dispatch the specified pointer data message to the running
  ///             root isolate.
  ///
  /// @param[in]  packet  The pointer data message to dispatch to the isolate.
  ///
  /// @return     If the pointer data message was dispatched. This may fail is
  ///             an isolate is not running.
  ///
  bool DispatchPointerDataPacket(const PointerDataPacket& packet);

  //----------------------------------------------------------------------------
  /// @brief      Dispatch the semantics action to the specified accessibility
  ///             node.
  ///
  /// @param[in]  view_id The identifier of the view.
  /// @param[in]  node_id The identifier of the accessibility node.
  /// @param[in]  action  The semantics action to perform on the specified
  ///                     accessibility node.
  /// @param[in]  args    Optional data that applies to the specified action.
  ///
  /// @return     If the semantics action was dispatched. This may fail if an
  ///             isolate is not running.
  ///
  bool DispatchSemanticsAction(int64_t view_id,
                               int32_t node_id,
                               SemanticsAction action,
                               fml::MallocMapping args);

  //----------------------------------------------------------------------------
  /// @brief      Gets the main port identifier of the root isolate.
  ///
  /// @return     The main port identifier. If no root isolate is running,
  ///             returns `ILLEGAL_PORT`.
  ///
  Dart_Port GetMainPort();

  //----------------------------------------------------------------------------
  /// @brief      Gets the debug name of the root isolate. But default, the
  ///             debug name of the isolate is derived from its advisory script
  ///             URI, advisory main entrypoint and its main port name. For
  ///             example, "main.dart$main-1234" where the script URI is
  ///             "main.dart", the entrypoint is "main" and the port name
  ///             "1234". Once launched, the isolate may re-christen itself
  ///             using a name it selects via `setIsolateDebugName` in
  ///             `window.dart`. This name is purely advisory and only used by
  ///             instrumentation and reporting purposes.
  ///
  /// @return     The debug name of the root isolate.
  ///
  std::string GetIsolateName();

  //----------------------------------------------------------------------------
  /// @brief      Returns if the root isolate has any live receive ports.
  ///
  /// @return     True if there are live receive ports, False otherwise. Return
  ///             False if the root isolate is not running as well.
  ///
  bool HasLivePorts();

  //----------------------------------------------------------------------------
  /// @brief      Returns if the root isolate has any pending microtasks.
  ///
  /// @return     True if there are microtasks that have been queued but not
  ///             run, False otherwise. Return False if the root isolate is not
  ///             running as well.
  ///
  bool HasPendingMicrotasks();

  //----------------------------------------------------------------------------
  /// @brief      Get the last error encountered by the microtask queue.
  ///
  /// @return     The last error encountered by the microtask queue.
  ///
  tonic::DartErrorHandleType GetLastError();

  //----------------------------------------------------------------------------
  /// @brief      Get the service ID of the root isolate if the root isolate is
  ///             running.
  ///
  /// @return     The root isolate service id.
  ///
  std::optional<std::string> GetRootIsolateServiceID() const;

  //----------------------------------------------------------------------------
  /// @brief      Get the return code specified by the root isolate (if one is
  ///             present).
  ///
  /// @return     The root isolate return code if the isolate has specified one.
  ///
  std::optional<uint32_t> GetRootIsolateReturnCode();

  //----------------------------------------------------------------------------
  /// @brief      Get an identifier that represents the Dart isolate group the
  ///             root isolate is in.
  ///
  /// @return     The root isolate group identifier, zero if one can't
  ///             be established.
  uint64_t GetRootIsolateGroup() const;

  //--------------------------------------------------------------------------
  /// @brief      Loads the Dart shared library into the Dart VM. When the
  ///             Dart library is loaded successfully, the Dart future
  ///             returned by the originating loadLibrary() call completes.
  ///
  ///             The Dart compiler may generate separate shared libraries
  ///             files called 'loading units' when libraries are imported
  ///             as deferred. Each of these shared libraries are identified
  ///             by a unique loading unit id. Callers should open and resolve
  ///             a SymbolMapping from the shared library. The Mappings should
  ///             be moved into this method, as ownership will be assumed by the
  ///             dart root isolate after successful loading and released after
  ///             shutdown of the root isolate. The loading unit may not be
  ///             used after isolate shutdown. If loading fails, the mappings
  ///             will be released.
  ///
  ///             This method is paired with a RequestDartDeferredLibrary
  ///             invocation that provides the embedder with the loading unit id
  ///             of the deferred library to load.
  ///
  ///
  /// @param[in]  loading_unit_id  The unique id of the deferred library's
  ///                              loading unit, as passed in by
  ///                              RequestDartDeferredLibrary.
  ///
  /// @param[in]  snapshot_data    Dart snapshot data of the loading unit's
  ///                              shared library.
  ///
  /// @param[in]  snapshot_data    Dart snapshot instructions of the loading
  ///                              unit's shared library.
  ///
  void LoadDartDeferredLibrary(
      intptr_t loading_unit_id,
      std::unique_ptr<const fml::Mapping> snapshot_data,
      std::unique_ptr<const fml::Mapping> snapshot_instructions);

  //--------------------------------------------------------------------------
  /// @brief      Indicates to the dart VM that the request to load a deferred
  ///             library with the specified loading unit id has failed.
  ///
  ///             The dart future returned by the initiating loadLibrary() call
  ///             will complete with an error.
  ///
  /// @param[in]  loading_unit_id  The unique id of the deferred library's
  ///                              loading unit, as passed in by
  ///                              RequestDartDeferredLibrary.
  ///
  /// @param[in]  error_message    The error message that will appear in the
  ///                              dart Future.
  ///
  /// @param[in]  transient        A transient error is a failure due to
  ///                              temporary conditions such as no network.
  ///                              Transient errors allow the dart VM to
  ///                              re-request the same deferred library and
  ///                              loading_unit_id again. Non-transient
  ///                              errors are permanent and attempts to
  ///                              re-request the library will instantly
  ///                              complete with an error.
  virtual void LoadDartDeferredLibraryError(intptr_t loading_unit_id,
                                            const std::string error_message,
                                            bool transient);

  // |PlatformConfigurationClient|
  void RequestDartDeferredLibrary(intptr_t loading_unit_id) override;

  // |PlatformConfigurationClient|
  std::shared_ptr<const fml::Mapping> GetPersistentIsolateData() override;

  const fml::WeakPtr<IOManager>& GetIOManager() const {
    return context_.io_manager;
  }

  virtual DartVM* GetDartVM() const { return vm_; }

  const fml::RefPtr<const DartSnapshot>& GetIsolateSnapshot() const {
    return isolate_snapshot_;
  }

  const PlatformData& GetPlatformData() const { return platform_data_; }

  const fml::RefPtr<SkiaUnrefQueue>& GetSkiaUnrefQueue() const {
    return context_.unref_queue;
  }

  const fml::TaskRunnerAffineWeakPtr<SnapshotDelegate>& GetSnapshotDelegate()
      const {
    return context_.snapshot_delegate;
  }

  std::weak_ptr<const DartIsolate> GetRootIsolate() const {
    return root_isolate_;
  }

  void FlushMicrotaskQueue() {
    if (auto isolate = root_isolate_.lock()) {
      isolate->FlushMicrotasksNow();
    }
  }

  std::shared_ptr<PlatformIsolateManager> GetPlatformIsolateManager() override {
    return platform_isolate_manager_;
  }

  //--------------------------------------------------------------------------
  /// @brief      Shuts down all registered platform isolates. Must be called
  ///             from the platform thread.
  ///
  void ShutdownPlatformIsolates();

 protected:
  /// Constructor for Mocks.
  RuntimeController(RuntimeDelegate& p_client, const TaskRunners& task_runners);

 private:
  struct Locale {
    Locale(std::string language_code_,
           std::string country_code_,
           std::string script_code_,
           std::string variant_code_);

    ~Locale();

    std::string language_code;
    std::string country_code;
    std::string script_code;
    std::string variant_code;
  };

  RuntimeDelegate& client_;
  DartVM* const vm_;
  fml::RefPtr<const DartSnapshot> isolate_snapshot_;
  std::function<void(int64_t)> idle_notification_callback_;
  PlatformData platform_data_;
  std::weak_ptr<DartIsolate> root_isolate_;
  std::weak_ptr<DartIsolate> spawning_isolate_;
  std::optional<uint32_t> root_isolate_return_code_;
  const fml::closure isolate_create_callback_;
  const fml::closure isolate_shutdown_callback_;
  std::shared_ptr<const fml::Mapping> persistent_isolate_data_;
  UIDartState::Context context_;
  PointerDataPacketConverter pointer_data_packet_converter_;
  std::shared_ptr<PlatformIsolateManager> platform_isolate_manager_ =
      std::shared_ptr<PlatformIsolateManager>(new PlatformIsolateManager());
  bool has_flushed_runtime_state_ = false;

  // Callbacks when `AddView` was called before the Dart isolate is launched.
  //
  // These views will be added when `FlushRuntimeStateToIsolate` is called.
  // This is no longer used once the Dart isolate starts.
  std::unordered_map<int64_t, AddViewCallback> pending_add_view_callbacks_;

  // Tracks the views that have been called `Render` during a frame.
  //
  // If all views that have been registered by `AddView` have been called
  // `Render`, then the runtime controller notifies the client of the end of
  // frame immediately, allowing the client to submit the views to the pipeline
  // a bit earlier than having to wait for the end of `BeginFrame`. See also
  // `Animator::OnAllViewsRendered`.
  //
  // This mechanism fixes https://github.com/flutter/flutter/issues/144584 with
  // option 2 and
  // https://github.com/flutter/engine/pull/51186#issuecomment-1977820525 with
  // option a in most cases, except if there are multiple views and only part of
  // them are rendered.
  // TODO(dkwingsmt): Fix these problems for all cases.
  std::unordered_set<uint64_t> rendered_views_during_frame_;

  void MarkAsFrameBorder();

  void CheckIfAllViewsRendered();

  PlatformConfiguration* GetPlatformConfigurationIfAvailable();

  bool FlushRuntimeStateToIsolate();

  // |PointerDataPacketConverter::Delegate|
  bool ViewExists(int64_t view_id) const override;

  // |PlatformConfigurationClient|
  std::string DefaultRouteName() override;

  // |PlatformConfigurationClient|
  void ScheduleFrame() override;

  // |PlatformConfigurationClient|
  void EndWarmUpFrame() override;

  // |PlatformConfigurationClient|
  void Render(int64_t view_id,
              Scene* scene,
              double width,
              double height) override;

  // |PlatformConfigurationClient|
  void UpdateSemantics(int64_t view_id, SemanticsUpdate* update) override;

  // |PlatformConfigurationClient|
  void HandlePlatformMessage(std::unique_ptr<PlatformMessage> message) override;

  // |PlatformConfigurationClient|
  FontCollection& GetFontCollection() override;

  // |PlatformConfigurationClient|
  std::shared_ptr<AssetManager> GetAssetManager() override;

  // |PlatformConfigurationClient|
  void UpdateIsolateDescription(const std::string isolate_name,
                                int64_t isolate_port) override;

  // |PlatformConfigurationClient|
  void SetNeedsReportTimings(bool value) override;

  // |PlatformConfigurationClient|
  std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocale(
      const std::vector<std::string>& supported_locale_data) override;

  // |PlatformConfigurationClient|
  void SendChannelUpdate(std::string name, bool listening) override;

  // |PlatformConfigurationClient|
  double GetScaledFontSize(double unscaled_font_size,
                           int configuration_id) const override;

  // |PlatformConfigurationClient|
  void RequestViewFocusChange(const ViewFocusChangeRequest& request) override;

  FML_DISALLOW_COPY_AND_ASSIGN(RuntimeController);
};

}  // namespace flutter

#endif  // FLUTTER_RUNTIME_RUNTIME_CONTROLLER_H_
