use crate::prelude::*;
use beet_core::prelude::*;
use beet_flow::prelude::*;
use std::hash::Hasher;
use std::path::PathBuf;
#[derive(Debug, Clone, Resource)]
pub struct LaunchConfig {
pub launch_file: WsPathBuf,
pub force_launch: bool,
pub package: Option<String>,
pub additional_args: Option<String>,
pub no_default_args: bool,
}
impl Default for LaunchConfig {
fn default() -> Self {
Self {
launch_file: WsPathBuf::new("launch.ron"),
force_launch: false,
package: None,
additional_args: None,
no_default_args: false,
}
}
}
impl LaunchConfig {
pub fn runner(mut app: App) -> AppExit {
app.init();
app.update();
app.world_mut()
.run_system_once::<_, (), _>(insert_launch_hash)
.unwrap();
app.world_mut()
.run_system_once::<_, (), _>(export_launch_scene)
.unwrap();
AppExit::Success
}
}
#[derive(
Debug, Deref, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect, Resource,
)]
#[reflect(Resource)]
pub struct LaunchHash {
hash: u64,
}
impl LaunchHash {
fn new(ws_config: &WorkspaceConfig) -> Result<Self> {
let files = ReadDir::files_recursive(&ws_config.root_dir.into_abs())?
.into_iter()
.filter(|path| ws_config.launch_filter.passes(path))
.collect::<Vec<_>>();
let hash = Self::hash_paths(&files)?;
Self { hash }.xok()
}
fn hash_paths(paths: &Vec<PathBuf>) -> Result<u64> {
let mut hasher = FixedHasher::default().build_hasher();
for path in paths {
let bytes = fs_ext::read(&path)?;
hasher.write(&bytes);
}
Ok(hasher.finish())
}
}
fn insert_launch_hash(
mut commands: Commands,
ws_config: Res<WorkspaceConfig>,
) -> Result {
let hash = LaunchHash::new(&ws_config)?;
commands.insert_resource(hash);
Ok(())
}
fn export_launch_scene(world: &mut World) -> Result {
let scene = world.build_scene();
let launch_file =
world.resource::<WorkspaceConfig>().launch_file.into_abs();
fs_ext::write(&launch_file, scene)?;
info!("Exported launch scene: {}", launch_file);
Ok(())
}
pub fn launch_sequence() -> impl Bundle {
(Name::new("Launch Sequence"), InfallibleSequence, children![
(
Name::new("Launch Step"),
Sequence,
children![launch_step_predicate(), run_launch_step()]
),
(
Name::new("Load Launch Scene"),
OnSpawn::observe(load_launch_scene)
)
])
}
fn load_launch_scene(
ev: On<GetOutcome>,
mut commands: Commands,
config: Res<LaunchConfig>,
) -> Result {
let scene = fs_ext::read_to_string(&config.launch_file.into_abs())?;
commands.load_scene(scene);
commands.entity(ev.target()).trigger_target(Outcome::Pass);
Ok(())
}
fn launch_step_predicate() -> impl Bundle {
(
Name::new("Launch Step Predicate"),
OnSpawn::observe(
|ev: On<GetOutcome>,
workspace_config: Res<WorkspaceConfig>,
launch_config: Res<LaunchConfig>,
type_registry: Res<AppTypeRegistry>,
mut commands: Commands|
-> Result<()> {
if launch_config.force_launch {
commands.entity(ev.target()).trigger_target(Outcome::Pass);
return Ok(());
}
let Ok(scene) = fs_ext::read_to_string(
&workspace_config.launch_file.into_abs(),
) else {
commands.entity(ev.target()).trigger_target(Outcome::Pass);
return Ok(());
};
let mut temp_world = World::new();
temp_world.insert_resource(type_registry.clone());
temp_world.load_scene(scene)?;
let launch_hash = temp_world.get_resource::<LaunchHash>().ok_or_else(||{
bevyhow!(
"LaunchHash is missing from launch scene, this can happen if it was not generated with the LaunchConfig::runner"
)
})?;
let ws_config = temp_world.resource::<WorkspaceConfig>();
let current_hash = LaunchHash::new(&ws_config)?;
let outcome = if ¤t_hash == launch_hash {
Outcome::Fail
} else {
Outcome::Pass
};
commands.entity(ev.target()).trigger_target(outcome);
Ok(())
},
),
)
}
fn run_launch_step() -> impl Bundle {
(
Name::new("Run Launch Step"),
OnSpawn::observe(
|ev: On<GetOutcome>,
mut cmd_runner: CommandRunner,
config: Res<LaunchConfig>| {
let cmd_config = if config.no_default_args {
let additional_args = config
.additional_args
.clone()
.ok_or_else(|| {
bevyhow!(
"LaunchConfig::no_default_args is true but no additional_args provided"
)
})
.unwrap();
CommandConfig::parse(additional_args)
} else {
let mut cmd = CargoBuildCmd::new("run")
.no_default_features()
.feature("launch");
if let Some(package) = &config.package {
cmd = cmd.package(package);
}
let mut cmd_config = CommandConfig::from_cargo(&cmd);
if let Some(launch_cargo_args) = &config.additional_args {
for arg in launch_cargo_args.split_whitespace() {
cmd_config = cmd_config.arg(arg);
}
}
cmd_config
};
cmd_runner.run(ev, cmd_config)
},
),
)
}
#[cfg(test)]
mod test {
use crate::prelude::*;
use beet_core::prelude::*;
#[test]
fn works() {
LaunchHash::new(&WorkspaceConfig::default())
.unwrap()
.hash
.xpect_not_eq(0);
}
}