| // 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; | |
| /// Runs [computation] on the platform thread and returns the result. | |
| /// | |
| /// This may run the computation on a separate isolate. That isolate will be | |
| /// reused for subsequent [runOnPlatformThread] calls. This means that global | |
| /// state is maintained in that isolate between calls. | |
| /// | |
| /// The [computation] and any state it captures may be sent to that isolate. | |
| /// See [SendPort.send] for information about what types can be sent. | |
| /// | |
| /// If [computation] is asynchronous (returns a `Future<R>`) then | |
| /// that future is awaited in the new isolate, completing the entire | |
| /// asynchronous computation, before returning the result. | |
| /// | |
| /// If [computation] throws, the `Future` returned by this function completes | |
| /// with that error. | |
| /// | |
| /// The [computation] function and its result (or error) must be | |
| /// sendable between isolates. Objects that cannot be sent include open | |
| /// files and sockets (see [SendPort.send] for details). | |
| /// | |
| /// This method can only be invoked from the main isolate. | |
| /// | |
| /// This API is currently experimental. | |
| Future<R> runOnPlatformThread<R>(FutureOr<R> Function() computation) { | |
| if (!_platformIsolatesEnabled) { | |
| throw UnsupportedError( | |
| 'Platform thread isolates are not supported by this platform.'); | |
| } | |
| if (isRunningOnPlatformThread) { | |
| return Future<R>(computation); | |
| } | |
| final SendPort? sendPort = _platformRunnerSendPort; | |
| if (sendPort != null) { | |
| return _sendComputation(sendPort, computation); | |
| } else { | |
| return (_platformRunnerSendPortFuture ??= _spawnPlatformIsolate()) | |
| .then((SendPort port) => _sendComputation(port, computation)); | |
| } | |
| } | |
| SendPort? _platformRunnerSendPort; | |
| Future<SendPort>? _platformRunnerSendPortFuture; | |
| final Map<int, Completer<Object?>> _pending = <int, Completer<Object?>>{}; | |
| int _nextId = 0; | |
| Future<SendPort> _spawnPlatformIsolate() { | |
| final Completer<SendPort> sendPortCompleter = Completer<SendPort>(); | |
| final RawReceivePort receiver = RawReceivePort()..keepIsolateAlive = false; | |
| receiver.handler = (Object? message) { | |
| if (message == null) { | |
| // This is the platform isolate's onExit handler. | |
| // This shouldn't really happen, since Isolate.exit is disabled, the | |
| // pause and terminate capabilities aren't provided to the parent | |
| // isolate, and errors are fatal is false. But if the isolate does | |
| // shutdown unexpectedly, clear the singleton so we can create another. | |
| for (final Completer<Object?> completer in _pending.values) { | |
| completer.completeError(RemoteError( | |
| 'PlatformIsolate shutdown unexpectedly', | |
| StackTrace.empty.toString())); | |
| } | |
| _pending.clear(); | |
| _platformRunnerSendPort = null; | |
| _platformRunnerSendPortFuture = null; | |
| } else if (message is _PlatformIsolateReadyMessage) { | |
| _platformRunnerSendPort = message.computationPort; | |
| sendPortCompleter.complete(message.computationPort); | |
| } else if (message is _ComputationResult) { | |
| final Completer<Object?> resultCompleter = _pending.remove(message.id)!; | |
| final Object? remoteStack = message.remoteStack; | |
| final Object? remoteError = message.remoteError; | |
| if (remoteStack != null) { | |
| if (remoteStack is StackTrace) { | |
| // Typed error. | |
| resultCompleter.completeError(remoteError!, remoteStack); | |
| } else { | |
| // onError handler message, uncaught async error. | |
| // Both values are strings, so calling `toString` is efficient. | |
| final RemoteError error = | |
| RemoteError(remoteError!.toString(), remoteStack.toString()); | |
| resultCompleter.completeError(error, error.stackTrace); | |
| } | |
| } else { | |
| resultCompleter.complete(message.result); | |
| } | |
| } else { | |
| // We encountered an error while starting the new isolate. | |
| if (!sendPortCompleter.isCompleted) { | |
| sendPortCompleter.completeError( | |
| IsolateSpawnException('Unable to spawn isolate: $message')); | |
| return; | |
| } | |
| // This shouldn't happen. | |
| throw IsolateSpawnException( | |
| "Internal error: unexpected message: '$message'"); | |
| } | |
| }; | |
| final Isolate parentIsolate = Isolate.current; | |
| final SendPort sendPort = receiver.sendPort; | |
| try { | |
| _nativeSpawn(() => _platformIsolateMain(parentIsolate, sendPort)); | |
| } on Object { | |
| receiver.close(); | |
| rethrow; | |
| } | |
| return sendPortCompleter.future; | |
| } | |
| Future<R> _sendComputation<R>( | |
| SendPort port, FutureOr<R> Function() computation) { | |
| final int id = ++_nextId; | |
| final Completer<R> resultCompleter = Completer<R>(); | |
| _pending[id] = resultCompleter; | |
| port.send(_ComputationRequest(id, computation)); | |
| return resultCompleter.future; | |
| } | |
| void _safeSend(SendPort sendPort, int id, Object? result, Object? error, | |
| Object? stackTrace) { | |
| try { | |
| sendPort.send(_ComputationResult(id, result, error, stackTrace)); | |
| } catch (sendError, sendStack) { | |
| sendPort.send(_ComputationResult(id, null, sendError, sendStack)); | |
| } | |
| } | |
| void _platformIsolateMain(Isolate parentIsolate, SendPort sendPort) { | |
| final RawReceivePort computationPort = RawReceivePort(); | |
| computationPort.handler = (_ComputationRequest? message) { | |
| if (message == null) { | |
| // The parent isolate has shutdown. Allow this isolate to shutdown. | |
| computationPort.keepIsolateAlive = false; | |
| return; | |
| } | |
| late final FutureOr<Object?> potentiallyAsyncResult; | |
| try { | |
| potentiallyAsyncResult = message.computation(); | |
| } catch (e, s) { | |
| _safeSend(sendPort, message.id, null, e, s); | |
| return; | |
| } | |
| if (potentiallyAsyncResult is Future<Object?>) { | |
| potentiallyAsyncResult.then((Object? result) { | |
| _safeSend(sendPort, message.id, result, null, null); | |
| }, onError: (Object? e, Object? s) { | |
| _safeSend(sendPort, message.id, null, e, s ?? StackTrace.empty); | |
| }); | |
| } else { | |
| _safeSend(sendPort, message.id, potentiallyAsyncResult, null, null); | |
| } | |
| }; | |
| Isolate.current.addOnExitListener(sendPort); | |
| parentIsolate.addOnExitListener(computationPort.sendPort); | |
| sendPort.send(_PlatformIsolateReadyMessage(computationPort.sendPort)); | |
| } | |
| @Native<Void Function(Handle)>(symbol: 'PlatformIsolateNativeApi::Spawn') | |
| external void _nativeSpawn(Function entryPoint); | |
| /// Whether the current isolate is running on the platform thread. | |
| final bool isRunningOnPlatformThread = _isRunningOnPlatformThread(); | |
| @Native<Bool Function()>( | |
| symbol: 'PlatformIsolateNativeApi::IsRunningOnPlatformThread', isLeaf: true) | |
| external bool _isRunningOnPlatformThread(); | |
| class _PlatformIsolateReadyMessage { | |
| _PlatformIsolateReadyMessage(this.computationPort); | |
| final SendPort computationPort; | |
| } | |
| class _ComputationRequest { | |
| _ComputationRequest(this.id, this.computation); | |
| final int id; | |
| final FutureOr<Object?> Function() computation; | |
| } | |
| class _ComputationResult { | |
| _ComputationResult(this.id, this.result, this.remoteError, this.remoteStack); | |
| final int id; | |
| final Object? result; | |
| final Object? remoteError; | |
| final Object? remoteStack; | |
| } |