use anyhow::{Error as Anyhow, anyhow};
use bevy_app::prelude::*;
use bevy_ecs::system::{ScheduleSystem, SystemParam};
use crossflow::*;
use std::error::Error;
use std::fmt::Debug;
pub mod components;
pub use components::*;
pub mod plugins;
pub use plugins::*;
pub mod resources;
pub use resources::*;
pub mod events;
pub use events::*;
mod systems;
pub(crate) use systems::*;
#[derive(Debug, Clone, Copy)]
pub struct SelectionCandidate {
pub candidate: Entity,
pub provisional: bool,
}
impl SelectionCandidate {
pub fn new(candidate: Entity) -> SelectionCandidate {
SelectionCandidate {
candidate,
provisional: false,
}
}
pub fn provisional(candidate: Entity) -> SelectionCandidate {
SelectionCandidate {
candidate,
provisional: true,
}
}
}
pub trait SpawnSelectionServiceExt {
fn spawn_selection_service<F: SystemParam + 'static>(
&mut self,
) -> Service<(), (), SelectionStreams>
where
for<'w, 's> F::Item<'w, 's>: SelectionFilter;
}
impl SpawnSelectionServiceExt for App {
fn spawn_selection_service<F: SystemParam + 'static>(
&mut self,
) -> Service<(), (), SelectionStreams>
where
for<'w, 's> F::Item<'w, 's>: SelectionFilter,
{
let picking_service = self.spawn_continuous_service(
Update,
picking_service::<F>.configure(|config: ScheduleConfigs<ScheduleSystem>| {
config.in_set(SelectionServiceStages::Pick)
}),
);
let hover_service = self.spawn_continuous_service(
Update,
hover_service::<F>.configure(|config: ScheduleConfigs<ScheduleSystem>| {
config.in_set(SelectionServiceStages::Hover)
}),
);
let select_service = self.spawn_continuous_service(
Update,
select_service::<F>.configure(|config: ScheduleConfigs<ScheduleSystem>| {
config.in_set(SelectionServiceStages::Select)
}),
);
self.world_mut()
.spawn_workflow::<_, _, SelectionStreams, _>(|scope, builder| {
let hover = builder.create_node(hover_service);
builder.connect(hover.streams, scope.streams.hover);
builder.connect(hover.output, scope.terminate);
let select = builder.create_node(select_service);
builder.connect(select.streams, scope.streams.select);
builder.connect(select.output, scope.terminate);
scope.input.chain(builder).fork_clone((
|chain: Chain<_>| {
chain
.then(refresh_picked.into_blocking_callback())
.then(picking_service)
.connect(scope.terminate)
},
|chain: Chain<_>| chain.connect(hover.input),
|chain: Chain<_>| chain.connect(select.input),
));
let buffer = builder.create_buffer::<()>(BufferSettings::keep_all());
builder.on_cleanup(buffer, |scope, builder| {
scope
.input
.chain(builder)
.trigger()
.then(clear_hover_select.into_blocking_callback())
.connect(scope.terminate);
});
})
}
}
#[derive(StreamPack)]
pub struct SelectionStreams {
hover: Hover,
select: Select,
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum SelectionServiceStages {
Pick,
PickFlush,
Hover,
HoverFlush,
Select,
SelectFlush,
}
#[derive(SystemParam)]
struct InspectorFilter<'w, 's> {
selectables: Query<'w, 's, &'static Selectable, Without<Preview>>,
}
impl<'w, 's> SelectionFilter for InspectorFilter<'w, 's> {
fn filter_pick(&mut self, select: Entity) -> Option<Entity> {
self.selectables
.get(select)
.ok()
.map(|selectable| selectable.element)
}
fn filter_select(&mut self, target: Entity) -> Option<Entity> {
Some(target)
}
fn on_click(&mut self, hovered: Hover) -> Option<Select> {
Some(Select::new(hovered.0))
}
}
pub type SelectionNodeResult<T = ()> = Result<T, Option<Anyhow>>;
pub trait CommonNodeErrors {
type Value;
fn or_broken_buffer(self) -> Result<Self::Value, Option<Anyhow>>;
fn or_broken_state(self) -> Result<Self::Value, Option<Anyhow>>;
fn or_broken_query(self) -> Result<Self::Value, Option<Anyhow>>;
}
impl<T, E: Error> CommonNodeErrors for Result<T, E> {
type Value = T;
fn or_broken_buffer(self) -> Result<Self::Value, Option<Anyhow>> {
self.map_err(|err| {
let backtrace = std::backtrace::Backtrace::force_capture();
Some(anyhow!(
"The buffer in the workflow is broken: {err}. Backtrace:\n{backtrace}"
))
})
}
fn or_broken_state(self) -> Result<Self::Value, Option<Anyhow>> {
self.map_err(|err| {
let backtrace = std::backtrace::Backtrace::force_capture();
Some(anyhow!(
"The state is missing from the workflow buffer: {err}. Backtrace:\n{backtrace}"
))
})
}
fn or_broken_query(self) -> Result<Self::Value, Option<Anyhow>> {
self.map_err(|err| {
let backtrace = std::backtrace::Backtrace::force_capture();
Some(anyhow!(
"A query that should have worked failed: {err}. Backtrace:\n{backtrace}"
))
})
}
}
impl<T> CommonNodeErrors for Option<T> {
type Value = T;
fn or_broken_buffer(self) -> Result<Self::Value, Option<Anyhow>> {
self.ok_or_else(|| {
let backtrace = std::backtrace::Backtrace::force_capture();
Some(anyhow!(
"The buffer in the workflow has been despawned. Backtrace:\n{backtrace}"
))
})
}
fn or_broken_state(self) -> Result<Self::Value, Option<Anyhow>> {
self.ok_or_else(|| {
let backtrace = std::backtrace::Backtrace::force_capture();
Some(anyhow!(
"The state is missing from the workflow buffer. Backtrace:\n{backtrace}"
))
})
}
fn or_broken_query(self) -> Result<Self::Value, Option<Anyhow>> {
self.ok_or_else(|| {
let backtrace = std::backtrace::Backtrace::force_capture();
Some(anyhow!(
"A query that should have worked failed. Backtrace:\n{backtrace}"
))
})
}
}
pub trait SelectionFilter: SystemParam {
fn filter_pick(&mut self, target: Entity) -> Option<Entity>;
fn filter_select(&mut self, target: Entity) -> Option<Entity>;
fn on_click(&mut self, hovered: Hover) -> Option<Select>;
}