diff --git a/crates/prometeu-core/src/hardware/asset.rs b/crates/prometeu-core/src/hardware/asset.rs index 429b5f30..2105ecb6 100644 --- a/crates/prometeu-core/src/hardware/asset.rs +++ b/crates/prometeu-core/src/hardware/asset.rs @@ -38,7 +38,7 @@ impl ResidentEntry { /// This is internal to the AssetManager and not visible to peripherals. pub struct BankPolicy { /// Dedup table: asset_id -> resident entry (value + telemetry). - resident: Arc>>>, + resident: Arc>>>, /// Staging area: handle -> value ready to commit. staging: Arc>>>, @@ -53,15 +53,15 @@ impl BankPolicy { } /// Try get a resident value by asset_id (dedupe path). - pub fn get_resident(&self, asset_id: &str) -> Option> { + pub fn get_resident(&self, asset_id: u32) -> Option> { let mut map = self.resident.write().unwrap(); - let entry = map.get_mut(asset_id)?; + 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 { + pub fn put_resident(&self, asset_id: u32, value: Arc, bytes: usize) -> Arc { let mut map = self.resident.write().unwrap(); match map.get_mut(&asset_id) { Some(existing) => { @@ -94,7 +94,8 @@ impl BankPolicy { } pub struct AssetManager { - assets: Arc>>, + assets: Arc>>, + name_to_id: Arc>>, handles: Arc>>, next_handle_id: Mutex, assets_data: Arc>>, @@ -104,8 +105,8 @@ pub struct AssetManager { sound_installer: Arc, /// Track what is installed in each hardware slot (for stats/info). - gfx_slots: Arc; 16]>>, - sound_slots: Arc; 16]>>, + gfx_slots: Arc; 16]>>, + sound_slots: Arc; 16]>>, /// Residency policy for GFX tile banks. gfx_policy: BankPolicy, @@ -117,7 +118,7 @@ pub struct AssetManager { } struct LoadHandleInfo { - _asset_id: String, + _asset_id: u32, slot: SlotRef, status: LoadStatus, } @@ -130,12 +131,15 @@ impl AssetManager { sound_installer: Arc, ) -> Self { let mut asset_map = HashMap::new(); + let mut name_to_id = HashMap::new(); for entry in assets { - asset_map.insert(entry.asset_id.clone(), entry); + name_to_id.insert(entry.asset_name.clone(), entry.asset_id); + asset_map.insert(entry.asset_id, entry); } Self { assets: Arc::new(RwLock::new(asset_map)), + name_to_id: Arc::new(RwLock::new(name_to_id)), gfx_installer, sound_installer, gfx_slots: Arc::new(RwLock::new(std::array::from_fn(|_| None))), @@ -153,9 +157,12 @@ impl AssetManager { self.shutdown(); { let mut asset_map = self.assets.write().unwrap(); + let mut name_to_id = self.name_to_id.write().unwrap(); asset_map.clear(); + name_to_id.clear(); for entry in assets.iter() { - asset_map.insert(entry.asset_id.clone(), entry.clone()); + name_to_id.insert(entry.asset_name.clone(), entry.asset_id); + asset_map.insert(entry.asset_id, entry.clone()); } } *self.assets_data.write().unwrap() = assets_data; @@ -164,7 +171,10 @@ impl AssetManager { for item in preload { let entry_opt = { let assets = self.assets.read().unwrap(); - assets.get(&item.asset_id).cloned() + let name_to_id = self.name_to_id.read().unwrap(); + name_to_id.get(&item.asset_name) + .and_then(|id| assets.get(id)) + .cloned() }; if let Some(entry) = entry_opt { @@ -173,43 +183,46 @@ impl AssetManager { BankType::TILES => { if let Ok(bank) = Self::perform_load_tile_bank(&entry, self.assets_data.clone()) { let bank_arc = Arc::new(bank); - self.gfx_policy.put_resident(entry.asset_id.clone(), Arc::clone(&bank_arc), entry.decoded_size as usize); + self.gfx_policy.put_resident(entry.asset_id, Arc::clone(&bank_arc), entry.decoded_size as usize); self.gfx_installer.install_tile_bank(slot_index, bank_arc); let mut slots = self.gfx_slots.write().unwrap(); if slot_index < slots.len() { - slots[slot_index] = Some(entry.asset_id.clone()); + slots[slot_index] = Some(entry.asset_id); } - println!("[AssetManager] Preloaded tile asset '{}' into slot {}", entry.asset_id, slot_index); + println!("[AssetManager] Preloaded tile asset '{}' (id: {}) into slot {}", entry.asset_name, entry.asset_id, slot_index); } else { - eprintln!("[AssetManager] Failed to preload tile asset '{}'", entry.asset_id); + eprintln!("[AssetManager] Failed to preload tile asset '{}'", entry.asset_name); } } BankType::SOUNDS => { if let Ok(bank) = Self::perform_load_sound_bank(&entry, self.assets_data.clone()) { let bank_arc = Arc::new(bank); - self.sound_policy.put_resident(entry.asset_id.clone(), Arc::clone(&bank_arc), entry.decoded_size as usize); + self.sound_policy.put_resident(entry.asset_id, Arc::clone(&bank_arc), entry.decoded_size as usize); self.sound_installer.install_sound_bank(slot_index, bank_arc); let mut slots = self.sound_slots.write().unwrap(); if slot_index < slots.len() { - slots[slot_index] = Some(entry.asset_id.clone()); + slots[slot_index] = Some(entry.asset_id); } - println!("[AssetManager] Preloaded sound asset '{}' into slot {}", entry.asset_id, slot_index); + println!("[AssetManager] Preloaded sound asset '{}' (id: {}) into slot {}", entry.asset_name, entry.asset_id, slot_index); } else { - eprintln!("[AssetManager] Failed to preload sound asset '{}'", entry.asset_id); + eprintln!("[AssetManager] Failed to preload sound asset '{}'", entry.asset_name); } } } } else { - eprintln!("[AssetManager] Preload failed: asset '{}' not found in table", item.asset_id); + eprintln!("[AssetManager] Preload failed: asset '{}' not found in table", item.asset_name); } } } - pub fn load(&self, asset_id: &str, slot: SlotRef) -> Result { + pub fn load(&self, asset_name: &str, slot: SlotRef) -> Result { let entry = { let assets = self.assets.read().unwrap(); - assets.get(asset_id).ok_or_else(|| format!("Asset not found: {}", asset_id))?.clone() + let name_to_id = self.name_to_id.read().unwrap(); + let id = name_to_id.get(asset_name).ok_or_else(|| format!("Asset not found: {}", asset_name))?; + assets.get(id).ok_or_else(|| format!("Asset ID {} not found in table", id))?.clone() }; + let asset_id = entry.asset_id; if slot.asset_type != entry.bank_type { return Err("INCOMPATIBLE_SLOT_KIND".to_string()); @@ -237,7 +250,7 @@ impl AssetManager { if already_resident { self.handles.write().unwrap().insert(handle_id, LoadHandleInfo { - _asset_id: asset_id.to_string(), + _asset_id: asset_id, slot, status: LoadStatus::READY, }); @@ -246,7 +259,7 @@ impl AssetManager { // Not resident, start loading self.handles.write().unwrap().insert(handle_id, LoadHandleInfo { - _asset_id: asset_id.to_string(), + _asset_id: asset_id, slot, status: LoadStatus::PENDING, }); @@ -254,7 +267,6 @@ impl AssetManager { let handles = self.handles.clone(); let assets_data = self.assets_data.clone(); let entry_clone = entry.clone(); - let asset_id_clone = asset_id.to_string(); // Capture policies for the worker thread let gfx_policy_resident = Arc::clone(&self.gfx_policy.resident); @@ -284,13 +296,13 @@ impl AssetManager { let bank_arc = Arc::new(tilebank); let resident_arc = { let mut map = gfx_policy_resident.write().unwrap(); - if let Some(existing) = map.get_mut(&asset_id_clone) { + if let Some(existing) = map.get_mut(&asset_id) { existing.last_used = Instant::now(); existing.loads += 1; Arc::clone(&existing.value) } else { let entry = ResidentEntry::new(Arc::clone(&bank_arc), entry_clone.decoded_size as usize); - map.insert(asset_id_clone, entry); + map.insert(asset_id, entry); bank_arc } }; @@ -310,13 +322,13 @@ impl AssetManager { let bank_arc = Arc::new(soundbank); let resident_arc = { let mut map = sound_policy_resident.write().unwrap(); - if let Some(existing) = map.get_mut(&asset_id_clone) { + if let Some(existing) = map.get_mut(&asset_id) { existing.last_used = Instant::now(); existing.loads += 1; Arc::clone(&existing.value) } else { let entry = ResidentEntry::new(Arc::clone(&bank_arc), entry_clone.decoded_size as usize); - map.insert(asset_id_clone, entry); + map.insert(asset_id, entry); bank_arc } }; @@ -459,7 +471,7 @@ impl AssetManager { self.gfx_installer.install_tile_bank(h.slot.index, bank); let mut slots = self.gfx_slots.write().unwrap(); if h.slot.index < slots.len() { - slots[h.slot.index] = Some(h._asset_id.clone()); + slots[h.slot.index] = Some(h._asset_id); } h.status = LoadStatus::COMMITTED; } @@ -469,7 +481,7 @@ impl AssetManager { self.sound_installer.install_sound_bank(h.slot.index, bank); let mut slots = self.sound_slots.write().unwrap(); if h.slot.index < slots.len() { - slots[h.slot.index] = Some(h._asset_id.clone()); + slots[h.slot.index] = Some(h._asset_id); } h.status = LoadStatus::COMMITTED; } @@ -573,17 +585,20 @@ impl AssetManager { 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() + let (bytes, asset_name) = if let Some(id) = &asset_id { + let bytes = self.gfx_policy.resident.read().unwrap() .get(id) .map(|entry| entry.bytes) - .unwrap_or(0) + .unwrap_or(0); + let name = self.assets.read().unwrap().get(id).map(|e| e.asset_name.clone()); + (bytes, name) } else { - 0 + (0, None) }; SlotStats { asset_id, + asset_name, generation: 0, resident_bytes: bytes, } @@ -592,17 +607,20 @@ impl AssetManager { let slots = self.sound_slots.read().unwrap(); let asset_id = slots.get(slot.index).and_then(|s| s.clone()); - let bytes = if let Some(id) = &asset_id { - self.sound_policy.resident.read().unwrap() + let (bytes, asset_name) = if let Some(id) = &asset_id { + let bytes = self.sound_policy.resident.read().unwrap() .get(id) .map(|entry| entry.bytes) - .unwrap_or(0) + .unwrap_or(0); + let name = self.assets.read().unwrap().get(id).map(|e| e.asset_name.clone()); + (bytes, name) } else { - 0 + (0, None) }; SlotStats { asset_id, + asset_name, generation: 0, resident_bytes: bytes, } @@ -635,7 +653,8 @@ mod tests { data.extend_from_slice(&[0u8; 2048]); let asset_entry = AssetEntry { - asset_id: "test_tiles".to_string(), + asset_id: 0, + asset_name: "test_tiles".to_string(), bank_type: BankType::TILES, offset: 0, size: data.len() as u64, @@ -684,7 +703,8 @@ mod tests { data.extend_from_slice(&[0u8; 2048]); let asset_entry = AssetEntry { - asset_id: "test_tiles".to_string(), + asset_id: 0, + asset_name: "test_tiles".to_string(), bank_type: BankType::TILES, offset: 0, size: data.len() as u64, @@ -724,7 +744,8 @@ mod tests { let data = vec![0u8; 200]; let asset_entry = AssetEntry { - asset_id: "test_sound".to_string(), + asset_id: 1, + asset_name: "test_sound".to_string(), bank_type: BankType::SOUNDS, offset: 0, size: data.len() as u64, @@ -763,7 +784,8 @@ mod tests { let data = vec![0u8; 200]; let asset_entry = AssetEntry { - asset_id: "preload_sound".to_string(), + asset_id: 2, + asset_name: "preload_sound".to_string(), bank_type: BankType::SOUNDS, offset: 0, size: data.len() as u64, @@ -775,7 +797,7 @@ mod tests { }; let preload = vec![ - PreloadEntry { asset_id: "preload_sound".to_string(), slot: 5 } + PreloadEntry { asset_name: "preload_sound".to_string(), slot: 5 } ]; let am = AssetManager::new(vec![], vec![], gfx_installer, sound_installer); @@ -787,6 +809,6 @@ mod tests { // After init, slot 5 should be occupied because of preload assert!(banks.sound_bank_slot(5).is_some()); - assert_eq!(am.slot_info(SlotRef::audio(5)).asset_id, Some("preload_sound".to_string())); + assert_eq!(am.slot_info(SlotRef::audio(5)).asset_id, Some(2)); } } diff --git a/crates/prometeu-core/src/model/asset.rs b/crates/prometeu-core/src/model/asset.rs index ba770784..195427f3 100644 --- a/crates/prometeu-core/src/model/asset.rs +++ b/crates/prometeu-core/src/model/asset.rs @@ -13,7 +13,8 @@ pub enum BankType { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct AssetEntry { - pub asset_id: String, + pub asset_id: u32, + pub asset_name: String, pub bank_type: BankType, pub offset: u64, pub size: u64, @@ -24,7 +25,7 @@ pub struct AssetEntry { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct PreloadEntry { - pub asset_id: String, + pub asset_name: String, pub slot: usize, } @@ -50,7 +51,8 @@ pub struct BankStats { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SlotStats { - pub asset_id: Option, + pub asset_id: Option, + pub asset_name: Option, pub generation: u32, pub resident_bytes: usize, } diff --git a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs index a7e231cf..8168b8ab 100644 --- a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs +++ b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs @@ -900,6 +900,7 @@ impl NativeInterface for PrometeuOS { let asset_type = match asset_type_val { 0 => crate::model::BankType::TILES, + 1 => crate::model::BankType::SOUNDS, _ => return Err("Invalid asset type".to_string()), }; let slot = crate::model::SlotRef { asset_type, index: slot_index }; @@ -942,6 +943,7 @@ impl NativeInterface for PrometeuOS { let asset_type_val = vm.pop_integer()? as u32; let asset_type = match asset_type_val { 0 => crate::model::BankType::TILES, + 1 => crate::model::BankType::SOUNDS, _ => return Err("Invalid asset type".to_string()), }; let info = hw.assets().bank_info(asset_type); @@ -954,6 +956,7 @@ impl NativeInterface for PrometeuOS { let asset_type_val = vm.pop_integer()? as u32; let asset_type = match asset_type_val { 0 => crate::model::BankType::TILES, + 1 => crate::model::BankType::SOUNDS, _ => return Err("Invalid asset type".to_string()), }; let slot = crate::model::SlotRef { asset_type, index: slot_index }; diff --git a/docs/specs/topics/chapter-15.md b/docs/specs/topics/chapter-15.md index 2599da9e..5f41fc3b 100644 --- a/docs/specs/topics/chapter-15.md +++ b/docs/specs/topics/chapter-15.md @@ -44,7 +44,8 @@ It describes **content**, not residency. ### Required Fields (conceptual) -* `asset_id` (string or hash) +* `asset_id` (integer, internal identifier) +* `asset_name` (string, user-facing identifier) * `asset_type` (TILEBANK, SOUNDBANK, BLOB, TILEMAP, ...) * `bank_kind` (mandatory, single) * `offset` (byte offset in cartridge) @@ -139,12 +140,12 @@ Conceptually, each Bank is a **specialized allocator**. ### Conceptual API ```text -handle = asset.load(asset_id, slotRef, flags) +handle = asset.load(asset_name, slotRef, flags) ``` Load flow: -1. Resolve `asset_id` via Asset Table +1. Resolve `asset_name` via Asset Table to get its `asset_id` 2. Read `bank_kind` from asset entry 3. Validate compatibility with `slotRef` 4. Enqueue load request @@ -264,6 +265,7 @@ Each Bank must expose: * inflight memory * occupied slots * `asset_id` per slot +* `asset_name` per slot * generation per slot This enables debuggers to visualize: @@ -279,7 +281,7 @@ This enables debuggers to visualize: The following syscalls form the minimal hardware contract for asset management: ```text -asset.load(asset_id, slotRef, flags) -> handle +asset.load(asset_name, slotRef, flags) -> handle asset.status(handle) -> LoadStatus asset.commit(handle) asset.cancel(handle) @@ -292,7 +294,7 @@ Where: * `LoadStatus` ∈ { PENDING, LOADING, READY, COMMITTED, CANCELED, ERROR } * `BankStats` exposes memory usage and limits -* `SlotStats` exposes current asset_id and generation +* `SlotStats` exposes current asset_id, asset_name and generation --- diff --git a/test-cartridges/color-square/cartridge/manifest.json b/test-cartridges/color-square/cartridge/manifest.json index 2d93b0b0..416453bb 100644 --- a/test-cartridges/color-square/cartridge/manifest.json +++ b/test-cartridges/color-square/cartridge/manifest.json @@ -8,7 +8,8 @@ "entrypoint": "0", "asset_table": [ { - "asset_id": "bgm_music", + "asset_id": 0, + "asset_name": "bgm_music", "bank_type": "SOUNDS", "offset": 0, "size": 88200, @@ -19,7 +20,8 @@ } }, { - "asset_id": "mouse_cursor", + "asset_id": 1, + "asset_name": "mouse_cursor", "bank_type": "TILES", "offset": 88200, "size": 2304, @@ -33,7 +35,7 @@ } ], "preload": [ - { "asset_id": "bgm_music", "slot": 0 }, - { "asset_id": "mouse_cursor", "slot": 1 } + { "asset_name": "bgm_music", "slot": 0 }, + { "asset_name": "mouse_cursor", "slot": 1 } ] }