From c8b3b6afcce413a3992802014aee7811394ac52b Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 9 Mar 2026 07:02:39 +0000 Subject: [PATCH] PR004: implement asset status-first lifecycle surface --- crates/console/prometeu-drivers/src/asset.rs | 52 +++++++++++++------ crates/console/prometeu-hal/src/asset.rs | 18 +++++++ .../console/prometeu-hal/src/asset_bridge.rs | 9 ++-- .../src/syscalls/domains/asset.rs | 6 +-- .../src/virtual_machine_runtime/dispatch.rs | 22 ++++++-- docs/runtime/specs/15-asset-management.md | 49 +++++++++++++++++ 6 files changed, 130 insertions(+), 26 deletions(-) diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index 7a0ce068..b437f64a 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -2,7 +2,8 @@ use crate::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller}; use prometeu_hal::AssetBridge; use prometeu_hal::asset::{ - AssetEntry, BankStats, BankType, HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats, + AssetEntry, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, LoadStatus, + PreloadEntry, SlotRef, SlotStats, }; use prometeu_hal::color::Color; use prometeu_hal::sample::Sample; @@ -139,16 +140,16 @@ impl AssetBridge for AssetManager { ) { self.initialize_for_cartridge(assets, preload, assets_data) } - fn load(&self, asset_name: &str, slot: SlotRef) -> Result { + fn load(&self, asset_name: &str, slot: SlotRef) -> Result { self.load(asset_name, slot) } fn status(&self, handle: HandleId) -> LoadStatus { self.status(handle) } - fn commit(&self, handle: HandleId) { + fn commit(&self, handle: HandleId) -> AssetOpStatus { self.commit(handle) } - fn cancel(&self, handle: HandleId) { + fn cancel(&self, handle: HandleId) -> AssetOpStatus { self.cancel(handle) } fn apply_commits(&self) { @@ -293,19 +294,22 @@ impl AssetManager { } } - pub fn load(&self, asset_name: &str, slot: SlotRef) -> Result { + pub fn load(&self, asset_name: &str, slot: SlotRef) -> Result { + if slot.index >= 16 { + return Err(AssetLoadError::SlotIndexInvalid); + } let entry = { let assets = self.assets.read().unwrap(); 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() + .ok_or(AssetLoadError::AssetNotFound)?; + assets.get(id).ok_or(AssetLoadError::BackendError)?.clone() }; let asset_id = entry.asset_id; if slot.asset_type != entry.bank_type { - return Err("INCOMPATIBLE_SLOT_KIND".to_string()); + return Err(AssetLoadError::SlotKindMismatch); } let mut next_id = self.next_handle_id.lock().unwrap(); @@ -533,21 +537,38 @@ impl AssetManager { } pub fn status(&self, handle: HandleId) -> LoadStatus { - self.handles.read().unwrap().get(&handle).map(|h| h.status).unwrap_or(LoadStatus::ERROR) + self.handles + .read() + .unwrap() + .get(&handle) + .map(|h| h.status) + .unwrap_or(LoadStatus::UnknownHandle) } - pub fn commit(&self, handle: HandleId) { + pub fn commit(&self, handle: HandleId) -> AssetOpStatus { let mut handles_map = self.handles.write().unwrap(); - if let Some(h) = handles_map.get_mut(&handle) { - if h.status == LoadStatus::READY { - self.pending_commits.lock().unwrap().push(handle); - } + let Some(h) = handles_map.get_mut(&handle) else { + return AssetOpStatus::UnknownHandle; + }; + if h.status == LoadStatus::READY { + self.pending_commits.lock().unwrap().push(handle); + AssetOpStatus::Ok + } else { + AssetOpStatus::InvalidState } } - pub fn cancel(&self, handle: HandleId) { + pub fn cancel(&self, handle: HandleId) -> AssetOpStatus { + let mut final_status = AssetOpStatus::UnknownHandle; let mut handles_map = self.handles.write().unwrap(); if let Some(h) = handles_map.get_mut(&handle) { + final_status = match h.status { + LoadStatus::PENDING | LoadStatus::LOADING | LoadStatus::READY => { + AssetOpStatus::Ok + } + LoadStatus::CANCELED => AssetOpStatus::Ok, + _ => AssetOpStatus::InvalidState, + }; match h.status { LoadStatus::PENDING | LoadStatus::LOADING | LoadStatus::READY => { h.status = LoadStatus::CANCELED; @@ -557,6 +578,7 @@ impl AssetManager { } self.gfx_policy.take_staging(handle); self.sound_policy.take_staging(handle); + final_status } pub fn apply_commits(&self) { diff --git a/crates/console/prometeu-hal/src/asset.rs b/crates/console/prometeu-hal/src/asset.rs index 9957240c..771003f7 100644 --- a/crates/console/prometeu-hal/src/asset.rs +++ b/crates/console/prometeu-hal/src/asset.rs @@ -37,6 +37,24 @@ pub enum LoadStatus { COMMITTED, CANCELED, ERROR, + UnknownHandle, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(i32)] +pub enum AssetLoadError { + AssetNotFound = 1, + SlotKindMismatch = 2, + SlotIndexInvalid = 3, + BackendError = 4, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(i32)] +pub enum AssetOpStatus { + Ok = 0, + UnknownHandle = 1, + InvalidState = 2, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/console/prometeu-hal/src/asset_bridge.rs b/crates/console/prometeu-hal/src/asset_bridge.rs index fa57e8ff..21e62f03 100644 --- a/crates/console/prometeu-hal/src/asset_bridge.rs +++ b/crates/console/prometeu-hal/src/asset_bridge.rs @@ -1,5 +1,6 @@ use crate::asset::{ - AssetEntry, BankStats, BankType, HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats, + AssetEntry, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, LoadStatus, + PreloadEntry, SlotRef, SlotStats, }; pub trait AssetBridge { @@ -9,10 +10,10 @@ pub trait AssetBridge { preload: Vec, assets_data: Vec, ); - fn load(&self, asset_name: &str, slot: SlotRef) -> Result; + fn load(&self, asset_name: &str, slot: SlotRef) -> Result; fn status(&self, handle: HandleId) -> LoadStatus; - fn commit(&self, handle: HandleId); - fn cancel(&self, handle: HandleId); + fn commit(&self, handle: HandleId) -> AssetOpStatus; + fn cancel(&self, handle: HandleId) -> AssetOpStatus; fn apply_commits(&self); fn bank_info(&self, kind: BankType) -> BankStats; fn slot_info(&self, slot: SlotRef) -> SlotStats; diff --git a/crates/console/prometeu-hal/src/syscalls/domains/asset.rs b/crates/console/prometeu-hal/src/syscalls/domains/asset.rs index 661c1798..30178e50 100644 --- a/crates/console/prometeu-hal/src/syscalls/domains/asset.rs +++ b/crates/console/prometeu-hal/src/syscalls/domains/asset.rs @@ -8,7 +8,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ "load", 1, 3, - 1, + 2, caps::ASSET, Determinism::NonDeterministic, false, @@ -32,7 +32,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ "commit", 1, 1, - 0, + 1, caps::ASSET, Determinism::NonDeterministic, false, @@ -44,7 +44,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[ "cancel", 1, 1, - 0, + 1, caps::ASSET, Determinism::NonDeterministic, false, diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs index a84d1459..5caf7151 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs @@ -1,6 +1,6 @@ use super::*; use prometeu_bytecode::{TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE, Value}; -use prometeu_hal::asset::{BankType, LoadStatus, SlotRef}; +use prometeu_hal::asset::{AssetLoadError, AssetOpStatus, BankType, LoadStatus, SlotRef}; use prometeu_hal::color::Color; use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::sprite::Sprite; @@ -373,10 +373,21 @@ impl NativeInterface for VirtualMachineRuntime { match hw.assets().load(&asset_id, slot) { Ok(handle) => { + ret.push_int(AssetOpStatus::Ok as i64); ret.push_int(handle as i64); Ok(()) } - Err(e) => Err(VmFault::Panic(e)), + Err(status) => { + let status_val = match status { + AssetLoadError::AssetNotFound => 3, + AssetLoadError::SlotKindMismatch => 4, + AssetLoadError::SlotIndexInvalid => 5, + AssetLoadError::BackendError => 6, + }; + ret.push_int(status_val); + ret.push_int(0); + Ok(()) + } } } Syscall::AssetStatus => { @@ -387,16 +398,19 @@ impl NativeInterface for VirtualMachineRuntime { LoadStatus::COMMITTED => 3, LoadStatus::CANCELED => 4, LoadStatus::ERROR => 5, + LoadStatus::UnknownHandle => 6, }; ret.push_int(status_val); Ok(()) } Syscall::AssetCommit => { - hw.assets().commit(expect_int(args, 0)? as u32); + let status = hw.assets().commit(expect_int(args, 0)? as u32); + ret.push_int(status as i64); Ok(()) } Syscall::AssetCancel => { - hw.assets().cancel(expect_int(args, 0)? as u32); + let status = hw.assets().cancel(expect_int(args, 0)? as u32); + ret.push_int(status as i64); Ok(()) } Syscall::BankInfo => { diff --git a/docs/runtime/specs/15-asset-management.md b/docs/runtime/specs/15-asset-management.md index 960b4380..96bdad9f 100644 --- a/docs/runtime/specs/15-asset-management.md +++ b/docs/runtime/specs/15-asset-management.md @@ -126,3 +126,52 @@ These preload entries are consumed during cartridge initialization so the asset - [`13-cartridge.md`](13-cartridge.md) defines cartridge fields that carry `asset_table`, `preload`, and `assets.pa`. - [`16-host-abi-and-syscalls.md`](16-host-abi-and-syscalls.md) defines the syscall boundary used to manipulate assets. - [`03-memory-stack-heap-and-allocation.md`](03-memory-stack-heap-and-allocation.md) defines the distinction between VM heap memory and host-owned memory. + +## 11 Syscall Surface and Status Policy + +`asset` follows status-first policy. + +Fault boundary: + +- `Trap`: structural ABI misuse (type/arity/capability/shape mismatch); +- `status`: operational failure; +- `Panic`: internal invariant break only. + +### 11.1 MVP syscall shape + +- `asset.load(name, kind, slot) -> (status:int, handle:int)` +- `asset.status(handle) -> status:int` +- `asset.commit(handle) -> status:int` +- `asset.cancel(handle) -> status:int` + +Rules: + +- `handle` is valid only when `load` status is `OK`; +- failed `load` returns `handle = 0`; +- `commit` and `cancel` must not be silent no-op for unknown/invalid handle state. + +### 11.2 Minimum status tables + +`asset.load` request statuses: + +- `0` = `OK` +- `3` = `ASSET_NOT_FOUND` +- `4` = `SLOT_KIND_MISMATCH` +- `5` = `SLOT_INDEX_INVALID` +- `6` = `BACKEND_ERROR` + +`asset.status` lifecycle statuses: + +- `0` = `PENDING` +- `1` = `LOADING` +- `2` = `READY` +- `3` = `COMMITTED` +- `4` = `CANCELED` +- `5` = `ERROR` +- `6` = `UNKNOWN_HANDLE` + +`asset.commit` and `asset.cancel` operation statuses: + +- `0` = `OK` +- `1` = `UNKNOWN_HANDLE` +- `2` = `INVALID_STATE`