Skip to content
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
6 changes: 6 additions & 0 deletions packages/pigeon/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 24.2.0
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be a breaking change couldn't it? I know that the old behavior was essentially just "wrong", but this could "break" what used to be unintentionally correct

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The old behavior had undefined ordering, and one of the possible orderings was the one that this will guarantee. So any code that reliably behaved correctly still will, because this is one of the orderings it must have handled.


* Adjusts task queues to use a shared task queue for all methods in a single
API instance, to give the same ordering guarantees as non-task-queue usage.
* [swift] Adds task queue support to the Swift generator.

## 24.1.1

* [swift, kotlin] Adds an error message when a ProxyAPI callback method that returns a non-null
Expand Down
2 changes: 1 addition & 1 deletion packages/pigeon/lib/src/generator_tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import 'ast.dart';
/// The current version of pigeon.
///
/// This must match the version in pubspec.yaml.
const String pigeonVersion = '24.1.1';
const String pigeonVersion = '24.2.0';

/// Read all the content from [stdin] to a String.
String readStdin() {
Expand Down
41 changes: 20 additions & 21 deletions packages/pigeon/lib/src/java/java_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import '../ast.dart';
import '../functional.dart';
import '../generator.dart';
import '../generator_tools.dart';
import '../pigeon_lib.dart' show TaskQueueType;
import '../types/task_queue.dart';

/// Documentation open symbol.
const String _docCommentPrefix = '/**';
Expand Down Expand Up @@ -835,15 +835,21 @@ if (wrapped == null) {
indent.addScoped('{', '}', () {
indent.writeln(
'messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix;');
String? serialBackgroundQueue;
if (api.methods.any((Method m) =>
m.taskQueueType == TaskQueueType.serialBackgroundThread)) {
serialBackgroundQueue = 'taskQueue';
indent.writeln(
'BinaryMessenger.TaskQueue $serialBackgroundQueue = binaryMessenger.makeBackgroundTaskQueue();');
}
for (final Method method in api.methods) {
_writeMethodSetUp(
generatorOptions,
root,
indent,
api,
method,
dartPackageName: dartPackageName,
);
_writeHostMethodMessageHandler(
generatorOptions, root, indent, api, method,
dartPackageName: dartPackageName,
serialBackgroundQueue:
method.taskQueueType == TaskQueueType.serialBackgroundThread
? serialBackgroundQueue
: null);
}
});
});
Expand Down Expand Up @@ -887,34 +893,27 @@ if (wrapped == null) {
indent.writeln('$returnType ${method.name}(${argSignature.join(', ')});');
}

/// Write a static setUp function in the interface.
/// Example:
/// static void setUp(BinaryMessenger binaryMessenger, Foo api) {...}
void _writeMethodSetUp(
/// Write a single method's handler for the setUp function.
void _writeHostMethodMessageHandler(
JavaOptions generatorOptions,
Root root,
Indent indent,
Api api,
final Method method, {
required String dartPackageName,
String? serialBackgroundQueue,
}) {
final String channelName = makeChannelName(api, method, dartPackageName);
indent.write('');
indent.addScoped('{', '}', () {
String? taskQueue;
if (method.taskQueueType != TaskQueueType.serial) {
taskQueue = 'taskQueue';
indent.writeln(
'BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue();');
}
indent.writeln('BasicMessageChannel<Object> channel =');
indent.nest(2, () {
indent.writeln('new BasicMessageChannel<>(');
indent.nest(2, () {
indent.write(
'binaryMessenger, "$channelName" + messageChannelSuffix, getCodec()');
if (taskQueue != null) {
indent.addln(', $taskQueue);');
if (serialBackgroundQueue != null) {
indent.addln(', $serialBackgroundQueue);');
} else {
indent.addln(');');
}
Expand Down
29 changes: 17 additions & 12 deletions packages/pigeon/lib/src/kotlin/kotlin_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import '../ast.dart';
import '../functional.dart';
import '../generator.dart';
import '../generator_tools.dart';
import '../pigeon_lib.dart' show TaskQueueType;
import '../types/task_queue.dart';
import 'templates.dart';

/// Documentation open symbol.
Expand Down Expand Up @@ -654,6 +654,13 @@ if (wrapped == null) {
indent.addScoped('{', '}', () {
indent.writeln(
r'val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""');
String? serialBackgroundQueue;
if (api.methods.any((Method m) =>
m.taskQueueType == TaskQueueType.serialBackgroundThread)) {
serialBackgroundQueue = 'taskQueue';
indent.writeln(
'val $serialBackgroundQueue = binaryMessenger.makeBackgroundTaskQueue()');
}
for (final Method method in api.methods) {
_writeHostMethodMessageHandler(
indent,
Expand All @@ -664,6 +671,10 @@ if (wrapped == null) {
parameters: method.parameters,
returnType: method.returnType,
isAsynchronous: method.isAsynchronous,
serialBackgroundQueue:
method.taskQueueType == TaskQueueType.serialBackgroundThread
? serialBackgroundQueue
: null,
);
}
});
Expand Down Expand Up @@ -1071,8 +1082,8 @@ if (wrapped == null) {
fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
sink.error(errorCode, errorMessage, errorDetails)
}
fun endOfStream() {

fun endOfStream() {
sink.endOfStream()
}
}
Expand Down Expand Up @@ -1249,24 +1260,18 @@ if (wrapped == null) {
required TypeDeclaration returnType,
String setHandlerCondition = 'api != null',
bool isAsynchronous = false,
String? serialBackgroundQueue,
String Function(List<String> safeArgNames, {required String apiVarName})?
onCreateCall,
}) {
indent.write('run ');
indent.addScoped('{', '}', () {
String? taskQueue;
if (taskQueueType != TaskQueueType.serial) {
taskQueue = 'taskQueue';
indent.writeln(
'val $taskQueue = binaryMessenger.makeBackgroundTaskQueue()');
}

indent.write(
'val channel = BasicMessageChannel<Any?>(binaryMessenger, "$channelName", codec',
);

if (taskQueue != null) {
indent.addln(', $taskQueue)');
if (serialBackgroundQueue != null) {
indent.addln(', $serialBackgroundQueue)');
} else {
indent.addln(')');
}
Expand Down
36 changes: 27 additions & 9 deletions packages/pigeon/lib/src/objc/objc_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import '../ast.dart';
import '../functional.dart';
import '../generator.dart';
import '../generator_tools.dart';
import '../pigeon_lib.dart' show Error, TaskQueueType;
import '../pigeon_lib.dart' show Error;
import '../types/task_queue.dart';

/// Documentation comment open symbol.
const String _docCommentPrefix = '///';
Expand Down Expand Up @@ -860,24 +861,33 @@ if (self.wrapped == nil) {
indent.addScoped('{', '}', () {
indent.writeln(
'messageChannelSuffix = messageChannelSuffix.length > 0 ? [NSString stringWithFormat: @".%@", messageChannelSuffix] : @"";');
String? serialBackgroundQueue;
if (api.methods.any((Method m) =>
m.taskQueueType == TaskQueueType.serialBackgroundThread)) {
serialBackgroundQueue = 'taskQueue';
// See https://github.com/flutter/flutter/issues/162613 for why this
// is an ifdef instead of just a respondsToSelector: check.
indent.format('''
#if TARGET_OS_IOS
NSObject<FlutterTaskQueue> *$serialBackgroundQueue = [binaryMessenger makeBackgroundTaskQueue];
#else
NSObject<FlutterTaskQueue> *$serialBackgroundQueue = nil;
#endif''');
}
for (final Method func in api.methods) {
addDocumentationComments(
indent, func.documentationComments, _docCommentSpec);

indent.writeScoped('{', '}', () {
String? taskQueue;
if (func.taskQueueType != TaskQueueType.serial) {
taskQueue = 'taskQueue';
indent.writeln(
'NSObject<FlutterTaskQueue> *$taskQueue = [binaryMessenger makeBackgroundTaskQueue];');
}
_writeChannelAllocation(
generatorOptions,
indent,
api,
func,
channelName,
taskQueue,
func.taskQueueType == TaskQueueType.serialBackgroundThread
? serialBackgroundQueue
: null,
dartPackageName: dartPackageName,
);
indent.write('if (api) ');
Expand Down Expand Up @@ -1112,7 +1122,15 @@ static FlutterError *createConnectionError(NSString *channelName) {

if (taskQueue != null) {
indent.newln();
indent.addln('taskQueue:$taskQueue];');
// See https://github.com/flutter/flutter/issues/162613 for why this
// is in an ifdef instead of just relying on the parameter being
// nullable.
indent.format('''
#ifdef TARGET_OS_IOS
taskQueue:$taskQueue
#endif
];
''');
} else {
indent.addln('];');
}
Expand Down
18 changes: 3 additions & 15 deletions packages/pigeon/lib/src/pigeon_lib.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import 'java/java_generator.dart';
import 'kotlin/kotlin_generator.dart';
import 'objc/objc_generator.dart';
import 'swift/swift_generator.dart';
import 'types/task_queue.dart';

export 'types/task_queue.dart' show TaskQueueType;

class _Asynchronous {
const _Asynchronous();
Expand Down Expand Up @@ -211,21 +214,6 @@ class SwiftClass {
const SwiftClass();
}

/// Type of TaskQueue which determines how handlers are dispatched for
/// HostApi's.
enum TaskQueueType {
/// Handlers are invoked serially on the default thread. This is the value if
/// unspecified.
serial,

/// Handlers are invoked serially on a background thread.
serialBackgroundThread,

// TODO(gaaclarke): Add support for concurrent task queues.
// /// Handlers are invoked concurrently on a background thread.
// concurrentBackgroundThread,
}

/// Metadata annotation to control how handlers are dispatched for HostApi's.
/// Note that the TaskQueue API might not be available on the target version of
/// Flutter, see also:
Expand Down
51 changes: 47 additions & 4 deletions packages/pigeon/lib/src/swift/swift_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import '../ast.dart';
import '../functional.dart';
import '../generator.dart';
import '../generator_tools.dart';
import '../types/task_queue.dart';
import 'templates.dart';

/// Documentation comment open symbol.
Expand Down Expand Up @@ -736,6 +737,21 @@ if (wrapped == nil) {
indent.addScoped('{', '}', () {
indent.writeln(
r'let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""');
String? serialBackgroundQueue;
if (api.methods.any((Method m) =>
m.taskQueueType == TaskQueueType.serialBackgroundThread)) {
serialBackgroundQueue = 'taskQueue';
// TODO(stuartmorgan): Remove the ? once macOS supports task queues
// and this is no longer an optional protocol method.
// See https://github.com/flutter/flutter/issues/162613 for why this
// is an ifdef instead of just relying on the optionality check.
indent.format('''
#if os(iOS)
let $serialBackgroundQueue = binaryMessenger.makeBackgroundTaskQueue?()
#else
let $serialBackgroundQueue: FlutterTaskQueue? = nil
#endif''');
}
for (final Method method in api.methods) {
_writeHostMethodMessageHandler(
indent,
Expand All @@ -747,6 +763,10 @@ if (wrapped == nil) {
isAsynchronous: method.isAsynchronous,
swiftFunction: method.swiftFunction,
documentationComments: method.documentationComments,
serialBackgroundQueue:
method.taskQueueType == TaskQueueType.serialBackgroundThread
? serialBackgroundQueue
: null,
);
}
});
Expand Down Expand Up @@ -1404,7 +1424,7 @@ private func nilOrValue<T>(_ value: Any?) -> T? {
func endOfStream() {
sink(FlutterEndOfEventStream)
}

}
''');
}
Expand All @@ -1413,7 +1433,7 @@ private func nilOrValue<T>(_ value: Any?) -> T? {
for (final Method func in api.methods) {
indent.format('''
class ${toUpperCamelCase(func.name)}StreamHandler: PigeonEventChannelWrapper<${_swiftTypeForDartType(func.returnType)}> {
static func register(with messenger: FlutterBinaryMessenger,
static func register(with messenger: FlutterBinaryMessenger,
instanceName: String = "",
streamHandler: ${toUpperCamelCase(func.name)}StreamHandler) {
var channelName = "${makeChannelName(api, func, dartPackageName)}"
Expand Down Expand Up @@ -1535,6 +1555,7 @@ private func nilOrValue<T>(_ value: Any?) -> T? {
required TypeDeclaration returnType,
required bool isAsynchronous,
required String? swiftFunction,
String? serialBackgroundQueue,
String setHandlerCondition = 'let api = api',
List<String> documentationComments = const <String>[],
String Function(List<String> safeArgNames, {required String apiVarName})?
Expand All @@ -1549,8 +1570,30 @@ private func nilOrValue<T>(_ value: Any?) -> T? {

final String varChannelName = '${name}Channel';
addDocumentationComments(indent, documentationComments, _docCommentSpec);
indent.writeln(
'let $varChannelName = FlutterBasicMessageChannel(name: "$channelName", binaryMessenger: binaryMessenger, codec: codec)');
final String baseArgs = 'name: "$channelName", '
'binaryMessenger: binaryMessenger, codec: codec';
// The version with taskQueue: is an optional protocol method that isn't
// implemented on macOS yet, so the call has to be conditionalized even
// though the taskQueue argument is nullable. The runtime branching can be
// removed once macOS supports task queues. The condition is on the task
// queue variable not being nil because the earlier code to set it will
// return nil on macOS where the optional parts of the protocol are not
// implemented.
final String channelCreationWithoutTaskQueue =
'FlutterBasicMessageChannel($baseArgs)';
if (serialBackgroundQueue == null) {
indent.writeln('let $varChannelName = $channelCreationWithoutTaskQueue');
} else {
final String channelCreationWithTaskQueue =
'FlutterBasicMessageChannel($baseArgs, taskQueue: $serialBackgroundQueue)';

indent.write('let $varChannelName = $serialBackgroundQueue == nil');
indent.addScoped('', '', () {
indent.writeln('? $channelCreationWithoutTaskQueue');
indent.writeln(': $channelCreationWithTaskQueue');
});
}

indent.write('if $setHandlerCondition ');
indent.addScoped('{', '}', () {
indent.write('$varChannelName.setMessageHandler ');
Expand Down
18 changes: 18 additions & 0 deletions packages/pigeon/lib/src/types/task_queue.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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.

/// Type of TaskQueue which determines how handlers are dispatched for
/// HostApi's.
enum TaskQueueType {
/// Handlers are invoked serially on the default thread. This is the value if
/// unspecified.
serial,

/// Handlers are invoked serially on a background thread.
serialBackgroundThread,

// TODO(gaaclarke): Add support for concurrent task queues.
// /// Handlers are invoked concurrently on a background thread.
// concurrentBackgroundThread,
}
Loading