1use std::collections::{BTreeMap, BTreeSet};
12
13use url::Url;
14
15use super::*;
16use crate::client::action::{MpdValidators, UtcId};
17use crate::client::config::DashClockPolicy;
18use crate::client::meta::UtcResponse;
19use crate::client::utc_sampler::{Step, UtcSampler};
20use crate::mpd::descriptors::SCHEME_RESET;
21use crate::timing::clock::{AdoptReason, Anchor, ClockSource, OfferOutcome, OfferPolicy};
22use crate::timing::live_edge::{join_index, oldest_in_window, presentation_delay_us};
23use crate::timing::period::duration_us;
24use crate::timing::utc_timing::{Exchange, UtcSample, sample_from_date_header};
25
26const FORCED_RESYNC_MIN_GAP_US: u64 = 30_000_000;
29const EARLY_RETRY_CAP_US: u64 = 1_000_000;
31const EXPIRED_REJOIN_RUN: u32 = 3;
33const PERIOD_FINAL_SEGMENTS: i128 = 2;
36const MAX_LANE_STEPS: usize = 8;
38const EARLY_SHIFT_MAX: u32 = 20;
40
41#[derive(Default)]
42pub(super) struct Refresh {
43 pub(super) next_at: Option<u64>,
44 pub(super) forced_at: Option<u64>,
45 pub(super) last_request_us: Option<u64>,
46 pub(super) publish_time_us: Option<i64>,
47 pub(super) body: Vec<u8>,
48 pub(super) validators: MpdValidators,
49 pub(super) redirect_url: Option<String>,
50 pub(super) awaiting_retry: bool,
51}
52
53#[derive(Default, Clone, Copy)]
54pub(super) struct Early {
55 attempts: u32,
56 retry_at: Option<u64>,
57}
58
59pub(super) struct Live {
60 pub(super) ready: bool,
61 sampler: Option<UtcSampler>,
62 next_resync_us: Option<u64>,
63 last_forced_resync_us: Option<u64>,
64 pub(super) refresh: Refresh,
65 pub(super) early: BTreeMap<ResourceId, Early>,
66 missing_seen: BTreeSet<RepId>,
67 announced_clock: Option<ClockSource>,
68 wake_utc: Option<i128>,
69 mpd_exchange: Exchange,
70 fetch_utc_us: Option<i128>,
71}
72
73impl Live {
74 pub(super) fn bookkeeping(&self) -> usize {
75 self.early.len() + self.missing_seen.len()
76 }
77
78 fn new(now_us: u64) -> Self {
79 Self {
80 ready: false,
81 sampler: None,
82 next_resync_us: None,
83 last_forced_resync_us: None,
84 refresh: Refresh::default(),
85 early: BTreeMap::new(),
86 missing_seen: BTreeSet::new(),
87 announced_clock: None,
88 wake_utc: None,
89 mpd_exchange: Exchange::new(now_us, now_us),
90 fetch_utc_us: None,
91 }
92 }
93}
94
95fn status_of(failure: Failure) -> Option<u16> {
96 match failure {
97 Failure::Status(s) => Some(s),
98 Failure::Transport | Failure::Timeout | Failure::TooLarge => None,
99 }
100}
101
102pub(super) fn failure_kind(failure: Failure) -> FailureKind {
103 match failure {
104 Failure::Status(STATUS_NOT_FOUND | STATUS_GONE) => FailureKind::Missing,
105 Failure::Status(STATUS_TOO_EARLY) => FailureKind::TooEarly,
106 Failure::Status(_) => FailureKind::Server,
107 Failure::Transport | Failure::Timeout => FailureKind::Transport,
108 Failure::TooLarge => FailureKind::Invalid,
109 }
110}
111
112fn i128_from_i64(v: i64) -> i128 {
113 i128::from(v)
114}
115
116fn unix_i64(us: i128) -> i64 {
117 i64::try_from(us.clamp(i128::from(i64::MIN), i128::from(i64::MAX))).unwrap_or(i64::MAX)
118}
119
120impl DashClient {
121 pub fn set_wall_clock(&mut self, now_us: u64, unix_us: u64) {
131 if self.stopped() {
132 return;
133 }
134 let sample = UtcSample {
135 mono_us: now_us,
136 utc_us: unix_i64(i128::from(unix_us)),
137 uncertainty_us: self.dash.host_clock_uncertainty_us,
138 };
139 let allow_downgrade = self.dash.clock_policy == DashClockPolicy::PreferHost;
140 let outcome = self.clock.offer(
141 sample,
142 ClockSource::Host,
143 now_us,
144 OfferPolicy::new(self.dash.utc_resync_us, allow_downgrade),
145 );
146 let stepped = matches!(outcome, OfferOutcome::Adopted(AdoptReason::Step));
147 let Some(live) = self.live.as_mut() else {
148 return;
149 };
150 if allow_downgrade && !live.ready {
151 let gating = live.sampler.as_ref().is_some_and(|s| s.gating);
152 if gating {
153 live.sampler = None;
154 self.finish_ladder(now_us);
155 }
156 }
157 if stepped {
158 self.force_refresh(now_us);
159 }
160 self.announce_clock();
161 }
162
163 #[must_use]
165 pub fn clock_anchor(&self) -> Option<Anchor> {
166 self.clock.anchor().copied()
167 }
168
169 #[must_use]
172 pub fn utc_at(&self, now_us: u64) -> Option<i64> {
173 self.clock.utc_at(now_us)
174 }
175
176 #[must_use]
181 pub fn next_wake(&self) -> Option<u64> {
182 let live = self.live.as_ref()?;
183 if self.stopped() {
184 return None;
185 }
186 let mut best: Option<u64> = None;
187 let mut take = |t: u64| best = Some(best.map_or(t, |b| b.min(t)));
188 if let Some(t) = self.refresh_due_at() {
189 take(t);
190 }
191 if let Some(s) = live.sampler.as_ref() {
192 if let Some(d) = s.deadline_us() {
193 take(d);
194 }
195 if let Some(w) = s.wait_deadline_us.filter(|_| s.gating) {
196 take(w);
197 }
198 } else if live.ready
199 && let Some(t) = live.next_resync_us
200 {
201 take(t);
202 }
203 for e in live.early.values() {
204 if let Some(t) = e.retry_at {
205 take(t);
206 }
207 }
208 if let Some(w) = live.wake_utc
209 && let Some(m) = self.clock.mono_for_utc(unix_i64(w))
210 {
211 take(m);
212 }
213 best
214 }
215
216 pub fn on_utc_response(
222 &mut self,
223 now_us: u64,
224 id: UtcId,
225 response: &UtcResponse<'_>,
226 ) -> Result<()> {
227 self.ensure_live()?;
228 let dash = self.dash;
229 let sample = {
230 let sampler = self
231 .live
232 .as_mut()
233 .and_then(|l| l.sampler.as_mut())
234 .filter(|s| s.awaits(id))
235 .ok_or(Error::UnrequestedUtc { id })?;
236 sampler.on_response(id, response, &dash)
237 };
238 if let Some(sample) = sample {
239 self.utc_sample(now_us, sample);
240 }
241 Ok(())
242 }
243
244 pub fn on_utc_failure(&mut self, _now_us: u64, id: UtcId, failure: Failure) -> Result<()> {
252 self.ensure_live()?;
253 let sampler = self
254 .live
255 .as_mut()
256 .and_then(|l| l.sampler.as_mut())
257 .filter(|s| s.awaits(id))
258 .ok_or(Error::UnrequestedUtc { id })?;
259 sampler.on_failure(id, failure);
260 Ok(())
261 }
262
263 pub fn on_mpd_unchanged(&mut self, now_us: u64, meta: &ResponseMeta<'_>) -> Result<()> {
269 self.ensure_live()?;
270 if !self.mpd_inflight || self.live.is_none() {
271 return Err(Error::MpdNotAwaited);
272 }
273 self.mpd_inflight = false;
274 self.note_exchange(now_us, meta);
275 self.schedule_refresh(now_us);
276 Ok(())
277 }
278
279 pub(super) fn is_live_mpd(&self) -> bool {
282 self.mpd
283 .as_ref()
284 .is_some_and(|m| m.kind == PresentationType::Dynamic)
285 }
286
287 pub(super) fn live_blocked(&self) -> bool {
288 self.is_live_mpd() && self.live.as_ref().is_none_or(|l| !l.ready)
289 }
290
291 pub(super) fn live_open(&self) -> bool {
292 self.mpd.as_ref().is_some_and(|m| {
293 m.kind == PresentationType::Dynamic
294 && m.minimum_update_period.is_some()
295 && m.media_presentation_duration.is_none()
296 })
297 }
298
299 pub(super) fn period_open(&self, pi: usize, now_us: u64) -> bool {
303 if !self.live_open() {
304 return false;
305 }
306 let Some(mpd) = self.mpd.as_ref() else {
307 return false;
308 };
309 let run = &self.periods[pi];
310 let Some(end) = period_timings(mpd).get(run.index).and_then(|t| t.end_us) else {
311 return true;
312 };
313 let len = end.saturating_sub(run.start_us);
314 let reaches = run.lanes.iter().filter_map(Lane::current).all(|r| {
315 r.plan.as_ref().is_some_and(|p| {
316 p.len()
317 .and_then(|n| n.checked_sub(1))
318 .and_then(|last| p.get(last))
319 .is_some_and(|s| {
320 s.start_us
321 .saturating_add(s.duration_us)
322 .saturating_add(MISSING_TAIL_MAX_US)
323 >= len
324 })
325 })
326 });
327 if reaches {
328 return false;
329 }
330 let Some(utc) = self.utc_now(now_us) else {
331 return false;
332 };
333 let ast = i128::from(mpd.availability_start_time_us.unwrap_or(0));
334 let mup = mpd.minimum_update_period.map_or(0, duration_us);
335 let seg = mpd.max_segment_duration.map_or(0, duration_us);
336 let grace = mup.max(seg.saturating_mul(PERIOD_FINAL_SEGMENTS));
337 utc < ast.saturating_add(end).saturating_add(grace)
338 }
339
340 pub(super) fn can_activate(&self, next: usize) -> bool {
341 let Some(mpd) = self.mpd.as_ref() else {
342 return false;
343 };
344 if next >= mpd.periods.len() {
345 return false;
346 }
347 mpd.kind == PresentationType::Static
348 || period_timings(mpd)
349 .get(next)
350 .is_some_and(|t| t.start_us.is_some())
351 }
352
353 pub(super) fn utc_now(&self, now_us: u64) -> Option<i128> {
354 self.clock.utc_at(now_us).map(i128_from_i64)
355 }
356
357 pub(super) fn plan_options(&self) -> PlanOptions<'static> {
358 let mut o = PlanOptions::default()
359 .with_availability_time_offset(self.dash.use_availability_time_offset);
360 if let Some(h) = self.horizon() {
361 o = o.with_horizon_utc_us(h);
362 }
363 o
364 }
365
366 fn horizon(&self) -> Option<i128> {
367 let mpd = self.mpd.as_ref()?;
368 let mup = duration_us(mpd.minimum_update_period?);
369 let fetch = self.live.as_ref()?.fetch_utc_us?;
370 Some(fetch.saturating_add(mup))
371 }
372
373 pub(super) fn reset_wake(&mut self) {
376 if let Some(l) = self.live.as_mut() {
377 l.wake_utc = None;
378 }
379 }
380
381 pub(super) fn note_wake(&mut self, at_utc_us: i128) {
382 if let Some(l) = self.live.as_mut() {
383 l.wake_utc = Some(l.wake_utc.map_or(at_utc_us, |w| w.min(at_utc_us)));
384 }
385 }
386
387 pub(super) fn note_exhausted(&mut self, now_us: u64) {
388 if !self.live_open() {
389 return;
390 }
391 let tail_us = self
392 .periods
393 .get(self.issue)
394 .map(|p| {
395 p.lanes
396 .iter()
397 .filter_map(|l| l.current())
398 .filter_map(|r| {
399 let p = r.plan.as_ref()?;
400 let last = p.len()?.checked_sub(1)?;
401 p.get(last).map(|s| s.duration_us)
402 })
403 .max()
404 .unwrap_or(0)
405 })
406 .unwrap_or(0);
407 let wait = u64::try_from(tail_us.max(0)).unwrap_or(u64::MAX);
408 self.force_refresh_after(now_us, wait);
409 }
410
411 fn refresh_due_at(&self) -> Option<u64> {
414 let live = self.live.as_ref()?;
415 if !self.live_open() || self.mpd_inflight || self.want_mpd || live.refresh.awaiting_retry {
416 return None;
417 }
418 match (live.refresh.next_at, live.refresh.forced_at) {
419 (Some(a), Some(b)) => Some(a.min(b)),
420 (a, b) => a.or(b),
421 }
422 }
423
424 pub(super) fn tick(&mut self, now_us: u64) {
425 if self.live.is_none() {
426 return;
427 }
428 let gate_expired = self
429 .live
430 .as_ref()
431 .and_then(|l| l.sampler.as_ref())
432 .is_some_and(|s| s.gating && s.wait_deadline_us.is_some_and(|d| now_us >= d));
433 if gate_expired {
434 if let Some(l) = self.live.as_mut() {
435 l.sampler = None;
436 l.next_resync_us = Some(now_us.saturating_add(self.dash.utc_resync_us));
437 }
438 self.finish_ladder(now_us);
439 if self.stopped() {
440 return;
441 }
442 }
443 let resync_due = self.live.as_ref().is_some_and(|l| {
444 l.ready && l.sampler.is_none() && l.next_resync_us.is_some_and(|t| now_us >= t)
445 });
446 if resync_due {
447 if let Some(l) = self.live.as_mut() {
448 l.next_resync_us = None;
449 }
450 self.start_sampler(now_us, false);
451 }
452 let mut due: Vec<ResourceId> = Vec::new();
453 if let Some(l) = self.live.as_mut() {
454 for (id, e) in &mut l.early {
455 if e.retry_at.is_some_and(|t| now_us >= t) {
456 e.retry_at = None;
457 due.push(*id);
458 }
459 }
460 }
461 for id in due.into_iter().rev() {
462 if self.reqs.contains_key(&id) && !self.inflight.contains(&id) {
463 self.queue.push_front(id);
464 }
465 }
466 if self.refresh_due_at().is_some_and(|t| now_us >= t) {
467 self.want_mpd = true;
468 }
469 }
470
471 pub(super) fn mpd_action(&mut self, now_us: u64) -> Option<Action> {
472 if !self.want_mpd || self.mpd_inflight {
473 return None;
474 }
475 self.want_mpd = false;
476 self.mpd_inflight = true;
477 let mut url = self.mpd_url.clone();
478 let mut validators = MpdValidators::default();
479 if let Some(live) = self.live.as_mut() {
480 live.refresh.last_request_us = Some(now_us);
481 live.refresh.next_at = None;
482 live.refresh.forced_at = None;
483 validators = live.refresh.validators.clone();
484 }
485 if let Some(u) = self.update_url() {
486 url = u;
487 }
488 Some(Action::FetchMpd { url, validators })
489 }
490
491 fn update_url(&self) -> Option<String> {
492 let live = self.live.as_ref()?;
493 let mpd = self.mpd.as_ref()?;
494 if let Some(loc) = mpd.locations.first() {
495 let base = self.doc_url.as_ref();
496 let resolved = match base {
497 Some(b) => b.join(loc).ok().map(|u| u.to_string()),
498 None => Url::parse(loc).ok().map(|u| u.to_string()),
499 };
500 if let Some(url) = resolved
501 && let Ok(parsed) = Url::parse(&url)
502 && matches!(parsed.scheme(), "http" | "https")
503 {
504 let pin = !self.dash.cross_origin_urls;
505 let origin_ok = !pin
506 || self
507 .doc_url
508 .as_ref()
509 .is_none_or(|d| same_origin(d, &parsed));
510 if origin_ok {
511 return Some(url);
512 }
513 }
514 }
515 live.refresh.redirect_url.clone()
516 }
517
518 pub(super) fn utc_action(&mut self, now_us: u64) -> Option<Action> {
519 let dash = self.dash;
520 let step = {
521 let live = self.live.as_mut()?;
522 let sampler = live.sampler.as_mut()?;
523 sampler.poll(now_us, &mut self.next_utc_id, &dash)
524 };
525 match step {
526 Step::Fetch(a) => Some(a),
527 Step::Sample(sample) => {
528 self.utc_sample(now_us, sample);
529 None
530 }
531 Step::Exhausted => {
532 self.sampler_exhausted(now_us);
533 None
534 }
535 Step::Idle => None,
536 }
537 }
538
539 pub(super) fn start_live(&mut self, now_us: u64, bytes: &[u8], meta: &ResponseMeta<'_>) {
542 self.live = Some(Live::new(now_us));
543 if let Some(f) = meta.final_url
544 && let Some(u) = self.accept_final_url(f)
545 {
546 if let Some(l) = self.live.as_mut() {
547 l.refresh.redirect_url = Some(u.to_string());
548 }
549 self.doc_url = Some(u);
550 }
551 self.note_exchange(now_us, meta);
552 if let Some(l) = self.live.as_mut() {
553 l.refresh.body = bytes.to_vec();
554 l.refresh.publish_time_us = self.mpd.as_ref().and_then(|m| m.publish_time_us);
555 l.refresh.last_request_us = Some(meta.sent_us.min(meta.received_us));
556 }
557 self.speculative_activate(now_us);
558 let want_sampler = self.begin_sampler(now_us, meta);
559 if !want_sampler {
560 self.finish_ladder(now_us);
561 }
562 self.schedule_refresh(now_us);
563 }
564
565 fn note_exchange(&mut self, now_us: u64, meta: &ResponseMeta<'_>) {
566 let adopted = meta.final_url.and_then(|f| self.accept_final_url(f));
567 let Some(live) = self.live.as_mut() else {
568 return;
569 };
570 live.mpd_exchange = Exchange::new(meta.sent_us.min(meta.received_us), meta.received_us);
571 if let Some(e) = meta.etag {
572 live.refresh.validators.etag = Some(e.to_owned());
573 }
574 if let Some(m) = meta.last_modified {
575 live.refresh.validators.last_modified = Some(m.to_owned());
576 }
577 if let Some(u) = adopted {
578 live.refresh.redirect_url = Some(u.to_string());
579 self.doc_url = Some(u);
580 }
581 if let Some(date) = meta.date {
582 let exch = live.mpd_exchange;
583 if let Ok(sample) = sample_from_date_header(date, exch, self.dash.utc_max_rtt_us) {
584 self.clock.offer(
585 sample,
586 ClockSource::MpdDate,
587 now_us,
588 OfferPolicy::new(self.dash.utc_resync_us, false),
589 );
590 }
591 }
592 let mid = self.live.as_ref().map_or(now_us, |l| {
593 l.mpd_exchange.sent_us + (l.mpd_exchange.received_us - l.mpd_exchange.sent_us) / 2
594 });
595 let fetch_utc = self.clock.utc_at(mid).map(i128_from_i64).or_else(|| {
596 self.mpd
597 .as_ref()
598 .and_then(|m| m.publish_time_us)
599 .map(i128_from_i64)
600 });
601 if let Some(l) = self.live.as_mut() {
602 l.fetch_utc_us = fetch_utc;
603 }
604 }
605
606 fn begin_sampler(&mut self, now_us: u64, meta: &ResponseMeta<'_>) -> bool {
607 let _ = meta;
608 let host_first = self.dash.clock_policy == DashClockPolicy::PreferHost
609 && self
610 .clock
611 .anchor()
612 .is_some_and(|a| a.source == ClockSource::Host);
613 if host_first {
614 return false;
615 }
616 self.start_sampler(now_us, true)
617 }
618
619 fn start_sampler(&mut self, now_us: u64, gating: bool) -> bool {
620 let host_holds = self.dash.clock_policy == DashClockPolicy::PreferHost
621 && self
622 .clock
623 .anchor()
624 .is_some_and(|a| a.source == ClockSource::Host);
625 if !gating && (host_holds || !self.is_live_mpd()) {
626 return false;
627 }
628 let Some(mpd) = self.mpd.as_ref() else {
629 return false;
630 };
631 let elements = mpd.utc_timings.clone();
632 let unsupported = elements.iter().any(|e| {
633 matches!(
634 e.scheme,
635 crate::timing::utc_timing::UtcScheme::Ntp
636 | crate::timing::utc_timing::UtcScheme::Sntp
637 | crate::timing::utc_timing::UtcScheme::Unknown
638 )
639 });
640 let doc = self.doc_url.clone();
641 let pin = !self.dash.cross_origin_urls;
642 let mut cross = false;
643 let exchange = self
644 .live
645 .as_ref()
646 .map_or(Exchange::new(now_us, now_us), |l| l.mpd_exchange);
647 let mut accept = |value: &str| -> Option<String> {
648 let url = match &doc {
649 Some(d) => d.join(value).ok()?,
650 None => Url::parse(value).ok()?,
651 };
652 if pin
653 && let Some(d) = &doc
654 && !same_origin(d, &url)
655 {
656 cross = true;
657 return None;
658 }
659 matches!(url.scheme(), "http" | "https").then(|| url.to_string())
660 };
661 let mut sampler = UtcSampler::new(&elements, &mut accept, exchange);
662 if unsupported {
663 self.notice_once(Notice::UtcSchemeUnsupported);
664 }
665 if cross {
666 self.notice_once(Notice::UtcCrossOrigin);
667 }
668 if sampler.is_empty() {
669 return false;
670 }
671 sampler.gating = gating;
672 if gating {
673 sampler.wait_deadline_us = Some(now_us.saturating_add(self.dash.utc_wait_us));
674 }
675 if let Some(l) = self.live.as_mut() {
676 l.sampler = Some(sampler);
677 }
678 true
679 }
680
681 fn utc_sample(&mut self, now_us: u64, sample: UtcSample) {
682 let outcome = self.clock.offer(
683 sample,
684 ClockSource::UtcTiming,
685 now_us,
686 OfferPolicy::new(self.dash.utc_resync_us, false),
687 );
688 let stepped = matches!(outcome, OfferOutcome::Adopted(AdoptReason::Step));
689 let ready = {
690 let Some(live) = self.live.as_mut() else {
691 return;
692 };
693 live.sampler = None;
694 live.next_resync_us = Some(now_us.saturating_add(self.dash.utc_resync_us));
695 live.ready
696 };
697 if stepped {
698 self.force_refresh(now_us);
699 }
700 if ready {
701 self.announce_clock();
702 } else {
703 self.finish_ladder(now_us);
704 }
705 }
706
707 fn sampler_exhausted(&mut self, now_us: u64) {
708 let ready = {
709 let Some(live) = self.live.as_mut() else {
710 return;
711 };
712 live.sampler = None;
713 live.next_resync_us = Some(now_us.saturating_add(self.dash.utc_resync_us));
714 live.ready
715 };
716 if !ready {
717 self.finish_ladder(now_us);
718 }
719 }
720
721 fn finish_ladder(&mut self, now_us: u64) {
722 if self.clock.anchor().is_none() {
723 self.go_fatal(FatalKind::ClockUnavailable);
724 return;
725 }
726 if let Some(live) = self.live.as_mut() {
727 if live.ready {
728 return;
729 }
730 live.ready = true;
731 }
732 self.announce_clock();
733 self.on_clock_ready(now_us);
734 }
735
736 fn announce_clock(&mut self) {
737 if self.stopped() {
738 return;
739 }
740 let Some(anchor) = self.clock.anchor().copied() else {
741 return;
742 };
743 let Some(live) = self.live.as_mut() else {
744 return;
745 };
746 if !live.ready || live.announced_clock == Some(anchor.source) {
747 return;
748 }
749 live.announced_clock = Some(anchor.source);
750 self.outputs.push_back(Output::ClockSynced {
751 source: anchor.source,
752 uncertainty_us: anchor.uncertainty_us,
753 });
754 }
755
756 fn request_resync(&mut self, now_us: u64) {
757 let Some(live) = self.live.as_mut() else {
758 return;
759 };
760 if !live.ready || live.sampler.is_some() {
761 return;
762 }
763 let spaced = live
764 .last_forced_resync_us
765 .is_none_or(|t| now_us >= t.saturating_add(FORCED_RESYNC_MIN_GAP_US));
766 if spaced {
767 live.last_forced_resync_us = Some(now_us);
768 live.next_resync_us = Some(now_us);
769 }
770 }
771
772 fn delay_us(&self, plan_hint_us: i128) -> i128 {
775 let Some(mpd) = self.mpd.as_ref() else {
776 return 0;
777 };
778 let max_seg = mpd.max_segment_duration.map_or(plan_hint_us, duration_us);
779 presentation_delay_us(
780 mpd.suggested_presentation_delay,
781 max_seg,
782 mpd.time_shift_buffer_depth.map(duration_us),
783 self.dash.live_delay_us.map(i128::from),
784 )
785 }
786
787 fn choose_join_period(&self, utc: i128) -> Option<usize> {
788 let mpd = self.mpd.as_ref()?;
789 let ast = i128::from(mpd.availability_start_time_us?);
790 let target = utc.saturating_sub(self.delay_us(0)).saturating_sub(ast);
791 let timings = period_timings(mpd);
792 let mut chosen: Option<usize> = None;
793 for (i, t) in timings.iter().enumerate() {
794 let Some(start) = t.start_us else { continue };
795 if chosen.is_none() || start <= target {
796 chosen = Some(i);
797 }
798 }
799 chosen
800 }
801
802 fn speculative_activate(&mut self, now_us: u64) {
803 let provisional = self.utc_now(now_us);
804 let pick = provisional
805 .and_then(|u| self.choose_join_period(u))
806 .or_else(|| {
807 let mpd = self.mpd.as_ref()?;
808 period_timings(mpd)
809 .iter()
810 .rposition(|t| t.start_us.is_some())
811 });
812 if let Some(index) = pick {
813 self.activate(index);
814 self.issue = self.periods.len() - 1;
815 }
816 }
817
818 fn on_clock_ready(&mut self, now_us: u64) {
819 let Some(utc) = self.utc_now(now_us) else {
820 return;
821 };
822 let target = self.choose_join_period(utc);
823 let current = self.periods.last().map(|p| p.index);
824 let started = self.periods.last().is_some_and(|p| p.started);
825 match (target, current) {
826 (Some(t), Some(c)) if t != c && !started => {
827 self.abandon_last_run();
828 self.activate(t);
829 }
830 (Some(t), None) => self.activate(t),
831 _ => {}
832 }
833 if !self.periods.is_empty() {
834 self.issue = self.periods.len() - 1;
835 if self.deliver >= self.periods.len() {
836 self.deliver = self.periods.len() - 1;
837 }
838 }
839 self.replan_all();
840 self.drain();
841 }
842
843 fn abandon_last_run(&mut self) {
844 let Some(run) = self.periods.pop() else {
845 return;
846 };
847 let pi = self.periods.len();
848 let mut ids: Vec<RepId> = Vec::new();
849 for lane in &run.lanes {
850 for rep in &lane.reps {
851 ids.push(rep.id);
852 }
853 }
854 self.rep_loc.retain(|_, (p, _, _)| *p != pi);
855 let rep_set: BTreeSet<RepId> = ids.iter().copied().collect();
856 let gone: Vec<ResourceId> = self
857 .reqs
858 .iter()
859 .filter(|(_, i)| rep_set.contains(&i.rep))
860 .map(|(id, _)| *id)
861 .collect();
862 for id in gone {
863 self.reqs.remove(&id);
864 self.inflight.remove(&id);
865 self.parked.remove(&id);
866 self.queue.retain(|q| *q != id);
867 }
868 let used: BTreeSet<InitKey> = self
869 .periods
870 .iter()
871 .flat_map(|p| p.lanes.iter())
872 .flat_map(|l| l.reps.iter())
873 .map(|r| r.init_key.clone())
874 .collect();
875 self.inits
876 .retain(|k, e| e.state == InitState::Ready || used.contains(k));
877 self.issue = self.periods.len().saturating_sub(1);
878 }
879
880 pub(super) fn live_lane_candidate(
881 &mut self,
882 pi: usize,
883 li: usize,
884 utc: i128,
885 now_us: u64,
886 ) -> Candidate {
887 let margin = i128::from(self.dash.availability_margin_us);
888 for _ in 0..MAX_LANE_STEPS {
889 let lane = &self.periods[pi].lanes[li];
890 let Some(rep) = lane.current() else {
891 return Candidate::Idle;
892 };
893 let Some(plan) = rep.plan.as_ref() else {
894 return Candidate::Idle;
895 };
896 if !rep.joined {
897 let hint = plan.get(0).map_or(0, |s| s.duration_us);
898 let delay = self.delay_us(hint);
899 match join_index(plan, utc, delay) {
900 Some(idx) => {
901 let last = idx
902 .checked_sub(1)
903 .and_then(|i| plan.get(i))
904 .map(|s| s.time_ticks);
905 let lane = &mut self.periods[pi].lanes[li];
906 let ri = lane.reps.len() - 1;
907 let rep = &mut lane.reps[ri];
908 rep.next_issue = idx;
909 rep.joined = true;
910 rep.last_time = last;
911 continue;
912 }
913 None => {
914 let first = plan
915 .get(0)
916 .and_then(|s| s.available_from_us)
917 .map(|a| a.saturating_add(margin));
918 return match first {
919 Some(a) if a > utc => Candidate::Wait(a),
920 _ => {
921 self.force_refresh(now_us);
922 self.request_resync(now_us);
923 Candidate::Idle
924 }
925 };
926 }
927 }
928 }
929 let Some(seg) = plan.get(rep.next_issue) else {
930 return Candidate::Idle;
931 };
932 if seg.available_until_us.is_some_and(|u| u < utc) {
933 self.expire_before_request(pi, li, utc);
934 continue;
935 }
936 let from = seg
937 .available_from_us
938 .unwrap_or(i128::MIN)
939 .saturating_add(margin);
940 if utc < from {
941 return Candidate::Wait(from);
942 }
943 return Candidate::Ready(seg.start_us);
944 }
945 Candidate::Idle
946 }
947
948 fn expire_before_request(&mut self, pi: usize, li: usize, utc: i128) {
949 let (oldest, next) = {
950 let lane = &self.periods[pi].lanes[li];
951 let Some(rep) = lane.current() else { return };
952 let Some(plan) = rep.plan.as_ref() else {
953 return;
954 };
955 (oldest_in_window(plan, utc), rep.next_issue)
956 };
957 self.outputs.push_back(Output::Notice(Notice::FellBehind));
958 let lane = &mut self.periods[pi].lanes[li];
959 lane.expired_run = lane.expired_run.saturating_add(1);
960 let rejoin = lane.expired_run >= EXPIRED_REJOIN_RUN || oldest.is_none();
961 match oldest {
962 Some(o) if o > next && !rejoin => {
963 let ri = lane.reps.len() - 1;
964 lane.reps[ri].next_issue = o;
965 let last = lane.reps[ri]
966 .plan
967 .as_ref()
968 .and_then(|p| o.checked_sub(1).and_then(|i| p.get(i)))
969 .map(|s| s.time_ticks);
970 lane.reps[ri].last_time = last;
971 push_marker(lane);
972 }
973 _ => self.rejoin_lane(pi, li),
974 }
975 }
976
977 fn rejoin_lane(&mut self, pi: usize, li: usize) {
978 let lane = &mut self.periods[pi].lanes[li];
979 lane.expired_run = 0;
980 if let Some(rep) = lane.reps.last_mut() {
981 rep.joined = false;
982 }
983 push_marker(lane);
984 }
985
986 pub(super) fn segment_delivered(&mut self, id: ResourceId) {
987 let rep = id.rep();
988 if let Some(live) = self.live.as_mut() {
989 live.early.remove(&id);
990 live.missing_seen.remove(&rep);
991 }
992 if let Some((pi, li, _)) = self.rep_loc.get(&rep).copied()
993 && let Some(lane) = self.periods.get_mut(pi).and_then(|p| p.lanes.get_mut(li))
994 {
995 lane.expired_run = 0;
996 }
997 }
998
999 pub(super) fn live_segment_failure(
1002 &mut self,
1003 now_us: u64,
1004 id: ResourceId,
1005 failure: Failure,
1006 ) -> Result<FailureClass> {
1007 let status = status_of(failure);
1008 if matches!(
1009 status,
1010 Some(STATUS_NOT_FOUND | STATUS_GONE | STATUS_TOO_EARLY)
1011 ) && let Some(utc) = self.utc_now(now_us)
1012 {
1013 let (from, until) = self
1014 .reqs
1015 .get(&id)
1016 .map_or((None, None), |r| (r.avail_from_us, r.avail_until_us));
1017 let grace = i128::from(self.dash.early_grace_us);
1018 let past_window = until.is_some_and(|u| utc > u);
1019 let refused_past_window = past_window
1020 && status == Some(STATUS_TOO_EARLY)
1021 && self
1022 .clock
1023 .anchor()
1024 .is_some_and(|a| a.source == ClockSource::UtcTiming);
1025 let early = !refused_past_window
1026 && (status == Some(STATUS_TOO_EARLY)
1027 || from.is_some_and(|f| utc < f.saturating_add(grace)));
1028 if early {
1029 return Ok(self.early_wait(now_us, id, from, utc));
1030 }
1031 if past_window {
1032 return Ok(self.expired_segment(now_us, id, utc));
1033 }
1034 }
1035 let kind = failure_kind(failure);
1036 if kind == FailureKind::Missing {
1037 let first = self
1038 .live
1039 .as_mut()
1040 .is_some_and(|l| l.missing_seen.insert(id.rep()));
1041 if first {
1042 self.force_refresh(now_us);
1043 self.request_resync(now_us);
1044 }
1045 }
1046 Ok(self.count_failure(now_us, id, kind, status))
1047 }
1048
1049 fn early_wait(
1050 &mut self,
1051 now_us: u64,
1052 id: ResourceId,
1053 from: Option<i128>,
1054 utc: i128,
1055 ) -> FailureClass {
1056 self.inflight.remove(&id);
1057 let base = self.dash.early_retry_us;
1058 let cap = EARLY_RETRY_CAP_US.max(base);
1059 let retry_at = {
1060 let Some(live) = self.live.as_mut() else {
1061 return FailureClass::Retry {
1062 attempt: 1,
1063 kind: FailureKind::TooEarly,
1064 };
1065 };
1066 let e = live.early.entry(id).or_default();
1067 e.attempts = e.attempts.saturating_add(1);
1068 let shift = e.attempts.saturating_sub(1).min(EARLY_SHIFT_MAX);
1069 let delay = base.saturating_mul(1u64 << shift).min(cap);
1070 let at = now_us.saturating_add(delay);
1071 e.retry_at = Some(at);
1072 at
1073 };
1074 if from.is_some_and(|f| utc >= f) {
1075 self.request_resync(now_us);
1076 }
1077 FailureClass::NotYetAvailable {
1078 retry_at_us: retry_at,
1079 }
1080 }
1081
1082 fn expired_segment(&mut self, _now_us: u64, id: ResourceId, utc: i128) -> FailureClass {
1083 self.inflight.remove(&id);
1084 self.failures.remove(&id);
1085 if let Some(live) = self.live.as_mut() {
1086 live.early.remove(&id);
1087 }
1088 self.mark_done(id);
1089 if let Some(slot) = self.slot_mut(id) {
1090 slot.state = SlotState::Skipped { marker: true };
1091 }
1092 self.outputs.push_back(Output::Notice(Notice::FellBehind));
1093 if let Some((pi, li, _)) = self.rep_loc.get(&id.rep()).copied() {
1094 let (oldest, next) = {
1095 let lane = &self.periods[pi].lanes[li];
1096 match lane.current().and_then(|r| r.plan.as_ref().map(|p| (r, p))) {
1097 Some((r, p)) => (oldest_in_window(p, utc), r.next_issue),
1098 None => (None, 0),
1099 }
1100 };
1101 let lane = &mut self.periods[pi].lanes[li];
1102 lane.expired_run = lane.expired_run.saturating_add(1);
1103 let rejoin = lane.expired_run >= EXPIRED_REJOIN_RUN || oldest.is_none();
1104 match oldest {
1105 Some(o) if o > next && !rejoin => {
1106 let ri = lane.reps.len() - 1;
1107 lane.reps[ri].next_issue = o;
1108 let last = lane.reps[ri]
1109 .plan
1110 .as_ref()
1111 .and_then(|p| o.checked_sub(1).and_then(|i| p.get(i)))
1112 .map(|s| s.time_ticks);
1113 lane.reps[ri].last_time = last;
1114 }
1115 Some(o) if o <= next && !rejoin => {}
1116 _ => self.rejoin_lane(pi, li),
1117 }
1118 }
1119 self.drain();
1120 FailureClass::Expired
1121 }
1122
1123 pub(super) fn give_up_live_segment(
1124 &mut self,
1125 id: ResourceId,
1126 status: Option<u16>,
1127 ) -> FailureClass {
1128 self.inflight.remove(&id);
1129 self.failures.remove(&id);
1130 if let Some(live) = self.live.as_mut() {
1131 live.early.remove(&id);
1132 }
1133 self.mark_done(id);
1134 if let Some(slot) = self.slot_mut(id) {
1135 slot.state = SlotState::Skipped { marker: true };
1136 }
1137 self.outputs.push_back(Output::SegmentLost { id, status });
1138 self.drain();
1139 FailureClass::SegmentSkipped
1140 }
1141
1142 pub(super) fn after_rotation(&mut self, now_us: u64) {
1143 if self.is_live_mpd() {
1144 self.force_refresh(now_us);
1145 }
1146 }
1147
1148 pub(super) fn mpd_failed(&mut self, _now_us: u64) -> u32 {
1149 self.mpd_failures = self.mpd_failures.saturating_add(1);
1150 if let Some(live) = self.live.as_mut() {
1151 live.refresh.awaiting_retry = true;
1152 }
1153 self.mpd_failures
1154 }
1155
1156 fn force_refresh(&mut self, now_us: u64) {
1159 self.force_refresh_after(now_us, 0);
1160 }
1161
1162 fn force_refresh_after(&mut self, now_us: u64, extra_us: u64) {
1163 let floor = self.dash.min_mpd_refresh_us.max(extra_us);
1164 let live_open = self.live_open();
1165 let Some(live) = self.live.as_mut() else {
1166 return;
1167 };
1168 if !live_open {
1169 return;
1170 }
1171 let earliest = live
1172 .refresh
1173 .last_request_us
1174 .map_or(now_us, |t| t.saturating_add(floor))
1175 .max(now_us);
1176 live.refresh.forced_at = Some(live.refresh.forced_at.map_or(earliest, |f| f.min(earliest)));
1177 }
1178
1179 fn schedule_refresh(&mut self, _now_us: u64) {
1180 let floor = self.dash.min_mpd_refresh_us;
1181 let mup = self
1182 .mpd
1183 .as_ref()
1184 .and_then(|m| m.minimum_update_period)
1185 .map(|d| u64::try_from(duration_us(d)).unwrap_or(u64::MAX));
1186 let Some(live) = self.live.as_mut() else {
1187 return;
1188 };
1189 self.mpd_failures = 0;
1190 live.refresh.awaiting_retry = false;
1191 let ex = live.mpd_exchange;
1192 let mid = ex
1193 .sent_us
1194 .saturating_add(ex.received_us.saturating_sub(ex.sent_us) / 2);
1195 live.refresh.next_at = mup.map(|m| mid.saturating_add(m.max(floor)));
1196 }
1197
1198 pub(super) fn refresh_mpd(
1199 &mut self,
1200 now_us: u64,
1201 new: Mpd,
1202 bytes: &[u8],
1203 meta: &ResponseMeta<'_>,
1204 ) {
1205 if self.live.is_none() {
1206 return;
1207 }
1208 self.note_exchange(now_us, meta);
1209 let (stale, same) = {
1210 let live = self.live.as_ref().expect("live present");
1211 let stale = matches!(
1212 (new.publish_time_us, live.refresh.publish_time_us),
1213 (Some(n), Some(o)) if n < o
1214 );
1215 (stale, live.refresh.body == bytes)
1216 };
1217 if stale {
1218 self.schedule_refresh(now_us);
1219 let floor = self.dash.min_mpd_refresh_us;
1220 if let Some(l) = self.live.as_mut() {
1221 l.refresh.next_at = Some(now_us.saturating_add(floor));
1222 }
1223 return;
1224 }
1225 self.schedule_refresh(now_us);
1226 if same {
1227 return;
1228 }
1229 if let Some(l) = self.live.as_mut() {
1230 l.refresh.body = bytes.to_vec();
1231 l.refresh.publish_time_us = new.publish_time_us.or(l.refresh.publish_time_us);
1232 }
1233 self.apply_mpd(now_us, new);
1234 self.refresh_schedule_after_apply(now_us);
1235 }
1236
1237 fn refresh_schedule_after_apply(&mut self, now_us: u64) {
1238 self.schedule_refresh(now_us);
1239 }
1240
1241 fn apply_mpd(&mut self, now_us: u64, new: Mpd) {
1242 if new.periods.is_empty() {
1243 return;
1244 }
1245 let dynamic = new.kind == PresentationType::Dynamic;
1246 if dynamic && new.availability_start_time_us.is_none() {
1247 return;
1248 }
1249 let features = new.features;
1250 let reset = {
1251 let old = self.mpd.as_ref().expect("mpd present");
1252 let ids_differ = matches!((&old.id, &new.id), (Some(a), Some(b)) if a != b);
1253 let has_reset = new
1254 .essential_properties
1255 .iter()
1256 .chain(new.supplemental_properties.iter())
1257 .any(|d| d.scheme_id_uri == SCHEME_RESET);
1258 let old_dynamic = old.kind == PresentationType::Dynamic;
1259 (old_dynamic
1260 && dynamic
1261 && old.availability_start_time_us != new.availability_start_time_us)
1262 || ids_differ
1263 || has_reset
1264 };
1265 self.mpd = Some(new);
1266 if features.patch_location {
1267 self.notice_once(Notice::PatchIgnored);
1268 }
1269 if features.event_stream || features.inband_event_stream {
1270 self.notice_once(Notice::EventStreamIgnored);
1271 }
1272 if reset || !self.remap_periods() {
1273 self.reset_presentation(now_us);
1274 return;
1275 }
1276 self.replan_all();
1277 self.drain();
1278 }
1279
1280 fn remap_periods(&mut self) -> bool {
1281 let Some(mpd) = self.mpd.as_ref() else {
1282 return false;
1283 };
1284 let timings = period_timings(mpd);
1285 let mut moves: Vec<(usize, usize)> = Vec::new();
1286 for (pi, run) in self.periods.iter().enumerate() {
1287 if run.finished {
1288 continue;
1289 }
1290 let found = mpd
1291 .periods
1292 .iter()
1293 .enumerate()
1294 .position(|(i, p)| match (&run.id, &p.id) {
1295 (Some(a), Some(b)) => a == b,
1296 (None, None) => timings.get(i).and_then(|t| t.start_us) == Some(run.start_us),
1297 _ => false,
1298 });
1299 match found {
1300 Some(ni) => moves.push((pi, ni)),
1301 None => return false,
1302 }
1303 }
1304 let mut vanished: Vec<(usize, usize)> = Vec::new();
1305 for (pi, ni) in moves {
1306 let policy = self.cfg.policy.clone();
1307 let mpd = self.mpd.as_ref().expect("mpd present");
1308 let selection = select(mpd, ni, &policy);
1309 let period = &mpd.periods[ni];
1310 let run = &mut self.periods[pi];
1311 run.index = ni;
1312 let mut kept_audio: Option<Option<RepRef>> = None;
1313 for (li, lane) in run.lanes.iter_mut().enumerate() {
1314 let audio_lane = lane.kind == SetClass::Audio;
1315 let Some(ri) = lane.reps.len().checked_sub(1) else {
1316 continue;
1317 };
1318 let rep = &mut lane.reps[ri];
1319 let new_set = period
1320 .adaptation_sets
1321 .iter()
1322 .position(|s| match rep.set_id {
1323 Some(id) => s.id == Some(id),
1324 None => false,
1325 });
1326 let new_set = new_set.or_else(|| {
1327 (rep.set_id.is_none() && rep.set_pos < period.adaptation_sets.len())
1328 .then_some(rep.set_pos)
1329 });
1330 let found = new_set.and_then(|si| {
1331 period.adaptation_sets[si]
1332 .representations
1333 .iter()
1334 .position(|r| r.id == rep.rep_key)
1335 .map(|rj| (si, rj))
1336 });
1337 match found {
1338 Some((si, rj)) => {
1339 rep.at = RepRef::at(ni, si, rj);
1340 rep.set_pos = si;
1341 if audio_lane {
1342 kept_audio = Some((!rep.dead).then_some(rep.at));
1343 }
1344 if let Some((p, s, r)) = rep.at.resolve(mpd) {
1345 rep.candidates = super::filter_candidates(
1346 representation_candidates(mpd, p, s, r),
1347 self.doc_url.as_ref(),
1348 self.dash.cross_origin_urls,
1349 );
1350 }
1351 }
1352 None => vanished.push((pi, li)),
1353 }
1354 }
1355 let chosen = run
1356 .lanes
1357 .iter()
1358 .filter_map(|l| l.current())
1359 .map(|r| r.at)
1360 .collect::<Vec<_>>();
1361 run.selection = selection;
1362 if let Some(audio) = kept_audio {
1363 run.selection.chosen_audio = audio;
1364 }
1365 for at in chosen {
1366 if let Some(pos) = run.selection.variants.iter().position(|v| *v == at) {
1367 run.selection.chosen_variant = Some(pos);
1368 }
1369 }
1370 }
1371 for (pi, li) in vanished {
1372 self.replace_vanished(pi, li);
1373 }
1374 true
1375 }
1376
1377 fn replace_vanished(&mut self, pi: usize, li: usize) {
1378 let (kind, index) = {
1379 let lane = &self.periods[pi].lanes[li];
1380 (lane.kind, self.periods[pi].index)
1381 };
1382 let replacement = {
1383 let sel = &self.periods[pi].selection;
1384 match kind {
1385 SetClass::Video => sel
1386 .chosen_variant
1387 .and_then(|i| sel.variants.get(i).copied()),
1388 _ => sel.chosen_audio,
1389 }
1390 };
1391 let switch_us = {
1392 let lane = &self.periods[pi].lanes[li];
1393 lane.current()
1394 .and_then(|c| c.plan.as_ref().and_then(|p| p.get(c.next_issue)))
1395 .map(|s| s.start_us)
1396 };
1397 let Some(at) = replacement.filter(|a| a.period == index) else {
1398 if let Some(rep) = self.periods[pi].lanes[li].reps.last_mut() {
1399 rep.dead = true;
1400 }
1401 return;
1402 };
1403 match self.build_rep(at) {
1404 Ok(mut rep) => {
1405 if let (Some(plan), Some(us)) = (rep.plan.as_ref(), switch_us) {
1406 rep.next_issue = plan.index_at_or_before(us).unwrap_or(0);
1407 rep.joined = true;
1408 rep.last_time = rep
1409 .next_issue
1410 .checked_sub(1)
1411 .and_then(|i| plan.get(i))
1412 .map(|s| s.time_ticks);
1413 }
1414 let ri = self.periods[pi].lanes[li].reps.len();
1415 push_marker(&mut self.periods[pi].lanes[li]);
1416 self.periods[pi].lanes[li].reps.push(rep);
1417 self.periods[pi].issue_done = false;
1418 self.register_rep(pi, li, ri);
1419 }
1420 Err(cause) => {
1421 self.outputs.push_back(Output::TrackDropped {
1422 period: index,
1423 set: at.set,
1424 rep: Some(at.rep),
1425 cause,
1426 });
1427 if let Some(rep) = self.periods[pi].lanes[li].reps.last_mut() {
1428 rep.dead = true;
1429 }
1430 }
1431 }
1432 }
1433
1434 pub(super) fn replan_all(&mut self) {
1435 let mut targets: Vec<(usize, usize, usize)> = Vec::new();
1436 for (pi, run) in self.periods.iter().enumerate() {
1437 if run.finished {
1438 continue;
1439 }
1440 for (li, lane) in run.lanes.iter().enumerate() {
1441 if let Some(ri) = lane.reps.len().checked_sub(1)
1442 && !lane.reps[ri].dead
1443 {
1444 targets.push((pi, li, ri));
1445 }
1446 }
1447 }
1448 for (pi, li, ri) in targets {
1449 self.replan_rep(pi, li, ri);
1450 }
1451 }
1452
1453 fn replan_rep(&mut self, pi: usize, li: usize, ri: usize) {
1454 let (at, has_index) = {
1455 let rep = &self.periods[pi].lanes[li].reps[ri];
1456 (rep.at, rep.index.is_some())
1457 };
1458 if has_index {
1459 return;
1460 }
1461 let planned = {
1462 let mpd = self.mpd.as_ref().expect("mpd present");
1463 plan_representation(mpd, at, &self.plan_options())
1464 };
1465 if let Ok(p) = planned {
1466 let marker = {
1467 let rep = &mut self.periods[pi].lanes[li].reps[ri];
1468 if rep.joined {
1469 let bound = p.plan.len().unwrap_or(u64::MAX / 2);
1470 rep.next_issue = match rep.last_time {
1471 Some(t) => p.plan.leading_true(bound, |s| s.time_ticks <= t),
1472 None => 0,
1473 };
1474 }
1475 rep.plan = Some(p.plan);
1476 rep.joined && rep.next_issue == 0 && rep.last_time.is_some()
1477 };
1478 if marker {
1479 push_marker(&mut self.periods[pi].lanes[li]);
1480 }
1481 }
1482 }
1483
1484 fn reset_presentation(&mut self, now_us: u64) {
1485 self.periods.clear();
1486 self.issue = 0;
1487 self.deliver = 0;
1488 self.queue.clear();
1489 self.reqs.clear();
1490 self.inflight.clear();
1491 self.parked.clear();
1492 self.failures.clear();
1493 self.done.clear();
1494 self.remembered_done.clear();
1495 self.outstanding = 0;
1496 self.rep_loc.clear();
1497 self.prev = None;
1498 self.inits.clear();
1499 if let Some(live) = self.live.as_mut() {
1500 live.early.clear();
1501 live.missing_seen.clear();
1502 }
1503 self.on_clock_ready(now_us);
1504 }
1505}
1506
1507fn push_marker(lane: &mut Lane) {
1508 if !lane
1509 .queue
1510 .back()
1511 .is_some_and(|s| matches!(s.state, SlotState::Marker))
1512 {
1513 lane.queue.push_back(Slot {
1514 id: None,
1515 rep: 0,
1516 plan_index: 0,
1517 state: SlotState::Marker,
1518 });
1519 }
1520}