mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Moved plugin API to Uid instead of usize for entity IDs, hello plugin to an example
This commit is contained in:
parent
023888f560
commit
fc96fd780b
@ -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)
BIN
assets/plugins/hello.plugin.tar
(Stored with Git LFS)
Binary file not shown.
@ -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>),
|
||||
}
|
@ -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 })
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
#![allow(clippy::eval_order_dependence)]
|
||||
|
||||
use crate::commands::{Command, FileInfo, LocalCommand, RemoteInfo};
|
||||
use async_std::{
|
||||
fs,
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
2
plugin/hello/.gitignore
vendored
2
plugin/hello/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
# Rust
|
||||
target
|
1276
plugin/hello/Cargo.lock
generated
1276
plugin/hello/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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"}
|
||||
|
@ -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())),
|
Loading…
Reference in New Issue
Block a user