veloren/common/sys/src/plugin/module.rs

205 lines
6.1 KiB
Rust
Raw Normal View History

use std::{
cell::Cell,
collections::HashSet,
marker::PhantomData,
sync::{Arc, Mutex},
};
use error::RuntimeError;
use wasmer_runtime::*;
2020-12-14 16:07:05 +00:00
use super::errors::{MemoryAllocationError, PluginError, PluginModuleError};
2020-12-12 13:01:54 +00:00
use plugin_api::{Action, Event};
// This represent a WASM function interface
pub type Function<'a> = Func<'a, (i32, u32), i32>;
#[derive(Clone)]
// This structure represent the WASM State of the plugin.
pub struct PluginModule {
2020-12-15 15:04:55 +00:00
wasm_state: Arc<Mutex<WasmState>>,
events: HashSet<String>,
}
impl PluginModule {
// This function takes bytes from a WASM File and compile them
pub fn new(wasm_data: &[u8]) -> Result<Self, PluginModuleError> {
let module = compile(&wasm_data).map_err(PluginModuleError::Compile)?;
let instance = module
.instantiate(&imports! {"env" => {
2020-12-12 19:26:42 +00:00
"raw_emit_actions" => func!(read_action),
}})
.map_err(PluginModuleError::Instantiate)?;
Ok(Self {
events: instance.exports.into_iter().map(|(name, _)| name).collect(),
2020-12-15 15:04:55 +00:00
wasm_state: Arc::new(Mutex::new(WasmState::new(instance))),
})
}
// This function tries to execute an event for the current module. Will return
// None if the event doesn't exists
pub fn try_execute<T>(
&self,
event_name: &str,
request: &PreparedEventQuery<T>,
) -> Option<Result<T::Response, PluginModuleError>>
where
T: Event,
{
if !self.events.contains(event_name) {
return None;
}
let bytes = {
2020-12-15 15:04:55 +00:00
let mut state = self.wasm_state.lock().unwrap();
match execute_raw(&mut state, event_name, &request.bytes)
2020-12-14 17:46:04 +00:00
.map_err(PluginModuleError::RunFunction)
{
Ok(e) => e,
Err(e) => return Some(Err(e)),
}
};
Some(bincode::deserialize(&bytes).map_err(PluginModuleError::Encoding))
}
}
2020-12-15 15:04:55 +00:00
pub struct WasmMemoryContext {
memory_buffer_size: usize,
memory_pointer: i32,
}
pub struct WasmState {
instance: Instance,
memory: WasmMemoryContext,
}
impl WasmState {
fn new(instance: Instance) -> Self {
Self {
instance,
memory: WasmMemoryContext {
memory_buffer_size: 0,
memory_pointer: 0,
},
}
}
}
// This structure represent a Pre-encoded event object (Useful to avoid
// reencoding for each module in every plugin)
pub struct PreparedEventQuery<T> {
bytes: Vec<u8>,
_phantom: PhantomData<T>,
}
impl<T: Event> PreparedEventQuery<T> {
// Create a prepared query from an event reference (Encode to bytes the struct)
// This Prepared Query is used by the `try_execute` method in `PluginModule`
pub fn new(event: &T) -> Result<Self, PluginError>
where
T: Event,
{
Ok(Self {
bytes: bincode::serialize(&event)
.map_err(|e| PluginError::PluginModuleError(PluginModuleError::Encoding(e)))?,
_phantom: PhantomData::default(),
})
}
}
// This function is not public because this function should not be used without
// an interface to limit unsafe behaviours
#[allow(clippy::needless_range_loop)]
fn execute_raw(
2020-12-15 15:04:55 +00:00
context: &mut WasmState,
event_name: &str,
bytes: &[u8],
) -> Result<Vec<u8>, RuntimeError> {
2020-12-14 16:07:05 +00:00
// This reserves space for the buffer
let len = bytes.len();
2020-12-14 16:07:05 +00:00
let start = {
2020-12-15 15:04:55 +00:00
let memory_pos = reserve_wasm_memory_buffer(len, &context.instance, &mut context.memory)
2020-12-14 17:46:04 +00:00
.expect("Fatal error while allocating memory for a plugin! Closing server...")
as usize;
2020-12-15 15:04:55 +00:00
let function: Func<(i32, u32), i32> = context
.instance
.exports
.get(event_name)
.expect("Function not found this should never happen");
let memory = context.instance.context().memory(0);
2020-12-14 16:07:05 +00:00
let view = memory.view::<u8>();
2020-12-14 17:46:04 +00:00
for (cell, byte) in view[memory_pos..memory_pos + len].iter().zip(bytes.iter()) {
2020-12-14 16:07:05 +00:00
cell.set(*byte)
}
function.call(memory_pos as i32, len as u32)? as usize
};
2020-12-14 17:46:04 +00:00
2020-12-15 15:04:55 +00:00
let memory = context.instance.context().memory(0);
let view = memory.view::<u8>();
let mut new_len_bytes = [0u8; 4];
// TODO: It is probably better to dirrectly make the new_len_bytes
for i in 0..4 {
new_len_bytes[i] = view.get(i + 1).map(Cell::get).unwrap_or(0);
}
let new_len = u32::from_ne_bytes(new_len_bytes) as usize;
Ok(view[start..start + new_len]
.iter()
.map(|c| c.get())
.collect())
}
pub fn read_action(ctx: &mut Ctx, ptr: u32, len: u32) {
let memory = ctx.memory(0);
let memory = memory.view::<u8>();
let str_slice = &memory[ptr as usize..(ptr + len) as usize];
let bytes: Vec<u8> = str_slice.iter().map(|x| x.get()).collect();
let e: Vec<Action> = match bincode::deserialize(&bytes) {
Ok(e) => e,
Err(e) => {
tracing::error!(?e, "Can't decode action");
return;
},
};
for action in e {
match action {
Action::ServerClose => {
tracing::info!("Server closed by plugin");
std::process::exit(-1);
},
Action::Print(e) => {
tracing::info!("{}", e);
},
Action::PlayerSendMessage(a, b) => {
tracing::info!("SendMessage {} -> {}", a, b);
},
Action::KillEntity(e) => {
tracing::info!("Kill Entity {}", e);
},
}
}
2020-12-12 13:01:54 +00:00
}
2020-12-14 16:07:05 +00:00
fn reserve_wasm_memory_buffer<'a>(
value: usize,
instance: &'a Instance,
2020-12-15 15:04:55 +00:00
context: &mut WasmMemoryContext,
2020-12-14 16:07:05 +00:00
) -> Result<i32, MemoryAllocationError> {
2020-12-15 15:04:55 +00:00
if context.memory_buffer_size >= value {
return Ok(context.memory_pointer);
}
let pointer = instance
2020-12-14 16:07:05 +00:00
.exports
.get::<Func<'a, i32, i32>>("wasm_prepare_buffer")
2020-12-14 17:31:01 +00:00
.map_err(MemoryAllocationError::AllocatorNotFound)?
2020-12-14 16:07:05 +00:00
.call(value as i32)
2020-12-15 15:04:55 +00:00
.map_err(MemoryAllocationError::CantAllocate)?;
context.memory_buffer_size = value;
context.memory_pointer = pointer;
Ok(pointer)
2020-12-14 17:46:04 +00:00
}