| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | |
| // for details. 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:convert'; | |
| import 'dart:io'; | |
| import 'dart:isolate'; | |
| import 'package:stream_channel/isolate_channel.dart'; | |
| import 'package:stream_channel/stream_channel.dart'; | |
| Future<void> main() async { | |
| // A StreamChannel<T>, is in simplest terms, a wrapper around a Stream<T> and | |
| // a StreamSink<T>. For example, you can create a channel that wraps standard | |
| // IO: | |
| var stdioChannel = StreamChannel(stdin, stdout); | |
| stdioChannel.sink.add('Hello!\n'.codeUnits); | |
| // Like a Stream<T> can be transformed with a StreamTransformer<T>, a | |
| // StreamChannel<T> can be transformed with a StreamChannelTransformer<T>. | |
| // For example, we can handle standard input as strings: | |
| var stringChannel = stdioChannel | |
| .transform(StreamChannelTransformer.fromCodec(utf8)) | |
| .transformStream(const LineSplitter()); | |
| stringChannel.sink.add('world!\n'); | |
| // You can implement StreamChannel<T> by extending StreamChannelMixin<T>, but | |
| // it's much easier to use a StreamChannelController<T>. A controller has two | |
| // StreamChannel<T> members: `local` and `foreign`. The creator of a | |
| // controller should work with the `local` channel, while the recipient should | |
| // work with the `foreign` channel, and usually will not have direct access to | |
| // the underlying controller. | |
| var ctrl = StreamChannelController<String>(); | |
| ctrl.local.stream.listen((event) { | |
| // Do something useful here... | |
| }); | |
| // You can also pipe events from one channel to another. | |
| ctrl | |
| ..foreign.pipe(stringChannel) | |
| ..local.sink.add('Piped!\n'); | |
| await ctrl.local.sink.close(); | |
| // The StreamChannel<T> interface provides several guarantees, which can be | |
| // found here: | |
| // https://pub.dev/documentation/stream_channel/latest/stream_channel/StreamChannel-class.html | |
| // | |
| // By calling `StreamChannel<T>.withGuarantees()`, you can create a | |
| // StreamChannel<T> that provides all guarantees. | |
| var dummyCtrl0 = StreamChannelController<String>(); | |
| var guaranteedChannel = StreamChannel.withGuarantees( | |
| dummyCtrl0.foreign.stream, dummyCtrl0.foreign.sink); | |
| // To close a StreamChannel, use `sink.close()`. | |
| await guaranteedChannel.sink.close(); | |
| // A MultiChannel<T> multiplexes multiple virtual channels across a single | |
| // underlying transport layer. For example, an application listening over | |
| // standard I/O can still support multiple clients if it has a mechanism to | |
| // separate events from different clients. | |
| // | |
| // A MultiChannel<T> splits events into numbered channels, which are | |
| // instances of VirtualChannel<T>. | |
| var dummyCtrl1 = StreamChannelController<Object>(); | |
| var multiChannel = MultiChannel<String>(dummyCtrl1.foreign); | |
| var channel1 = multiChannel.virtualChannel(); | |
| await multiChannel.sink.close(); | |
| // The client/peer should also create its own MultiChannel<T>, connected to | |
| // the underlying transport, use the corresponding ID's to handle events in | |
| // their respective channels. It is up to you how to communicate channel ID's | |
| // across different endpoints. | |
| var dummyCtrl2 = StreamChannelController<Object>(); | |
| var multiChannel2 = MultiChannel<String>(dummyCtrl2.foreign); | |
| var channel2 = multiChannel2.virtualChannel(channel1.id); | |
| await channel2.sink.close(); | |
| await multiChannel2.sink.close(); | |
| // Multiple instances of a Dart application can communicate easily across | |
| // `SendPort`/`ReceivePort` pairs by means of the `IsolateChannel<T>` class. | |
| // Typically, one endpoint will create a `ReceivePort`, and call the | |
| // `IsolateChannel.connectReceive` constructor. The other endpoint will be | |
| // given the corresponding `SendPort`, and then call | |
| // `IsolateChannel.connectSend`. | |
| var recv = ReceivePort(); | |
| var recvChannel = IsolateChannel<void>.connectReceive(recv); | |
| var sendChannel = IsolateChannel<void>.connectSend(recv.sendPort); | |
| // You must manually close `IsolateChannel<T>` sinks, however. | |
| await recvChannel.sink.close(); | |
| await sendChannel.sink.close(); | |
| // You can use the `Disconnector` transformer to cause a channel to act as | |
| // though the remote end of its transport had disconnected. | |
| var disconnector = Disconnector<String>(); | |
| var disconnectable = stringChannel.transform(disconnector); | |
| disconnectable.sink.add('Still connected!'); | |
| await disconnector.disconnect(); | |
| // Additionally: | |
| // * The `DelegatingStreamController<T>` class can be extended to build a | |
| // basis for wrapping other `StreamChannel<T>` objects. | |
| // * The `jsonDocument` transformer converts events to/from JSON, using | |
| // the `json` codec from `dart:convert`. | |
| // * `package:json_rpc_2` directly builds on top of | |
| // `package:stream_channel`, so any compatible transport can be used to | |
| // create interactive client/server or peer-to-peer applications (i.e. | |
| // language servers, microservices, etc. | |
| } |