use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EngineCommand {
Log {
message: String,
},
SpawnPrimitive {
primitive: Primitive,
x: f32,
y: f32,
z: f32,
request_id: u64,
},
DespawnEntity {
entity_id: u64,
},
SetEntityPosition {
entity_id: u64,
x: f32,
y: f32,
z: f32,
},
SetEntityScale {
entity_id: u64,
x: f32,
y: f32,
z: f32,
},
SetEntityRotation {
entity_id: u64,
x: f32,
y: f32,
z: f32,
w: f32,
},
GetEntityPosition {
entity_id: u64,
request_id: u64,
},
GetEntityScale {
entity_id: u64,
request_id: u64,
},
GetEntityRotation {
entity_id: u64,
request_id: u64,
},
ReadFile {
path: String,
request_id: u64,
},
LoadTexture {
path: String,
request_id: u64,
},
LoadPrefab {
path: String,
x: f32,
y: f32,
z: f32,
request_id: u64,
},
SetEntityMaterial {
entity_id: u64,
texture_id: u64,
},
SetEntityColor {
entity_id: u64,
r: f32,
g: f32,
b: f32,
a: f32,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EngineEvent {
KeyPressed {
key_code: u32,
},
KeyReleased {
key_code: u32,
},
MouseMoved {
x: f32,
y: f32,
},
MouseButtonPressed {
button: u32,
},
MouseButtonReleased {
button: u32,
},
EntitySpawned {
request_id: u64,
entity_id: u64,
},
EntityPosition {
request_id: u64,
entity_id: u64,
x: f32,
y: f32,
z: f32,
},
EntityScale {
request_id: u64,
entity_id: u64,
x: f32,
y: f32,
z: f32,
},
EntityRotation {
request_id: u64,
entity_id: u64,
x: f32,
y: f32,
z: f32,
w: f32,
},
EntityNotFound {
request_id: u64,
entity_id: u64,
},
FrameStart {
delta_time: f32,
frame_count: u64,
},
FileLoaded {
request_id: u64,
data: Vec<u8>,
},
FileError {
request_id: u64,
error: String,
},
TextureLoaded {
request_id: u64,
texture_id: u64,
},
PrefabLoaded {
request_id: u64,
entity_id: u64,
},
AssetError {
request_id: u64,
error: String,
},
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[repr(u8)]
pub enum Primitive {
Cube = 1,
Sphere = 2,
Cylinder = 3,
Plane = 4,
Cone = 5,
}
impl EngineCommand {
pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
postcard::to_allocvec(self)
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
postcard::from_bytes(bytes).ok()
}
}
impl EngineEvent {
pub fn to_bytes(&self) -> Result<Vec<u8>, postcard::Error> {
postcard::to_allocvec(self)
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
postcard::from_bytes(bytes).ok()
}
}
#[cfg(all(feature = "plugins", target_arch = "wasm32"))]
mod guest {
use super::{EngineCommand, EngineEvent, Primitive};
use std::cell::RefCell;
use std::sync::atomic::{AtomicU64, Ordering};
unsafe extern "C" {
fn host_send_command(ptr: *const u8, len: u32);
}
static REQUEST_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
thread_local! {
static EVENT_BUFFER: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
static PENDING_EVENTS: RefCell<Vec<EngineEvent>> = const { RefCell::new(Vec::new()) };
}
pub fn next_request_id() -> u64 {
REQUEST_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
}
pub fn send_engine_command(command: EngineCommand) {
let Ok(bytes) = command.to_bytes() else {
return;
};
unsafe {
host_send_command(bytes.as_ptr(), bytes.len() as u32);
}
}
pub fn log(message: &str) {
send_engine_command(EngineCommand::Log {
message: message.to_string(),
});
}
pub fn spawn_primitive(primitive: Primitive, x: f32, y: f32, z: f32) -> u64 {
let request_id = next_request_id();
send_engine_command(EngineCommand::SpawnPrimitive {
primitive,
x,
y,
z,
request_id,
});
request_id
}
pub fn spawn_cube(x: f32, y: f32, z: f32) -> u64 {
spawn_primitive(Primitive::Cube, x, y, z)
}
pub fn spawn_sphere(x: f32, y: f32, z: f32) -> u64 {
spawn_primitive(Primitive::Sphere, x, y, z)
}
pub fn spawn_cylinder(x: f32, y: f32, z: f32) -> u64 {
spawn_primitive(Primitive::Cylinder, x, y, z)
}
pub fn spawn_plane(x: f32, y: f32, z: f32) -> u64 {
spawn_primitive(Primitive::Plane, x, y, z)
}
pub fn spawn_cone(x: f32, y: f32, z: f32) -> u64 {
spawn_primitive(Primitive::Cone, x, y, z)
}
pub fn despawn_entity(entity_id: u64) {
send_engine_command(EngineCommand::DespawnEntity { entity_id });
}
pub fn set_entity_position(entity_id: u64, x: f32, y: f32, z: f32) {
send_engine_command(EngineCommand::SetEntityPosition { entity_id, x, y, z });
}
pub fn set_entity_scale(entity_id: u64, x: f32, y: f32, z: f32) {
send_engine_command(EngineCommand::SetEntityScale { entity_id, x, y, z });
}
pub fn set_entity_rotation(entity_id: u64, x: f32, y: f32, z: f32, w: f32) {
send_engine_command(EngineCommand::SetEntityRotation {
entity_id,
x,
y,
z,
w,
});
}
pub fn get_entity_position(entity_id: u64) -> u64 {
let request_id = next_request_id();
send_engine_command(EngineCommand::GetEntityPosition {
entity_id,
request_id,
});
request_id
}
pub fn get_entity_scale(entity_id: u64) -> u64 {
let request_id = next_request_id();
send_engine_command(EngineCommand::GetEntityScale {
entity_id,
request_id,
});
request_id
}
pub fn get_entity_rotation(entity_id: u64) -> u64 {
let request_id = next_request_id();
send_engine_command(EngineCommand::GetEntityRotation {
entity_id,
request_id,
});
request_id
}
pub fn read_file(path: &str) -> u64 {
let request_id = next_request_id();
send_engine_command(EngineCommand::ReadFile {
path: path.to_string(),
request_id,
});
request_id
}
pub fn load_texture(path: &str) -> u64 {
let request_id = next_request_id();
send_engine_command(EngineCommand::LoadTexture {
path: path.to_string(),
request_id,
});
request_id
}
pub fn load_prefab(path: &str, x: f32, y: f32, z: f32) -> u64 {
let request_id = next_request_id();
send_engine_command(EngineCommand::LoadPrefab {
path: path.to_string(),
x,
y,
z,
request_id,
});
request_id
}
pub fn set_entity_material(entity_id: u64, texture_id: u64) {
send_engine_command(EngineCommand::SetEntityMaterial {
entity_id,
texture_id,
});
}
pub fn set_entity_color(entity_id: u64, r: f32, g: f32, b: f32, a: f32) {
send_engine_command(EngineCommand::SetEntityColor {
entity_id,
r,
g,
b,
a,
});
}
#[unsafe(no_mangle)]
pub extern "C" fn plugin_alloc(size: u32) -> *mut u8 {
EVENT_BUFFER.with_borrow_mut(|buffer| {
buffer.resize(size as usize, 0);
buffer.as_mut_ptr()
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn plugin_receive_event(ptr: *const u8, len: u32) {
let bytes = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
if let Some(event) = EngineEvent::from_bytes(bytes) {
PENDING_EVENTS.with_borrow_mut(|events| {
events.push(event);
});
}
}
pub fn drain_engine_events() -> Vec<EngineEvent> {
PENDING_EVENTS.with_borrow_mut(std::mem::take)
}
}
#[cfg(all(feature = "plugins", target_arch = "wasm32"))]
pub use guest::*;