Began work on plugin API and plugin loading

This commit is contained in:
Joshua Barretto 2020-12-09 20:47:42 +00:00
parent fc7a3748c0
commit 12b29ea174
16 changed files with 1894 additions and 4 deletions

22
Cargo.lock generated
View File

@ -3973,7 +3973,7 @@ dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"spin 0.5.2",
"untrusted",
"web-sys",
"winapi 0.3.9",
@ -4495,6 +4495,12 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spin"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "652ac3743312871a5fb703f0337e68ffa3cdc28c863efad0b8dc858fa10c991b"
[[package]]
name = "spin_sleep"
version = "1.0.0"
@ -4922,9 +4928,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.5.6"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
dependencies = [
"serde",
]
@ -5346,11 +5352,21 @@ dependencies = [
"spin_sleep",
"structopt",
"sum_type",
"tar",
"toml",
"tracing",
"tracy-client",
"vek 0.12.0",
]
[[package]]
name = "veloren-plugin-api"
version = "0.1.0"
dependencies = [
"spin 0.7.0",
"veloren-common",
]
[[package]]
name = "veloren-server"
version = "0.8.0"

View File

@ -5,6 +5,7 @@ members = [
"common",
"common/sys",
"client",
"plugin-api",
"server",
"server-cli",
"voxygen",
@ -75,6 +76,6 @@ inherits = 'release'
debug = 1
[patch.crates-io]
# macos CI fix isn't merged yet
# macos CI fix isn't merged yet
winit = { git = "https://gitlab.com/veloren/winit.git", branch = "macos-test-spiffed" }
vek = { git = "https://gitlab.com/veloren/vek.git", branch = "fix_intrinsics" }

Binary file not shown.

View File

@ -34,6 +34,7 @@ directories-next = "2.0"
dot_vox = "4.0"
image = { version = "0.23.8", default-features = false, features = ["png"] }
notify = "5.0.0-pre.3"
tar = "0.4.30"
# Auth
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "b943c85e4a38f5ec60cd18c34c73097640162bfe" }
@ -53,6 +54,7 @@ ron = { version = "0.6", default-features = false }
serde = { version = "1.0.110", features = ["derive", "rc"] }
serde_json = "1.0.50"
serde_repr = "0.1.6"
toml = "0.5.7"
#esv export
csv = { version = "1.1.3", optional = true }

View File

@ -38,6 +38,7 @@ pub mod msg;
pub mod npc;
pub mod outcome;
pub mod path;
pub mod plugin;
pub mod ray;
pub mod recipe;
pub mod region;

130
common/src/plugin.rs Normal file
View File

@ -0,0 +1,130 @@
use crate::assets::ASSETS_PATH;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fs,
io::{self, Read},
path::{Path, PathBuf},
};
use tracing::{error, info};
#[derive(Debug)]
pub enum PluginError {
Io(io::Error),
Toml(toml::de::Error),
NoConfig,
NoSuchModule,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PluginData {
name: String,
modules: Vec<PathBuf>,
dependencies: Vec<String>,
}
#[derive(Clone)]
pub struct PluginModule {
wasm_data: Vec<u8>,
}
#[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)?;
Ok(PluginModule { wasm_data })
})
.collect::<Result<_, _>>()?;
Ok(Plugin {
data,
modules,
files,
})
}
}
#[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 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

@ -2,6 +2,7 @@ use common::{
comp,
event::{EventBus, LocalEvent, ServerEvent},
metrics::{PhysicsMetrics, SysMetrics},
plugin::PluginMgr,
region::RegionMap,
resources::{DeltaTime, Time, TimeOfDay},
span,
@ -18,6 +19,7 @@ use specs::{
Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
};
use std::{sync::Arc, time::Duration};
use tracing::info;
use vek::*;
/// How much faster should an in-game day be compared to a real day?
@ -176,6 +178,15 @@ impl State {
ecs.insert(SysMetrics::default());
ecs.insert(PhysicsMetrics::default());
// Load plugins from asset directory
ecs.insert(match PluginMgr::from_assets() {
Ok(plugin_mgr) => plugin_mgr,
Err(_) => {
info!("Error occurred when loading plugins. Running without plugins instead.");
PluginMgr::default()
},
});
ecs
}

10
plugin-api/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "veloren-plugin-api"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[dependencies]
common = { package = "veloren-common", path = "../common" }
spin = "0.7"

2
plugin-api/hello/.gitignore vendored Normal file
View File

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

1647
plugin-api/hello/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
[package]
name = "hello"
version = "0.1.0"
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
plugin-api = { package = "veloren-plugin-api", path = ".." }
[workspace]

View File

@ -0,0 +1,7 @@
use plugin_api::{PLUGIN, api};
pub extern fn main() {
PLUGIN.on_start(|| {
api::print("Hello from my plugin!");
});
}

View File

@ -0,0 +1,7 @@
use crate::raw_api;
pub fn print(s: &str) {
unsafe {
raw_api::print(s.as_bytes().as_ptr(), s.len());
}
}

36
plugin-api/src/lib.rs Normal file
View File

@ -0,0 +1,36 @@
#![feature(const_fn)]
pub mod api;
pub mod raw_api;
pub mod raw_hooks;
use spin::Mutex;
#[derive(Copy, Clone, Debug)]
pub enum Hook {
OnStart,
OnTick,
OnStop,
}
pub struct Plugin {
pub on_start: Mutex<Vec<Box<dyn Fn() + Send + Sync>>>,
pub on_tick: Mutex<Vec<Box<dyn Fn() + Send + Sync>>>,
pub on_stop: Mutex<Vec<Box<dyn Fn() + Send + Sync>>>,
}
impl Plugin {
pub const fn new() -> Self {
Self {
on_start: Mutex::new(Vec::new()),
on_tick: Mutex::new(Vec::new()),
on_stop: Mutex::new(Vec::new()),
}
}
pub fn on_start(&self, f: impl Fn() + Send + Sync + 'static) {
self.on_start.lock().push(Box::new(f));
}
}
pub static PLUGIN: Plugin = Plugin::new();

View File

@ -0,0 +1,3 @@
extern "C" {
pub fn print(s: *const u8, len: usize);
}

View File

@ -0,0 +1,4 @@
use super::*;
// API
pub extern "C" fn on_tick() { PLUGIN.on_tick.lock().iter().for_each(|f| f()); }