feat: add abi stable crates
This commit is contained in:
206
__ffi/abi_stable_crates/application/src/app.rs
Normal file
206
__ffi/abi_stable_crates/application/src/app.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
use super::*;
|
||||
use std::time::{Duration,Instant};
|
||||
|
||||
pub struct TheApplication{
|
||||
pub(super) plugins:Vec<PluginType>,
|
||||
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<PluginId>,
|
||||
pub(super) id_map:HashMap<RString,PluginModAndIndices>,
|
||||
pub(super) delayed_commands:VecDeque<DelayedCommand>,
|
||||
pub(super) responses:VecDeque<DelayedResponse>,
|
||||
pub(super) sender:RSender<AsyncCommand>,
|
||||
pub(super) receiver:RReceiver<AsyncCommand>,
|
||||
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<usize,AppError> {
|
||||
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<PluginIndices, AppError> {
|
||||
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<RVec<PluginId>, AppError> {
|
||||
self.expand_which_plugin(which_plugin)
|
||||
.map(|list|{
|
||||
list.into_iter()
|
||||
.map(|i| self.plugin_ids[i].clone() )
|
||||
.collect::<RVec<PluginId>>()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn sender(&self) -> RSender<AsyncCommand> {
|
||||
self.sender.clone()
|
||||
}
|
||||
|
||||
fn loaded_plugins(&self) -> RVec<PluginId> {
|
||||
self.plugin_ids.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
315
__ffi/abi_stable_crates/application/src/main.rs
Normal file
315
__ffi/abi_stable_crates/application/src/main.rs
Normal file
@@ -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<PathBuf>{
|
||||
let debug_dir = "pluginlibs/".as_ref_::<Path>().into_(PathBuf::T);
|
||||
let release_dir = "pluginlibs/".as_ref_::<Path>().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<String>,
|
||||
}
|
||||
}
|
||||
|
||||
fn one_u64()->u64{
|
||||
1
|
||||
}
|
||||
|
||||
/// The type that the configuration file is deserialized into.
|
||||
#[derive(Debug,Clone,Deserialize)]
|
||||
pub struct Config{
|
||||
pub plugins:RVec<PluginToLoad>,
|
||||
pub commands:VecFromMap<WhichPlugin,Box<RawValue>>,
|
||||
}
|
||||
|
||||
|
||||
/// 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<usize>,
|
||||
}
|
||||
|
||||
|
||||
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<RString>,
|
||||
}
|
||||
|
||||
|
||||
/// 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::<String>::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::<PluginMod_Ref>()
|
||||
})();
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
57
__ffi/abi_stable_crates/application/src/vec_from_map.rs
Normal file
57
__ffi/abi_stable_crates/application/src/vec_from_map.rs
Normal file
@@ -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<K,V>{
|
||||
pub vec:Vec<(K,V)>,
|
||||
}
|
||||
|
||||
|
||||
struct VecFromMapVisitor<K, V> {
|
||||
marker: PhantomData<(K, V)>
|
||||
}
|
||||
|
||||
impl<K, V> VecFromMapVisitor<K, V> {
|
||||
const NEW:Self=Self{marker:PhantomData};
|
||||
}
|
||||
|
||||
impl<'de, K, V> Visitor<'de> for VecFromMapVisitor<K, V>
|
||||
where
|
||||
K: Deserialize<'de>,
|
||||
V: Deserialize<'de>,
|
||||
{
|
||||
type Value = VecFromMap<K, V>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a map")
|
||||
}
|
||||
|
||||
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
|
||||
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<K, V>
|
||||
where
|
||||
K: Deserialize<'de>,
|
||||
V: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_map(VecFromMapVisitor::NEW)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user