Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

psx_residency/
slots.rs

1//! Fixed-slot residency policy: a handful of equally sized private arenas, a
2//! wishlist, and a stored-then-decoded pipeline.
3//!
4//! Lifted from hk-psx `game/src/room_residency.rs` (a policy independent of
5//! the CD transport and of GPU calls) and made generic over the slot count and
6//! the wishlist length. The logic and its tests carry over unchanged; what
7//! changed is naming (`loading` is [`SlotResidency::is_loading`], `stored` is
8//! [`SlotResidency::find_stored`], `Residency` is [`SlotResidency`]) and
9//! layout. Region ids are plain `usize`; [`EMPTY`] means none.
10//!
11//! A slot is empty, being read or decoded (`loading`), holding completed but
12//! unverified bytes (`stored`, `len == 0`), or holding a verified room (`len`
13//! non-zero). Only a decoder may turn stored bytes into a verified room, so
14//! completed transport data can never be mistaken for one. The current slot,
15//! the slot an upload is reading from (`protected`) and every loading slot are
16//! never reused. The wishlist ranks speculative targets: a lower-priority
17//! speculative read never displaces an earlier wishlist entry, and an urgent
18//! read (the room needed now) may.
19//!
20//! Use [`crate::Residency`] when payloads differ in size, several pools or
21//! categories are in play, or distance should drive eviction; use this when
22//! the game keeps a few interchangeable slots.
23
24/// Sentinel region id: none.
25pub const EMPTY: usize = usize::MAX;
26
27/// One arena slot.
28#[derive(Copy, Clone)]
29pub struct Slot {
30    /// Region held (verified or stored), or [`EMPTY`].
31    pub region: usize,
32    /// Verified length in bytes; zero while the slot is empty or only stored.
33    pub len: usize,
34    used: u32,
35}
36
37impl Slot {
38    const fn new() -> Self {
39        Self {
40            region: EMPTY,
41            len: 0,
42            used: 0,
43        }
44    }
45}
46
47/// Residency over `SLOTS` arenas with a wishlist of `WANTED` regions (hk-psx
48/// uses 5 and 4: the current room's slot plus four others).
49pub struct SlotResidency<const SLOTS: usize, const WANTED: usize> {
50    /// The arenas.
51    pub slots: [Slot; SLOTS],
52    /// Index of the slot holding the room being shown.
53    pub current: usize,
54    protected: usize,
55    loading: [usize; SLOTS],
56    wanted: [usize; WANTED],
57    clock: u32,
58    decode_ready: usize,
59}
60
61impl<const SLOTS: usize, const WANTED: usize> Default for SlotResidency<SLOTS, WANTED> {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67impl<const SLOTS: usize, const WANTED: usize> SlotResidency<SLOTS, WANTED> {
68    /// All slots empty, nothing wanted.
69    pub const fn new() -> Self {
70        Self {
71            slots: [Slot::new(); SLOTS],
72            current: 0,
73            protected: EMPTY,
74            loading: [EMPTY; SLOTS],
75            wanted: [EMPTY; WANTED],
76            clock: 0,
77            decode_ready: EMPTY,
78        }
79    }
80
81    /// Slot holding the verified room `region`.
82    pub fn find(&self, region: usize) -> Option<usize> {
83        if region == EMPTY {
84            None
85        } else {
86            self.slots
87                .iter()
88                .position(|s| s.region == region && s.len != 0)
89        }
90    }
91
92    /// Slot holding completed but unverified bytes of `region`. A completed
93    /// CD payload is not yet a room: `len == 0` is exclusively stored data;
94    /// only a private decoder may turn it into a verified resident entry.
95    pub fn find_stored(&self, region: usize) -> Option<usize> {
96        if region == EMPTY {
97            None
98        } else {
99            self.slots
100                .iter()
101                .position(|s| s.region == region && s.len == 0)
102        }
103    }
104
105    fn refresh_decode(&mut self) {
106        self.decode_ready = self
107            .wanted
108            .iter()
109            .find_map(|&r| self.find_stored(r))
110            .unwrap_or(EMPTY);
111    }
112
113    /// The stored slot to decode next: `demand` if it is stored, otherwise the
114    /// highest-ranked wishlist entry that is.
115    pub fn next_decode(&self, demand: usize) -> Option<usize> {
116        self.find_stored(demand).or(if self.decode_ready == EMPTY {
117            None
118        } else {
119            Some(self.decode_ready)
120        })
121    }
122
123    /// Like [`SlotResidency::next_decode`], while a read of `reading` is live.
124    /// Preserves speculative read/decode overlap: only an explicit unread
125    /// urgent demand can hold the idle decoder while the transport is live.
126    pub fn next_decode_during_read(&self, demand: usize, reading: usize) -> Option<usize> {
127        let slot = self.next_decode(demand)?;
128        if self.slots[slot].region == demand || !self.is_loading(reading) {
129            return Some(slot);
130        }
131        // The demand may await the current transfer's Pause/cancel. Mere
132        // wishlist priority is not urgency and must not serialize speculation.
133        if demand != EMPTY && self.find(demand).is_none() {
134            None
135        } else {
136            Some(slot)
137        }
138    }
139
140    /// Slot `i` finished reading `region`. The caller must have observed the
141    /// transport done, including its Pause retirement. This releases the IRQ
142    /// lease but does not publish or pin unverified bytes.
143    pub fn park_completed(&mut self, i: usize, region: usize) {
144        assert_eq!(self.loading[i], region);
145        assert_ne!(region, EMPTY);
146        assert_ne!(i, self.current);
147        assert_ne!(i, self.protected);
148        self.loading[i] = EMPTY;
149        self.slots[i].region = region;
150        self.slots[i].len = 0;
151        self.touch(i);
152        self.refresh_decode();
153    }
154
155    /// Hand stored slot `i` to a decoder: it becomes loading again. Returns
156    /// the region.
157    pub fn claim_stored(&mut self, i: usize) -> usize {
158        let region = self.slots[i].region;
159        assert_ne!(region, EMPTY);
160        assert_eq!(self.slots[i].len, 0);
161        assert_eq!(self.loading[i], EMPTY);
162        assert_ne!(i, self.current);
163        assert_ne!(i, self.protected);
164        self.slots[i] = Slot::new();
165        self.loading[i] = region;
166        self.refresh_decode();
167        region
168    }
169
170    /// Protect the slot holding `region` from reuse while an upload reads it
171    /// (`None` clears the protection). Returns `false`, keeping any earlier
172    /// protection, when `region` is not resident.
173    pub fn protect_upload(&mut self, region: Option<usize>) -> bool {
174        match region {
175            None => {
176                self.protected = EMPTY;
177                true
178            }
179            Some(region) => match self.find(region) {
180                Some(i) => {
181                    self.protected = i;
182                    true
183                }
184                None => false,
185            },
186        }
187    }
188
189    /// Replace the wishlist (highest priority first). The current region,
190    /// duplicates and [`EMPTY`] are dropped; extra entries are ignored.
191    pub fn set_wanted(&mut self, regions: &[usize]) {
192        let previous = self.wanted;
193        self.wanted = [EMPTY; WANTED];
194        let mut count = 0;
195        for &region in regions {
196            if region == EMPTY
197                || region == self.slots[self.current].region
198                || self.wanted.contains(&region)
199            {
200                continue;
201            }
202            self.wanted[count] = region;
203            count += 1;
204            if count == self.wanted.len() {
205                break;
206            }
207        }
208        if self.wanted != previous {
209            self.refresh_decode();
210        }
211    }
212
213    /// Drop completed or parked payloads outside the keep set. Current,
214    /// protected and in-flight slots remain untouched. Returns how many slots
215    /// were freed.
216    pub fn evict_unwanted(&mut self, keep: &[usize]) -> usize {
217        let mut removed = 0;
218        for i in 0..SLOTS {
219            if i == self.current || i == self.protected || self.loading[i] != EMPTY {
220                continue;
221            }
222            let region = self.slots[i].region;
223            if region == EMPTY || keep.contains(&region) {
224                continue;
225            }
226            self.slots[i] = Slot::new();
227            removed += 1;
228        }
229        if removed != 0 {
230            self.refresh_decode();
231        }
232        removed
233    }
234
235    /// Whether `region` is being read or decoded.
236    pub fn is_loading(&self, region: usize) -> bool {
237        region != EMPTY && self.loading.contains(&region)
238    }
239
240    /// Give up the loading lease on slot `i` (a cancelled read or a failed
241    /// decode); the slot stays empty.
242    pub fn release(&mut self, i: usize) {
243        self.loading[i] = EMPTY;
244    }
245
246    /// The first wishlist entry that is not resident, stored or loading and
247    /// has a slot it may take.
248    pub fn next_request(&self) -> Option<usize> {
249        self.wanted.iter().copied().find(|&r| {
250            r != EMPTY
251                && self.find(r).is_none()
252                && self.find_stored(r).is_none()
253                && !self.is_loading(r)
254                && self.victim(r, false).is_some()
255        })
256    }
257
258    fn touch(&mut self, i: usize) {
259        // Rebase before wraparound; preserve recent ordering over long sessions.
260        self.clock = self.clock.saturating_add(1);
261        if self.clock == u32::MAX {
262            for slot in &mut self.slots {
263                slot.used = slot.used.saturating_sub(u32::MAX / 2);
264            }
265            self.clock -= u32::MAX / 2;
266        }
267        self.slots[i].used = self.clock;
268    }
269
270    /// Make the verified room `region` current. `Some(changed)` where
271    /// `changed` says whether the current slot moved; `None` when `region` is
272    /// not resident.
273    pub fn select(&mut self, region: usize) -> Option<bool> {
274        let i = self.find(region)?;
275        let changed = i != self.current;
276        self.current = i;
277        self.touch(i);
278        Some(changed)
279    }
280
281    /// Reserve one private arena. Current and an in-progress GPU upload are
282    /// never evicted; prefer empty, then no-longer-requested, then least
283    /// recent.
284    fn victim(&self, region: usize, urgent: bool) -> Option<usize> {
285        if self.is_loading(region) || self.find_stored(region).is_some() {
286            return None;
287        }
288        let priority = self
289            .wanted
290            .iter()
291            .position(|&r| r == region)
292            .unwrap_or(usize::MAX);
293        let mut victim = None;
294        for i in 0..SLOTS {
295            if i == self.current || i == self.protected || self.loading[i] != EMPTY {
296                continue;
297            }
298            let s = &self.slots[i];
299            // A lower-priority speculative target must not displace an earlier
300            // wishlist entry when a pinned upload temporarily reduces capacity.
301            if !urgent
302                && s.region != EMPTY
303                && self
304                    .wanted
305                    .iter()
306                    .position(|&r| r == s.region)
307                    .is_some_and(|p| p <= priority)
308            {
309                continue;
310            }
311            let score = (s.region != EMPTY, self.wanted.contains(&s.region), s.used);
312            if victim.is_none_or(|(_, best)| score < best) {
313                victim = Some((i, score));
314            }
315        }
316        victim.map(|(i, _)| i)
317    }
318
319    /// Whether [`SlotResidency::reserve`] would succeed.
320    pub fn can_reserve(&self, region: usize, urgent: bool) -> bool {
321        self.victim(region, urgent).is_some()
322    }
323
324    /// Take a slot for reading `region` (its previous contents are dropped).
325    /// `urgent` lets the read displace wishlist entries.
326    pub fn reserve(&mut self, region: usize, urgent: bool) -> Option<usize> {
327        let i = self.victim(region, urgent)?;
328        self.slots[i] = Slot::new();
329        self.loading[i] = region;
330        self.refresh_decode();
331        Some(i)
332    }
333
334    /// Slot `i` now holds the verified room `region` of `len` bytes.
335    pub fn admit(&mut self, i: usize, region: usize, len: usize) {
336        assert_eq!(self.loading[i], region);
337        assert_ne!(len, 0);
338        self.loading[i] = EMPTY;
339        self.slots[i].region = region;
340        self.slots[i].len = len;
341        self.touch(i);
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348
349    type Residency = SlotResidency<5, 4>;
350
351    fn load(r: &mut Residency, region: usize) -> usize {
352        let i = r.reserve(region, true).unwrap();
353        r.admit(i, region, 100);
354        i
355    }
356
357    #[test]
358    fn decode_admission_only_waits_for_an_urgent_unread_demand_with_live_transport() {
359        let mut r = Residency::new();
360        load(&mut r, 10);
361        r.select(10);
362        let stored = r.reserve(12, true).unwrap();
363        r.park_completed(stored, 12);
364        r.set_wanted(&[11, 12]);
365        // Missing 11, and even a stale reading id without a private lease, are
366        // insufficient reasons to hold a complete candidate indefinitely.
367        assert_eq!(r.next_decode_during_read(EMPTY, EMPTY), Some(stored));
368        assert_eq!(r.next_decode_during_read(EMPTY, 11), Some(stored));
369        let read = r.reserve(11, true).unwrap();
370        assert_eq!(r.next_decode_during_read(EMPTY, 11), Some(stored));
371        assert_eq!(r.next_decode_during_read(11, 11), None);
372        assert_eq!(r.next_decode_during_read(12, 11), Some(stored));
373        r.set_wanted(&[12, 11]);
374        assert_eq!(r.next_decode_during_read(EMPTY, 11), Some(stored));
375        // An unread urgent destination may await the live transport's cancel.
376        assert_eq!(r.next_decode_during_read(13, 11), None);
377        r.release(read);
378        assert_eq!(r.next_decode_during_read(13, EMPTY), Some(stored));
379        // A read removed from the plan no longer outranks a wanted candidate.
380        r.reserve(11, true).unwrap();
381        r.set_wanted(&[12]);
382        assert_eq!(r.next_decode_during_read(EMPTY, 11), Some(stored));
383    }
384
385    #[test]
386    fn obsolete_completed_payload_is_evictable_but_never_visible_as_a_room() {
387        let mut r = Residency::new();
388        load(&mut r, 10);
389        r.select(10).unwrap();
390        load(&mut r, 11);
391        load(&mut r, 12);
392        load(&mut r, 13);
393        r.protect_upload(Some(12));
394        let old = r.reserve(14, true).unwrap();
395        r.set_wanted(&[11, 12, 13, 15]);
396        assert_eq!(r.next_request(), None);
397        r.park_completed(old, 14);
398        assert!(!r.is_loading(14));
399        assert_eq!(r.find_stored(14), Some(old));
400        assert!(r.find(14).is_none());
401        assert_eq!(r.select(14), None);
402        assert!(!r.protect_upload(Some(14)));
403        assert_eq!(r.next_decode(EMPTY), None);
404        assert_eq!(r.next_request(), Some(15));
405        let next = r.reserve(15, false).unwrap();
406        assert_eq!(next, old);
407        assert!(r.find_stored(14).is_none());
408        assert_eq!(r.next_decode(EMPTY), None);
409        for id in [10, 11, 12, 13] {
410            assert!(r.find(id).is_some());
411        }
412    }
413
414    #[test]
415    fn brief_wishlist_reversal_reuses_completed_bytes_without_another_cd_request() {
416        let mut r = Residency::new();
417        load(&mut r, 10);
418        r.select(10).unwrap();
419        let read = r.reserve(11, true).unwrap();
420        let decoder = r.reserve(12, true).unwrap();
421        r.set_wanted(&[13]);
422        r.park_completed(read, 11);
423        assert!(r.is_loading(12));
424        assert_eq!(r.next_decode(EMPTY), None);
425        r.set_wanted(&[11, 13]);
426        assert_eq!(r.next_decode(EMPTY), Some(read));
427        assert_eq!(r.next_request(), Some(13));
428        assert!(!r.can_reserve(11, true));
429        assert_eq!(r.claim_stored(read), 11);
430        assert!(r.find_stored(11).is_none());
431        assert!(r.is_loading(11) && r.is_loading(12));
432        assert_eq!(r.next_decode(EMPTY), None);
433        // Bytes remain private until the actual decoder confirms both hashes.
434        assert!(r.find(11).is_none());
435        r.admit(read, 11, 100);
436        assert!(r.find(11).is_some());
437        r.release(decoder);
438        assert!(r.find(12).is_none());
439    }
440
441    #[test]
442    fn decoder_priority_follows_demand_then_latest_wishlist_without_reordering_cd_leases() {
443        let mut r = Residency::new();
444        load(&mut r, 10);
445        r.select(10).unwrap();
446        let a = r.reserve(11, true).unwrap();
447        let b = r.reserve(12, true).unwrap();
448        r.park_completed(a, 11);
449        r.park_completed(b, 12);
450        r.set_wanted(&[11, 12]);
451        assert_eq!(r.next_decode(EMPTY), Some(a));
452        assert_eq!(r.next_decode(12), Some(b));
453        assert_eq!(r.claim_stored(b), 12);
454        assert_eq!(r.next_decode(EMPTY), Some(a));
455        r.set_wanted(&[13]);
456        assert_eq!(r.next_decode(EMPTY), None);
457        assert_eq!(r.next_decode(11), Some(a)); // Urgent, even outside the wishlist.
458        r.release(b);
459        assert!(r.find(12).is_none() && r.find_stored(12).is_none());
460        assert_eq!(r.next_request(), Some(13));
461    }
462
463    #[test]
464    fn wanted_completed_payload_survives_lower_priority_capacity_pressure() {
465        let mut r = Residency::new();
466        load(&mut r, 10);
467        r.select(10).unwrap();
468        let pinned = load(&mut r, 99);
469        r.protect_upload(Some(99));
470        let decode = r.reserve(12, true).unwrap();
471        let read = r.reserve(11, true).unwrap();
472        load(&mut r, 13);
473        r.set_wanted(&[11, 12, 13, 14]);
474        r.park_completed(read, 11);
475        assert_eq!(r.next_request(), None);
476        assert!(!r.can_reserve(14, false));
477        assert_eq!(r.next_decode(EMPTY), Some(read));
478        r.protect_upload(None);
479        let next = r.reserve(14, false).unwrap();
480        assert_eq!(next, pinned);
481        assert_ne!(next, read);
482        assert_ne!(next, decode);
483        assert_eq!(r.next_decode(EMPTY), Some(read));
484    }
485
486    #[test]
487    fn concurrent_decode_and_read_keep_distinct_private_arenas() {
488        let mut r = Residency::new();
489        load(&mut r, 10);
490        r.select(10).unwrap();
491        load(&mut r, 11);
492        r.protect_upload(Some(11));
493        load(&mut r, 14);
494        r.set_wanted(&[14, 12, 13, 15]);
495        let decode = r.reserve(12, false).unwrap();
496        let read = r.reserve(13, false).unwrap();
497        assert_ne!(decode, read);
498        assert!(r.is_loading(12) && r.is_loading(13));
499        assert_eq!(r.reserve(12, true), None);
500        assert!(!r.can_reserve(13, true));
501        assert!(r.find(12).is_none() && r.find(13).is_none());
502        assert_eq!(r.next_request(), None);
503        assert_eq!(r.reserve(15, false), None);
504        assert!(r.can_reserve(15, true)); // Urgent demand may replace unpinned 14.
505        r.admit(decode, 12, 100);
506        assert!(r.find(12).is_some());
507        assert!(r.is_loading(13));
508        r.release(read);
509        assert!(!r.is_loading(13));
510        assert_eq!(r.next_request(), Some(13));
511        assert!(r.find(10).is_some() && r.find(11).is_some());
512    }
513
514    #[test]
515    fn cancelled_read_cannot_release_completed_decode_or_gpu_pin() {
516        let mut r = Residency::new();
517        load(&mut r, 10);
518        r.select(10).unwrap();
519        let decode = r.reserve(11, true).unwrap();
520        let read = r.reserve(12, true).unwrap();
521        r.admit(decode, 11, 100);
522        r.protect_upload(Some(11));
523        r.release(read);
524        let replacement = r.reserve(13, true).unwrap();
525        assert_ne!(replacement, decode);
526        assert!(r.find(11).is_some());
527        assert!(r.is_loading(13));
528    }
529
530    #[test]
531    fn keeps_current_and_background_upload_while_loading_two_ahead() {
532        let mut r = Residency::new();
533        load(&mut r, 10);
534        r.select(10).unwrap();
535        load(&mut r, 11);
536        assert!(r.protect_upload(Some(11)));
537        load(&mut r, 12);
538        load(&mut r, 13);
539        load(&mut r, 14);
540        load(&mut r, 15);
541        assert!(r.find(10).is_some());
542        assert!(r.find(11).is_some());
543        assert!(r.find(12).is_none());
544        assert!(r.find(13).is_some());
545        assert!(r.find(14).is_some());
546    }
547
548    #[test]
549    fn queue_deduplicates_and_skips_already_resident_neighbors() {
550        let mut r = Residency::new();
551        load(&mut r, 10);
552        r.select(10).unwrap();
553        r.set_wanted(&[10, 11, 11, 12, 13, 14]);
554        assert_eq!(r.next_request(), Some(11));
555        load(&mut r, 11);
556        assert_eq!(r.next_request(), Some(12));
557        load(&mut r, 12);
558        assert_eq!(r.next_request(), Some(13));
559        load(&mut r, 13);
560        assert_eq!(r.next_request(), Some(14));
561        load(&mut r, 14);
562        assert_eq!(r.next_request(), None);
563    }
564
565    #[test]
566    fn replaces_unwanted_newer_neighbor_before_requested_older_one() {
567        let mut r = Residency::new();
568        load(&mut r, 10);
569        r.select(10).unwrap();
570        load(&mut r, 11);
571        load(&mut r, 12);
572        load(&mut r, 13);
573        load(&mut r, 14);
574        r.set_wanted(&[11, 12, 15]);
575        load(&mut r, 15);
576        assert!(r.find(11).is_some());
577        assert!(r.find(12).is_some());
578        assert!(r.find(13).is_none());
579    }
580
581    #[test]
582    fn protected_stale_upload_cannot_make_requested_neighbors_thrash() {
583        let mut r = Residency::new();
584        load(&mut r, 10);
585        r.select(10).unwrap();
586        load(&mut r, 11);
587        r.protect_upload(Some(11));
588        r.set_wanted(&[12, 13, 14, 15]);
589        for expected in [12, 13, 14] {
590            assert_eq!(r.next_request(), Some(expected));
591            let i = r.reserve(expected, false).unwrap();
592            r.admit(i, expected, 100);
593        }
594        assert_eq!(r.next_request(), None);
595        assert!(r.reserve(15, false).is_none());
596        r.protect_upload(None);
597        assert_eq!(r.next_request(), Some(15));
598        let i = r.reserve(15, false).unwrap();
599        r.admit(i, 15, 100);
600        for region in [10, 12, 13, 14, 15] {
601            assert!(r.find(region).is_some());
602        }
603    }
604
605    #[test]
606    fn missing_protection_request_preserves_prior_pin() {
607        let mut r = Residency::new();
608        load(&mut r, 10);
609        r.select(10).unwrap();
610        load(&mut r, 11);
611        assert!(r.protect_upload(Some(11)));
612        assert!(!r.protect_upload(Some(99)));
613        for region in 12..20 {
614            load(&mut r, region);
615        }
616        assert!(r.find(11).is_some());
617        assert!(r.protect_upload(None));
618        for region in 20..24 {
619            load(&mut r, region);
620        }
621        assert!(r.find(11).is_none());
622    }
623
624    #[test]
625    fn directed_eviction_keeps_current_upload_and_requested_payloads() {
626        let mut r = Residency::new();
627        load(&mut r, 10);
628        r.select(10).unwrap();
629        load(&mut r, 11);
630        load(&mut r, 12);
631        let parked = r.reserve(13, true).unwrap();
632        r.park_completed(parked, 13);
633        r.protect_upload(Some(11));
634        assert_eq!(r.evict_unwanted(&[12]), 1);
635        assert!(r.find(10).is_some());
636        assert!(r.find(11).is_some());
637        assert!(r.find(12).is_some());
638        assert!(r.find_stored(13).is_none());
639    }
640
641    /// The residency half of hk-format's `stored_rooms` integration test: a
642    /// parked payload becomes visible only when the decoder admits it, and a
643    /// failed decode leaves no trace.
644    #[test]
645    fn parked_payload_is_published_only_by_admit_and_a_failed_decode_leaves_nothing() {
646        for fail in [false, true] {
647            let mut r = Residency::new();
648            let current = r.reserve(0, true).unwrap();
649            r.admit(current, 0, 92);
650            r.select(0);
651            let slot = r.reserve(1, true).unwrap();
652            r.set_wanted(&[2]);
653            r.park_completed(slot, 1);
654            assert_eq!(r.next_decode(EMPTY), None);
655            assert_eq!(r.select(1), None);
656            assert!(!r.protect_upload(Some(1)));
657            assert!(r.find(1).is_none());
658            // A later turn requests the same source; no new reserve/read is possible.
659            r.set_wanted(&[1, 2]);
660            assert_eq!(r.next_decode(EMPTY), Some(slot));
661            assert!(!r.can_reserve(1, true));
662            assert_eq!(r.claim_stored(slot), 1);
663            assert!(r.find(1).is_none() && r.is_loading(1));
664            if fail {
665                r.release(slot);
666                assert!(r.find(1).is_none() && r.find_stored(1).is_none());
667                assert!(r.find(0).is_some());
668            } else {
669                r.admit(slot, 1, 92);
670                assert_eq!(r.select(1), Some(true));
671            }
672        }
673    }
674}