Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1950e72
Impl engine
dkwingsmt Feb 12, 2024
f55dfdf
Web
dkwingsmt Feb 12, 2024
7f76c5f
Rename to RequestWarmUpFrame and add web time
dkwingsmt Feb 13, 2024
31a5ea3
Change to scheduleWarmUpFrame and EndWarmUpFrame
dkwingsmt Feb 13, 2024
590287d
Comment
dkwingsmt Feb 13, 2024
40b269b
Doc
dkwingsmt Feb 13, 2024
2d70a0f
Simplify web implementation
dkwingsmt Feb 14, 2024
4af9f06
Fix comment
dkwingsmt Feb 14, 2024
bfe5570
More doc
dkwingsmt Feb 14, 2024
21439c4
Fix doc
dkwingsmt Feb 14, 2024
1bd21db
More doc
dkwingsmt Feb 14, 2024
4f41658
Fix linter
dkwingsmt Feb 14, 2024
eeac064
Fix comment
dkwingsmt Feb 14, 2024
9c5bce4
Add test
dkwingsmt Feb 15, 2024
cdcf71a
Fix test
dkwingsmt Feb 15, 2024
7ab7d8e
Better test
dkwingsmt Feb 15, 2024
afbcfae
Merge remote-tracking branch 'origin/main' into force-sync-frame
dkwingsmt Feb 15, 2024
7381087
Simplify test and add platformdispatcher test
dkwingsmt Feb 15, 2024
05d3d2d
Better structure
dkwingsmt Feb 15, 2024
06957bb
Better name
dkwingsmt Feb 15, 2024
68953c0
Merge branch 'main' into force-sync-frame
dkwingsmt Feb 15, 2024
a9b7511
Merge branch 'main' into force-sync-frame
dkwingsmt Feb 20, 2024
7789d11
Original changes
dkwingsmt Feb 20, 2024
06b4a50
Merge branch 'force-sync-frame' into reland-3-mv-pipeline-base
dkwingsmt Feb 20, 2024
3464c05
Additional changes
dkwingsmt Feb 20, 2024
fe14311
Comments
dkwingsmt Feb 20, 2024
5b0719b
Merge branch 'main' into reland-3-mv-pipeline
dkwingsmt Feb 20, 2024
8731e47
Add web platform dispatcher test
dkwingsmt Feb 20, 2024
cdeffd9
Merge branch 'main' into force-sync-frame
dkwingsmt Feb 20, 2024
4c91a9e
Merge branch 'force-sync-frame' into reland-3-mv-pipeline
dkwingsmt Feb 20, 2024
34998fa
Fix lint
dkwingsmt Feb 20, 2024
658d5d1
Merge branch 'main' into reland-3-mv-pipeline
dkwingsmt Feb 21, 2024
946ffb5
Merge remote-tracking branch 'origin/main' into reland-3-mv-pipeline
dkwingsmt Feb 23, 2024
f9d5ad3
Merge remote-tracking branch 'dkwingsmt/reland-3-mv-pipeline' into re…
dkwingsmt Feb 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add test
  • Loading branch information
dkwingsmt committed Feb 15, 2024
commit 9c5bce4def4d2feaaf79d6d4d9ae432b78253b1f
217 changes: 217 additions & 0 deletions shell/common/engine_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#include <cstring>

#include "flutter/runtime/dart_vm_lifecycle.h"
#include "flutter/common/constants.h"
#include "flutter/shell/common/shell.h"
#include "flutter/shell/common/shell_test.h"
#include "flutter/shell/common/thread_host.h"
#include "flutter/testing/fixture_test.h"
#include "flutter/testing/testing.h"
Expand All @@ -19,6 +22,19 @@ namespace flutter {

namespace {

using ::testing::Invoke;
using ::testing::ReturnRef;

static void PostSync(const fml::RefPtr<fml::TaskRunner>& task_runner,
const fml::closure& task) {
fml::AutoResetWaitableEvent latch;
fml::TaskRunner::RunNowOrPostTask(task_runner, [&latch, &task] {
task();
latch.Signal();
});
latch.Wait();
}

class MockDelegate : public Engine::Delegate {
public:
MOCK_METHOD(void,
Expand Down Expand Up @@ -118,6 +134,51 @@ class MockRuntimeController : public RuntimeController {
MOCK_METHOD(bool, NotifyIdle, (fml::TimeDelta), (override));
};

class MockAnimatorDelegate : public Animator::Delegate {
public:
/* Animator::Delegate */
MOCK_METHOD(void,
OnAnimatorBeginFrame,
(fml::TimePoint frame_target_time, uint64_t frame_number),
(override));
MOCK_METHOD(void,
OnAnimatorNotifyIdle,
(fml::TimeDelta deadline),
(override));
MOCK_METHOD(void,
OnAnimatorUpdateLatestFrameTargetTime,
(fml::TimePoint frame_target_time),
(override));
MOCK_METHOD(void,
OnAnimatorDraw,
(std::shared_ptr<FramePipeline> pipeline),
(override));
MOCK_METHOD(void,
OnAnimatorDrawLastLayerTrees,
(std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder),
(override));
};

class MockPlatformMessageHandler : public PlatformMessageHandler {
public:
MOCK_METHOD(void,
HandlePlatformMessage,
(std::unique_ptr<PlatformMessage> message),
(override));
MOCK_METHOD(bool,
DoesHandlePlatformMessageOnPlatformThread,
(),
(const, override));
MOCK_METHOD(void,
InvokePlatformMessageResponseCallback,
(int response_id, std::unique_ptr<fml::Mapping> mapping),
(override));
MOCK_METHOD(void,
InvokePlatformMessageEmptyResponseCallback,
(int response_id),
(override));
};

std::unique_ptr<PlatformMessage> MakePlatformMessage(
const std::string& channel,
const std::map<std::string, std::string>& values,
Expand Down Expand Up @@ -186,6 +247,96 @@ class EngineTest : public testing::FixtureTest {
std::shared_ptr<fml::ConcurrentTaskRunner> image_decoder_task_runner_;
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate_;
};

// A class that can launch an Engine with the specified Engine::Delegate.
//
// To use this class, contruct this class with Create, call Run, and use the
// engine with EngineTaskSync().
class EngineContext {
public:
using EngineCallback = std::function<void(Engine&)>;

[[nodiscard]] static std::unique_ptr<EngineContext> Create(
Engine::Delegate& delegate, //
Settings settings, //
const TaskRunners& task_runners, //
std::unique_ptr<Animator> animator) {
auto [vm, isolate_snapshot] = Shell::InferVmInitDataFromSettings(settings);
FML_CHECK(vm) << "Must be able to initialize the VM.";
// Construct the class with `new` because `make_unique` has no access to the
// private constructor.
EngineContext* raw_pointer =
new EngineContext(delegate, settings, task_runners, std::move(animator),
std::move(vm), isolate_snapshot);
return std::unique_ptr<EngineContext>(raw_pointer);
}

void Run(RunConfiguration configuration) {
PostSync(task_runners_.GetUITaskRunner(), [this, &configuration] {
Engine::RunStatus run_status = engine_->Run(std::move(configuration));
FML_CHECK(run_status == Engine::RunStatus::Success)
<< "Engine failed to run.";
(void)run_status; // Suppress unused-variable warning
});
}

// Run a task that operates the Engine on the UI thread, and wait for the
// task to end.
//
// If called on the UI thread, the task is executed synchronously.
void EngineTaskSync(EngineCallback task) {
ASSERT_TRUE(engine_);
ASSERT_TRUE(task);
auto runner = task_runners_.GetUITaskRunner();
if (runner->RunsTasksOnCurrentThread()) {
task(*engine_);
} else {
PostSync(task_runners_.GetUITaskRunner(), [&]() { task(*engine_); });
}
}

~EngineContext() {
PostSync(task_runners_.GetUITaskRunner(), [this] { engine_.reset(); });
}

private:
EngineContext(Engine::Delegate& delegate, //
Settings settings, //
const TaskRunners& task_runners, //
std::unique_ptr<Animator> animator, //
DartVMRef vm, //
fml::RefPtr<const DartSnapshot> isolate_snapshot)
: task_runners_(task_runners), vm_(std::move(vm)) {
PostSync(task_runners.GetUITaskRunner(), [this, &settings, &animator,
&delegate, &isolate_snapshot] {
auto dispatcher_maker =
[](DefaultPointerDataDispatcher::Delegate& delegate) {
return std::make_unique<DefaultPointerDataDispatcher>(delegate);
};
engine_ = std::make_unique<Engine>(
/*delegate=*/delegate,
/*dispatcher_maker=*/dispatcher_maker,
/*vm=*/*&vm_,
/*isolate_snapshot=*/std::move(isolate_snapshot),
/*task_runners=*/task_runners_,
/*platform_data=*/PlatformData(),
/*settings=*/settings,
/*animator=*/std::move(animator),
/*io_manager=*/io_manager_,
/*unref_queue=*/nullptr,
/*snapshot_delegate=*/snapshot_delegate_,
/*volatile_path_tracker=*/nullptr,
/*gpu_disabled_switch=*/std::make_shared<fml::SyncSwitch>());
});
}

TaskRunners task_runners_;
DartVMRef vm_;
std::unique_ptr<Engine> engine_;

fml::WeakPtr<IOManager> io_manager_;
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate_;
};
} // namespace

TEST_F(EngineTest, Create) {
Expand Down Expand Up @@ -419,4 +570,70 @@ TEST_F(EngineTest, PassesLoadDartDeferredLibraryErrorToRuntime) {
});
}

// The animator should submit to the pipeline the implicit view rendered in a
// warm up frame if there's already a continuation (i.e. Animator::BeginFrame
// has been called)
TEST_F(EngineTest, AnimatorSubmitWarmUpImplicitView) {
MockAnimatorDelegate animator_delegate;
std::unique_ptr<EngineContext> engine_context;

std::shared_ptr<PlatformMessageHandler> platform_message_handler =
std::make_shared<MockPlatformMessageHandler>();
EXPECT_CALL(delegate_, GetPlatformMessageHandler)
.WillOnce(ReturnRef(platform_message_handler));

fml::AutoResetWaitableEvent draw_latch;
EXPECT_CALL(animator_delegate, OnAnimatorDraw)
.WillOnce(
Invoke([&draw_latch](const std::shared_ptr<FramePipeline>& pipeline) {
auto status =
pipeline->Consume([&](std::unique_ptr<FrameItem> item) {
EXPECT_EQ(item->layer_tree_tasks.size(), 1u);
EXPECT_EQ(item->layer_tree_tasks[0]->view_id, kFlutterImplicitViewId);
});
EXPECT_EQ(status, PipelineConsumeResult::Done);
draw_latch.Signal();
}));
EXPECT_CALL(animator_delegate, OnAnimatorBeginFrame)
.Times(2)
.WillRepeatedly(Invoke([&engine_context](fml::TimePoint frame_target_time,
uint64_t frame_number) {
engine_context->EngineTaskSync([&](Engine& engine) {
engine.BeginFrame(frame_target_time, frame_number);
});
}));

static fml::AutoResetWaitableEvent continuation_ready_latch;
continuation_ready_latch.Reset();
AddNativeCallback("NotifyNative",
[](auto args) { continuation_ready_latch.Signal(); });

std::unique_ptr<Animator> animator;
PostSync(task_runners_.GetUITaskRunner(),
[&animator, &animator_delegate, &task_runners = task_runners_] {
animator = std::make_unique<Animator>(
animator_delegate, task_runners,
static_cast<std::unique_ptr<VsyncWaiter>>(
std::make_unique<testing::ConstantFiringVsyncWaiter>(
task_runners)));
});

engine_context = EngineContext::Create(delegate_, settings_, task_runners_,
std::move(animator));

auto configuration = RunConfiguration::InferFromSettings(settings_);
configuration.SetEntrypoint("renderWarmUpImplicitViewAfterMetricsChanged");
engine_context->Run(std::move(configuration));

continuation_ready_latch.Wait();


// Set metrics, which notifies the Dart isolate to render the views.
engine_context->EngineTaskSync([](Engine& engine) {
engine.AddView(kFlutterImplicitViewId, ViewportMetrics{});
});

draw_latch.Wait();
}

} // namespace flutter
62 changes: 56 additions & 6 deletions shell/common/fixtures/shell_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert' show utf8, json;
import 'dart:async' show scheduleMicrotask;
import 'dart:convert' show json, utf8;
import 'dart:isolate';
import 'dart:typed_data';
import 'dart:ui';

void expect(Object? a, Object? b) {
if (a != b) {
throw AssertionError('Expected $a to == $b');
}
}

void main() {}

@pragma('vm:entry-point')
Expand Down Expand Up @@ -349,11 +356,6 @@ Future<void> toImageSync() async {

onBeforeToImageSync();
final Image image = picture.toImageSync(20, 25);
void expect(Object? a, Object? b) {
if (a != b) {
throw 'Expected $a to == $b';
}
}
expect(image.width, 20);
expect(image.height, 25);

Expand Down Expand Up @@ -529,3 +531,51 @@ void testReportViewWidths() {
nativeReportViewWidthsCallback(getCurrentViewWidths());
};
}

@pragma('vm:entry-point')
void renderWarmUpImplicitViewAfterMetricsChanged() {
PlatformDispatcher.instance.onBeginFrame = (Duration beginTime) {
// Make sure onBeginFrame and onDrawFrame are not used later.
PlatformDispatcher.instance.onBeginFrame = null;
PlatformDispatcher.instance.onDrawFrame = null;
// Let the test know that a continuation is available.
notifyNative();
};

bool microtaskDone = false;

// As soon as 2 views are added, render these views.
PlatformDispatcher.instance.onMetricsChanged = () {
PlatformDispatcher.instance.scheduleWarmUpFrame(
beginFrame: () {
expect(microtaskDone, false);
scheduleMicrotask(() {
microtaskDone = true;
});
},
drawFrame: () {
// TODO(dkwingsmt): According to the document in
// [ScheduleBinding.scheduleWarmUpFrame], the microtasks should be
// executed between two `Timer`s. It doesn't, at least with the current
// set up. We should examine more closely.
expect(microtaskDone, false);

final SceneBuilder builder = SceneBuilder();
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
canvas.drawPaint(Paint()..color = const Color(0xFFABCDEF));
final Picture picture = recorder.endRecording();
builder.addPicture(Offset.zero, picture);

final Scene scene = builder.build();
PlatformDispatcher.instance.implicitView!.render(scene);

scene.dispose();
picture.dispose();
},
);
};

// Schedule a frame to produce an Animator continuation.
PlatformDispatcher.instance.scheduleFrame();
}