From 1dfe4af9168e05a0e5b3449d5f6951f2dec5e1ab Mon Sep 17 00:00:00 2001 From: ccgauche Date: Sun, 10 Jan 2021 21:58:17 +0100 Subject: [PATCH] Testing preliminary plugin loading through network --- assets/plugins/plugin1.plugin.tar | 3 + client/src/lib.rs | 10 +- common/net/src/msg/server.rs | 1 + common/sys/src/plugin/mod.rs | 275 ++++++++++++++++++++++++++---- plugin/rt/src/lib.rs | 12 +- server/src/lib.rs | 7 + 6 files changed, 270 insertions(+), 38 deletions(-) create mode 100644 assets/plugins/plugin1.plugin.tar diff --git a/assets/plugins/plugin1.plugin.tar b/assets/plugins/plugin1.plugin.tar new file mode 100644 index 0000000000..32faa00cae --- /dev/null +++ b/assets/plugins/plugin1.plugin.tar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f278c0113135184e973d9b7615b2d1633984a32d1874eaa7cc01b1692d591df2 +size 268800 diff --git a/client/src/lib.rs b/client/src/lib.rs index 3f611de31d..4064ef854e 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -41,7 +41,7 @@ use common_net::{ }, sync::WorldSyncExt, }; -use common_sys::state::State; +use common_sys::{plugin::PluginMgr, state::State}; use comp::BuffKind; use futures_executor::block_on; use futures_timer::Delay; @@ -218,6 +218,7 @@ impl Client { ServerInit::GameSync { entity_package, time_of_day, + plugins, max_group_size, client_timeout, world_map, @@ -372,6 +373,11 @@ impl Client { let map_bounds = Vec2::new(sea_level, max_height); debug!("Done preparing image..."); + #[cfg(feature = "plugins")] + if let Some(e) = plugins { + state.ecs_mut().write_resource::().load_server_plugins(&e); + } + Ok(( state, entity, @@ -382,7 +388,7 @@ impl Client { world_map.sites, recipe_book, max_group_size, - client_timeout, + client_timeout )) }, ServerInit::TooManyPlayers => Err(Error::TooManyPlayers), diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index 45890f717d..2a97d2a362 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -55,6 +55,7 @@ pub enum ServerInit { client_timeout: Duration, world_map: crate::msg::world_msg::WorldMapMsg, recipe_book: RecipeBook, + plugins: Option>)>>, ability_map: comp::item::tool::AbilityMap, }, } diff --git a/common/sys/src/plugin/mod.rs b/common/sys/src/plugin/mod.rs index 31f548cb7a..4233ebd972 100644 --- a/common/sys/src/plugin/mod.rs +++ b/common/sys/src/plugin/mod.rs @@ -20,21 +20,52 @@ use self::{ use rayon::prelude::*; +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum PluginEnvironement { + LOCAL, + DISTANT, + BOTH +} + +impl PluginEnvironement { + + pub fn is_local(&self) -> bool { + matches!(self, Self::LOCAL | Self::BOTH) + } + + pub fn is_distant(&self) -> bool { + matches!(self, Self::DISTANT | Self::BOTH) + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PluginData { name: String, + target: PluginEnvironement, modules: HashSet, dependencies: HashSet, } #[derive(Clone)] -pub struct Plugin { - data: PluginData, - modules: Vec, - files: HashMap>, +pub enum PluginFile { + ForDistant { + data: PluginData, + bytes: Vec>, + }, + ForLocal { + data: PluginData, + modules: Vec, + files: HashMap>, + }, + Both { + data: PluginData, + modules: Vec, + files: HashMap>, + bytes: Vec>, + } } -impl Plugin { +impl PluginFile { pub fn from_reader(mut reader: R) -> Result { let mut buf = Vec::new(); reader.read_to_end(&mut buf).map_err(PluginError::Io)?; @@ -60,25 +91,65 @@ impl Plugin { ) .map_err(PluginError::Toml)?; - let modules = data - .modules - .iter() - .map(|path| { - let wasm_data = files.remove(path).ok_or(PluginError::NoSuchModule)?; - PluginModule::new(data.name.to_owned(), &wasm_data).map_err(|e| { - PluginError::PluginModuleError(data.name.to_owned(), "".to_owned(), e) - }) - }) - .collect::>()?; - Ok(Plugin { - data, - modules, - files, + Ok(match (data.target.is_local(),data.target.is_distant()) { + (true,e) => { + let mut bytes = Vec::new(); + let modules = data + .modules + .iter() + .map(|path| { + let wasm_data = files.remove(path).ok_or(PluginError::NoSuchModule)?; + let tmp = PluginModule::new(data.name.to_owned(), &wasm_data).map_err(|e| { + PluginError::PluginModuleError(data.name.to_owned(), "".to_owned(), e) + }); + bytes.push(wasm_data); + tmp + }) + .collect::>()?; + if e { + Self::Both { + data, + modules, + files, + bytes, + } + } else { + Self::ForLocal { + data, + modules, + files, + } + } + }, + (false,_) => { + let bytes = data + .modules + .iter() + .map(|path| { + files.remove(path).ok_or(PluginError::NoSuchModule) + }) + .collect::>()?; + Self::ForDistant { + data, + bytes, + } + + } }) } - pub fn execute_prepared( + pub fn get_data(&self) -> &PluginData { + // Wait for let-or syntax to be stable + match self { + Self::ForLocal {data,..} | Self::Both {data,..} | Self::ForDistant {data,..} => data + } + } + +} + +impl PluginExecutable for PluginFile { + fn execute_prepared( &self, event_name: &str, event: &PreparedEventQuery, @@ -86,13 +157,14 @@ impl Plugin { where T: Event, { - self.modules + if let Self::ForLocal {modules,data,..} | Self::Both {modules,data,..} = self { + modules .iter() .flat_map(|module| { module.try_execute(event_name, event).map(|x| { x.map_err(|e| { PluginError::PluginModuleError( - self.data.name.to_owned(), + data.name.to_owned(), event_name.to_owned(), e, ) @@ -100,22 +172,68 @@ impl Plugin { }) }) .collect::, _>>() + } else { + Ok(Vec::new()) + } + } + + fn get_name(&self) -> &str { + &self.get_data().name } } #[derive(Clone, Default)] pub struct PluginMgr { - plugins: Vec, + plugins: Vec, + plugins_from_server: Vec } impl PluginMgr { pub fn from_assets() -> Result { + println!("NEW PLUGIN MGR"); let mut assets_path = (&*ASSETS_PATH).clone(); assets_path.push("plugins"); info!("Searching {:?} for plugins...", assets_path); Self::from_dir(assets_path) } + pub fn load_server_plugins(&mut self, plugins: &Vec<(String,Vec>)>) { + let prepared = PreparedEventQuery::new(&plugin_api::event::PluginLoadEvent { game_mode: plugin_api::GameMode::Client }).unwrap(); + self.plugins_from_server.extend(plugins.iter().flat_map(|(name,bytes)| { + info!("Loading {} with {} module(s) from server",name,bytes.len()); + match BinaryPlugin::from_bytes(name.clone(), bytes) { + Ok(e) => { + if let Err(e) = e.modules.iter().flat_map(|x| { + x.try_execute("on_load", &prepared) + }).collect::, _>>() { + error!("Error while executing `on_load` on network retreived plugin: `{}` \n{:?}",name,e); + } + Some(e) + }, + Err(e) => { + tracing::error!("Error while loading distant plugin! Contact the server administrator!\n{:?}",e); + None + } + } + })); + println!("Plugins from server: {}",self.plugins_from_server.len()); + } + + pub fn clear_server_plugins(&mut self) { + self.plugins_from_server.clear(); + } + + pub fn get_module_bytes(&self) -> Vec<(String,Vec>)> { + self.plugins.iter().flat_map(|x| { + if let PluginFile::ForDistant {data, bytes, ..} | + PluginFile::Both {data, bytes, ..} = x { + Some((data.name.clone(), bytes.clone())) + } else { + None + } + }).collect() + } + pub fn execute_prepared( &self, event_name: &str, @@ -124,14 +242,27 @@ impl PluginMgr { where T: Event, { - Ok(self + println!("{}",event_name); + let mut o = self .plugins .par_iter() .map(|plugin| plugin.execute_prepared(event_name, event)) .collect::, _>>()? .into_iter() .flatten() - .collect()) + .collect::>(); + println!("Event exe 2 {}",self.plugins_from_server.len()); + o.extend(self + .plugins_from_server + .par_iter() + .map(|plugin| { + println!("Event exe 3"); + plugin.execute_prepared(event_name, event) + }) + .collect::, _>>()? + .into_iter() + .flatten()); + Ok(o) } pub fn execute_event( @@ -142,6 +273,8 @@ impl PluginMgr { where T: Event, { + + println!("Event exe 1"); self.execute_prepared(event_name, &PreparedEventQuery::new(event)?) } @@ -159,7 +292,7 @@ impl PluginMgr { .unwrap_or(false) { info!("Loading plugin at {:?}", entry.path()); - Plugin::from_reader(fs::File::open(entry.path()).map_err(PluginError::Io)?) + PluginFile::from_reader(fs::File::open(entry.path()).map_err(PluginError::Io)?) .map(Some) } else { Ok(None) @@ -172,13 +305,91 @@ impl PluginMgr { .collect::, _>>()?; for plugin in &plugins { - info!( - "Loaded plugin '{}' with {} module(s)", - plugin.data.name, - plugin.modules.len() - ); + match plugin { + PluginFile::Both { data, modules, .. } | PluginFile::ForLocal { data, modules, .. } => { + info!( + "Loaded plugin '{}' with {} module(s)", + data.name, + modules.len() + ); + } + PluginFile::ForDistant { data, bytes } => { + info!( + "Loaded plugin '{}' with {} module(s)", + data.name, + bytes.len() + ); + } + } } - Ok(Self { plugins }) + Ok(Self { plugins, plugins_from_server: Vec::new() }) } } + +#[derive(Clone)] +pub struct BinaryPlugin { + modules: Vec, + name: String, +} + +impl BinaryPlugin { + + pub fn from_bytes(name: String, bytes: &Vec>) -> Result { + Ok( + Self { + modules: bytes.iter().enumerate().map(|(i,module)| { + PluginModule::new(format!("{}-module({})",name,i), module).map_err(|e| { + PluginError::PluginModuleError(name.clone(), "".to_owned(), e) + }) + }).collect::,_>>()?, + name + } + ) + } +} + +impl PluginExecutable for BinaryPlugin { + fn execute_prepared( + &self, + event_name: &str, + event: &PreparedEventQuery, + ) -> Result, PluginError> + where + T: Event, + { + println!("Event launched"); + self.modules + .iter() + .flat_map(|module| { + println!("1: {}",event_name); + module.try_execute(event_name, event).map(|x| { + x.map_err(|e| { + PluginError::PluginModuleError( + self.name.to_owned(), + event_name.to_owned(), + e, + ) + }) + }) + }) + .collect::, _>>() + } + + fn get_name(&self) -> &str { + &self.name + } +} + +pub trait PluginExecutable { + + fn execute_prepared( + &self, + event_name: &str, + event: &PreparedEventQuery, + ) -> Result, PluginError> + where + T: Event; + + fn get_name(&self) -> &str; +} \ No newline at end of file diff --git a/plugin/rt/src/lib.rs b/plugin/rt/src/lib.rs index bf7d4cdd09..f0e962524b 100644 --- a/plugin/rt/src/lib.rs +++ b/plugin/rt/src/lib.rs @@ -7,16 +7,20 @@ pub use plugin_derive::*; use serde::{de::DeserializeOwned, Serialize}; +#[cfg(target_arch = "wasm32")] extern "C" { fn raw_emit_actions(ptr: *const u8, len: usize); } pub fn emit_action(action: api::Action) { emit_actions(vec![action]) } -pub fn emit_actions(actions: Vec) { - let ret = bincode::serialize(&actions).expect("Can't serialize action in emit"); - unsafe { - raw_emit_actions(ret.as_ptr(), ret.len()); +pub fn emit_actions(_actions: Vec) { + #[cfg(target_arch = "wasm32")] + { + let ret = bincode::serialize(&_actions).expect("Can't serialize action in emit"); + unsafe { + raw_emit_actions(ret.as_ptr(), ret.len()); + } } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 9eb1f84565..6a7bda52fb 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -940,6 +940,13 @@ impl Server { client_timeout: self.settings().client_timeout, world_map: self.map.clone(), recipe_book: default_recipe_book().cloned(), + #[cfg(feature = "plugins")] + plugins: Some(self + .state + .ecs() + .read_resource::().get_module_bytes()), + #[cfg(not(feature = "plugins"))] + plugins: None, ability_map: (&*self .state .ecs()