Skip to main content

sidereon_core/
artifact_bytes.rs

1//! Backing storage for readers over large binary artifacts.
2//!
3//! The terrain store and the precise-interpolant store are both read by
4//! indexing into a byte span: construction parses only the header, datum tag,
5//! and tile/segment index, and every lookup addresses payload by offset. Neither
6//! reader holds a reference into its own bytes, so the bytes can be owned,
7//! borrowed, or memory-mapped without any of them being a self-referential
8//! struct - the span is derived on demand from whichever backing is present.
9//!
10//! That is what makes the mapped variant safe to add: there is no interior
11//! pointer to keep valid, no drop-order invariant hiding in field declaration
12//! order, and no `unsafe` at any interface boundary.
13//!
14//! # Why mapping matters more than the copy
15//!
16//! Avoiding one `memcpy` is the smaller half. A memory map is demand-paged, so a
17//! reader that queries a geographically local region faults in the handful of
18//! pages covering those tiles and never touches the rest of the file. A
19//! constructor that copies - however the bytes arrive - forfeits that and pays
20//! for the whole artifact on every open. For a 30+ GB terrain store that is the
21//! difference between a working process and one that cannot start.
22
23use std::borrow::Cow;
24
25/// Where a reader's bytes live.
26///
27/// `Mapped` is only available with the `mmap` feature, so the default build
28/// carries no additional dependency and targets where mapping is meaningless
29/// (wasm) simply do not enable it.
30#[derive(Debug, Clone)]
31pub enum ArtifactBytes<'a> {
32    /// A span the caller owns and keeps alive.
33    Borrowed(&'a [u8]),
34    /// A vector this reader owns.
35    Owned(Vec<u8>),
36    /// A read-only memory map this reader owns.
37    ///
38    /// Shared behind an `Arc` so the reader stays cheap to clone; a map is a
39    /// kernel-level resource and duplicating it per clone would be wasteful.
40    #[cfg(feature = "mmap")]
41    Mapped(std::sync::Arc<memmap2::Mmap>),
42}
43
44impl<'a> ArtifactBytes<'a> {
45    /// Borrow the artifact bytes.
46    #[must_use]
47    pub fn as_slice(&self) -> &[u8] {
48        match self {
49            Self::Borrowed(bytes) => bytes,
50            Self::Owned(bytes) => bytes.as_slice(),
51            #[cfg(feature = "mmap")]
52            Self::Mapped(map) => &map[..],
53        }
54    }
55
56    /// Whether these bytes are a memory map rather than a copy in process
57    /// memory.
58    ///
59    /// Exposed so a caller - or a test - can assert that a path-based open
60    /// actually mapped the file instead of reading it. A change that quietly
61    /// relocated the copy would otherwise be indistinguishable from a fix.
62    #[must_use]
63    pub fn is_memory_mapped(&self) -> bool {
64        #[cfg(feature = "mmap")]
65        {
66            matches!(self, Self::Mapped(_))
67        }
68        #[cfg(not(feature = "mmap"))]
69        {
70            false
71        }
72    }
73}
74
75impl AsRef<[u8]> for ArtifactBytes<'_> {
76    fn as_ref(&self) -> &[u8] {
77        self.as_slice()
78    }
79}
80
81impl<'a> From<Cow<'a, [u8]>> for ArtifactBytes<'a> {
82    fn from(bytes: Cow<'a, [u8]>) -> Self {
83        match bytes {
84            Cow::Borrowed(bytes) => Self::Borrowed(bytes),
85            Cow::Owned(bytes) => Self::Owned(bytes),
86        }
87    }
88}
89
90/// Map a file read-only.
91///
92/// These artifacts are content-addressed and are mounted read-only where they
93/// are deployed, so the map is never opened for writing.
94///
95/// # Safety of the underlying map
96///
97/// `memmap2::Mmap::map` is unsafe because the mapped file can be modified by
98/// another process, which would change bytes under the reader. The contract
99/// here is the same one the format already relies on: an artifact is
100/// content-addressed and immutable once published. A caller that maps a file
101/// somebody else is concurrently rewriting has a corrupt read either way; the
102/// map does not introduce that hazard, it inherits it.
103#[cfg(feature = "mmap")]
104pub fn map_file_read_only(path: &std::path::Path) -> std::io::Result<ArtifactBytes<'static>> {
105    let file = std::fs::File::open(path)?;
106    // SAFETY: opened read-only, and the artifact is immutable once published -
107    // see the note above.
108    let map = unsafe { memmap2::Mmap::map(&file)? };
109    Ok(ArtifactBytes::Mapped(std::sync::Arc::new(map)))
110}