diff --git a/assets/.gitignore b/assets/.gitignore new file mode 100644 index 0000000000..475bc4d296 --- /dev/null +++ b/assets/.gitignore @@ -0,0 +1 @@ +plugins/* diff --git a/common/state/src/plugin/memory_manager.rs b/common/state/src/plugin/memory_manager.rs index a76ba45382..b21f819090 100644 --- a/common/state/src/plugin/memory_manager.rs +++ b/common/state/src/plugin/memory_manager.rs @@ -228,13 +228,19 @@ pub fn read_data( position: u64, length: u64, ) -> Result { - bincode::deserialize(&read_bytes(memory, position, length)) + bincode::deserialize( + &read_bytes(memory, position, length).ok_or(bincode::ErrorKind::SizeLimit)?, + ) } /// This function read raw bytes from memory at a position with the array length -pub fn read_bytes(memory: &Memory, position: u64, length: u64) -> Vec { - memory.view()[(position as usize)..(position as usize) + length as usize] - .iter() - .map(|x| x.get()) - .collect() +pub fn read_bytes(memory: &Memory, position: u64, length: u64) -> Option> { + Some( + memory + .view() + .get((position as usize)..(position as usize) + length as usize)? + .iter() + .map(|x| x.get()) + .collect(), + ) } diff --git a/common/state/src/plugin/module.rs b/common/state/src/plugin/module.rs index 4adba15e63..a55f155ce2 100644 --- a/common/state/src/plugin/module.rs +++ b/common/state/src/plugin/module.rs @@ -1,5 +1,6 @@ +use hashbrown::HashSet; use std::{ - collections::HashSet, + borrow::Cow, convert::TryInto, marker::PhantomData, sync::{Arc, Mutex}, @@ -11,7 +12,7 @@ use wasmer::{imports, Cranelift, Function, Instance, Memory, Module, Store, Valu use super::{ errors::{PluginError, PluginModuleError}, memory_manager::{self, EcsAccessManager, EcsWorld, MemoryManager}, - wasm_env::HostFunctionEnvironement, + wasm_env::HostFunctionEnvironment, }; use plugin_api::{Action, EcsAccessError, Event, Retrieve, RetrieveError, RetrieveResult}; @@ -39,7 +40,7 @@ impl PluginModule { let module = Module::new(&store, &wasm_data).expect("Can't compile"); // This is the function imported into the wasm environement - fn raw_emit_actions(env: &HostFunctionEnvironement, ptr: i64, len: i64) { + fn raw_emit_actions(env: &HostFunctionEnvironment, ptr: i64, len: i64) { handle_actions(match env.read_data(from_i64(ptr), from_i64(len)) { Ok(e) => e, Err(e) => { @@ -49,7 +50,7 @@ impl PluginModule { }); } - fn raw_retrieve_action(env: &HostFunctionEnvironement, ptr: i64, len: i64) -> i64 { + fn raw_retrieve_action(env: &HostFunctionEnvironment, ptr: i64, len: i64) -> i64 { let out = match env.read_data(from_i64(ptr), from_i64(len)) { Ok(data) => retrieve_action(&env.ecs, data), Err(e) => Err(RetrieveError::BincodeError(e.to_string())), @@ -60,8 +61,15 @@ impl PluginModule { to_i64(env.write_data_as_pointer(&out).unwrap()) } - fn dbg(a: i32) { - println!("WASM DEBUG: {}", a); + fn raw_print(env: &HostFunctionEnvironment, ptr: i64, len: i64) { + if let Some(msg) = env + .read_bytes(from_i64(ptr), from_i64(len)) + .and_then(|bytes| String::from_utf8(bytes).ok()) + { + tracing::info!("[{}] {}", env.name, msg); + } else { + tracing::error!("Logging message from plugin {} failed!", env.name); + } } let ecs = Arc::new(EcsAccessManager::default()); @@ -70,9 +78,9 @@ impl PluginModule { // Create an import object. let import_object = imports! { "env" => { - "raw_emit_actions" => Function::new_native_with_env(&store, HostFunctionEnvironement::new(name.clone(), ecs.clone(),memory_manager.clone()), raw_emit_actions), - "raw_retrieve_action" => Function::new_native_with_env(&store, HostFunctionEnvironement::new(name.clone(), ecs.clone(),memory_manager.clone()), raw_retrieve_action), - "dbg" => Function::new_native(&store, dbg), + "raw_emit_actions" => Function::new_native_with_env(&store, HostFunctionEnvironment::new(name.clone(), ecs.clone(), memory_manager.clone()), raw_emit_actions), + "raw_retrieve_action" => Function::new_native_with_env(&store, HostFunctionEnvironment::new(name.clone(), ecs.clone(), memory_manager.clone()), raw_retrieve_action), + "raw_print" => Function::new_native_with_env(&store, HostFunctionEnvironment::new(name.clone(), ecs.clone(), memory_manager.clone()), raw_print), } }; @@ -112,7 +120,7 @@ impl PluginModule { where T: Event, { - if !self.events.contains(&request.function_name) { + if !self.events.contains(request.function_name.as_ref()) { return None; } // Store the ECS Pointer for later use in `retreives` @@ -131,7 +139,7 @@ impl PluginModule { /// reencoding for each module in every plugin) pub struct PreparedEventQuery { bytes: Vec, - function_name: String, + function_name: Cow<'static, str>, _phantom: PhantomData, } @@ -214,17 +222,20 @@ fn execute_raw( .ok_or_else(PluginModuleError::InvalidArgumentType)?, ); - let bytes = memory_manager::read_bytes(&module.memory, u128_pointer, 16); + // TODO: Codify error + let bytes = memory_manager::read_bytes(&module.memory, u128_pointer, 16).unwrap(); // We read the return object and deserialize it // The first 8 bytes are encoded as le and represent the pointer to the data // The next 8 bytes are encoded as le and represent the length of the data + // TODO: Codify error Ok(memory_manager::read_bytes( &module.memory, u64::from_le_bytes(bytes[0..8].try_into().unwrap()), u64::from_le_bytes(bytes[8..16].try_into().unwrap()), - )) + ) + .unwrap()) } fn retrieve_action( diff --git a/common/state/src/plugin/wasm_env.rs b/common/state/src/plugin/wasm_env.rs index bcd5ec3d55..c64955d58e 100644 --- a/common/state/src/plugin/wasm_env.rs +++ b/common/state/src/plugin/wasm_env.rs @@ -9,7 +9,7 @@ use super::{ }; #[derive(Clone)] -pub struct HostFunctionEnvironement { +pub struct HostFunctionEnvironment { pub ecs: Arc, /* This represent the pointer to the ECS object (set to * i32::MAX if to ECS is * availible) */ @@ -20,7 +20,7 @@ pub struct HostFunctionEnvironement { pub name: String, // This represent the plugin name } -impl HostFunctionEnvironement { +impl HostFunctionEnvironment { pub fn new( name: String, ecs: Arc, @@ -67,9 +67,15 @@ impl HostFunctionEnvironement { ) -> Result { memory_manager::read_data(self.memory.get_ref().unwrap(), position, length) } + + /// This function is a safe interface to WASM memory that reads memory from + /// pointer and length returning an object + pub fn read_bytes(&self, position: u64, length: u64) -> Option> { + memory_manager::read_bytes(self.memory.get_ref().unwrap(), position, length) + } } -impl WasmerEnv for HostFunctionEnvironement { +impl WasmerEnv for HostFunctionEnvironment { fn init_with_instance(&mut self, instance: &Instance) -> Result<(), HostEnvInitError> { let memory = instance.exports.get_memory("memory").unwrap(); self.memory.initialize(memory.clone()); diff --git a/plugin/api/src/lib.rs b/plugin/api/src/lib.rs index 3794310313..2f871b86f2 100644 --- a/plugin/api/src/lib.rs +++ b/plugin/api/src/lib.rs @@ -1,9 +1,8 @@ -pub extern crate common; +//#![deny(missing_docs)] -pub use common::comp::Health; +pub use common::{comp::Health, resources::GameMode, uid::Uid}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -pub use common::{resources::GameMode, uid::Uid}; +use std::borrow::Cow; mod errors; @@ -97,7 +96,7 @@ pub enum RetrieveResult { pub trait Event: Serialize + DeserializeOwned + Send + Sync { type Response: Serialize + DeserializeOwned + Send + Sync; - fn get_event_name(&self) -> String; + fn get_event_name(&self) -> Cow<'static, str>; } /// This module contains all events from the api @@ -141,7 +140,9 @@ pub mod event { impl Event for ChatCommandEvent { type Response = Result, String>; - fn get_event_name(&self) -> String { format!("on_command_{}", self.command) } + fn get_event_name(&self) -> Cow<'static, str> { + Cow::Owned(format!("on_command_{}", self.command)) + } } /// This struct represent a player @@ -172,7 +173,7 @@ pub mod event { impl Event for PlayerJoinEvent { type Response = PlayerJoinResult; - fn get_event_name(&self) -> String { "on_join".to_owned() } + fn get_event_name(&self) -> Cow<'static, str> { Cow::Borrowed("on_join") } } /// This is the return type of an `on_join` event. See [`PlayerJoinEvent`] @@ -213,7 +214,7 @@ pub mod event { impl Event for PluginLoadEvent { type Response = (); - fn get_event_name(&self) -> String { "on_load".to_owned() } + fn get_event_name(&self) -> Cow<'static, str> { Cow::Borrowed("on_load") } } // impl Default for PlayerJoinResult { diff --git a/plugin/rt/src/lib.rs b/plugin/rt/src/lib.rs index 30d37635e3..c73f181876 100644 --- a/plugin/rt/src/lib.rs +++ b/plugin/rt/src/lib.rs @@ -1,24 +1,35 @@ -pub extern crate plugin_derive; +extern crate plugin_derive; pub mod retrieve; -use api::RetrieveError; -pub use retrieve::*; - -use std::convert::TryInto; - -pub use retrieve::*; - pub use plugin_api as api; -pub use plugin_derive::*; +pub use plugin_derive::{event_handler, global_state}; +pub use retrieve::*; +use api::RetrieveError; use serde::{de::DeserializeOwned, Serialize}; +use std::{convert::TryInto, marker::PhantomData}; + +pub struct Game { + phantom: PhantomData<()>, +} + +impl Game { + /// This is not strictly unsafe today, but it may become unsafe in the + /// future. No safety guarantees are listed because we don't intend this + /// to ever be manually called. Do not use this function! + pub unsafe fn __new() -> Self { + Self { + phantom: PhantomData, + } + } +} #[cfg(target_arch = "wasm32")] extern "C" { fn raw_emit_actions(ptr: i64, len: i64); fn raw_retrieve_action(ptr: i64, len: i64) -> i64; - pub fn dbg(i: i32); + fn raw_print(ptr: i64, len: i64); } pub fn retrieve_action(_actions: &api::Retrieve) -> Result { @@ -51,6 +62,19 @@ pub fn emit_actions(_actions: Vec) { } } +pub fn print_str(s: &str) { + let bytes = s.as_bytes(); + unsafe { + // Safety: ptr and len are valid for byte slice + raw_print(to_i64(bytes.as_ptr() as _), to_i64(bytes.len() as _)); + } +} + +#[macro_export] +macro_rules! log { + ($($x:tt)*) => { $crate::print_str(&format!($($x)*)) }; +} + pub fn read_input(ptr: i64, len: i64) -> Result where T: DeserializeOwned,