Skip to main content

petaplot_core/storage/
mmap_reader.rs

1use std::fs::File;
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4use memmap2::{Mmap, MmapOptions};
5use crate::error::{Result, TeraError};
6
7/// Lector de archivos mapeados en memoria virtual (`mmap`) con costo cero de asignación inicial.
8#[derive(Clone)]
9pub struct MmapReader {
10    path: PathBuf,
11    mmap: Arc<Mmap>,
12}
13
14impl MmapReader {
15    /// Abre un archivo binario y crea el mapeo de memoria virtual (`mmap`).
16    ///
17    /// Tiempo de apertura $< 10\text{ ms}$ independientemente del tamaño del archivo.
18    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
19        let path_buf = path.as_ref().to_path_buf();
20        let file = File::open(&path_buf)?;
21
22        let mmap = unsafe {
23            MmapOptions::new()
24                .map(&file)
25                .map_err(|e| TeraError::Mmap(format!("Falló el mapeo de memoria para {:?}: {}", path_buf, e)))?
26        };
27
28        Ok(Self {
29            path: path_buf,
30            mmap: Arc::new(mmap),
31        })
32    }
33
34    /// Retorna la ruta del archivo mapeado.
35    pub fn path(&self) -> &Path {
36        &self.path
37    }
38
39    /// Retorna el tamaño total en bytes del archivo mapeado.
40    pub fn len(&self) -> usize {
41        self.mmap.len()
42    }
43
44    /// Indica si el archivo mapeado está vacío.
45    pub fn is_empty(&self) -> bool {
46        self.mmap.is_empty()
47    }
48
49    /// Obtiene una referencia inmutable a la totalidad del espacio de direcciones mapeado (cero-copia).
50    pub fn as_slice(&self) -> &[u8] {
51        &self.mmap[..]
52    }
53
54    /// Obtiene un segmento específico de bytes con comprobación de límites.
55    pub fn as_slice_range(&self, range: std::ops::Range<usize>) -> Result<&[u8]> {
56        if range.end > self.mmap.len() || range.start > range.end {
57            return Err(TeraError::OutOfRange(format!(
58                "Rango {:?} fuera de los límites del archivo (tamaño total: {} bytes)",
59                range,
60                self.mmap.len()
61            )));
62        }
63        Ok(&self.mmap[range])
64    }
65
66    /// Informa al kernel del sistema operativo mediante `madvise` que ciertos bloques de memoria serán leídos pronto.
67    #[cfg(unix)]
68    pub fn advise_will_need(&self, offset: usize, length: usize) -> Result<()> {
69        let end = offset.saturating_add(length).min(self.mmap.len());
70        if offset >= self.mmap.len() {
71            return Ok(());
72        }
73
74        self.mmap
75            .advise_range(memmap2::Advice::WillNeed, offset, end - offset)
76            .map_err(|e| TeraError::Mmap(format!("Error en madvise(WillNeed): {}", e)))
77    }
78
79    /// En Windows, la memoria virtual mapeada maneja la precarga por demanda en el subsistema I/O.
80    #[cfg(not(unix))]
81    pub fn advise_will_need(&self, _offset: usize, _length: usize) -> Result<()> {
82        Ok(())
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use std::io::Write;
90
91    #[test]
92    fn test_mmap_reader_basic() -> Result<()> {
93        let temp_dir = std::env::temp_dir();
94        let test_file = temp_dir.join("petaplot_test_mmap.bin");
95
96        let sample_data = b"PetaPlot high performance zero-copy time series data engine.";
97        {
98            let mut file = File::create(&test_file)?;
99            file.write_all(sample_data)?;
100        }
101
102        let reader = MmapReader::open(&test_file)?;
103        assert_eq!(reader.len(), sample_data.len());
104        assert_eq!(reader.as_slice(), sample_data);
105        assert_eq!(reader.as_slice_range(0..8)?, b"PetaPlot");
106
107        let _ = std::fs::remove_file(&test_file);
108        Ok(())
109    }
110}