316 lines
8.6 KiB
Rust
316 lines
8.6 KiB
Rust
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(())
|
|
}
|
|
|
|
|