// 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`) 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 runOnPlatformThread(FutureOr Function() computation) { if (!_platformIsolatesEnabled) { throw UnsupportedError('Platform thread isolates are not supported by this platform.'); } if (isRunningOnPlatformThread) { return Future(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? _platformRunnerSendPortFuture; final Map> _pending = >{}; int _nextId = 0; Future _spawnPlatformIsolate() { final sendPortCompleter = Completer(); final 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 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 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 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 _sendComputation(SendPort port, FutureOr Function() computation) { final int id = ++_nextId; final resultCompleter = Completer(); _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 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 potentiallyAsyncResult; try { potentiallyAsyncResult = message.computation(); } catch (e, s) { _safeSend(sendPort, message.id, null, e, s); return; } if (potentiallyAsyncResult is Future) { 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(symbol: 'PlatformIsolateNativeApi::Spawn') external void _nativeSpawn(Function entryPoint); /// Whether the current isolate is running on the platform thread. final bool isRunningOnPlatformThread = _isRunningOnPlatformThread(); @Native( 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 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; }