#[cfg(feature = "io-mmap")]
use memmap2::Mmap;
use std::fs::File;
use std::path::Path;
use threecrate_core::{Result, Error};
pub struct MmapReader {
#[cfg(feature = "io-mmap")]
mmap: Option<Mmap>,
#[cfg(feature = "io-mmap")]
position: usize,
#[cfg(not(feature = "io-mmap"))]
_phantom: std::marker::PhantomData<()>,
}
impl MmapReader {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Option<Self>> {
#[cfg(feature = "io-mmap")]
{
if !Self::is_supported() {
return Ok(None);
}
let file = File::open(path)?;
let metadata = file.metadata()?;
const MIN_MMAP_SIZE: u64 = 64 * 1024; if metadata.len() < MIN_MMAP_SIZE {
return Ok(None);
}
let mmap = unsafe {
match Mmap::map(&file) {
Ok(mmap) => mmap,
Err(_) => return Ok(None), }
};
Ok(Some(Self {
mmap: Some(mmap),
position: 0,
}))
}
#[cfg(not(feature = "io-mmap"))]
{
let _ = path; Ok(None)
}
}
pub fn is_supported() -> bool {
#[cfg(feature = "io-mmap")]
{
cfg!(any(unix, windows))
}
#[cfg(not(feature = "io-mmap"))]
{
false
}
}
pub fn len(&self) -> usize {
#[cfg(feature = "io-mmap")]
{
self.mmap.as_ref().map(|m| m.len()).unwrap_or(0)
}
#[cfg(not(feature = "io-mmap"))]
{
0
}
}
pub fn position(&self) -> usize {
#[cfg(feature = "io-mmap")]
{
self.position
}
#[cfg(not(feature = "io-mmap"))]
{
0
}
}
pub fn seek(&mut self, pos: usize) -> Result<()> {
#[cfg(feature = "io-mmap")]
{
if pos > self.len() {
return Err(Error::InvalidData("Seek position beyond file end".to_string()));
}
self.position = pos;
Ok(())
}
#[cfg(not(feature = "io-mmap"))]
{
let _ = pos;
Err(Error::Unsupported("Memory mapping not available".to_string()))
}
}
pub fn read_slice(&mut self, len: usize) -> Result<&[u8]> {
#[cfg(feature = "io-mmap")]
{
let mmap = self.mmap.as_ref().ok_or_else(||
Error::InvalidData("No memory mapping available".to_string()))?;
if self.position + len > mmap.len() {
return Err(Error::InvalidData("Read beyond file end".to_string()));
}
let slice = &mmap[self.position..self.position + len];
self.position += len;
Ok(slice)
}
#[cfg(not(feature = "io-mmap"))]
{
let _ = len;
Err(Error::Unsupported("Memory mapping not available".to_string()))
}
}
pub fn read_u8(&mut self) -> Result<u8> {
let slice = self.read_slice(1)?;
Ok(slice[0])
}
pub fn read_u16_le(&mut self) -> Result<u16> {
let slice = self.read_slice(2)?;
Ok(u16::from_le_bytes([slice[0], slice[1]]))
}
pub fn read_u32_le(&mut self) -> Result<u32> {
let slice = self.read_slice(4)?;
Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
}
pub fn read_f32_le(&mut self) -> Result<f32> {
let slice = self.read_slice(4)?;
Ok(f32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
}
pub fn read_f64_le(&mut self) -> Result<f64> {
let slice = self.read_slice(8)?;
Ok(f64::from_le_bytes([
slice[0], slice[1], slice[2], slice[3],
slice[4], slice[5], slice[6], slice[7]
]))
}
pub fn read_u16_be(&mut self) -> Result<u16> {
let slice = self.read_slice(2)?;
Ok(u16::from_be_bytes([slice[0], slice[1]]))
}
pub fn read_u32_be(&mut self) -> Result<u32> {
let slice = self.read_slice(4)?;
Ok(u32::from_be_bytes([slice[0], slice[1], slice[2], slice[3]]))
}
pub fn read_f32_be(&mut self) -> Result<f32> {
let slice = self.read_slice(4)?;
Ok(f32::from_be_bytes([slice[0], slice[1], slice[2], slice[3]]))
}
pub fn read_f64_be(&mut self) -> Result<f64> {
let slice = self.read_slice(8)?;
Ok(f64::from_be_bytes([
slice[0], slice[1], slice[2], slice[3],
slice[4], slice[5], slice[6], slice[7]
]))
}
pub fn skip(&mut self, bytes: usize) -> Result<()> {
#[cfg(feature = "io-mmap")]
{
let new_pos = self.position + bytes;
if new_pos > self.len() {
return Err(Error::InvalidData("Skip beyond file end".to_string()));
}
self.position = new_pos;
Ok(())
}
#[cfg(not(feature = "io-mmap"))]
{
let _ = bytes;
Err(Error::Unsupported("Memory mapping not available".to_string()))
}
}
pub fn is_at_end(&self) -> bool {
#[cfg(feature = "io-mmap")]
{
self.position >= self.len()
}
#[cfg(not(feature = "io-mmap"))]
{
true
}
}
pub fn remaining(&self) -> usize {
#[cfg(feature = "io-mmap")]
{
self.len().saturating_sub(self.position)
}
#[cfg(not(feature = "io-mmap"))]
{
0
}
}
}
pub fn should_use_mmap<P: AsRef<Path>>(path: P) -> bool {
if !MmapReader::is_supported() {
return false;
}
if let Ok(metadata) = std::fs::metadata(path) {
const MIN_MMAP_SIZE: u64 = 64 * 1024; metadata.len() >= MIN_MMAP_SIZE
} else {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_mmap_availability() {
let supported = MmapReader::is_supported();
println!("Memory mapping supported: {}", supported);
}
#[cfg(feature = "io-mmap")]
#[test]
fn test_mmap_basic_operations() -> Result<()> {
let mut temp_file = NamedTempFile::new().unwrap();
let test_data = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
for _ in 0..10000 {
temp_file.write_all(&test_data)?;
}
temp_file.flush()?;
if let Some(mut reader) = MmapReader::new(temp_file.path())? {
assert_eq!(reader.position(), 0);
assert!(reader.len() > 64 * 1024);
let byte = reader.read_u8()?;
assert_eq!(byte, 0x01);
assert_eq!(reader.position(), 1);
reader.seek(4)?;
assert_eq!(reader.position(), 4);
let byte = reader.read_u8()?;
assert_eq!(byte, 0x05);
reader.seek(0)?;
let u32_val = reader.read_u32_le()?;
assert_eq!(u32_val, 0x04030201);
reader.seek(0)?;
let u32_val = reader.read_u32_be()?;
assert_eq!(u32_val, 0x01020304); }
Ok(())
}
#[test]
fn test_should_use_mmap() {
let temp_file = NamedTempFile::new().unwrap();
let should_mmap = should_use_mmap(temp_file.path());
println!("Should use mmap for small file: {}", should_mmap);
}
}