dev/asset-management #6
@ -18,8 +18,10 @@ pub struct Codegen {
|
|||||||
pub symbols: Vec<Symbol>,
|
pub symbols: Vec<Symbol>,
|
||||||
instructions: Vec<(Asm, bool)>, // (Asm, has_symbol)
|
instructions: Vec<(Asm, bool)>, // (Asm, has_symbol)
|
||||||
locals: HashMap<String, u32>,
|
locals: HashMap<String, u32>,
|
||||||
|
globals: HashMap<String, u32>,
|
||||||
constant_pool: Vec<ConstantPoolEntry>,
|
constant_pool: Vec<ConstantPoolEntry>,
|
||||||
next_local: u32,
|
next_local: u32,
|
||||||
|
next_global: u32,
|
||||||
label_count: u32,
|
label_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,8 +33,10 @@ impl Codegen {
|
|||||||
symbols: Vec::new(),
|
symbols: Vec::new(),
|
||||||
instructions: Vec::new(),
|
instructions: Vec::new(),
|
||||||
locals: HashMap::new(),
|
locals: HashMap::new(),
|
||||||
|
globals: HashMap::new(),
|
||||||
constant_pool: vec![ConstantPoolEntry::Null], // Index 0 is always Null
|
constant_pool: vec![ConstantPoolEntry::Null], // Index 0 is always Null
|
||||||
next_local: 0,
|
next_local: 0,
|
||||||
|
next_global: 0,
|
||||||
label_count: 0,
|
label_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,7 +56,7 @@ impl Codegen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile_programs(&mut self, programs: Vec<(String, String, &Program)>) -> Result<Vec<u8>> {
|
pub fn compile_programs(&mut self, programs: Vec<(String, String, &Program)>) -> Result<Vec<u8>> {
|
||||||
// First pass: collect all functions and their indices
|
// First pass: collect all functions and global variables
|
||||||
let mut all_functions = Vec::new();
|
let mut all_functions = Vec::new();
|
||||||
for (file, source, program) in &programs {
|
for (file, source, program) in &programs {
|
||||||
for item in &program.body {
|
for item in &program.body {
|
||||||
@ -63,6 +67,29 @@ impl Codegen {
|
|||||||
Statement::ExportNamedDeclaration(decl) => {
|
Statement::ExportNamedDeclaration(decl) => {
|
||||||
if let Some(Declaration::FunctionDeclaration(f)) = &decl.declaration {
|
if let Some(Declaration::FunctionDeclaration(f)) = &decl.declaration {
|
||||||
all_functions.push((file.clone(), source.clone(), f.as_ref()));
|
all_functions.push((file.clone(), source.clone(), f.as_ref()));
|
||||||
|
} else if let Some(Declaration::VariableDeclaration(var)) = &decl.declaration {
|
||||||
|
for decl in &var.declarations {
|
||||||
|
if let BindingPattern::BindingIdentifier(ident) = &decl.id {
|
||||||
|
let name = ident.name.to_string();
|
||||||
|
if !self.globals.contains_key(&name) {
|
||||||
|
let id = self.next_global;
|
||||||
|
self.globals.insert(name, id);
|
||||||
|
self.next_global += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::VariableDeclaration(var) => {
|
||||||
|
for decl in &var.declarations {
|
||||||
|
if let BindingPattern::BindingIdentifier(ident) = &decl.id {
|
||||||
|
let name = ident.name.to_string();
|
||||||
|
if !self.globals.contains_key(&name) {
|
||||||
|
let id = self.next_global;
|
||||||
|
self.globals.insert(name, id);
|
||||||
|
self.next_global += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -70,8 +97,8 @@ impl Codegen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find tick function (should be in the first program, which is the entry)
|
// Find frame function (should be in the first program, which is the entry)
|
||||||
let mut tick_fn_name = None;
|
let mut frame_fn_name = None;
|
||||||
if let Some((_, _, entry_program)) = programs.first() {
|
if let Some((_, _, entry_program)) = programs.first() {
|
||||||
for item in &entry_program.body {
|
for item in &entry_program.body {
|
||||||
let f_opt = match item {
|
let f_opt = match item {
|
||||||
@ -88,8 +115,8 @@ impl Codegen {
|
|||||||
|
|
||||||
if let Some(f) = f_opt {
|
if let Some(f) = f_opt {
|
||||||
if let Some(ident) = &f.id {
|
if let Some(ident) = &f.id {
|
||||||
if ident.name == "tick" {
|
if ident.name == "frame" {
|
||||||
tick_fn_name = Some(ident.name.to_string());
|
frame_fn_name = Some(ident.name.to_string());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,11 +124,45 @@ impl Codegen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let tick_fn_name = tick_fn_name.ok_or_else(|| anyhow!("export function tick() not found in entry file"))?;
|
let frame_fn_name = frame_fn_name.ok_or_else(|| anyhow!("export function frame() not found in entry file"))?;
|
||||||
|
|
||||||
// Entry point: loop calling tick
|
// Initialize globals
|
||||||
|
for (file, source, program) in &programs {
|
||||||
|
self.file_name = file.clone();
|
||||||
|
self.source_text = source.clone();
|
||||||
|
for item in &program.body {
|
||||||
|
let var_opt = match item {
|
||||||
|
Statement::VariableDeclaration(var) => Some(var.as_ref()),
|
||||||
|
Statement::ExportNamedDeclaration(decl) => {
|
||||||
|
if let Some(Declaration::VariableDeclaration(var)) = &decl.declaration {
|
||||||
|
Some(var.as_ref())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(var) = var_opt {
|
||||||
|
for decl in &var.declarations {
|
||||||
|
if let BindingPattern::BindingIdentifier(ident) = &decl.id {
|
||||||
|
let name = ident.name.to_string();
|
||||||
|
let id = *self.globals.get(&name).unwrap();
|
||||||
|
if let Some(init) = &decl.init {
|
||||||
|
self.compile_expr(init)?;
|
||||||
|
} else {
|
||||||
|
self.emit_op(OpCode::PushI32, vec![Operand::I32(0)], decl.span);
|
||||||
|
}
|
||||||
|
self.emit_op(OpCode::SetGlobal, vec![Operand::U32(id)], decl.span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry point: loop calling frame
|
||||||
self.emit_label("entry".to_string());
|
self.emit_label("entry".to_string());
|
||||||
self.emit_op(OpCode::Call, vec![Operand::Label(tick_fn_name), Operand::U32(0)], Span::default());
|
self.emit_op(OpCode::Call, vec![Operand::Label(frame_fn_name), Operand::U32(0)], Span::default());
|
||||||
self.emit_op(OpCode::Pop, vec![], Span::default());
|
self.emit_op(OpCode::Pop, vec![], Span::default());
|
||||||
self.emit_op(OpCode::FrameSync, vec![], Span::default());
|
self.emit_op(OpCode::FrameSync, vec![], Span::default());
|
||||||
self.emit_op(OpCode::Jmp, vec![Operand::Label("entry".to_string())], Span::default());
|
self.emit_op(OpCode::Jmp, vec![Operand::Label("entry".to_string())], Span::default());
|
||||||
@ -221,7 +282,7 @@ impl Codegen {
|
|||||||
if val.fract() == 0.0 && val >= i32::MIN as f64 && val <= i32::MAX as f64 {
|
if val.fract() == 0.0 && val >= i32::MIN as f64 && val <= i32::MAX as f64 {
|
||||||
self.emit_op(OpCode::PushI32, vec![Operand::I32(val as i32)], n.span);
|
self.emit_op(OpCode::PushI32, vec![Operand::I32(val as i32)], n.span);
|
||||||
} else {
|
} else {
|
||||||
self.emit_op(OpCode::PushI64, vec![Operand::I64(val as i64)], n.span);
|
self.emit_op(OpCode::PushF64, vec![Operand::F64(val)], n.span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::BooleanLiteral(b) => {
|
Expression::BooleanLiteral(b) => {
|
||||||
@ -238,6 +299,8 @@ impl Codegen {
|
|||||||
let name = ident.name.to_string();
|
let name = ident.name.to_string();
|
||||||
if let Some(&id) = self.locals.get(&name) {
|
if let Some(&id) = self.locals.get(&name) {
|
||||||
self.emit_op(OpCode::GetLocal, vec![Operand::U32(id)], ident.span);
|
self.emit_op(OpCode::GetLocal, vec![Operand::U32(id)], ident.span);
|
||||||
|
} else if let Some(&id) = self.globals.get(&name) {
|
||||||
|
self.emit_op(OpCode::GetGlobal, vec![Operand::U32(id)], ident.span);
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span));
|
return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span));
|
||||||
}
|
}
|
||||||
@ -249,6 +312,9 @@ impl Codegen {
|
|||||||
if let Some(&id) = self.locals.get(&name) {
|
if let Some(&id) = self.locals.get(&name) {
|
||||||
self.emit_op(OpCode::SetLocal, vec![Operand::U32(id)], assign.span);
|
self.emit_op(OpCode::SetLocal, vec![Operand::U32(id)], assign.span);
|
||||||
self.emit_op(OpCode::GetLocal, vec![Operand::U32(id)], assign.span);
|
self.emit_op(OpCode::GetLocal, vec![Operand::U32(id)], assign.span);
|
||||||
|
} else if let Some(&id) = self.globals.get(&name) {
|
||||||
|
self.emit_op(OpCode::SetGlobal, vec![Operand::U32(id)], assign.span);
|
||||||
|
self.emit_op(OpCode::GetGlobal, vec![Operand::U32(id)], assign.span);
|
||||||
} else {
|
} else {
|
||||||
return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span));
|
return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,6 +80,14 @@ pub enum DebugEvent {
|
|||||||
vm_steps: u32,
|
vm_steps: u32,
|
||||||
syscalls: u32,
|
syscalls: u32,
|
||||||
cycles: u64,
|
cycles: u64,
|
||||||
|
host_cpu_time_us: u64,
|
||||||
|
violations: u32,
|
||||||
|
gfx_used_bytes: usize,
|
||||||
|
gfx_inflight_bytes: usize,
|
||||||
|
gfx_slots_occupied: u32,
|
||||||
|
audio_used_bytes: usize,
|
||||||
|
audio_inflight_bytes: usize,
|
||||||
|
audio_slots_occupied: u32,
|
||||||
},
|
},
|
||||||
#[serde(rename = "cert")]
|
#[serde(rename = "cert")]
|
||||||
Cert {
|
Cert {
|
||||||
|
|||||||
@ -46,8 +46,11 @@ impl Firmware {
|
|||||||
///
|
///
|
||||||
/// This method is called exactly once per Host frame (60Hz).
|
/// This method is called exactly once per Host frame (60Hz).
|
||||||
/// It updates peripheral signals and delegates the logic to the current state.
|
/// It updates peripheral signals and delegates the logic to the current state.
|
||||||
pub fn step_frame(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) {
|
pub fn tick(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) {
|
||||||
// 1. Update peripheral state using the latest signals from the Host.
|
// 0. Process asset commits at the beginning of the frame boundary.
|
||||||
|
hw.assets_mut().apply_commits();
|
||||||
|
|
||||||
|
// 1. Update the peripheral state using the latest signals from the Host.
|
||||||
// This ensures input is consistent throughout the entire update.
|
// This ensures input is consistent throughout the entire update.
|
||||||
hw.pad_mut().begin_frame(signals);
|
hw.pad_mut().begin_frame(signals);
|
||||||
hw.touch_mut().begin_frame(signals);
|
hw.touch_mut().begin_frame(signals);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ impl GameRunningStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
let result = ctx.os.step_frame(ctx.vm, ctx.signals, ctx.hw);
|
let result = ctx.os.tick(ctx.vm, ctx.signals, ctx.hw);
|
||||||
|
|
||||||
if !ctx.os.logical_frame_active {
|
if !ctx.os.logical_frame_active {
|
||||||
ctx.hw.gfx_mut().present();
|
ctx.hw.gfx_mut().present();
|
||||||
|
|||||||
@ -21,7 +21,7 @@ impl HubHomeStep {
|
|||||||
ctx.hub.window_manager.remove_window(focused_id);
|
ctx.hub.window_manager.remove_window(focused_id);
|
||||||
} else {
|
} else {
|
||||||
// System App runs here, drawing over the Hub background
|
// System App runs here, drawing over the Hub background
|
||||||
error = ctx.os.step_frame(ctx.vm, ctx.signals, ctx.hw);
|
error = ctx.os.tick(ctx.vm, ctx.signals, ctx.hw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,14 @@ pub struct LoadCartridgeStep {
|
|||||||
impl LoadCartridgeStep {
|
impl LoadCartridgeStep {
|
||||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||||
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, format!("Loading cartridge: {}", self.cartridge.title));
|
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, format!("Loading cartridge: {}", self.cartridge.title));
|
||||||
|
|
||||||
|
// Initialize Asset Manager
|
||||||
|
ctx.hw.assets_mut().initialize_for_cartridge(
|
||||||
|
self.cartridge.asset_table.clone(),
|
||||||
|
self.cartridge.preload.clone(),
|
||||||
|
self.cartridge.assets.clone()
|
||||||
|
);
|
||||||
|
|
||||||
ctx.os.initialize_vm(ctx.vm, &self.cartridge);
|
ctx.os.initialize_vm(ctx.vm, &self.cartridge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep};
|
use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep};
|
||||||
use crate::firmware::prometeu_context::PrometeuContext;
|
use crate::firmware::prometeu_context::PrometeuContext;
|
||||||
use crate::hardware::LoopMode;
|
|
||||||
use crate::model::Color;
|
|
||||||
use crate::log::{LogLevel, LogSource};
|
use crate::log::{LogLevel, LogSource};
|
||||||
|
use crate::model::Color;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SplashScreenStep {
|
pub struct SplashScreenStep {
|
||||||
@ -13,9 +12,7 @@ impl SplashScreenStep {
|
|||||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||||
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Showing SplashScreen".to_string());
|
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Showing SplashScreen".to_string());
|
||||||
// Play sound on enter
|
// Play sound on enter
|
||||||
if let Some(sample) = ctx.os.sample_square.clone() {
|
// ctx.hw.audio_mut().play(0, 0, 0, 255, 127, 1.0, 0, LoopMode::Off);
|
||||||
ctx.hw.audio_mut().play(sample, 0, 255, 127, 1.0, 0, LoopMode::Off);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||||
|
|||||||
864
crates/prometeu-core/src/hardware/asset.rs
Normal file
864
crates/prometeu-core/src/hardware/asset.rs
Normal file
@ -0,0 +1,864 @@
|
|||||||
|
use crate::hardware::memory_banks::{TileBankPoolInstaller, SoundBankPoolInstaller};
|
||||||
|
use crate::model::{AssetEntry, BankStats, BankType, Color, HandleId, LoadStatus, SlotRef, SlotStats, TileBank, TileSize, SoundBank, Sample, PreloadEntry};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
/// Resident metadata for a decoded/materialized asset inside a BankPolicy.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ResidentEntry<T> {
|
||||||
|
/// The resident, materialized object.
|
||||||
|
pub value: Arc<T>,
|
||||||
|
|
||||||
|
/// 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<T> ResidentEntry<T> {
|
||||||
|
pub fn new(value: Arc<T>, 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<T> {
|
||||||
|
/// Dedup table: asset_id -> resident entry (value + telemetry).
|
||||||
|
resident: Arc<RwLock<HashMap<u32, ResidentEntry<T>>>>,
|
||||||
|
|
||||||
|
/// Staging area: handle -> value ready to commit.
|
||||||
|
staging: Arc<RwLock<HashMap<HandleId, Arc<T>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> BankPolicy<T> {
|
||||||
|
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: u32) -> Option<Arc<T>> {
|
||||||
|
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<T>.
|
||||||
|
pub fn put_resident(&self, asset_id: u32, value: Arc<T>, bytes: usize) -> Arc<T> {
|
||||||
|
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<T>) {
|
||||||
|
self.staging.write().unwrap().insert(handle, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take staged value (used by commit path).
|
||||||
|
pub fn take_staging(&self, handle: HandleId) -> Option<Arc<T>> {
|
||||||
|
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<RwLock<HashMap<u32, AssetEntry>>>,
|
||||||
|
name_to_id: Arc<RwLock<HashMap<String, u32>>>,
|
||||||
|
handles: Arc<RwLock<HashMap<HandleId, LoadHandleInfo>>>,
|
||||||
|
next_handle_id: Mutex<HandleId>,
|
||||||
|
assets_data: Arc<RwLock<Vec<u8>>>,
|
||||||
|
|
||||||
|
/// Narrow hardware interfaces
|
||||||
|
gfx_installer: Arc<dyn TileBankPoolInstaller>,
|
||||||
|
sound_installer: Arc<dyn SoundBankPoolInstaller>,
|
||||||
|
|
||||||
|
/// Track what is installed in each hardware slot (for stats/info).
|
||||||
|
gfx_slots: Arc<RwLock<[Option<u32>; 16]>>,
|
||||||
|
sound_slots: Arc<RwLock<[Option<u32>; 16]>>,
|
||||||
|
|
||||||
|
/// Residency policy for GFX tile banks.
|
||||||
|
gfx_policy: BankPolicy<TileBank>,
|
||||||
|
/// Residency policy for sound banks.
|
||||||
|
sound_policy: BankPolicy<SoundBank>,
|
||||||
|
|
||||||
|
// Commits that are ready to be applied at the next frame boundary.
|
||||||
|
pending_commits: Mutex<Vec<HandleId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoadHandleInfo {
|
||||||
|
_asset_id: u32,
|
||||||
|
slot: SlotRef,
|
||||||
|
status: LoadStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetManager {
|
||||||
|
pub fn new(
|
||||||
|
assets: Vec<AssetEntry>,
|
||||||
|
assets_data: Vec<u8>,
|
||||||
|
gfx_installer: Arc<dyn TileBankPoolInstaller>,
|
||||||
|
sound_installer: Arc<dyn SoundBankPoolInstaller>,
|
||||||
|
) -> Self {
|
||||||
|
let mut asset_map = HashMap::new();
|
||||||
|
let mut name_to_id = HashMap::new();
|
||||||
|
for entry in assets {
|
||||||
|
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))),
|
||||||
|
sound_slots: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
|
||||||
|
gfx_policy: BankPolicy::new(),
|
||||||
|
sound_policy: BankPolicy::new(),
|
||||||
|
handles: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
next_handle_id: Mutex::new(1),
|
||||||
|
assets_data: Arc::new(RwLock::new(assets_data)),
|
||||||
|
pending_commits: Mutex::new(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize_for_cartridge(&self, assets: Vec<AssetEntry>, preload: Vec<PreloadEntry>, assets_data: Vec<u8>) {
|
||||||
|
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() {
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Perform Preload for assets in the preload list
|
||||||
|
for item in preload {
|
||||||
|
let entry_opt = {
|
||||||
|
let assets = self.assets.read().unwrap();
|
||||||
|
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 {
|
||||||
|
let slot_index = item.slot;
|
||||||
|
match entry.bank_type {
|
||||||
|
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, 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);
|
||||||
|
}
|
||||||
|
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_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, 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);
|
||||||
|
}
|
||||||
|
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_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("[AssetManager] Preload failed: asset '{}' not found in table", item.asset_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(&self, asset_name: &str, slot: SlotRef) -> Result<HandleId, String> {
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
let asset_id = entry.asset_id;
|
||||||
|
|
||||||
|
if slot.asset_type != entry.bank_type {
|
||||||
|
return Err("INCOMPATIBLE_SLOT_KIND".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut next_id = self.next_handle_id.lock().unwrap();
|
||||||
|
let handle_id = *next_id;
|
||||||
|
*next_id += 1;
|
||||||
|
|
||||||
|
// Check if already resident (Dedup)
|
||||||
|
let already_resident = match entry.bank_type {
|
||||||
|
BankType::TILES => {
|
||||||
|
if let Some(bank) = self.gfx_policy.get_resident(asset_id) {
|
||||||
|
self.gfx_policy.stage(handle_id, bank);
|
||||||
|
true
|
||||||
|
} else { false }
|
||||||
|
}
|
||||||
|
BankType::SOUNDS => {
|
||||||
|
if let Some(bank) = self.sound_policy.get_resident(asset_id) {
|
||||||
|
self.sound_policy.stage(handle_id, bank);
|
||||||
|
true
|
||||||
|
} else { false }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if already_resident {
|
||||||
|
self.handles.write().unwrap().insert(handle_id, LoadHandleInfo {
|
||||||
|
_asset_id: asset_id,
|
||||||
|
slot,
|
||||||
|
status: LoadStatus::READY,
|
||||||
|
});
|
||||||
|
return Ok(handle_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not resident, start loading
|
||||||
|
self.handles.write().unwrap().insert(handle_id, LoadHandleInfo {
|
||||||
|
_asset_id: asset_id,
|
||||||
|
slot,
|
||||||
|
status: LoadStatus::PENDING,
|
||||||
|
});
|
||||||
|
|
||||||
|
let handles = self.handles.clone();
|
||||||
|
let assets_data = self.assets_data.clone();
|
||||||
|
let entry_clone = entry.clone();
|
||||||
|
|
||||||
|
// Capture policies for the worker thread
|
||||||
|
let gfx_policy_resident = Arc::clone(&self.gfx_policy.resident);
|
||||||
|
let gfx_policy_staging = Arc::clone(&self.gfx_policy.staging);
|
||||||
|
let sound_policy_resident = Arc::clone(&self.sound_policy.resident);
|
||||||
|
let sound_policy_staging = Arc::clone(&self.sound_policy.staging);
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
// Update status to LOADING
|
||||||
|
{
|
||||||
|
let mut handles_map = handles.write().unwrap();
|
||||||
|
if let Some(h) = handles_map.get_mut(&handle_id) {
|
||||||
|
if h.status == LoadStatus::PENDING {
|
||||||
|
h.status = LoadStatus::LOADING;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match entry_clone.bank_type {
|
||||||
|
BankType::TILES => {
|
||||||
|
let result = Self::perform_load_tile_bank(&entry_clone, assets_data);
|
||||||
|
if let Ok(tilebank) = result {
|
||||||
|
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) {
|
||||||
|
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, entry);
|
||||||
|
bank_arc
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gfx_policy_staging.write().unwrap().insert(handle_id, resident_arc);
|
||||||
|
let mut handles_map = handles.write().unwrap();
|
||||||
|
if let Some(h) = handles_map.get_mut(&handle_id) {
|
||||||
|
if h.status == LoadStatus::LOADING { h.status = LoadStatus::READY; }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut handles_map = handles.write().unwrap();
|
||||||
|
if let Some(h) = handles_map.get_mut(&handle_id) { h.status = LoadStatus::ERROR; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BankType::SOUNDS => {
|
||||||
|
let result = Self::perform_load_sound_bank(&entry_clone, assets_data);
|
||||||
|
if let Ok(soundbank) = result {
|
||||||
|
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) {
|
||||||
|
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, entry);
|
||||||
|
bank_arc
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sound_policy_staging.write().unwrap().insert(handle_id, resident_arc);
|
||||||
|
let mut handles_map = handles.write().unwrap();
|
||||||
|
if let Some(h) = handles_map.get_mut(&handle_id) {
|
||||||
|
if h.status == LoadStatus::LOADING { h.status = LoadStatus::READY; }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut handles_map = handles.write().unwrap();
|
||||||
|
if let Some(h) = handles_map.get_mut(&handle_id) { h.status = LoadStatus::ERROR; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(handle_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_load_tile_bank(entry: &AssetEntry, assets_data: Arc<RwLock<Vec<u8>>>) -> Result<TileBank, String> {
|
||||||
|
if entry.codec != "RAW" {
|
||||||
|
return Err(format!("Unsupported codec: {}", entry.codec));
|
||||||
|
}
|
||||||
|
|
||||||
|
let assets_data = assets_data.read().unwrap();
|
||||||
|
|
||||||
|
let start = entry.offset as usize;
|
||||||
|
let end = start + entry.size as usize;
|
||||||
|
|
||||||
|
if end > assets_data.len() {
|
||||||
|
return Err("Asset offset/size out of bounds".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = &assets_data[start..end];
|
||||||
|
|
||||||
|
// Decode TILEBANK metadata
|
||||||
|
let tile_size_val = entry.metadata.get("tile_size").and_then(|v| v.as_u64()).ok_or("Missing tile_size")?;
|
||||||
|
let width = entry.metadata.get("width").and_then(|v| v.as_u64()).ok_or("Missing width")? as usize;
|
||||||
|
let height = entry.metadata.get("height").and_then(|v| v.as_u64()).ok_or("Missing height")? as usize;
|
||||||
|
|
||||||
|
let tile_size = match tile_size_val {
|
||||||
|
8 => TileSize::Size8,
|
||||||
|
16 => TileSize::Size16,
|
||||||
|
32 => TileSize::Size32,
|
||||||
|
_ => return Err(format!("Invalid tile_size: {}", tile_size_val)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let pixel_data_size = width * height;
|
||||||
|
if buffer.len() < pixel_data_size + 2048 {
|
||||||
|
return Err("Buffer too small for TILEBANK".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let pixel_indices = buffer[0..pixel_data_size].to_vec();
|
||||||
|
let palette_data = &buffer[pixel_data_size..pixel_data_size + 2048];
|
||||||
|
|
||||||
|
let mut palettes = [[Color::BLACK; 16]; 64];
|
||||||
|
for p in 0..64 {
|
||||||
|
for c in 0..16 {
|
||||||
|
let offset = (p * 16 + c) * 2;
|
||||||
|
let color_raw = u16::from_le_bytes([palette_data[offset], palette_data[offset + 1]]);
|
||||||
|
palettes[p][c] = Color(color_raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(TileBank {
|
||||||
|
tile_size,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
pixel_indices,
|
||||||
|
palettes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_load_sound_bank(entry: &AssetEntry, assets_data: Arc<RwLock<Vec<u8>>>) -> Result<SoundBank, String> {
|
||||||
|
if entry.codec != "RAW" {
|
||||||
|
return Err(format!("Unsupported codec: {}", entry.codec));
|
||||||
|
}
|
||||||
|
|
||||||
|
let assets_data = assets_data.read().unwrap();
|
||||||
|
|
||||||
|
let start = entry.offset as usize;
|
||||||
|
let end = start + entry.size as usize;
|
||||||
|
|
||||||
|
if end > assets_data.len() {
|
||||||
|
return Err("Asset offset/size out of bounds".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = &assets_data[start..end];
|
||||||
|
|
||||||
|
let sample_rate = entry.metadata.get("sample_rate").and_then(|v| v.as_u64()).unwrap_or(44100) as u32;
|
||||||
|
|
||||||
|
let mut data = Vec::with_capacity(buffer.len() / 2);
|
||||||
|
for i in (0..buffer.len()).step_by(2) {
|
||||||
|
if i + 1 < buffer.len() {
|
||||||
|
data.push(i16::from_le_bytes([buffer[i], buffer[i+1]]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sample = Arc::new(Sample::new(sample_rate, data));
|
||||||
|
Ok(SoundBank::new(vec![sample]))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status(&self, handle: HandleId) -> LoadStatus {
|
||||||
|
self.handles.read().unwrap().get(&handle).map(|h| h.status).unwrap_or(LoadStatus::ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit(&self, handle: HandleId) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel(&self, handle: HandleId) {
|
||||||
|
let mut handles_map = self.handles.write().unwrap();
|
||||||
|
if let Some(h) = handles_map.get_mut(&handle) {
|
||||||
|
match h.status {
|
||||||
|
LoadStatus::PENDING | LoadStatus::LOADING | LoadStatus::READY => {
|
||||||
|
h.status = LoadStatus::CANCELED;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.gfx_policy.take_staging(handle);
|
||||||
|
self.sound_policy.take_staging(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_commits(&self) {
|
||||||
|
let mut pending = self.pending_commits.lock().unwrap();
|
||||||
|
let mut handles = self.handles.write().unwrap();
|
||||||
|
|
||||||
|
for handle_id in pending.drain(..) {
|
||||||
|
if let Some(h) = handles.get_mut(&handle_id) {
|
||||||
|
if h.status == LoadStatus::READY {
|
||||||
|
match h.slot.asset_type {
|
||||||
|
BankType::TILES => {
|
||||||
|
if let Some(bank) = self.gfx_policy.take_staging(handle_id) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
h.status = LoadStatus::COMMITTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BankType::SOUNDS => {
|
||||||
|
if let Some(bank) = self.sound_policy.take_staging(handle_id) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
h.status = LoadStatus::COMMITTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bank_info(&self, kind: BankType) -> BankStats {
|
||||||
|
match kind {
|
||||||
|
BankType::TILES => {
|
||||||
|
let mut used_bytes = 0;
|
||||||
|
{
|
||||||
|
let resident = self.gfx_policy.resident.read().unwrap();
|
||||||
|
for entry in resident.values() {
|
||||||
|
used_bytes += entry.bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inflight_bytes = 0;
|
||||||
|
{
|
||||||
|
let staging = self.gfx_policy.staging.read().unwrap();
|
||||||
|
let assets = self.assets.read().unwrap();
|
||||||
|
let handles = self.handles.read().unwrap();
|
||||||
|
|
||||||
|
for (handle_id, _) in staging.iter() {
|
||||||
|
if let Some(h) = handles.get(handle_id) {
|
||||||
|
if let Some(entry) = assets.get(&h._asset_id) {
|
||||||
|
inflight_bytes += entry.decoded_size as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut slots_occupied = 0;
|
||||||
|
{
|
||||||
|
let slots = self.gfx_slots.read().unwrap();
|
||||||
|
for s in slots.iter() {
|
||||||
|
if s.is_some() { slots_occupied += 1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BankStats {
|
||||||
|
total_bytes: 16 * 1024 * 1024,
|
||||||
|
used_bytes,
|
||||||
|
free_bytes: (16usize * 1024 * 1024).saturating_sub(used_bytes),
|
||||||
|
inflight_bytes,
|
||||||
|
slot_count: 16,
|
||||||
|
slots_occupied,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BankType::SOUNDS => {
|
||||||
|
let mut used_bytes = 0;
|
||||||
|
{
|
||||||
|
let resident = self.sound_policy.resident.read().unwrap();
|
||||||
|
for entry in resident.values() {
|
||||||
|
used_bytes += entry.bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inflight_bytes = 0;
|
||||||
|
{
|
||||||
|
let staging = self.sound_policy.staging.read().unwrap();
|
||||||
|
let assets = self.assets.read().unwrap();
|
||||||
|
let handles = self.handles.read().unwrap();
|
||||||
|
|
||||||
|
for (handle_id, _) in staging.iter() {
|
||||||
|
if let Some(h) = handles.get(handle_id) {
|
||||||
|
if let Some(entry) = assets.get(&h._asset_id) {
|
||||||
|
inflight_bytes += entry.decoded_size as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut slots_occupied = 0;
|
||||||
|
{
|
||||||
|
let slots = self.sound_slots.read().unwrap();
|
||||||
|
for s in slots.iter() {
|
||||||
|
if s.is_some() { slots_occupied += 1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BankStats {
|
||||||
|
total_bytes: 32 * 1024 * 1024,
|
||||||
|
used_bytes,
|
||||||
|
free_bytes: (32usize * 1024 * 1024).saturating_sub(used_bytes),
|
||||||
|
inflight_bytes,
|
||||||
|
slot_count: 16,
|
||||||
|
slots_occupied,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slot_info(&self, slot: SlotRef) -> SlotStats {
|
||||||
|
match slot.asset_type {
|
||||||
|
BankType::TILES => {
|
||||||
|
let slots = self.gfx_slots.read().unwrap();
|
||||||
|
let asset_id = slots.get(slot.index).and_then(|s| s.clone());
|
||||||
|
|
||||||
|
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);
|
||||||
|
let name = self.assets.read().unwrap().get(id).map(|e| e.asset_name.clone());
|
||||||
|
(bytes, name)
|
||||||
|
} else {
|
||||||
|
(0, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
SlotStats {
|
||||||
|
asset_id,
|
||||||
|
asset_name,
|
||||||
|
generation: 0,
|
||||||
|
resident_bytes: bytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BankType::SOUNDS => {
|
||||||
|
let slots = self.sound_slots.read().unwrap();
|
||||||
|
let asset_id = slots.get(slot.index).and_then(|s| s.clone());
|
||||||
|
|
||||||
|
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);
|
||||||
|
let name = self.assets.read().unwrap().get(id).map(|e| e.asset_name.clone());
|
||||||
|
(bytes, name)
|
||||||
|
} else {
|
||||||
|
(0, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
SlotStats {
|
||||||
|
asset_id,
|
||||||
|
asset_name,
|
||||||
|
generation: 0,
|
||||||
|
resident_bytes: bytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_slot_by_name(&self, asset_name: &str, kind: BankType) -> Option<u8> {
|
||||||
|
let asset_id = {
|
||||||
|
let name_to_id = self.name_to_id.read().unwrap();
|
||||||
|
*name_to_id.get(asset_name)?
|
||||||
|
};
|
||||||
|
|
||||||
|
match kind {
|
||||||
|
BankType::TILES => {
|
||||||
|
let slots = self.gfx_slots.read().unwrap();
|
||||||
|
slots.iter().position(|&s| s == Some(asset_id)).map(|p| p as u8)
|
||||||
|
}
|
||||||
|
BankType::SOUNDS => {
|
||||||
|
let slots = self.sound_slots.read().unwrap();
|
||||||
|
slots.iter().position(|&s| s == Some(asset_id)).map(|p| p as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(&self) {
|
||||||
|
self.gfx_policy.clear();
|
||||||
|
self.sound_policy.clear();
|
||||||
|
self.handles.write().unwrap().clear();
|
||||||
|
self.pending_commits.lock().unwrap().clear();
|
||||||
|
self.gfx_slots.write().unwrap().fill(None);
|
||||||
|
self.sound_slots.write().unwrap().fill(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::hardware::memory_banks::{TileBankPoolAccess, SoundBankPoolAccess, MemoryBanks};
|
||||||
|
|
||||||
|
#[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 mut data = vec![1u8; 256];
|
||||||
|
data.extend_from_slice(&[0u8; 2048]);
|
||||||
|
|
||||||
|
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 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.tile_bank_slot(0).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_asset_dedup() {
|
||||||
|
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];
|
||||||
|
data.extend_from_slice(&[0u8; 2048]);
|
||||||
|
|
||||||
|
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 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
let handle2 = am.load("test_tiles", SlotRef::gfx(1)).unwrap();
|
||||||
|
assert_eq!(am.status(handle2), LoadStatus::READY);
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sound_asset_loading() {
|
||||||
|
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>;
|
||||||
|
|
||||||
|
// 100 samples of 16-bit PCM (zeros)
|
||||||
|
let data = vec![0u8; 200];
|
||||||
|
|
||||||
|
let asset_entry = AssetEntry {
|
||||||
|
asset_id: 1,
|
||||||
|
asset_name: "test_sound".to_string(),
|
||||||
|
bank_type: BankType::SOUNDS,
|
||||||
|
offset: 0,
|
||||||
|
size: data.len() as u64,
|
||||||
|
decoded_size: data.len() as u64,
|
||||||
|
codec: "RAW".to_string(),
|
||||||
|
metadata: serde_json::json!({
|
||||||
|
"sample_rate": 44100
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let am = AssetManager::new(vec![asset_entry], data, gfx_installer, sound_installer);
|
||||||
|
let slot = SlotRef::audio(0);
|
||||||
|
|
||||||
|
let handle = am.load("test_sound", slot).expect("Should start loading");
|
||||||
|
|
||||||
|
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.status(handle), LoadStatus::READY);
|
||||||
|
|
||||||
|
am.commit(handle);
|
||||||
|
am.apply_commits();
|
||||||
|
|
||||||
|
assert_eq!(am.status(handle), LoadStatus::COMMITTED);
|
||||||
|
assert!(banks.sound_bank_slot(0).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preload_on_init() {
|
||||||
|
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 = vec![0u8; 200];
|
||||||
|
|
||||||
|
let asset_entry = AssetEntry {
|
||||||
|
asset_id: 2,
|
||||||
|
asset_name: "preload_sound".to_string(),
|
||||||
|
bank_type: BankType::SOUNDS,
|
||||||
|
offset: 0,
|
||||||
|
size: data.len() as u64,
|
||||||
|
decoded_size: data.len() as u64,
|
||||||
|
codec: "RAW".to_string(),
|
||||||
|
metadata: serde_json::json!({
|
||||||
|
"sample_rate": 44100
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let preload = vec![
|
||||||
|
PreloadEntry { asset_name: "preload_sound".to_string(), slot: 5 }
|
||||||
|
];
|
||||||
|
|
||||||
|
let am = AssetManager::new(vec![], vec![], gfx_installer, sound_installer);
|
||||||
|
|
||||||
|
// Before init, slot 5 is empty
|
||||||
|
assert!(banks.sound_bank_slot(5).is_none());
|
||||||
|
|
||||||
|
am.initialize_for_cartridge(vec![asset_entry], preload, data);
|
||||||
|
|
||||||
|
// 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(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_slot_by_name() {
|
||||||
|
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![0u8; 256]; // pixels
|
||||||
|
data.extend_from_slice(&[0u8; 2048]); // palette
|
||||||
|
|
||||||
|
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 am = AssetManager::new(vec![], vec![], gfx_installer, sound_installer);
|
||||||
|
am.initialize_for_cartridge(vec![asset_entry], preload, data);
|
||||||
|
|
||||||
|
assert_eq!(am.find_slot_by_name("my_tiles", BankType::TILES), Some(3));
|
||||||
|
assert_eq!(am.find_slot_by_name("unknown", BankType::TILES), None);
|
||||||
|
assert_eq!(am.find_slot_by_name("my_tiles", BankType::SOUNDS), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
use crate::hardware::memory_banks::SoundBankPoolAccess;
|
||||||
use crate::model::Sample;
|
use crate::model::Sample;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -21,8 +22,10 @@ pub enum LoopMode {
|
|||||||
/// The Core maintains this state to provide information to the App (e.g., is_playing),
|
/// The Core maintains this state to provide information to the App (e.g., is_playing),
|
||||||
/// but the actual real-time mixing is performed by the Host using commands.
|
/// but the actual real-time mixing is performed by the Host using commands.
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
/// Reference to the PCM data being played.
|
/// The actual sample data being played.
|
||||||
pub sample: Option<Arc<Sample>>,
|
pub sample: Option<Arc<Sample>>,
|
||||||
|
/// Whether this channel is currently active.
|
||||||
|
pub active: bool,
|
||||||
/// Current playback position within the sample (fractional for pitch shifting).
|
/// Current playback position within the sample (fractional for pitch shifting).
|
||||||
pub pos: f64,
|
pub pos: f64,
|
||||||
/// Playback speed multiplier (1.0 = original speed).
|
/// Playback speed multiplier (1.0 = original speed).
|
||||||
@ -41,6 +44,7 @@ impl Default for Channel {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
sample: None,
|
sample: None,
|
||||||
|
active: false,
|
||||||
pos: 0.0,
|
pos: 0.0,
|
||||||
pitch: 1.0,
|
pitch: 1.0,
|
||||||
volume: 255,
|
volume: 255,
|
||||||
@ -85,6 +89,10 @@ pub enum AudioCommand {
|
|||||||
voice_id: usize,
|
voice_id: usize,
|
||||||
pitch: f64,
|
pitch: f64,
|
||||||
},
|
},
|
||||||
|
/// Pause all audio processing.
|
||||||
|
MasterPause,
|
||||||
|
/// Resume audio processing.
|
||||||
|
MasterResume,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PROMETEU Audio Subsystem.
|
/// PROMETEU Audio Subsystem.
|
||||||
@ -97,68 +105,99 @@ pub struct Audio {
|
|||||||
pub voices: [Channel; MAX_CHANNELS],
|
pub voices: [Channel; MAX_CHANNELS],
|
||||||
/// Queue of pending commands to be processed by the Host mixer.
|
/// Queue of pending commands to be processed by the Host mixer.
|
||||||
pub commands: Vec<AudioCommand>,
|
pub commands: Vec<AudioCommand>,
|
||||||
|
/// Interface to access sound memory banks.
|
||||||
|
pub sound_banks: Arc<dyn SoundBankPoolAccess>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Audio {
|
impl Audio {
|
||||||
/// Initializes the audio system with empty voices.
|
/// Initializes the audio system with empty voices and sound bank access.
|
||||||
pub fn new() -> Self {
|
pub fn new(sound_banks: Arc<dyn SoundBankPoolAccess>) -> Self {
|
||||||
const EMPTY_CHANNEL: Channel = Channel {
|
|
||||||
sample: None,
|
|
||||||
pos: 0.0,
|
|
||||||
pitch: 1.0,
|
|
||||||
volume: 255,
|
|
||||||
pan: 127,
|
|
||||||
loop_mode: LoopMode::Off,
|
|
||||||
priority: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
voices: [EMPTY_CHANNEL; MAX_CHANNELS],
|
voices: std::array::from_fn(|_| Channel::default()),
|
||||||
commands: Vec::new(),
|
commands: Vec::new(),
|
||||||
|
sound_banks,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play(&mut self, sample: Arc<Sample>, voice_id: usize, volume: u8, pan: u8, pitch: f64, priority: u8, loop_mode: LoopMode) {
|
pub fn play(&mut self, bank_id: u8, sample_id: u16, voice_id: usize, volume: u8, pan: u8, pitch: f64, priority: u8, loop_mode: LoopMode) {
|
||||||
if voice_id < MAX_CHANNELS {
|
if voice_id >= MAX_CHANNELS {
|
||||||
self.commands.push(AudioCommand::Play {
|
return;
|
||||||
sample,
|
|
||||||
voice_id,
|
|
||||||
volume,
|
|
||||||
pan,
|
|
||||||
pitch,
|
|
||||||
priority,
|
|
||||||
loop_mode,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve the sample from the hardware pools
|
||||||
|
let sample = self.sound_banks.sound_bank_slot(bank_id as usize)
|
||||||
|
.and_then(|bank| bank.samples.get(sample_id as usize).map(Arc::clone));
|
||||||
|
|
||||||
|
|
||||||
|
if let Some(s) = sample {
|
||||||
|
println!("[Audio] Resolved sample from bank {} sample {}. Playing on voice {}.", bank_id, sample_id, voice_id);
|
||||||
|
self.play_sample(s, voice_id, volume, pan, pitch, priority, loop_mode);
|
||||||
|
} else {
|
||||||
|
eprintln!("[Audio] Failed to resolve sample from bank {} sample {}.", bank_id, sample_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_sample(&mut self, sample: Arc<Sample>, voice_id: usize, volume: u8, pan: u8, pitch: f64, priority: u8, loop_mode: LoopMode) {
|
||||||
|
if voice_id >= MAX_CHANNELS {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update local state
|
||||||
|
self.voices[voice_id] = Channel {
|
||||||
|
sample: Some(Arc::clone(&sample)),
|
||||||
|
active: true,
|
||||||
|
pos: 0.0,
|
||||||
|
pitch,
|
||||||
|
volume,
|
||||||
|
pan,
|
||||||
|
loop_mode,
|
||||||
|
priority,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Push command to the host
|
||||||
|
self.commands.push(AudioCommand::Play {
|
||||||
|
sample,
|
||||||
|
voice_id,
|
||||||
|
volume,
|
||||||
|
pan,
|
||||||
|
pitch,
|
||||||
|
priority,
|
||||||
|
loop_mode,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&mut self, voice_id: usize) {
|
pub fn stop(&mut self, voice_id: usize) {
|
||||||
if voice_id < MAX_CHANNELS {
|
if voice_id < MAX_CHANNELS {
|
||||||
|
self.voices[voice_id].active = false;
|
||||||
|
self.voices[voice_id].sample = None;
|
||||||
self.commands.push(AudioCommand::Stop { voice_id });
|
self.commands.push(AudioCommand::Stop { voice_id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_volume(&mut self, voice_id: usize, volume: u8) {
|
pub fn set_volume(&mut self, voice_id: usize, volume: u8) {
|
||||||
if voice_id < MAX_CHANNELS {
|
if voice_id < MAX_CHANNELS {
|
||||||
|
self.voices[voice_id].volume = volume;
|
||||||
self.commands.push(AudioCommand::SetVolume { voice_id, volume });
|
self.commands.push(AudioCommand::SetVolume { voice_id, volume });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_pan(&mut self, voice_id: usize, pan: u8) {
|
pub fn set_pan(&mut self, voice_id: usize, pan: u8) {
|
||||||
if voice_id < MAX_CHANNELS {
|
if voice_id < MAX_CHANNELS {
|
||||||
|
self.voices[voice_id].pan = pan;
|
||||||
self.commands.push(AudioCommand::SetPan { voice_id, pan });
|
self.commands.push(AudioCommand::SetPan { voice_id, pan });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_pitch(&mut self, voice_id: usize, pitch: f64) {
|
pub fn set_pitch(&mut self, voice_id: usize, pitch: f64) {
|
||||||
if voice_id < MAX_CHANNELS {
|
if voice_id < MAX_CHANNELS {
|
||||||
|
self.voices[voice_id].pitch = pitch;
|
||||||
self.commands.push(AudioCommand::SetPitch { voice_id, pitch });
|
self.commands.push(AudioCommand::SetPitch { voice_id, pitch });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_playing(&self, voice_id: usize) -> bool {
|
pub fn is_playing(&self, voice_id: usize) -> bool {
|
||||||
if voice_id < MAX_CHANNELS {
|
if voice_id < MAX_CHANNELS {
|
||||||
self.voices[voice_id].sample.is_some()
|
self.voices[voice_id].active
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
|
use crate::hardware::memory_banks::TileBankPoolAccess;
|
||||||
use crate::model::{Color, HudTileLayer, ScrollableTileLayer, Sprite, TileBank, TileMap, TileSize};
|
use crate::model::{Color, HudTileLayer, ScrollableTileLayer, Sprite, TileBank, TileMap, TileSize};
|
||||||
use std::mem::size_of;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Blending modes inspired by classic 16-bit hardware.
|
/// Blending modes inspired by classic 16-bit hardware.
|
||||||
/// Defines how source pixels are combined with existing pixels in the framebuffer.
|
/// Defines how source pixels are combined with existing pixels in the framebuffer.
|
||||||
@ -46,8 +47,8 @@ pub struct Gfx {
|
|||||||
pub layers: [ScrollableTileLayer; 4],
|
pub layers: [ScrollableTileLayer; 4],
|
||||||
/// 1 fixed layer for User Interface.
|
/// 1 fixed layer for User Interface.
|
||||||
pub hud: HudTileLayer,
|
pub hud: HudTileLayer,
|
||||||
/// Up to 16 sets of graphical assets (tiles + palettes).
|
/// Interface to access graphical memory banks.
|
||||||
pub banks: [Option<TileBank>; 16],
|
pub tile_banks: Arc<dyn TileBankPoolAccess>,
|
||||||
/// Hardware sprites (Object Attribute Memory equivalent).
|
/// Hardware sprites (Object Attribute Memory equivalent).
|
||||||
pub sprites: [Sprite; 512],
|
pub sprites: [Sprite; 512],
|
||||||
|
|
||||||
@ -65,9 +66,8 @@ pub struct Gfx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Gfx {
|
impl Gfx {
|
||||||
/// Initializes the graphics system with a specific resolution.
|
/// Initializes the graphics system with a specific resolution and shared memory banks.
|
||||||
pub fn new(w: usize, h: usize) -> Self {
|
pub fn new(w: usize, h: usize, tile_banks: Arc<dyn TileBankPoolAccess>) -> Self {
|
||||||
const EMPTY_BANK: Option<TileBank> = None;
|
|
||||||
const EMPTY_SPRITE: Sprite = Sprite {
|
const EMPTY_SPRITE: Sprite = Sprite {
|
||||||
tile: crate::model::Tile { id: 0, flip_x: false, flip_y: false, palette_id: 0 },
|
tile: crate::model::Tile { id: 0, flip_x: false, flip_y: false, palette_id: 0 },
|
||||||
x: 0,
|
x: 0,
|
||||||
@ -94,7 +94,7 @@ impl Gfx {
|
|||||||
back: vec![0; len],
|
back: vec![0; len],
|
||||||
layers,
|
layers,
|
||||||
hud: HudTileLayer::new(64, 32),
|
hud: HudTileLayer::new(64, 32),
|
||||||
banks: [EMPTY_BANK; 16],
|
tile_banks,
|
||||||
sprites: [EMPTY_SPRITE; 512],
|
sprites: [EMPTY_SPRITE; 512],
|
||||||
scene_fade_level: 31,
|
scene_fade_level: 31,
|
||||||
scene_fade_color: Color::BLACK,
|
scene_fade_color: Color::BLACK,
|
||||||
@ -328,25 +328,25 @@ impl Gfx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. Priority 0 sprites: drawn at the very back, behind everything else.
|
// 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, &self.banks);
|
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.
|
// 2. Main layers and prioritized sprites.
|
||||||
// Order: Layer 0 -> Sprites 1 -> Layer 1 -> Sprites 2 ...
|
// Order: Layer 0 -> Sprites 1 -> Layer 1 -> Sprites 2 ...
|
||||||
for i in 0..self.layers.len() {
|
for i in 0..self.layers.len() {
|
||||||
let bank_id = self.layers[i].bank_id as usize;
|
let bank_id = self.layers[i].bank_id as usize;
|
||||||
if let Some(Some(bank)) = self.banks.get(bank_id) {
|
if let Some(bank) = self.tile_banks.tile_bank_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);
|
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
|
// 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, &self.banks);
|
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).
|
// 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);
|
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.
|
// 5. HUD: The fixed interface layer, always drawn on top of the world.
|
||||||
self.render_hud();
|
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.
|
// 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);
|
Self::apply_fade_to_buffer(&mut self.back, self.hud_fade_level, self.hud_fade_color);
|
||||||
@ -360,23 +360,27 @@ impl Gfx {
|
|||||||
let scroll_x = self.layers[layer_idx].scroll_x;
|
let scroll_x = self.layers[layer_idx].scroll_x;
|
||||||
let scroll_y = self.layers[layer_idx].scroll_y;
|
let scroll_y = self.layers[layer_idx].scroll_y;
|
||||||
|
|
||||||
let bank = match self.banks.get(bank_id) {
|
let bank = match self.tile_banks.tile_bank_slot(bank_id) {
|
||||||
Some(Some(b)) => b,
|
Some(b) => b,
|
||||||
_ => return,
|
_ => 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).
|
/// Renders the HUD (fixed position, no scroll).
|
||||||
pub fn render_hud(&mut self) {
|
pub fn render_hud(&mut self) {
|
||||||
let bank_id = self.hud.bank_id as usize;
|
Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, &*self.tile_banks);
|
||||||
let bank = match self.banks.get(bank_id) {
|
}
|
||||||
Some(Some(b)) => b,
|
|
||||||
|
fn render_hud_with_pool(back: &mut [u16], w: usize, h: usize, hud: &HudTileLayer, tile_banks: &dyn TileBankPoolAccess) {
|
||||||
|
let bank_id = hud.bank_id as usize;
|
||||||
|
let bank = match tile_banks.tile_bank_slot(bank_id) {
|
||||||
|
Some(b) => b,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::draw_tile_map(&mut self.back, self.w, self.h, &self.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.
|
/// Rasterizes a TileMap into the provided pixel buffer using scrolling.
|
||||||
@ -464,13 +468,13 @@ impl Gfx {
|
|||||||
screen_h: usize,
|
screen_h: usize,
|
||||||
bucket: &[usize],
|
bucket: &[usize],
|
||||||
sprites: &[Sprite],
|
sprites: &[Sprite],
|
||||||
banks: &[Option<TileBank>],
|
tile_banks: &dyn TileBankPoolAccess,
|
||||||
) {
|
) {
|
||||||
for &idx in bucket {
|
for &idx in bucket {
|
||||||
let s = &sprites[idx];
|
let s = &sprites[idx];
|
||||||
let bank_id = s.bank_id as usize;
|
let bank_id = s.bank_id as usize;
|
||||||
if let Some(Some(bank)) = banks.get(bank_id) {
|
if let Some(bank) = tile_banks.tile_bank_slot(bank_id) {
|
||||||
Self::draw_sprite_pixel_by_pixel(back, screen_w, screen_h, s, bank);
|
Self::draw_sprite_pixel_by_pixel(back, screen_w, screen_h, s, &bank);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,45 +536,6 @@ impl Gfx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the actual memory usage of the graphics structures in bytes.
|
|
||||||
/// Reflects index buffers, palettes, and OAM as per Chapter 10.8.
|
|
||||||
pub fn memory_usage_bytes(&self) -> usize {
|
|
||||||
let mut total = 0;
|
|
||||||
|
|
||||||
// 1. Framebuffers (Front + Back)
|
|
||||||
// Each is Vec<u16>, occupying 2 bytes per pixel.
|
|
||||||
total += self.front.len() * 2;
|
|
||||||
total += self.back.len() * 2;
|
|
||||||
|
|
||||||
// 2. Tile Layers (4 Game Layers)
|
|
||||||
for layer in &self.layers {
|
|
||||||
// Struct size + map data (Vec<Tile>)
|
|
||||||
total += size_of::<ScrollableTileLayer>();
|
|
||||||
total += layer.map.tiles.len() * size_of::<crate::model::Tile>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. HUD Layer
|
|
||||||
total += size_of::<HudTileLayer>();
|
|
||||||
total += self.hud.map.tiles.len() * size_of::<crate::model::Tile>();
|
|
||||||
|
|
||||||
// 4. Tile Banks (Assets and Palettes)
|
|
||||||
for bank_opt in &self.banks {
|
|
||||||
if let Some(bank) = bank_opt {
|
|
||||||
total += size_of::<TileBank>();
|
|
||||||
total += bank.pixel_indices.len();
|
|
||||||
|
|
||||||
// Palette Table: 256 palettes * 16 colors * Color struct size
|
|
||||||
total += 256 * 16 * size_of::<Color>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Sprites (OAM)
|
|
||||||
// Fixed array of 512 Sprites.
|
|
||||||
total += self.sprites.len() * size_of::<Sprite>();
|
|
||||||
|
|
||||||
total
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color) {
|
pub fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color) {
|
||||||
let mut cx = x;
|
let mut cx = x;
|
||||||
for c in text.chars() {
|
for c in text.chars() {
|
||||||
@ -647,10 +612,12 @@ impl Gfx {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::hardware::MemoryBanks;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_draw_pixel() {
|
fn test_draw_pixel() {
|
||||||
let mut gfx = Gfx::new(10, 10);
|
let banks = Arc::new(MemoryBanks::new());
|
||||||
|
let mut gfx = Gfx::new(10, 10, banks);
|
||||||
gfx.draw_pixel(5, 5, Color::WHITE);
|
gfx.draw_pixel(5, 5, Color::WHITE);
|
||||||
assert_eq!(gfx.back[5 * 10 + 5], Color::WHITE.0);
|
assert_eq!(gfx.back[5 * 10 + 5], Color::WHITE.0);
|
||||||
|
|
||||||
@ -661,7 +628,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_draw_line() {
|
fn test_draw_line() {
|
||||||
let mut gfx = Gfx::new(10, 10);
|
let banks = Arc::new(MemoryBanks::new());
|
||||||
|
let mut gfx = Gfx::new(10, 10, banks);
|
||||||
gfx.draw_line(0, 0, 9, 9, Color::WHITE);
|
gfx.draw_line(0, 0, 9, 9, Color::WHITE);
|
||||||
assert_eq!(gfx.back[0], Color::WHITE.0);
|
assert_eq!(gfx.back[0], Color::WHITE.0);
|
||||||
assert_eq!(gfx.back[9 * 10 + 9], Color::WHITE.0);
|
assert_eq!(gfx.back[9 * 10 + 9], Color::WHITE.0);
|
||||||
@ -669,7 +637,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_draw_rect() {
|
fn test_draw_rect() {
|
||||||
let mut gfx = Gfx::new(10, 10);
|
let banks = Arc::new(MemoryBanks::new());
|
||||||
|
let mut gfx = Gfx::new(10, 10, banks);
|
||||||
gfx.draw_rect(0, 0, 10, 10, Color::WHITE);
|
gfx.draw_rect(0, 0, 10, 10, Color::WHITE);
|
||||||
assert_eq!(gfx.back[0], Color::WHITE.0);
|
assert_eq!(gfx.back[0], Color::WHITE.0);
|
||||||
assert_eq!(gfx.back[9], Color::WHITE.0);
|
assert_eq!(gfx.back[9], Color::WHITE.0);
|
||||||
@ -679,14 +648,16 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fill_circle() {
|
fn test_fill_circle() {
|
||||||
let mut gfx = Gfx::new(10, 10);
|
let banks = Arc::new(MemoryBanks::new());
|
||||||
|
let mut gfx = Gfx::new(10, 10, banks);
|
||||||
gfx.fill_circle(5, 5, 2, Color::WHITE);
|
gfx.fill_circle(5, 5, 2, Color::WHITE);
|
||||||
assert_eq!(gfx.back[5 * 10 + 5], Color::WHITE.0);
|
assert_eq!(gfx.back[5 * 10 + 5], Color::WHITE.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_draw_square() {
|
fn test_draw_square() {
|
||||||
let mut gfx = Gfx::new(10, 10);
|
let banks = Arc::new(MemoryBanks::new());
|
||||||
|
let mut gfx = Gfx::new(10, 10, banks);
|
||||||
gfx.draw_square(2, 2, 6, 6, Color::WHITE, Color::BLACK);
|
gfx.draw_square(2, 2, 6, 6, Color::WHITE, Color::BLACK);
|
||||||
// Border
|
// Border
|
||||||
assert_eq!(gfx.back[2 * 10 + 2], Color::WHITE.0);
|
assert_eq!(gfx.back[2 * 10 + 2], Color::WHITE.0);
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
use crate::hardware::{Audio, Gfx, HardwareBridge, Pad, Touch};
|
use crate::hardware::{AssetManager, Audio, Gfx, HardwareBridge, Pad, Touch, MemoryBanks};
|
||||||
|
use crate::hardware::memory_banks::{TileBankPoolAccess, TileBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Aggregate structure for all virtual hardware peripherals.
|
/// Aggregate structure for all virtual hardware peripherals.
|
||||||
///
|
///
|
||||||
/// This struct represents the "Mainboard" of the PROMETEU console,
|
/// This struct represents the "Mainboard" of the PROMETEU console,
|
||||||
/// containing instances of GFX, Audio, Input (Pad), and Touch.
|
/// containing instances of GFX, Audio, Input (Pad), and Touch.
|
||||||
pub struct Hardware {
|
pub struct Hardware {
|
||||||
|
/// Shared memory banks for hardware assets.
|
||||||
|
pub memory_banks: Arc<MemoryBanks>,
|
||||||
/// The Graphics Processing Unit.
|
/// The Graphics Processing Unit.
|
||||||
pub gfx: Gfx,
|
pub gfx: Gfx,
|
||||||
/// The Sound Processing Unit.
|
/// The Sound Processing Unit.
|
||||||
@ -13,6 +17,8 @@ pub struct Hardware {
|
|||||||
pub pad: Pad,
|
pub pad: Pad,
|
||||||
/// The absolute pointer input device.
|
/// The absolute pointer input device.
|
||||||
pub touch: Touch,
|
pub touch: Touch,
|
||||||
|
/// The Asset Management system.
|
||||||
|
pub assets: AssetManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HardwareBridge for Hardware {
|
impl HardwareBridge for Hardware {
|
||||||
@ -27,6 +33,9 @@ impl HardwareBridge for Hardware {
|
|||||||
|
|
||||||
fn touch(&self) -> &Touch { &self.touch }
|
fn touch(&self) -> &Touch { &self.touch }
|
||||||
fn touch_mut(&mut self) -> &mut Touch { &mut self.touch }
|
fn touch_mut(&mut self) -> &mut Touch { &mut self.touch }
|
||||||
|
|
||||||
|
fn assets(&self) -> &AssetManager { &self.assets }
|
||||||
|
fn assets_mut(&mut self) -> &mut AssetManager { &mut self.assets }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hardware {
|
impl Hardware {
|
||||||
@ -37,11 +46,19 @@ impl Hardware {
|
|||||||
|
|
||||||
/// Creates a fresh hardware instance with default settings.
|
/// Creates a fresh hardware instance with default settings.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
let memory_banks = Arc::new(MemoryBanks::new());
|
||||||
Self {
|
Self {
|
||||||
gfx: Gfx::new(Self::W, Self::H),
|
memory_banks: Arc::clone(&memory_banks),
|
||||||
audio: Audio::new(),
|
gfx: Gfx::new(Self::W, Self::H, Arc::clone(&memory_banks) as Arc<dyn TileBankPoolAccess>),
|
||||||
|
audio: Audio::new(Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolAccess>),
|
||||||
pad: Pad::default(),
|
pad: Pad::default(),
|
||||||
touch: Touch::default(),
|
touch: Touch::default(),
|
||||||
|
assets: AssetManager::new(
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
Arc::clone(&memory_banks) as Arc<dyn TileBankPoolInstaller>,
|
||||||
|
Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolInstaller>,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
90
crates/prometeu-core/src/hardware/memory_banks.rs
Normal file
90
crates/prometeu-core/src/hardware/memory_banks.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
use crate::model::{TileBank, SoundBank};
|
||||||
|
|
||||||
|
/// Non-generic interface for peripherals to access graphical tile banks.
|
||||||
|
pub trait TileBankPoolAccess: Send + Sync {
|
||||||
|
/// Returns a reference to the resident TileBank in the specified slot, if any.
|
||||||
|
fn tile_bank_slot(&self, slot: usize) -> Option<Arc<TileBank>>;
|
||||||
|
/// Returns the total number of slots available in this bank.
|
||||||
|
fn tile_bank_slot_count(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Non-generic interface for the AssetManager to install graphical tile banks.
|
||||||
|
pub trait TileBankPoolInstaller: Send + Sync {
|
||||||
|
/// Atomically swaps the resident TileBank in the specified slot.
|
||||||
|
fn install_tile_bank(&self, slot: usize, bank: Arc<TileBank>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Non-generic interface for peripherals to access sound banks.
|
||||||
|
pub trait SoundBankPoolAccess: Send + Sync {
|
||||||
|
/// Returns a reference to the resident SoundBank in the specified slot, if any.
|
||||||
|
fn sound_bank_slot(&self, slot: usize) -> Option<Arc<SoundBank>>;
|
||||||
|
/// Returns the total number of slots available in this bank.
|
||||||
|
fn sound_bank_slot_count(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Non-generic interface for the AssetManager to install sound banks.
|
||||||
|
pub trait SoundBankPoolInstaller: Send + Sync {
|
||||||
|
/// Atomically swaps the resident SoundBank in the specified slot.
|
||||||
|
fn install_sound_bank(&self, slot: usize, bank: Arc<SoundBank>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Centralized container for all hardware memory banks.
|
||||||
|
///
|
||||||
|
/// MemoryBanks represent 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 {
|
||||||
|
tile_bank_pool: Arc<RwLock<[Option<Arc<TileBank>>; 16]>>,
|
||||||
|
sound_bank_pool: Arc<RwLock<[Option<Arc<SoundBank>>; 16]>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryBanks {
|
||||||
|
/// Creates a new set of memory banks with empty slots.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
tile_bank_pool: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
|
||||||
|
sound_bank_pool: Arc::new(RwLock::new(std::array::from_fn(|_| None))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TileBankPoolAccess for MemoryBanks {
|
||||||
|
fn tile_bank_slot(&self, slot: usize) -> Option<Arc<TileBank>> {
|
||||||
|
let pool = self.tile_bank_pool.read().unwrap();
|
||||||
|
pool.get(slot).and_then(|s| s.as_ref().map(Arc::clone))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tile_bank_slot_count(&self) -> usize {
|
||||||
|
16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TileBankPoolInstaller for MemoryBanks {
|
||||||
|
fn install_tile_bank(&self, slot: usize, bank: Arc<TileBank>) {
|
||||||
|
let mut pool = self.tile_bank_pool.write().unwrap();
|
||||||
|
if slot < 16 {
|
||||||
|
pool[slot] = Some(bank);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SoundBankPoolAccess for MemoryBanks {
|
||||||
|
fn sound_bank_slot(&self, slot: usize) -> Option<Arc<SoundBank>> {
|
||||||
|
let pool = self.sound_bank_pool.read().unwrap();
|
||||||
|
pool.get(slot).and_then(|s| s.as_ref().map(Arc::clone))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sound_bank_slot_count(&self) -> usize {
|
||||||
|
16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SoundBankPoolInstaller for MemoryBanks {
|
||||||
|
fn install_sound_bank(&self, slot: usize, bank: Arc<SoundBank>) {
|
||||||
|
let mut pool = self.sound_bank_pool.write().unwrap();
|
||||||
|
if slot < 16 {
|
||||||
|
pool[slot] = Some(bank);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +1,21 @@
|
|||||||
|
mod asset;
|
||||||
mod gfx;
|
mod gfx;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod touch;
|
mod touch;
|
||||||
mod input_signal;
|
mod input_signal;
|
||||||
mod audio;
|
mod audio;
|
||||||
|
mod memory_banks;
|
||||||
pub mod hardware;
|
pub mod hardware;
|
||||||
|
|
||||||
pub use gfx::Gfx;
|
pub use crate::model::HandleId;
|
||||||
|
pub use asset::AssetManager;
|
||||||
|
pub use audio::{Audio, AudioCommand, Channel, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
|
||||||
pub use gfx::BlendMode;
|
pub use gfx::BlendMode;
|
||||||
|
pub use gfx::Gfx;
|
||||||
pub use input_signal::InputSignals;
|
pub use input_signal::InputSignals;
|
||||||
|
pub use memory_banks::MemoryBanks;
|
||||||
pub use pad::Pad;
|
pub use pad::Pad;
|
||||||
pub use touch::Touch;
|
pub use touch::Touch;
|
||||||
pub use audio::{Audio, AudioCommand, Channel, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
|
|
||||||
|
|
||||||
pub trait HardwareBridge {
|
pub trait HardwareBridge {
|
||||||
fn gfx(&self) -> &Gfx;
|
fn gfx(&self) -> &Gfx;
|
||||||
@ -24,4 +29,7 @@ pub trait HardwareBridge {
|
|||||||
|
|
||||||
fn touch(&self) -> &Touch;
|
fn touch(&self) -> &Touch;
|
||||||
fn touch_mut(&mut self) -> &mut Touch;
|
fn touch_mut(&mut self) -> &mut Touch;
|
||||||
|
|
||||||
|
fn assets(&self) -> &AssetManager;
|
||||||
|
fn assets_mut(&mut self) -> &mut AssetManager;
|
||||||
}
|
}
|
||||||
|
|||||||
80
crates/prometeu-core/src/model/asset.rs
Normal file
80
crates/prometeu-core/src/model/asset.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub type HandleId = u32;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub enum BankType {
|
||||||
|
TILES,
|
||||||
|
SOUNDS,
|
||||||
|
// TILEMAPS,
|
||||||
|
// BLOBS,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct AssetEntry {
|
||||||
|
pub asset_id: u32,
|
||||||
|
pub asset_name: String,
|
||||||
|
pub bank_type: BankType,
|
||||||
|
pub offset: u64,
|
||||||
|
pub size: u64,
|
||||||
|
pub decoded_size: u64,
|
||||||
|
pub codec: String, // e.g., "RAW"
|
||||||
|
pub metadata: serde_json::Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct PreloadEntry {
|
||||||
|
pub asset_name: String,
|
||||||
|
pub slot: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum LoadStatus {
|
||||||
|
PENDING,
|
||||||
|
LOADING,
|
||||||
|
READY,
|
||||||
|
COMMITTED,
|
||||||
|
CANCELED,
|
||||||
|
ERROR,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct BankStats {
|
||||||
|
pub total_bytes: usize,
|
||||||
|
pub used_bytes: usize,
|
||||||
|
pub free_bytes: usize,
|
||||||
|
pub inflight_bytes: usize,
|
||||||
|
pub slot_count: usize,
|
||||||
|
pub slots_occupied: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SlotStats {
|
||||||
|
pub asset_id: Option<u32>,
|
||||||
|
pub asset_name: Option<String>,
|
||||||
|
pub generation: u32,
|
||||||
|
pub resident_bytes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct SlotRef {
|
||||||
|
pub asset_type: BankType,
|
||||||
|
pub index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SlotRef {
|
||||||
|
pub fn gfx(index: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
asset_type: BankType::TILES,
|
||||||
|
index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn audio(index: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
asset_type: BankType::SOUNDS,
|
||||||
|
index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use std::path::PathBuf;
|
use crate::model::asset::{AssetEntry, PreloadEntry};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
@ -15,7 +15,9 @@ pub struct Cartridge {
|
|||||||
pub app_mode: AppMode,
|
pub app_mode: AppMode,
|
||||||
pub entrypoint: String,
|
pub entrypoint: String,
|
||||||
pub program: Vec<u8>,
|
pub program: Vec<u8>,
|
||||||
pub assets_path: Option<PathBuf>,
|
pub assets: Vec<u8>,
|
||||||
|
pub asset_table: Vec<AssetEntry>,
|
||||||
|
pub preload: Vec<PreloadEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
@ -26,7 +28,11 @@ pub struct CartridgeDTO {
|
|||||||
pub app_mode: AppMode,
|
pub app_mode: AppMode,
|
||||||
pub entrypoint: String,
|
pub entrypoint: String,
|
||||||
pub program: Vec<u8>,
|
pub program: Vec<u8>,
|
||||||
pub assets_path: Option<PathBuf>,
|
pub assets: Vec<u8>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub asset_table: Vec<AssetEntry>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub preload: Vec<PreloadEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CartridgeDTO> for Cartridge {
|
impl From<CartridgeDTO> for Cartridge {
|
||||||
@ -38,7 +44,9 @@ impl From<CartridgeDTO> for Cartridge {
|
|||||||
app_mode: dto.app_mode,
|
app_mode: dto.app_mode,
|
||||||
entrypoint: dto.entrypoint,
|
entrypoint: dto.entrypoint,
|
||||||
program: dto.program,
|
program: dto.program,
|
||||||
assets_path: dto.assets_path,
|
assets: dto.assets,
|
||||||
|
asset_table: dto.asset_table,
|
||||||
|
preload: dto.preload,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,4 +70,8 @@ pub struct CartridgeManifest {
|
|||||||
pub app_version: String,
|
pub app_version: String,
|
||||||
pub app_mode: AppMode,
|
pub app_mode: AppMode,
|
||||||
pub entrypoint: String,
|
pub entrypoint: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub asset_table: Vec<AssetEntry>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub preload: Vec<PreloadEntry>,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,11 +48,11 @@ impl DirectoryCartridgeLoader {
|
|||||||
|
|
||||||
let program = fs::read(program_path).map_err(|_| CartridgeError::IoError)?;
|
let program = fs::read(program_path).map_err(|_| CartridgeError::IoError)?;
|
||||||
|
|
||||||
let assets_path = path.join("assets");
|
let assets_pa_path = path.join("assets.pa");
|
||||||
let assets_path = if assets_path.exists() && assets_path.is_dir() {
|
let assets = if assets_pa_path.exists() {
|
||||||
Some(assets_path)
|
fs::read(assets_pa_path).map_err(|_| CartridgeError::IoError)?
|
||||||
} else {
|
} else {
|
||||||
None
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let dto = CartridgeDTO {
|
let dto = CartridgeDTO {
|
||||||
@ -62,7 +62,9 @@ impl DirectoryCartridgeLoader {
|
|||||||
app_mode: manifest.app_mode,
|
app_mode: manifest.app_mode,
|
||||||
entrypoint: manifest.entrypoint,
|
entrypoint: manifest.entrypoint,
|
||||||
program,
|
program,
|
||||||
assets_path,
|
assets,
|
||||||
|
asset_table: manifest.asset_table,
|
||||||
|
preload: manifest.preload,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Cartridge::from(dto))
|
Ok(Cartridge::from(dto))
|
||||||
|
|||||||
@ -1,19 +1,23 @@
|
|||||||
|
mod asset;
|
||||||
mod color;
|
mod color;
|
||||||
mod button;
|
mod button;
|
||||||
mod tile;
|
mod tile;
|
||||||
mod tile_layer;
|
mod tile_layer;
|
||||||
mod tile_bank;
|
mod tile_bank;
|
||||||
|
mod sound_bank;
|
||||||
mod sprite;
|
mod sprite;
|
||||||
mod sample;
|
mod sample;
|
||||||
mod cartridge;
|
mod cartridge;
|
||||||
mod cartridge_loader;
|
mod cartridge_loader;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
|
pub use asset::{AssetEntry, BankType, BankStats, LoadStatus, SlotRef, SlotStats, HandleId, PreloadEntry};
|
||||||
pub use button::{Button, ButtonId};
|
pub use button::{Button, ButtonId};
|
||||||
pub use cartridge::{AppMode, Cartridge, CartridgeDTO, CartridgeError};
|
pub use cartridge::{AppMode, Cartridge, CartridgeDTO, CartridgeError};
|
||||||
pub use cartridge_loader::{CartridgeLoader, DirectoryCartridgeLoader, PackedCartridgeLoader};
|
pub use cartridge_loader::{CartridgeLoader, DirectoryCartridgeLoader, PackedCartridgeLoader};
|
||||||
pub use color::Color;
|
pub use color::Color;
|
||||||
pub use sample::Sample;
|
pub use sample::Sample;
|
||||||
|
pub use sound_bank::SoundBank;
|
||||||
pub use sprite::Sprite;
|
pub use sprite::Sprite;
|
||||||
pub use tile::Tile;
|
pub use tile::Tile;
|
||||||
pub use tile_bank::{TileBank, TileSize};
|
pub use tile_bank::{TileBank, TileSize};
|
||||||
|
|||||||
16
crates/prometeu-core/src/model/sound_bank.rs
Normal file
16
crates/prometeu-core/src/model/sound_bank.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use crate::model::Sample;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// A container for audio assets.
|
||||||
|
///
|
||||||
|
/// A SoundBank stores multiple audio samples that can be played by the
|
||||||
|
/// audio subsystem.
|
||||||
|
pub struct SoundBank {
|
||||||
|
pub samples: Vec<Arc<Sample>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SoundBank {
|
||||||
|
pub fn new(samples: Vec<Arc<Sample>>) -> Self {
|
||||||
|
Self { samples }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,8 +27,8 @@ pub struct TileBank {
|
|||||||
/// Pixel data stored as 4-bit indices (packed into 8-bit values).
|
/// Pixel data stored as 4-bit indices (packed into 8-bit values).
|
||||||
/// Index 0 is always reserved for transparency.
|
/// Index 0 is always reserved for transparency.
|
||||||
pub pixel_indices: Vec<u8>,
|
pub pixel_indices: Vec<u8>,
|
||||||
/// Table of 256 palettes, each containing 16 RGB565 colors.
|
/// Table of 64 palettes, each containing 16 RGB565 colors, total of 1024 colors for a bank.
|
||||||
pub palettes: [[Color; 16]; 256],
|
pub palettes: [[Color; 16]; 64],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TileBank {
|
impl TileBank {
|
||||||
@ -39,7 +39,7 @@ impl TileBank {
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
pixel_indices: vec![0; width * height], // Index 0 = Transparent
|
pixel_indices: vec![0; width * height], // Index 0 = Transparent
|
||||||
palettes: [[Color::BLACK; 16]; 256],
|
palettes: [[Color::BLACK; 16]; 64],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,7 @@ impl TileLayer {
|
|||||||
fn create(width: usize, height: usize, tile_size: TileSize) -> Self {
|
fn create(width: usize, height: usize, tile_size: TileSize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bank_id: 0,
|
bank_id: 0,
|
||||||
tile_size: tile_size,
|
tile_size,
|
||||||
map: TileMap::create(width, height),
|
map: TileMap::create(width, height),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
use crate::fs::{FsBackend, FsState, VirtualFS};
|
use crate::fs::{FsBackend, FsState, VirtualFS};
|
||||||
use crate::hardware::{HardwareBridge, InputSignals};
|
use crate::hardware::{HardwareBridge, InputSignals};
|
||||||
use crate::log::{LogLevel, LogService, LogSource};
|
use crate::log::{LogLevel, LogService, LogSource};
|
||||||
use crate::model::{Cartridge, Color, Sample};
|
use crate::model::{BankType, Cartridge, Color};
|
||||||
use crate::prometeu_os::{NativeInterface, Syscall};
|
use crate::prometeu_os::{NativeInterface, Syscall};
|
||||||
use crate::telemetry::{CertificationConfig, Certifier, TelemetryFrame};
|
use crate::telemetry::{CertificationConfig, Certifier, TelemetryFrame};
|
||||||
use crate::virtual_machine::{Value, VirtualMachine};
|
use crate::virtual_machine::{Value, VirtualMachine};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
/// PrometeuOS (POS): The system firmware/base.
|
/// PrometeuOS (POS): The system firmware/base.
|
||||||
@ -25,11 +24,6 @@ pub struct PrometeuOS {
|
|||||||
/// Real-world CPU time (in microseconds) consumed by the last host tick.
|
/// Real-world CPU time (in microseconds) consumed by the last host tick.
|
||||||
pub last_frame_cpu_time_us: u64,
|
pub last_frame_cpu_time_us: u64,
|
||||||
|
|
||||||
// Example assets (kept for compatibility with v0 audio syscalls)
|
|
||||||
pub sample_square: Option<Arc<Sample>>,
|
|
||||||
pub sample_kick: Option<Arc<Sample>>,
|
|
||||||
pub sample_snare: Option<Arc<Sample>>,
|
|
||||||
|
|
||||||
// Filesystem
|
// Filesystem
|
||||||
/// The virtual filesystem interface.
|
/// The virtual filesystem interface.
|
||||||
pub fs: VirtualFS,
|
pub fs: VirtualFS,
|
||||||
@ -87,9 +81,6 @@ impl PrometeuOS {
|
|||||||
logical_frame_active: false,
|
logical_frame_active: false,
|
||||||
logical_frame_remaining_cycles: 0,
|
logical_frame_remaining_cycles: 0,
|
||||||
last_frame_cpu_time_us: 0,
|
last_frame_cpu_time_us: 0,
|
||||||
sample_square: None,
|
|
||||||
sample_kick: None,
|
|
||||||
sample_snare: None,
|
|
||||||
fs: VirtualFS::new(),
|
fs: VirtualFS::new(),
|
||||||
fs_state: FsState::Unmounted,
|
fs_state: FsState::Unmounted,
|
||||||
open_files: HashMap::new(),
|
open_files: HashMap::new(),
|
||||||
@ -108,9 +99,6 @@ impl PrometeuOS {
|
|||||||
boot_time,
|
boot_time,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initializes basic samples (same logic as LogicalHardware)
|
|
||||||
os.sample_square = Some(Arc::new(Self::create_square_sample(440.0, 0.1)));
|
|
||||||
|
|
||||||
os.log(LogLevel::Info, LogSource::Pos, 0, "PrometeuOS starting...".to_string());
|
os.log(LogLevel::Info, LogSource::Pos, 0, "PrometeuOS starting...".to_string());
|
||||||
|
|
||||||
os
|
os
|
||||||
@ -188,8 +176,8 @@ impl PrometeuOS {
|
|||||||
/// This method is responsible for managing the logical frame lifecycle.
|
/// This method is responsible for managing the logical frame lifecycle.
|
||||||
/// A single host tick might execute a full logical frame, part of it,
|
/// A single host tick might execute a full logical frame, part of it,
|
||||||
/// or multiple frames depending on the configured slices.
|
/// or multiple frames depending on the configured slices.
|
||||||
pub fn step_frame(&mut self, vm: &mut VirtualMachine, signals: &InputSignals, hw: &mut dyn HardwareBridge) -> Option<String> {
|
pub fn tick(&mut self, vm: &mut VirtualMachine, signals: &InputSignals, hw: &mut dyn HardwareBridge) -> Option<String> {
|
||||||
let start = std::time::Instant::now();
|
let start = Instant::now();
|
||||||
self.tick_index += 1;
|
self.tick_index += 1;
|
||||||
|
|
||||||
// If the system is paused, we don't advance unless there's a debug step request.
|
// If the system is paused, we don't advance unless there's a debug step request.
|
||||||
@ -214,13 +202,13 @@ impl PrometeuOS {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Budget Allocation
|
// 2. Budget Allocation
|
||||||
// Determine how many cycles we can run in this host tick.
|
// Determines how many cycles we can run in this host tick.
|
||||||
let budget = std::cmp::min(Self::SLICE_PER_TICK, self.logical_frame_remaining_cycles);
|
let budget = std::cmp::min(Self::SLICE_PER_TICK, self.logical_frame_remaining_cycles);
|
||||||
|
|
||||||
// 3. VM Execution
|
// 3. VM Execution
|
||||||
if budget > 0 {
|
if budget > 0 {
|
||||||
// Run the VM until budget is hit or FRAME_SYNC is reached.
|
// Run the VM until the budget is hit or FRAME_SYNC is reached.
|
||||||
let run_result = vm.run_budget(budget, self, hw);
|
let run_result = vm.run_budget(budget, self, hw); // internally dispatch to frame on SDK
|
||||||
|
|
||||||
match run_result {
|
match run_result {
|
||||||
Ok(run) => {
|
Ok(run) => {
|
||||||
@ -275,9 +263,26 @@ impl PrometeuOS {
|
|||||||
|
|
||||||
self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64;
|
self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64;
|
||||||
|
|
||||||
|
// Update bank telemetry in current frame (snapshot)
|
||||||
|
let gfx_stats = hw.assets().bank_info(BankType::TILES);
|
||||||
|
self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes;
|
||||||
|
self.telemetry_current.gfx_inflight_bytes = gfx_stats.inflight_bytes;
|
||||||
|
self.telemetry_current.gfx_slots_occupied = gfx_stats.slots_occupied as u32;
|
||||||
|
|
||||||
|
let audio_stats = hw.assets().bank_info(BankType::SOUNDS);
|
||||||
|
self.telemetry_current.audio_used_bytes = audio_stats.used_bytes;
|
||||||
|
self.telemetry_current.audio_inflight_bytes = audio_stats.inflight_bytes;
|
||||||
|
self.telemetry_current.audio_slots_occupied = audio_stats.slots_occupied as u32;
|
||||||
|
|
||||||
// If the frame ended exactly in this tick, we update the final real time in the latch.
|
// If the frame ended exactly in this tick, we update the final real time in the latch.
|
||||||
if !self.logical_frame_active && self.telemetry_last.frame_index == self.logical_frame_index.wrapping_sub(1) {
|
if !self.logical_frame_active && self.telemetry_last.frame_index == self.logical_frame_index.wrapping_sub(1) {
|
||||||
self.telemetry_last.host_cpu_time_us = self.last_frame_cpu_time_us;
|
self.telemetry_last.host_cpu_time_us = self.last_frame_cpu_time_us;
|
||||||
|
self.telemetry_last.gfx_used_bytes = self.telemetry_current.gfx_used_bytes;
|
||||||
|
self.telemetry_last.gfx_inflight_bytes = self.telemetry_current.gfx_inflight_bytes;
|
||||||
|
self.telemetry_last.gfx_slots_occupied = self.telemetry_current.gfx_slots_occupied;
|
||||||
|
self.telemetry_last.audio_used_bytes = self.telemetry_current.audio_used_bytes;
|
||||||
|
self.telemetry_last.audio_inflight_bytes = self.telemetry_current.audio_inflight_bytes;
|
||||||
|
self.telemetry_last.audio_slots_occupied = self.telemetry_current.audio_slots_occupied;
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
@ -367,24 +372,6 @@ impl PrometeuOS {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_square_sample(freq: f64, duration: f64) -> Sample {
|
|
||||||
let sample_rate = crate::hardware::OUTPUT_SAMPLE_RATE;
|
|
||||||
let num_samples = (duration * sample_rate as f64) as usize;
|
|
||||||
let mut data = Vec::with_capacity(num_samples);
|
|
||||||
let period = sample_rate as f64 / freq;
|
|
||||||
|
|
||||||
for i in 0..num_samples {
|
|
||||||
let val = if (i as f64 % period) < (period / 2.0) {
|
|
||||||
10000
|
|
||||||
} else {
|
|
||||||
-10000
|
|
||||||
};
|
|
||||||
data.push(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
Sample::new(sample_rate, data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -410,17 +397,19 @@ mod tests {
|
|||||||
app_mode: AppMode::Game,
|
app_mode: AppMode::Game,
|
||||||
entrypoint: "0".to_string(),
|
entrypoint: "0".to_string(),
|
||||||
program: rom,
|
program: rom,
|
||||||
assets_path: None,
|
assets: vec![],
|
||||||
|
asset_table: vec![],
|
||||||
|
preload: vec![],
|
||||||
};
|
};
|
||||||
os.initialize_vm(&mut vm, &cartridge);
|
os.initialize_vm(&mut vm, &cartridge);
|
||||||
|
|
||||||
// First tick
|
// First tick
|
||||||
os.step_frame(&mut vm, &signals, &mut hw);
|
os.tick(&mut vm, &signals, &mut hw);
|
||||||
let cycles_after_tick_1 = vm.cycles;
|
let cycles_after_tick_1 = vm.cycles;
|
||||||
assert!(cycles_after_tick_1 >= PrometeuOS::CYCLES_PER_LOGICAL_FRAME);
|
assert!(cycles_after_tick_1 >= PrometeuOS::CYCLES_PER_LOGICAL_FRAME);
|
||||||
|
|
||||||
// Second tick - Now it SHOULD NOT gain more budget
|
// Second tick - Now it SHOULD NOT gain more budget
|
||||||
os.step_frame(&mut vm, &signals, &mut hw);
|
os.tick(&mut vm, &signals, &mut hw);
|
||||||
let cycles_after_tick_2 = vm.cycles;
|
let cycles_after_tick_2 = vm.cycles;
|
||||||
|
|
||||||
// FIX: It should not have consumed cycles in the second tick because the logical frame budget ended
|
// FIX: It should not have consumed cycles in the second tick because the logical frame budget ended
|
||||||
@ -450,12 +439,14 @@ mod tests {
|
|||||||
app_mode: AppMode::Game,
|
app_mode: AppMode::Game,
|
||||||
entrypoint: "0".to_string(),
|
entrypoint: "0".to_string(),
|
||||||
program: rom,
|
program: rom,
|
||||||
assets_path: None,
|
assets: vec![],
|
||||||
|
asset_table: vec![],
|
||||||
|
preload: vec![],
|
||||||
};
|
};
|
||||||
os.initialize_vm(&mut vm, &cartridge);
|
os.initialize_vm(&mut vm, &cartridge);
|
||||||
|
|
||||||
// First tick
|
// First tick
|
||||||
os.step_frame(&mut vm, &signals, &mut hw);
|
os.tick(&mut vm, &signals, &mut hw);
|
||||||
let cycles_after_tick_1 = vm.cycles;
|
let cycles_after_tick_1 = vm.cycles;
|
||||||
|
|
||||||
// Should have stopped at FrameSync
|
// Should have stopped at FrameSync
|
||||||
@ -463,7 +454,7 @@ mod tests {
|
|||||||
assert!(cycles_after_tick_1 < PrometeuOS::CYCLES_PER_LOGICAL_FRAME);
|
assert!(cycles_after_tick_1 < PrometeuOS::CYCLES_PER_LOGICAL_FRAME);
|
||||||
|
|
||||||
// Second tick - Should reset the budget and run a bit more until the next FrameSync
|
// Second tick - Should reset the budget and run a bit more until the next FrameSync
|
||||||
os.step_frame(&mut vm, &signals, &mut hw);
|
os.tick(&mut vm, &signals, &mut hw);
|
||||||
let cycles_after_tick_2 = vm.cycles;
|
let cycles_after_tick_2 = vm.cycles;
|
||||||
|
|
||||||
assert!(cycles_after_tick_2 > cycles_after_tick_1, "VM should have consumed more cycles because FrameSync reset the budget");
|
assert!(cycles_after_tick_2 > cycles_after_tick_1, "VM should have consumed more cycles because FrameSync reset the budget");
|
||||||
@ -480,6 +471,54 @@ mod tests {
|
|||||||
assert_eq!(os.get_color(3), Color::from_raw(3));
|
assert_eq!(os.get_color(3), Color::from_raw(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gfx_set_sprite_syscall_pops() {
|
||||||
|
let mut os = PrometeuOS::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hw = Hardware::new();
|
||||||
|
|
||||||
|
// Push arguments in order 1 to 10
|
||||||
|
vm.push(Value::String("mouse_cursor".to_string())); // arg1: assetName
|
||||||
|
vm.push(Value::Int32(0)); // arg2: id
|
||||||
|
|
||||||
|
// Simulating touch.x and touch.y syscalls
|
||||||
|
vm.push(Value::Int32(10)); // arg3: x (returned from syscall)
|
||||||
|
vm.push(Value::Int32(20)); // arg4: y (returned from syscall)
|
||||||
|
|
||||||
|
vm.push(Value::Int32(0)); // arg5: tileId
|
||||||
|
vm.push(Value::Int32(0)); // arg6: paletteId
|
||||||
|
vm.push(Value::Boolean(true)); // arg7: active
|
||||||
|
vm.push(Value::Boolean(false)); // arg8: flipX
|
||||||
|
vm.push(Value::Boolean(false)); // arg9: flipY
|
||||||
|
vm.push(Value::Int32(4)); // arg10: priority
|
||||||
|
|
||||||
|
let res = os.syscall(0x1007, &mut vm, &mut hw);
|
||||||
|
assert!(res.is_ok(), "GfxSetSprite syscall should succeed, but got: {:?}", res.err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gfx_set_sprite_with_swapped_arguments_repro() {
|
||||||
|
let mut os = PrometeuOS::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hw = Hardware::new();
|
||||||
|
|
||||||
|
// Repro: what if the compiler is pushing in reverse order?
|
||||||
|
vm.push(Value::Int32(4)); // arg10?
|
||||||
|
vm.push(Value::Boolean(false));
|
||||||
|
vm.push(Value::Boolean(false));
|
||||||
|
vm.push(Value::Boolean(true));
|
||||||
|
vm.push(Value::Int32(0));
|
||||||
|
vm.push(Value::Int32(0));
|
||||||
|
vm.push(Value::Int32(20));
|
||||||
|
vm.push(Value::Int32(10));
|
||||||
|
vm.push(Value::Int32(0));
|
||||||
|
vm.push(Value::String("mouse_cursor".to_string())); // arg1?
|
||||||
|
|
||||||
|
let res = os.syscall(0x1007, &mut vm, &mut hw);
|
||||||
|
assert!(res.is_err());
|
||||||
|
assert_eq!(res.err().unwrap(), "Expected integer"); // Because it tries to pop priority but gets a string
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_syscall_log_write_and_rate_limit() {
|
fn test_syscall_log_write_and_rate_limit() {
|
||||||
let mut os = PrometeuOS::new(None);
|
let mut os = PrometeuOS::new(None);
|
||||||
@ -659,6 +698,40 @@ impl NativeInterface for PrometeuOS {
|
|||||||
vm.push(Value::Null);
|
vm.push(Value::Null);
|
||||||
Ok(200)
|
Ok(200)
|
||||||
}
|
}
|
||||||
|
// gfx.set_sprite(asset_name, id, x, y, tile_id, palette_id, active, flip_x, flip_y, priority)
|
||||||
|
Syscall::GfxSetSprite => {
|
||||||
|
let priority = vm.pop_integer()? as u8;
|
||||||
|
let flip_y = vm.pop_integer()? != 0;
|
||||||
|
let flip_x = vm.pop_integer()? != 0;
|
||||||
|
let active = vm.pop_integer()? != 0;
|
||||||
|
let palette_id = vm.pop_integer()? as u8;
|
||||||
|
let tile_id = vm.pop_integer()? as u16;
|
||||||
|
let y = vm.pop_integer()? as i32;
|
||||||
|
let x = vm.pop_integer()? as i32;
|
||||||
|
let index = vm.pop_integer()? as usize;
|
||||||
|
let val = vm.pop()?;
|
||||||
|
let asset_name = match val {
|
||||||
|
Value::String(ref s) => s.clone(),
|
||||||
|
_ => return Err(format!("Expected string asset_name in GfxSetSprite, but got {:?}", val).into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let bank_id = hw.assets().find_slot_by_name(&asset_name, crate::model::BankType::TILES).unwrap_or(0);
|
||||||
|
|
||||||
|
if index < 512 {
|
||||||
|
hw.gfx_mut().sprites[index] = crate::model::Sprite {
|
||||||
|
tile: crate::model::Tile { id: tile_id, flip_x: false, flip_y: false, palette_id },
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
bank_id,
|
||||||
|
active,
|
||||||
|
flip_x,
|
||||||
|
flip_y,
|
||||||
|
priority,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
vm.push(Value::Null);
|
||||||
|
Ok(100)
|
||||||
|
}
|
||||||
|
|
||||||
// --- Input Syscalls ---
|
// --- Input Syscalls ---
|
||||||
|
|
||||||
@ -723,16 +796,31 @@ impl NativeInterface for PrometeuOS {
|
|||||||
let voice_id = vm.pop_integer()? as usize;
|
let voice_id = vm.pop_integer()? as usize;
|
||||||
let sample_id = vm.pop_integer()? as u32;
|
let sample_id = vm.pop_integer()? as u32;
|
||||||
|
|
||||||
let sample = match sample_id {
|
hw.audio_mut().play(0, sample_id as u16, voice_id, volume, pan, pitch, 0, crate::hardware::LoopMode::Off);
|
||||||
0 => self.sample_square.clone(),
|
vm.push(Value::Null);
|
||||||
1 => self.sample_kick.clone(),
|
Ok(300)
|
||||||
2 => self.sample_snare.clone(),
|
}
|
||||||
_ => None,
|
|
||||||
|
// audio.play(asset_name, sample_id, voice_id, volume, pan, pitch, loop_mode)
|
||||||
|
Syscall::AudioPlay => {
|
||||||
|
let loop_mode = match vm.pop_integer()? {
|
||||||
|
0 => crate::hardware::LoopMode::Off,
|
||||||
|
_ => crate::hardware::LoopMode::On,
|
||||||
|
};
|
||||||
|
let pitch = vm.pop_number()?;
|
||||||
|
let pan = vm.pop_integer()? as u8;
|
||||||
|
let volume = vm.pop_integer()? as u8;
|
||||||
|
let voice_id = vm.pop_integer()? as usize;
|
||||||
|
let sample_id = vm.pop_integer()? as u16;
|
||||||
|
let val = vm.pop()?;
|
||||||
|
let asset_name = match val {
|
||||||
|
Value::String(ref s) => s.clone(),
|
||||||
|
_ => return Err(format!("Expected string asset_name in AudioPlay, but got {:?}", val).into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(s) = sample {
|
let bank_id = hw.assets().find_slot_by_name(&asset_name, crate::model::BankType::SOUNDS).unwrap_or(0);
|
||||||
hw.audio_mut().play(s, voice_id, volume, pan, pitch, 0, crate::hardware::LoopMode::Off);
|
|
||||||
}
|
hw.audio_mut().play(bank_id, sample_id, voice_id, volume, pan, pitch, 0, loop_mode);
|
||||||
vm.push(Value::Null);
|
vm.push(Value::Null);
|
||||||
Ok(300)
|
Ok(300)
|
||||||
}
|
}
|
||||||
@ -860,6 +948,83 @@ impl NativeInterface for PrometeuOS {
|
|||||||
let level = vm.pop_integer()?;
|
let level = vm.pop_integer()?;
|
||||||
self.syscall_log_write(vm, level, tag, msg)
|
self.syscall_log_write(vm, level, tag, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Asset Syscalls ---
|
||||||
|
Syscall::AssetLoad => {
|
||||||
|
let asset_id = match vm.pop()? {
|
||||||
|
Value::String(s) => s,
|
||||||
|
_ => return Err("Expected string asset_id".into()),
|
||||||
|
};
|
||||||
|
let asset_type_val = vm.pop_integer()? as u32;
|
||||||
|
let slot_index = vm.pop_integer()? as usize;
|
||||||
|
|
||||||
|
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 };
|
||||||
|
|
||||||
|
match hw.assets().load(&asset_id, slot) {
|
||||||
|
Ok(handle) => {
|
||||||
|
vm.push(Value::Int64(handle as i64));
|
||||||
|
Ok(1000)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Syscall::AssetStatus => {
|
||||||
|
let handle = vm.pop_integer()? as u32;
|
||||||
|
let status = hw.assets().status(handle);
|
||||||
|
let status_val = match status {
|
||||||
|
crate::model::LoadStatus::PENDING => 0,
|
||||||
|
crate::model::LoadStatus::LOADING => 1,
|
||||||
|
crate::model::LoadStatus::READY => 2,
|
||||||
|
crate::model::LoadStatus::COMMITTED => 3,
|
||||||
|
crate::model::LoadStatus::CANCELED => 4,
|
||||||
|
crate::model::LoadStatus::ERROR => 5,
|
||||||
|
};
|
||||||
|
vm.push(Value::Int64(status_val));
|
||||||
|
Ok(100)
|
||||||
|
}
|
||||||
|
Syscall::AssetCommit => {
|
||||||
|
let handle = vm.pop_integer()? as u32;
|
||||||
|
hw.assets().commit(handle);
|
||||||
|
vm.push(Value::Null);
|
||||||
|
Ok(100)
|
||||||
|
}
|
||||||
|
Syscall::AssetCancel => {
|
||||||
|
let handle = vm.pop_integer()? as u32;
|
||||||
|
hw.assets().cancel(handle);
|
||||||
|
vm.push(Value::Null);
|
||||||
|
Ok(100)
|
||||||
|
}
|
||||||
|
Syscall::BankInfo => {
|
||||||
|
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);
|
||||||
|
let json = serde_json::to_string(&info).unwrap_or_default();
|
||||||
|
vm.push(Value::String(json));
|
||||||
|
Ok(500)
|
||||||
|
}
|
||||||
|
Syscall::BankSlotInfo => {
|
||||||
|
let slot_index = vm.pop_integer()? as usize;
|
||||||
|
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 };
|
||||||
|
let info = hw.assets().slot_info(slot);
|
||||||
|
let json = serde_json::to_string(&info).unwrap_or_default();
|
||||||
|
vm.push(Value::String(json));
|
||||||
|
Ok(500)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -12,6 +12,7 @@ pub enum Syscall {
|
|||||||
GfxDrawCircle = 0x1004,
|
GfxDrawCircle = 0x1004,
|
||||||
GfxDrawDisc = 0x1005,
|
GfxDrawDisc = 0x1005,
|
||||||
GfxDrawSquare = 0x1006,
|
GfxDrawSquare = 0x1006,
|
||||||
|
GfxSetSprite = 0x1007,
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
InputGetPad = 0x2001,
|
InputGetPad = 0x2001,
|
||||||
@ -28,6 +29,7 @@ pub enum Syscall {
|
|||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
AudioPlaySample = 0x3001,
|
AudioPlaySample = 0x3001,
|
||||||
|
AudioPlay = 0x3002,
|
||||||
|
|
||||||
// FS
|
// FS
|
||||||
FsOpen = 0x4001,
|
FsOpen = 0x4001,
|
||||||
@ -41,6 +43,16 @@ pub enum Syscall {
|
|||||||
// Log
|
// Log
|
||||||
LogWrite = 0x5001,
|
LogWrite = 0x5001,
|
||||||
LogWriteTag = 0x5002,
|
LogWriteTag = 0x5002,
|
||||||
|
|
||||||
|
// Asset
|
||||||
|
AssetLoad = 0x6001,
|
||||||
|
AssetStatus = 0x6002,
|
||||||
|
AssetCommit = 0x6003,
|
||||||
|
AssetCancel = 0x6004,
|
||||||
|
|
||||||
|
// Bank
|
||||||
|
BankInfo = 0x6101,
|
||||||
|
BankSlotInfo = 0x6102,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Syscall {
|
impl Syscall {
|
||||||
@ -54,6 +66,7 @@ impl Syscall {
|
|||||||
0x1004 => Some(Self::GfxDrawCircle),
|
0x1004 => Some(Self::GfxDrawCircle),
|
||||||
0x1005 => Some(Self::GfxDrawDisc),
|
0x1005 => Some(Self::GfxDrawDisc),
|
||||||
0x1006 => Some(Self::GfxDrawSquare),
|
0x1006 => Some(Self::GfxDrawSquare),
|
||||||
|
0x1007 => Some(Self::GfxSetSprite),
|
||||||
0x2001 => Some(Self::InputGetPad),
|
0x2001 => Some(Self::InputGetPad),
|
||||||
0x2002 => Some(Self::InputGetPadPressed),
|
0x2002 => Some(Self::InputGetPadPressed),
|
||||||
0x2003 => Some(Self::InputGetPadReleased),
|
0x2003 => Some(Self::InputGetPadReleased),
|
||||||
@ -65,6 +78,7 @@ impl Syscall {
|
|||||||
0x2105 => Some(Self::TouchIsReleased),
|
0x2105 => Some(Self::TouchIsReleased),
|
||||||
0x2106 => Some(Self::TouchGetHold),
|
0x2106 => Some(Self::TouchGetHold),
|
||||||
0x3001 => Some(Self::AudioPlaySample),
|
0x3001 => Some(Self::AudioPlaySample),
|
||||||
|
0x3002 => Some(Self::AudioPlay),
|
||||||
0x4001 => Some(Self::FsOpen),
|
0x4001 => Some(Self::FsOpen),
|
||||||
0x4002 => Some(Self::FsRead),
|
0x4002 => Some(Self::FsRead),
|
||||||
0x4003 => Some(Self::FsWrite),
|
0x4003 => Some(Self::FsWrite),
|
||||||
@ -74,6 +88,12 @@ impl Syscall {
|
|||||||
0x4007 => Some(Self::FsDelete),
|
0x4007 => Some(Self::FsDelete),
|
||||||
0x5001 => Some(Self::LogWrite),
|
0x5001 => Some(Self::LogWrite),
|
||||||
0x5002 => Some(Self::LogWriteTag),
|
0x5002 => Some(Self::LogWriteTag),
|
||||||
|
0x6001 => Some(Self::AssetLoad),
|
||||||
|
0x6002 => Some(Self::AssetStatus),
|
||||||
|
0x6003 => Some(Self::AssetCommit),
|
||||||
|
0x6004 => Some(Self::AssetCancel),
|
||||||
|
0x6101 => Some(Self::BankInfo),
|
||||||
|
0x6102 => Some(Self::BankSlotInfo),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,6 +140,7 @@ impl Syscall {
|
|||||||
"gfx.drawCircle" | "gfx.draw_circle" => Some(Self::GfxDrawCircle),
|
"gfx.drawCircle" | "gfx.draw_circle" => Some(Self::GfxDrawCircle),
|
||||||
"gfx.drawDisc" | "gfx.draw_disc" => Some(Self::GfxDrawDisc),
|
"gfx.drawDisc" | "gfx.draw_disc" => Some(Self::GfxDrawDisc),
|
||||||
"gfx.drawSquare" | "gfx.draw_square" => Some(Self::GfxDrawSquare),
|
"gfx.drawSquare" | "gfx.draw_square" => Some(Self::GfxDrawSquare),
|
||||||
|
"gfx.setSprite" | "gfx.set_sprite" => Some(Self::GfxSetSprite),
|
||||||
"input.getPad" => Some(Self::InputGetPad),
|
"input.getPad" => Some(Self::InputGetPad),
|
||||||
"input.getPadPressed" | "input.get_pad_pressed" => Some(Self::InputGetPadPressed),
|
"input.getPadPressed" | "input.get_pad_pressed" => Some(Self::InputGetPadPressed),
|
||||||
"input.getPadReleased" | "input.get_pad_released" => Some(Self::InputGetPadReleased),
|
"input.getPadReleased" | "input.get_pad_released" => Some(Self::InputGetPadReleased),
|
||||||
@ -131,6 +152,7 @@ impl Syscall {
|
|||||||
"touch.isReleased" | "touch.is_released" => Some(Self::TouchIsReleased),
|
"touch.isReleased" | "touch.is_released" => Some(Self::TouchIsReleased),
|
||||||
"touch.getHold" | "touch.get_hold" => Some(Self::TouchGetHold),
|
"touch.getHold" | "touch.get_hold" => Some(Self::TouchGetHold),
|
||||||
"audio.playSample" | "audio.play_sample" => Some(Self::AudioPlaySample),
|
"audio.playSample" | "audio.play_sample" => Some(Self::AudioPlaySample),
|
||||||
|
"audio.play" => Some(Self::AudioPlay),
|
||||||
"fs.open" => Some(Self::FsOpen),
|
"fs.open" => Some(Self::FsOpen),
|
||||||
"fs.read" => Some(Self::FsRead),
|
"fs.read" => Some(Self::FsRead),
|
||||||
"fs.write" => Some(Self::FsWrite),
|
"fs.write" => Some(Self::FsWrite),
|
||||||
@ -140,6 +162,12 @@ impl Syscall {
|
|||||||
"fs.delete" => Some(Self::FsDelete),
|
"fs.delete" => Some(Self::FsDelete),
|
||||||
"log.write" => Some(Self::LogWrite),
|
"log.write" => Some(Self::LogWrite),
|
||||||
"log.writeTag" | "log.write_tag" => Some(Self::LogWriteTag),
|
"log.writeTag" | "log.write_tag" => Some(Self::LogWriteTag),
|
||||||
|
"asset.load" => Some(Self::AssetLoad),
|
||||||
|
"asset.status" => Some(Self::AssetStatus),
|
||||||
|
"asset.commit" => Some(Self::AssetCommit),
|
||||||
|
"asset.cancel" => Some(Self::AssetCancel),
|
||||||
|
"bank.info" => Some(Self::BankInfo),
|
||||||
|
"bank.slotInfo" | "bank.slot_info" => Some(Self::BankSlotInfo),
|
||||||
_ => {
|
_ => {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,16 @@ pub struct TelemetryFrame {
|
|||||||
pub syscalls: u32,
|
pub syscalls: u32,
|
||||||
pub host_cpu_time_us: u64,
|
pub host_cpu_time_us: u64,
|
||||||
pub violations: u32,
|
pub violations: u32,
|
||||||
|
|
||||||
|
// GFX Banks
|
||||||
|
pub gfx_used_bytes: usize,
|
||||||
|
pub gfx_inflight_bytes: usize,
|
||||||
|
pub gfx_slots_occupied: u32,
|
||||||
|
|
||||||
|
// Audio Banks
|
||||||
|
pub audio_used_bytes: usize,
|
||||||
|
pub audio_inflight_bytes: usize,
|
||||||
|
pub audio_slots_occupied: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
|||||||
@ -666,6 +666,9 @@ impl VirtualMachine {
|
|||||||
|
|
||||||
pub fn pop_integer(&mut self) -> Result<i64, String> {
|
pub fn pop_integer(&mut self) -> Result<i64, String> {
|
||||||
let val = self.pop()?;
|
let val = self.pop()?;
|
||||||
|
if let Value::Boolean(b) = val {
|
||||||
|
return Ok(if b { 1 } else { 0 });
|
||||||
|
}
|
||||||
val.as_integer().ok_or_else(|| "Expected integer".into())
|
val.as_integer().ok_or_else(|| "Expected integer".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -688,9 +691,9 @@ impl VirtualMachine {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::virtual_machine::Value;
|
|
||||||
use crate::prometeu_os::NativeInterface;
|
|
||||||
use crate::hardware::HardwareBridge;
|
use crate::hardware::HardwareBridge;
|
||||||
|
use crate::prometeu_os::NativeInterface;
|
||||||
|
use crate::virtual_machine::Value;
|
||||||
|
|
||||||
struct MockNative;
|
struct MockNative;
|
||||||
impl NativeInterface for MockNative {
|
impl NativeInterface for MockNative {
|
||||||
@ -709,6 +712,8 @@ mod tests {
|
|||||||
fn pad_mut(&mut self) -> &mut crate::hardware::Pad { todo!() }
|
fn pad_mut(&mut self) -> &mut crate::hardware::Pad { todo!() }
|
||||||
fn touch(&self) -> &crate::hardware::Touch { todo!() }
|
fn touch(&self) -> &crate::hardware::Touch { todo!() }
|
||||||
fn touch_mut(&mut self) -> &mut crate::hardware::Touch { todo!() }
|
fn touch_mut(&mut self) -> &mut crate::hardware::Touch { todo!() }
|
||||||
|
fn assets(&self) -> &crate::hardware::AssetManager { todo!() }
|
||||||
|
fn assets_mut(&mut self) -> &mut crate::hardware::AssetManager { todo!() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use prometeu_core::hardware::{AudioCommand, Channel, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
|
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
|
use prometeu_core::hardware::{AudioCommand, Channel, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
|
||||||
use ringbuf::traits::{Consumer, Producer, Split};
|
use ringbuf::traits::{Consumer, Producer, Split};
|
||||||
use ringbuf::HeapRb;
|
use ringbuf::HeapRb;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -69,7 +69,9 @@ impl HostAudio {
|
|||||||
pub fn send_commands(&mut self, commands: &mut Vec<AudioCommand>) {
|
pub fn send_commands(&mut self, commands: &mut Vec<AudioCommand>) {
|
||||||
if let Some(producer) = &mut self.producer {
|
if let Some(producer) = &mut self.producer {
|
||||||
for cmd in commands.drain(..) {
|
for cmd in commands.drain(..) {
|
||||||
let _ = producer.try_push(cmd);
|
if let Err(_) = producer.try_push(cmd) {
|
||||||
|
eprintln!("[HostAudio] Command ringbuffer full, dropping command.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,6 +88,7 @@ impl HostAudio {
|
|||||||
pub struct AudioMixer {
|
pub struct AudioMixer {
|
||||||
voices: [Channel; MAX_CHANNELS],
|
voices: [Channel; MAX_CHANNELS],
|
||||||
pub last_processing_time: Duration,
|
pub last_processing_time: Duration,
|
||||||
|
paused: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioMixer {
|
impl AudioMixer {
|
||||||
@ -93,6 +96,7 @@ impl AudioMixer {
|
|||||||
Self {
|
Self {
|
||||||
voices: Default::default(),
|
voices: Default::default(),
|
||||||
last_processing_time: Duration::ZERO,
|
last_processing_time: Duration::ZERO,
|
||||||
|
paused: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,8 +112,10 @@ impl AudioMixer {
|
|||||||
loop_mode,
|
loop_mode,
|
||||||
} => {
|
} => {
|
||||||
if voice_id < MAX_CHANNELS {
|
if voice_id < MAX_CHANNELS {
|
||||||
|
println!("[AudioMixer] Playing voice {}: vol={}, pitch={}, loop={:?}", voice_id, volume, pitch, loop_mode);
|
||||||
self.voices[voice_id] = Channel {
|
self.voices[voice_id] = Channel {
|
||||||
sample: Some(sample),
|
sample: Some(sample),
|
||||||
|
active: true,
|
||||||
pos: 0.0,
|
pos: 0.0,
|
||||||
pitch,
|
pitch,
|
||||||
volume,
|
volume,
|
||||||
@ -121,6 +127,7 @@ impl AudioMixer {
|
|||||||
}
|
}
|
||||||
AudioCommand::Stop { voice_id } => {
|
AudioCommand::Stop { voice_id } => {
|
||||||
if voice_id < MAX_CHANNELS {
|
if voice_id < MAX_CHANNELS {
|
||||||
|
self.voices[voice_id].active = false;
|
||||||
self.voices[voice_id].sample = None;
|
self.voices[voice_id].sample = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,6 +146,14 @@ impl AudioMixer {
|
|||||||
self.voices[voice_id].pitch = pitch;
|
self.voices[voice_id].pitch = pitch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AudioCommand::MasterPause => {
|
||||||
|
println!("[AudioMixer] Master Pause");
|
||||||
|
self.paused = true;
|
||||||
|
}
|
||||||
|
AudioCommand::MasterResume => {
|
||||||
|
println!("[AudioMixer] Master Resume");
|
||||||
|
self.paused = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +164,11 @@ impl AudioMixer {
|
|||||||
*sample = 0.0;
|
*sample = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.paused {
|
||||||
|
self.last_processing_time = start.elapsed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for voice in self.voices.iter_mut() {
|
for voice in self.voices.iter_mut() {
|
||||||
let sample_data = match &voice.sample {
|
let sample_data = match &voice.sample {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
@ -168,6 +188,7 @@ impl AudioMixer {
|
|||||||
let pos_fract = voice.pos - pos_int as f64;
|
let pos_fract = voice.pos - pos_int as f64;
|
||||||
|
|
||||||
if pos_int >= sample_data.data.len() {
|
if pos_int >= sample_data.data.len() {
|
||||||
|
voice.active = false;
|
||||||
voice.sample = None;
|
voice.sample = None;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -197,6 +218,7 @@ impl AudioMixer {
|
|||||||
let loop_start = sample_data.loop_start.unwrap_or(0) as f64;
|
let loop_start = sample_data.loop_start.unwrap_or(0) as f64;
|
||||||
voice.pos = loop_start + (voice.pos - end_pos);
|
voice.pos = loop_start + (voice.pos - end_pos);
|
||||||
} else {
|
} else {
|
||||||
|
voice.active = false;
|
||||||
voice.sample = None;
|
voice.sample = None;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -230,17 +230,32 @@ impl HostDebugger {
|
|||||||
|
|
||||||
// Map Certification tags (0xCA01-0xCA03) to 'Cert' protocol events.
|
// Map Certification tags (0xCA01-0xCA03) to 'Cert' protocol events.
|
||||||
if event.tag >= 0xCA01 && event.tag <= 0xCA03 {
|
if event.tag >= 0xCA01 && event.tag <= 0xCA03 {
|
||||||
let rule = match event.tag {
|
let tel = &firmware.os.telemetry_last;
|
||||||
0xCA01 => "cycles_budget",
|
let cert_config = &firmware.os.certifier.config;
|
||||||
0xCA02 => "max_syscalls",
|
|
||||||
0xCA03 => "max_host_cpu_us",
|
let (rule, used, limit) = match event.tag {
|
||||||
_ => "unknown"
|
0xCA01 => (
|
||||||
}.to_string();
|
"cycles_budget".to_string(),
|
||||||
|
tel.cycles_used,
|
||||||
|
cert_config.cycles_budget_per_frame.unwrap_or(0),
|
||||||
|
),
|
||||||
|
0xCA02 => (
|
||||||
|
"max_syscalls".to_string(),
|
||||||
|
tel.syscalls as u64,
|
||||||
|
cert_config.max_syscalls_per_frame.unwrap_or(0) as u64,
|
||||||
|
),
|
||||||
|
0xCA03 => (
|
||||||
|
"max_host_cpu_us".to_string(),
|
||||||
|
tel.host_cpu_time_us,
|
||||||
|
cert_config.max_host_cpu_us_per_frame.unwrap_or(0),
|
||||||
|
),
|
||||||
|
_ => ("unknown".to_string(), 0, 0),
|
||||||
|
};
|
||||||
|
|
||||||
self.send_event(DebugEvent::Cert {
|
self.send_event(DebugEvent::Cert {
|
||||||
rule,
|
rule,
|
||||||
used: 0,
|
used,
|
||||||
limit: 0,
|
limit,
|
||||||
frame_index: firmware.os.logical_frame_index,
|
frame_index: firmware.os.logical_frame_index,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -261,6 +276,14 @@ impl HostDebugger {
|
|||||||
vm_steps: tel.vm_steps,
|
vm_steps: tel.vm_steps,
|
||||||
syscalls: tel.syscalls,
|
syscalls: tel.syscalls,
|
||||||
cycles: tel.cycles_used,
|
cycles: tel.cycles_used,
|
||||||
|
host_cpu_time_us: tel.host_cpu_time_us,
|
||||||
|
violations: tel.violations,
|
||||||
|
gfx_used_bytes: tel.gfx_used_bytes,
|
||||||
|
gfx_inflight_bytes: tel.gfx_inflight_bytes,
|
||||||
|
gfx_slots_occupied: tel.gfx_slots_occupied,
|
||||||
|
audio_used_bytes: tel.audio_used_bytes,
|
||||||
|
audio_inflight_bytes: tel.audio_inflight_bytes,
|
||||||
|
audio_slots_occupied: tel.audio_slots_occupied,
|
||||||
});
|
});
|
||||||
self.last_telemetry_frame = current_frame;
|
self.last_telemetry_frame = current_frame;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,6 +64,8 @@ pub struct HostRunner {
|
|||||||
|
|
||||||
/// The physical audio driver.
|
/// The physical audio driver.
|
||||||
audio: HostAudio,
|
audio: HostAudio,
|
||||||
|
/// Last known pause state to sync with audio.
|
||||||
|
last_paused_state: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostRunner {
|
impl HostRunner {
|
||||||
@ -99,6 +101,7 @@ impl HostRunner {
|
|||||||
debugger: HostDebugger::new(),
|
debugger: HostDebugger::new(),
|
||||||
overlay_enabled: false,
|
overlay_enabled: false,
|
||||||
audio: HostAudio::new(),
|
audio: HostAudio::new(),
|
||||||
|
last_paused_state: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,21 +127,31 @@ impl HostRunner {
|
|||||||
let color_bg = prometeu_core::model::Color::INDIGO; // Dark blue to stand out
|
let color_bg = prometeu_core::model::Color::INDIGO; // Dark blue to stand out
|
||||||
let color_warn = prometeu_core::model::Color::RED;
|
let color_warn = prometeu_core::model::Color::RED;
|
||||||
|
|
||||||
self.hardware.gfx.fill_rect(5, 5, 140, 65, color_bg);
|
self.hardware.gfx.fill_rect(5, 5, 140, 100, color_bg);
|
||||||
self.hardware.gfx.draw_text(10, 10, &format!("FPS: {:.1}", self.stats.current_fps), color_text);
|
self.hardware.gfx.draw_text(10, 10, &format!("FPS: {:.1}", self.stats.current_fps), color_text);
|
||||||
self.hardware.gfx.draw_text(10, 18, &format!("HOST: {:.2}MS", tel.host_cpu_time_us as f64 / 1000.0), color_text);
|
self.hardware.gfx.draw_text(10, 18, &format!("HOST: {:.2}MS", tel.host_cpu_time_us as f64 / 1000.0), color_text);
|
||||||
self.hardware.gfx.draw_text(10, 26, &format!("STEPS: {}", tel.vm_steps), color_text);
|
self.hardware.gfx.draw_text(10, 26, &format!("STEPS: {}", tel.vm_steps), color_text);
|
||||||
self.hardware.gfx.draw_text(10, 34, &format!("SYSC: {}", tel.syscalls), color_text);
|
self.hardware.gfx.draw_text(10, 34, &format!("SYSC: {}", tel.syscalls), color_text);
|
||||||
self.hardware.gfx.draw_text(10, 42, &format!("CYC: {}", tel.cycles_used), color_text);
|
self.hardware.gfx.draw_text(10, 42, &format!("CYC: {}", tel.cycles_used), color_text);
|
||||||
|
|
||||||
|
self.hardware.gfx.draw_text(10, 50, &format!("GFX: {}K/16M ({}S)", tel.gfx_used_bytes / 1024, tel.gfx_slots_occupied), color_text);
|
||||||
|
if tel.gfx_inflight_bytes > 0 {
|
||||||
|
self.hardware.gfx.draw_text(10, 58, &format!("LOAD GFX: {}KB", tel.gfx_inflight_bytes / 1024), color_warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.hardware.gfx.draw_text(10, 66, &format!("AUD: {}K/32M ({}S)", tel.audio_used_bytes / 1024, tel.audio_slots_occupied), color_text);
|
||||||
|
if tel.audio_inflight_bytes > 0 {
|
||||||
|
self.hardware.gfx.draw_text(10, 74, &format!("LOAD AUD: {}KB", tel.audio_inflight_bytes / 1024), color_warn);
|
||||||
|
}
|
||||||
|
|
||||||
let cert_color = if tel.violations > 0 { color_warn } else { color_text };
|
let cert_color = if tel.violations > 0 { color_warn } else { color_text };
|
||||||
self.hardware.gfx.draw_text(10, 50, &format!("CERT LAST: {}", tel.violations), cert_color);
|
self.hardware.gfx.draw_text(10, 82, &format!("CERT LAST: {}", tel.violations), cert_color);
|
||||||
|
|
||||||
if tel.violations > 0 {
|
if tel.violations > 0 {
|
||||||
if let Some(event) = self.firmware.os.log_service.get_recent(10).into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA03) {
|
if let Some(event) = self.firmware.os.log_service.get_recent(10).into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA03) {
|
||||||
let mut msg = event.msg.clone();
|
let mut msg = event.msg.clone();
|
||||||
if msg.len() > 30 { msg.truncate(30); }
|
if msg.len() > 30 { msg.truncate(30); }
|
||||||
self.hardware.gfx.draw_text(10, 58, &msg, color_warn);
|
self.hardware.gfx.draw_text(10, 90, &msg, color_warn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,7 +279,21 @@ impl ApplicationHandler for HostRunner {
|
|||||||
while self.accumulator >= self.frame_target_dt {
|
while self.accumulator >= self.frame_target_dt {
|
||||||
// Unless the debugger is waiting for a 'start' command, advance the system.
|
// Unless the debugger is waiting for a 'start' command, advance the system.
|
||||||
if !self.debugger.waiting_for_start {
|
if !self.debugger.waiting_for_start {
|
||||||
self.firmware.step_frame(&self.input.signals, &mut self.hardware);
|
self.firmware.tick(&self.input.signals, &mut self.hardware);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync pause state with audio.
|
||||||
|
// We do this AFTER firmware.tick to avoid MasterPause/Resume commands
|
||||||
|
// being cleared by the OS if a new logical frame starts in this tick.
|
||||||
|
let is_paused = self.firmware.os.paused || self.debugger.waiting_for_start;
|
||||||
|
if is_paused != self.last_paused_state {
|
||||||
|
self.last_paused_state = is_paused;
|
||||||
|
let cmd = if is_paused {
|
||||||
|
prometeu_core::hardware::AudioCommand::MasterPause
|
||||||
|
} else {
|
||||||
|
prometeu_core::hardware::AudioCommand::MasterResume
|
||||||
|
};
|
||||||
|
self.hardware.audio.commands.push(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync virtual audio commands to the physical mixer.
|
// Sync virtual audio commands to the physical mixer.
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
use prometeu_core::firmware::Firmware;
|
||||||
|
use prometeu_core::Hardware;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
use prometeu_core::Hardware;
|
|
||||||
use prometeu_core::firmware::Firmware;
|
|
||||||
|
|
||||||
pub struct HostStats {
|
pub struct HostStats {
|
||||||
pub last_stats_update: Instant,
|
pub last_stats_update: Instant,
|
||||||
@ -31,14 +31,12 @@ impl HostStats {
|
|||||||
self.audio_load_samples += 1;
|
self.audio_load_samples += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, now: Instant, window: Option<&Window>, hardware: &Hardware, firmware: &Firmware) {
|
pub fn update(&mut self, now: Instant, window: Option<&Window>, _hardware: &Hardware, firmware: &Firmware) {
|
||||||
let stats_elapsed = now.duration_since(self.last_stats_update);
|
let stats_elapsed = now.duration_since(self.last_stats_update);
|
||||||
if stats_elapsed >= Duration::from_secs(1) {
|
if stats_elapsed >= Duration::from_secs(1) {
|
||||||
self.current_fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64();
|
self.current_fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64();
|
||||||
|
|
||||||
if let Some(window) = window {
|
if let Some(window) = window {
|
||||||
let kb = hardware.gfx.memory_usage_bytes() as f64 / 1024.0;
|
|
||||||
|
|
||||||
// Fixed comparison always against 60Hz, keep even when doing CPU stress tests
|
// Fixed comparison always against 60Hz, keep even when doing CPU stress tests
|
||||||
let frame_budget_us = 16666.0;
|
let frame_budget_us = 16666.0;
|
||||||
let cpu_load_core = (firmware.os.last_frame_cpu_time_us as f64 / frame_budget_us) * 100.0;
|
let cpu_load_core = (firmware.os.last_frame_cpu_time_us as f64 / frame_budget_us) * 100.0;
|
||||||
@ -51,7 +49,7 @@ impl HostStats {
|
|||||||
|
|
||||||
let title = format!(
|
let title = format!(
|
||||||
"PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Load: {:.1}% (C) + {:.1}% (A) | Frame: tick {} logical {}",
|
"PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Load: {:.1}% (C) + {:.1}% (A) | Frame: tick {} logical {}",
|
||||||
kb, self.current_fps, cpu_load_core, cpu_load_audio, firmware.os.tick_index, firmware.os.logical_frame_index
|
0, self.current_fps, cpu_load_core, cpu_load_audio, firmware.os.tick_index, firmware.os.logical_frame_index
|
||||||
);
|
);
|
||||||
window.set_title(&title);
|
window.set_title(&title);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,7 +51,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"event": "telemetry",
|
"event": "telemetry",
|
||||||
"fields": ["frame_index", "vm_steps", "syscalls", "cycles"]
|
"fields": [
|
||||||
|
"frame_index", "vm_steps", "syscalls", "cycles", "host_cpu_time_us", "violations",
|
||||||
|
"gfx_used_bytes", "gfx_inflight_bytes", "gfx_slots_occupied",
|
||||||
|
"audio_used_bytes", "audio_inflight_bytes", "audio_slots_occupied"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"event": "cert",
|
"event": "cert",
|
||||||
|
|||||||
6
devtools/typescript-sdk/types/index.d.ts
vendored
6
devtools/typescript-sdk/types/index.d.ts
vendored
@ -33,12 +33,15 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Gfx {
|
interface Gfx {
|
||||||
|
clear(color: Color565): void;
|
||||||
|
|
||||||
fillRect(x: number, y: number, w: number, h: number, color: Color565): void;
|
fillRect(x: number, y: number, w: number, h: number, color: Color565): void;
|
||||||
drawLine(x1: number, y1: number, x2: number, y2: number, color: Color565): void;
|
drawLine(x1: number, y1: number, x2: number, y2: number, color: Color565): void;
|
||||||
drawCircle(x: number, y: number, r: number, color: Color565): void;
|
drawCircle(x: number, y: number, r: number, color: Color565): void;
|
||||||
drawDisc(x: number, y: number, r: number, borderColor: Color565, fillColor: Color565): void;
|
drawDisc(x: number, y: number, r: number, borderColor: Color565, fillColor: Color565): void;
|
||||||
drawSquare(x: number, y: number, w: number, h: number, borderColor: Color565, fillColor: Color565): void;
|
drawSquare(x: number, y: number, w: number, h: number, borderColor: Color565, fillColor: Color565): void;
|
||||||
clear(color: Color565): void;
|
|
||||||
|
setSprite(assetName: string, id: number, x: number, y: number, tileId: number, paletteId: number, active: boolean, flipX: boolean, flipY: boolean, priority: number): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Color {
|
interface Color {
|
||||||
@ -64,6 +67,7 @@ declare global {
|
|||||||
|
|
||||||
interface Audio {
|
interface Audio {
|
||||||
playSample(sampleId: number, voiceId: number, volume: number, pan: number, pitch: number): void;
|
playSample(sampleId: number, voiceId: number, volume: number, pan: number, pitch: number): void;
|
||||||
|
play(assetName: string, sampleId: number, voiceId: number, volume: number, pan: number, pitch: number, loopMode: number): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Fs {
|
interface Fs {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
< [Back](chapter-13.md) | [Summary](table-of-contents.md) >
|
< [Back](chapter-13.md) | [Summary](table-of-contents.md) | [Next](chapter-15.md) >
|
||||||
|
|
||||||
# Boot Profiles
|
# Boot Profiles
|
||||||
|
|
||||||
@ -132,4 +132,4 @@ When `debug == true`:
|
|||||||
* New boot modes must be compatible extensions
|
* New boot modes must be compatible extensions
|
||||||
|
|
||||||
|
|
||||||
< [Back](chapter-13.md) | [Summary](table-of-contents.md) >
|
< [Back](chapter-13.md) | [Summary](table-of-contents.md) | [Next](chapter-15.md) >
|
||||||
|
|||||||
331
docs/specs/topics/chapter-15.md
Normal file
331
docs/specs/topics/chapter-15.md
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
< [Back](chapter-14.md) | [Summary](table-of-contents.md) >
|
||||||
|
|
||||||
|
# Asset Management
|
||||||
|
|
||||||
|
## Bank-Centric Hardware Asset Model
|
||||||
|
|
||||||
|
**Scope:** Runtime / Hardware Asset Management (SDK-agnostic)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Fundamental Principles
|
||||||
|
|
||||||
|
1. **Every asset in Prometeu resides in a Bank**
|
||||||
|
2. **A Bank is a hardware memory management system**
|
||||||
|
3. **Assets are cold binaries stored in the cartridge**
|
||||||
|
4. **Asset memory belongs to the console, not to the VM**
|
||||||
|
5. **Loading, residency, and eviction are explicit**
|
||||||
|
6. **Hardware does not interpret gameplay semantics**
|
||||||
|
7. **The SDK orchestrates policies; hardware executes contracts**
|
||||||
|
|
||||||
|
> In Prometeu, you do not load “data”. You load **residency**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Asset Origin (Cold Storage)
|
||||||
|
|
||||||
|
* All assets initially reside in **cold storage** inside the cartridge.
|
||||||
|
* Typically, each asset corresponds to a binary file.
|
||||||
|
* The runtime **never scans the cartridge directly**.
|
||||||
|
* All access goes through the **Asset Table**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Asset Table
|
||||||
|
|
||||||
|
The Asset Table is an index loaded at cartridge boot.
|
||||||
|
|
||||||
|
It describes **content**, not residency.
|
||||||
|
|
||||||
|
### Location
|
||||||
|
|
||||||
|
* The Asset Table **must be embedded as JSON inside `manifest.json`**.
|
||||||
|
* Tooling may compile this JSON into a binary table for runtime use, but the source of truth is the manifest.
|
||||||
|
|
||||||
|
### Required Fields (conceptual)
|
||||||
|
|
||||||
|
* `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)
|
||||||
|
* `size` (cold size)
|
||||||
|
* `decoded_size` (resident size)
|
||||||
|
* `codec` (RAW, LZ4, ZSTD, ...)
|
||||||
|
* `asset_metadata` (type-specific)
|
||||||
|
|
||||||
|
> `bank_kind` defines **where** an asset may reside in hardware.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Bank — Definition
|
||||||
|
|
||||||
|
> **A Bank is the residency and swapping mechanism of the Prometeu hardware.**
|
||||||
|
|
||||||
|
A Bank:
|
||||||
|
|
||||||
|
* owns **numbered slots**
|
||||||
|
* enforces **resident memory budgets**
|
||||||
|
* enforces **staging / inflight budgets**
|
||||||
|
* accepts only compatible assets
|
||||||
|
* supports **atomic swap via commit**
|
||||||
|
* exposes memory and residency metrics
|
||||||
|
|
||||||
|
Banks are hardware infrastructure, not assets.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. BankKind
|
||||||
|
|
||||||
|
Each Bank belongs to a **BankKind**, defining its pipeline and constraints.
|
||||||
|
|
||||||
|
### Example BankKinds
|
||||||
|
|
||||||
|
* `GFX_TILEBANK`
|
||||||
|
* `AUDIO_SOUNDBANK`
|
||||||
|
* `DATA_BLOBBANK`
|
||||||
|
* `MAP_TILEMAPBANK`
|
||||||
|
|
||||||
|
Each BankKind defines:
|
||||||
|
|
||||||
|
* slot count
|
||||||
|
* maximum resident memory
|
||||||
|
* inflight / staging memory budget
|
||||||
|
* decode and validation pipeline
|
||||||
|
* hardware consumer subsystem (renderer, audio mixer, etc.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Slots and Slot References
|
||||||
|
|
||||||
|
* Each BankKind owns a fixed set of **slots**.
|
||||||
|
* A slot:
|
||||||
|
|
||||||
|
* references a resident asset (or is empty)
|
||||||
|
* never stores data directly
|
||||||
|
* may expose a **generation counter** (debug)
|
||||||
|
|
||||||
|
### Slot Reference
|
||||||
|
|
||||||
|
Slots are always referenced with explicit BankKind context:
|
||||||
|
|
||||||
|
* `gfxSlot(3)`
|
||||||
|
* `audioSlot(1)`
|
||||||
|
* `blobSlot(7)`
|
||||||
|
|
||||||
|
This prevents cross-bank ambiguity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Bank Memory Model
|
||||||
|
|
||||||
|
* Bank memory is **console-owned memory**.
|
||||||
|
* It does not belong to the VM heap.
|
||||||
|
* It does not participate in GC.
|
||||||
|
* It can be fully released when the cartridge shuts down.
|
||||||
|
|
||||||
|
Each Bank manages:
|
||||||
|
|
||||||
|
* total memory
|
||||||
|
* used memory
|
||||||
|
* free memory
|
||||||
|
* inflight (staging) memory
|
||||||
|
|
||||||
|
Conceptually, each Bank is a **specialized allocator**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Unified Loader
|
||||||
|
|
||||||
|
### Conceptual API
|
||||||
|
|
||||||
|
```text
|
||||||
|
handle = asset.load(asset_name, slotRef, flags)
|
||||||
|
```
|
||||||
|
|
||||||
|
Load flow:
|
||||||
|
|
||||||
|
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
|
||||||
|
5. Perform IO + decode on worker
|
||||||
|
6. Produce materialized asset in staging
|
||||||
|
|
||||||
|
### Handle States
|
||||||
|
|
||||||
|
* `PENDING` — enqueued
|
||||||
|
* `LOADING` — IO/decode in progress
|
||||||
|
* `READY` — staging completed
|
||||||
|
* `COMMITTED` — installed into slot
|
||||||
|
* `CANCELED`
|
||||||
|
* `ERROR`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Commit
|
||||||
|
|
||||||
|
```text
|
||||||
|
asset.commit(handle)
|
||||||
|
```
|
||||||
|
|
||||||
|
* Commit is **explicit** and **atomic**
|
||||||
|
* Executed at a safe frame boundary
|
||||||
|
* Performs **pointer swap** in the target slot
|
||||||
|
* Previous asset is released if no longer referenced
|
||||||
|
|
||||||
|
The hardware **never swaps slots automatically**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Asset Deduplication
|
||||||
|
|
||||||
|
* A decoded asset exists **at most once per BankKind**.
|
||||||
|
* Multiple slots may reference the same resident asset.
|
||||||
|
* Redundant loads become **install-only** operations.
|
||||||
|
|
||||||
|
Memory duplication is forbidden by contract.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Bank Specializations
|
||||||
|
|
||||||
|
### 11.1 GFX_TILEBANK
|
||||||
|
|
||||||
|
* AssetType: TILEBANK
|
||||||
|
* Immutable graphical tile + palette structure
|
||||||
|
* Consumed by the graphics subsystem
|
||||||
|
|
||||||
|
### 11.2 AUDIO_SOUNDBANK
|
||||||
|
|
||||||
|
* AssetType: SOUNDBANK
|
||||||
|
* Resident audio samples or streams
|
||||||
|
* Consumed by the audio mixer
|
||||||
|
|
||||||
|
### 11.3 DATA_BLOBBANK
|
||||||
|
|
||||||
|
* AssetType: BLOB
|
||||||
|
* Read-only byte buffers
|
||||||
|
* Hardware does not interpret contents
|
||||||
|
|
||||||
|
Used by the SDK via:
|
||||||
|
|
||||||
|
* **Views** (zero-copy, read-only)
|
||||||
|
* **Decode** (materialization into VM heap)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Views and Decode (SDK Contract)
|
||||||
|
|
||||||
|
* **View**
|
||||||
|
|
||||||
|
* read-only
|
||||||
|
* zero-copy
|
||||||
|
* valid only while the blob remains resident
|
||||||
|
|
||||||
|
* **Decode**
|
||||||
|
|
||||||
|
* allocates VM-owned entities
|
||||||
|
* independent from the Bank after creation
|
||||||
|
|
||||||
|
Hardware is unaware of Views and Decode semantics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Manifest and Initial Load
|
||||||
|
|
||||||
|
* The cartridge may include a `manifest.json`.
|
||||||
|
* The manifest may declare:
|
||||||
|
|
||||||
|
* assets to preload
|
||||||
|
* target slot references
|
||||||
|
|
||||||
|
These loads occur **before the first frame**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Shutdown and Eviction
|
||||||
|
|
||||||
|
* On cartridge shutdown:
|
||||||
|
|
||||||
|
* all Banks are cleared
|
||||||
|
* all resident memory is released
|
||||||
|
|
||||||
|
No implicit persistence exists.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Debugger and Telemetry
|
||||||
|
|
||||||
|
Each Bank must expose:
|
||||||
|
|
||||||
|
* total memory
|
||||||
|
* used memory
|
||||||
|
* free memory
|
||||||
|
* inflight memory
|
||||||
|
* occupied slots
|
||||||
|
* `asset_id` per slot
|
||||||
|
* `asset_name` per slot
|
||||||
|
* generation per slot
|
||||||
|
|
||||||
|
This enables debuggers to visualize:
|
||||||
|
|
||||||
|
* memory stacks per Bank
|
||||||
|
* memory pressure
|
||||||
|
* streaming strategies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. Minimal Syscall API (Derived)
|
||||||
|
|
||||||
|
The following syscalls form the minimal hardware contract for asset management:
|
||||||
|
|
||||||
|
```text
|
||||||
|
asset.load(asset_name, slotRef, flags) -> handle
|
||||||
|
asset.status(handle) -> LoadStatus
|
||||||
|
asset.commit(handle)
|
||||||
|
asset.cancel(handle)
|
||||||
|
|
||||||
|
bank.info(bank_kind) -> BankStats
|
||||||
|
bank.slot_info(slotRef) -> SlotStats
|
||||||
|
```
|
||||||
|
|
||||||
|
Where:
|
||||||
|
|
||||||
|
* `LoadStatus` ∈ { PENDING, LOADING, READY, COMMITTED, CANCELED, ERROR }
|
||||||
|
* `BankStats` exposes memory usage and limits
|
||||||
|
* `SlotStats` exposes current asset_id, asset_name and generation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. Separation of Responsibilities
|
||||||
|
|
||||||
|
### Hardware (Prometeu)
|
||||||
|
|
||||||
|
* manages memory
|
||||||
|
* loads bytes
|
||||||
|
* decodes assets
|
||||||
|
* swaps pointers
|
||||||
|
* reports usage
|
||||||
|
|
||||||
|
### SDK
|
||||||
|
|
||||||
|
* defines packs, scenes, and policies
|
||||||
|
* interprets blobs
|
||||||
|
* creates VM entities
|
||||||
|
|
||||||
|
### VM
|
||||||
|
|
||||||
|
* executes logic
|
||||||
|
* manages its own heap
|
||||||
|
* never owns hardware assets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18. Golden Rule
|
||||||
|
|
||||||
|
> **Banks are the foundation of the hardware.**
|
||||||
|
> **Asset Types describe content.**
|
||||||
|
> **The SDK orchestrates; the hardware executes.**
|
||||||
|
|
||||||
|
< [Back](chapter-14.md) | [Summary](table-of-contents.md) >
|
||||||
@ -14,6 +14,7 @@
|
|||||||
- [Chapter 12: Firmware — PrometeuOS (POS) + PrometeuHub](chapter-12.md)
|
- [Chapter 12: Firmware — PrometeuOS (POS) + PrometeuHub](chapter-12.md)
|
||||||
- [Chapter 13: Cartridge](chapter-13.md)
|
- [Chapter 13: Cartridge](chapter-13.md)
|
||||||
- [Chapter 14: Boot Profiles](chapter-14.md)
|
- [Chapter 14: Boot Profiles](chapter-14.md)
|
||||||
|
- [Chapter 15: Asset Management](chapter-15.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
[Back to README](../README.md)
|
[Back to README](../README.md)
|
||||||
80
symbols.json
80
symbols.json
@ -1,80 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"pc": 20,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 1,
|
|
||||||
"col": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 22,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 2,
|
|
||||||
"col": 13
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 28,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 2,
|
|
||||||
"col": 17
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 34,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 2,
|
|
||||||
"col": 13
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 46,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 5,
|
|
||||||
"col": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 48,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 6,
|
|
||||||
"col": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 54,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 6,
|
|
||||||
"col": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 60,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 6,
|
|
||||||
"col": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 70,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 6,
|
|
||||||
"col": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 72,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 7,
|
|
||||||
"col": 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 78,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 7,
|
|
||||||
"col": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 84,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 7,
|
|
||||||
"col": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pc": 90,
|
|
||||||
"file": "temp_test_2.ts",
|
|
||||||
"line": 7,
|
|
||||||
"col": 5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
00000000 Call U32(46) U32(0)
|
|
||||||
0000000A Pop
|
|
||||||
0000000C FrameSync
|
|
||||||
0000000E Jmp U32(0)
|
|
||||||
00000014 PushScope ; temp_test_2.ts:1
|
|
||||||
00000016 GetLocal U32(0) ; temp_test_2.ts:2
|
|
||||||
0000001C GetLocal U32(1) ; temp_test_2.ts:2
|
|
||||||
00000022 Add ; temp_test_2.ts:2
|
|
||||||
00000024 PopScope
|
|
||||||
00000026 PushConst U32(0)
|
|
||||||
0000002C Ret
|
|
||||||
0000002E PushScope ; temp_test_2.ts:5
|
|
||||||
00000030 PushI32 U32(10) ; temp_test_2.ts:6
|
|
||||||
00000036 PushI32 U32(20) ; temp_test_2.ts:6
|
|
||||||
0000003C Call U32(20) U32(2) ; temp_test_2.ts:6
|
|
||||||
00000046 Pop ; temp_test_2.ts:6
|
|
||||||
00000048 PushI32 U32(1) ; temp_test_2.ts:7
|
|
||||||
0000004E PushConst U32(1) ; temp_test_2.ts:7
|
|
||||||
00000054 Syscall U32(20481) ; temp_test_2.ts:7
|
|
||||||
0000005A Pop ; temp_test_2.ts:7
|
|
||||||
0000005C PopScope
|
|
||||||
0000005E PushConst U32(0)
|
|
||||||
00000064 Ret
|
|
||||||
@ -3,156 +3,170 @@
|
|||||||
0000000C FrameSync
|
0000000C FrameSync
|
||||||
0000000E Jmp U32(0)
|
0000000E Jmp U32(0)
|
||||||
00000014 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:5
|
00000014 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:5
|
||||||
00000016 Call U32(428) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:6
|
00000016 Call U32(92) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:6
|
||||||
00000020 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:6
|
00000020 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:6
|
||||||
00000022 Call U32(236) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:7
|
00000022 Call U32(564) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:7
|
||||||
0000002C Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:7
|
0000002C Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:7
|
||||||
0000002E Call U32(362) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:8
|
0000002E Call U32(702) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:8
|
||||||
00000038 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:8
|
00000038 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:8
|
||||||
0000003A Call U32(92) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:9
|
0000003A Call U32(420) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:9
|
||||||
00000044 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:9
|
00000044 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:9
|
||||||
00000046 Call U32(644) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:10
|
00000046 Call U32(308) U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:10
|
||||||
00000050 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:10
|
00000050 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/main.ts:10
|
||||||
00000052 PopScope
|
00000052 PopScope
|
||||||
00000054 PushConst U32(0)
|
00000054 PushConst U32(0)
|
||||||
0000005A Ret
|
0000005A Ret
|
||||||
0000005C PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:1
|
0000005C PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:1
|
||||||
0000005E PushConst U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:2
|
0000005E PushI32 U32(18448) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
|
||||||
00000064 Syscall U32(16385) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:2
|
00000064 Syscall U32(4097) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
|
||||||
0000006A GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
0000006A Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
|
||||||
00000070 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
0000006C PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
||||||
00000076 Gte ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
00000072 PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
||||||
00000078 JmpIfFalse U32(226) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
00000078 PushI32 U32(50) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
||||||
0000007E PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
0000007E PushI32 U32(50) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
||||||
00000080 GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
|
00000084 PushI32 U32(63488) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
||||||
00000086 PushConst U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
|
0000008A Syscall U32(4098) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
||||||
0000008C Syscall U32(16387) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
|
00000090 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
||||||
00000092 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
|
00000092 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
||||||
00000094 GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:5
|
00000098 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
||||||
0000009A Syscall U32(16386) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:5
|
0000009E PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
||||||
000000A0 GetLocal U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
000000A4 PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
||||||
000000A6 JmpIfFalse U32(204) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
000000AA PushI32 U32(65535) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
||||||
000000AC PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
000000B0 Syscall U32(4099) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
||||||
000000B2 PushI32 U32(101) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
000000B6 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
||||||
000000B8 GetLocal U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
000000B8 PushI32 U32(64) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
||||||
000000BE Syscall U32(20482) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
000000BE PushI32 U32(64) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
||||||
000000C4 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
000000C4 PushI32 U32(20) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
||||||
000000C6 Jmp U32(204)
|
000000CA PushI32 U32(31) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
||||||
000000CC GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
|
000000D0 Syscall U32(4100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
||||||
000000D2 Syscall U32(16388) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
|
000000D6 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
||||||
000000D8 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
|
000000D8 PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
||||||
000000DA PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
000000DE PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
||||||
000000DC Jmp U32(226)
|
000000E4 PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
||||||
000000E2 PopScope
|
000000EA PushI32 U32(2016) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
||||||
000000E4 PushConst U32(0)
|
000000F0 PushI32 U32(65504) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
||||||
000000EA Ret
|
000000F6 Syscall U32(4101) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
||||||
000000EC PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:1
|
000000FC Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
||||||
000000EE PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
|
000000FE PushI32 U32(20) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
||||||
000000F4 Syscall U32(8193) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
|
00000104 PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
||||||
000000FA JmpIfFalse U32(286) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
|
0000010A PushI32 U32(30) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
||||||
00000100 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
|
00000110 PushI32 U32(30) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
||||||
00000102 PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
|
00000116 PushI32 U32(2047) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
||||||
00000108 PushConst U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
|
0000011C PushI32 U32(63519) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
||||||
0000010E Syscall U32(20481) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
|
00000122 Syscall U32(4102) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
||||||
00000114 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
|
00000128 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
||||||
00000116 PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
|
0000012A PopScope
|
||||||
00000118 Jmp U32(286)
|
0000012C PushConst U32(0)
|
||||||
0000011E PushI32 U32(4) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
|
00000132 Ret
|
||||||
00000124 Syscall U32(8194) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
|
00000134 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:10
|
||||||
0000012A JmpIfFalse U32(352) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
|
00000136 PushI32 U32(255) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
00000130 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
|
0000013C PushI32 U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
00000132 PushI32 U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
00000142 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
00000138 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
00000144 PushI32 U32(11) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
0000013E PushI32 U32(255) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
0000014A Shl ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
00000144 PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
0000014C PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
0000014A PushI32 U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
00000152 PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
00000150 Syscall U32(12289) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
00000158 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
00000156 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
0000015A PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
00000158 PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
|
00000160 Shl ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
0000015A Jmp U32(352)
|
00000162 BitOr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
00000160 PopScope
|
00000164 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
00000162 PushConst U32(0)
|
0000016A PushI32 U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
00000168 Ret
|
00000170 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
0000016A PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:11
|
00000172 BitOr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
||||||
0000016C Syscall U32(8451) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
00000174 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
||||||
00000172 JmpIfFalse U32(418) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
0000017A PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
||||||
00000178 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
00000180 PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
||||||
0000017A Syscall U32(8449) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
|
00000186 PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
||||||
00000180 Syscall U32(8450) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
|
0000018C GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
||||||
00000186 PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
|
00000192 Syscall U32(4098) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
||||||
0000018C PushI32 U32(65535) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
|
00000198 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
||||||
00000192 Syscall U32(4100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
|
0000019A PopScope
|
||||||
00000198 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
|
0000019C PushConst U32(0)
|
||||||
0000019A PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
000001A2 Ret
|
||||||
0000019C Jmp U32(418)
|
000001A4 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:1
|
||||||
000001A2 PopScope
|
000001A6 PushConst U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:2
|
||||||
000001A4 PushConst U32(0)
|
000001AC Syscall U32(16385) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:2
|
||||||
000001AA Ret
|
000001B2 GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
||||||
000001AC PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:1
|
000001B8 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
||||||
000001AE PushI32 U32(18448) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
|
000001BE Gte ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
||||||
000001B4 Syscall U32(4097) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
|
000001C0 JmpIfFalse U32(554) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
||||||
000001BA Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:2
|
000001C6 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
||||||
000001BC PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
000001C8 GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
|
||||||
000001C2 PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
000001CE PushConst U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
|
||||||
000001C8 PushI32 U32(50) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
000001D4 Syscall U32(16387) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
|
||||||
000001CE PushI32 U32(50) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
000001DA Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:4
|
||||||
000001D4 PushI32 U32(63488) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
000001DC GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:5
|
||||||
000001DA Syscall U32(4098) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
000001E2 Syscall U32(16386) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:5
|
||||||
000001E0 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:3
|
000001E8 GetLocal U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
||||||
000001E2 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
000001EE JmpIfFalse U32(532) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
||||||
000001E8 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
000001F4 PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
||||||
000001EE PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
000001FA PushI32 U32(101) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
||||||
000001F4 PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
00000200 GetLocal U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
||||||
000001FA PushI32 U32(65535) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
00000206 Syscall U32(20482) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
||||||
00000200 Syscall U32(4099) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
0000020C Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:6
|
||||||
00000206 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:4
|
0000020E Jmp U32(532)
|
||||||
00000208 PushI32 U32(64) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
00000214 GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
|
||||||
0000020E PushI32 U32(64) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
0000021A Syscall U32(16388) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
|
||||||
00000214 PushI32 U32(20) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
00000220 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:7
|
||||||
0000021A PushI32 U32(31) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
00000222 PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_fs.ts:3
|
||||||
00000220 Syscall U32(4100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
00000224 Jmp U32(554)
|
||||||
00000226 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:5
|
0000022A PopScope
|
||||||
00000228 PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
0000022C PushConst U32(0)
|
||||||
0000022E PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
00000232 Ret
|
||||||
00000234 PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
00000234 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:1
|
||||||
0000023A PushI32 U32(2016) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
00000236 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
|
||||||
00000240 PushI32 U32(65504) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
0000023C Syscall U32(8193) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
|
||||||
00000246 Syscall U32(4101) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
00000242 JmpIfFalse U32(614) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
|
||||||
0000024C Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:6
|
00000248 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
|
||||||
0000024E PushI32 U32(20) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
0000024A PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
|
||||||
00000254 PushI32 U32(100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
00000250 PushConst U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
|
||||||
0000025A PushI32 U32(30) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
00000256 Syscall U32(20481) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
|
||||||
00000260 PushI32 U32(30) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
0000025C Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:3
|
||||||
00000266 PushI32 U32(2047) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
0000025E PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:2
|
||||||
0000026C PushI32 U32(63519) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
00000260 Jmp U32(614)
|
||||||
00000272 Syscall U32(4102) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
00000266 PushI32 U32(4) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
|
||||||
00000278 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:7
|
0000026C Syscall U32(8194) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
|
||||||
0000027A PopScope
|
00000272 JmpIfFalse U32(692) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
|
||||||
0000027C PushConst U32(0)
|
00000278 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
|
||||||
00000282 Ret
|
0000027A PushConst U32(4) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
||||||
00000284 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:10
|
00000280 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
||||||
00000286 PushI32 U32(255) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
00000286 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
||||||
0000028C PushI32 U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
0000028C PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
||||||
00000292 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
00000292 PushI32 U32(127) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
||||||
00000294 PushI32 U32(11) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
00000298 PushI32 U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
||||||
0000029A Shl ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
0000029E PushI32 U32(1) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
||||||
0000029C PushI32 U32(128) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
000002A4 Syscall U32(12290) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
||||||
000002A2 PushI32 U32(2) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
000002AA Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:7
|
||||||
000002A8 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
000002AC PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:6
|
||||||
000002AA PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
000002AE Jmp U32(692)
|
||||||
000002B0 Shl ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
000002B4 PopScope
|
||||||
000002B2 BitOr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
000002B6 PushConst U32(0)
|
||||||
000002B4 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
000002BC Ret
|
||||||
000002BA PushI32 U32(3) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
000002BE PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:11
|
||||||
000002C0 Shr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
000002C0 PushConst U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
000002C2 BitOr ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:11
|
000002C6 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
000002C4 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
000002CC Syscall U32(8449) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
000002CA PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
000002D2 Syscall U32(8450) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
000002D0 PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
000002D8 PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
000002D6 PushI32 U32(5) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
000002DE PushI32 U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
000002DC GetLocal U32(0) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
000002E4 PushBool Bool(true) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
000002E2 Syscall U32(4098) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
000002E7 PushBool Bool(false) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
000002E8 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_gfx.ts:12
|
000002EA PushBool Bool(false) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
000002EA PopScope
|
000002ED PushI32 U32(4) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
000002EC PushConst U32(0)
|
000002F3 Syscall U32(4103) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
000002F2 Ret
|
000002F9 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:12
|
||||||
|
000002FB Syscall U32(8451) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
|
||||||
|
00000301 JmpIfFalse U32(817) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
|
||||||
|
00000307 PushScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
|
||||||
|
00000309 Syscall U32(8449) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
|
||||||
|
0000030F Syscall U32(8450) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
|
||||||
|
00000315 PushI32 U32(10) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
|
||||||
|
0000031B PushI32 U32(65535) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
|
||||||
|
00000321 Syscall U32(4100) ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
|
||||||
|
00000327 Pop ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:14
|
||||||
|
00000329 PopScope ; /Users/niltonconstantino/personal/workspace.personal/intrepid/prometeu/runtime/test-cartridges/color-square/src/my_input.ts:13
|
||||||
|
0000032B Jmp U32(817)
|
||||||
|
00000331 PopScope
|
||||||
|
00000333 PushConst U32(0)
|
||||||
|
00000339 Ret
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
test-cartridges/color-square/cartridge/assets.pa
Normal file
BIN
test-cartridges/color-square/cartridge/assets.pa
Normal file
Binary file not shown.
@ -5,5 +5,37 @@
|
|||||||
"title": "Color Square",
|
"title": "Color Square",
|
||||||
"app_version": "0.1.0",
|
"app_version": "0.1.0",
|
||||||
"app_mode": "Game",
|
"app_mode": "Game",
|
||||||
"entrypoint": "0"
|
"entrypoint": "0",
|
||||||
|
"asset_table": [
|
||||||
|
{
|
||||||
|
"asset_id": 0,
|
||||||
|
"asset_name": "bgm_music",
|
||||||
|
"bank_type": "SOUNDS",
|
||||||
|
"offset": 0,
|
||||||
|
"size": 88200,
|
||||||
|
"decoded_size": 88200,
|
||||||
|
"codec": "RAW",
|
||||||
|
"metadata": {
|
||||||
|
"sample_rate": 44100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"asset_id": 1,
|
||||||
|
"asset_name": "mouse_cursor",
|
||||||
|
"bank_type": "TILES",
|
||||||
|
"offset": 88200,
|
||||||
|
"size": 2304,
|
||||||
|
"decoded_size": 2304,
|
||||||
|
"codec": "RAW",
|
||||||
|
"metadata": {
|
||||||
|
"tile_size": 16,
|
||||||
|
"width": 16,
|
||||||
|
"height": 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"preload": [
|
||||||
|
{ "asset_name": "bgm_music", "slot": 0 },
|
||||||
|
{ "asset_name": "mouse_cursor", "slot": 1 }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@ -1,4 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
./prometeu-sdk/prometeu build .
|
./prometeu-sdk/prometeu build .
|
||||||
cp build/program.pbc cartridge
|
cp build/program.pbc cartridge
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import {do_init_gfx, print_orange} from "./my_gfx";
|
|||||||
import {do_pad, do_touch} from "./my_input";
|
import {do_pad, do_touch} from "./my_input";
|
||||||
import {do_fs} from "./my_fs";
|
import {do_fs} from "./my_fs";
|
||||||
|
|
||||||
export function tick(): void {
|
export function frame(): void {
|
||||||
do_init_gfx();
|
do_init_gfx();
|
||||||
do_pad();
|
do_pad();
|
||||||
do_touch();
|
do_touch();
|
||||||
|
|||||||
@ -4,12 +4,13 @@ export function do_pad(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pad.a.pressed) {
|
if (pad.a.pressed) {
|
||||||
audio.playSample(1, 0, 255, 128, 1.0);
|
audio.play("bgm_music", 0, 0, 128, 127, 1.0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function do_touch(): void {
|
export function do_touch(): void {
|
||||||
|
gfx.setSprite("mouse_cursor", 0, touch.x, touch.y, 0, 0, true, false, false, 4);
|
||||||
if (touch.button.down) {
|
if (touch.button.down) {
|
||||||
gfx.drawCircle(touch.x, touch.y, 5, color.white);
|
gfx.drawCircle(touch.x, touch.y, 10, color.white);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user