implements PR-011d asset manager on-demand payload and op mode
This commit is contained in:
parent
077378a3b5
commit
4b31248dff
@ -11,6 +11,7 @@ use prometeu_hal::sample::Sample;
|
||||
use prometeu_hal::sound_bank::SoundBank;
|
||||
use prometeu_hal::tile_bank::{TileBank, TileSize};
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread;
|
||||
use std::time::Instant;
|
||||
@ -132,6 +133,12 @@ struct LoadHandleInfo {
|
||||
status: LoadStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum AssetOpMode {
|
||||
DirectFromSlice,
|
||||
StageInMemory,
|
||||
}
|
||||
|
||||
impl AssetBridge for AssetManager {
|
||||
fn initialize_for_cartridge(
|
||||
&self,
|
||||
@ -171,6 +178,14 @@ impl AssetBridge for AssetManager {
|
||||
}
|
||||
|
||||
impl AssetManager {
|
||||
fn op_mode_for(entry: &AssetEntry) -> Result<AssetOpMode, String> {
|
||||
match (entry.bank_type, entry.codec.as_str()) {
|
||||
(BankType::TILES, "RAW") => Ok(AssetOpMode::StageInMemory),
|
||||
(BankType::SOUNDS, "RAW") => Ok(AssetOpMode::DirectFromSlice),
|
||||
_ => Err(format!("Unsupported codec: {}", entry.codec)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
assets: Vec<AssetEntry>,
|
||||
assets_data: AssetsPayloadSource,
|
||||
@ -453,19 +468,27 @@ impl AssetManager {
|
||||
entry: &AssetEntry,
|
||||
assets_data: Arc<RwLock<AssetsPayloadSource>>,
|
||||
) -> Result<TileBank, String> {
|
||||
if entry.codec != "RAW" {
|
||||
return Err(format!("Unsupported codec: {}", entry.codec));
|
||||
}
|
||||
|
||||
let buffer = {
|
||||
let op_mode = Self::op_mode_for(entry)?;
|
||||
let slice = {
|
||||
let assets_data = assets_data.read().unwrap();
|
||||
assets_data
|
||||
.open_slice(entry.offset, entry.size)
|
||||
.map_err(|_| "Asset offset/size out of bounds".to_string())?
|
||||
.read_all()
|
||||
.map_err(|_| "Asset payload read failed".to_string())?
|
||||
};
|
||||
|
||||
match op_mode {
|
||||
AssetOpMode::StageInMemory => {
|
||||
let buffer = slice.read_all().map_err(|_| "Asset payload read failed".to_string())?;
|
||||
Self::decode_tile_bank_from_buffer(entry, &buffer)
|
||||
}
|
||||
AssetOpMode::DirectFromSlice => {
|
||||
let mut reader = slice.open_reader().map_err(|_| "Asset payload read failed".to_string())?;
|
||||
Self::decode_tile_bank_from_reader(entry, &mut reader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_tile_bank_from_buffer(entry: &AssetEntry, buffer: &[u8]) -> Result<TileBank, String> {
|
||||
// Decode TILEBANK metadata
|
||||
let tile_size_val =
|
||||
entry.metadata.get("tile_size").and_then(|v| v.as_u64()).ok_or("Missing tile_size")?;
|
||||
@ -502,23 +525,69 @@ impl AssetManager {
|
||||
Ok(TileBank { tile_size, width, height, pixel_indices, palettes })
|
||||
}
|
||||
|
||||
fn decode_tile_bank_from_reader(
|
||||
entry: &AssetEntry,
|
||||
reader: &mut impl Read,
|
||||
) -> Result<TileBank, String> {
|
||||
let tile_size_val =
|
||||
entry.metadata.get("tile_size").and_then(|v| v.as_u64()).ok_or("Missing tile_size")?;
|
||||
let width =
|
||||
entry.metadata.get("width").and_then(|v| v.as_u64()).ok_or("Missing width")? as usize;
|
||||
let height =
|
||||
entry.metadata.get("height").and_then(|v| v.as_u64()).ok_or("Missing height")? as usize;
|
||||
|
||||
let tile_size = match tile_size_val {
|
||||
8 => TileSize::Size8,
|
||||
16 => TileSize::Size16,
|
||||
32 => TileSize::Size32,
|
||||
_ => return Err(format!("Invalid tile_size: {}", tile_size_val)),
|
||||
};
|
||||
|
||||
let pixel_data_size = width * height;
|
||||
let mut pixel_indices = vec![0_u8; pixel_data_size];
|
||||
reader.read_exact(&mut pixel_indices).map_err(|_| "Buffer too small for TILEBANK".to_string())?;
|
||||
|
||||
let mut palette_data = [0_u8; 2048];
|
||||
reader.read_exact(&mut palette_data).map_err(|_| "Buffer too small for TILEBANK".to_string())?;
|
||||
|
||||
let mut palettes = [[Color::BLACK; 16]; 64];
|
||||
for (p, pal) in palettes.iter_mut().enumerate() {
|
||||
for (c, slot) in pal.iter_mut().enumerate() {
|
||||
let offset = (p * 16 + c) * 2;
|
||||
let color_raw =
|
||||
u16::from_le_bytes([palette_data[offset], palette_data[offset + 1]]);
|
||||
*slot = Color(color_raw);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(TileBank { tile_size, width, height, pixel_indices, palettes })
|
||||
}
|
||||
|
||||
fn perform_load_sound_bank(
|
||||
entry: &AssetEntry,
|
||||
assets_data: Arc<RwLock<AssetsPayloadSource>>,
|
||||
) -> Result<SoundBank, String> {
|
||||
if entry.codec != "RAW" {
|
||||
return Err(format!("Unsupported codec: {}", entry.codec));
|
||||
}
|
||||
|
||||
let buffer = {
|
||||
let op_mode = Self::op_mode_for(entry)?;
|
||||
let slice = {
|
||||
let assets_data = assets_data.read().unwrap();
|
||||
assets_data
|
||||
.open_slice(entry.offset, entry.size)
|
||||
.map_err(|_| "Asset offset/size out of bounds".to_string())?
|
||||
.read_all()
|
||||
.map_err(|_| "Asset payload read failed".to_string())?
|
||||
};
|
||||
|
||||
match op_mode {
|
||||
AssetOpMode::DirectFromSlice => {
|
||||
let mut reader = slice.open_reader().map_err(|_| "Asset payload read failed".to_string())?;
|
||||
Self::decode_sound_bank_from_reader(entry, &mut reader)
|
||||
}
|
||||
AssetOpMode::StageInMemory => {
|
||||
let buffer = slice.read_all().map_err(|_| "Asset payload read failed".to_string())?;
|
||||
Self::decode_sound_bank_from_buffer(entry, &buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_sound_bank_from_buffer(entry: &AssetEntry, buffer: &[u8]) -> Result<SoundBank, String> {
|
||||
let sample_rate =
|
||||
entry.metadata.get("sample_rate").and_then(|v| v.as_u64()).unwrap_or(44100) as u32;
|
||||
|
||||
@ -533,6 +602,15 @@ impl AssetManager {
|
||||
Ok(SoundBank::new(vec![sample]))
|
||||
}
|
||||
|
||||
fn decode_sound_bank_from_reader(
|
||||
entry: &AssetEntry,
|
||||
reader: &mut impl Read,
|
||||
) -> Result<SoundBank, String> {
|
||||
let mut raw = Vec::new();
|
||||
reader.read_to_end(&mut raw).map_err(|_| "Asset payload read failed".to_string())?;
|
||||
Self::decode_sound_bank_from_buffer(entry, &raw)
|
||||
}
|
||||
|
||||
pub fn status(&self, handle: HandleId) -> LoadStatus {
|
||||
self.handles
|
||||
.read()
|
||||
@ -806,6 +884,31 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_op_mode_for_tiles_raw_stages_in_memory() {
|
||||
let entry = test_tile_asset_entry("tiles", test_tile_asset_data().len());
|
||||
|
||||
assert_eq!(AssetManager::op_mode_for(&entry), Ok(AssetOpMode::StageInMemory));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_op_mode_for_sounds_raw_reads_direct_from_slice() {
|
||||
let entry = AssetEntry {
|
||||
asset_id: 1,
|
||||
asset_name: "sound".to_string(),
|
||||
bank_type: BankType::SOUNDS,
|
||||
offset: 0,
|
||||
size: 8,
|
||||
decoded_size: 8,
|
||||
codec: "RAW".to_string(),
|
||||
metadata: serde_json::json!({
|
||||
"sample_rate": 44100
|
||||
}),
|
||||
};
|
||||
|
||||
assert_eq!(AssetManager::op_mode_for(&entry), Ok(AssetOpMode::DirectFromSlice));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asset_loading_flow() {
|
||||
let banks = Arc::new(MemoryBanks::new());
|
||||
|
||||
@ -2,7 +2,7 @@ use crate::asset::{AssetEntry, PreloadEntry};
|
||||
use crate::syscalls::CapFlags;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Seek, SeekFrom};
|
||||
use std::io::{self, Cursor, Read, Seek, SeekFrom};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -125,18 +125,95 @@ pub enum AssetsPayloadSlice {
|
||||
}
|
||||
|
||||
impl AssetsPayloadSlice {
|
||||
pub fn read_all(&self) -> io::Result<Vec<u8>> {
|
||||
pub fn open_reader(&self) -> io::Result<AssetsPayloadReader> {
|
||||
match self {
|
||||
Self::Memory { bytes, start, len } => Ok(bytes[*start..*start + *len].to_vec()),
|
||||
Self::Memory { bytes, start, len } => {
|
||||
let data = Arc::<[u8]>::from(bytes[*start..*start + *len].to_vec());
|
||||
Ok(AssetsPayloadReader::Memory(Cursor::new(data)))
|
||||
}
|
||||
Self::File { source, offset, size } => {
|
||||
let mut file = File::open(&source.path)?;
|
||||
file.seek(SeekFrom::Start(source.payload_offset + offset))?;
|
||||
let mut buffer = vec![0_u8; usize::try_from(*size).map_err(|_| invalid_input("asset size overflow"))?];
|
||||
file.read_exact(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
let absolute_start = source.payload_offset + offset;
|
||||
file.seek(SeekFrom::Start(absolute_start))?;
|
||||
Ok(AssetsPayloadReader::File(FileSliceReader {
|
||||
file,
|
||||
start: absolute_start,
|
||||
len: *size,
|
||||
position: 0,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_all(&self) -> io::Result<Vec<u8>> {
|
||||
let mut reader = self.open_reader()?;
|
||||
let mut buffer = Vec::new();
|
||||
reader.read_to_end(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AssetsPayloadReader {
|
||||
Memory(Cursor<Arc<[u8]>>),
|
||||
File(FileSliceReader),
|
||||
}
|
||||
|
||||
impl Read for AssetsPayloadReader {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match self {
|
||||
Self::Memory(reader) => reader.read(buf),
|
||||
Self::File(reader) => reader.read(buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for AssetsPayloadReader {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
match self {
|
||||
Self::Memory(reader) => reader.seek(pos),
|
||||
Self::File(reader) => reader.seek(pos),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FileSliceReader {
|
||||
file: File,
|
||||
start: u64,
|
||||
len: u64,
|
||||
position: u64,
|
||||
}
|
||||
|
||||
impl Read for FileSliceReader {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
if self.position >= self.len {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let remaining = (self.len - self.position) as usize;
|
||||
let to_read = remaining.min(buf.len());
|
||||
let read = self.file.read(&mut buf[..to_read])?;
|
||||
self.position += read as u64;
|
||||
Ok(read)
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for FileSliceReader {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
let next = match pos {
|
||||
SeekFrom::Start(offset) => offset as i128,
|
||||
SeekFrom::Current(delta) => self.position as i128 + delta as i128,
|
||||
SeekFrom::End(delta) => self.len as i128 + delta as i128,
|
||||
};
|
||||
|
||||
if next < 0 || next as u64 > self.len {
|
||||
return Err(invalid_input("slice seek out of bounds"));
|
||||
}
|
||||
|
||||
let next = next as u64;
|
||||
self.file.seek(SeekFrom::Start(self.start + next))?;
|
||||
self.position = next;
|
||||
Ok(self.position)
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_input(message: &'static str) -> io::Error {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user