Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Channel buffers #12167

Merged
merged 47 commits into from
Sep 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
12c4f89
Added channel buffers to ui so that messages have a place to
gaaclarke Sep 9, 2019
8a8343d
Added the RingBuffer class.
gaaclarke Sep 9, 2019
cff180e
Moved over to using the ring buffer.
gaaclarke Sep 10, 2019
31a845b
Did a bit of cleanup.
gaaclarke Sep 10, 2019
c28999d
Removed trailing space.
gaaclarke Sep 10, 2019
4253c1c
Made RingBuffer private.
gaaclarke Sep 10, 2019
e19f59c
Fixed formatting and documenation on default ringbuffer size.
gaaclarke Sep 10, 2019
63f7b8a
Updated the warning messages.
gaaclarke Sep 10, 2019
4e87cb0
Expanded on docstring.
gaaclarke Sep 10, 2019
24607ab
Updated constructor location and docstring.
gaaclarke Sep 10, 2019
c4bec51
Added extra test for resizing.
gaaclarke Sep 10, 2019
ea3823f
Switched to ternary operator since it's more clear.
gaaclarke Sep 10, 2019
afc09b6
added type annotation to literal
gaaclarke Sep 10, 2019
0a3c609
added new file to licenses_flutter
gaaclarke Sep 10, 2019
9df8b23
added channel buffers to the web ui.dart
gaaclarke Sep 10, 2019
f32af48
added channel_buffers to web_ui
gaaclarke Sep 10, 2019
97be8ca
For web_ui, replaced ListQueue with Queue and removed log statements.
gaaclarke Sep 10, 2019
d041d62
fixed collections reference
gaaclarke Sep 10, 2019
1533428
Added web_ui file to licenses_flutter
gaaclarke Sep 10, 2019
42c742d
fixed web_ui "part of" declaration
gaaclarke Sep 10, 2019
652a822
Added docstrings.
gaaclarke Sep 12, 2019
4d84d1b
update docstring
gaaclarke Sep 12, 2019
33c21e9
Reduced the exposed surface of the mechanism so it's just one function,
gaaclarke Sep 12, 2019
afa7bb0
Made ChannelBuffers private too.
gaaclarke Sep 12, 2019
6b6dbb9
Updated tests, made ChannelBuffers public and put the drain method on…
gaaclarke Sep 12, 2019
bcc5ea7
Made it such that the callback for messages gets called even if they get
gaaclarke Sep 12, 2019
3ff518c
Moved the default queue size to 1.
gaaclarke Sep 12, 2019
8d4a1e4
fixed formatting in docstring
gaaclarke Sep 12, 2019
8e6ee3b
addressed linter errors
gaaclarke Sep 13, 2019
3e3c38d
removed return type from setter
gaaclarke Sep 13, 2019
a4204c9
Fixed tests that started failing after the buffer size was set to 1.
gaaclarke Sep 13, 2019
1527c3b
Added todo
gaaclarke Sep 13, 2019
837ed7e
Made the web_ui call the callback (behave like size zero for everythi…
gaaclarke Sep 13, 2019
23a9ce9
removed newline
gaaclarke Sep 13, 2019
007edb1
Merge branch 'master' into channel-buffers
gaaclarke Sep 17, 2019
741981b
added some docstrings, removed duplication
gaaclarke Sep 17, 2019
571594e
added test for zero size
gaaclarke Sep 17, 2019
478520f
added docstring
gaaclarke Sep 17, 2019
8569f21
update docstring and error message
gaaclarke Sep 17, 2019
96761f8
added docstringg
gaaclarke Sep 17, 2019
adcd641
Updated web_ui documentation
gaaclarke Sep 17, 2019
24cf927
shuffled around docstrings to collapse the setter and the ivar.
gaaclarke Sep 17, 2019
67aecdb
added more documentation
gaaclarke Sep 17, 2019
b13a3dc
updated variable name, tweaked log message
gaaclarke Sep 17, 2019
282bff0
updated docstrings
gaaclarke Sep 17, 2019
a0a4c0e
Made it so that channel buffer's error messages are only
gaaclarke Sep 17, 2019
26d9deb
fixed formatting of c++ code
gaaclarke Sep 17, 2019
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
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ FILE: ../../../flutter/lib/io/dart_io.cc
FILE: ../../../flutter/lib/io/dart_io.h
FILE: ../../../flutter/lib/snapshot/libraries.json
FILE: ../../../flutter/lib/snapshot/snapshot.h
FILE: ../../../flutter/lib/ui/channel_buffers.dart
FILE: ../../../flutter/lib/ui/compositing.dart
FILE: ../../../flutter/lib/ui/compositing/scene.cc
FILE: ../../../flutter/lib/ui/compositing/scene.h
Expand Down Expand Up @@ -427,6 +428,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/validators.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/vector_math.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/window.dart
FILE: ../../../flutter/lib/web_ui/lib/src/ui/canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/ui/channel_buffers.dart
FILE: ../../../flutter/lib/web_ui/lib/src/ui/compositing.dart
FILE: ../../../flutter/lib/web_ui/lib/src/ui/geometry.dart
FILE: ../../../flutter/lib/web_ui/lib/src/ui/hash_codes.dart
Expand Down
196 changes: 196 additions & 0 deletions lib/ui/channel_buffers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// 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.

part of dart.ui;

/// A saved platform message for a channel with its callback.
class _StoredMessage {
/// Default constructor, takes in a [ByteData] that represents the
/// payload of the message and a [PlatformMessageResponseCallback]
/// that represents the callback that will be called when the message
/// is handled.
_StoredMessage(this._data, this._callback);

/// Representation of the message's payload.
final ByteData _data;
ByteData get data => _data;

/// Callback to be called when the message is received.
final PlatformMessageResponseCallback _callback;
PlatformMessageResponseCallback get callback => _callback;
}

/// A fixed-size circular queue.
Copy link
Member

Choose a reason for hiding this comment

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

A per channel, fixed-size circular queue.

Copy link
Member Author

Choose a reason for hiding this comment

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

There isn't anything about the implementation that requires this to be used for channels. It's a generic RingBuffer that could be used anywhere.

class _RingBuffer<T> {
/// The underlying data for the RingBuffer. ListQueue's dynamically resize,
/// [_RingBuffer]s do not.
final collection.ListQueue<T> _queue;

_RingBuffer(this._capacity)
: _queue = collection.ListQueue<T>(_capacity);

/// Returns the number of items in the [_RingBuffer].
int get length => _queue.length;

/// The number of items that can be stored in the [_RingBuffer].
int _capacity;
int get capacity => _capacity;

/// Returns true if there are no items in the [_RingBuffer].
bool get isEmpty => _queue.isEmpty;

Copy link
Member

Choose a reason for hiding this comment

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

doc

Copy link
Member Author

Choose a reason for hiding this comment

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

done

Copy link
Member

Choose a reason for hiding this comment

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

This doesn't say anything :) https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#avoid-useless-documentation

Something like Add a callback when previously queued messages are being dropped due to overflow. The message instance is returned in the callback..

Copy link
Member Author

Choose a reason for hiding this comment

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

That seemed redundant since the docstring for _dropItemCallback says all of that. The intent was to point them to that documentation and not repeat it.

I found examples elsewhere and they put the ivar next to the setter and getter and it gets collapsed in the generated documentation so I don't have to repeat it, so done

Copy link
Member

Choose a reason for hiding this comment

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

Yup, this works.

/// A callback that get's called when items are ejected from the [_RingBuffer]
/// by way of an overflow or a resizing.
Function(T) _dropItemCallback;
set dropItemCallback(Function(T) callback) {
_dropItemCallback = callback;
}

/// Returns true on overflow.
bool push(T val) {
if (_capacity <= 0) {
return true;
} else {
final int overflowCount = _dropOverflowItems(_capacity - 1);
_queue.addLast(val);
return overflowCount > 0;
}
}

/// Returns null when empty.
T pop() {
return _queue.isEmpty ? null : _queue.removeFirst();
}

/// Removes items until then length reaches [lengthLimit] and returns
/// the number of items removed.
int _dropOverflowItems(int lengthLimit) {
int result = 0;
while (_queue.length > lengthLimit) {
final T item = _queue.removeFirst();
if (_dropItemCallback != null) {
_dropItemCallback(item);
}
result += 1;
}
return result;
}

/// Returns the number of discarded items resulting from resize.
int resize(int newSize) {
_capacity = newSize;
return _dropOverflowItems(newSize);
}
}

/// Signature for [ChannelBuffers.drain].
typedef DrainChannelCallback = Future<void> Function(ByteData, PlatformMessageResponseCallback);
Copy link
Member

Choose a reason for hiding this comment

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

Doc. Who is supposed to use it. Use See also: for references.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.


/// Storage of channel messages until the channels are completely routed,
/// i.e. when a message handler is attached to the channel on the framework side.
///
/// Each channel has a finite buffer capacity and in a FIFO manner messages will
/// be deleted if the capacity is exceeded. The intention is that these buffers
/// will be drained once a callback is setup on the BinaryMessenger in the
Copy link
Member

Choose a reason for hiding this comment

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

This should be once a callback is setup per channel on the BinaryMessenger now right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, the companion PR needs to land as well for that to be true.

/// Flutter framework.
Copy link
Member

Choose a reason for hiding this comment

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

Also document how this should be used. Should users instantiate these things themselves? Who needs to hook these up? Or is everything already done implicitly. If so, reference those classes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

///
/// Clients of Flutter shouldn't need to allocate their own ChannelBuffers
/// and should only access this package's [channelBuffers] if they are writing
/// their own custom [BinaryMessenger].
class ChannelBuffers {
/// By default we store one message per channel. There are tradeoffs associated
/// with any size. The correct size should be chosen for the semantics of your
/// channel.
///
/// Size 0 implies you want to ignore any message that gets sent before the engine
/// is ready (keeping in mind there is no way to know when the engine is ready).
///
/// Size 1 implies that you only care about the most recent value.
///
/// Size >1 means you want to process every single message and want to chose a
/// buffer size that will avoid any overflows.
static const int kDefaultBufferSize = 1;

/// A mapping between a channel name and its associated [_RingBuffer].
final Map<String, _RingBuffer<_StoredMessage>> _messages =
<String, _RingBuffer<_StoredMessage>>{};

_RingBuffer<_StoredMessage> _makeRingBuffer(int size) {
final _RingBuffer<_StoredMessage> result = _RingBuffer<_StoredMessage>(size);
result.dropItemCallback = _onDropItem;
return result;
}

void _onDropItem(_StoredMessage message) {
message.callback(null);
}

/// Returns true on overflow.
Copy link
Member

Choose a reason for hiding this comment

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

Each field of a dart class has its own dartdoc page. For example https://api.flutter.dev/flutter/dart-ui/Offset/infinite-constant.html

It needs to be sufficiently complete to stand on its own.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can't see what you are referring to in this comment.

Copy link
Member

Choose a reason for hiding this comment

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

I meant each field's doc needs to fully describe what they do, when they're used etc without the context of any surrounding fields' docs or the class doc since they appear independently when we generate dartdoc pages

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I added documentation even on private fields.

Copy link
Member

Choose a reason for hiding this comment

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

I didn't mean adding documentations on private fields. I just meant each public field of public classes (appearing in their own URL with their own page in the API doc website) should fully describe themselves.

Consider, as example, seeing https://screenshot.googleplex.com/K4iw8SwzUuE vs https://screenshot.googleplex.com/QxCLRcoZFqY

bool push(String channel, ByteData data, PlatformMessageResponseCallback callback) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this API futureproof enough? When each channel can specify its own cache size, how would it express it? I assume via the channel constructor. When it does so, how does it pass that size int into this signature? Would we need to leak the implementation detail of this class's implementation into the channel class implementation (by forcing it to come call ChannelBuffers.resize)?

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe so. For basic channels the constructor will call ui.channelBuffers.resize(_name, size) . For method channels they will call ui.channelBuffers.resize(_name + '/' + method, size) each time they encounter a new method, not in the constructor.

This comment is on the "push" method, I'm guessing it was suppose to be somewhere else.

Copy link
Member

Choose a reason for hiding this comment

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

Ah ok, SG

_RingBuffer<_StoredMessage> queue = _messages[channel];
if (queue == null) {
queue = _makeRingBuffer(kDefaultBufferSize);
_messages[channel] = queue;
}
final bool didOverflow = queue.push(_StoredMessage(data, callback));
if (didOverflow) {
// TODO(aaclarke): Update this message to include instructions on how to resize
// the buffer once that is available to users and print in all engine builds
// after we verify that dropping messages isn't part of normal execution.
_printDebug('Overflow on channel: $channel. '
'Messages on this channel are being discarded in FIFO fashion. '
'The engine may not be running or you need to adjust '
'the buffer size if of the channel.');
}
return didOverflow;
}

/// Returns null on underflow.
Copy link
Member

Choose a reason for hiding this comment

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

When would this happen? Wouldn't consumers check isEmpty first?

Copy link
Member Author

Choose a reason for hiding this comment

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

They should be calling isEmpty. You know how it is, troublemakers don't follow the protocol.

Copy link
Member

Choose a reason for hiding this comment

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

dem delinquent kids these days

_StoredMessage _pop(String channel) {
final _RingBuffer<_StoredMessage> queue = _messages[channel];
final _StoredMessage result = queue?.pop();
return result;
}

bool _isEmpty(String channel) {
final _RingBuffer<_StoredMessage> queue = _messages[channel];
return (queue == null) ? true : queue.isEmpty;
}

/// Changes the capacity of the queue associated with the given channel.
///
/// This could result in the dropping of messages if newSize is less
/// than the current length of the queue.
void resize(String channel, int newSize) {
Copy link
Member

Choose a reason for hiding this comment

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

All public Dart classes and fields need dartdocs.

Copy link
Member Author

Choose a reason for hiding this comment

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

done.

_RingBuffer<_StoredMessage> queue = _messages[channel];
if (queue == null) {
queue = _makeRingBuffer(newSize);
_messages[channel] = queue;
} else {
final int numberOfDroppedMessages = queue.resize(newSize);
if (numberOfDroppedMessages > 0) {
_Logger._printString('Dropping messages on channel "$channel" as a result of shrinking the buffer size.');
}
}
}

/// Remove and process all stored messages for a given channel.
///
/// This should be called once a channel is prepared to handle messages
/// (i.e. when a message handler is setup in the framework).
Future<void> drain(String channel, DrainChannelCallback callback) async {
while (!_isEmpty(channel)) {
final _StoredMessage message = _pop(channel);
await callback(message.data, message.callback);
}
}
}

/// [ChannelBuffer]s that allow the storage of messages between the
/// Engine and the Framework. Typically messages that can't be delivered
/// are stored here until the Framework is able to process them.
///
/// See also:
/// * [BinaryMessenger] - The place where ChannelBuffers are typically read.
final ChannelBuffers channelBuffers = ChannelBuffers();
Copy link
Member

Choose a reason for hiding this comment

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

This should probably not be a static but owned by someone. When there are multiple engines, each should (via some chain of ownership) end up with a different instance of buffers.

Copy link
Member Author

Choose a reason for hiding this comment

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

Won't each engine have its own isolate and therefore its own channelBuffers? If this is true this will also be a problem for ui.window which I followed.

Copy link
Member

Choose a reason for hiding this comment

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

Yup you're right, that was a nonsensical comment :D

Copy link
Member

Choose a reason for hiding this comment

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

Still needs a doc (for public fields)

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

17 changes: 12 additions & 5 deletions lib/ui/dart_runtime_hooks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ namespace flutter {
#define DECLARE_FUNCTION(name, count) \
extern void name(Dart_NativeArguments args);

#define BUILTIN_NATIVE_LIST(V) \
V(Logger_PrintString, 1) \
V(SaveCompilationTrace, 0) \
V(ScheduleMicrotask, 1) \
V(GetCallbackHandle, 1) \
#define BUILTIN_NATIVE_LIST(V) \
V(Logger_PrintString, 1) \
V(Logger_PrintDebugString, 1) \
V(SaveCompilationTrace, 0) \
V(ScheduleMicrotask, 1) \
V(GetCallbackHandle, 1) \
V(GetCallbackFromHandle, 1)

BUILTIN_NATIVE_LIST(DECLARE_FUNCTION);
Expand Down Expand Up @@ -152,6 +153,12 @@ void DartRuntimeHooks::Install(bool is_ui_isolate,
InitDartIO(builtin, script_uri);
}

void Logger_PrintDebugString(Dart_NativeArguments args) {
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
Logger_PrintString(args);
#endif
}

// Implementation of native functions which are used for some
// test/debug functionality in standalone dart mode.
void Logger_PrintString(Dart_NativeArguments args) {
Expand Down
1 change: 1 addition & 0 deletions lib/ui/dart_ui.gni
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# found in the LICENSE file.

dart_ui_files = [
"$flutter_root/lib/ui/channel_buffers.dart",
"$flutter_root/lib/ui/compositing.dart",
"$flutter_root/lib/ui/geometry.dart",
"$flutter_root/lib/ui/hash_codes.dart",
Expand Down
4 changes: 3 additions & 1 deletion lib/ui/hooks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ void _dispatchPlatformMessage(String name, ByteData data, int responseId) {
},
);
} else {
window._respondToPlatformMessage(responseId, null);
channelBuffers.push(name, data, (ByteData responseData) {
Copy link
Member

Choose a reason for hiding this comment

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

Related to the comment on the other PR. I think there are 2 lifecycles and we can't really conflate them. There's setting the onPlatformMessage which happens once per binding, and there's adding per channel handlers which happens at arbitrary time.

Once the onPlatformMessage is attached, all buffers will stop queueing. But there are no guarantee that the following sequence of actions won't happen.

1- run entrypoint, entrypoint runApps, which attaches window.onPlatformMessage.
2- engine sends a message on some channel. It hits _DefaultBinaryMessenger. _handlers for that channel is null. Nothing happens. It's also not queued since onPlatformMessage is not null.
3- framework side code creates that channel and attaches the setMessageHandler for that channel. Nothing happens.

It probably works now since the synchronous execution of runApp+binding constructor+our system channels setting handler all happens at the same time. But we can't guarantee that for users' own channels.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've addressed this in the other PR. Both cases get queued up now.

window._respondToPlatformMessage(responseId, responseData);
});
}
}

Expand Down
5 changes: 5 additions & 0 deletions lib/ui/natives.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ void _print(dynamic arg) {
_Logger._printString(arg.toString());
}

void _printDebug(dynamic arg) {
_Logger._printDebugString(arg.toString());
}

class _Logger {
static void _printString(String s) native 'Logger_PrintString';
static void _printDebugString(String s) native 'Logger_PrintDebugString';
}

// If we actually run on big endian machines, we'll need to do something smarter
Expand Down
1 change: 1 addition & 0 deletions lib/ui/ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import 'dart:math' as math;
import 'dart:nativewrappers';
import 'dart:typed_data';

part 'channel_buffers.dart';
part 'compositing.dart';
part 'geometry.dart';
part 'hash_codes.dart';
Expand Down
29 changes: 29 additions & 0 deletions lib/web_ui/lib/src/ui/channel_buffers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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.

part of ui;

/// A callback for [ChannelBuffers.drain], called as it pops stored messages.
typedef DrainChannelCallback = Future<void> Function(ByteData, PlatformMessageResponseCallback);
Copy link
Member

Choose a reason for hiding this comment

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

dartdoc

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure what you are referring to. I updated the docstring to match the ones found in lib/ui/window.dart


/// Web implementation of [ChannelBuffers]. Currently it just drops all messages
/// to match legacy behavior and acts as if all caches are size zero.
class ChannelBuffers {
/// Always returns true to denote an overflow.
bool push(String channel, ByteData data, PlatformMessageResponseCallback callback) {
callback(null);
return true;
}

/// Noop in web_ui, caches are always size zero.
void resize(String channel, int newSize) {}

/// Remove and process all stored messages for a given channel.
///
/// A noop in web_ui since all caches are size zero.
Future<void> drain(String channel, DrainChannelCallback callback) async {
}
}

final ChannelBuffers channelBuffers = ChannelBuffers();
1 change: 1 addition & 0 deletions lib/web_ui/lib/ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export 'src/engine.dart'
webOnlyInitializeEngine;

part 'src/ui/canvas.dart';
part 'src/ui/channel_buffers.dart';
part 'src/ui/compositing.dart';
part 'src/ui/geometry.dart';
part 'src/ui/hash_codes.dart';
Expand Down
Loading