From 8d34b85a9cf38f9a12839ed4037301d07b5ba488 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sun, 27 Dec 2020 14:43:46 +0800 Subject: [PATCH] feat: add abi stable crates --- .gitignore | 2 + __ffi/abi_stable_crates/README.md | 5 + .../abi_stable_crates/application/Cargo.lock | 367 ++++++++++++++++++ .../abi_stable_crates/application/Cargo.toml | 13 + .../application/data/app_config.json | 33 ++ .../abi_stable_crates/application/src/app.rs | 206 ++++++++++ .../abi_stable_crates/application/src/main.rs | 315 +++++++++++++++ .../application/src/vec_from_map.rs | 57 +++ __ffi/abi_stable_crates/interface/Cargo.toml | 12 + .../interface/src/commands.rs | 154 ++++++++ .../abi_stable_crates/interface/src/error.rs | 127 ++++++ __ffi/abi_stable_crates/interface/src/lib.rs | 234 +++++++++++ .../abi_stable_crates/interface/src/utils.rs | 129 ++++++ .../interface/src/vec_from_map.rs | 57 +++ .../interface/src/which_plugin.rs | 262 +++++++++++++ __ffi/abi_stable_crates/plugin_0/Cargo.lock | 366 +++++++++++++++++ __ffi/abi_stable_crates/plugin_0/Cargo.toml | 16 + __ffi/abi_stable_crates/plugin_0/src/lib.rs | 161 ++++++++ __ffi/abi_stable_crates/plugin_1/Cargo.lock | 366 +++++++++++++++++ __ffi/abi_stable_crates/plugin_1/Cargo.toml | 16 + __ffi/abi_stable_crates/plugin_1/src/lib.rs | 174 +++++++++ 21 files changed, 3072 insertions(+) create mode 100644 __ffi/abi_stable_crates/README.md create mode 100644 __ffi/abi_stable_crates/application/Cargo.lock create mode 100644 __ffi/abi_stable_crates/application/Cargo.toml create mode 100644 __ffi/abi_stable_crates/application/data/app_config.json create mode 100644 __ffi/abi_stable_crates/application/src/app.rs create mode 100644 __ffi/abi_stable_crates/application/src/main.rs create mode 100644 __ffi/abi_stable_crates/application/src/vec_from_map.rs create mode 100644 __ffi/abi_stable_crates/interface/Cargo.toml create mode 100644 __ffi/abi_stable_crates/interface/src/commands.rs create mode 100644 __ffi/abi_stable_crates/interface/src/error.rs create mode 100644 __ffi/abi_stable_crates/interface/src/lib.rs create mode 100644 __ffi/abi_stable_crates/interface/src/utils.rs create mode 100644 __ffi/abi_stable_crates/interface/src/vec_from_map.rs create mode 100644 __ffi/abi_stable_crates/interface/src/which_plugin.rs create mode 100644 __ffi/abi_stable_crates/plugin_0/Cargo.lock create mode 100644 __ffi/abi_stable_crates/plugin_0/Cargo.toml create mode 100644 __ffi/abi_stable_crates/plugin_0/src/lib.rs create mode 100644 __ffi/abi_stable_crates/plugin_1/Cargo.lock create mode 100644 __ffi/abi_stable_crates/plugin_1/Cargo.toml create mode 100644 __ffi/abi_stable_crates/plugin_1/src/lib.rs diff --git a/.gitignore b/.gitignore index e773610..f36b791 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ linux_target/ node_modules/ *.o *.so +pluginlibs + # These are backup files generated by rustfmt **/*.rs.bk diff --git a/__ffi/abi_stable_crates/README.md b/__ffi/abi_stable_crates/README.md new file mode 100644 index 0000000..587d024 --- /dev/null +++ b/__ffi/abi_stable_crates/README.md @@ -0,0 +1,5 @@ + + +url +https://github.com/rodrimati1992/abi_stable_crates/tree/master/examples/1_trait_objects + diff --git a/__ffi/abi_stable_crates/application/Cargo.lock b/__ffi/abi_stable_crates/application/Cargo.lock new file mode 100644 index 0000000..ebe10f9 --- /dev/null +++ b/__ffi/abi_stable_crates/application/Cargo.lock @@ -0,0 +1,367 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "abi_stable" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112fbc83f18cf0f12358975cac27eb4556cc5d777bde72312e0cec7a294e1699" +dependencies = [ + "abi_stable_derive", + "abi_stable_shared", + "core_extensions", + "crossbeam-channel", + "generational-arena", + "libloading", + "lock_api", + "parking_lot", + "repr_offset", + "rustc_version", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "abi_stable_derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be19f4da4a4a1f2537f1f14d8f0917c07758acb7419d3bad9e9d62a11f6b5b0" +dependencies = [ + "abi_stable_shared", + "as_derive_utils", + "core_extensions", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "typed-arena", +] + +[[package]] +name = "abi_stable_shared" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96112d6a13f37dd1cc2a2f07f7f12b7c3d176b6eec36e7987432a70fece8b48" +dependencies = [ + "core_extensions", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "as_derive_utils" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc3613a62c7b739739a2cb1ee166ac275f16c5b86caf454ba21a2f79f04b025" +dependencies = [ + "core_extensions", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core_extensions" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff55d62b113b98e0b0a55112741376c4cfe36f1290d18392c2c5727e09629a7f" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "example_application" +version = "0.1.0" +dependencies = [ + "abi_stable", + "core_extensions", + "example_interface", + "serde", + "serde_json", + "smallvec", +] + +[[package]] +name = "example_interface" +version = "0.1.0" +dependencies = [ + "abi_stable", + "arrayvec", + "core_extensions", + "serde", + "serde_json", +] + +[[package]] +name = "generational-arena" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" + +[[package]] +name = "libloading" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9367bdfa836b7e3cf895867f7a570283444da90562980ec2263d6e1569b16bc" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "repr_offset" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68e33940e1f1e7ab77493ad7d983dba544ce72185c61d4b1b23e6c32dd2a165" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" + +[[package]] +name = "syn" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "typed-arena" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/__ffi/abi_stable_crates/application/Cargo.toml b/__ffi/abi_stable_crates/application/Cargo.toml new file mode 100644 index 0000000..dd92365 --- /dev/null +++ b/__ffi/abi_stable_crates/application/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "example_application" +version = "0.1.0" +authors = ["rodrimati1992 "] +edition = "2018" + +[dependencies] +abi_stable = "0.9.1" +core_extensions={ version = "0.1.18", default_features = false, features = ["std"] } +serde={ version = "1.0.117", features = ["derive"] } +serde_json = { version = "1.0.59", features = ["raw_value"] } +smallvec= "1.4.2" +example_interface={ version="0.1", path="../interface" } diff --git a/__ffi/abi_stable_crates/application/data/app_config.json b/__ffi/abi_stable_crates/application/data/app_config.json new file mode 100644 index 0000000..dfbc9dc --- /dev/null +++ b/__ffi/abi_stable_crates/application/data/app_config.json @@ -0,0 +1,33 @@ +{ + "plugins":[ + { + "name":"example_plugin_text_munging", + "instances":2, + "rename":"text_munging" + }, + { + "name":"example_plugin_utils", + "rename":"utils" + } + ], + "_hidden":[ + ], + "commands":{ + "text_munging:first":{"Rot13":"what"}, + "text_munging:last":"GetCommands", + "text_munging:all":{"CapitalizeWords":{ + "text":"This is an example text.", + "words":["an","text"] + }}, + "utils":{"Batch":{ + "plugin":"text_munging:first", + "commands":[ + {"Rot13":"Fancr xvyyf Qhzoyrqber"}, + {"CapitalizeWords":{ + "text":"Oh,that's not something I expected.", + "words":["not","expected"] + }} + ] + }} + } +} \ No newline at end of file diff --git a/__ffi/abi_stable_crates/application/src/app.rs b/__ffi/abi_stable_crates/application/src/app.rs new file mode 100644 index 0000000..b655ebd --- /dev/null +++ b/__ffi/abi_stable_crates/application/src/app.rs @@ -0,0 +1,206 @@ +use super::*; +use std::time::{Duration,Instant}; + +pub struct TheApplication{ + pub(super) plugins:Vec, + pub(super) state:ApplicationState, +} + + +pub struct ApplicationState{ + // This is separate so that I can use the address + // of a PluginType to find its index inside of plugins. + pub(super) plugin_ids:Vec, + pub(super) id_map:HashMap, + pub(super) delayed_commands:VecDeque, + pub(super) responses:VecDeque, + pub(super) sender:RSender, + pub(super) receiver:RReceiver, + pub(super) last_run_at:Instant, +} + + + +fn print_response( + plugin_id:&PluginId, + response:&str, +){ + println!( + "reponse:\n{}\nfrom:\n {:?}\n\n", + response.left_pad(4), + plugin_id, + ); +} + +impl TheApplication{ + /// Runs a command, + pub fn run_command(&mut self,which_plugin:WhichPlugin,command:RStr<'_>)->Result<(),AppError>{ + let list=self.state.expand_which_plugin(which_plugin)?; + for index in list { + let state=Application_TO::from_ptr(&mut self.state,TU_Opaque); + let plugin=&mut self.plugins[index as usize]; + println!("command:\n{}", command.left_pad(4)); + let resp=plugin.json_command(command,state).into_result()?; + self.state.register_command_run(); + print_response(&self.state.plugin_ids[index],&resp); + } + Ok(()) + } + + pub fn tick(&mut self) -> Result<(), AppError>{ + if let Ok(ac)=self.state.receiver.try_recv() { + self.state.send_command_to_plugin(&ac.from,ac.which_plugin,ac.command).into_result()?; + } + + if let Some(dc)=self.state.delayed_commands.pop_front() { + self.run_command_(&dc.from,dc.plugin_index,&dc.command)?; + } + + let mut responses=mem::replace(&mut self.state.responses, VecDeque::new()); + for DelayedResponse{to,from,response} in responses.drain(..) { + let response=PluginResponse::owned_response(from,response); + let state=Application_TO::from_ptr(&mut self.state,TU_Opaque); + if let RSome(res)=self.plugins[to] + .handle_response(response, state) + .into_result()? + { + print_response(&res.plugin_id, &res.response); + } + } + self.state.responses=responses; + + Ok(()) + } + + pub fn is_finished(&self)->bool{ + self.state.last_run_at.elapsed()>=Duration::from_secs(5) + } + + fn run_command_(&mut self,from:&PluginId, to:usize, command:&str) -> Result<(), AppError> { + let state=Application_TO::from_ptr(&mut self.state,TU_Opaque); + let response=self.plugins[to].json_command(command.into(),state).into_result()?; + let to=self.state.index_for_plugin_id(from)?; + + self.state.register_command_run(); + + let response = DelayedResponse { + from:self.state.plugin_ids[to].clone(), + to, + response, + }; + + self.state.responses.push_back(response); + Ok(()) + } +} + + +impl ApplicationState{ + pub(crate) fn new() -> Self { + let (sender,receiver)=crossbeam_channel::unbounded(); + + Self{ + plugin_ids:Vec::new(), + id_map:HashMap::new(), + delayed_commands:VecDeque::new(), + responses:VecDeque::new(), + sender, + receiver, + last_run_at:Instant::now(), + } + } + + fn register_command_run(&mut self) { + self.last_run_at=Instant::now(); + } + + fn index_for_plugin_id(&self, id:&PluginId) -> Result { + self.id_map.get(&*id.named) + .and_then(|x| x.indices.get(id.instance as usize).cloned()) + .ok_or_else(|| AppError::InvalidPlugin(WhichPlugin::Id(id.clone()))) + } + + fn expand_which_plugin(&self, which_plugin:WhichPlugin) -> Result { + match which_plugin { + WhichPlugin::Id(id)=>{ + self.index_for_plugin_id(&id) + .map(|i| PluginIndices::from([i]) ) + }, + WhichPlugin::First{ref named} + | WhichPlugin::Last{ref named} + | WhichPlugin::Every{ref named} => { + self.id_map.get(&**named).and_then(|x| { + let list=&x.indices; + match which_plugin { + WhichPlugin::First{..} => { + PluginIndices::from([*list.first()?]) + }, + WhichPlugin::Last{..} => { + PluginIndices::from([*list.last()?]) + }, + WhichPlugin::Every{..} => { + PluginIndices::from(&**list) + }, + _=>unreachable!(), + }.piped(Some) + }) + .ok_or_else(|| AppError::InvalidPlugin(which_plugin.clone())) + }, + WhichPlugin::Many(list) => { + let mut plugin_indices=PluginIndices::new(); + for elem in list { + plugin_indices.extend(self.expand_which_plugin(elem)?); + } + Ok(plugin_indices) + } + } + } +} + + +impl Application for ApplicationState { + fn send_command_to_plugin( + &mut self, + from:&PluginId, + which_plugin:WhichPlugin, + command:RString, + ) -> RResult<(),AppError> { + self.expand_which_plugin(which_plugin).map(|plugin_indices|{ + let command=Arc::new(command); + for plugin_index in plugin_indices { + let from=from.clone(); + self.delayed_commands.push_back(DelayedCommand { + from, + plugin_index, + command:command.clone(), + }); + } + }).into() + } + + fn get_plugin_id(&self, which_plugin:WhichPlugin) -> RResult, AppError> { + self.expand_which_plugin(which_plugin) + .map(|list|{ + list.into_iter() + .map(|i| self.plugin_ids[i].clone() ) + .collect::>() + }) + .into() + } + + fn sender(&self) -> RSender { + self.sender.clone() + } + + fn loaded_plugins(&self) -> RVec { + self.plugin_ids.clone().into() + } +} + + + +//////////////////////////////////////////////////////////////////////////////// + + + + diff --git a/__ffi/abi_stable_crates/application/src/main.rs b/__ffi/abi_stable_crates/application/src/main.rs new file mode 100644 index 0000000..f02fec0 --- /dev/null +++ b/__ffi/abi_stable_crates/application/src/main.rs @@ -0,0 +1,315 @@ +use std::{collections::{HashMap, VecDeque}, io, path::{Path, PathBuf}, mem, sync::Arc}; +use abi_stable::{ + external_types::crossbeam_channel::{self, RSender, RReceiver}, + std_types::{RString, RStr, RResult, RVec, ROk, RErr, RSome}, + sabi_trait::prelude::TU_Opaque, + library::{RawLibrary, LibraryError, LibrarySuffix, lib_header_from_path}, +}; +#[allow(unused_imports)] +use core_extensions::{SelfOps, SliceExt, StringExt}; +use example_interface::{ + AsyncCommand, + Application_TO, + Application, + Error as AppError, + PluginId, + PluginMod_Ref, + PluginType, + PluginResponse, + WhichPlugin, +}; +use serde::Deserialize; +use serde_json::value::RawValue; +use smallvec::SmallVec; + +mod vec_from_map; +mod app; + +use crate::{ + app::{TheApplication,ApplicationState}, + vec_from_map::VecFromMap, +}; + + +/// Returns the path the plugin will be loaded from. +fn compute_plugin_path(base_name:&str)->io::Result{ + let debug_dir = "pluginlibs/".as_ref_::().into_(PathBuf::T); + let release_dir = "pluginlibs/".as_ref_::().into_(PathBuf::T); + + let debug_path= + RawLibrary::path_in_directory(&debug_dir ,base_name,LibrarySuffix::NoSuffix); + + let release_path= + RawLibrary::path_in_directory(&release_dir,base_name,LibrarySuffix::NoSuffix); + + match (debug_path.exists(),release_path.exists()) { + (false,false)=>debug_path, + (true,false)=>debug_path, + (false,true)=>release_path, + (true,true)=>{ + if debug_path.metadata()?.modified()? < release_path.metadata()?.modified()? { + release_path + }else{ + debug_path + } + } + }.piped(Ok) +} + +/// A description of what plugin to load. +#[derive(Debug,Clone,Deserialize)] +#[serde(untagged)] +pub enum PluginToLoad{ + Named(String), + WithInstances{ + #[serde(alias = "name")] + named:String, + #[serde(default="one_u64")] + instances:u64, + #[serde(alias = "renamed")] + rename:Option, + } +} + +fn one_u64()->u64{ + 1 +} + +/// The type that the configuration file is deserialized into. +#[derive(Debug,Clone,Deserialize)] +pub struct Config{ + pub plugins:RVec, + pub commands:VecFromMap>, +} + + +/// A description of plugin instances that were instantiated and left to instantiate, +/// as well as the root module of the plugin's dynamic library to instantiate the plugins. +pub struct PluginModAndIndices{ + root_module:PluginMod_Ref, + to_be_instantiated:u64, + indices:Vec, +} + + +pub type PluginIndices=SmallVec<[usize;1]>; + +/// Commands sent to plugins after calling `Application::send_command_to_plugin`. +#[derive(Debug)] +pub struct DelayedCommand{ + /// Which plugin sent the command + from:PluginId, + /// The index in plugins to which the command is sent. + plugin_index:usize, + /// The command for the `plugin_index` plugin. + command:Arc, +} + + +/// Used to handle the responses to the delayed commands sent to plugins after calling +/// `Application::send_command_to_plugin`. +#[derive(Debug)] +pub struct DelayedResponse{ + /// The plugin that sends the reponse. + from:PluginId, + /// The plugin that sent the command for which this is the reponse. + to:usize, + /// The response from the `from` plugin to the `to` plugin. + response:RString, +} + + +fn main()-> io::Result<()> { + + let config_path = match std::env::args_os().nth(1) { + Some(os)=>PathBuf::from(os), + None=>{ + println!( + "Help:You can pass a configuration's path as a command-line argument." + ); + PathBuf::from("./data/app_config.json") + } + }; + + let file_contents=match std::fs::read_to_string(&*config_path) { + Ok(x)=>x, + Err(e)=>{ + eprintln!( + "Could not load the configuration file at:\n\ + \t{}\n\ + Because of this error:\n{}\n", + config_path.display(), + e + ); + std::process::exit(1); + } + }; + + let config:Config=match serde_json::from_str(&file_contents) { + Ok(x) => x, + Err(e) => { + eprintln!( + "Could not parse the configuration file at:\n\ + \t{}\n\ + Because of this error:\n\ + {}\n", + config_path.display(), + e + ); + std::process::exit(1); + }, + }; + + let mut nonexistent_files=Vec::<(String,io::Error)>::new(); + + let mut library_errs=Vec::<(String,LibraryError)>::new(); + + let mut loaded_libraries=Vec::::new(); + + let mut plugins=Vec::new(); + let mut state=ApplicationState::new(); + + for plug in &config.plugins { + let (named, instances, rename) = match plug { + PluginToLoad::Named(named) => ((*named).clone(),1,None), + PluginToLoad::WithInstances{named,instances,rename} => + ((*named).clone(),*instances,rename.clone()), + }; + + let name_key=rename.unwrap_or_else(||named.clone()); + + if let Some(mod_i)=state.id_map.get_mut(&*name_key) { + mod_i.to_be_instantiated+=instances; + continue; + } + + let library_path:PathBuf = match compute_plugin_path(named.as_ref()) { + Ok(x) => x, + Err(e) => { + nonexistent_files.push((named.clone(),e)); + continue; + } + }; + + let res = (|| { + let header=lib_header_from_path(&library_path)?; + header.init_root_module::() + })(); + + let root_module=match res { + Ok(x) => x, + Err(e) => { + library_errs.push((named.clone(),e)); + continue; + } + }; + + loaded_libraries.push(name_key.clone()); + + state.id_map.insert(name_key.into_(RString::T),PluginModAndIndices{ + root_module, + to_be_instantiated:instances, + indices:Vec::with_capacity(instances as usize), + }); + } + + if !nonexistent_files.is_empty(){ + for (name, e) in nonexistent_files { + eprintln!( + "Could not load librarr:\n\ + \t{}\n\ + because of this error:\n\ + {}\n\ + ", + name,e + ) + } + std::process::exit(1); + } + + if !library_errs.is_empty(){ + for (name,e) in library_errs { + eprintln!( + "Could not load librarr:\n\ + \t{}\n\ + because of this error:\n\ + {}\n\ + ", + name,e + ) + } + std::process::exit(1); + } + + let mut plugin_new_errs=Vec::<(String,AppError)>::new(); + + for name in loaded_libraries { + let mod_i=state.id_map.get_mut(&*name).unwrap(); + for _ in 0..mem::replace(&mut mod_i.to_be_instantiated,0) { + let plugin_constructor=mod_i.root_module.new(); + let new_id=PluginId{ + named:name.clone().into(), + instance:mod_i.indices.len() as u64, + }; + let plugin=match plugin_constructor(state.sender.clone(),new_id.clone()) { + ROk(x)=>x, + RErr(e)=>{ + plugin_new_errs.push((name.clone(),e)); + continue; + } + }; + + let new_index=plugins.len(); + + plugins.push(plugin); + + mod_i.indices.push(new_index); + state.plugin_ids.push(new_id); + } + } + + if !plugin_new_errs.is_empty() { + for (name,e) in plugin_new_errs { + eprintln!( + "Could not instantiate plugin:\n\ + \t{}\n\ + because of this error:\n\ + {}\n\ + ", + name,e + ) + } + std::process::exit(1); + } + + let mut config_commands=config.commands.vec.into_iter(); + + let mut app=TheApplication{ + plugins, + state, + }; + + while !app.is_finished() { + if let Some((which_plugin,command))=config_commands.next() { + let command=command.get(); + if let Err(e)=app.run_command(which_plugin.clone(),command.into()){ + eprintln!( + "Error while running command on:\n{:?}\nError:{}\nCommand:\n{:?}\n", + which_plugin,e,command + ); + } + } + + if let Err(e)=app.tick() { + eprintln!("Error in application loop:\n{}\n",e); + } + } + + if app.is_finished() { + println!("timeout waiting for events"); + } + + Ok(()) +} + + diff --git a/__ffi/abi_stable_crates/application/src/vec_from_map.rs b/__ffi/abi_stable_crates/application/src/vec_from_map.rs new file mode 100644 index 0000000..71276bf --- /dev/null +++ b/__ffi/abi_stable_crates/application/src/vec_from_map.rs @@ -0,0 +1,57 @@ +use std::{fmt, marker::PhantomData}; +use serde::de::{Deserialize, Deserializer, Visitor, MapAccess}; + + +/// Used to deserialize a json object to a list of key-value pairs +#[derive(Clone,Debug)] +pub struct VecFromMap{ + pub vec:Vec<(K,V)>, +} + + +struct VecFromMapVisitor { + marker: PhantomData<(K, V)> +} + +impl VecFromMapVisitor { + const NEW:Self=Self{marker:PhantomData}; +} + +impl<'de, K, V> Visitor<'de> for VecFromMapVisitor +where + K: Deserialize<'de>, + V: Deserialize<'de>, +{ + type Value = VecFromMap; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map") + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let cap=access.size_hint().unwrap_or(0); + let mut vec = Vec::<(K,V)>::with_capacity(cap); + + while let Some(pair) = access.next_entry()? { + vec.push(pair); + } + + Ok(VecFromMap{vec}) + } +} + +impl<'de, K, V> Deserialize<'de> for VecFromMap +where + K: Deserialize<'de>, + V: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(VecFromMapVisitor::NEW) + } +} diff --git a/__ffi/abi_stable_crates/interface/Cargo.toml b/__ffi/abi_stable_crates/interface/Cargo.toml new file mode 100644 index 0000000..c8f9af3 --- /dev/null +++ b/__ffi/abi_stable_crates/interface/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "example_interface" +version = "0.1.0" +authors = ["rodrimati1992 "] +edition = "2018" + +[dependencies] +abi_stable = "0.9.1" +serde={ version = "1.0.117", features = ["derive"] } +arrayvec= "0.5.1" +core_extensions={ version = "0.1.18", default_features = false, features = ["std"] } +serde_json= "1.0.59" diff --git a/__ffi/abi_stable_crates/interface/src/commands.rs b/__ffi/abi_stable_crates/interface/src/commands.rs new file mode 100644 index 0000000..9323e61 --- /dev/null +++ b/__ffi/abi_stable_crates/interface/src/commands.rs @@ -0,0 +1,154 @@ +use crate::{PluginId,WhichPlugin}; + +use std::fmt; +use serde::{ + Serialize,Deserialize, + de::{self,Deserializer, DeserializeOwned, IgnoredAny, Visitor, MapAccess, Error as _}, +}; +use abi_stable::{StableAbi, std_types::*}; + +/// The commands that map to methods in the Plugin trait. +// This is intentionally not `#[derive(StableAbi)]`, +// since it can be extended in minor versions of the interface. +// I has to be serialized to pass it through ffi. +#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize)] +pub enum BasicCommand{ + GetCommands, +} + + +/// These is the (serialized) return value of calling `PluginExt::send_basic_command`. +// This is intentionally not `#[derive(StableAbi)]`, +// since it can be extended in minor versions of the interface. +// I has to be serialized to pass it through ffi. +#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize)] +pub enum BasicRetVal{ + GetCommands(RVec), +} + + +// This is intentionally not `#[derive(StableAbi)]`, +// since it can be extended in minor versions of the interface. +// I has to be serialized to pass it through ffi. +#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize)] +#[serde(untagged)] +pub enum CommandUnion{ + ForPlugin(T), + Basic(BasicCommand), +} + + +#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize)] +#[serde(untagged)] +pub enum ReturnValUnion{ + ForPlugin(T), + Basic(BasicRetVal), +} + + + +//////////////////////////////////////////////////////////////////////////////// + + +/// A partially deserialize command,that only deserialized its variant. +#[derive(Debug,Clone)] +pub struct WhichVariant{ + pub variant:RString, +} + + +struct WhichVariantVisitor; + +impl<'de> Visitor<'de> for WhichVariantVisitor{ + type Value = WhichVariant; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map with a single entry,or a string") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(WhichVariant{variant:value.to_string().into()}) + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let (variant,_)=access.next_entry::()? + .ok_or_else(||M::Error::custom("Expected a map with a single entry"))?; + if let Some((second,_))=access.next_entry::()? { + let s=format!( + "Expected a map with a single field,\n\ + instead found both {{ \"{}\":... , \"{}\": ... }}", + variant, + second, + ); + return Err(M::Error::custom(s)); + } + Ok(WhichVariant{variant}) + } +} + +impl<'de> Deserialize<'de> for WhichVariant{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(WhichVariantVisitor) + } +} + + + + +//////////////////////////////////////////////////////////////////////////////// + + +/// Denotes this as a command type. +pub trait CommandTrait:Serialize{ + type Returns:DeserializeOwned; +} + +impl CommandTrait for BasicCommand{ + type Returns=BasicRetVal; +} + + +/// Describes a command. +#[repr(C)] +#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize,StableAbi)] +pub struct CommandDescription{ + /// A description of what this command does. + pub name:RCow<'static,str>, + /// A description of what this command does, + /// optionally with a description of the command format. + pub description:RCow<'static,str>, +} + + +impl CommandDescription{ + pub fn from_literals( + name:&'static str, + description:&'static str, + )->Self{ + CommandDescription{ + name:name.into(), + description:description.into(), + } + } +} + + +//////////////////////////////////////////////////////////////////////////////// + + +#[repr(C)] +#[derive(Debug,Clone,PartialEq,Eq,StableAbi)] +pub struct AsyncCommand{ + pub from:PluginId, + pub which_plugin:WhichPlugin, + pub command:RString, +} diff --git a/__ffi/abi_stable_crates/interface/src/error.rs b/__ffi/abi_stable_crates/interface/src/error.rs new file mode 100644 index 0000000..f1a024f --- /dev/null +++ b/__ffi/abi_stable_crates/interface/src/error.rs @@ -0,0 +1,127 @@ +use crate::{commands::CommandDescription,WhichPlugin,WhichCommandRet}; + +use abi_stable::{StableAbi, std_types::{RBoxError,RBox,RString,RVec}}; +use std::{error::Error as ErrorTrait, fmt::{self,Display}}; +use core_extensions::strings::StringExt; + +#[repr(u8)] +#[derive(Debug,StableAbi)] +pub enum Error{ + /// An error produced by `serde_json::to_string`. + Serialize(RBoxError,WhichCommandRet), + /// An error produced by `serde_json::from_string`. + Deserialize(RBoxError,WhichCommandRet), + /// A deserialization error produced when trying to deserialize json + /// as a particular command type. + UnsupportedCommand(RBox), + /// A deserialization error produced when trying to deserialize json + /// as a particular return value type. + UnsupportedReturnValue(RBox), + /// An invalid plugin. + InvalidPlugin(WhichPlugin), + /// A custom error. + Custom(RBoxError), + /// A list of errors. + Many(RVec), +} + +/// Represents a command or return value that wasn't supported. +#[repr(C)] +#[derive(Debug,StableAbi)] +pub struct Unsupported{ + /// The name of the plugin for which the command/return value wasn't supported. + pub plugin_name:RString, + /// The command/return value that wasn't supported. + pub command_name:RString, + /// A custom error. + pub error:RBoxError, + /// A list of the commands that the plugin supports + pub supported_commands:RVec, +} + + +impl Error{ + pub fn unsupported_command(what:Unsupported)->Self{ + Error::UnsupportedCommand(RBox::new(what)) + } + pub fn unsupported_return_value(what:Unsupported)->Self{ + Error::UnsupportedReturnValue(RBox::new(what)) + } +} + +impl Display for Error{ + fn fmt(&self,f:&mut fmt::Formatter<'_>)->fmt::Result { + match self { + Error::Serialize(e,which)=>{ + let which=match which { + WhichCommandRet::Command=>"command", + WhichCommandRet::Return=>"return value", + }; + writeln!(f,"Error happened while serializing the {}:\n{}\n",which,e) + } + Error::Deserialize(e,which)=>{ + let which=match which { + WhichCommandRet::Command=>"command", + WhichCommandRet::Return=>"return value", + }; + writeln!(f,"Error happened while deserializing {}:\n{}\n",which,e) + } + Error::UnsupportedCommand(v)=>{ + writeln!( + f, + "Plugin '{}' ooes not support this command:\n\ + \t'{}'\n\ + Because of this error:\n{}\n\ + Supported commands:\ + ", + v.plugin_name, + v.command_name, + v.error, + + )?; + + for supported in &v.supported_commands { + write!( + f, + "{}", + format!( + "\nName:\n{}\nDescription:\n{}\n\n", + supported.name.left_padder(4), + supported.description.left_padder(4), + ).left_padder(4) + )?; + } + + Ok(()) + } + Error::UnsupportedReturnValue(v)=> + writeln!( + f, + "Unrecognized return value from '{}',named:\n\ + \t'{}'\n\ + Because of this error:\n{}\n\ + ", + v.plugin_name, + v.command_name, + v.error, + ), + Error::InvalidPlugin(wc)=> + writeln!( + f, + "Attempted to access a nonexistent plugin with the WhichPlugin:\n\t{:?}\n", + wc + ), + Error::Custom(e)=>Display::fmt(e,f), + Error::Many(list)=>{ + for e in list { + writeln!(f,"{}",e)?; + } + Ok(()) + } + } + } +} + + +impl ErrorTrait for Error{} + diff --git a/__ffi/abi_stable_crates/interface/src/lib.rs b/__ffi/abi_stable_crates/interface/src/lib.rs new file mode 100644 index 0000000..3579a4e --- /dev/null +++ b/__ffi/abi_stable_crates/interface/src/lib.rs @@ -0,0 +1,234 @@ +/*! +This is an example `interface crate`, +where all publically available modules(structs of function pointers) and types are declared, + +To load the library and the modules together, +call `::load_from_directory`, +which will load the dynamic library from a directory(folder), +and all the modules inside of the library. + +*/ + +use abi_stable::{ + StableAbi, + sabi_trait, + package_version_strings, + declare_root_module_statics, + library::RootModule, + sabi_types::VersionStrings, + external_types::{ + crossbeam_channel::RSender, + }, + std_types::{RBox, RCow, RVec, RStr, RString,RResult, ROption, ROk,RSome}, +}; + +use serde::{Serialize,Deserialize}; + +mod commands; +mod error; +mod which_plugin; +mod vec_from_map; +pub mod utils; + + +pub use self::{ + commands::{ + BasicCommand,BasicRetVal,CommandDescription,CommandTrait,WhichVariant,AsyncCommand, + }, + error::{Error,Unsupported}, + which_plugin::WhichPlugin, + vec_from_map::VecFromMap, +}; + + +/////////////////////////////////////////////////////////////////////////////// + + +/** +The identifier for a plugin. +*/ +#[repr(C)] +#[derive(Debug,Clone,PartialEq,Eq,StableAbi,Serialize,Deserialize)] +pub struct PluginId{ + pub named:RCow<'static,str>, + /// The number of the instance of this Plugin. + pub instance:u64, +} + + +/// Describes whether a boxed error is a command or a return value. +#[repr(u8)] +#[derive(Debug,Clone,PartialEq,Eq,StableAbi,Serialize,Deserialize)] +pub enum WhichCommandRet{ + Command, + Return, +} + + +/// The response from having called `ApplicationMut::send_command_to_plugin` ealier. +#[repr(C)] +#[derive(Debug,Clone,PartialEq,Eq,StableAbi)] +pub struct PluginResponse<'a>{ + /// The id of the plugin that is responding. + pub plugin_id:PluginId, + /// The response from the plugin + pub response:RCow<'a,str>, +} + + +impl<'a> PluginResponse<'a>{ + pub fn owned_response(plugin_id:PluginId,response:RString)->Self{ + Self{plugin_id,response:response.into()} + } + pub fn borrowed_response(plugin_id:PluginId,response:RStr<'a>)->Self{ + Self{plugin_id,response:response.into()} + } +} + + + +/////////////////////////////////////////////////////////////////////////////// + + +pub type PluginType=Plugin_TO<'static,RBox<()>>; + + +/** +A plugin which is loaded by the application,and provides some functionality. + + +*/ +#[sabi_trait] +//#[sabi(debug_print)] +pub trait Plugin { + + /// Handles a JSON encoded command. + fn json_command( + &mut self, + command: RStr<'_>, + app:ApplicationMut<'_>, + )->RResult; + + /// Handles a response from another Plugin, + /// from having called `ApplicationMut::send_command_to_plugin` ealier. + fn handle_response<'a>( + &mut self, + response:PluginResponse<'a>, + _app:ApplicationMut<'_>, + )->RResult>,Error>{ + ROk(RSome(response)) + } + + /// Gets the PluginId that was passed to this plugin in its constructor. + fn plugin_id(&self)->&PluginId; + + /// Gets a description of all commands from this Plugin. + fn list_commands(&self)->RVec; + + /* +Closes the plugin, + +This does not unload the dynamic library of this plugin, +you can instantiate another instance of this plugin with +`PluginMod_Ref::get_module().new()(application_handle)`. + + + +The `#[sabi(last_prefix_field)]` attribute here means that this is the last method +that was defined in the first compatible version of the library +(0.1.0, 0.2.0, 0.3.0, 1.0.0, 2.0.0 ,etc), +requiring new methods to always be added below preexisting ones. + +The `#[sabi(last_prefix_field)]` attribute would stay on this method until the library +bumps its "major" version, +at which point it would be moved to the last method at the time. + */ + #[sabi(last_prefix_field)] + fn close(self,app:ApplicationMut<'_>); +} + + +/////////////////////////////////////////////////////////////////////////////// + +/// The root module of a`plugin` dynamic library. +/// +/// To load this module, +/// call ::load_from_directory(some_directory_path) +#[repr(C)] +#[derive(StableAbi)] +#[sabi(kind(Prefix(prefix_ref="PluginMod_Ref")))] +#[sabi(missing_field(panic))] +pub struct PluginMod { +/** +Constructs the plugin. + + +The `#[sabi(last_prefix_field)]` attribute here means that this is the last field in this struct +that was defined in the first compatible version of the library +(0.1.0, 0.2.0, 0.3.0, 1.0.0, 2.0.0 ,etc), +requiring new fields to always be added below preexisting ones. + +The `#[sabi(last_prefix_field)]` attribute would stay on this field until the library +bumps its "major" version, +at which point it would be moved to the last field at the time. + +*/ + #[sabi(last_prefix_field)] + pub new: extern "C" fn(RSender,PluginId) -> RResult, +} + + +impl RootModule for PluginMod_Ref { + declare_root_module_statics!{PluginMod_Ref} + const BASE_NAME: &'static str = "plugin"; + const NAME: &'static str = "plugin"; + const VERSION_STRINGS: VersionStrings = package_version_strings!(); +} + + + +/////////////////////////////////////////////////////////////////////////////// + + +/// A mutable reference to the application implementation. +pub type ApplicationMut<'a>=Application_TO<'a,&'a mut ()>; + + +#[sabi_trait] +pub trait Application{ + + /// Asynchronously Sends a command to the plugin(s) specified by `which_plugin`. + /// + /// # Errors + /// + /// Returns an `Error::InvalidPlugin` if `which_plugin` is invalid. + fn send_command_to_plugin( + &mut self, + from:&PluginId, + which_plugin:WhichPlugin, + command:RString, + )->RResult<(),Error>; + +/** +Gets the `PluginId`s of the plugins specified by `which_plugin`. + + +The `#[sabi(last_prefix_field)]` attribute here means that this is the last method +that was defined in the first compatible version of the library +(0.1.0, 0.2.0, 0.3.0, 1.0.0, 2.0.0 ,etc), +requiring new methods to always be added below preexisting ones. + +The `#[sabi(last_prefix_field)]` attribute would stay on this method until the library +bumps its "major" version, +at which point it would be moved to the last method at the time. + +*/ + #[sabi(last_prefix_field)] + fn get_plugin_id(&self,which_plugin:WhichPlugin)->RResult,Error>; + + /// Gets the sender end of a channel to send commands to the application/other plugins. + fn sender(&self)->RSender; + + /// Gets the PluginId of all loaded plugins + fn loaded_plugins(&self)->RVec; +} \ No newline at end of file diff --git a/__ffi/abi_stable_crates/interface/src/utils.rs b/__ffi/abi_stable_crates/interface/src/utils.rs new file mode 100644 index 0000000..de522e4 --- /dev/null +++ b/__ffi/abi_stable_crates/interface/src/utils.rs @@ -0,0 +1,129 @@ +use crate::{ + commands::{ + BasicCommand, + CommandUnion, + CommandUnion as CU, + CommandTrait, + ReturnValUnion, + ReturnValUnion as RVU, + WhichVariant, + BasicRetVal, + }, + error::Unsupported, + ApplicationMut,WhichCommandRet,Error,Plugin,PluginType, +}; +use abi_stable::{std_types::{RStr, RString, RBoxError, RResult}}; +use serde::{Serialize,Deserialize}; + + +/** +Sends a json encoded command to a plugin,and returns the response by encoding it to json. + +# Errors + +These are all error that this function returns +(this does not include error returned as part of the command): + +- Error::Serialize: + If the command/return value could not be serialized to JSON. + +- Error::Deserialize + If the command/return value could not be deserialized from JSON(this comes from the plugin). + +- Error::UnsupportedCommand + If the command is not supported by the plugin. + +*/ +pub fn process_command<'de,P,C,R,F>(this:&mut P,command:RStr<'de>,f:F)->RResult +where + P:Plugin, + F:FnOnce(&mut P,C)->Result, + C:Deserialize<'de>, + R:Serialize, +{ + (||->Result{ + let command=command.as_str(); + + let which_variant=serde_json::from_str::(&command) + .map_err(|e| Error::Deserialize(RBoxError::new(e),WhichCommandRet::Command) )?; + + let command=serde_json::from_str::>(command) + .map_err(|e|{ + Error::unsupported_command(Unsupported{ + plugin_name:this.plugin_id().named.clone().into_owned(), + command_name:which_variant.variant, + error:RBoxError::new(e), + supported_commands:this.list_commands(), + }) + })?; + + let ret:ReturnValUnion=match command { + CU::Basic(BasicCommand::GetCommands)=>{ + let commands=this.list_commands(); + RVU::Basic(BasicRetVal::GetCommands(commands)) + } + CU::ForPlugin(cmd)=>{ + RVU::ForPlugin(f(this,cmd)?) + } + }; + + match serde_json::to_string(&ret) { + Ok(v)=>Ok(v.into()), + Err(e)=>Err(Error::Serialize(RBoxError::new(e),WhichCommandRet::Return)), + } + })().into() +} + + + + +/** +Sends a typed command to a plugin. + +# Errors + +These are all error that this function returns +(this does not include error returned as part of the command): + +- Error::Serialize: + If the command/return value could not be serialized to JSON. + +- Error::Deserialize + If the command/return value could not be deserialized from JSON(this comes from the plugin). + +- Error::UnsupportedReturnValue: + If the return value could not be deserialized from JSON + (after checking that it has the `{"name":"...",description: ... }` format), + containing the name of the command this is a return value for . + +- Error::UnsupportedCommand + If the command is not supported by the plugin. + +*/ +pub fn send_command( + this:&mut PluginType, + command:&C, + app:ApplicationMut<'_> +)->Result +where + C:CommandTrait, +{ + let cmd=serde_json::to_string(&command) + .map_err(|e| Error::Serialize(RBoxError::new(e),WhichCommandRet::Command) )?; + + let ret=this.json_command(RStr::from(&*cmd),app).into_result()?; + + let which_variant=serde_json::from_str::(&*ret) + .map_err(|e| Error::Deserialize(RBoxError::new(e),WhichCommandRet::Return) )?; + + serde_json::from_str::(&ret) + .map_err(|e|{ + Error::unsupported_return_value(Unsupported{ + plugin_name:this.plugin_id().named.clone().into_owned(), + command_name:which_variant.variant, + error:RBoxError::new(e), + supported_commands:this.list_commands(), + }) + }) + +} diff --git a/__ffi/abi_stable_crates/interface/src/vec_from_map.rs b/__ffi/abi_stable_crates/interface/src/vec_from_map.rs new file mode 100644 index 0000000..ae3a977 --- /dev/null +++ b/__ffi/abi_stable_crates/interface/src/vec_from_map.rs @@ -0,0 +1,57 @@ +use std::{fmt, marker::PhantomData}; +use serde::de::{Deserialize, Deserializer, Visitor, MapAccess}; + + +/// Used to deserialize a list of key-value pairs from a json object. +#[derive(Clone,Debug)] +pub struct VecFromMap{ + pub vec:Vec<(K,V)>, +} + + +struct VecFromMapVisitor { + marker: PhantomData<(K, V)> +} + +impl VecFromMapVisitor { + const NEW:Self=Self{marker:PhantomData}; +} + +impl<'de, K, V> Visitor<'de> for VecFromMapVisitor +where + K: Deserialize<'de>, + V: Deserialize<'de>, +{ + type Value = VecFromMap; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map") + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let cap=access.size_hint().unwrap_or(0); + let mut vec = Vec::<(K,V)>::with_capacity(cap); + + while let Some(pair) = access.next_entry()? { + vec.push(pair); + } + + Ok(VecFromMap{vec}) + } +} + +impl<'de, K, V> Deserialize<'de> for VecFromMap +where + K: Deserialize<'de>, + V: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(VecFromMapVisitor::NEW) + } +} diff --git a/__ffi/abi_stable_crates/interface/src/which_plugin.rs b/__ffi/abi_stable_crates/interface/src/which_plugin.rs new file mode 100644 index 0000000..e9dbf8d --- /dev/null +++ b/__ffi/abi_stable_crates/interface/src/which_plugin.rs @@ -0,0 +1,262 @@ +use crate::PluginId; + +use std::{fmt::{self,Display}, str::FromStr}; +use arrayvec::ArrayVec; +use abi_stable::{StableAbi, std_types::{RCow,RVec,RString,cow::BorrowingRCowStr}}; +use core_extensions::{StringExt,SelfOps}; +use serde::{Serialize,Deserialize,Deserializer,Serializer}; + + +/// A way to choose to which plugins one refers to when sending commands,and other operations. +#[repr(u8)] +#[derive(Debug,Clone,PartialEq,Eq,StableAbi)] +pub enum WhichPlugin{ + Id(PluginId), + First{ + named:RCow<'static,str>, + }, + Last{ + named:RCow<'static,str>, + }, + Every{ + named:RCow<'static,str>, + }, + Many(RVec), +} + + +impl WhichPlugin{ + /// Converts this `WhichPlugin` to its json representation, + /// generally used as a key in a json object. + pub fn to_key(&self)->RString{ + let mut buffer=RString::new(); + self.write_key(&mut buffer); + buffer + } + + /// Writes the value of this as a key usable in the application config. + pub fn write_key(&self,buf:&mut RString){ + use std::fmt::Write; + match self { + WhichPlugin::Id(id)=>write!(buf,"{}:{}",id.named,id.instance).drop_(), + WhichPlugin::First{named}=>write!(buf,"{}:first",named).drop_(), + WhichPlugin::Last{named}=>write!(buf,"{}:last",named).drop_(), + WhichPlugin::Every{named}=>write!(buf,"{}:every",named).drop_(), + WhichPlugin::Many(list)=>{ + for elem in list { + elem.write_key(buf); + buf.push(','); + } + }, + } + } +} + + +impl FromStr for WhichPlugin{ + type Err=WhichPluginError; + + fn from_str(full_str:&str)->Result{ + let mut comma_sep=full_str.split(',').peekable(); + let first=comma_sep.next().unwrap_or("").piped(|s|Self::parse_single(s,full_str))?; + + if comma_sep.peek().is_some() { + let mut list:RVec=vec![first].into(); + for s in comma_sep.filter(|s| !s.is_empty() ) { + list.push( Self::parse_single(s,full_str)? ); + } + WhichPlugin::Many(list) + }else{ + first + }.piped(Ok) + } +} + + +impl WhichPlugin{ + + fn parse_single(s:&str,full_str:&str)->Result{ + let splitted=s.splitn(2,':').map(|s|s.trim()).collect::>(); + let named=splitted.get(0) + .filter(|s| !s.is_empty() ) + .ok_or_else(|| WhichPluginError(full_str.into()) )? + .to_string() + .into_(RCow::<'static,str>::T); + let selector=splitted.get(1).map_or("",|x|*x); + + match selector { + "first"=>return Ok(WhichPlugin::First{named}), + ""|"last"=>return Ok(WhichPlugin::Last{named}), + "all"|"every"=>return Ok(WhichPlugin::Every{named}), + _=>(), + } + + let instance=selector.parse::().map_err(|_| WhichPluginError(full_str.into()) )?; + Ok(WhichPlugin::Id(PluginId{named,instance})) + } + + pub const FMT_MSG:&'static str=r##" + +"plugin name": + refers to the last plugin named "plugin name". + +"plugin name:10": + refers to the 10th instance of the plugin named "plugin name". + +"plugin name:first": + refers to the first instance of the plugin named "plugin name". + +"plugin name:last": + refers to the last instance of the plugin named "plugin name". + +"plugin name:every": + refers to all the instances of the plugin named "plugin name". + +"plugin name 1,plugin name 2:first,plugin name 3:every": + refers to the last instance of the plugin named "plugin name 1". + refers to the first instance of the plugin named "plugin name 2". + refers to the all the instances of the plugin named "plugin name 3". + +Plugin names: + +- Are trimmed,so you can add spaces at the start and the end. + +- Cannot contain commas,since they will be interpreted as a list of plugins. + + + "##; + +} + + +impl<'de> Deserialize<'de> for WhichPlugin{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + use serde::de; + BorrowingRCowStr::deserialize(deserializer)? + .cow + .parse::() + .map_err(de::Error::custom) + } +} + + +impl Serialize for WhichPlugin{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.to_key().serialize(serializer) + } +} + + +/////////////////////////////////////// + + +#[repr(transparent)] +#[derive(Debug,Clone,StableAbi)] +pub struct WhichPluginError(RString); + + +impl Display for WhichPluginError{ + fn fmt(&self,f:&mut fmt::Formatter)->fmt::Result{ + writeln!( + f, + "Could not parse this as a `WhichPlugin`:\n\t'{}'\nExpected format:\n{}\n", + self.0, + WhichPlugin::FMT_MSG.left_padder(4), + ) + } +} + + +#[cfg(test)] +mod tests{ + use super::*; + + fn new_str_expected()->Vec<(&'static str,WhichPlugin)>{ + vec![ + ( + "plugin name", + WhichPlugin::Last{named:"plugin name".into()} + ), + ( + "plugin name:10", + WhichPlugin::Id(PluginId{ + named:"plugin name".into(), + instance:10, + }) + ), + ( + "plugin name:first", + WhichPlugin::First{named:"plugin name".into()} + ), + ( + "plugin name:last", + WhichPlugin::Last{named:"plugin name".into()} + ), + ( + "plugin name:every", + WhichPlugin::Every{named:"plugin name".into()} + ), + ( + "plugin name 1,plugin name 2:first,plugin name 3:every", + WhichPlugin::Many(vec![ + WhichPlugin::Last{named:"plugin name 1".into()}, + WhichPlugin::First{named:"plugin name 2".into()}, + WhichPlugin::Every{named:"plugin name 3".into()}, + ].into()) + ), + ] + } + + #[test] + fn parses_correctly(){ + let str_expected=new_str_expected(); + + for (str_,expected) in str_expected { + let parsed=str_.parse::().unwrap(); + assert_eq!(parsed,expected); + + assert_eq!( + parsed.to_key().parse::().unwrap(), + expected, + ); + } + } + + + #[test] + fn serde_(){ + let str_expected=new_str_expected(); + + for (_,elem) in str_expected { + let str_=serde_json::to_string(&elem).unwrap(); + let other:WhichPlugin=serde_json::from_str(&str_) + .unwrap_or_else(|e| panic!("{}",e) ); + assert_eq!(other,elem); + } + + } + + + #[test] + fn parses_incorrectly(){ + let list=vec![ + // An empty plugin name is invalid + "", + ":", + ":first", + ":last", + ",", + ",,,:first,:last", + ]; + + for str_ in list { + str_.parse::().unwrap_err(); + } + } +} \ No newline at end of file diff --git a/__ffi/abi_stable_crates/plugin_0/Cargo.lock b/__ffi/abi_stable_crates/plugin_0/Cargo.lock new file mode 100644 index 0000000..fd83a67 --- /dev/null +++ b/__ffi/abi_stable_crates/plugin_0/Cargo.lock @@ -0,0 +1,366 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "abi_stable" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112fbc83f18cf0f12358975cac27eb4556cc5d777bde72312e0cec7a294e1699" +dependencies = [ + "abi_stable_derive", + "abi_stable_shared", + "core_extensions", + "crossbeam-channel", + "generational-arena", + "libloading", + "lock_api", + "parking_lot", + "repr_offset", + "rustc_version", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "abi_stable_derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be19f4da4a4a1f2537f1f14d8f0917c07758acb7419d3bad9e9d62a11f6b5b0" +dependencies = [ + "abi_stable_shared", + "as_derive_utils", + "core_extensions", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "typed-arena", +] + +[[package]] +name = "abi_stable_shared" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96112d6a13f37dd1cc2a2f07f7f12b7c3d176b6eec36e7987432a70fece8b48" +dependencies = [ + "core_extensions", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "as_derive_utils" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc3613a62c7b739739a2cb1ee166ac275f16c5b86caf454ba21a2f79f04b025" +dependencies = [ + "core_extensions", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core_extensions" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff55d62b113b98e0b0a55112741376c4cfe36f1290d18392c2c5727e09629a7f" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "example_interface" +version = "0.1.0" +dependencies = [ + "abi_stable", + "arrayvec", + "core_extensions", + "serde", + "serde_json", +] + +[[package]] +name = "example_plugin_text_munging" +version = "0.1.0" +dependencies = [ + "abi_stable", + "core_extensions", + "example_interface", + "serde", + "serde_json", +] + +[[package]] +name = "generational-arena" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" + +[[package]] +name = "libloading" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9367bdfa836b7e3cf895867f7a570283444da90562980ec2263d6e1569b16bc" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "repr_offset" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68e33940e1f1e7ab77493ad7d983dba544ce72185c61d4b1b23e6c32dd2a165" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" + +[[package]] +name = "syn" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "typed-arena" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/__ffi/abi_stable_crates/plugin_0/Cargo.toml b/__ffi/abi_stable_crates/plugin_0/Cargo.toml new file mode 100644 index 0000000..586767c --- /dev/null +++ b/__ffi/abi_stable_crates/plugin_0/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "example_plugin_text_munging" +version = "0.1.0" +authors = ["rodrimati1992 "] +edition = "2018" + +[dependencies] +abi_stable = "0.9.1" +core_extensions={ version = "0.1.18", default_features = false, features = ["std"] } +serde={ version = "1.0.117", features = ["derive"] } +serde_json= "1.0.59" +example_interface={ version="0.1", path="../interface"} + +[lib] +name = "example_plugin_text_munging" +crate-type = ["cdylib",'rlib'] diff --git a/__ffi/abi_stable_crates/plugin_0/src/lib.rs b/__ffi/abi_stable_crates/plugin_0/src/lib.rs new file mode 100644 index 0000000..f805a27 --- /dev/null +++ b/__ffi/abi_stable_crates/plugin_0/src/lib.rs @@ -0,0 +1,161 @@ +/*! +This is an `implementation crate`, +It exports the root module(a struct of function pointers) required by the +`example_0_interface`(the `interface crate`). + +*/ + +use std::collections::HashSet; +use abi_stable::{ + export_root_module, + sabi_extern_fn, + external_types::crossbeam_channel::RSender, + prefix_type::PrefixTypeTrait, + sabi_trait::prelude::TU_Opaque, + std_types::{RStr,RVec, RString,RResult,ROk}, +}; +use example_interface::{ + AsyncCommand, + ApplicationMut, + CommandDescription, + Error as AppError, + Plugin,PluginType,PluginId,PluginMod,PluginMod_Ref, + Plugin_TO, + utils::process_command, +}; +use core_extensions::{SelfOps,StringExt}; +use serde::{Serialize,Deserialize}; + +/////////////////////////////////////////////////////////////////////////////////// + + +/// Exports the root module of this library. +/// +/// This code isn't run until the layout of the type it returns is checked. +#[export_root_module] +fn instantiate_root_module()->PluginMod_Ref{ + PluginMod { + new, + }.leak_into_prefix() +} + + +////////////////////////////////////////////////////////////////////////////////////// + + +/// Instantiates the plugin. +#[sabi_extern_fn] +pub fn new(_sender:RSender,plugin_id:PluginId) -> RResult { + let this=TextMunging{ + plugin_id, + }; + ROk(Plugin_TO::from_value(this,TU_Opaque)) +} + + +////////////////////////////////////////////////////////////////////////////////////// + + + +#[derive(Debug,Serialize,Deserialize)] +pub enum ProcessTextCmd{ + Rot13(String), + CapitalizeWords{ + text:String, + words:HashSet + }, +} + +#[derive(Debug,Serialize,Deserialize)] +pub enum ReturnValue{ + Rot13(String), + CapitalizeWords(String), +} + + + + + +////////////////////////////////////////////////////////////////////////////////////// + + +fn run_command_inner( + _this:&mut TextMunging, + command:ProcessTextCmd, + _app:ApplicationMut<'_>, +)->Result{ + match command { + ProcessTextCmd::Rot13(text)=>{ + pub fn rot13(this:char)-> char{ + match this{ + v@'a'..='z'=> + ((((v as u8 - b'a')+13)%26)+b'a')as char, + v@'A'..='Z'=> + ((((v as u8 - b'A')+13)%26)+b'A')as char, + v=>v + } + } + text.chars() + .map(rot13) + .collect::() + .piped(ReturnValue::Rot13) + } + ProcessTextCmd::CapitalizeWords{text,words}=>{ + let mut buffer=String::with_capacity(10); + + for kv in text.split_while(|c|c.is_alphabetic()) { + let str_=kv.str; + let is_a_word=kv.key; + if is_a_word && words.contains(str_) { + buffer.extend(str_.chars().flat_map(char::to_uppercase)); + }else{ + buffer.push_str(str_); + } + } + ReturnValue::CapitalizeWords(buffer) + } + }.piped(Ok) +} + +////////////////////////////////////////////////////////////////////////////////////// + + +struct TextMunging{ + plugin_id:PluginId, +} + + +impl Plugin for TextMunging { + fn json_command( + &mut self, + command: RStr<'_>, + app:ApplicationMut<'_>, + )->RResult{ + process_command(self,command,|this,command:ProcessTextCmd|{ + run_command_inner(this,command,app) + }) + } + + fn plugin_id(&self)->&PluginId{ + &self.plugin_id + } + + fn list_commands(&self)->RVec{ + vec![ + CommandDescription::from_literals( + "Rot13", + "Uses the rot13 algorithm to hide spoilers in plain text." + ), + CommandDescription::from_literals( + "CapitalizeWords", + "Capitalizes the words specified in words.\n\ + \n\ + Command parans:\n\ + {\"text\":\"text here\",words:[\"word0\",\"word1\"]}", + ), + ].into() + } + + fn close(self,_app:ApplicationMut<'_>){} +} + diff --git a/__ffi/abi_stable_crates/plugin_1/Cargo.lock b/__ffi/abi_stable_crates/plugin_1/Cargo.lock new file mode 100644 index 0000000..e8f48e4 --- /dev/null +++ b/__ffi/abi_stable_crates/plugin_1/Cargo.lock @@ -0,0 +1,366 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "abi_stable" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112fbc83f18cf0f12358975cac27eb4556cc5d777bde72312e0cec7a294e1699" +dependencies = [ + "abi_stable_derive", + "abi_stable_shared", + "core_extensions", + "crossbeam-channel", + "generational-arena", + "libloading", + "lock_api", + "parking_lot", + "repr_offset", + "rustc_version", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "abi_stable_derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be19f4da4a4a1f2537f1f14d8f0917c07758acb7419d3bad9e9d62a11f6b5b0" +dependencies = [ + "abi_stable_shared", + "as_derive_utils", + "core_extensions", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "typed-arena", +] + +[[package]] +name = "abi_stable_shared" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96112d6a13f37dd1cc2a2f07f7f12b7c3d176b6eec36e7987432a70fece8b48" +dependencies = [ + "core_extensions", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "as_derive_utils" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc3613a62c7b739739a2cb1ee166ac275f16c5b86caf454ba21a2f79f04b025" +dependencies = [ + "core_extensions", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core_extensions" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff55d62b113b98e0b0a55112741376c4cfe36f1290d18392c2c5727e09629a7f" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "example_interface" +version = "0.1.0" +dependencies = [ + "abi_stable", + "arrayvec", + "core_extensions", + "serde", + "serde_json", +] + +[[package]] +name = "example_plugin_utils" +version = "0.1.0" +dependencies = [ + "abi_stable", + "core_extensions", + "example_interface", + "serde", + "serde_json", +] + +[[package]] +name = "generational-arena" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" + +[[package]] +name = "libloading" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9367bdfa836b7e3cf895867f7a570283444da90562980ec2263d6e1569b16bc" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "repr_offset" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68e33940e1f1e7ab77493ad7d983dba544ce72185c61d4b1b23e6c32dd2a165" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" + +[[package]] +name = "syn" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "typed-arena" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/__ffi/abi_stable_crates/plugin_1/Cargo.toml b/__ffi/abi_stable_crates/plugin_1/Cargo.toml new file mode 100644 index 0000000..40ec2a6 --- /dev/null +++ b/__ffi/abi_stable_crates/plugin_1/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "example_plugin_utils" +version = "0.1.0" +authors = ["rodrimati1992 "] +edition = "2018" + +[dependencies] +abi_stable = "0.9.1" +core_extensions={ version = "0.1.18", default_features = false, features = ["std"] } +serde={ version = "1.0.117", features = ["derive"] } +serde_json = { version = "1.0.59", features = ["raw_value"] } +example_interface={ version="0.1", path="../interface"} + +[lib] +name = "example_plugin_utils" +crate-type = ["cdylib",'rlib'] diff --git a/__ffi/abi_stable_crates/plugin_1/src/lib.rs b/__ffi/abi_stable_crates/plugin_1/src/lib.rs new file mode 100644 index 0000000..15192ad --- /dev/null +++ b/__ffi/abi_stable_crates/plugin_1/src/lib.rs @@ -0,0 +1,174 @@ +/*! +This is an `implementation crate`, +It exports the root module(a struct of function pointers) required by the +`example_0_interface`(the `interface crate`). +*/ + +use abi_stable::{ + export_root_module, + sabi_extern_fn, + external_types::crossbeam_channel::RSender, + prefix_type::PrefixTypeTrait, + sabi_trait::prelude::TU_Opaque, + std_types::{RStr,RVec, RString,RResult,ROk}, +}; +use example_interface::{ + AsyncCommand, + ApplicationMut, + CommandDescription, + Error as AppError, + Plugin,PluginType,PluginId,PluginMod,PluginMod_Ref, + Plugin_TO, + utils::process_command, + WhichPlugin, +}; +use core_extensions::SelfOps; +use serde::{Serialize, Deserialize}; +use serde_json::value::Value; + +/////////////////////////////////////////////////////////////////////////////////// + + +/// Exports the root module of this library. +/// +/// This code isn't run until the layout of the type it returns is checked. +#[export_root_module] +fn instantiate_root_module()->PluginMod_Ref{ + PluginMod { + new, + }.leak_into_prefix() +} + + +////////////////////////////////////////////////////////////////////////////////////// + + +#[sabi_extern_fn] +pub fn new(_sender:RSender,plugin_id:PluginId) -> RResult { + let this=CommandUtils{ + plugin_id, + }; + ROk(Plugin_TO::from_value(this,TU_Opaque)) +} + + +////////////////////////////////////////////////////////////////////////////////////// + + + +#[derive(Debug,Serialize,Deserialize)] +pub enum UtilCmd{ + Repeat{ + how_much:usize, + plugin:WhichPlugin, + command:Value, + }, + Batch{ + plugin:WhichPlugin, + commands:Vec, + }, +} + +#[derive(Debug,Serialize,Deserialize)] +pub enum ReturnValue{ + Repeat, + Batch, +} + + + + + +////////////////////////////////////////////////////////////////////////////////////// + + +fn run_command_inner( + this:&mut CommandUtils, + command:UtilCmd, + mut app:ApplicationMut<'_>, +)->Result{ + match command { + UtilCmd::Repeat{how_much,plugin,command}=>{ + let s=serde_json::to_string(&command).unwrap(); + for _ in 0..how_much { + app.send_command_to_plugin( + this.plugin_id(), + plugin.clone(), + s.clone().into(), + ).into_result()?; + } + ReturnValue::Repeat + } + UtilCmd::Batch{plugin,commands}=>{ + for command in commands { + let s=serde_json::to_string(&command).unwrap(); + app.send_command_to_plugin( + this.plugin_id(), + plugin.clone(), + s.into(), + ).into_result()?; + } + ReturnValue::Batch + } + }.piped(Ok) +} + +////////////////////////////////////////////////////////////////////////////////////// + + +struct CommandUtils{ + plugin_id:PluginId, +} + + +impl Plugin for CommandUtils { + fn json_command( + &mut self, + command: RStr<'_>, + app:ApplicationMut<'_>, + )->RResult{ + process_command(self,command,|this,command|{ + run_command_inner(this,command,app) + }) + } + + fn plugin_id(&self)->&PluginId{ + &self.plugin_id + } + + fn list_commands(&self)->RVec{ + vec![ + CommandDescription::from_literals( + "Repeat", +"\ +Sends a command to a plugin N times. + +Command params: +{ + \"how_much\":10, + \"plugin\":\"plugin:last\", + \"command\":{ ... some command ... } +} +" + ), + CommandDescription::from_literals( + "Batch", +"\ +Sends a sequence of commands to a plugin. + +Command params: +{ + \"plugin\":\"plugin_name\", + \"commands\":[ + { ... some command ... }, + { ... some command ... }, + { ... some command ... } + ] +}", + ), + ].into() + } + + fn close(self,_app:ApplicationMut<'_>){} +} +