pr 00.1
This commit is contained in:
parent
bbe7ea4dbe
commit
c5c1f68f7c
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -2188,6 +2188,14 @@ dependencies = [
|
||||
"prometeu-runtime-desktop",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometeu-abi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometeu-analysis"
|
||||
version = "0.1.0"
|
||||
@ -2226,6 +2234,7 @@ dependencies = [
|
||||
name = "prometeu-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"prometeu-abi",
|
||||
"prometeu-bytecode",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/prometeu-abi",
|
||||
"crates/prometeu-core",
|
||||
"crates/prometeu-runtime-desktop",
|
||||
"crates/prometeu",
|
||||
|
||||
9
crates/prometeu-abi/Cargo.toml
Normal file
9
crates/prometeu-abi/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "prometeu-abi"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
147
crates/prometeu-abi/src/debugger_protocol.rs
Normal file
147
crates/prometeu-abi/src/debugger_protocol.rs
Normal file
@ -0,0 +1,147 @@
|
||||
use crate::model::AppMode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::virtual_machine::Value;
|
||||
|
||||
pub const DEVTOOLS_PROTOCOL_VERSION: u32 = 1;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum DebugCommand {
|
||||
#[serde(rename = "ok")]
|
||||
Ok,
|
||||
#[serde(rename = "start")]
|
||||
Start,
|
||||
#[serde(rename = "pause")]
|
||||
Pause,
|
||||
#[serde(rename = "resume")]
|
||||
Resume,
|
||||
#[serde(rename = "step")]
|
||||
Step,
|
||||
#[serde(rename = "stepFrame")]
|
||||
StepFrame,
|
||||
#[serde(rename = "getState")]
|
||||
GetState,
|
||||
#[serde(rename = "setBreakpoint")]
|
||||
SetBreakpoint { pc: usize },
|
||||
#[serde(rename = "listBreakpoints")]
|
||||
ListBreakpoints,
|
||||
#[serde(rename = "clearBreakpoint")]
|
||||
ClearBreakpoint { pc: usize },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum DebugResponse {
|
||||
#[serde(rename = "handshake")]
|
||||
Handshake {
|
||||
protocol_version: u32,
|
||||
runtime_version: String,
|
||||
cartridge: HandshakeCartridge,
|
||||
},
|
||||
#[serde(rename = "getState")]
|
||||
GetState {
|
||||
pc: usize,
|
||||
stack_top: Vec<Value>,
|
||||
frame_index: u64,
|
||||
app_id: u32,
|
||||
},
|
||||
#[serde(rename = "breakpoints")]
|
||||
Breakpoints {
|
||||
pcs: Vec<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct HandshakeCartridge {
|
||||
pub app_id: u32,
|
||||
pub title: String,
|
||||
pub app_version: String,
|
||||
pub app_mode: AppMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "event")]
|
||||
pub enum DebugEvent {
|
||||
#[serde(rename = "breakpointHit")]
|
||||
BreakpointHit {
|
||||
pc: usize,
|
||||
frame_index: u64,
|
||||
},
|
||||
#[serde(rename = "log")]
|
||||
Log {
|
||||
level: String,
|
||||
source: String,
|
||||
msg: String,
|
||||
},
|
||||
#[serde(rename = "telemetry")]
|
||||
Telemetry {
|
||||
frame_index: u64,
|
||||
vm_steps: u32,
|
||||
syscalls: u32,
|
||||
cycles: u64,
|
||||
cycles_budget: 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")]
|
||||
Cert {
|
||||
rule: String,
|
||||
used: u64,
|
||||
limit: u64,
|
||||
frame_index: u64,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::virtual_machine::Value;
|
||||
|
||||
#[test]
|
||||
fn test_telemetry_event_serialization() {
|
||||
let event = DebugEvent::Telemetry {
|
||||
frame_index: 10,
|
||||
vm_steps: 100,
|
||||
syscalls: 5,
|
||||
cycles: 5000,
|
||||
cycles_budget: 10000,
|
||||
host_cpu_time_us: 1200,
|
||||
violations: 0,
|
||||
gfx_used_bytes: 1024,
|
||||
gfx_inflight_bytes: 0,
|
||||
gfx_slots_occupied: 1,
|
||||
audio_used_bytes: 2048,
|
||||
audio_inflight_bytes: 0,
|
||||
audio_slots_occupied: 2,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
assert!(json.contains("\"event\":\"telemetry\""));
|
||||
assert!(json.contains("\"cycles\":5000"));
|
||||
assert!(json.contains("\"cycles_budget\":10000"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_state_serialization() {
|
||||
let resp = DebugResponse::GetState {
|
||||
pc: 42,
|
||||
stack_top: vec![Value::Int64(10), Value::String("test".into()), Value::Boolean(true)],
|
||||
frame_index: 5,
|
||||
app_id: 1,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&resp).unwrap();
|
||||
assert!(json.contains("\"type\":\"getState\""));
|
||||
assert!(json.contains("\"pc\":42"));
|
||||
assert!(json.contains("\"stack_top\":[10,\"test\",true]"));
|
||||
assert!(json.contains("\"frame_index\":5"));
|
||||
assert!(json.contains("\"app_id\":1"));
|
||||
}
|
||||
}
|
||||
12
crates/prometeu-abi/src/lib.rs
Normal file
12
crates/prometeu-abi/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
//! Prometeu ABI: tipos e contratos compartilhados entre os crates.
|
||||
|
||||
pub mod model;
|
||||
pub mod log;
|
||||
pub mod telemetry;
|
||||
pub mod debugger_protocol;
|
||||
|
||||
// Tipos da VM que fazem parte do contrato (ex.: inspeção de pilha pelo debugger)
|
||||
pub mod virtual_machine {
|
||||
mod value;
|
||||
pub use value::Value;
|
||||
}
|
||||
13
crates/prometeu-abi/src/log/log_event.rs
Normal file
13
crates/prometeu-abi/src/log/log_event.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use crate::log::LogLevel;
|
||||
use crate::log::LogSource;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LogEvent {
|
||||
pub seq: u64,
|
||||
pub ts_ms: u64,
|
||||
pub frame: u64,
|
||||
pub level: LogLevel,
|
||||
pub source: LogSource,
|
||||
pub tag: u16,
|
||||
pub msg: String,
|
||||
}
|
||||
8
crates/prometeu-abi/src/log/log_level.rs
Normal file
8
crates/prometeu-abi/src/log/log_level.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LogLevel {
|
||||
Trace,
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
93
crates/prometeu-abi/src/log/log_service.rs
Normal file
93
crates/prometeu-abi/src/log/log_service.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use crate::log::{LogEvent, LogLevel, LogSource};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub struct LogService {
|
||||
events: VecDeque<LogEvent>,
|
||||
capacity: usize,
|
||||
next_seq: u64,
|
||||
}
|
||||
|
||||
impl LogService {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
events: VecDeque::with_capacity(capacity),
|
||||
capacity,
|
||||
next_seq: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log(&mut self, ts_ms: u64, frame: u64, level: LogLevel, source: LogSource, tag: u16, msg: String) {
|
||||
if self.events.len() >= self.capacity {
|
||||
self.events.pop_front();
|
||||
}
|
||||
self.events.push_back(LogEvent {
|
||||
seq: self.next_seq,
|
||||
ts_ms,
|
||||
frame,
|
||||
level,
|
||||
source,
|
||||
tag,
|
||||
msg,
|
||||
});
|
||||
self.next_seq += 1;
|
||||
}
|
||||
|
||||
pub fn get_recent(&self, n: usize) -> Vec<LogEvent> {
|
||||
self.events.iter().rev().take(n).cloned().collect::<Vec<_>>().into_iter().rev().collect()
|
||||
}
|
||||
|
||||
pub fn get_after(&self, seq: u64) -> Vec<LogEvent> {
|
||||
self.events.iter().filter(|e| e.seq > seq).cloned().collect()
|
||||
}
|
||||
|
||||
pub fn last_seq(&self) -> Option<u64> {
|
||||
self.events.back().map(|e| e.seq)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ring_buffer_capacity() {
|
||||
let mut service = LogService::new(3);
|
||||
service.log(100, 1, LogLevel::Info, LogSource::Pos, 0, "Log 1".to_string());
|
||||
service.log(110, 1, LogLevel::Info, LogSource::Pos, 0, "Log 2".to_string());
|
||||
service.log(120, 1, LogLevel::Info, LogSource::Pos, 0, "Log 3".to_string());
|
||||
|
||||
assert_eq!(service.events.len(), 3);
|
||||
assert_eq!(service.events[0].msg, "Log 1");
|
||||
|
||||
service.log(130, 1, LogLevel::Info, LogSource::Pos, 0, "Log 4".to_string());
|
||||
assert_eq!(service.events.len(), 3);
|
||||
assert_eq!(service.events[0].msg, "Log 2");
|
||||
assert_eq!(service.events[2].msg, "Log 4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_recent() {
|
||||
let mut service = LogService::new(10);
|
||||
for i in 0..5 {
|
||||
service.log(i as u64, 1, LogLevel::Info, LogSource::Pos, 0, format!("Log {}", i));
|
||||
}
|
||||
|
||||
let recent = service.get_recent(2);
|
||||
assert_eq!(recent.len(), 2);
|
||||
assert_eq!(recent[0].msg, "Log 3");
|
||||
assert_eq!(recent[1].msg, "Log 4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_after() {
|
||||
let mut service = LogService::new(10);
|
||||
for i in 0..5 {
|
||||
service.log(i as u64, 1, LogLevel::Info, LogSource::Pos, 0, format!("Log {}", i));
|
||||
}
|
||||
|
||||
let after = service.get_after(2); // seqs are 0, 1, 2, 3, 4. Should return 3 and 4.
|
||||
assert_eq!(after.len(), 2);
|
||||
assert_eq!(after[0].msg, "Log 3");
|
||||
assert_eq!(after[1].msg, "Log 4");
|
||||
}
|
||||
}
|
||||
8
crates/prometeu-abi/src/log/log_source.rs
Normal file
8
crates/prometeu-abi/src/log/log_source.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LogSource {
|
||||
Pos,
|
||||
Hub,
|
||||
Vm,
|
||||
Fs,
|
||||
App { app_id: u32 },
|
||||
}
|
||||
9
crates/prometeu-abi/src/log/mod.rs
Normal file
9
crates/prometeu-abi/src/log/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
mod log_level;
|
||||
mod log_source;
|
||||
mod log_event;
|
||||
mod log_service;
|
||||
|
||||
pub use log_level::LogLevel;
|
||||
pub use log_source::LogSource;
|
||||
pub use log_event::LogEvent;
|
||||
pub use log_service::LogService;
|
||||
80
crates/prometeu-abi/src/model/asset.rs
Normal file
80
crates/prometeu-abi/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,
|
||||
}
|
||||
}
|
||||
}
|
||||
40
crates/prometeu-abi/src/model/button.rs
Normal file
40
crates/prometeu-abi/src/model/button.rs
Normal file
@ -0,0 +1,40 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum ButtonId {
|
||||
Up = 0,
|
||||
Down = 1,
|
||||
Left = 2,
|
||||
Right = 3,
|
||||
A = 4,
|
||||
B = 5,
|
||||
X = 6,
|
||||
Y = 7,
|
||||
L = 8,
|
||||
R = 9,
|
||||
Start = 10,
|
||||
Select = 11,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
pub struct Button {
|
||||
pub pressed: bool,
|
||||
pub released: bool,
|
||||
pub down: bool,
|
||||
pub hold_frames: u32,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
pub fn begin_frame(&mut self, is_down_now: bool) {
|
||||
let was_down = self.down;
|
||||
self.down = is_down_now.clone();
|
||||
|
||||
self.pressed = !was_down && self.down;
|
||||
self.released = was_down && !self.down;
|
||||
|
||||
if self.down {
|
||||
self.hold_frames = self.hold_frames.saturating_add(1);
|
||||
} else {
|
||||
self.hold_frames = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
crates/prometeu-abi/src/model/cartridge.rs
Normal file
77
crates/prometeu-abi/src/model/cartridge.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use crate::model::asset::{AssetEntry, PreloadEntry};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum AppMode {
|
||||
Game,
|
||||
System,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cartridge {
|
||||
pub app_id: u32,
|
||||
pub title: String,
|
||||
pub app_version: String,
|
||||
pub app_mode: AppMode,
|
||||
pub entrypoint: String,
|
||||
pub program: Vec<u8>,
|
||||
pub assets: Vec<u8>,
|
||||
pub asset_table: Vec<AssetEntry>,
|
||||
pub preload: Vec<PreloadEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct CartridgeDTO {
|
||||
pub app_id: u32,
|
||||
pub title: String,
|
||||
pub app_version: String,
|
||||
pub app_mode: AppMode,
|
||||
pub entrypoint: String,
|
||||
pub program: Vec<u8>,
|
||||
pub assets: Vec<u8>,
|
||||
#[serde(default)]
|
||||
pub asset_table: Vec<AssetEntry>,
|
||||
#[serde(default)]
|
||||
pub preload: Vec<PreloadEntry>,
|
||||
}
|
||||
|
||||
impl From<CartridgeDTO> for Cartridge {
|
||||
fn from(dto: CartridgeDTO) -> Self {
|
||||
Self {
|
||||
app_id: dto.app_id,
|
||||
title: dto.title,
|
||||
app_version: dto.app_version,
|
||||
app_mode: dto.app_mode,
|
||||
entrypoint: dto.entrypoint,
|
||||
program: dto.program,
|
||||
assets: dto.assets,
|
||||
asset_table: dto.asset_table,
|
||||
preload: dto.preload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CartridgeError {
|
||||
NotFound,
|
||||
InvalidFormat,
|
||||
InvalidManifest,
|
||||
UnsupportedVersion,
|
||||
MissingProgram,
|
||||
IoError,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CartridgeManifest {
|
||||
pub magic: String,
|
||||
pub cartridge_version: u32,
|
||||
pub app_id: u32,
|
||||
pub title: String,
|
||||
pub app_version: String,
|
||||
pub app_mode: AppMode,
|
||||
pub entrypoint: String,
|
||||
#[serde(default)]
|
||||
pub asset_table: Vec<AssetEntry>,
|
||||
#[serde(default)]
|
||||
pub preload: Vec<PreloadEntry>,
|
||||
}
|
||||
80
crates/prometeu-abi/src/model/cartridge_loader.rs
Normal file
80
crates/prometeu-abi/src/model/cartridge_loader.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use crate::model::cartridge::{Cartridge, CartridgeDTO, CartridgeError, CartridgeManifest};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct CartridgeLoader;
|
||||
|
||||
impl CartridgeLoader {
|
||||
pub fn load(path: impl AsRef<Path>) -> Result<Cartridge, CartridgeError> {
|
||||
let path = path.as_ref();
|
||||
if !path.exists() {
|
||||
return Err(CartridgeError::NotFound);
|
||||
}
|
||||
|
||||
if path.is_dir() {
|
||||
DirectoryCartridgeLoader::load(path)
|
||||
} else if path.extension().is_some_and(|ext| ext == "pmc") {
|
||||
PackedCartridgeLoader::load(path)
|
||||
} else {
|
||||
Err(CartridgeError::InvalidFormat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirectoryCartridgeLoader;
|
||||
|
||||
impl DirectoryCartridgeLoader {
|
||||
pub fn load(path: &Path) -> Result<Cartridge, CartridgeError> {
|
||||
let manifest_path = path.join("manifest.json");
|
||||
if !manifest_path.exists() {
|
||||
return Err(CartridgeError::InvalidManifest);
|
||||
}
|
||||
|
||||
let manifest_content = fs::read_to_string(manifest_path).map_err(|_| CartridgeError::IoError)?;
|
||||
let manifest: CartridgeManifest = serde_json::from_str(&manifest_content).map_err(|_| CartridgeError::InvalidManifest)?;
|
||||
|
||||
// Additional validation as per requirements
|
||||
if manifest.magic != "PMTU" {
|
||||
return Err(CartridgeError::InvalidManifest);
|
||||
}
|
||||
if manifest.cartridge_version != 1 {
|
||||
return Err(CartridgeError::UnsupportedVersion);
|
||||
}
|
||||
|
||||
let program_path = path.join("program.pbc");
|
||||
if !program_path.exists() {
|
||||
return Err(CartridgeError::MissingProgram);
|
||||
}
|
||||
|
||||
let program = fs::read(program_path).map_err(|_| CartridgeError::IoError)?;
|
||||
|
||||
let assets_pa_path = path.join("assets.pa");
|
||||
let assets = if assets_pa_path.exists() {
|
||||
fs::read(assets_pa_path).map_err(|_| CartridgeError::IoError)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let dto = CartridgeDTO {
|
||||
app_id: manifest.app_id,
|
||||
title: manifest.title,
|
||||
app_version: manifest.app_version,
|
||||
app_mode: manifest.app_mode,
|
||||
entrypoint: manifest.entrypoint,
|
||||
program,
|
||||
assets,
|
||||
asset_table: manifest.asset_table,
|
||||
preload: manifest.preload,
|
||||
};
|
||||
|
||||
Ok(Cartridge::from(dto))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PackedCartridgeLoader;
|
||||
|
||||
impl PackedCartridgeLoader {
|
||||
pub fn load(_path: &Path) -> Result<Cartridge, CartridgeError> {
|
||||
Err(CartridgeError::InvalidFormat)
|
||||
}
|
||||
}
|
||||
76
crates/prometeu-abi/src/model/color.rs
Normal file
76
crates/prometeu-abi/src/model/color.rs
Normal file
@ -0,0 +1,76 @@
|
||||
/// Represents a 16-bit color in the RGB565 format.
|
||||
///
|
||||
/// The RGB565 format is a common high-color representation for embedded systems:
|
||||
/// - **Red**: 5 bits (0..31)
|
||||
/// - **Green**: 6 bits (0..63)
|
||||
/// - **Blue**: 5 bits (0..31)
|
||||
///
|
||||
/// Prometeu does not have a hardware alpha channel. Transparency is achieved
|
||||
/// by using a specific color key (Magenta / 0xF81F) which the GFX engine
|
||||
/// skips during rendering.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Color(pub u16);
|
||||
|
||||
impl Color {
|
||||
pub const BLACK: Self = Self::rgb(0, 0, 0); // 0x0000
|
||||
pub const WHITE: Self = Self::rgb(255, 255, 255); // 0xFFFF
|
||||
pub const RED: Self = Self::rgb(255, 0, 0); // 0xF800
|
||||
pub const GREEN: Self = Self::rgb(0, 255, 0); // 0x07E0
|
||||
pub const BLUE: Self = Self::rgb(0, 0, 255); // 0x001F
|
||||
pub const YELLOW: Self = Self::rgb(255, 255, 0); // 0xFFE0
|
||||
pub const ORANGE: Self = Self::rgb(255, 128, 0);
|
||||
pub const INDIGO: Self = Self::rgb(75, 0, 130);
|
||||
pub const GRAY: Self = Self::rgb(128, 128, 128);
|
||||
pub const CYAN: Self = Self::rgb(0, 255, 255); // 0x07FF
|
||||
pub const MAGENTA: Self = Self::rgb(255, 0, 255); // 0xF81F
|
||||
pub const COLOR_KEY: Self = Self::MAGENTA;
|
||||
pub const TRANSPARENT: Self = Self::MAGENTA;
|
||||
|
||||
/// Extracts channels in the native RGB565 range:
|
||||
/// R: 0..31, G: 0..63, B: 0..31
|
||||
#[inline(always)]
|
||||
pub const fn unpack_to_native(px: u16) -> (u8, u8, u8) {
|
||||
let r = ((px >> 11) & 0x1F) as u8;
|
||||
let g = ((px >> 5) & 0x3F) as u8;
|
||||
let b = (px & 0x1F) as u8;
|
||||
(r, g, b)
|
||||
}
|
||||
|
||||
/// Packs channels from the native RGB565 range into a pixel:
|
||||
/// R: 0..31, G: 0..63, B: 0..31
|
||||
#[inline(always)]
|
||||
pub const fn pack_from_native(r: u8, g: u8, b: u8) -> u16 {
|
||||
((r as u16 & 0x1F) << 11) | ((g as u16 & 0x3F) << 5) | (b as u16 & 0x1F)
|
||||
}
|
||||
|
||||
/// Creates an RGB565 color from 8-bit components (0..255).
|
||||
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
|
||||
let r5 = (r as u16 >> 3) & 0x1F;
|
||||
let g6 = (g as u16 >> 2) & 0x3F;
|
||||
let b5 = (b as u16 >> 3) & 0x1F;
|
||||
|
||||
Self((r5 << 11) | (g6 << 5) | b5)
|
||||
}
|
||||
|
||||
pub const fn gray_scale(c: u8) -> Self {
|
||||
Self::rgb(c, c, c)
|
||||
}
|
||||
|
||||
pub const fn from_raw(raw: u16) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
|
||||
pub const fn raw(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub const fn hex(self) -> i32 {
|
||||
let (r5, g6, b5) = Self::unpack_to_native(self.0);
|
||||
let r8 = ((r5 as u32) << 3) | ((r5 as u32) >> 2);
|
||||
let g8 = ((g6 as u32) << 2) | ((g6 as u32) >> 4);
|
||||
let b8 = ((b5 as u32) << 3) | ((b5 as u32) >> 2);
|
||||
let hex = r8 << 16 | g8 << 8 | b8;
|
||||
hex as i32
|
||||
}
|
||||
|
||||
}
|
||||
25
crates/prometeu-abi/src/model/mod.rs
Normal file
25
crates/prometeu-abi/src/model/mod.rs
Normal file
@ -0,0 +1,25 @@
|
||||
mod asset;
|
||||
mod color;
|
||||
mod button;
|
||||
mod tile;
|
||||
mod tile_layer;
|
||||
mod tile_bank;
|
||||
mod sound_bank;
|
||||
mod sprite;
|
||||
mod sample;
|
||||
mod cartridge;
|
||||
mod cartridge_loader;
|
||||
mod window;
|
||||
|
||||
pub use asset::{AssetEntry, BankStats, BankType, HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats};
|
||||
pub use button::{Button, ButtonId};
|
||||
pub use cartridge::{AppMode, Cartridge, CartridgeDTO, CartridgeError};
|
||||
pub use cartridge_loader::{CartridgeLoader, DirectoryCartridgeLoader, PackedCartridgeLoader};
|
||||
pub use color::Color;
|
||||
pub use sample::Sample;
|
||||
pub use sound_bank::SoundBank;
|
||||
pub use sprite::Sprite;
|
||||
pub use tile::Tile;
|
||||
pub use tile_bank::{TileBank, TileSize};
|
||||
pub use tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap};
|
||||
pub use window::{Rect, Window, WindowId};
|
||||
27
crates/prometeu-abi/src/model/sample.rs
Normal file
27
crates/prometeu-abi/src/model/sample.rs
Normal file
@ -0,0 +1,27 @@
|
||||
pub struct Sample {
|
||||
pub sample_rate: u32,
|
||||
pub data: Vec<i16>,
|
||||
pub loop_start: Option<u32>,
|
||||
pub loop_end: Option<u32>,
|
||||
}
|
||||
|
||||
impl Sample {
|
||||
pub fn new(sample_rate: u32, data: Vec<i16>) -> Self {
|
||||
Self {
|
||||
sample_rate,
|
||||
data,
|
||||
loop_start: None,
|
||||
loop_end: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_loop(mut self, start: u32, end: u32) -> Self {
|
||||
self.loop_start = Some(start);
|
||||
self.loop_end = Some(end);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn frames_len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
}
|
||||
16
crates/prometeu-abi/src/model/sound_bank.rs
Normal file
16
crates/prometeu-abi/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 }
|
||||
}
|
||||
}
|
||||
13
crates/prometeu-abi/src/model/sprite.rs
Normal file
13
crates/prometeu-abi/src/model/sprite.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use crate::model::Tile;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Sprite {
|
||||
pub tile: Tile,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub bank_id: u8,
|
||||
pub active: bool,
|
||||
pub flip_x: bool,
|
||||
pub flip_y: bool,
|
||||
pub priority: u8,
|
||||
}
|
||||
7
crates/prometeu-abi/src/model/tile.rs
Normal file
7
crates/prometeu-abi/src/model/tile.rs
Normal file
@ -0,0 +1,7 @@
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Tile {
|
||||
pub id: u16,
|
||||
pub flip_x: bool,
|
||||
pub flip_y: bool,
|
||||
pub palette_id: u8,
|
||||
}
|
||||
75
crates/prometeu-abi/src/model/tile_bank.rs
Normal file
75
crates/prometeu-abi/src/model/tile_bank.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use crate::model::Color;
|
||||
|
||||
/// Standard sizes for square tiles.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TileSize {
|
||||
/// 8x8 pixels.
|
||||
Size8 = 8,
|
||||
/// 16x16 pixels.
|
||||
Size16 = 16,
|
||||
/// 32x32 pixels.
|
||||
Size32 = 32,
|
||||
}
|
||||
|
||||
/// A container for graphical assets.
|
||||
///
|
||||
/// A TileBank stores both the raw pixel data (as palette indices) and the
|
||||
/// color palettes themselves. This encapsulates all the information needed
|
||||
/// to render a set of tiles.
|
||||
pub struct TileBank {
|
||||
/// Dimension of each individual tile in the bank.
|
||||
pub tile_size: TileSize,
|
||||
/// Width of the full bank sheet in pixels.
|
||||
pub width: usize,
|
||||
/// Height of the full bank sheet in pixels.
|
||||
pub height: usize,
|
||||
|
||||
/// Pixel data stored as 4-bit indices (packed into 8-bit values).
|
||||
/// Index 0 is always reserved for transparency.
|
||||
pub pixel_indices: Vec<u8>,
|
||||
/// Table of 64 palettes, each containing 16 RGB565 colors, total of 1024 colors for a bank.
|
||||
pub palettes: [[Color; 16]; 64],
|
||||
}
|
||||
|
||||
impl TileBank {
|
||||
/// Creates an empty tile bank with the specified dimensions.
|
||||
pub fn new(tile_size: TileSize, width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
tile_size,
|
||||
width,
|
||||
height,
|
||||
pixel_indices: vec![0; width * height], // Index 0 = Transparent
|
||||
palettes: [[Color::BLACK; 16]; 64],
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves a global tile ID and local pixel coordinates to a palette index.
|
||||
/// tile_id: the tile index in the bank
|
||||
/// local_x, local_y: the pixel position inside the tile (0 to tile_size-1)
|
||||
pub fn get_pixel_index(&self, tile_id: u16, local_x: usize, local_y: usize) -> u8 {
|
||||
let size = self.tile_size as usize;
|
||||
let tiles_per_row = self.width / size;
|
||||
let tile_x = (tile_id as usize % tiles_per_row) * size;
|
||||
let tile_y = (tile_id as usize / tiles_per_row) * size;
|
||||
|
||||
let pixel_x = tile_x + local_x;
|
||||
let pixel_y = tile_y + local_y;
|
||||
|
||||
if pixel_x < self.width && pixel_y < self.height {
|
||||
self.pixel_indices[pixel_y * self.width + pixel_x]
|
||||
} else {
|
||||
0 // Default to transparent if out of bounds
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a 4-bit index to a real RGB565 Color using the specified palette.
|
||||
pub fn resolve_color(&self, palette_id: u8, pixel_index: u8) -> Color {
|
||||
// Hardware Rule: Index 0 is always transparent.
|
||||
// We use Magenta as the 'transparent' signal color during composition.
|
||||
if pixel_index == 0 {
|
||||
return Color::COLOR_KEY;
|
||||
}
|
||||
|
||||
self.palettes[palette_id as usize][pixel_index as usize]
|
||||
}
|
||||
}
|
||||
109
crates/prometeu-abi/src/model/tile_layer.rs
Normal file
109
crates/prometeu-abi/src/model/tile_layer.rs
Normal file
@ -0,0 +1,109 @@
|
||||
use crate::model::tile_bank::TileSize;
|
||||
use crate::model::Tile;
|
||||
use crate::model::TileSize::Size8;
|
||||
|
||||
pub struct TileMap {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub tiles: Vec<Tile>,
|
||||
}
|
||||
|
||||
impl TileMap {
|
||||
fn create(width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
tiles: vec![Tile::default(); width * height],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_tile(&mut self, x: usize, y: usize, tile: Tile) {
|
||||
if x < self.width && y < self.height {
|
||||
self.tiles[y * self.width + x] = tile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct TileLayer {
|
||||
pub bank_id: u8,
|
||||
pub tile_size: TileSize,
|
||||
pub map: TileMap,
|
||||
}
|
||||
|
||||
impl TileLayer {
|
||||
fn create(width: usize, height: usize, tile_size: TileSize) -> Self {
|
||||
Self {
|
||||
bank_id: 0,
|
||||
tile_size,
|
||||
map: TileMap::create(width, height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for TileLayer {
|
||||
type Target = TileMap;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.map
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for TileLayer {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.map
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScrollableTileLayer {
|
||||
pub layer: TileLayer,
|
||||
pub scroll_x: i32,
|
||||
pub scroll_y: i32,
|
||||
}
|
||||
|
||||
impl ScrollableTileLayer {
|
||||
pub fn new(width: usize, height: usize, tile_size: TileSize) -> Self {
|
||||
Self {
|
||||
layer: TileLayer::create(width, height, tile_size),
|
||||
scroll_x: 0,
|
||||
scroll_y: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ScrollableTileLayer {
|
||||
type Target = TileLayer;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.layer
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for ScrollableTileLayer {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.layer
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HudTileLayer {
|
||||
pub layer: TileLayer,
|
||||
}
|
||||
|
||||
impl HudTileLayer {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
layer: TileLayer::create(width, height, Size8),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for HudTileLayer {
|
||||
type Target = TileLayer;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.layer
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for HudTileLayer {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.layer
|
||||
}
|
||||
}
|
||||
21
crates/prometeu-abi/src/model/window.rs
Normal file
21
crates/prometeu-abi/src/model/window.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use crate::model::Color;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rect {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub w: i32,
|
||||
pub h: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct WindowId(pub u32);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Window {
|
||||
pub id: WindowId,
|
||||
pub viewport: Rect,
|
||||
pub has_focus: bool,
|
||||
pub title: String,
|
||||
pub color: Color,
|
||||
}
|
||||
123
crates/prometeu-abi/src/telemetry.rs
Normal file
123
crates/prometeu-abi/src/telemetry.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use crate::log::{LogLevel, LogService, LogSource};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct TelemetryFrame {
|
||||
pub frame_index: u64,
|
||||
pub vm_steps: u32,
|
||||
pub cycles_used: u64,
|
||||
pub cycles_budget: u64,
|
||||
pub syscalls: u32,
|
||||
pub host_cpu_time_us: u64,
|
||||
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)]
|
||||
pub struct CertificationConfig {
|
||||
pub enabled: bool,
|
||||
pub cycles_budget_per_frame: Option<u64>,
|
||||
pub max_syscalls_per_frame: Option<u32>,
|
||||
pub max_host_cpu_us_per_frame: Option<u64>,
|
||||
}
|
||||
|
||||
pub struct Certifier {
|
||||
pub config: CertificationConfig,
|
||||
}
|
||||
|
||||
impl Certifier {
|
||||
pub fn new(config: CertificationConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
pub fn evaluate(&self, telemetry: &TelemetryFrame, log_service: &mut LogService, ts_ms: u64) -> usize {
|
||||
if !self.config.enabled {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut violations = 0;
|
||||
|
||||
if let Some(budget) = self.config.cycles_budget_per_frame {
|
||||
if telemetry.cycles_used > budget {
|
||||
log_service.log(
|
||||
ts_ms,
|
||||
telemetry.frame_index,
|
||||
LogLevel::Warn,
|
||||
LogSource::Pos,
|
||||
0xCA01,
|
||||
format!("Cert: cycles_used exceeded budget ({} > {})", telemetry.cycles_used, budget),
|
||||
);
|
||||
violations += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(limit) = self.config.max_syscalls_per_frame {
|
||||
if telemetry.syscalls > limit {
|
||||
log_service.log(
|
||||
ts_ms,
|
||||
telemetry.frame_index,
|
||||
LogLevel::Warn,
|
||||
LogSource::Pos,
|
||||
0xCA02,
|
||||
format!("Cert: syscalls per frame exceeded limit ({} > {})", telemetry.syscalls, limit),
|
||||
);
|
||||
violations += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(limit) = self.config.max_host_cpu_us_per_frame {
|
||||
if telemetry.host_cpu_time_us > limit {
|
||||
log_service.log(
|
||||
ts_ms,
|
||||
telemetry.frame_index,
|
||||
LogLevel::Warn,
|
||||
LogSource::Pos,
|
||||
0xCA03,
|
||||
format!("Cert: host_cpu_time_us exceeded limit ({} > {})", telemetry.host_cpu_time_us, limit),
|
||||
);
|
||||
violations += 1;
|
||||
}
|
||||
}
|
||||
|
||||
violations
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::log::LogService;
|
||||
|
||||
#[test]
|
||||
fn test_certifier_violations() {
|
||||
let config = CertificationConfig {
|
||||
enabled: true,
|
||||
cycles_budget_per_frame: Some(100),
|
||||
max_syscalls_per_frame: Some(5),
|
||||
max_host_cpu_us_per_frame: Some(1000),
|
||||
};
|
||||
let cert = Certifier::new(config);
|
||||
let mut ls = LogService::new(10);
|
||||
|
||||
let mut tel = TelemetryFrame::default();
|
||||
tel.cycles_used = 150;
|
||||
tel.syscalls = 10;
|
||||
tel.host_cpu_time_us = 500;
|
||||
|
||||
let violations = cert.evaluate(&tel, &mut ls, 1000);
|
||||
assert_eq!(violations, 2);
|
||||
|
||||
let logs = ls.get_recent(10);
|
||||
assert_eq!(logs.len(), 2);
|
||||
assert!(logs[0].msg.contains("cycles_used"));
|
||||
assert!(logs[1].msg.contains("syscalls"));
|
||||
}
|
||||
}
|
||||
151
crates/prometeu-abi/src/virtual_machine/value.rs
Normal file
151
crates/prometeu-abi/src/virtual_machine/value.rs
Normal file
@ -0,0 +1,151 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
/// Represents any piece of data that can be stored on the VM stack or in globals.
|
||||
///
|
||||
/// The PVM is "dynamically typed" at the bytecode level, meaning a single
|
||||
/// `Value` enum can hold different primitive types. The VM performs
|
||||
/// automatic type promotion (e.g., adding an Int32 to a Float64 results
|
||||
/// in a Float64) to ensure mathematical correctness.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Value {
|
||||
/// 32-bit signed integer. Used for most loop counters and indices.
|
||||
Int32(i32),
|
||||
/// 64-bit signed integer. Used for large numbers and timestamps.
|
||||
Int64(i64),
|
||||
/// 64-bit double precision float. Used for physics and complex math.
|
||||
Float(f64),
|
||||
/// Boolean value (true/false).
|
||||
Boolean(bool),
|
||||
/// UTF-8 string. Strings are immutable and usually come from the Constant Pool.
|
||||
String(String),
|
||||
/// Bounded 16-bit-ish integer.
|
||||
Bounded(u32),
|
||||
/// A pointer to an object on the heap.
|
||||
Gate(usize),
|
||||
/// Represents the absence of a value (equivalent to `null` or `undefined`).
|
||||
Null,
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Value::Int32(a), Value::Int32(b)) => a == b,
|
||||
(Value::Int64(a), Value::Int64(b)) => a == b,
|
||||
(Value::Int32(a), Value::Int64(b)) => *a as i64 == *b,
|
||||
(Value::Int64(a), Value::Int32(b)) => *a == *b as i64,
|
||||
(Value::Float(a), Value::Float(b)) => a == b,
|
||||
(Value::Int32(a), Value::Float(b)) => *a as f64 == *b,
|
||||
(Value::Float(a), Value::Int32(b)) => *a == *b as f64,
|
||||
(Value::Int64(a), Value::Float(b)) => *a as f64 == *b,
|
||||
(Value::Float(a), Value::Int64(b)) => *a == *b as f64,
|
||||
(Value::Boolean(a), Value::Boolean(b)) => a == b,
|
||||
(Value::String(a), Value::String(b)) => a == b,
|
||||
(Value::Bounded(a), Value::Bounded(b)) => a == b,
|
||||
(Value::Gate(a), Value::Gate(b)) => a == b,
|
||||
(Value::Null, Value::Null) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Value {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
(Value::Int32(a), Value::Int32(b)) => a.partial_cmp(b),
|
||||
(Value::Int64(a), Value::Int64(b)) => a.partial_cmp(b),
|
||||
(Value::Int32(a), Value::Int64(b)) => (*a as i64).partial_cmp(b),
|
||||
(Value::Int64(a), Value::Int32(b)) => a.partial_cmp(&(*b as i64)),
|
||||
(Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
|
||||
(Value::Bounded(a), Value::Bounded(b)) => a.partial_cmp(b),
|
||||
(Value::Int32(a), Value::Float(b)) => (*a as f64).partial_cmp(b),
|
||||
(Value::Float(a), Value::Int32(b)) => a.partial_cmp(&(*b as f64)),
|
||||
(Value::Int64(a), Value::Float(b)) => (*a as f64).partial_cmp(b),
|
||||
(Value::Float(a), Value::Int64(b)) => a.partial_cmp(&(*b as f64)),
|
||||
(Value::Boolean(a), Value::Boolean(b)) => a.partial_cmp(b),
|
||||
(Value::String(a), Value::String(b)) => a.partial_cmp(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn as_float(&self) -> Option<f64> {
|
||||
match self {
|
||||
Value::Int32(i) => Some(*i as f64),
|
||||
Value::Int64(i) => Some(*i as f64),
|
||||
Value::Float(f) => Some(*f),
|
||||
Value::Bounded(b) => Some(*b as f64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_integer(&self) -> Option<i64> {
|
||||
match self {
|
||||
Value::Int32(i) => Some(*i as i64),
|
||||
Value::Int64(i) => Some(*i),
|
||||
Value::Float(f) => Some(*f as i64),
|
||||
Value::Bounded(b) => Some(*b as i64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
Value::Int32(i) => i.to_string(),
|
||||
Value::Int64(i) => i.to_string(),
|
||||
Value::Float(f) => f.to_string(),
|
||||
Value::Bounded(b) => format!("{}b", b),
|
||||
Value::Boolean(b) => b.to_string(),
|
||||
Value::String(s) => s.clone(),
|
||||
Value::Gate(r) => format!("[Gate {}]", r),
|
||||
Value::Null => "null".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_value_equality() {
|
||||
assert_eq!(Value::Int32(10), Value::Int32(10));
|
||||
assert_eq!(Value::Int64(10), Value::Int64(10));
|
||||
assert_eq!(Value::Int32(10), Value::Int64(10));
|
||||
assert_eq!(Value::Int64(10), Value::Int32(10));
|
||||
assert_eq!(Value::Float(10.5), Value::Float(10.5));
|
||||
assert_eq!(Value::Int32(10), Value::Float(10.0));
|
||||
assert_eq!(Value::Float(10.0), Value::Int32(10));
|
||||
assert_eq!(Value::Int64(10), Value::Float(10.0));
|
||||
assert_eq!(Value::Float(10.0), Value::Int64(10));
|
||||
assert_ne!(Value::Int32(10), Value::Int32(11));
|
||||
assert_ne!(Value::Int64(10), Value::Int64(11));
|
||||
assert_ne!(Value::Int32(10), Value::Int64(11));
|
||||
assert_ne!(Value::Int32(10), Value::Float(10.1));
|
||||
assert_eq!(Value::Boolean(true), Value::Boolean(true));
|
||||
assert_ne!(Value::Boolean(true), Value::Boolean(false));
|
||||
assert_eq!(Value::String("oi".into()), Value::String("oi".into()));
|
||||
assert_eq!(Value::Null, Value::Null);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_conversions() {
|
||||
let v_int32 = Value::Int32(42);
|
||||
assert_eq!(v_int32.as_float(), Some(42.0));
|
||||
assert_eq!(v_int32.as_integer(), Some(42));
|
||||
|
||||
let v_int64 = Value::Int64(42);
|
||||
assert_eq!(v_int64.as_float(), Some(42.0));
|
||||
assert_eq!(v_int64.as_integer(), Some(42));
|
||||
|
||||
let v_float = Value::Float(42.7);
|
||||
assert_eq!(v_float.as_float(), Some(42.7));
|
||||
assert_eq!(v_float.as_integer(), Some(42));
|
||||
|
||||
let v_bool = Value::Boolean(true);
|
||||
assert_eq!(v_bool.as_float(), None);
|
||||
assert_eq!(v_bool.as_integer(), None);
|
||||
}
|
||||
}
|
||||
@ -7,4 +7,5 @@ license.workspace = true
|
||||
[dependencies]
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
prometeu-bytecode = { path = "../prometeu-bytecode" }
|
||||
prometeu-bytecode = { path = "../prometeu-bytecode" }
|
||||
prometeu-abi = { path = "../prometeu-abi" }
|
||||
@ -16,14 +16,17 @@
|
||||
//! The "Source of Truth" for the console's behavior lives here.
|
||||
|
||||
pub mod hardware;
|
||||
pub mod log;
|
||||
pub mod virtual_machine;
|
||||
pub mod model;
|
||||
pub mod firmware;
|
||||
pub mod fs;
|
||||
pub mod telemetry;
|
||||
pub mod debugger_protocol;
|
||||
pub mod prometeu_os;
|
||||
mod prometeu_hub;
|
||||
|
||||
// Facade/reexports for ABI modules (temporary during PR-00.x)
|
||||
pub use prometeu_abi as abi;
|
||||
pub use prometeu_abi::model;
|
||||
pub use prometeu_abi::log;
|
||||
pub use prometeu_abi::telemetry;
|
||||
pub use prometeu_abi::debugger_protocol;
|
||||
|
||||
pub use hardware::hardware::Hardware;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
mod virtual_machine;
|
||||
mod value;
|
||||
mod call_frame;
|
||||
mod scope_frame;
|
||||
mod program;
|
||||
@ -12,7 +11,7 @@ use crate::hardware::HardwareBridge;
|
||||
pub use program::ProgramImage;
|
||||
pub use prometeu_bytecode::abi::TrapInfo;
|
||||
pub use prometeu_bytecode::opcode::OpCode;
|
||||
pub use value::Value;
|
||||
pub use prometeu_abi::virtual_machine::Value;
|
||||
pub use verifier::VerifierError;
|
||||
pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::hardware::HardwareBridge;
|
||||
use crate::virtual_machine::call_frame::CallFrame;
|
||||
use crate::virtual_machine::scope_frame::ScopeFrame;
|
||||
use crate::virtual_machine::value::Value;
|
||||
use crate::virtual_machine::Value;
|
||||
use crate::virtual_machine::{NativeInterface, ProgramImage, VmInitError};
|
||||
use prometeu_bytecode::abi::{TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_OOB, TRAP_TYPE};
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
|
||||
@ -405,38 +405,6 @@ pub struct AnalysisDb {
|
||||
|
||||
---
|
||||
|
||||
## PR-00.1 — Introduzir `prometeu-abi` (renomear/diluir `prometeu-core`)
|
||||
|
||||
**Branch:** `pr-00-1-prometeu-abi`
|
||||
|
||||
### Objetivo
|
||||
|
||||
Criar crate `prometeu-abi` contendo **somente tipos e contratos** (types+model+protocols), e manter `prometeu-core` temporariamente como facade/reexport para não quebrar consumidores.
|
||||
|
||||
### Passos prescritivos
|
||||
|
||||
1. Criar novo crate `crates/prometeu-abi/` com `src/lib.rs`.
|
||||
2. Copiar para `prometeu-abi` (sem execução):
|
||||
|
||||
* `model/*`
|
||||
* `debugger_protocol.rs`
|
||||
* `telemetry.rs`
|
||||
* `log.rs` (somente tipos/config; se tiver runtime logger, deixar no core por enquanto)
|
||||
* **traits/contratos** usados por outros crates (ex.: IDs, enums de eventos)
|
||||
3. Atualizar dependências dos crates consumidores para apontar para `prometeu-abi` onde aplicável.
|
||||
4. Manter `prometeu-core` compilando com:
|
||||
|
||||
* `pub use prometeu_abi as abi;`
|
||||
* reexports temporários de tipos que mudaram de path.
|
||||
|
||||
### Critérios de aceite
|
||||
|
||||
* Workspace compila.
|
||||
* Nenhum comportamento muda.
|
||||
* `prometeu-core` ainda existe (temporário).
|
||||
|
||||
---
|
||||
|
||||
## PR-00.2 — Extrair `prometeu-vm` do `prometeu-core`
|
||||
|
||||
**Branch:** `pr-00-2-prometeu-vm`
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user