byte-engine-resource-management 0.1.0

Asset, resource, shader, and storage management for Byte-Engine.
use std::io::{Read, Seek};

use utils::sync::File;

use super::{MappedFileBacking, ResourceReader, ResourceReaderBacking};
use crate::{
	resource::{ReadTargets, ReadTargetsMut},
	StreamDescription,
};

#[derive(Debug)]
pub struct FileResourceReader {
	file: File,
}

impl FileResourceReader {
	pub fn new(file: File) -> Self {
		Self { file }
	}
}

impl ResourceReader for FileResourceReader {
	fn read_into<'b, 'c: 'b, 'a: 'b>(
		&'b mut self,
		stream_descriptions: Option<&'c [StreamDescription]>,
		read_target: ReadTargetsMut<'a>,
	) -> Result<ReadTargets<'a>, ()> {
		match read_target {
			ReadTargetsMut::Buffer { buffer, offset, size } => {
				let read_len = size.unwrap_or(buffer.len()).min(buffer.len());
				self.file.seek(std::io::SeekFrom::Start(offset as u64)).or(Err(()))?;
				self.file.read_exact(&mut buffer[..read_len]).or(Err(()))?;
				Ok(ReadTargets::Buffer(&buffer[..read_len]))
			}
			ReadTargetsMut::Box {
				mut buffer,
				offset,
				size,
			} => {
				let read_len = size.unwrap_or(buffer.len()).min(buffer.len());
				self.file.seek(std::io::SeekFrom::Start(offset as u64)).or(Err(()))?;
				self.file.read_exact(&mut buffer[..read_len]).or(Err(()))?;
				if read_len < buffer.len() {
					let mut v = buffer.into_vec();
					v.truncate(read_len);
					Ok(ReadTargets::Box(v.into_boxed_slice()))
				} else {
					Ok(ReadTargets::Box(buffer))
				}
			}
			ReadTargetsMut::Streams(mut streams) => {
				if let Some(stream_descriptions) = stream_descriptions {
					for sd in stream_descriptions {
						let stream_offset = sd.offset;
						if let Some(s) = streams.iter_mut().find(|s| s.name() == sd.name) {
							let offset = s.offset();
							let read_len = s.size().unwrap_or(s.buffer().len()).min(s.buffer().len());
							self.file
								.seek(std::io::SeekFrom::Start((stream_offset + offset) as u64))
								.or(Err(()))?;
							self.file
								.read_exact(&mut s.buffer_mut()[..read_len])
								.inspect_err(|e| {
									log::error!(
										"Failed to read stream '{}' from file resource. Expected to read: {}. Error: {}",
										s.name(),
										read_len,
										e,
									)
								})
								.or(Err(()))?;
						}
					}

					Ok(ReadTargets::Streams(
						streams.into_iter().map(|stream| stream.into()).collect(),
					))
				} else {
					log::error!("Stream descriptions are required for reading into streams");
					Err(())
				}
			}
			ReadTargetsMut::BackingStorage => {
				let mapped_file = MappedFileBacking::new(&self.file)?;
				Ok(ReadTargets::Backing(ResourceReaderBacking::MappedFile(mapped_file)))
			}
		}
	}

	fn into_backing_storage(self: Box<Self>) -> Result<ResourceReaderBacking, Box<dyn ResourceReader>> {
		let mapped_file = MappedFileBacking::new(&self.file).map_err(|_| self as Box<dyn ResourceReader>)?;
		Ok(ResourceReaderBacking::MappedFile(mapped_file))
	}
}

#[cfg(test)]
mod tests {
	use std::{
		fs,
		io::Write,
		path::PathBuf,
		time::{SystemTime, UNIX_EPOCH},
	};

	use super::*;

	fn temporary_file_path() -> PathBuf {
		std::env::temp_dir().join(format!(
			"byte-engine-file-resource-reader-{}-{}.bin",
			std::process::id(),
			SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos()
		))
	}

	#[test]
	fn file_resource_reader_can_expose_mapped_backing_storage() {
		let path = temporary_file_path();
		let expected = b"shader-bytes";

		{
			let mut file = fs::File::create(&path).unwrap();
			file.write_all(expected).unwrap();
			file.sync_all().unwrap();
		}

		let reader: Box<dyn ResourceReader> = Box::new(FileResourceReader::new(fs::File::open(&path).unwrap()));
		let backing = reader.into_backing_storage().unwrap();

		assert_eq!(backing.as_slice(), expected);
		fs::remove_file(path).unwrap();
	}
}