blob: 7eaf6181b01e97b08acf0afb0b88fd395f2c8dee [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:developer' show Timeline, Flow;
import 'dart:isolate';
import 'package:meta/meta.dart';
import 'profile.dart';
/// Signature for the callback passed to [compute].
///
/// {@macro flutter.foundation.compute.types}
///
/// Instances of [ComputeCallback] must be top-level functions or static methods
/// of classes, not closures or instance methods of objects.
///
/// {@macro flutter.foundation.compute.limitations}
typedef ComputeCallback<Q, R> = R Function(Q message);
/// Spawn an isolate, run `callback` on that isolate, passing it `message`, and
/// (eventually) return the value returned by `callback`.
///
/// This is useful for operations that take longer than a few milliseconds, and
/// which would therefore risk skipping frames. For tasks that will only take a
/// few milliseconds, consider [scheduleTask] instead.
///
/// {@template flutter.foundation.compute.types}
/// `Q` is the type of the message that kicks off the computation.
///
/// `R` is the type of the value returned.
/// {@endtemplate}
///
/// The `callback` argument must be a top-level function, not a closure or an
/// instance or static method of a class.
///
/// {@template flutter.foundation.compute.limitations}
/// There are limitations on the values that can be sent and received to and
/// from isolates. These limitations constrain the values of `Q` and `R` that
/// are possible. See the discussion at [SendPort.send].
/// {@endtemplate}
///
/// The `debugLabel` argument can be specified to provide a name to add to the
/// [Timeline]. This is useful when profiling an application.
Future<R> compute<Q, R>(ComputeCallback<Q, R> callback, Q message, { String debugLabel }) async {
profile(() { debugLabel ??= callback.toString(); });
final Flow flow = Flow.begin();
Timeline.startSync('$debugLabel: start', flow: flow);
final ReceivePort resultPort = ReceivePort();
Timeline.finishSync();
final Isolate isolate = await Isolate.spawn<_IsolateConfiguration<Q, R>>(
_spawn,
_IsolateConfiguration<Q, R>(
callback,
message,
resultPort.sendPort,
debugLabel,
flow.id,
),
errorsAreFatal: true,
onExit: resultPort.sendPort,
);
final R result = await resultPort.first;
Timeline.startSync('$debugLabel: end', flow: Flow.end(flow.id));
resultPort.close();
isolate.kill();
Timeline.finishSync();
return result;
}
@immutable
class _IsolateConfiguration<Q, R> {
const _IsolateConfiguration(
this.callback,
this.message,
this.resultPort,
this.debugLabel,
this.flowId,
);
final ComputeCallback<Q, R> callback;
final Q message;
final SendPort resultPort;
final String debugLabel;
final int flowId;
R apply() => callback(message);
}
void _spawn<Q, R>(_IsolateConfiguration<Q, R> configuration) {
R result;
Timeline.timeSync(
'${configuration.debugLabel}',
() {
result = configuration.apply();
},
flow: Flow.step(configuration.flowId),
);
Timeline.timeSync(
'${configuration.debugLabel}: returning result',
() { configuration.resultPort.send(result); },
flow: Flow.step(configuration.flowId),
);
}