-
Notifications
You must be signed in to change notification settings - Fork 50
Conversation
Note: I opted to not have |
Now proposes release of |
Can you share any of the real world use cases you have in mind for this? |
Uses this under the hood. It makes it relatively easy to read a tar-stream... Because you can do So the implementation is greatly simplified, as you don't have to manage buffering. Especially, since a tar reader will want to expose the content of entries as substreams. It often comes in handy if you want to consume a byte-stream without writing the entire stream to a buffer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool stuff!
lib/src/chunked_stream_reader.dart
Outdated
|
||
final c = StreamController<List<T>>(); | ||
|
||
c.addStream(() async* { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd love to try to optimize this.
It seems OK for size, but the async*
function could probably just be an async
function where yield e
is replaced by c.add(e)
. Don't worry about it for now if it works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we get no back-pressure.. We'd have to also hook onPause
/ onResume
.
Yes, it could probably be a tiny bit faster, but I'm generally very weary of using StreamController.add
because it doesn't return a future I can await.
/// Read bytes into a [Uint8List]. | ||
/// | ||
/// This does the same as [readChunk], except it uses [collectBytes] to create | ||
/// a [Uint8List], which offers better performance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And because you only use sublist
internally, which preserves typed_data lists, it's going to be Uint8List
all the way down. Nice!
I tried to integrate this into import 'dart:typed_data';
import 'package:async/async.dart';
Future<void> main() async {
final block = Uint8List(512);
final stream = Stream.fromIterable(Iterable.generate(10, (_) => block));
final reader = ChunkedStreamReader(stream);
while (true) {
final chunk = await reader.readBytes(1024);
if (chunk.isEmpty) break;
}
} This fails with |
I know this already landed, but since it hasn't been released yet, something that may be worth considering: could this be re-imagined as a set of extension methods on extension ChunkedStreamQueueExtension<T> on StreamQueue<List<T>> {
Future<List<T>> readChunk(int size);
Stream<List<T>> readChunkedSubstream(int size);
} I think |
@nex3, hmm, an extension method would have been nice, but I think I'll need a place to cache a partially consumed chunk. It might have been possible if If the stream has the type Example Stream<List<int>> makeStreamWithChunksOfBytes() async* {
// Byte chunk 1:
yield [1],
// Byte chunk 2:
yield [2,3,4,5];
// Byte chunk 3:
yield [6],
}
final reader = ChunkedStreamReader(makeStreamWithChunksOfBytes());
// Read the first two bytes
expect(await reader.readChunk(2), equals([1,2]));
// At this point "Byte chunk 1" and "Byte chunk 2" has been read from the underlying stream
// and all the elements from "Byte chunk 1" have been return the user.
// BUT: elements 3,4,5 from "Byte chunk 2" are now cached inside [reader]
// Read the next 100 bytes (or in this case until the end of the stream)
expect(await reader.readChunk(100), equals([3,4,5,6])); It's mostly useful when reading byte streams.. I don't see how an extension on |
Yeah, I see what you mean—it's not implementable as an extension method on the class's public API. I think you could do it on the private API, though: extension ChunkedStreamQueueExt<T> on StreamQueue<List<T>> {
Stream<List<T>> readChunkedSubstream(int size) {
if (size < 0) throw RangeError.range(size, 0, null, 'size');
if (!_isClosed) {
var request = _ReadChunkedSubstreamRequest<T>(size);
_addRequest(request);
return request.stream;
}
throw _failClosed();
}
}
class _ReadChunkedSubstreamRequest<T> extends _EventRequest<List<T>> {
Stream<List<T>> get stream => _controller.stream;
final _controller = StreamController<List<T>>(sync: true);
final int _size;
int _consumed = 0;
_ReadChunkedSubstreamRequest(this._size);
@override
bool update(QueueList<Result<List<T>>> events, bool isDone) {
while (events.isNotEmpty) {
var event = events.removeFirst();
if (event.isError) {
event.addTo(_controller);
continue;
}
var data = event.asValue!.value;
if (_consumed + data.length < _size) {
_consumed += data.length;
_controller.add(data);
} else {
if (_consumed + data.length > _size) {
data = data.sublist(0, _size - _consumed);
events.addFirst(Result.value(data.sublist(_size - _consumed)));
}
_controller.add(data);
_controller.close();
return true;
}
}
if (isDone) _controller.close();
return isDone;
}
} (Warning: I didn't test this!) |
The |
I suppose this might be possible. But I'm not sure it's desirable. Using the internal API is not ideal, nor the worse. Certainly we could as @lrhn points out make a But for most users who want to read a byte-stream block by block, you really don't want other methods available. Those are just footguns.
If using Example:
Building
Essentially, anyone using |
Yeah, I see what you mean. In that case, maybe it should be a wrapper rather than an extension, so you can get the nice request-sequencing behavior without having methods at two different abstraction levels. That's something that could probably be added to the existing API after a release, though. |
* Added ChunkedStreamReader * Updated changelog * Prepare 2.6.0 release * Address review comments, fix boundary issue * Cleanup tests * More tests and const empty list
This adds a
ChunkedStreamReader
as a replacement forChunkedStreamIterator
frompackage:chunked_stream
.Motivation: We would like to use
ChunkedStreamIterator
in Dart SDK and avoid dependency onpackage:chunked_stream
. Also for consuming a chunked byte stream in pull-fashion, this has proven very useful :)I refactored the code a bit, mostly just cleanup, better documentation comments, and rename the class so that code that imports both
package:async
andpackage:chunked_stream
won't get a conflict. Test cases are the same to ensure easy migration.Credits @walnutdust for the original
ChunkedStreamIterator
inpackage:chunked_stream
.