Asset Status-First Surface and Lifecycle
This commit is contained in:
parent
9cb0e77b01
commit
0c32457a10
@ -786,29 +786,37 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::memory_banks::{MemoryBanks, SoundBankPoolAccess, TileBankPoolAccess};
|
use crate::memory_banks::{MemoryBanks, SoundBankPoolAccess, TileBankPoolAccess};
|
||||||
|
|
||||||
#[test]
|
fn test_tile_asset_data() -> Vec<u8> {
|
||||||
fn test_asset_loading_flow() {
|
|
||||||
let banks = Arc::new(MemoryBanks::new());
|
|
||||||
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
|
|
||||||
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
|
||||||
|
|
||||||
let mut data = vec![1u8; 256];
|
let mut data = vec![1u8; 256];
|
||||||
data.extend_from_slice(&[0u8; 2048]);
|
data.extend_from_slice(&[0u8; 2048]);
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
let asset_entry = AssetEntry {
|
fn test_tile_asset_entry(asset_name: &str, data_len: usize) -> AssetEntry {
|
||||||
|
AssetEntry {
|
||||||
asset_id: 0,
|
asset_id: 0,
|
||||||
asset_name: "test_tiles".to_string(),
|
asset_name: asset_name.to_string(),
|
||||||
bank_type: BankType::TILES,
|
bank_type: BankType::TILES,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
size: data.len() as u64,
|
size: data_len as u64,
|
||||||
decoded_size: data.len() as u64,
|
decoded_size: data_len as u64,
|
||||||
codec: "RAW".to_string(),
|
codec: "RAW".to_string(),
|
||||||
metadata: serde_json::json!({
|
metadata: serde_json::json!({
|
||||||
"tile_size": 16,
|
"tile_size": 16,
|
||||||
"width": 16,
|
"width": 16,
|
||||||
"height": 16
|
"height": 16
|
||||||
}),
|
}),
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_asset_loading_flow() {
|
||||||
|
let banks = Arc::new(MemoryBanks::new());
|
||||||
|
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
|
||||||
|
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
||||||
|
|
||||||
|
let data = test_tile_asset_data();
|
||||||
|
let asset_entry = test_tile_asset_entry("test_tiles", data.len());
|
||||||
|
|
||||||
let am = AssetManager::new(vec![asset_entry], data, gfx_installer, sound_installer);
|
let am = AssetManager::new(vec![asset_entry], data, gfx_installer, sound_installer);
|
||||||
let slot = SlotRef::gfx(0);
|
let slot = SlotRef::gfx(0);
|
||||||
@ -842,23 +850,8 @@ mod tests {
|
|||||||
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
|
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
|
||||||
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
||||||
|
|
||||||
let mut data = vec![1u8; 256];
|
let data = test_tile_asset_data();
|
||||||
data.extend_from_slice(&[0u8; 2048]);
|
let asset_entry = test_tile_asset_entry("test_tiles", data.len());
|
||||||
|
|
||||||
let asset_entry = AssetEntry {
|
|
||||||
asset_id: 0,
|
|
||||||
asset_name: "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, sound_installer);
|
let am = AssetManager::new(vec![asset_entry], data, gfx_installer, sound_installer);
|
||||||
|
|
||||||
@ -959,19 +952,9 @@ mod tests {
|
|||||||
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
|
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
|
||||||
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
||||||
|
|
||||||
let mut data = vec![0u8; 256]; // pixels
|
let data = test_tile_asset_data();
|
||||||
data.extend_from_slice(&[0u8; 2048]); // palette
|
let mut asset_entry = test_tile_asset_entry("my_tiles", data.len());
|
||||||
|
asset_entry.asset_id = 10;
|
||||||
let asset_entry = AssetEntry {
|
|
||||||
asset_id: 10,
|
|
||||||
asset_name: "my_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 preload = vec![PreloadEntry { asset_name: "my_tiles".to_string(), slot: 3 }];
|
let preload = vec![PreloadEntry { asset_name: "my_tiles".to_string(), slot: 3 }];
|
||||||
|
|
||||||
@ -982,4 +965,89 @@ mod tests {
|
|||||||
assert_eq!(am.find_slot_by_name("unknown", BankType::TILES), None);
|
assert_eq!(am.find_slot_by_name("unknown", BankType::TILES), None);
|
||||||
assert_eq!(am.find_slot_by_name("my_tiles", BankType::SOUNDS), None);
|
assert_eq!(am.find_slot_by_name("my_tiles", BankType::SOUNDS), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_returns_asset_not_found() {
|
||||||
|
let banks = Arc::new(MemoryBanks::new());
|
||||||
|
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
|
||||||
|
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
||||||
|
let am = AssetManager::new(vec![], vec![], gfx_installer, sound_installer);
|
||||||
|
|
||||||
|
let result = am.load("missing", SlotRef::gfx(0));
|
||||||
|
|
||||||
|
assert_eq!(result, Err(AssetLoadError::AssetNotFound));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_returns_slot_index_invalid() {
|
||||||
|
let banks = Arc::new(MemoryBanks::new());
|
||||||
|
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
|
||||||
|
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
||||||
|
let data = test_tile_asset_data();
|
||||||
|
let am = AssetManager::new(
|
||||||
|
vec![test_tile_asset_entry("test_tiles", data.len())],
|
||||||
|
data,
|
||||||
|
gfx_installer,
|
||||||
|
sound_installer,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = am.load("test_tiles", SlotRef::gfx(16));
|
||||||
|
|
||||||
|
assert_eq!(result, Err(AssetLoadError::SlotIndexInvalid));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_returns_slot_kind_mismatch() {
|
||||||
|
let banks = Arc::new(MemoryBanks::new());
|
||||||
|
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
|
||||||
|
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
||||||
|
let data = test_tile_asset_data();
|
||||||
|
let am = AssetManager::new(
|
||||||
|
vec![test_tile_asset_entry("test_tiles", data.len())],
|
||||||
|
data,
|
||||||
|
gfx_installer,
|
||||||
|
sound_installer,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = am.load("test_tiles", SlotRef::audio(0));
|
||||||
|
|
||||||
|
assert_eq!(result, Err(AssetLoadError::SlotKindMismatch));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_status_returns_unknown_handle() {
|
||||||
|
let banks = Arc::new(MemoryBanks::new());
|
||||||
|
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
|
||||||
|
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
||||||
|
let am = AssetManager::new(vec![], vec![], gfx_installer, sound_installer);
|
||||||
|
|
||||||
|
assert_eq!(am.status(999), LoadStatus::UnknownHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_commit_and_cancel_return_explicit_statuses() {
|
||||||
|
let banks = Arc::new(MemoryBanks::new());
|
||||||
|
let gfx_installer = Arc::clone(&banks) as Arc<dyn TileBankPoolInstaller>;
|
||||||
|
let sound_installer = Arc::clone(&banks) as Arc<dyn SoundBankPoolInstaller>;
|
||||||
|
let data = test_tile_asset_data();
|
||||||
|
let am = AssetManager::new(
|
||||||
|
vec![test_tile_asset_entry("test_tiles", data.len())],
|
||||||
|
data,
|
||||||
|
gfx_installer,
|
||||||
|
sound_installer,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(am.commit(999), AssetOpStatus::UnknownHandle);
|
||||||
|
assert_eq!(am.cancel(999), AssetOpStatus::UnknownHandle);
|
||||||
|
|
||||||
|
let handle = am.load("test_tiles", SlotRef::gfx(0)).expect("load must allocate handle");
|
||||||
|
let start = Instant::now();
|
||||||
|
while am.status(handle) != LoadStatus::READY && start.elapsed().as_secs() < 5 {
|
||||||
|
thread::sleep(std::time::Duration::from_millis(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(am.cancel(handle), AssetOpStatus::Ok);
|
||||||
|
assert_eq!(am.status(handle), LoadStatus::CANCELED);
|
||||||
|
assert_eq!(am.commit(handle), AssetOpStatus::InvalidState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,23 +30,24 @@ pub struct PreloadEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[repr(i32)]
|
||||||
pub enum LoadStatus {
|
pub enum LoadStatus {
|
||||||
PENDING,
|
PENDING = 0,
|
||||||
LOADING,
|
LOADING = 1,
|
||||||
READY,
|
READY = 2,
|
||||||
COMMITTED,
|
COMMITTED = 3,
|
||||||
CANCELED,
|
CANCELED = 4,
|
||||||
ERROR,
|
ERROR = 5,
|
||||||
UnknownHandle,
|
UnknownHandle = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
pub enum AssetLoadError {
|
pub enum AssetLoadError {
|
||||||
AssetNotFound = 1,
|
AssetNotFound = 3,
|
||||||
SlotKindMismatch = 2,
|
SlotKindMismatch = 4,
|
||||||
SlotIndexInvalid = 3,
|
SlotIndexInvalid = 5,
|
||||||
BackendError = 4,
|
BackendError = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
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 crate::services::memcard::{MemcardSlotState, MemcardStatus};
|
use crate::services::memcard::{MemcardSlotState, MemcardStatus};
|
||||||
use prometeu_hal::asset::{AssetLoadError, AssetOpStatus, BankType, LoadStatus, SlotRef};
|
use prometeu_hal::asset::{AssetOpStatus, BankType, SlotRef};
|
||||||
use prometeu_hal::cartridge::AppMode;
|
use prometeu_hal::cartridge::AppMode;
|
||||||
use prometeu_hal::color::Color;
|
use prometeu_hal::color::Color;
|
||||||
use prometeu_hal::log::{LogLevel, LogSource};
|
use prometeu_hal::log::{LogLevel, LogSource};
|
||||||
@ -458,29 +458,14 @@ impl NativeInterface for VirtualMachineRuntime {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(status) => {
|
Err(status) => {
|
||||||
let status_val = match status {
|
ret.push_int(status as i64);
|
||||||
AssetLoadError::AssetNotFound => 3,
|
|
||||||
AssetLoadError::SlotKindMismatch => 4,
|
|
||||||
AssetLoadError::SlotIndexInvalid => 5,
|
|
||||||
AssetLoadError::BackendError => 6,
|
|
||||||
};
|
|
||||||
ret.push_int(status_val);
|
|
||||||
ret.push_int(0);
|
ret.push_int(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Syscall::AssetStatus => {
|
Syscall::AssetStatus => {
|
||||||
let status_val = match hw.assets().status(expect_int(args, 0)? as u32) {
|
ret.push_int(hw.assets().status(expect_int(args, 0)? as u32) as i64);
|
||||||
LoadStatus::PENDING => 0,
|
|
||||||
LoadStatus::LOADING => 1,
|
|
||||||
LoadStatus::READY => 2,
|
|
||||||
LoadStatus::COMMITTED => 3,
|
|
||||||
LoadStatus::CANCELED => 4,
|
|
||||||
LoadStatus::ERROR => 5,
|
|
||||||
LoadStatus::UnknownHandle => 6,
|
|
||||||
};
|
|
||||||
ret.push_int(status_val);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Syscall::AssetCommit => {
|
Syscall::AssetCommit => {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use prometeu_bytecode::model::{BytecodeModule, ConstantPoolEntry, FunctionMeta,
|
|||||||
use prometeu_drivers::hardware::Hardware;
|
use prometeu_drivers::hardware::Hardware;
|
||||||
use crate::fs::{FsBackend, FsEntry, FsError};
|
use crate::fs::{FsBackend, FsEntry, FsError};
|
||||||
use prometeu_hal::AudioOpStatus;
|
use prometeu_hal::AudioOpStatus;
|
||||||
use prometeu_hal::asset::AssetOpStatus;
|
use prometeu_hal::asset::{AssetEntry, AssetLoadError, AssetOpStatus, BankType, LoadStatus};
|
||||||
use prometeu_hal::GfxOpStatus;
|
use prometeu_hal::GfxOpStatus;
|
||||||
use prometeu_hal::InputSignals;
|
use prometeu_hal::InputSignals;
|
||||||
use prometeu_hal::cartridge::Cartridge;
|
use prometeu_hal::cartridge::Cartridge;
|
||||||
@ -92,6 +92,29 @@ fn serialized_single_function_module_with_consts(
|
|||||||
.serialize()
|
.serialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_tile_asset_entry(asset_name: &str, data_len: usize) -> AssetEntry {
|
||||||
|
AssetEntry {
|
||||||
|
asset_id: 7,
|
||||||
|
asset_name: asset_name.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
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_tile_asset_data() -> Vec<u8> {
|
||||||
|
let mut data = vec![1u8; 256];
|
||||||
|
data.extend_from_slice(&[0u8; 2048]);
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() {
|
fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
@ -569,6 +592,184 @@ fn tick_asset_commit_operational_error_returns_status_not_crash() {
|
|||||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::UnknownHandle as i64)]);
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::UnknownHandle as i64)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_asset_load_missing_asset_returns_status_and_zero_handle() {
|
||||||
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
let code = assemble("PUSH_CONST 0\nPUSH_I32 0\nPUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||||
|
let program = serialized_single_function_module_with_consts(
|
||||||
|
code,
|
||||||
|
vec![ConstantPoolEntry::String("missing_asset".into())],
|
||||||
|
vec![SyscallDecl {
|
||||||
|
module: "asset".into(),
|
||||||
|
name: "load".into(),
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 3,
|
||||||
|
ret_slots: 2,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||||
|
|
||||||
|
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||||
|
assert!(report.is_none(), "missing asset must not crash");
|
||||||
|
assert!(vm.is_halted());
|
||||||
|
assert_eq!(
|
||||||
|
vm.operand_stack_top(2),
|
||||||
|
vec![Value::Int64(0), Value::Int64(AssetLoadError::AssetNotFound as i64)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() {
|
||||||
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
let asset_data = test_tile_asset_data();
|
||||||
|
hardware.assets.initialize_for_cartridge(
|
||||||
|
vec![test_tile_asset_entry("tile_asset", asset_data.len())],
|
||||||
|
vec![],
|
||||||
|
asset_data,
|
||||||
|
);
|
||||||
|
let code = assemble("PUSH_CONST 0\nPUSH_I32 0\nPUSH_I32 16\nHOSTCALL 0\nHALT").expect("assemble");
|
||||||
|
let program = serialized_single_function_module_with_consts(
|
||||||
|
code,
|
||||||
|
vec![ConstantPoolEntry::String("tile_asset".into())],
|
||||||
|
vec![SyscallDecl {
|
||||||
|
module: "asset".into(),
|
||||||
|
name: "load".into(),
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 3,
|
||||||
|
ret_slots: 2,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||||
|
|
||||||
|
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||||
|
assert!(report.is_none(), "invalid slot must not crash");
|
||||||
|
assert!(vm.is_halted());
|
||||||
|
assert_eq!(
|
||||||
|
vm.operand_stack_top(2),
|
||||||
|
vec![Value::Int64(0), Value::Int64(AssetLoadError::SlotIndexInvalid as i64)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_asset_load_kind_mismatch_returns_status_and_zero_handle() {
|
||||||
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
let asset_data = test_tile_asset_data();
|
||||||
|
hardware.assets.initialize_for_cartridge(
|
||||||
|
vec![test_tile_asset_entry("tile_asset", asset_data.len())],
|
||||||
|
vec![],
|
||||||
|
asset_data,
|
||||||
|
);
|
||||||
|
let code = assemble("PUSH_CONST 0\nPUSH_I32 1\nPUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||||
|
let program = serialized_single_function_module_with_consts(
|
||||||
|
code,
|
||||||
|
vec![ConstantPoolEntry::String("tile_asset".into())],
|
||||||
|
vec![SyscallDecl {
|
||||||
|
module: "asset".into(),
|
||||||
|
name: "load".into(),
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 3,
|
||||||
|
ret_slots: 2,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||||
|
|
||||||
|
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||||
|
assert!(report.is_none(), "slot kind mismatch must not crash");
|
||||||
|
assert!(vm.is_halted());
|
||||||
|
assert_eq!(
|
||||||
|
vm.operand_stack_top(2),
|
||||||
|
vec![Value::Int64(0), Value::Int64(AssetLoadError::SlotKindMismatch as i64)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_asset_status_unknown_handle_returns_status_not_crash() {
|
||||||
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
let code = assemble("PUSH_I32 999\nHOSTCALL 0\nHALT").expect("assemble");
|
||||||
|
let program = serialized_single_function_module(
|
||||||
|
code,
|
||||||
|
vec![SyscallDecl {
|
||||||
|
module: "asset".into(),
|
||||||
|
name: "status".into(),
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||||
|
|
||||||
|
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||||
|
assert!(report.is_none(), "unknown asset handle must not crash");
|
||||||
|
assert!(vm.is_halted());
|
||||||
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(LoadStatus::UnknownHandle as i64)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
|
||||||
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
let code = assemble(
|
||||||
|
"PUSH_I32 1\nHOSTCALL 0\nPOP_N 1\nPUSH_I32 1\nHOSTCALL 1\nHALT",
|
||||||
|
)
|
||||||
|
.expect("assemble");
|
||||||
|
let program = serialized_single_function_module(
|
||||||
|
code,
|
||||||
|
vec![
|
||||||
|
SyscallDecl {
|
||||||
|
module: "asset".into(),
|
||||||
|
name: "cancel".into(),
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
},
|
||||||
|
SyscallDecl {
|
||||||
|
module: "asset".into(),
|
||||||
|
name: "commit".into(),
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 1,
|
||||||
|
ret_slots: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||||
|
|
||||||
|
let asset_data = test_tile_asset_data();
|
||||||
|
hardware.assets.initialize_for_cartridge(
|
||||||
|
vec![test_tile_asset_entry("tile_asset", asset_data.len())],
|
||||||
|
vec![],
|
||||||
|
asset_data,
|
||||||
|
);
|
||||||
|
let handle = hardware
|
||||||
|
.assets
|
||||||
|
.load("tile_asset", prometeu_hal::asset::SlotRef::gfx(0))
|
||||||
|
.expect("asset handle must be allocated");
|
||||||
|
|
||||||
|
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||||
|
assert!(report.is_none(), "invalid transition must not crash");
|
||||||
|
assert!(vm.is_halted());
|
||||||
|
assert_eq!(handle, 1);
|
||||||
|
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(AssetOpStatus::InvalidState as i64)]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tick_gfx_set_sprite_type_mismatch_surfaces_trap_not_panic() {
|
fn tick_gfx_set_sprite_type_mismatch_surfaces_trap_not_panic() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
|||||||
@ -1,54 +0,0 @@
|
|||||||
# PR004 - Asset Status-First Surface and Lifecycle
|
|
||||||
|
|
||||||
## Briefing
|
|
||||||
|
|
||||||
A decision `010` fechou a superficie final de `asset`:
|
|
||||||
|
|
||||||
- `asset.load(name, kind, slot) -> (status, handle)`
|
|
||||||
- `asset.status(handle) -> status`
|
|
||||||
- `asset.commit(handle) -> status`
|
|
||||||
- `asset.cancel(handle) -> status`
|
|
||||||
|
|
||||||
Sem no-op silencioso e sem `Panic` operacional.
|
|
||||||
|
|
||||||
## Alvo
|
|
||||||
|
|
||||||
Implementar o contrato de `asset` em spec, registry e runtime.
|
|
||||||
|
|
||||||
Arquivos principais:
|
|
||||||
|
|
||||||
- `docs/runtime/specs/15-asset-management.md`
|
|
||||||
- `crates/console/prometeu-hal/src/syscalls/domains/asset.rs`
|
|
||||||
- `crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs`
|
|
||||||
- `crates/console/prometeu-drivers/src/asset.rs`
|
|
||||||
- `crates/console/prometeu-hal/src/asset_bridge.rs` (se necessario)
|
|
||||||
|
|
||||||
## Escopo Funcional
|
|
||||||
|
|
||||||
- substituir `Err -> Panic` de `asset.load` por status operacional;
|
|
||||||
- tornar `asset.commit` e `asset.cancel` retornos explicitos de status;
|
|
||||||
- explicitar `UNKNOWN_HANDLE` em `asset.status`;
|
|
||||||
- validar slot index/kind no request path de `load`;
|
|
||||||
- manter erros de residency/slot no dominio `asset` (nao migrar para `bank`).
|
|
||||||
|
|
||||||
## Fora de Escopo
|
|
||||||
|
|
||||||
- redesign da politica interna de residencia;
|
|
||||||
- novos dominios de asset alem de `TILES` e `SOUNDS`.
|
|
||||||
|
|
||||||
## Critérios de Aceite
|
|
||||||
|
|
||||||
- assinaturas de syscall `asset` alinhadas com decision `010`;
|
|
||||||
- `asset.load` nao produz `Panic` para falha operacional;
|
|
||||||
- `commit`/`cancel` nao ficam em no-op silencioso;
|
|
||||||
- tabela de status por operacao documentada e testada.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
- `cargo test -p prometeu-system`
|
|
||||||
- `cargo test -p prometeu-drivers`
|
|
||||||
- cenarios de:
|
|
||||||
- handle desconhecido;
|
|
||||||
- transicao invalida;
|
|
||||||
- asset nao encontrado;
|
|
||||||
- slot invalido/kind mismatch.
|
|
||||||
@ -149,6 +149,7 @@ Rules:
|
|||||||
- `handle` is valid only when `load` status is `OK`;
|
- `handle` is valid only when `load` status is `OK`;
|
||||||
- failed `load` returns `handle = 0`;
|
- failed `load` returns `handle = 0`;
|
||||||
- `commit` and `cancel` must not be silent no-op for unknown/invalid handle state.
|
- `commit` and `cancel` must not be silent no-op for unknown/invalid handle state.
|
||||||
|
- slot validation, kind mismatch, and residency/lifecycle rejection remain in `asset` status space and are not delegated to `bank`.
|
||||||
|
|
||||||
### 11.2 Minimum status tables
|
### 11.2 Minimum status tables
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user