diff --git a/crates/prometeu-core/src/hardware/asset.rs b/crates/prometeu-core/src/hardware/asset.rs index 9a53fd05..ff0aff49 100644 --- a/crates/prometeu-core/src/hardware/asset.rs +++ b/crates/prometeu-core/src/hardware/asset.rs @@ -1,8 +1,97 @@ +use crate::hardware::memory_banks::GfxTileBankPoolInstaller; +use crate::model::{AssetEntry, BankStats, BankType, Color, HandleId, LoadStatus, SlotRef, SlotStats, TileBank, TileSize}; use std::collections::HashMap; -use std::sync::{Arc, RwLock, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use std::thread; -use crate::model::{AssetEntry, BankType, BankStats, LoadStatus, SlotRef, SlotStats, TileBank, TileSize, Color, HandleId}; -use crate::hardware::MemoryBanks; +use std::time::Instant; + +/// Resident metadata for a decoded/materialized asset inside a BankPolicy. +#[derive(Debug)] +pub struct ResidentEntry { + /// The resident, materialized object. + pub value: Arc, + + /// Resident size in bytes (post-decode). Used for telemetry/budgets. + pub bytes: usize, + + // /// Pin count (optional): if > 0, entry should not be evicted by policy. + // pub pins: u32, + + /// Telemetry / profiling fields (optional but useful). + pub loads: u64, + pub last_used: Instant, +} + +impl ResidentEntry { + pub fn new(value: Arc, bytes: usize) -> Self { + Self { + value, + bytes, + // pins: 0, + loads: 1, + last_used: Instant::now(), + } + } +} + +/// Encapsulates the residency and staging policy for a specific type of asset. +/// This is internal to the AssetManager and not visible to peripherals. +pub struct BankPolicy { + /// Dedup table: asset_id -> resident entry (value + telemetry). + resident: Arc>>>, + + /// Staging area: handle -> value ready to commit. + staging: Arc>>>, +} + +impl BankPolicy { + pub fn new() -> Self { + Self { + resident: Arc::new(RwLock::new(HashMap::new())), + staging: Arc::new(RwLock::new(HashMap::new())), + } + } + + /// Try get a resident value by asset_id (dedupe path). + pub fn get_resident(&self, asset_id: &str) -> Option> { + let mut map = self.resident.write().unwrap(); + let entry = map.get_mut(asset_id)?; + entry.last_used = Instant::now(); + Some(Arc::clone(&entry.value)) + } + + /// Insert or reuse a resident entry. Returns the resident Arc. + pub fn put_resident(&self, asset_id: String, value: Arc, bytes: usize) -> Arc { + let mut map = self.resident.write().unwrap(); + match map.get_mut(&asset_id) { + Some(existing) => { + existing.last_used = Instant::now(); + existing.loads += 1; + Arc::clone(&existing.value) + } + None => { + let entry = ResidentEntry::new(Arc::clone(&value), bytes); + map.insert(asset_id, entry); + value + } + } + } + + /// Place a value into staging for a given handle. + pub fn stage(&self, handle: HandleId, value: Arc) { + self.staging.write().unwrap().insert(handle, value); + } + + /// Take staged value (used by commit path). + pub fn take_staging(&self, handle: HandleId) -> Option> { + self.staging.write().unwrap().remove(&handle) + } + + pub fn clear(&self) { + self.resident.write().unwrap().clear(); + self.staging.write().unwrap().clear(); + } +} pub struct AssetManager { assets: Arc>>, @@ -10,7 +99,14 @@ pub struct AssetManager { next_handle_id: Mutex, assets_data: Arc>>, - pub memory_banks: Arc, + /// Narrow hardware interfaces + gfx_installer: Arc, + + /// Track what is installed in each hardware slot (for stats/info). + gfx_slots: Arc; 16]>>, + + /// Residency policy for GFX tile banks. + gfx_policy: BankPolicy, // Commits that are ready to be applied at the next frame boundary. pending_commits: Mutex>, @@ -22,14 +118,12 @@ struct LoadHandleInfo { status: LoadStatus, } -impl Default for AssetManager { - fn default() -> Self { - Self::new(vec![], vec![], Arc::new(MemoryBanks::new())) - } -} - impl AssetManager { - pub fn new(assets: Vec, assets_data: Vec, memory_banks: Arc) -> Self { + pub fn new( + assets: Vec, + assets_data: Vec, + gfx_installer: Arc, + ) -> Self { let mut asset_map = HashMap::new(); for entry in assets { asset_map.insert(entry.asset_id.clone(), entry); @@ -37,7 +131,9 @@ impl AssetManager { Self { assets: Arc::new(RwLock::new(asset_map)), - memory_banks, + gfx_installer, + gfx_slots: Arc::new(RwLock::new(std::array::from_fn(|_| None))), + gfx_policy: BankPolicy::new(), handles: Arc::new(RwLock::new(HashMap::new())), next_handle_id: Mutex::new(1), assets_data: Arc::new(RwLock::new(assets_data)), @@ -70,14 +166,14 @@ impl AssetManager { *next_id += 1; // Check if already resident - if let Some(bank) = self.memory_banks.gfx.get_resident(asset_id) { + if let Some(bank) = self.gfx_policy.get_resident(asset_id) { // Dedup: already resident self.handles.write().unwrap().insert(handle_id, LoadHandleInfo { _asset_id: asset_id.to_string(), slot, status: LoadStatus::READY, }); - self.memory_banks.gfx.stage(handle_id, bank); + self.gfx_policy.stage(handle_id, bank); return Ok(handle_id); } @@ -88,7 +184,8 @@ impl AssetManager { status: LoadStatus::PENDING, }); - let memory_banks = Arc::clone(&self.memory_banks); + let gfx_policy_resident = Arc::clone(&self.gfx_policy.resident); + let gfx_policy_staging = Arc::clone(&self.gfx_policy.staging); let handles = self.handles.clone(); let assets_data = self.assets_data.clone(); let entry_clone = entry.clone(); @@ -118,10 +215,24 @@ impl AssetManager { let bank_arc = Arc::new(tilebank); // Insert or reuse a resident entry (dedup) - let resident_arc = memory_banks.gfx.put_resident(asset_id_clone, bank_arc, entry_clone.decoded_size as usize); + let resident_arc = { + let mut map = gfx_policy_resident.write().unwrap(); + match map.get_mut(&asset_id_clone) { + Some(existing) => { + existing.last_used = Instant::now(); + existing.loads += 1; + Arc::clone(&existing.value) + } + None => { + let entry = ResidentEntry::new(Arc::clone(&bank_arc), entry_clone.decoded_size as usize); + map.insert(asset_id_clone, entry); + bank_arc + } + } + }; // Add to staging - memory_banks.gfx.stage(handle_id, resident_arc); + gfx_policy_staging.write().unwrap().insert(handle_id, resident_arc); // Update status to READY let mut handles_map = handles.write().unwrap(); @@ -216,17 +327,13 @@ impl AssetManager { match h.status { LoadStatus::PENDING | LoadStatus::LOADING | LoadStatus::READY => { h.status = LoadStatus::CANCELED; - // We don't actually stop the worker thread if it's already LOADING, - // but we will ignore its result when it finishes. } _ => {} } } - self.memory_banks.gfx.take_staging(handle); + self.gfx_policy.take_staging(handle); } - /// Collects all pending commits and returns them. - /// This is called at the frame boundary to apply the changes to the hardware. pub fn apply_commits(&self) { let mut pending = self.pending_commits.lock().unwrap(); let mut handles = self.handles.write().unwrap(); @@ -234,9 +341,15 @@ impl AssetManager { for handle_id in pending.drain(..) { if let Some(h) = handles.get_mut(&handle_id) { if h.status == LoadStatus::READY { - if let Some(bank) = self.memory_banks.gfx.take_staging(handle_id) { + if let Some(bank) = self.gfx_policy.take_staging(handle_id) { if h.slot.asset_type == BankType::TILES { - self.memory_banks.gfx.install(h.slot.index, bank); + self.gfx_installer.install_tilebank(h.slot.index, bank); + + // Update internal tracking of what's in the slot + let mut slots = self.gfx_slots.write().unwrap(); + if h.slot.index < slots.len() { + slots[h.slot.index] = Some(h._asset_id.clone()); + } } h.status = LoadStatus::COMMITTED; } @@ -245,12 +358,12 @@ impl AssetManager { } } - pub fn bank_info(&self, kind: BankType, _gfx_banks: &[Option>; 16]) -> BankStats { + pub fn bank_info(&self, kind: BankType) -> BankStats { match kind { BankType::TILES => { let mut used_bytes = 0; { - let resident = self.memory_banks.gfx.resident.read().unwrap(); + let resident = self.gfx_policy.resident.read().unwrap(); for entry in resident.values() { used_bytes += entry.bytes; } @@ -258,11 +371,10 @@ impl AssetManager { let mut inflight_bytes = 0; { - let staging = self.memory_banks.gfx.staging.read().unwrap(); + let staging = self.gfx_policy.staging.read().unwrap(); let assets = self.assets.read().unwrap(); let handles = self.handles.read().unwrap(); - // This is a bit complex because we need to map handle -> asset_id -> decoded_size for (handle_id, _) in staging.iter() { if let Some(h) = handles.get(handle_id) { if let Some(entry) = assets.get(&h._asset_id) { @@ -273,7 +385,7 @@ impl AssetManager { } BankStats { - total_bytes: 16 * 1024 * 1024, // 16MB budget (arbitrary for now) + total_bytes: 16 * 1024 * 1024, used_bytes, free_bytes: (16usize * 1024 * 1024).saturating_sub(used_bytes), inflight_bytes, @@ -283,111 +395,48 @@ impl AssetManager { } } - pub fn slot_info(&self, slot: SlotRef, gfx_banks: &[Option>; 16]) -> SlotStats { + pub fn slot_info(&self, slot: SlotRef) -> SlotStats { match slot.asset_type { BankType::TILES => { - if let Some(Some(bank)) = gfx_banks.get(slot.index) { - // We need asset_id. - // Let's find it in resident entries. - let resident = self.memory_banks.gfx.resident.read().unwrap(); - let (asset_id, bytes) = resident.iter() - .find(|(_, entry)| Arc::ptr_eq(&entry.value, bank)) - .map(|(id, entry)| (Some(id.clone()), entry.bytes)) - .unwrap_or((None, 0)); - - SlotStats { - asset_id, - generation: 0, // generation not yet implemented - resident_bytes: bytes, - } + let slots = self.gfx_slots.read().unwrap(); + let asset_id = slots.get(slot.index).and_then(|s| s.clone()); + + let bytes = if let Some(id) = &asset_id { + self.gfx_policy.resident.read().unwrap() + .get(id) + .map(|entry| entry.bytes) + .unwrap_or(0) } else { - SlotStats { - asset_id: None, - generation: 0, - resident_bytes: 0, - } + 0 + }; + + SlotStats { + asset_id, + generation: 0, + resident_bytes: bytes, } } } } pub fn shutdown(&self) { - self.memory_banks.gfx.resident.write().unwrap().clear(); - self.memory_banks.gfx.staging.write().unwrap().clear(); + self.gfx_policy.clear(); self.handles.write().unwrap().clear(); self.pending_commits.lock().unwrap().clear(); - // gfx_pool is cleared by Hardware when it owns Gfx + self.gfx_slots.write().unwrap().fill(None); } } #[cfg(test)] mod tests { use super::*; - use std::time::Instant; + use crate::hardware::memory_banks::{GfxTileBankPoolAccess, MemoryBanks}; #[test] fn test_asset_loading_flow() { let banks = Arc::new(MemoryBanks::new()); - // Mock data for a 16x16 tilebank (256 pixels) + 2048 bytes of palette - let mut data = vec![1u8; 256]; // all pixel indices are 1 - data.extend_from_slice(&[0u8; 2048]); // all colors are BLACK (0,0) + let gfx_installer = Arc::clone(&banks) as Arc; - let asset_entry = AssetEntry { - asset_id: "test_tiles".to_string(), - bank_type: BankType::TILES, - offset: 0, - size: data.len() as u64, - decoded_size: data.len() as u64, - codec: "RAW".to_string(), - metadata: serde_json::json!({ - "tile_size": 16, - "width": 16, - "height": 16 - }), - }; - - let am = AssetManager::new(vec![asset_entry], data, Arc::clone(&banks)); - let slot = SlotRef::gfx(0); - - let handle = am.load("test_tiles", slot).expect("Should start loading"); - - // Wait for loading to finish (since it's a thread) - let mut status = am.status(handle); - let start = Instant::now(); - while status != LoadStatus::READY && start.elapsed().as_secs() < 5 { - thread::sleep(std::time::Duration::from_millis(10)); - status = am.status(handle); - } - - assert_eq!(status, LoadStatus::READY); - - // Check staging - { - let staging = am.memory_banks.gfx.staging.read().unwrap(); - assert!(staging.contains_key(&handle)); - } - - // Commit - am.commit(handle); - - const EMPTY_BANK: Option> = None; - let mut gfx_banks = [EMPTY_BANK; 16]; - am.apply_commits(); - - // Let's verify if it's installed in the shared pool - { - let pool = am.memory_banks.gfx.pool.read().unwrap(); - assert!(pool[0].is_some()); - gfx_banks[0] = pool[0].clone(); - } - - assert_eq!(am.status(handle), LoadStatus::COMMITTED); - assert!(gfx_banks[0].is_some()); - } - - #[test] - fn test_asset_dedup() { - let banks = Arc::new(MemoryBanks::new()); let mut data = vec![1u8; 256]; data.extend_from_slice(&[0u8; 2048]); @@ -405,23 +454,66 @@ mod tests { }), }; - let am = AssetManager::new(vec![asset_entry], data, Arc::clone(&banks)); + let am = AssetManager::new(vec![asset_entry], data, gfx_installer); + let slot = SlotRef::gfx(0); + + let handle = am.load("test_tiles", slot).expect("Should start loading"); + + let mut status = am.status(handle); + let start = Instant::now(); + while status != LoadStatus::READY && start.elapsed().as_secs() < 5 { + thread::sleep(std::time::Duration::from_millis(10)); + status = am.status(handle); + } + + assert_eq!(status, LoadStatus::READY); + + { + let staging = am.gfx_policy.staging.read().unwrap(); + assert!(staging.contains_key(&handle)); + } + + am.commit(handle); + am.apply_commits(); + + assert_eq!(am.status(handle), LoadStatus::COMMITTED); + assert!(banks.tilebank_slot(0).is_some()); + } + + #[test] + fn test_asset_dedup() { + let banks = Arc::new(MemoryBanks::new()); + let gfx_installer = Arc::clone(&banks) as Arc; + + let mut data = vec![1u8; 256]; + data.extend_from_slice(&[0u8; 2048]); + + let asset_entry = AssetEntry { + asset_id: "test_tiles".to_string(), + bank_type: BankType::TILES, + offset: 0, + size: data.len() as u64, + decoded_size: data.len() as u64, + codec: "RAW".to_string(), + metadata: serde_json::json!({ + "tile_size": 16, + "width": 16, + "height": 16 + }), + }; + + let am = AssetManager::new(vec![asset_entry], data, gfx_installer); - // Load once let handle1 = am.load("test_tiles", SlotRef::gfx(0)).unwrap(); let start = Instant::now(); while am.status(handle1) != LoadStatus::READY && start.elapsed().as_secs() < 5 { thread::sleep(std::time::Duration::from_millis(10)); } - // Load again into another slot let handle2 = am.load("test_tiles", SlotRef::gfx(1)).unwrap(); - - // Second load should be READY immediately (or very fast) because of dedup assert_eq!(am.status(handle2), LoadStatus::READY); - // Check that both handles point to the same Arc - let staging = am.memory_banks.gfx.staging.read().unwrap(); + let staging = am.gfx_policy.staging.read().unwrap(); let bank1 = staging.get(&handle1).unwrap(); let bank2 = staging.get(&handle2).unwrap(); assert!(Arc::ptr_eq(bank1, bank2)); diff --git a/crates/prometeu-core/src/hardware/gfx.rs b/crates/prometeu-core/src/hardware/gfx.rs index 0649cdf2..108d7f18 100644 --- a/crates/prometeu-core/src/hardware/gfx.rs +++ b/crates/prometeu-core/src/hardware/gfx.rs @@ -1,4 +1,4 @@ -use crate::hardware::MemoryBanks; +use crate::hardware::memory_banks::GfxTileBankPoolAccess; use crate::model::{Color, HudTileLayer, ScrollableTileLayer, Sprite, TileBank, TileMap, TileSize}; use std::sync::Arc; @@ -47,8 +47,8 @@ pub struct Gfx { pub layers: [ScrollableTileLayer; 4], /// 1 fixed layer for User Interface. pub hud: HudTileLayer, - /// Memory banks containing graphical assets. - pub memory_banks: Arc, + /// Interface to access graphical memory banks. + pub tile_banks: Arc, /// Hardware sprites (Object Attribute Memory equivalent). pub sprites: [Sprite; 512], @@ -67,7 +67,7 @@ pub struct Gfx { impl Gfx { /// Initializes the graphics system with a specific resolution and shared memory banks. - pub fn new(w: usize, h: usize, memory_banks: Arc) -> Self { + pub fn new(w: usize, h: usize, tile_banks: Arc) -> Self { const EMPTY_SPRITE: Sprite = Sprite { tile: crate::model::Tile { id: 0, flip_x: false, flip_y: false, palette_id: 0 }, x: 0, @@ -94,7 +94,7 @@ impl Gfx { back: vec![0; len], layers, hud: HudTileLayer::new(64, 32), - memory_banks, + tile_banks, sprites: [EMPTY_SPRITE; 512], scene_fade_level: 31, scene_fade_color: Color::BLACK, @@ -327,29 +327,26 @@ impl Gfx { } } - let pool_guard = self.memory_banks.gfx.pool.read().unwrap(); - let pool = &*pool_guard; - // 1. Priority 0 sprites: drawn at the very back, behind everything else. - Self::draw_bucket_on_buffer(&mut self.back, self.w, self.h, &self.priority_buckets[0], &self.sprites, pool); + Self::draw_bucket_on_buffer(&mut self.back, self.w, self.h, &self.priority_buckets[0], &self.sprites, &*self.tile_banks); // 2. Main layers and prioritized sprites. // Order: Layer 0 -> Sprites 1 -> Layer 1 -> Sprites 2 ... for i in 0..self.layers.len() { let bank_id = self.layers[i].bank_id as usize; - if let Some(Some(bank)) = pool.get(bank_id) { - Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[i].map, bank, self.layers[i].scroll_x, self.layers[i].scroll_y); + if let Some(bank) = self.tile_banks.tilebank_slot(bank_id) { + Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[i].map, &bank, self.layers[i].scroll_x, self.layers[i].scroll_y); } // Draw sprites that belong to this depth level - Self::draw_bucket_on_buffer(&mut self.back, self.w, self.h, &self.priority_buckets[i + 1], &self.sprites, pool); + Self::draw_bucket_on_buffer(&mut self.back, self.w, self.h, &self.priority_buckets[i + 1], &self.sprites, &*self.tile_banks); } // 4. Scene Fade: Applies a color blend to the entire world (excluding HUD). Self::apply_fade_to_buffer(&mut self.back, self.scene_fade_level, self.scene_fade_color); // 5. HUD: The fixed interface layer, always drawn on top of the world. - Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, pool); + Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, &*self.tile_banks); // 6. HUD Fade: Independent fade effect for the UI. Self::apply_fade_to_buffer(&mut self.back, self.hud_fade_level, self.hud_fade_color); @@ -363,29 +360,27 @@ impl Gfx { let scroll_x = self.layers[layer_idx].scroll_x; let scroll_y = self.layers[layer_idx].scroll_y; - let pool = self.memory_banks.gfx.pool.read().unwrap(); - let bank = match pool.get(bank_id) { - Some(Some(b)) => b, + let bank = match self.tile_banks.tilebank_slot(bank_id) { + Some(b) => b, _ => return, }; - Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[layer_idx].map, bank, scroll_x, scroll_y); + Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[layer_idx].map, &bank, scroll_x, scroll_y); } /// Renders the HUD (fixed position, no scroll). pub fn render_hud(&mut self) { - let pool = self.memory_banks.gfx.pool.read().unwrap(); - Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, &*pool); + Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, &*self.tile_banks); } - fn render_hud_with_pool(back: &mut [u16], w: usize, h: usize, hud: &HudTileLayer, pool: &[Option>; 16]) { + fn render_hud_with_pool(back: &mut [u16], w: usize, h: usize, hud: &HudTileLayer, tile_banks: &dyn GfxTileBankPoolAccess) { let bank_id = hud.bank_id as usize; - let bank = match pool.get(bank_id) { - Some(Some(b)) => b, + let bank = match tile_banks.tilebank_slot(bank_id) { + Some(b) => b, _ => return, }; - Self::draw_tile_map(back, w, h, &hud.map, bank, 0, 0); + Self::draw_tile_map(back, w, h, &hud.map, &bank, 0, 0); } /// Rasterizes a TileMap into the provided pixel buffer using scrolling. @@ -473,13 +468,13 @@ impl Gfx { screen_h: usize, bucket: &[usize], sprites: &[Sprite], - banks: &[Option>], + tile_banks: &dyn GfxTileBankPoolAccess, ) { for &idx in bucket { let s = &sprites[idx]; let bank_id = s.bank_id as usize; - if let Some(Some(bank)) = banks.get(bank_id) { - Self::draw_sprite_pixel_by_pixel(back, screen_w, screen_h, s, bank); + if let Some(bank) = tile_banks.tilebank_slot(bank_id) { + Self::draw_sprite_pixel_by_pixel(back, screen_w, screen_h, s, &bank); } } } diff --git a/crates/prometeu-core/src/hardware/hardware.rs b/crates/prometeu-core/src/hardware/hardware.rs index 7f519a39..d51a01ba 100644 --- a/crates/prometeu-core/src/hardware/hardware.rs +++ b/crates/prometeu-core/src/hardware/hardware.rs @@ -1,4 +1,5 @@ use crate::hardware::{AssetManager, Audio, Gfx, HardwareBridge, Pad, Touch, MemoryBanks}; +use crate::hardware::memory_banks::{GfxTileBankPoolAccess, GfxTileBankPoolInstaller}; use std::sync::Arc; /// Aggregate structure for all virtual hardware peripherals. @@ -50,11 +51,15 @@ impl Hardware { let memory_banks = Arc::new(MemoryBanks::new()); Self { memory_banks: Arc::clone(&memory_banks), - gfx: Gfx::new(Self::W, Self::H, Arc::clone(&memory_banks)), + gfx: Gfx::new(Self::W, Self::H, Arc::clone(&memory_banks) as Arc), audio: Audio::new(), pad: Pad::default(), touch: Touch::default(), - assets: AssetManager::new(vec![], vec![], Arc::clone(&memory_banks)), + assets: AssetManager::new( + vec![], + vec![], + Arc::clone(&memory_banks) as Arc, + ), } } } diff --git a/crates/prometeu-core/src/hardware/memory_banks.rs b/crates/prometeu-core/src/hardware/memory_banks.rs index a153fbb9..75adc367 100644 --- a/crates/prometeu-core/src/hardware/memory_banks.rs +++ b/crates/prometeu-core/src/hardware/memory_banks.rs @@ -1,24 +1,54 @@ -use crate::model::{Bank, TileBank}; +use std::sync::{Arc, RwLock}; +use crate::model::TileBank; + +/// Non-generic interface for peripherals to access graphical tile banks. +pub trait GfxTileBankPoolAccess: Send + Sync { + /// Returns a reference to the resident TileBank in the specified slot, if any. + fn tilebank_slot(&self, slot: usize) -> Option>; + /// Returns the total number of slots available in this bank. + fn tilebank_slot_count(&self) -> usize; +} + +/// Non-generic interface for the AssetManager to install graphical tile banks. +pub trait GfxTileBankPoolInstaller: Send + Sync { + /// Atomically swaps the resident TileBank in the specified slot. + fn install_tilebank(&self, slot: usize, bank: Arc); +} /// Centralized container for all hardware memory banks. /// -/// This structure owns the actual residency pools, staging areas, and -/// deduplication tables for different types of hardware assets. -/// It is shared between the AssetManager (writer) and hardware -/// consumers like Gfx (reader). +/// MemoryBanks represents the actual hardware slot state. +/// Peripherals consume this state via narrow, non-generic traits. +/// AssetManager coordinates residency and installs assets into these slots. pub struct MemoryBanks { - /// Graphical tile banks. - pub gfx: Bank, - // In the future, add other banks here: - // pub audio: Bank, - // pub blobs: Bank, + gfx_tilebank_pool: Arc>; 16]>>, } impl MemoryBanks { - /// Creates a new, empty set of memory banks. + /// Creates a new set of memory banks with empty slots. pub fn new() -> Self { Self { - gfx: Bank::new(), + gfx_tilebank_pool: Arc::new(RwLock::new(std::array::from_fn(|_| None))), + } + } +} + +impl GfxTileBankPoolAccess for MemoryBanks { + fn tilebank_slot(&self, slot: usize) -> Option> { + let pool = self.gfx_tilebank_pool.read().unwrap(); + pool.get(slot).and_then(|s| s.as_ref().map(Arc::clone)) + } + + fn tilebank_slot_count(&self) -> usize { + 16 + } +} + +impl GfxTileBankPoolInstaller for MemoryBanks { + fn install_tilebank(&self, slot: usize, bank: Arc) { + let mut pool = self.gfx_tilebank_pool.write().unwrap(); + if slot < 16 { + pool[slot] = Some(bank); } } } diff --git a/crates/prometeu-core/src/model/asset.rs b/crates/prometeu-core/src/model/asset.rs index 3c48069c..bbd5d7af 100644 --- a/crates/prometeu-core/src/model/asset.rs +++ b/crates/prometeu-core/src/model/asset.rs @@ -1,6 +1,3 @@ -use std::collections::HashMap; -use std::sync::{Arc, RwLock}; -use std::time::Instant; use serde::{Deserialize, Serialize}; pub type HandleId = u32; @@ -65,121 +62,3 @@ impl SlotRef { } } } - -#[derive(Debug)] -pub struct ResidentEntry { - /// The resident, materialized object. - pub value: Arc, - - /// Resident size in bytes (post-decode). Used for telemetry/budgets. - pub bytes: usize, - - /// Pin count (optional): if > 0, entry should not be evicted by policy. - pub pins: u32, - - /// Telemetry / profiling fields (optional but useful). - pub loads: u64, - pub last_used: Instant, -} - -impl ResidentEntry { - pub fn new(value: Arc, bytes: usize) -> Self { - Self { - value, - bytes, - pins: 0, - loads: 1, - last_used: Instant::now(), - } - } -} - -pub struct Bank { - /// Dedup table: asset_id -> resident entry (value + telemetry). - pub resident: Arc>>>, - - /// Slot pool: hardware-visible residency pointers. - pub pool: Arc>; S]>>, - - /// Staging area: handle -> value ready to commit. - pub staging: Arc>>>, -} - -impl Bank { - pub fn new() -> Self { - Self { - resident: Arc::new(RwLock::new(HashMap::new())), - pool: Arc::new(RwLock::new(std::array::from_fn(|_| None))), - staging: Arc::new(RwLock::new(HashMap::new())), - } - } - - /// Try get a resident value by asset_id (dedupe path). - pub fn get_resident(&self, asset_id: &str) -> Option> { - let mut map = self.resident.write().unwrap(); - let entry = map.get_mut(asset_id)?; - entry.last_used = Instant::now(); - Some(Arc::clone(&entry.value)) - } - - /// Insert or reuse a resident entry. Returns the resident Arc. - /// - If already resident, updates telemetry and returns existing value. - /// - If new, inserts ResidentEntry and returns inserted value. - pub fn put_resident(&self, asset_id: String, value: Arc, bytes: usize) -> Arc { - let mut map = self.resident.write().unwrap(); - match map.get_mut(&asset_id) { - Some(existing) => { - existing.last_used = Instant::now(); - existing.loads += 1; - Arc::clone(&existing.value) - } - None => { - let entry = ResidentEntry::new(Arc::clone(&value), bytes); - map.insert(asset_id, entry); - value - } - } - } - - /// Place a value into staging for a given handle. - pub fn stage(&self, handle: HandleId, value: Arc) { - self.staging.write().unwrap().insert(handle, value); - } - - /// Take staged value (used by commit path). - pub fn take_staging(&self, handle: HandleId) -> Option> { - self.staging.write().unwrap().remove(&handle) - } - - /// Install (commit) a value into a slot (pointer swap). - pub fn install(&self, slot: usize, value: Arc) { - let mut pool = self.pool.write().unwrap(); - if slot < S { - pool[slot] = Some(value); - } - } - - /// Read current slot value (if any). - pub fn slot_current(&self, slot: usize) -> Option> { - if slot < S { - self.pool.read().unwrap()[slot].as_ref().map(Arc::clone) - } else { - None - } - } - - /// Optional: pin/unpin API (future eviction policy support). - pub fn pin(&self, asset_id: &str) { - if let Some(e) = self.resident.write().unwrap().get_mut(asset_id) { - e.pins = e.pins.saturating_add(1); - e.last_used = Instant::now(); - } - } - - pub fn unpin(&self, asset_id: &str) { - if let Some(e) = self.resident.write().unwrap().get_mut(asset_id) { - e.pins = e.pins.saturating_sub(1); - e.last_used = Instant::now(); - } - } -} diff --git a/crates/prometeu-core/src/model/mod.rs b/crates/prometeu-core/src/model/mod.rs index 50505054..5af6e6e1 100644 --- a/crates/prometeu-core/src/model/mod.rs +++ b/crates/prometeu-core/src/model/mod.rs @@ -10,7 +10,7 @@ mod cartridge; mod cartridge_loader; mod window; -pub use asset::{AssetEntry, BankType, BankStats, LoadStatus, SlotRef, SlotStats, HandleId, Bank, ResidentEntry}; +pub use asset::{AssetEntry, BankType, BankStats, LoadStatus, SlotRef, SlotStats, HandleId}; pub use button::{Button, ButtonId}; pub use cartridge::{AppMode, Cartridge, CartridgeDTO, CartridgeError}; pub use cartridge_loader::{CartridgeLoader, DirectoryCartridgeLoader, PackedCartridgeLoader}; diff --git a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs index 773084ac..2feedac5 100644 --- a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs +++ b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs @@ -918,8 +918,7 @@ impl NativeInterface for PrometeuOS { 0 => crate::model::BankType::TILES, _ => return Err("Invalid asset type".to_string()), }; - let pool = hw.memory_banks().gfx.pool.read().unwrap(); - let info = hw.assets().bank_info(asset_type, &*pool); + let info = hw.assets().bank_info(asset_type); let json = serde_json::to_string(&info).unwrap_or_default(); vm.push(Value::String(json)); Ok(500) @@ -932,8 +931,7 @@ impl NativeInterface for PrometeuOS { _ => return Err("Invalid asset type".to_string()), }; let slot = crate::model::SlotRef { asset_type, index: slot_index }; - let pool = hw.memory_banks().gfx.pool.read().unwrap(); - let info = hw.assets().slot_info(slot, &*pool); + let info = hw.assets().slot_info(slot); let json = serde_json::to_string(&info).unwrap_or_default(); vm.push(Value::String(json)); Ok(500)