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 7 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
110 changes: 110 additions & 0 deletions lib/ui/channel_buffers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// 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.
Comment thread
dnfield marked this conversation as resolved.
class StoredMessage {
final ByteData _data;
final PlatformMessageResponseCallback _callback;

StoredMessage(this._data, this._callback);
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
ByteData get data => _data;
Comment thread
gaaclarke marked this conversation as resolved.
PlatformMessageResponseCallback get callback => _callback;
Comment thread
gaaclarke marked this conversation as resolved.
}

/// A fixed-size circular queue.

Copy link
Copy Markdown
Contributor

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
Copy Markdown
Contributor 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> {
final collection.ListQueue<T> _queue;
int _capacity;

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

int get length => _queue.length;

int get capacity => _capacity;

bool get isEmpty => _queue.isEmpty;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

doc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Copy Markdown
Contributor

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
Copy Markdown
Contributor 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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yup, this works.

/// Returns true on overflow.
bool push(T val) {
bool overflow = false;
while (_queue.length >= _capacity) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

wouldn't you want to reuse the resize logic here or extract a part of it? There's some repetition here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, it wasn't trivial since the logic was a bit different and didn't buy many lines, but since you asked there you go.

_queue.removeFirst();
overflow = true;
}
_queue.addLast(val);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this work if the capacity is zero? Add test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

return overflow;
}

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

/// Returns the number of discarded items resulting from resize.
int resize(int newSize) {
int result = 0;

while (length > newSize) {
result += 1;
_queue.removeFirst();
}

_capacity = newSize;

return result;
}
}

/// Storage of channel messages until the channels are completely routed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

completely routed upon attaching a channel message handler on the framework side.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

class ChannelBuffers {
Comment thread
gaaclarke marked this conversation as resolved.
/// A somewhat arbitrary size that tries to balance handling typical
/// cases and not wasting memory.
static const int kDefaultBufferSize = 100;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this seems arbitrary. i would expect this to be 0, 1, or infinity.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we're not going to be able to have a 1 size fits all solution. Users instantiating their own channels should be able to specify the caching behavior per channel.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How would a user control this though?

A message could be multiple megabytes of data (e.g. what I did in the scenario app to get the timeline data from dart back to Java). Or it could be a single byte.

If we default this to 1 for now, it would at least solve the "variables" use case - but it would be nice if we can come up with some way for users to say how many messages their channel shoul dbe allowed to buffer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@dnfield I plan resizing for a future PR. I've since moved the default size to 1.


final Map<String, _RingBuffer<StoredMessage>> _messages = {};

/// Returns true on overflow.

Copy link
Copy Markdown
Contributor

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
Copy Markdown
Contributor 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
Copy Markdown
Contributor

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
Copy Markdown
Contributor 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
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
Copy Markdown
Contributor 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
Copy Markdown
Contributor

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 = _RingBuffer<StoredMessage>(kDefaultBufferSize);
_messages[channel] = queue;
}
final bool result = queue.push(StoredMessage(data, callback));
if (result) {
_Logger._printString('Overflow on channel:' + channel);
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
}
return result;
}

/// Returns null on underflow.

Copy link
Copy Markdown
Contributor

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
Copy Markdown
Contributor 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
Copy Markdown
Contributor

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();
if (result == null) {
_Logger._printString('Underflow on channel:' + channel);
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
}
return result;
}

bool isEmpty(String channel) {
final _RingBuffer<StoredMessage> queue = _messages[channel];
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
return queue?.isEmpty ?? true;
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
}

void resize(String channel, int newSize) {

Copy link
Copy Markdown
Contributor

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
Copy Markdown
Contributor 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 = _RingBuffer<StoredMessage>(newSize);
_messages[channel] = queue;
} else {
queue.resize(newSize);
}
}
Comment thread
gaaclarke marked this conversation as resolved.
}

final ChannelBuffers channelBuffers = ChannelBuffers();

Copy link
Copy Markdown
Contributor

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
Copy Markdown
Contributor 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
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

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
Copy Markdown
Contributor

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
Copy Markdown
Contributor 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
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
62 changes: 62 additions & 0 deletions testing/dart/channel_buffers_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'dart:ui' as ui;
import 'dart:typed_data';
import 'dart:convert';

import 'package:test/test.dart';

void main() {
Comment thread
gaaclarke marked this conversation as resolved.

ByteData _makeByteData(String str) {
var list = utf8.encode(str);
var buffer = list is Uint8List ? list.buffer : new Uint8List.fromList(list).buffer;
return ByteData.view(buffer);
}

String _getString(ByteData data) {
final buffer = data.buffer;
var list = buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
return utf8.decode(list);
}

test('push pop', () async {
String channel = "foo";
ByteData data = _makeByteData('bar');
ui.ChannelBuffers buffers = ui.ChannelBuffers();
ui.PlatformMessageResponseCallback callback = (ByteData responseData) {};
buffers.push(channel, data, callback);
ui.StoredMessage storedMessage = buffers.pop(channel);
expect(storedMessage.data, equals(data));
expect(storedMessage.callback, equals(callback));
});

test('empty', () async {
String channel = "foo";
ByteData data = _makeByteData('bar');
ui.ChannelBuffers buffers = ui.ChannelBuffers();
ui.PlatformMessageResponseCallback callback = (ByteData responseData) {};
expect(buffers.isEmpty(channel), equals(true));
buffers.push(channel, data, callback);
expect(buffers.isEmpty(channel), equals(false));
});

test('pop', () async {
ui.ChannelBuffers buffers = ui.ChannelBuffers();
expect(buffers.pop("channel"), equals(null));
});

test('overflow', () async {
String channel = "foo";
ByteData one = _makeByteData('one');
ByteData two = _makeByteData('two');
ByteData three = _makeByteData('three');
ByteData four = _makeByteData('four');
ui.ChannelBuffers buffers = ui.ChannelBuffers();
ui.PlatformMessageResponseCallback callback = (ByteData responseData) {};
buffers.resize(channel, 3);
expect(buffers.push(channel, one, callback), equals(false));
expect(buffers.push(channel, two, callback), equals(false));
expect(buffers.push(channel, three, callback), equals(false));
expect(buffers.push(channel, four, callback), equals(true));
expect(_getString(buffers.pop(channel).data), equals('two'));
});
}
Comment thread
gaaclarke marked this conversation as resolved.
1 change: 1 addition & 0 deletions testing/dart/window_hooks_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import 'dart:typed_data';
import 'package:test/test.dart';

// HACK: these parts are to get access to private functions tested here.
part '../../lib/ui/channel_buffers.dart';
part '../../lib/ui/compositing.dart';
part '../../lib/ui/geometry.dart';
part '../../lib/ui/hash_codes.dart';
Expand Down