atlas_tier/
mem.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! Host-RAM reference impls: [`VecSlotArena`] (hot tier) and [`MemSwapStore`]
4//! (cold tier).
5
6use std::collections::HashMap;
7
8use anyhow::{Result, bail};
9
10use crate::traits::{SlotArena, SwapStore};
11
12/// Host-RAM [`SlotArena`] over one flat `Vec<u8>`. The hot tier for in-process
13/// consumers — e.g. the unified SSM spill store's RAM cache. Allocates
14/// `slot_bytes * num_slots` up front.
15pub struct VecSlotArena {
16    buf: Vec<u8>,
17    slot_bytes: usize,
18    n: usize,
19}
20
21impl VecSlotArena {
22    pub fn new(slot_bytes: usize, num_slots: usize) -> Self {
23        Self {
24            buf: vec![0u8; slot_bytes * num_slots],
25            slot_bytes,
26            n: num_slots,
27        }
28    }
29}
30
31impl SlotArena for VecSlotArena {
32    fn slot_bytes(&self) -> usize {
33        self.slot_bytes
34    }
35    fn num_slots(&self) -> usize {
36        self.n
37    }
38    fn read_slot(&self, slot: usize, out: &mut [u8]) -> Result<()> {
39        if slot >= self.n || out.len() != self.slot_bytes {
40            bail!("VecSlotArena::read_slot({slot}) out of range / size mismatch");
41        }
42        let o = slot * self.slot_bytes;
43        out.copy_from_slice(&self.buf[o..o + self.slot_bytes]);
44        Ok(())
45    }
46    fn write_slot(&mut self, slot: usize, bytes: &[u8]) -> Result<()> {
47        if slot >= self.n || bytes.len() != self.slot_bytes {
48            bail!("VecSlotArena::write_slot({slot}) out of range / size mismatch");
49        }
50        let o = slot * self.slot_bytes;
51        self.buf[o..o + self.slot_bytes].copy_from_slice(bytes);
52        Ok(())
53    }
54}
55
56/// Host-RAM [`SwapStore`] over a `HashMap`. Records live in ordinary heap
57/// memory — the "swap" tier when no NVMe directory is configured (unbounded,
58/// still LRU-ordered by the residency).
59pub struct MemSwapStore {
60    recs: HashMap<usize, Vec<u8>>,
61    record_bytes: usize,
62}
63
64impl MemSwapStore {
65    pub fn new(record_bytes: usize) -> Self {
66        Self {
67            recs: HashMap::new(),
68            record_bytes,
69        }
70    }
71}
72
73impl SwapStore for MemSwapStore {
74    fn record_bytes(&self) -> usize {
75        self.record_bytes
76    }
77    fn write_record(&mut self, disk_slot: usize, bytes: &[u8]) -> Result<()> {
78        if bytes.len() != self.record_bytes {
79            bail!(
80                "MemSwapStore::write_record: {} bytes, expected {}",
81                bytes.len(),
82                self.record_bytes
83            );
84        }
85        self.recs.insert(disk_slot, bytes.to_vec());
86        Ok(())
87    }
88    fn read_record(&self, disk_slot: usize, out: &mut [u8]) -> Result<()> {
89        match self.recs.get(&disk_slot) {
90            Some(v) => {
91                out.copy_from_slice(v);
92                Ok(())
93            }
94            None => bail!("MemSwapStore: no record {disk_slot}"),
95        }
96    }
97    fn discard_record(&mut self, disk_slot: usize) {
98        self.recs.remove(&disk_slot);
99    }
100}