I have a stream and I need to use the last value of this stream, and if there is no value emitted by this stream I need to wait for the fist value. I only want to use this value once. What is the correct way to do it?
Sounds like you want the most recent event emitted by a stream (which is presumably a broadcast stream, because otherwise there is no events until you listen), or, if there has been no events before, you want the next event instead.
For a plain Dart Stream, that's impossible. It doesn't remember previous events. You need to have listened to that stream previously in order to know what the most recent event was (but if you do that, it doesn't have to be a broadcast stream anyway).
You can build your own memorizing stream wrapper fairly easily (but as always with asynchronous programming, you need to be careful about race conditions)
// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0
import "dart:async";
/// Listens to [source] to returned stream.
///
/// Each listener on the returned stream receives the most recent
/// event sent on [source] followed by all further events of [source]
/// until they stop listening.
/// If there has been no events on [source] yet, only the further events
/// are forwarded.
Stream<T> mostRecentStream<T>(Stream<T> source) {
var isDone = false;
var hasEvent = false;
T? mostRecentEvent;
List<MultiStreamController>? pendingListeners;
var listeners = <MultiStreamController>[];
void forEachListener(void Function(MultiStreamController) action) {
var active = 0;
var originalLength = listeners.length;
for (var i = 0; i < listeners.length; i++) {
var controller = listeners[i];
if (controller.hasListener) {
listeners[active++] = controller;
if (i < originalLength) action(controller);
}
}
listeners.length = active;
}
source.listen((event) {
mostRecentEvent = event;
hasEvent = true;
forEachListener((controller) {
controller.addSync(event);
});
}, onError: (e, s) {
forEachListener((controller) {
controller.addErrorSync(e, s);
});
}, onDone: () {
isDone = true;
for (var controller in listeners) {
controller.close();
}
listeners.clear();
});
return Stream<T>.multi((controller) {
if (hasEvent) controller.add(mostRecentEvent as T);
if (isDone) {
controller.close();
} else {
listeners.add(controller);
}
});
}
With that, you can simply do var recentStream = mostRecentStream(yourStream) and then later do recentStream.first to get either the most recent event or, if there is none, the next event (if there is one, you get an error if the stream is completely empty).
Related
Hi i am building a blockchain and am trying to sync a list of connected peers,
but if the following code is called twice from different nodes the first call is still busy while the second call kicks in does anyone know how i could wait for the first message to be complete
class Peer {
Peer(
{required this.us,
required this.peers,
required this.allPeers,
required this.myPeers});
String us;
Map<String, int> peers;
Map<String, List<Online>> allPeers;
Map<String, List<Online>> myPeers;
List<String>? keep;
Map<String, List<NewNodePeerMessage>> nnpms = {};
//listen should never trigger a response to connect we will give the address / ip
// so your ndoe two will only sync when a thrid node joins the network
Future listen() async {
ServerSocket ss =
await ServerSocket.bind(us.split(':')[0], int.parse(us.split(':')[1]));
print('listening on ${us.split(':')[1]}');
// List<ReceivePort> lrp = [];
ss.listen((client) {
// ReceivePort rp = ReceivePort();
utf8.decoder.bind(client).listen((data) async {
final PeerMessage pm =
PeerMessage.fromJson(json.decode(data) as Map<String, dynamic>);
print('recieved msg from ${pm.from}');
switch (pm.type) {
case 'new-node':
{
// rp.listen((_) async {
final NewNodePeerMessage nnpm = NewNodePeerMessage.fromJson(
json.decode(data) as Map<String, dynamic>);
print('msg${nnpm.toJson()}');
peers[pm.code] ??= 3;
allPeers[pm.code] ??= [];
final List<Online> news = [];
print('apl ${allPeers[pm.code]!.length}');
// ,maby a list would let me know
// maby we should have a simple check to the loop bool that it doesnt start looping when it is adjusting the peers
print(
'allpeers before looping ${allPeers[pm.code]!.map((e) => e.toJson()).toList()}');
for (Online one in allPeers[pm.code]!.where((element) =>
element.address != pm.from &&
element.address != us &&
!nnpm.recieved.contains(element.address))) {
print(
'i am still loopin current one ${one.toJson()} current from ${pm.from}');
try {
final Socket ones = await Socket.connect(
one.address.split(':')[0],
int.parse(one.address.split(':')[1]));
nnpm.recieved.add(us);
nnpm.recieved.add(pm.from);
// if we would only move ones out of the for loop maby the program wont work at the same point in time
// we could have a bool that keeeps track of the msg is working on printstatement you inside off the msg
// so 8787 trigger 5442 because it has him in the list
// mabe a bool can be added to list if you is inished with listening
// isbusy knows iff its stuck in the loop if we write from here we are
// if we write ffrom connect we arent or this write could know if it is busy
// is busy shoudl be from down
// because if we wirte from here or we write from you is busy is true and false
// so if we write from below can isbusy stop us
// is isusy is true herewe can go into listen but on you we can not
ones.write(json.encode(NewNodePeerMessage(
isBusy: false,
max: peers[pm.code]!,
peer: nnpm.peer,
type: 'new-node',
from: us,
code: pm.code,
recieved: nnpm.recieved)
.toJson()));
print('propablywrote ${one.toJson()} from ${nnpm.from}');
ones.listen((ppmru) async {
print('listentedtoppmru ${one.toJson()}');
final PeersPeerMessageResponse ppmr =
peersPeerMessageResponse(ppmru);
print(
'abouttoaddnewonlines ${ppmr.onlines.map((e) => e.toJson())} and from ${ppmr.from}');
// news.add(Online(online: true, address: ppmr.from));
news.addAll(ppmr.onlines);
await ones.close();
});
} catch (err) {
one.online = false;
// break;
}
print('abouttoloopagain ${one.toJson()} from ${nnpm.from}');
}
print('gothereagainactuallydonelooping ${pm.from}');
// its actually that new node only should write to the client again only if its the first time maby
allPeers[pm.code]!.addAll(news);
allPeers[pm.code]!.removeWhere((element) => !element.online);
print(allPeers[pm.code]!.map((e) => e.toJson()).toList());
/// the problem occurs because of client clients response will shut down base or we could wrap it inside o try an catch
/// we need to know if this message will send the code up here or down to printstatement you to ones.listen or to s that listen
/// one global bool could say like will go down maby even when it goes up herte to printstatement abouttoaddnewonlines
/// how do we know here below that it will go to the you printstatement or down
/// so we need a message from up
allPeers[pm.code]!.add(Online(online: true, address: nnpm.peer));
// print('abouttowriteto ${client.address.address} ${client.port}');
print('abouttorespondto ${pm.from}');
client.write(json.encode(PeersPeerMessageResponse(
isBusy: true,
peer: nnpm.peer,
onlines: allPeers[pm.code]!
.where((element) => element.address != pm.from)
.toList(),
code: pm.code,
from: us)
.toJson()));
// await client.close();
// rp.sendPort.send(null);
// });
client.destroy();
break;
}
case 'new-node-through':
{
break;
}
case 'is-online':
client.write(null);
break;
case 'is-test':
print('recieved');
client.write('irespond');
break;
default:
break;
}
// client.destroy();
}, onDone: () {});
});
}
bool loop = false;
// Future connect(List<dynamic> args) async {
// is busy shoudl bee ffrom down
Future connect(String bootnode, String code) async {
final Socket s = await Socket.connect(
"${bootnode.split(':')[0]}", int.parse(bootnode.split(':')[1]));
print('connected to ${bootnode}');
s.write(json.encode(NewNodePeerMessage(
isBusy: true,
max: 3,
peer: us,
type: 'new-node',
code: code,
from: us,
recieved: []).toJson()));
print('befforelistening');
s.listen((pmmru) async {
print('whatwas first');
PeersPeerMessageResponse ppmr = peersPeerMessageResponse(pmmru);
print(ppmr.toJson());
allPeers[ppmr.code] ??= [];
allPeers[ppmr.code]!.addAll(ppmr.onlines);
allPeers[ppmr.code]!.add(Online(online: true, address: ppmr.from));
print(allPeers);
await s.close();
}, onDone: () {
print('doschopnescheee');
});
//because off up being triggered ffrom this msg we know it will go down because off client that write
// and client that write is up their and it m
// final somekindloop;
//if we would just never listen here would it relay on up might solve problem because up might be busy we could also ssst the isolate
// s.listen((ppmru) async {
// loop = true;
// // whenever we recieve here the ppmru could have the isbusy instead
// // print('you');
// // isBusy
// // while (!loop) {}
// PeersPeerMessageResponse ppmr = peersPeerMessageResponse(ppmru);
// print(ppmr.toJson());
// allPeers[ppmr.code] ??= [];
// allPeers[ppmr.code]!.addAll(ppmr.onlines);
// allPeers[ppmr.code]!.add(Online(online: true, address: ppmr.from));
// print(allPeers);
// await s.close();
// });
}
PeersPeerMessageResponse peersPeerMessageResponse(Uint8List resp) {
final PeersPeerMessageResponse ppm = PeersPeerMessageResponse.fromJson(
json.decode(String.fromCharCodes(resp).trim()) as Map<String, dynamic>);
return ppm;
}
// Future isOnline(String code) async {
// for (Online p in allPeers[code] ??= []) {
// try {
// final Socket peer = await Socket.connect(
// p.address.split(':')[0], int.parse(p.address.split(':')[1]));
// await peer.close();
// } catch (err) {
// p.ischis = false;
// }
// }
// }
}
its about the new node function inside of the switch statement while the first call is inside off the for loop the second call does not loop but increments the allPeers with new peers which because off the first call evolves into
Unhandled exception:
Concurrent modification during iteration: Instance(length:3) of '_GrowableList'.
#0 ListIterator.moveNext (dart:_internal/iterable.dart:336:7)
#1 WhereIterator.moveNext (dart:_internal/iterable.dart:438:22)
#2 Peer.listen.<anonymous closure>.<anonymous closure> (package:gov/peer/peer.dart:180:53)
<asynchronous suspension>
how can is use the on done event to wait or the first call to be complete?
is there anything like onCancel for a StreamSubscription?
example:
var subscription = someStream.listen((item) => null);
subscription.cancel(); // does this trigger any event?
I ended up creating a _StreamSubscriptionDelegate that delegates all methods and so I can put some logic when the subscription is cancelled, however, maybe there is an easier solution to it.
If the stream comes from a StreamController, then the controller is notified of the cancel. The listener is expected to keep track of their own subscription, so if one part of the client code needs to know that another part has cancelled the stream, then wrapping the subscription in something which records that you cancelled it, is a perfectly good approach.
Another approach could be to wrap the stream before listening to it:
Stream<T> onCancel<T>(Stream<T> source, void onCancel()) async* {
bool isCancelled = true;
try {
await for (var event in source) {
yield event; // exits if cancelled.
}
isCancelled = false;
} finally {
if (isCancelled) onCancel();
}
}
or
Stream<T> onCancel<T>(Stream<T> source, void onCancel()) {
var sink = StreamController<T>();
sink.onListen = () {
var subscription = source.listen(sink.add, onError: sink.onError, onDone: sink.close);
sink
..onPause = subscription.pause
..onResume = subscription.resume
..onCancel = () {
subscription.cancel();
onCancel();
};
};
return sink.stream;
}
In Dart, stdin is a Stream<List<int>>. Bytes come in in chunks. What I want is a function that reads from stdin until I get some character (say '\0'), and then returns, so that future readers of stdin get the data after the '\0'.
Unfortunately because of the chunking, the '\0' byte might be in the middle of a chunk, so I kind of want to read a chunk, remove part of it, and then push it back to the start of the stream. But there isn't any way to do this.
Another option would be readByteSync() but reading bytes one at a time is going to be slow and this is in a GUI program so I can't use sync methods.
I think actually because a Stream<> can only ever be listened to once - even if a previous listener cancels its subscription - the only way is to have something permanently filtering the stream's events until the end of time. So you may as well just split the stream into two streams:
import 'dart:async';
class StdioPreambleSplitter {
StdioPreambleSplitter(this._input) {
var preambleFinished = false;
_input.listen((chunk) {
if (preambleFinished) {
_dataStream.add(chunk);
} else {
final nullByte = chunk.indexOf(0);
if (nullByte == -1) {
_dataStream.add(chunk);
} else {
preambleFinished = true;
_preambleStream.add(chunk.sublist(0, nullByte));
_dataStream.add(chunk.sublist(nullByte));
}
}
});
}
Stream<List<int>> preambleStream() {
return _preambleStream.stream;
}
Stream<List<int>> dataStream() {
return _dataStream.stream;
}
final Stream<List<int>> _input;
final StreamController<List<int>> _preambleStream = new StreamController();
final StreamController<List<int>> _dataStream = new StreamController();
}
Hopefully it doesn't add too much overhead.
After reading a bunch of documentation about Streams and StreamControllers in dart I tried to build a little example and was surprised of the results. All documentation I have read states that a stream starts emiting data as soon as a listener is registered. But this doesn't show any printed data:
class Order
{
String type;
Order(this.type);
}
class Pizza
{
}
void main()
{
Order order = Order("pzza");
final StreamController sc = StreamController();
sc.sink.add(order);
sc.sink.add(order);
sc.sink.add(new Order("pizza"));
Stream st = sc.stream.map((order) {
return order.type;
})
.map((orderType) {
if(orderType == "pizza")
return Pizza();
else
throw ("dude!, I don't know how to do that");
});
var sus = st.listen((pizza)
{
print("We did a pizza");
},
onError: (error)
{
print(error);
});
sus.cancel();
sc.sink.add(new Order("pizza2"));
}
I was expecting this output:
dude!, I don't know how to do that
dude!, I don't know how to do that
We did a pizza
When creating streams and adding data is all "sinked" data scheduled to be emited on the next application step?
Cheers.
You are right in that the documentation states that you listen on a stream to make it start generating events. However, streams are asynchronous so when you call the listen() method you are registering to receive events from the stream at some point in the future. Dart will then continue to run the remainder of your main function. Immediately after calling listen() you call cancel() to cancel the subscription which is why nothing is being printed.
If you remove or comment out the cancel and run it again you will see the expected output.
A slightly modified version of your code will hopefully highlight the run of events:
class Order {
String type;
Order(this.type);
}
class Pizza {}
void main() {
print("Main starts");
Order order = Order("pzza");
final StreamController sc = StreamController();
sc.sink.add(order);
sc.sink.add(order);
sc.sink.add(new Order("pizza"));
Stream st = sc.stream.map((order) {
return order.type;
}).map((orderType) {
if (orderType == "pizza")
return Pizza();
else
throw ("dude!, I don't know how to do that");
});
var sus = st.listen((pizza) {
print("We did a pizza");
}, onError: (error) {
print(error);
});
// sus.cancel();
sc.sink.add(new Order("pizza2"));
print("Main ends");
}
Running this produces the output:
Main starts
Main ends
dude!, I don't know how to do that
dude!, I don't know how to do that
We did a pizza
dude!, I don't know how to do that
I try to write a chat application to chat with a computer. The user can write a message and gets a response of the computer. A chat history might look like this:
user: Hi
computer: Hello
user: What's your name?
computer: Bot
...
My circular stream based design is inspired by the ideas of Cycle.js. I've got a stream of user messages, which get transformed to a stream of computer messages, which are in turn the input of the user message stream:
|----> user message stream ---->|
| |
transform transform
| |
|<-- computer message stream <--|
This code already works:
import 'dart:io';
import 'dart:async';
void main() {
cycle(computer, user);
}
typedef Stream<T> Transform<T>(Stream<T> input);
void cycle(Transform aToB, Transform bToA) {
var aProxy = new StreamController.broadcast();
var b = aToB(aProxy.stream);
var a = bToA(b);
aProxy.add('start'); // start with user
aProxy.addStream(a);
}
Stream<String> user(Stream<String> computerMessages) {
computerMessages = computerMessages.asBroadcastStream();
computerMessages.listen((message) => print('computer: $message'));
return computerMessages.map((message) {
stdout.write('user: ');
return stdin.readLineSync();
});
}
Stream<String> computer(Stream<String> userMessages) {
var messages = <String, String>{
"Hi": "Hello",
"What's your name?": "Bot"
};
return userMessages.map((m) => messages.containsKey(m) ? messages[m] : 'What?');
}
There is only one problem. You need a start value to get a circular stream running. Therefore, I put this line in my function cycle:
aProxy.add('start'); // start with user
Actually, this logic belongs into my function user, since cycle shouldn't know the initial value(s). Moreover, I don't like to print the initial value. It should only trigger the user input stream. Thus, I changed cycle and user:
void cycle(Transform aToB, Transform bToA) {
var aProxy = new StreamController.broadcast();
var b = aToB(aProxy.stream);
var a = bToA(b);
aProxy.addStream(a);
}
Stream<String> user(Stream<String> computerMessages) {
computerMessages = computerMessages.asBroadcastStream();
computerMessages.listen((message) => print('computer: $message'));
var requestInput = new StreamController<String>.broadcast();
requestInput.add('start'); // start with user
requestInput.addStream(computerMessages); // continue on computer response
return requestInput.stream.map((message) {
stdout.write('user: ');
return stdin.readLineSync();
});
}
But with this change my application terminates immediately with no message in stdout. What's wrong?
I found a solution. Create a normal instead of a broadcast StreamController in user:
Stream<String> user(Stream<String> computerMessages) {
computerMessages = computerMessages.asBroadcastStream();
computerMessages.listen((message) => print('computer: $message'));
var requestInput = new StreamController<String>();
requestInput.add('start'); // start with user
requestInput.addStream(computerMessages); // continue on computer response
return requestInput.stream.map((message) {
stdout.write('user: ');
return stdin.readLineSync();
});
}
Even though I found a solution, I sadly can not really explain what's the problem with a broadcast StreamController. null is successfully added by requestInput.add(null); but strangely neither requestInput.addStream(computerMessages); completes nor events of computerMessages are added to requestInput. Thus, requestInput is closed and the application terminates. I would appreciate if someone could provide further explanation.