Moved plugin API to Uid instead of usize for entity IDs, hello plugin to an example

This commit is contained in:
Joshua Barretto 2020-12-13 23:08:15 +00:00
parent 023888f560
commit fc96fd780b
13 changed files with 18 additions and 1621 deletions

View File

@ -3,11 +3,12 @@ cargo-features = ["named-profiles","profile-overrides"]
[workspace]
members = [
"common",
"common/net",
"common/sys",
"plugin/rt",
"client",
"plugin/api",
"plugin/derive",
"client",
"plugin/rt",
"server",
"server-cli",
"voxygen",

BIN
assets/plugins/hello.plugin.tar (Stored with Git LFS)

Binary file not shown.

View File

@ -1,21 +0,0 @@
use bincode::ErrorKind;
use wasmer_runtime::error::{ResolveError, RuntimeError};
#[derive(Debug)]
pub enum PluginError {
Io(std::io::Error),
Toml(toml::de::Error),
NoConfig,
NoSuchModule,
PluginModuleError(PluginModuleError)
}
#[derive(Debug)]
pub enum PluginModuleError {
FindFunction(String),
FunctionGet(ResolveError),
Compile(wasmer_runtime::error::CompileError),
Instantiate(wasmer_runtime::error::Error),
RunFunction(RuntimeError),
Encoding(Box<ErrorKind>),
}

View File

@ -1,138 +0,0 @@
pub mod errors;
pub mod module;
use common::assets::ASSETS_PATH;
use serde::{Deserialize, Serialize};
use std::{collections::{HashMap, HashSet}, fs, io::Read, path::{Path, PathBuf}};
use tracing::{error, info};
use plugin_api::Event;
use self::{ errors::PluginError, module::{PluginModule, PreparedEventQuery}};
use rayon::prelude::*;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PluginData {
name: String,
modules: HashSet<PathBuf>,
dependencies: HashSet<String>,
}
#[derive(Clone)]
pub struct Plugin {
data: PluginData,
modules: Vec<PluginModule>,
files: HashMap<PathBuf, Vec<u8>>,
}
impl Plugin {
pub fn from_reader<R: Read>(mut reader: R) -> Result<Self, PluginError> {
let mut buf = Vec::new();
reader.read_to_end(&mut buf).map_err(PluginError::Io)?;
let mut files = tar::Archive::new(&*buf)
.entries()
.map_err(PluginError::Io)?
.map(|e| {
e.and_then(|e| {
Ok((e.path()?.into_owned(), {
let offset = e.raw_file_position() as usize;
buf[offset..offset + e.size() as usize].to_vec()
}))
})
})
.collect::<Result<HashMap<_, _>, _>>()
.map_err(PluginError::Io)?;
let data = toml::de::from_slice::<PluginData>(
&files
.get(Path::new("plugin.toml"))
.ok_or(PluginError::NoConfig)?,
)
.map_err(PluginError::Toml)?;
let modules = data
.modules
.iter()
.map(|path| {
let wasm_data = files.remove(path).ok_or(PluginError::NoSuchModule)?;
PluginModule::new(&wasm_data).map_err(|e| PluginError::PluginModuleError(e))
})
.collect::<Result<_, _>>()?;
Ok(Plugin {
data,
modules,
files,
})
}
pub fn execute_prepared<T>(&self, event_name: &str, event: &PreparedEventQuery<T>) -> Result<Vec<T::Response>, PluginError> where T: Event {
self.modules.iter().flat_map(|module| {
module.try_execute(event_name, event).map(|x| x.map_err(|e| PluginError::PluginModuleError(e)))
}).collect::<Result<Vec<_>,_>>()
}
}
#[derive(Clone, Default)]
pub struct PluginMgr {
plugins: Vec<Plugin>,
}
impl PluginMgr {
pub fn from_assets() -> Result<Self, PluginError> {
let mut assets_path = (&*ASSETS_PATH).clone();
assets_path.push("plugins");
info!("Searching {:?} for assets...", assets_path);
Self::from_dir(assets_path)
}
pub fn execute_prepared<T>(&self, event_name: &str,event: &PreparedEventQuery<T>) -> Result<Vec<T::Response>, PluginError> where T: Event {
Ok(self.plugins.par_iter().map(|plugin| {
plugin.execute_prepared(event_name, event)
}).collect::<Result<Vec<_>,_>>()?.into_iter().flatten().collect())
}
pub fn execute_event<T>(&self, event_name: &str,event: &T) -> Result<Vec<T::Response>, PluginError> where T: Event {
self.execute_prepared(event_name, &PreparedEventQuery::new(event)?)
}
pub fn from_dir<P: AsRef<Path>>(path: P) -> Result<Self, PluginError> {
let plugins = fs::read_dir(path)
.map_err(PluginError::Io)?
.filter_map(|e| e.ok())
.map(|entry| {
if entry.file_type().map(|ft| ft.is_file()).unwrap_or(false)
&& entry
.path()
.file_name()
.and_then(|n| n.to_str())
.map(|s| s.ends_with(".plugin.tar"))
.unwrap_or(false)
{
info!("Loading plugin at {:?}", entry.path());
Plugin::from_reader(fs::File::open(entry.path()).map_err(PluginError::Io)?)
.map(Some)
} else {
Ok(None)
}
})
.filter_map(Result::transpose)
.inspect(|p| {
let _ = p.as_ref().map_err(|e| error!(?e, "Failed to load plugin"));
})
.collect::<Result<Vec<_>, _>>()?;
for plugin in &plugins {
info!(
"Loaded plugin '{}' with {} module(s)",
plugin.data.name,
plugin.modules.len()
);
}
Ok(Self { plugins })
}
}

View File

@ -1,150 +0,0 @@
use std::{
cell::Cell,
collections::HashSet,
marker::PhantomData,
sync::{Arc, Mutex},
};
use error::RuntimeError;
use wasmer_runtime::*;
use super::errors::{PluginError, PluginModuleError};
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 {
wasm_instance: Arc<Mutex<Instance>>,
events: HashSet<String>,
}
impl PluginModule {
// This function takes bytes from a WASM File and compile them
pub fn new(wasm_data: &Vec<u8>) -> Result<Self,PluginModuleError> {
let module = compile(&wasm_data).map_err(|e| PluginModuleError::Compile(e))?;
let instance = module
.instantiate(&imports! {"env" => {
"raw_emit_actions" => func!(read_action),
}}).map_err(|e| PluginModuleError::Instantiate(e))?;
Ok(Self {
events: instance.exports.into_iter().map(|(name, _)| name).collect(),
wasm_instance: Arc::new(Mutex::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 = {
let instance = self.wasm_instance.lock().unwrap();
let func = match instance.exports.get(event_name).map_err(|e| PluginModuleError::FunctionGet(e)) {
Ok(e) => e,
Err(e) => return Some(Err(e))
};
let mem = instance.context().memory(0);
match execute_raw(&mem, &func, &request.bytes).map_err(|e| PluginModuleError::RunFunction(e)) {
Ok(e) => e,
Err(e) => return Some(Err(e))
}
};
Some(bincode::deserialize(&bytes).map_err(|e| PluginModuleError::Encoding(e)))
}
}
// 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(),
})
}
}
const MEMORY_POS: usize = 100000;
// This function is not public because this function should not be used without an interface to limit unsafe behaviours
fn execute_raw(
memory: &Memory,
function: &Function,
bytes: &[u8],
) -> Result<Vec<u8>, RuntimeError> {
let view = memory.view::<u8>();
let len = bytes.len();
for (cell, byte) in view[MEMORY_POS..len + MEMORY_POS].iter().zip(bytes.iter()) {
cell.set(*byte)
}
let start = function
.call(MEMORY_POS as i32, len as u32)? as usize;
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);
}
}
}
}

View File

@ -1,5 +1,3 @@
#![allow(clippy::eval_order_dependence)]
use crate::commands::{Command, FileInfo, LocalCommand, RemoteInfo};
use async_std::{
fs,

View File

@ -5,8 +5,8 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub enum Action {
ServerClose,
Print(String),
PlayerSendMessage(usize, String),
KillEntity(usize),
PlayerSendMessage(Uid, String),
KillEntity(Uid),
}
pub trait Event: Serialize + DeserializeOwned + Send + Sync {
@ -38,7 +38,7 @@ pub mod event {
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct PlayerJoinEvent {
pub player_name: String,
pub player_id: usize,
pub player_id: Uid,
}
impl Event for PlayerJoinEvent {

View File

@ -16,16 +16,16 @@ pub fn event_handler(_args: TokenStream, item: TokenStream) -> TokenStream {
let out: proc_macro2::TokenStream = quote! {
#[no_mangle]
pub fn #fn_name(intern__ptr: i32, intern__len: u32) -> i32 {
let input = ::plugin_rt::read_input(intern__ptr,intern__len).unwrap();
let input = ::veloren_plugin_rt::read_input(intern__ptr,intern__len).unwrap();
#[inline]
fn inner(#fn_args) #fn_return {
#fn_body
}
// Artificially force the event handler to be type-correct
fn force_event<E: ::plugin_rt::api::Event>(event: E, inner: fn(E) -> E::Response) -> E::Response {
fn force_event<E: ::veloren_plugin_rt::api::Event>(event: E, inner: fn(E) -> E::Response) -> E::Response {
inner(event)
}
::plugin_rt::write_output(&force_event(input, inner))
::veloren_plugin_rt::write_output(&force_event(input, inner))
}
};
out.into()

View File

@ -1,2 +0,0 @@
# Rust
target

1276
plugin/hello/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
[package]
name = "hello"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
plugin-rt = { package = "veloren-plugin-rt", path = "../rt" }
[workspace]
[profile.dev]
opt-level = "s"
debug = false

View File

@ -9,3 +9,10 @@ plugin-api = { package = "veloren-plugin-api", path = "../api" }
plugin-derive = { package = "veloren-plugin-derive", path = "../derive"}
serde = {version = "1.0.118", features = ["derive"]}
bincode = "1.3.1"
[[example]]
name = "hello"
crate-type = ["cdylib"]
[dev-dependencies]
plugin-derive = { package = "veloren-plugin-derive", path = "../derive"}

View File

@ -1,11 +1,9 @@
extern crate plugin_rt;
use plugin_rt::{
use veloren_plugin_rt::{
api::{event::*, Action, GameMode},
*,
};
#[event_handler]
#[veloren_plugin_rt::event_handler]
pub fn on_load(load: PluginLoadEvent) {
match load.game_mode {
GameMode::Server => emit_action(Action::Print("Hello, server!".to_owned())),