Skip to main content

moq_net/model/
group.rs

1//! A group is a stream of frames, split into a [GroupProducer] and [GroupConsumer] handle.
2//!
3//! A [GroupProducer] writes an ordered stream of frames.
4//! Frames can be written all at once, or in chunks.
5//!
6//! A [GroupConsumer] reads an ordered stream of frames.
7//! The reader can be cloned, in which case each reader receives a copy of each frame. (fanout)
8//!
9//! The stream is closed with [Error] when all writers or readers are dropped.
10use std::collections::VecDeque;
11use std::task::{Poll, ready};
12
13use bytes::Bytes;
14
15use crate::{Error, MAX_FRAME_SIZE, Result};
16
17use super::{Frame, FrameConsumer, FrameProducer};
18
19/// Maximum total size of frames cached in a group before old frames are evicted.
20///
21/// Kept equal to `MAX_FRAME_SIZE` so a single maximum-size frame can fill a group's cache.
22const MAX_GROUP_CACHE: u64 = 32 * 1024 * 1024; // 32 MB
23
24/// Maximum number of frames cached in a group before old frames are evicted.
25const MAX_GROUP_FRAMES: usize = 1024;
26
27/// A group contains a sequence number because they can arrive out of order.
28///
29/// You can use [crate::TrackProducer::append_group] if you just want to +1 the sequence number.
30#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Group {
33	/// Per-track sequence number used to detect ordering and gaps. Higher numbers
34	/// supersede lower ones; consumers may skip late arrivals.
35	pub sequence: u64,
36}
37
38impl Group {
39	/// Consume this [`Group`] to create a producer that owns its sequence number.
40	pub fn produce(self) -> GroupProducer {
41		GroupProducer::new(self)
42	}
43}
44
45impl From<usize> for Group {
46	fn from(sequence: usize) -> Self {
47		Self {
48			sequence: sequence as u64,
49		}
50	}
51}
52
53impl From<u64> for Group {
54	fn from(sequence: u64) -> Self {
55		Self { sequence }
56	}
57}
58
59impl From<u32> for Group {
60	fn from(sequence: u32) -> Self {
61		Self {
62			sequence: sequence as u64,
63		}
64	}
65}
66
67impl From<u16> for Group {
68	fn from(sequence: u16) -> Self {
69		Self {
70			sequence: sequence as u64,
71		}
72	}
73}
74
75#[derive(Default)]
76struct GroupState {
77	// The frames currently cached in the group.
78	// Evicted frames are popped from the front; `offset` tracks how many.
79	frames: VecDeque<FrameProducer>,
80
81	// The number of frames evicted from the front of the group.
82	offset: usize,
83
84	// The total size (in bytes) of all cached frames.
85	cache: u64,
86
87	// Whether the group has been finalized (no more frames).
88	fin: bool,
89
90	// The error that caused the group to be aborted, if any.
91	abort: Option<Error>,
92}
93
94impl GroupState {
95	fn poll_get_frame(&self, index: usize) -> Poll<Result<Option<FrameConsumer>>> {
96		if index < self.offset {
97			Poll::Ready(Err(Error::CacheFull))
98		} else if let Some(frame) = self.frames.get(index - self.offset) {
99			Poll::Ready(Ok(Some(frame.consume())))
100		} else if self.fin {
101			Poll::Ready(Ok(None))
102		} else if let Some(err) = &self.abort {
103			Poll::Ready(Err(err.clone()))
104		} else {
105			Poll::Pending
106		}
107	}
108
109	/// Poll for the full payload of the frame at `index`, reading it in place.
110	///
111	/// Unlike [`Self::poll_get_frame`] this never mints a [`FrameConsumer`] (which
112	/// would churn the frame's consumer count and wake its waiters every poll); it
113	/// reads the cached [`FrameProducer`] directly. `waiter` is registered on the
114	/// frame's state so the reader wakes when it finishes.
115	fn poll_frame_read_all(&self, index: usize, waiter: &kio::Waiter) -> Poll<Result<Option<Bytes>>> {
116		if index < self.offset {
117			return Poll::Ready(Err(Error::CacheFull));
118		}
119		match self.frames.get(index - self.offset) {
120			Some(frame) => Poll::Ready(Ok(Some(ready!(frame.poll_read_all(waiter))?))),
121			None if self.fin => Poll::Ready(Ok(None)),
122			None => match &self.abort {
123				Some(err) => Poll::Ready(Err(err.clone())),
124				None => Poll::Pending,
125			},
126		}
127	}
128
129	fn poll_finished(&self) -> Poll<Result<u64>> {
130		if self.fin {
131			Poll::Ready(Ok((self.offset + self.frames.len()) as u64))
132		} else if let Some(err) = &self.abort {
133			Poll::Ready(Err(err.clone()))
134		} else {
135			Poll::Pending
136		}
137	}
138
139	/// Evict frames from the front of the group until within both limits.
140	fn evict(&mut self) {
141		while self.cache > MAX_GROUP_CACHE || self.frames.len() > MAX_GROUP_FRAMES {
142			let Some(frame) = self.frames.pop_front() else {
143				break;
144			};
145			self.cache -= frame.size;
146			self.offset += 1;
147		}
148	}
149}
150
151fn modify(state: &kio::Producer<GroupState>) -> Result<kio::Mut<'_, GroupState>> {
152	state.write().map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
153}
154
155/// Writes frames to a group in order.
156///
157/// Each group is delivered independently over a QUIC stream.
158/// Use [Self::write_frame] for simple single-buffer frames,
159/// or [Self::create_frame] for multi-chunk streaming writes.
160pub struct GroupProducer {
161	// Mutable stream state.
162	state: kio::Producer<GroupState>,
163
164	// The group header containing the sequence number.
165	info: Group,
166}
167
168impl std::ops::Deref for GroupProducer {
169	type Target = Group;
170
171	fn deref(&self) -> &Self::Target {
172		&self.info
173	}
174}
175
176impl GroupProducer {
177	/// Create a new group producer.
178	pub fn new(info: Group) -> Self {
179		Self {
180			info,
181			state: kio::Producer::default(),
182		}
183	}
184
185	/// A helper method to write a frame from a single byte buffer.
186	///
187	/// If you want to write multiple chunks, use [Self::create_frame] to get a frame producer.
188	/// But an upfront size is required.
189	pub fn write_frame<B: Into<Bytes>>(&mut self, frame: B) -> Result<()> {
190		let data = frame.into();
191		let frame = Frame {
192			size: data.len() as u64,
193		};
194		let mut frame = self.create_frame(frame)?;
195		frame.write(data)?;
196		frame.finish()?;
197		Ok(())
198	}
199
200	/// Create a frame with an upfront size
201	pub fn create_frame(&mut self, info: Frame) -> Result<FrameProducer> {
202		// Reject before `produce()`: `FrameProducer::new` preallocates `size` bytes, so an oversized
203		// frame must be caught here or it triggers the very allocation the limit exists to prevent.
204		if info.size > MAX_FRAME_SIZE {
205			return Err(Error::FrameTooLarge);
206		}
207		let frame = info.produce();
208		self.append_frame(frame.clone())?;
209		Ok(frame)
210	}
211
212	/// Append a frame producer to the group.
213	pub fn append_frame(&mut self, frame: FrameProducer) -> Result<()> {
214		// Backstop for direct callers (the buffer is already allocated by the time we hold a
215		// FrameProducer); `create_frame` is the path that rejects before allocating.
216		if frame.size > MAX_FRAME_SIZE {
217			return Err(Error::FrameTooLarge);
218		}
219		let mut state = modify(&self.state)?;
220		if state.fin {
221			return Err(Error::Closed);
222		}
223		state.cache += frame.size;
224		state.frames.push_back(frame);
225		state.evict();
226		Ok(())
227	}
228
229	/// Return the number of frames written so far.
230	pub fn frame_count(&self) -> usize {
231		let state = self.state.read();
232		state.offset + state.frames.len()
233	}
234
235	/// Mark the group as complete; no more frames will be written.
236	pub fn finish(&mut self) -> Result<()> {
237		let mut state = modify(&self.state)?;
238		state.fin = true;
239		Ok(())
240	}
241
242	/// Abort the group with the given error.
243	///
244	/// No updates can be made after this point. Drops the cached frames so a stale
245	/// [`GroupConsumer`] can't pin their buffers in memory forever; consumers that
246	/// haven't drained yet surface the abort error instead of the leftover cache.
247	/// Child frames are independent: a consumer that already pulled a
248	/// [`FrameConsumer`] keeps its own handle and can finish reading it.
249	pub fn abort(&mut self, err: Error) -> Result<()> {
250		let mut guard = modify(&self.state)?;
251		guard.abort = Some(err);
252		guard.frames.clear();
253		guard.cache = 0;
254		guard.close();
255		Ok(())
256	}
257
258	/// Create a new consumer for the group.
259	pub fn consume(&self) -> GroupConsumer {
260		GroupConsumer {
261			info: self.info.clone(),
262			state: self.state.consume(),
263			index: 0,
264		}
265	}
266
267	/// Block until the group is closed or aborted.
268	pub async fn closed(&self) -> Error {
269		self.state.closed().await;
270		self.state.read().abort.clone().unwrap_or(Error::Dropped)
271	}
272
273	/// Block until there are no active consumers.
274	pub async fn unused(&self) -> Result<()> {
275		self.state
276			.unused()
277			.await
278			.map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
279	}
280}
281
282impl Clone for GroupProducer {
283	fn clone(&self) -> Self {
284		Self {
285			info: self.info.clone(),
286			state: self.state.clone(),
287		}
288	}
289}
290
291impl Drop for GroupProducer {
292	fn drop(&mut self) {
293		// See TrackProducer::drop: the last producer dropping without a clean
294		// finish releases the cached frames so a stale consumer can't pin their
295		// buffers forever. A finished group keeps its cache so consumers can drain.
296		if !self.state.is_last() {
297			return;
298		}
299		if let Ok(mut state) = modify(&self.state)
300			&& !state.fin
301		{
302			state.frames.clear();
303			state.cache = 0;
304		}
305	}
306}
307
308impl From<Group> for GroupProducer {
309	fn from(info: Group) -> Self {
310		GroupProducer::new(info)
311	}
312}
313
314/// Consume a group, frame-by-frame.
315#[derive(Clone)]
316pub struct GroupConsumer {
317	// Shared state with the producer.
318	state: kio::Consumer<GroupState>,
319
320	// Immutable stream state.
321	info: Group,
322
323	// The number of frames we've read.
324	// NOTE: Cloned readers inherit this offset, but then run in parallel.
325	index: usize,
326}
327
328impl std::ops::Deref for GroupConsumer {
329	type Target = Group;
330
331	fn deref(&self) -> &Self::Target {
332		&self.info
333	}
334}
335
336impl GroupConsumer {
337	// A helper to automatically apply Dropped if the state is closed without an error.
338	fn poll<F, R>(&self, waiter: &kio::Waiter, f: F) -> Poll<Result<R>>
339	where
340		F: Fn(&kio::Ref<'_, GroupState>) -> Poll<Result<R>>,
341	{
342		Poll::Ready(match ready!(self.state.poll(waiter, f)) {
343			Ok(res) => res,
344			// We try to clone abort just in case the function forgot to check for terminal state.
345			Err(state) => Err(state.abort.clone().unwrap_or(Error::Dropped)),
346		})
347	}
348
349	/// Block until the frame at the given index is available.
350	///
351	/// Returns None if the group is finished and the index is out of range.
352	pub async fn get_frame(&self, index: usize) -> Result<Option<FrameConsumer>> {
353		kio::wait(|waiter| self.poll_get_frame(waiter, index)).await
354	}
355
356	/// Poll for the frame at the given index, without blocking.
357	///
358	/// Returns None if the group is finished and the index is out of range.
359	pub fn poll_get_frame(&self, waiter: &kio::Waiter, index: usize) -> Poll<Result<Option<FrameConsumer>>> {
360		self.poll(waiter, |state| state.poll_get_frame(index))
361	}
362
363	/// Return a consumer for the next frame for chunked reading.
364	pub async fn next_frame(&mut self) -> Result<Option<FrameConsumer>> {
365		kio::wait(|waiter| self.poll_next_frame(waiter)).await
366	}
367
368	/// Poll for the next frame, without blocking.
369	///
370	/// Returns None if the group is finished and the index is out of range.
371	pub fn poll_next_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<FrameConsumer>>> {
372		let Some(frame) = ready!(self.poll(waiter, |state| state.poll_get_frame(self.index))?) else {
373			return Poll::Ready(Ok(None));
374		};
375
376		self.index += 1;
377		Poll::Ready(Ok(Some(frame)))
378	}
379
380	/// Read the next frame's data all at once, without blocking.
381	pub fn poll_read_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Bytes>>> {
382		let index = self.index;
383		let Some(data) = ready!(self.poll(waiter, |state| state.poll_frame_read_all(index, waiter))?) else {
384			return Poll::Ready(Ok(None));
385		};
386
387		self.index += 1;
388		Poll::Ready(Ok(Some(data)))
389	}
390
391	/// Read the next frame's data all at once.
392	pub async fn read_frame(&mut self) -> Result<Option<Bytes>> {
393		kio::wait(|waiter| self.poll_read_frame(waiter)).await
394	}
395
396	/// Read all of the chunks of the next frame, without blocking.
397	pub fn poll_read_frame_chunks(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Vec<Bytes>>>> {
398		let index = self.index;
399		let Some(data) = ready!(self.poll(waiter, |state| state.poll_frame_read_all(index, waiter))?) else {
400			return Poll::Ready(Ok(None));
401		};
402
403		self.index += 1;
404		// In-place reads return the whole frame as one slice; keep the chunked API
405		// shape (empty payload -> no chunks).
406		Poll::Ready(Ok(Some(if data.is_empty() { Vec::new() } else { vec![data] })))
407	}
408
409	/// Read all of the chunks of the next frame.
410	pub async fn read_frame_chunks(&mut self) -> Result<Option<Vec<Bytes>>> {
411		kio::wait(|waiter| self.poll_read_frame_chunks(waiter)).await
412	}
413
414	/// Poll for the final number of frames in the group.
415	pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
416		self.poll(waiter, |state| state.poll_finished())
417	}
418
419	/// Block until the group is finished, returning the number of frames in the group.
420	pub async fn finished(&mut self) -> Result<u64> {
421		kio::wait(|waiter| self.poll_finished(waiter)).await
422	}
423}
424
425#[cfg(test)]
426mod test {
427	use super::*;
428	use futures::FutureExt;
429
430	#[test]
431	fn basic_frame_reading() {
432		let mut producer = Group { sequence: 0 }.produce();
433		producer.write_frame(Bytes::from_static(b"frame0")).unwrap();
434		producer.write_frame(Bytes::from_static(b"frame1")).unwrap();
435		producer.finish().unwrap();
436
437		let mut consumer = producer.consume();
438		let f0 = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
439		assert_eq!(f0.size, 6);
440		let f1 = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
441		assert_eq!(f1.size, 6);
442		let end = consumer.next_frame().now_or_never().unwrap().unwrap();
443		assert!(end.is_none());
444	}
445
446	#[test]
447	fn read_frame_all_at_once() {
448		let mut producer = Group { sequence: 0 }.produce();
449		producer.write_frame(Bytes::from_static(b"hello")).unwrap();
450		producer.finish().unwrap();
451
452		let mut consumer = producer.consume();
453		let data = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
454		assert_eq!(data, Bytes::from_static(b"hello"));
455	}
456
457	#[test]
458	fn read_frame_chunks() {
459		let mut producer = Group { sequence: 0 }.produce();
460		let mut frame = producer.create_frame(Frame { size: 10 }).unwrap();
461		frame.write(Bytes::from_static(b"hello")).unwrap();
462		frame.write(Bytes::from_static(b"world")).unwrap();
463		frame.finish().unwrap();
464		producer.finish().unwrap();
465
466		// Frame data is held in a single per-frame buffer; consumers see the full
467		// contents in one chunk rather than the individual write boundaries.
468		let mut consumer = producer.consume();
469		let chunks = consumer.read_frame_chunks().now_or_never().unwrap().unwrap().unwrap();
470		assert_eq!(chunks.len(), 1);
471		assert_eq!(chunks[0], Bytes::from_static(b"helloworld"));
472	}
473
474	#[test]
475	fn append_rejects_oversized_frame() {
476		let mut producer = Group { sequence: 0 }.produce();
477		let err = producer.create_frame(Frame {
478			size: MAX_FRAME_SIZE + 1,
479		});
480		assert!(
481			matches!(err, Err(Error::FrameTooLarge)),
482			"a frame over the limit is rejected"
483		);
484		// A frame at the limit is still accepted.
485		assert!(producer.create_frame(Frame { size: MAX_FRAME_SIZE }).is_ok());
486	}
487
488	#[test]
489	fn get_frame_by_index() {
490		let mut producer = Group { sequence: 0 }.produce();
491		producer.write_frame(Bytes::from_static(b"a")).unwrap();
492		producer.write_frame(Bytes::from_static(b"bb")).unwrap();
493		producer.finish().unwrap();
494
495		let consumer = producer.consume();
496		let f0 = consumer.get_frame(0).now_or_never().unwrap().unwrap().unwrap();
497		assert_eq!(f0.size, 1);
498		let f1 = consumer.get_frame(1).now_or_never().unwrap().unwrap().unwrap();
499		assert_eq!(f1.size, 2);
500		let f2 = consumer.get_frame(2).now_or_never().unwrap().unwrap();
501		assert!(f2.is_none());
502	}
503
504	#[test]
505	fn group_finish_returns_none() {
506		let mut producer = Group { sequence: 0 }.produce();
507		producer.finish().unwrap();
508
509		let mut consumer = producer.consume();
510		let end = consumer.next_frame().now_or_never().unwrap().unwrap();
511		assert!(end.is_none());
512	}
513
514	#[test]
515	fn abort_propagates() {
516		let mut producer = Group { sequence: 0 }.produce();
517		let mut consumer = producer.consume();
518		producer.abort(crate::Error::Cancel).unwrap();
519
520		let result = consumer.next_frame().now_or_never().unwrap();
521		assert!(matches!(result, Err(crate::Error::Cancel)));
522	}
523
524	#[test]
525	fn abort_clears_cached_frames() {
526		let mut producer = Group { sequence: 0 }.produce();
527		producer.write_frame(Bytes::from_static(b"data")).unwrap();
528
529		// A stale consumer that never reads must not pin the cached frames.
530		let _consumer = producer.consume();
531		assert_eq!(producer.state.read().frames.len(), 1);
532
533		producer.abort(crate::Error::Cancel).unwrap();
534
535		let state = producer.state.read();
536		assert!(state.frames.is_empty(), "cached frames should be dropped on abort");
537		assert_eq!(state.cache, 0);
538	}
539
540	#[test]
541	fn drop_unfinished_clears_cached_frames() {
542		let producer = Group { sequence: 0 }.produce();
543		let mut writer = producer.clone();
544		writer.write_frame(Bytes::from_static(b"data")).unwrap();
545
546		// A stale consumer keeps the channel (and thus the cache) alive.
547		let mut consumer = producer.consume();
548		assert_eq!(producer.state.read().frames.len(), 1);
549
550		// Drop every producer without finishing: the cache is released.
551		drop(writer);
552		drop(producer);
553
554		let result = consumer.next_frame().now_or_never().unwrap();
555		assert!(matches!(result, Err(crate::Error::Dropped)));
556	}
557
558	#[test]
559	fn drop_finished_keeps_cached_frames() {
560		let mut producer = Group { sequence: 0 }.produce();
561		producer.write_frame(Bytes::from_static(b"data")).unwrap();
562		producer.finish().unwrap();
563
564		let mut consumer = producer.consume();
565		drop(producer);
566
567		// A cleanly finished group keeps its cache so the consumer can still drain.
568		let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
569		assert_eq!(frame, Bytes::from_static(b"data"));
570	}
571
572	#[tokio::test]
573	async fn pending_then_ready() {
574		let mut producer = Group { sequence: 0 }.produce();
575		let mut consumer = producer.consume();
576
577		// Consumer blocks because no frames yet.
578		assert!(consumer.next_frame().now_or_never().is_none());
579
580		producer.write_frame(Bytes::from_static(b"data")).unwrap();
581		producer.finish().unwrap();
582
583		let frame = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
584		assert_eq!(frame.size, 4);
585	}
586
587	#[test]
588	fn eviction_drops_old_frames() {
589		let mut producer = Group { sequence: 0 }.produce();
590
591		// Write frames that total more than MAX_GROUP_CACHE.
592		let big = Bytes::from(vec![0u8; MAX_GROUP_CACHE as usize]);
593		producer.write_frame(big.clone()).unwrap();
594		producer.write_frame(big).unwrap();
595
596		// The first frame should have been evicted.
597		let consumer = producer.consume();
598		let result = consumer.get_frame(0).now_or_never().unwrap();
599		assert!(matches!(result, Err(crate::Error::CacheFull)));
600
601		// The second frame should still be available.
602		let f1 = consumer.get_frame(1).now_or_never().unwrap().unwrap().unwrap();
603		assert_eq!(f1.size, MAX_GROUP_CACHE);
604	}
605
606	#[test]
607	fn no_eviction_under_limit() {
608		let mut producer = Group { sequence: 0 }.produce();
609		producer.write_frame(Bytes::from_static(b"small")).unwrap();
610		producer.write_frame(Bytes::from_static(b"frames")).unwrap();
611		producer.finish().unwrap();
612
613		let consumer = producer.consume();
614		let f0 = consumer.get_frame(0).now_or_never().unwrap().unwrap().unwrap();
615		assert_eq!(f0.size, 5);
616		let f1 = consumer.get_frame(1).now_or_never().unwrap().unwrap().unwrap();
617		assert_eq!(f1.size, 6);
618	}
619
620	#[test]
621	fn eviction_by_frame_count() {
622		let mut producer = Group { sequence: 0 }.produce();
623
624		// Write more than MAX_GROUP_FRAMES frames.
625		for _ in 0..=MAX_GROUP_FRAMES {
626			producer.write_frame(Bytes::from_static(b"x")).unwrap();
627		}
628
629		// The first frame should have been evicted.
630		let consumer = producer.consume();
631		let result = consumer.get_frame(0).now_or_never().unwrap();
632		assert!(matches!(result, Err(crate::Error::CacheFull)));
633
634		// The last frame should still be available.
635		let f = consumer
636			.get_frame(MAX_GROUP_FRAMES)
637			.now_or_never()
638			.unwrap()
639			.unwrap()
640			.unwrap();
641		assert_eq!(f.size, 1);
642	}
643
644	#[test]
645	fn next_frame_returns_cache_full_on_tombstone() {
646		let mut producer = Group { sequence: 0 }.produce();
647
648		let big = Bytes::from(vec![0u8; MAX_GROUP_CACHE as usize]);
649		producer.write_frame(big.clone()).unwrap();
650		producer.write_frame(big).unwrap();
651
652		let mut consumer = producer.consume();
653		// First frame was evicted, next_frame should return CacheFull.
654		let result = consumer.next_frame().now_or_never().unwrap();
655		assert!(matches!(result, Err(crate::Error::CacheFull)));
656	}
657
658	#[test]
659	fn clone_consumer_independent() {
660		let mut producer = Group { sequence: 0 }.produce();
661		producer.write_frame(Bytes::from_static(b"a")).unwrap();
662
663		let mut c1 = producer.consume();
664		// Read one frame from c1
665		let _ = c1.next_frame().now_or_never().unwrap().unwrap().unwrap();
666
667		// Clone c1 — inherits index (past first frame)
668		let mut c2 = c1.clone();
669
670		producer.write_frame(Bytes::from_static(b"b")).unwrap();
671		producer.finish().unwrap();
672
673		// c2 should get the second frame (inherited index)
674		let f = c2.next_frame().now_or_never().unwrap().unwrap().unwrap();
675		assert_eq!(f.size, 1); // "b"
676
677		let end = c2.next_frame().now_or_never().unwrap().unwrap();
678		assert!(end.is_none());
679	}
680}