use crate::prelude::*;
use std::fmt::Display;
use std::process::Child;
use std::process::Command;
todo!("beet_flow style");
#[derive(Reflect, Component)]
pub struct ChildProcessSequence {
kill_before_run: bool,
}
impl ChildProcessSequence {
pub fn new(kill_before_run: bool) -> Self { Self { kill_before_run } }
pub fn system(
query: Query<(Entity, &ChildProcessSequence)>,
children: Query<&Children>,
mut processes: Query<&mut ChildProcess>,
) -> Result {
for (entity, sequence) in query.iter() {
if sequence.kill_before_run {
for child in children.iter_descendants_depth_first(entity) {
if let Ok(mut child) = processes.get_mut(child) {
child.try_kill()?;
}
}
}
for child in children.iter_descendants_depth_first(entity) {
if let Ok(mut child) = processes.get_mut(child) {
child.run()?;
}
}
}
Ok(())
}
}
#[derive(Reflect, Component)]
pub struct ChildProcess {
pub args: Vec<String>,
pub run_mode: ExecProcessMode,
#[reflect(ignore)]
pub handle: Option<Child>,
pub ignore_errors: bool,
}
#[derive(Clone, Reflect)]
pub enum ExecProcessMode {
Blocking,
SpawnHandle,
}
impl ChildProcess {
pub fn new<S: AsRef<str>>(
args: Vec<S>,
run_mode: ExecProcessMode,
ignore_errors: bool,
) -> Self {
Self {
args: args.into_iter().map(|s| s.as_ref().to_string()).collect(),
run_mode,
handle: None,
ignore_errors,
}
}
pub fn try_kill(&mut self) -> Result<()> {
if let Some(mut handle) = self.handle.take() {
debug!("Killing Child Process: {}", self.args.join(" "));
handle.kill().xmap(|res| self.handle_error(res))?;
}
Ok(())
}
pub fn run(&mut self) -> Result {
self.try_kill()?;
debug!("Running Child Process: {}", self.args.join(" "));
if self.args.is_empty() {
return self
.handle_error(Err(BevyhowError::new("command is empty")));
}
let mut cmd = Command::new(&self.args[0]);
cmd.args(&self.args[1..]);
match self.run_mode {
ExecProcessMode::Blocking => {
cmd.status()
.xmap(|res| self.handle_error(res))?
.exit_ok()
.xmap(|res| self.handle_error(res))?;
}
ExecProcessMode::SpawnHandle => {
self.handle = cmd
.spawn()
.map(|res| Some(res))
.xmap(|res| self.handle_error(res))?
}
};
Ok(())
}
fn handle_error<
T: Default,
E: 'static + Send + Sync + Display + Into<BevyError>,
>(
&self,
result: Result<T, E>,
) -> Result<T> {
match (result, self.ignore_errors) {
(Err(err), false) => Err(err.into()),
(Err(err), true) => {
error!("Child Process Error: {}", err);
Ok(T::default())
}
(Ok(val), _) => Ok(val),
}
}
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod test {
use crate::prelude::*;
#[test]
fn works() {
let mut world = World::new();
world.spawn((ChildProcessSequence::new(false), children![
ChildProcess::new::<String>(
vec![],
ExecProcessMode::Blocking,
true
),
ChildProcess::new(
vec!["this_command_should_not_exist_12345"],
ExecProcessMode::Blocking,
true,
),
ChildProcess::new(vec!["echo"], ExecProcessMode::Blocking, false),
ChildProcess::new(
vec!["echo", "howdy"],
ExecProcessMode::Blocking,
false,
),
children![ChildProcess::new(
vec!["echo", "doody"],
ExecProcessMode::Blocking,
false,
)],
]));
world
.run_system_cached::<Result, _, _>(ChildProcessSequence::system)
.unwrap()
.unwrap();
}
#[test]
#[should_panic]
fn panics_on_invalid_command() {
let mut world = World::new();
world.spawn((ChildProcessSequence::new(false), children![
ChildProcess::new(
vec!["this_command_should_not_exist_12345"],
ExecProcessMode::Blocking,
false,
),
]));
world
.run_system_cached::<Result, _, _>(ChildProcessSequence::system)
.unwrap()
.unwrap();
}
}