text: // Copyright 2015 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "flutter/sky/shell/ui/engine.h"
  
  #include <unistd.h>
  
  #include <utility>
  
  #include "flutter/assets/directory_asset_bundle.h"
  #include "flutter/assets/zip_asset_bundle.h"
  #include "flutter/glue/movable_wrapper.h"
  #include "flutter/glue/trace_event.h"
  #include "lib/ftl/files/path.h"
  #include "mojo/public/cpp/application/connect.h"
  #include "flutter/sky/engine/bindings/mojo_services.h"
  #include "flutter/sky/engine/core/script/dart_controller.h"
  #include "flutter/sky/engine/core/script/dart_init.h"
  #include "flutter/sky/engine/core/script/ui_dart_state.h"
  #include "flutter/sky/engine/public/platform/Platform.h"
  #include "flutter/sky/engine/public/web/Sky.h"
  #include "flutter/sky/shell/ui/animator.h"
  #include "flutter/sky/shell/ui/flutter_font_selector.h"
  #include "flutter/sky/shell/ui/platform_impl.h"
  #include "third_party/skia/include/core/SkCanvas.h"
  #include "third_party/skia/include/core/SkPictureRecorder.h"
  
  namespace sky {
  namespace shell {
  namespace {
  
  bool PathExists(const std::string& path) {
    return access(path.c_str(), R_OK) == 0;
  }
  
  std::string FindPackagesPath(const std::string& main_dart) {
    std::string directory = files::GetDirectoryName(main_dart);
    std::string packages_path = directory + "/.packages";
    if (!PathExists(packages_path)) {
      directory = files::GetDirectoryName(directory);
      packages_path = directory + "/.packages";
      if (!PathExists(packages_path))
        packages_path = std::string();
    }
    return packages_path;
  }
  
  PlatformImpl* g_platform_impl = nullptr;
  
  }  // namespace
  
  Engine::Config::Config() {}
  
  Engine::Config::~Config() {}
  
  Engine::Engine(const Config& config, rasterizer::RasterizerPtr rasterizer)
      : config_(config),
        animator_(new Animator(config, rasterizer.Pass(), this)),
        binding_(this),
        activity_running_(false),
        have_surface_(false),
        weak_factory_(this) {}
  
  Engine::~Engine() {}
  
  ftl::WeakPtr<Engine> Engine::GetWeakPtr() {
    return weak_factory_.GetWeakPtr();
  }
  
  void Engine::Init() {
    TRACE_EVENT0("flutter", "Engine::Init");
  
    DCHECK(!g_platform_impl);
    g_platform_impl = new PlatformImpl();
    blink::initialize(g_platform_impl);
  }
  
  void Engine::BeginFrame(ftl::TimePoint frame_time) {
    TRACE_EVENT0("flutter", "Engine::BeginFrame");
    if (sky_view_)
      sky_view_->BeginFrame(frame_time);
  }
  
  void Engine::RunFromSource(const std::string& main,
                             const std::string& packages,
                             const std::string& bundle) {
    TRACE_EVENT0("flutter", "Engine::RunFromSource");
    std::string packages_path = packages;
    if (packages_path.empty())
      packages_path = FindPackagesPath(main);
    if (!bundle.empty())
      ConfigureDirectoryAssetBundle(bundle);
    ConfigureView(main);
    sky_view_->dart_controller()->RunFromSource(main, packages_path);
  }
  
  Dart_Port Engine::GetUIIsolateMainPort() {
    if (!sky_view_)
      return ILLEGAL_PORT;
    return sky_view_->GetMainPort();
  }
  
  void Engine::ConnectToEngine(mojo::InterfaceRequest<SkyEngine> request) {
    binding_.Bind(request.Pass());
  }
  
  void Engine::OnOutputSurfaceCreated(const ftl::Closure& gpu_continuation) {
    config_.gpu_task_runner->PostTask(gpu_continuation);
    have_surface_ = true;
    StartAnimatorIfPossible();
    if (sky_view_)
      ScheduleFrame();
  }
  
  void Engine::OnOutputSurfaceDestroyed(const ftl::Closure& gpu_continuation) {
    have_surface_ = false;
    StopAnimator();
    config_.gpu_task_runner->PostTask(gpu_continuation);
  }
  
  void Engine::SetServices(ServicesDataPtr services) {
    services_ = services.Pass();
  
    if (services_->incoming_services) {
      incoming_services_ =
          mojo::ServiceProviderPtr::Create(services_->incoming_services.Pass());
      service_provider_impl_.set_fallback_service_provider(
          incoming_services_.get());
    }
  
    if (services_->frame_scheduler) {
      animator_->Reset();
      animator_->set_frame_scheduler(services_->frame_scheduler.Pass());
    } else {
  #if defined(OS_ANDROID) || defined(OS_IOS) || defined(OS_MACOSX)
      vsync::VSyncProviderPtr vsync_provider;
      if (services_->shell) {
        // We bind and unbind our Shell here, since this is the only place we use
        // it in this class.
        auto shell = mojo::ShellPtr::Create(services_->shell.Pass());
        mojo::ConnectToService(shell.get(), "mojo:vsync",
                               mojo::GetProxy(&vsync_provider));
        services_->shell = shell.Pass();
      } else {
        mojo::ConnectToService(incoming_services_.get(),
                               mojo::GetProxy(&vsync_provider));
      }
      animator_->Reset();
      animator_->set_vsync_provider(vsync_provider.Pass());
  #endif
    }
  }
  
  void Engine::OnViewportMetricsChanged(ViewportMetricsPtr metrics) {
    viewport_metrics_ = metrics.Pass();
    if (sky_view_)
      sky_view_->SetViewportMetrics(viewport_metrics_);
  }
  
  void Engine::OnLocaleChanged(const mojo::String& language_code,
                               const mojo::String& country_code) {
    language_code_ = language_code;
    country_code_ = country_code;
    if (sky_view_)
      sky_view_->SetLocale(language_code_, country_code_);
  }
  
  void Engine::OnPointerPacket(pointer::PointerPacketPtr packet) {
    TRACE_EVENT0("flutter", "Engine::OnPointerPacket");
  
    // Convert the pointers' x and y coordinates to logical pixels.
    for (auto it = packet->pointers.begin(); it != packet->pointers.end(); ++it) {
      (*it)->x /= viewport_metrics_->device_pixel_ratio;
      (*it)->y /= viewport_metrics_->device_pixel_ratio;
    }
  
    if (sky_view_)
      sky_view_->HandlePointerPacket(packet);
  }
  
  void Engine::RunFromSnapshotStream(
      const std::string& script_uri,
      mojo::ScopedDataPipeConsumerHandle snapshot) {
    TRACE_EVENT0("flutter", "Engine::RunFromSnapshotStream");
    ConfigureView(script_uri);
    snapshot_drainer_.reset(new glue::DrainDataPipeJob(
        std::move(snapshot), [this](std::vector<char> snapshot) {
          FTL_DCHECK(sky_view_);
          FTL_DCHECK(sky_view_->dart_controller());
          sky_view_->dart_controller()->RunFromSnapshot(
              reinterpret_cast<uint8_t*>(snapshot.data()), snapshot.size());
        }));
  }
  
  void Engine::ConfigureZipAssetBundle(const std::string& path) {
    asset_store_ = ftl::MakeRefCounted<blink::ZipAssetStore>(
        path, ftl::RefPtr<ftl::TaskRunner>(
                  blink::Platform::current()->GetIOTaskRunner()));
    new blink::ZipAssetBundle(mojo::GetProxy(&root_bundle_), asset_store_);
  }
  
  void Engine::ConfigureDirectoryAssetBundle(const std::string& path) {
    new blink::DirectoryAssetBundle(
        mojo::GetProxy(&root_bundle_), path,
        ftl::RefPtr<ftl::TaskRunner>(
            blink::Platform::current()->GetIOTaskRunner()));
  }
  
  void Engine::ConfigureView(const std::string& script_uri) {
    snapshot_drainer_.reset();
    sky_view_ = blink::SkyView::Create(this);
    sky_view_->CreateView(std::move(script_uri));
    sky_view_->SetViewportMetrics(viewport_metrics_);
    sky_view_->SetLocale(language_code_, country_code_);
    if (!initial_route_.empty())
      sky_view_->PushRoute(initial_route_);
  }
  
  void Engine::RunFromPrecompiledSnapshot(const mojo::String& bundle_path) {
    TRACE_EVENT0("flutter", "Engine::RunFromPrecompiledSnapshot");
    ConfigureZipAssetBundle(bundle_path.get());
    ConfigureView("http://localhost");
    sky_view_->dart_controller()->RunFromPrecompiledSnapshot();
  }
  
  void Engine::RunFromFile(const mojo::String& main,
                           const mojo::String& packages,
                           const mojo::String& bundle) {
    RunFromSource(main, packages, bundle);
  }
  
  void Engine::RunFromBundle(const mojo::String& script_uri,
                             const mojo::String& path) {
    TRACE_EVENT0("flutter", "Engine::RunFromBundle");
    ConfigureZipAssetBundle(path);
    mojo::DataPipe pipe;
    asset_store_->GetAsStream(blink::kSnapshotAssetKey,
                              std::move(pipe.producer_handle));
    RunFromSnapshotStream(script_uri, std::move(pipe.consumer_handle));
  }
  
  void Engine::RunFromBundleAndSnapshot(const mojo::String& script_uri,
                                        const mojo::String& bundle_path,
                                        const mojo::String& snapshot_path) {
    TRACE_EVENT0("flutter", "Engine::RunFromBundleAndSnapshot");
  
    ConfigureZipAssetBundle(bundle_path);
  
    asset_store_->AddOverlayFile(blink::kSnapshotAssetKey, snapshot_path);
    mojo::DataPipe pipe;
    asset_store_->GetAsStream(blink::kSnapshotAssetKey,
                              std::move(pipe.producer_handle));
    RunFromSnapshotStream(script_uri, std::move(pipe.consumer_handle));
  }
  
  void Engine::PushRoute(const mojo::String& route) {
    if (sky_view_)
      sky_view_->PushRoute(route);
    else
      initial_route_ = route;
  }
  
  void Engine::PopRoute() {
    if (sky_view_)
      sky_view_->PopRoute();
  }
  
  void Engine::OnAppLifecycleStateChanged(sky::AppLifecycleState state) {
    switch (state) {
      case sky::AppLifecycleState::PAUSED:
        activity_running_ = false;
        StopAnimator();
        break;
  
      case sky::AppLifecycleState::RESUMED:
        activity_running_ = true;
        StartAnimatorIfPossible();
        break;
    }
  
    if (sky_view_)
      sky_view_->OnAppLifecycleStateChanged(state);
  }
  
  void Engine::DidCreateMainIsolate(Dart_Isolate isolate) {
    mojo::ServiceProviderPtr services_from_embedder;
    service_provider_bindings_.AddBinding(
        &service_provider_impl_, mojo::GetProxy(&services_from_embedder));
  
    blink::MojoServices::Create(isolate, services_.Pass(),
                                services_from_embedder.Pass(),
                                root_bundle_.Pass());
  
    if (asset_store_)
      FlutterFontSelector::Install(asset_store_);
  }
  
  void Engine::DidCreateSecondaryIsolate(Dart_Isolate isolate) {
    mojo::ServiceProviderPtr services_from_embedder;
    auto request = glue::WrapMovable(mojo::GetProxy(&services_from_embedder));
    ftl::WeakPtr<Engine> engine = weak_factory_.GetWeakPtr();
    blink::Platform::current()->GetUITaskRunner()->PostTask(
        [engine, request]() mutable {
          if (engine)
            engine->BindToServiceProvider(request.Unwrap());
        });
    blink::MojoServices::Create(isolate, nullptr,
                                std::move(services_from_embedder), nullptr);
  }
  
  void Engine::BindToServiceProvider(
      mojo::InterfaceRequest<mojo::ServiceProvider> request) {
    service_provider_bindings_.AddBinding(&service_provider_impl_,
                                          request.Pass());
  }
  
  void Engine::StopAnimator() {
    animator_->Stop();
  }
  
  void Engine::StartAnimatorIfPossible() {
    if (activity_running_ && have_surface_)
      animator_->Start();
  }
  
  void Engine::ScheduleFrame() {
    animator_->RequestFrame();
  }
  
  void Engine::FlushRealTimeEvents() {
    animator_->FlushRealTimeEvents();
  }
  
  void Engine::Render(std::unique_ptr<flow::LayerTree> layer_tree) {
    if (!layer_tree)
      return;
    if (viewport_metrics_) {
      layer_tree->set_scene_version(viewport_metrics_->scene_version);
      layer_tree->set_frame_size(SkISize::Make(
          viewport_metrics_->physical_width, viewport_metrics_->physical_height));
    } else {
      layer_tree->set_scene_version(0);
      layer_tree->set_frame_size(SkISize::Make(0, 0));
    }
    animator_->Render(std::move(layer_tree));
  }
  
  }  // namespace shell
  }  // namespace sky
  