psx_residency/config.rs
1//! Budgets and policy knobs.
2
3use crate::backoff::Backoff;
4use crate::key::Class;
5use crate::pool::Placement;
6
7/// One page pool: its size and how new runs are placed.
8#[derive(Copy, Clone, Debug, PartialEq, Eq)]
9pub struct PoolConfig {
10 /// Pages in the pool (0 disables it).
11 pub capacity_pages: u32,
12 /// Gap choice for new runs.
13 pub placement: Placement,
14}
15
16impl PoolConfig {
17 /// A pool that holds nothing.
18 pub const UNUSED: PoolConfig = PoolConfig {
19 capacity_pages: 0,
20 placement: Placement::FirstFit,
21 };
22}
23
24/// Budget and behaviour of one category.
25#[derive(Copy, Clone, Debug, PartialEq, Eq)]
26pub struct CategoryConfig {
27 /// The pool (see [`Config::pools`]) this category allocates from. Several
28 /// categories may share one.
29 pub pool: u8,
30 /// Most pages this category may hold, across all its entries.
31 pub max_pages: u32,
32 /// Most entries (resident or in flight) this category may hold. 0 disables
33 /// the category.
34 pub max_entries: u16,
35 /// Whether compaction may move this category's unpinned entries. A
36 /// category whose bytes are addressed by hardware or by long-lived raw
37 /// pointers (VRAM, SPU RAM, archetype packs) is not movable.
38 pub movable: bool,
39 /// Whether a landed payload needs an install step (verify, relocate,
40 /// upload) before it is resident. When false a finished read is resident.
41 pub installs: bool,
42 /// Unwanted entries farther than this are free victims; nearer ones are
43 /// in the hysteresis band and go last. Set it to the lead radius plus the
44 /// hysteresis, see [`CategoryConfig::keep_radius_for`].
45 pub keep_radius: u32,
46}
47
48impl CategoryConfig {
49 /// A disabled category.
50 pub const UNUSED: CategoryConfig = CategoryConfig {
51 pool: 0,
52 max_pages: 0,
53 max_entries: 0,
54 movable: false,
55 installs: false,
56 keep_radius: 0,
57 };
58
59 /// The eviction keep radius the design prescribes: the lead radius plus
60 /// a hysteresis of `max(region_width, lead_radius / 4)`, so a player
61 /// standing in a doorway cannot make two regions thrash.
62 pub const fn keep_radius_for(lead_radius: u32, region_width: u32) -> u32 {
63 let quarter = lead_radius / 4;
64 let hysteresis = if region_width > quarter {
65 region_width
66 } else {
67 quarter
68 };
69 lead_radius.saturating_add(hysteresis)
70 }
71}
72
73/// Everything configurable about a [`crate::Residency`]. `CATEGORIES` is both
74/// the number of category numbers in use and the number of pools.
75#[derive(Copy, Clone, Debug, PartialEq, Eq)]
76pub struct Config<const CATEGORIES: usize> {
77 /// Page pools, indexed by pool number.
78 pub pools: [PoolConfig; CATEGORIES],
79 /// Category budgets, indexed by [`crate::Category::number`]. A key whose
80 /// category number is out of range is ignored (and counted).
81 pub categories: [CategoryConfig; CATEGORIES],
82 /// Requests the transport may hold at once (the design's queue depth
83 /// is 2). Cancelling requests do not count.
84 pub max_in_flight: u8,
85 /// Bookkeeping budget per [`crate::Residency::step`], in work units (see
86 /// the crate docs). `u32::MAX` means unlimited.
87 pub step_budget: u32,
88 /// Retry schedule for failed reads and installs.
89 pub backoff: Backoff,
90 /// A wanted entry of the same class as a requester is displaced only when
91 /// it is farther than the requester by more than this (distance units),
92 /// so two entries of nearly equal distance cannot trade places each frame.
93 pub displace_margin: u32,
94 /// Per requester class, the highest victim tier it may evict: 0 is an
95 /// unwanted entry beyond the keep radius, 1 an unwanted entry inside the
96 /// hysteresis band, 2 a wanted entry of lower priority than the
97 /// requester. Indexed by [`Class::as_index`].
98 pub max_victim_tier: [u8; Class::COUNT],
99 /// Per class, whether an entry wanted at that class this frame can never
100 /// be evicted this frame. Indexed by [`Class::as_index`].
101 pub protect_wanted: [bool; Class::COUNT],
102 /// Let a [`Class::Demand`] miss cancel the least urgent in-flight request
103 /// when the transport is at depth.
104 pub cancel_for_demand: bool,
105}
106
107impl<const CATEGORIES: usize> Config<CATEGORIES> {
108 /// All pools and categories disabled, the design's default knobs: queue
109 /// depth 2, unlimited step budget, the grid backoff, tiers
110 /// `[2, 2, 1, 0, 0]`, demand and combat fill protected, cancel for demand.
111 pub const fn new() -> Self {
112 Self {
113 pools: [PoolConfig::UNUSED; CATEGORIES],
114 categories: [CategoryConfig::UNUSED; CATEGORIES],
115 max_in_flight: 2,
116 step_budget: u32::MAX,
117 backoff: Backoff::GRID_DEFAULT,
118 displace_margin: 0,
119 max_victim_tier: [2, 2, 1, 0, 0],
120 protect_wanted: [true, true, false, false, false],
121 cancel_for_demand: true,
122 }
123 }
124}
125
126impl<const CATEGORIES: usize> Default for Config<CATEGORIES> {
127 fn default() -> Self {
128 Self::new()
129 }
130}