PR004: implement asset status-first lifecycle surface
This commit is contained in:
parent
998252aa25
commit
c8b3b6afcc
@ -2,7 +2,8 @@
|
|||||||
use crate::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller};
|
use crate::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller};
|
||||||
use prometeu_hal::AssetBridge;
|
use prometeu_hal::AssetBridge;
|
||||||
use prometeu_hal::asset::{
|
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::color::Color;
|
||||||
use prometeu_hal::sample::Sample;
|
use prometeu_hal::sample::Sample;
|
||||||
@ -139,16 +140,16 @@ impl AssetBridge for AssetManager {
|
|||||||
) {
|
) {
|
||||||
self.initialize_for_cartridge(assets, preload, assets_data)
|
self.initialize_for_cartridge(assets, preload, assets_data)
|
||||||
}
|
}
|
||||||
fn load(&self, asset_name: &str, slot: SlotRef) -> Result<HandleId, String> {
|
fn load(&self, asset_name: &str, slot: SlotRef) -> Result<HandleId, AssetLoadError> {
|
||||||
self.load(asset_name, slot)
|
self.load(asset_name, slot)
|
||||||
}
|
}
|
||||||
fn status(&self, handle: HandleId) -> LoadStatus {
|
fn status(&self, handle: HandleId) -> LoadStatus {
|
||||||
self.status(handle)
|
self.status(handle)
|
||||||
}
|
}
|
||||||
fn commit(&self, handle: HandleId) {
|
fn commit(&self, handle: HandleId) -> AssetOpStatus {
|
||||||
self.commit(handle)
|
self.commit(handle)
|
||||||
}
|
}
|
||||||
fn cancel(&self, handle: HandleId) {
|
fn cancel(&self, handle: HandleId) -> AssetOpStatus {
|
||||||
self.cancel(handle)
|
self.cancel(handle)
|
||||||
}
|
}
|
||||||
fn apply_commits(&self) {
|
fn apply_commits(&self) {
|
||||||
@ -293,19 +294,22 @@ impl AssetManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(&self, asset_name: &str, slot: SlotRef) -> Result<HandleId, String> {
|
pub fn load(&self, asset_name: &str, slot: SlotRef) -> Result<HandleId, AssetLoadError> {
|
||||||
|
if slot.index >= 16 {
|
||||||
|
return Err(AssetLoadError::SlotIndexInvalid);
|
||||||
|
}
|
||||||
let entry = {
|
let entry = {
|
||||||
let assets = self.assets.read().unwrap();
|
let assets = self.assets.read().unwrap();
|
||||||
let name_to_id = self.name_to_id.read().unwrap();
|
let name_to_id = self.name_to_id.read().unwrap();
|
||||||
let id = name_to_id
|
let id = name_to_id
|
||||||
.get(asset_name)
|
.get(asset_name)
|
||||||
.ok_or_else(|| format!("Asset not found: {}", asset_name))?;
|
.ok_or(AssetLoadError::AssetNotFound)?;
|
||||||
assets.get(id).ok_or_else(|| format!("Asset ID {} not found in table", id))?.clone()
|
assets.get(id).ok_or(AssetLoadError::BackendError)?.clone()
|
||||||
};
|
};
|
||||||
let asset_id = entry.asset_id;
|
let asset_id = entry.asset_id;
|
||||||
|
|
||||||
if slot.asset_type != entry.bank_type {
|
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();
|
let mut next_id = self.next_handle_id.lock().unwrap();
|
||||||
@ -533,21 +537,38 @@ impl AssetManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn status(&self, handle: HandleId) -> LoadStatus {
|
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();
|
let mut handles_map = self.handles.write().unwrap();
|
||||||
if let Some(h) = handles_map.get_mut(&handle) {
|
let Some(h) = handles_map.get_mut(&handle) else {
|
||||||
if h.status == LoadStatus::READY {
|
return AssetOpStatus::UnknownHandle;
|
||||||
self.pending_commits.lock().unwrap().push(handle);
|
};
|
||||||
}
|
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();
|
let mut handles_map = self.handles.write().unwrap();
|
||||||
if let Some(h) = handles_map.get_mut(&handle) {
|
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 {
|
match h.status {
|
||||||
LoadStatus::PENDING | LoadStatus::LOADING | LoadStatus::READY => {
|
LoadStatus::PENDING | LoadStatus::LOADING | LoadStatus::READY => {
|
||||||
h.status = LoadStatus::CANCELED;
|
h.status = LoadStatus::CANCELED;
|
||||||
@ -557,6 +578,7 @@ impl AssetManager {
|
|||||||
}
|
}
|
||||||
self.gfx_policy.take_staging(handle);
|
self.gfx_policy.take_staging(handle);
|
||||||
self.sound_policy.take_staging(handle);
|
self.sound_policy.take_staging(handle);
|
||||||
|
final_status
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_commits(&self) {
|
pub fn apply_commits(&self) {
|
||||||
|
|||||||
@ -37,6 +37,24 @@ pub enum LoadStatus {
|
|||||||
COMMITTED,
|
COMMITTED,
|
||||||
CANCELED,
|
CANCELED,
|
||||||
ERROR,
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use crate::asset::{
|
use crate::asset::{
|
||||||
AssetEntry, BankStats, BankType, HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats,
|
AssetEntry, AssetLoadError, AssetOpStatus, BankStats, BankType, HandleId, LoadStatus,
|
||||||
|
PreloadEntry, SlotRef, SlotStats,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait AssetBridge {
|
pub trait AssetBridge {
|
||||||
@ -9,10 +10,10 @@ pub trait AssetBridge {
|
|||||||
preload: Vec<PreloadEntry>,
|
preload: Vec<PreloadEntry>,
|
||||||
assets_data: Vec<u8>,
|
assets_data: Vec<u8>,
|
||||||
);
|
);
|
||||||
fn load(&self, asset_name: &str, slot: SlotRef) -> Result<HandleId, String>;
|
fn load(&self, asset_name: &str, slot: SlotRef) -> Result<HandleId, AssetLoadError>;
|
||||||
fn status(&self, handle: HandleId) -> LoadStatus;
|
fn status(&self, handle: HandleId) -> LoadStatus;
|
||||||
fn commit(&self, handle: HandleId);
|
fn commit(&self, handle: HandleId) -> AssetOpStatus;
|
||||||
fn cancel(&self, handle: HandleId);
|
fn cancel(&self, handle: HandleId) -> AssetOpStatus;
|
||||||
fn apply_commits(&self);
|
fn apply_commits(&self);
|
||||||
fn bank_info(&self, kind: BankType) -> BankStats;
|
fn bank_info(&self, kind: BankType) -> BankStats;
|
||||||
fn slot_info(&self, slot: SlotRef) -> SlotStats;
|
fn slot_info(&self, slot: SlotRef) -> SlotStats;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[
|
|||||||
"load",
|
"load",
|
||||||
1,
|
1,
|
||||||
3,
|
3,
|
||||||
1,
|
2,
|
||||||
caps::ASSET,
|
caps::ASSET,
|
||||||
Determinism::NonDeterministic,
|
Determinism::NonDeterministic,
|
||||||
false,
|
false,
|
||||||
@ -32,7 +32,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[
|
|||||||
"commit",
|
"commit",
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
0,
|
1,
|
||||||
caps::ASSET,
|
caps::ASSET,
|
||||||
Determinism::NonDeterministic,
|
Determinism::NonDeterministic,
|
||||||
false,
|
false,
|
||||||
@ -44,7 +44,7 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[
|
|||||||
"cancel",
|
"cancel",
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
0,
|
1,
|
||||||
caps::ASSET,
|
caps::ASSET,
|
||||||
Determinism::NonDeterministic,
|
Determinism::NonDeterministic,
|
||||||
false,
|
false,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use prometeu_bytecode::{TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE, Value};
|
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::color::Color;
|
||||||
use prometeu_hal::log::{LogLevel, LogSource};
|
use prometeu_hal::log::{LogLevel, LogSource};
|
||||||
use prometeu_hal::sprite::Sprite;
|
use prometeu_hal::sprite::Sprite;
|
||||||
@ -373,10 +373,21 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
|
|
||||||
match hw.assets().load(&asset_id, slot) {
|
match hw.assets().load(&asset_id, slot) {
|
||||||
Ok(handle) => {
|
Ok(handle) => {
|
||||||
|
ret.push_int(AssetOpStatus::Ok as i64);
|
||||||
ret.push_int(handle as i64);
|
ret.push_int(handle as i64);
|
||||||
Ok(())
|
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 => {
|
Syscall::AssetStatus => {
|
||||||
@ -387,16 +398,19 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
LoadStatus::COMMITTED => 3,
|
LoadStatus::COMMITTED => 3,
|
||||||
LoadStatus::CANCELED => 4,
|
LoadStatus::CANCELED => 4,
|
||||||
LoadStatus::ERROR => 5,
|
LoadStatus::ERROR => 5,
|
||||||
|
LoadStatus::UnknownHandle => 6,
|
||||||
};
|
};
|
||||||
ret.push_int(status_val);
|
ret.push_int(status_val);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Syscall::AssetCommit => {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Syscall::AssetCancel => {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Syscall::BankInfo => {
|
Syscall::BankInfo => {
|
||||||
|
|||||||
@ -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`.
|
- [`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.
|
- [`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.
|
- [`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`
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user