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 all commits
Commits
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
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,7 @@ FILE: ../../../flutter/lib/ui/window/platform_message_response.cc
FILE: ../../../flutter/lib/ui/window/platform_message_response.h
FILE: ../../../flutter/lib/ui/window/platform_message_response_dart.cc
FILE: ../../../flutter/lib/ui/window/platform_message_response_dart.h
FILE: ../../../flutter/lib/ui/window/platform_message_response_dart_unittests.cc
FILE: ../../../flutter/lib/ui/window/pointer_data.cc
FILE: ../../../flutter/lib/ui/window/pointer_data.h
FILE: ../../../flutter/lib/ui/window/pointer_data_packet.cc
Expand Down
1 change: 1 addition & 0 deletions lib/ui/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ if (enable_unittests) {
"painting/vertices_unittests.cc",
"semantics/semantics_update_builder_unittests.cc",
"window/platform_configuration_unittests.cc",
"window/platform_message_response_dart_unittests.cc",
"window/pointer_data_packet_converter_unittests.cc",
]

Expand Down
15 changes: 15 additions & 0 deletions lib/ui/fixtures/ui_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,21 @@ void frameCallback(_Image, int) {
print('called back');
}

@pragma('vm:entry-point')
void platformMessageResponseTest() {
_callPlatformMessageResponseDart((ByteData? result) {
if (result is UnmodifiableByteDataView &&
result.lengthInBytes == 100) {
_finishCallResponse(true);
} else {
_finishCallResponse(false);
}
});
}

void _callPlatformMessageResponseDart(void Function(ByteData? result) callback) native 'CallPlatformMessageResponseDart';
void _finishCallResponse(bool didPass) native 'FinishCallResponse';

@pragma('vm:entry-point')
void messageCallback(dynamic data) {}

Expand Down
4 changes: 4 additions & 0 deletions lib/ui/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ const double _kUnsetGestureSetting = -1.0;
// See embedder.cc::kFlutterKeyDataChannel for more information.
const String _kFlutterKeyDataChannel = 'flutter/keydata';

@pragma('vm:entry-point')
ByteData? _wrapUnmodifiableByteData(ByteData? byteData) =>
byteData == null ? null : UnmodifiableByteDataView(byteData);

/// Platform event dispatcher singleton.
///
/// The most basic interface to the host operating system's interface.
Expand Down
39 changes: 34 additions & 5 deletions lib/ui/window/platform_message_response_dart.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ static std::atomic<uint64_t> platform_message_counter = 1;

namespace flutter {
namespace {

void MappingFinalizer(void* isolate_callback_data, void* peer) {
delete static_cast<fml::Mapping*>(peer);
}

template <typename Callback, typename TaskRunner, typename Result>
void PostCompletion(Callback&& callback,
const TaskRunner& ui_task_runner,
Expand Down Expand Up @@ -63,11 +68,35 @@ PlatformMessageResponseDart::~PlatformMessageResponseDart() {
}

void PlatformMessageResponseDart::Complete(std::unique_ptr<fml::Mapping> data) {
PostCompletion(std::move(callback_), ui_task_runner_, &is_complete_, channel_,
[data = std::move(data)] {
return tonic::DartByteData::Create(data->GetMapping(),
data->GetSize());
});
PostCompletion(
std::move(callback_), ui_task_runner_, &is_complete_, channel_,
[data = std::move(data)]() mutable {
Dart_Handle byte_buffer;
intptr_t size = data->GetSize();
if (data->GetSize() > tonic::DartByteData::kExternalSizeThreshold) {
const void* mapping = data->GetMapping();
byte_buffer = Dart_NewUnmodifiableExternalTypedDataWithFinalizer(
/*type=*/Dart_TypedData_kByteData,
/*data=*/mapping,
/*length=*/size,
/*peer=*/data.release(),
/*external_allocation_size=*/size,
/*callback=*/MappingFinalizer);
} else {
Dart_Handle mutable_byte_buffer =
tonic::DartByteData::Create(data->GetMapping(), data->GetSize());
Dart_Handle ui_lib = Dart_LookupLibrary(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DBC

You may be able to speed this up by using the Dart_PersistentHandle for the library that is already cached in the tonic::DartState object in its tonic::DartClassLibrary field. Then you won't have to allocate the "dart:ui" string on every small message.

I'd suggest also caching the string "_wrapUnmodifiableByteData" in a Dart_PersistentHandle instead of allocating it on every small message. Not sure if tonic has a convenience class for that already, but you can always add a field to flutter::UIDartState if there's a speedup.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh yeah allocating these strings does add up if this gets chatty. We store a bunch of things like this in platform_configuration.cc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I considered it but didn't know where to put it. FWIW I don't think the wrapping code had a big impact on the microbenchmark, less than 1% of the round trip time for a message. I expected looking up the library to be expensive too but seemed fast. I'll double check everything this afternoon when i go back and look at the official performance numbers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lookup is fast but the string creation adds up.

tonic::DartConverter<std::string>().ToDart("dart:ui"));
FML_DCHECK(!(Dart_IsNull(ui_lib) || Dart_IsError(ui_lib)));
byte_buffer = Dart_Invoke(ui_lib,
tonic::DartConverter<std::string>().ToDart(
"_wrapUnmodifiableByteData"),
1, &mutable_byte_buffer);
FML_DCHECK(!(Dart_IsNull(byte_buffer) || Dart_IsError(byte_buffer)));
}

return byte_buffer;
});
}

void PlatformMessageResponseDart::CompleteEmpty() {
Expand Down
73 changes: 73 additions & 0 deletions lib/ui/window/platform_message_response_dart_unittests.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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.

#include "flutter/common/task_runners.h"
#include "flutter/fml/mapping.h"
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/lib/ui/window/platform_message_response_dart.h"
#include "flutter/runtime/dart_vm.h"
#include "flutter/shell/common/shell_test.h"
#include "flutter/shell/common/thread_host.h"
#include "flutter/testing/testing.h"

namespace flutter {
namespace testing {

TEST_F(ShellTest, PlatformMessageResponseDart) {
bool did_pass = false;
auto message_latch = std::make_shared<fml::AutoResetWaitableEvent>();
TaskRunners task_runners("test", // label
GetCurrentTaskRunner(), // platform
CreateNewThread(), // raster
CreateNewThread(), // ui
CreateNewThread() // io
);

auto nativeCallPlatformMessageResponseDart =
[ui_task_runner =
task_runners.GetUITaskRunner()](Dart_NativeArguments args) {
auto dart_state = std::make_shared<tonic::DartState>();
auto response = fml::MakeRefCounted<PlatformMessageResponseDart>(
tonic::DartPersistentValue(tonic::DartState::Current(),
Dart_GetNativeArgument(args, 0)),
ui_task_runner, "foobar");
uint8_t* data = static_cast<uint8_t*>(malloc(100));
auto mapping = std::make_unique<fml::MallocMapping>(data, 100);
response->Complete(std::move(mapping));
};

AddNativeCallback("CallPlatformMessageResponseDart",
CREATE_NATIVE_ENTRY(nativeCallPlatformMessageResponseDart));

auto nativeFinishCallResponse = [message_latch,
&did_pass](Dart_NativeArguments args) {
did_pass =
tonic::DartConverter<bool>::FromDart(Dart_GetNativeArgument(args, 0));
message_latch->Signal();
};

AddNativeCallback("FinishCallResponse",
CREATE_NATIVE_ENTRY(nativeFinishCallResponse));

Settings settings = CreateSettingsForFixture();

std::unique_ptr<Shell> shell =
CreateShell(std::move(settings), std::move(task_runners));

ASSERT_TRUE(shell->IsSetup());
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("platformMessageResponseTest");

shell->RunEngine(std::move(configuration), [](auto result) {
ASSERT_EQ(result, Engine::RunStatus::Success);
});

message_latch->Wait();

ASSERT_TRUE(did_pass);
DestroyShell(std::move(shell), std::move(task_runners));
}

} // namespace testing
} // namespace flutter
8 changes: 4 additions & 4 deletions third_party/tonic/typed_data/dart_byte_data.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ namespace tonic {

namespace {

// For large objects it is more efficient to use an external typed data object
// with a buffer allocated outside the Dart heap.
const int kExternalSizeThreshold = 1000;

void FreeFinalizer(void* isolate_callback_data, void* peer) {
free(peer);
}

} // anonymous namespace

// For large objects it is more efficient to use an external typed data object
// with a buffer allocated outside the Dart heap.
const size_t DartByteData::kExternalSizeThreshold = 1000;

Dart_Handle DartByteData::Create(const void* data, size_t length) {
if (length < kExternalSizeThreshold) {
auto handle = DartByteData{data, length}.dart_handle();
Expand Down
1 change: 1 addition & 0 deletions third_party/tonic/typed_data/dart_byte_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace tonic {

class DartByteData {
public:
static const size_t kExternalSizeThreshold;
static Dart_Handle Create(const void* data, size_t length);

explicit DartByteData(Dart_Handle list);
Expand Down