use bevy_ecs::prelude::{Component, Entity, World};
use crate::{
Disposal, ForkTargetStorage, Input, InputBundle, ManageDisposal, ManageInput, Operation,
OperationCleanup, OperationReachability, OperationRequest, OperationResult, OperationRoster,
OperationSetup, OrBroken, ReachabilityResult, SingleInputStorage,
};
use thiserror::Error as ThisError;
pub struct Branching<Input, Outputs, F> {
activator: F,
targets: ForkTargetStorage,
_ignore: std::marker::PhantomData<fn(Input, Outputs)>,
}
#[allow(clippy::type_complexity)]
pub(crate) fn make_result_branching<T, E>(
targets: ForkTargetStorage,
) -> Branching<Result<T, E>, (T, E), fn(Result<T, E>) -> (BranchResult<T>, BranchResult<E>)> {
Branching {
activator: branch_result,
targets,
_ignore: Default::default(),
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn make_option_branching<T>(
targets: ForkTargetStorage,
) -> Branching<Option<T>, (T, ()), fn(Option<T>) -> (BranchResult<T>, BranchResult<()>)> {
Branching {
activator: branch_option,
targets,
_ignore: Default::default(),
}
}
#[derive(Component, Clone, Copy)]
struct BranchingActivatorStorage<F: 'static + Send + Sync + Copy>(F);
impl<InputT, Outputs, F> Operation for Branching<InputT, Outputs, F>
where
InputT: 'static + Send + Sync,
Outputs: Branchable,
F: Fn(InputT) -> Outputs::Activation + Copy + 'static + Send + Sync,
{
fn setup(self, OperationSetup { source, world }: OperationSetup) -> OperationResult {
for target in &self.targets.0 {
world
.get_entity_mut(*target)
.or_broken()?
.insert(SingleInputStorage::new(source));
}
world.entity_mut(source).insert((
self.targets,
InputBundle::<InputT>::new(),
BranchingActivatorStorage(self.activator),
));
Ok(())
}
fn execute(
OperationRequest {
source,
world,
roster,
}: OperationRequest,
) -> OperationResult {
let mut source_mut = world.get_entity_mut(source).or_broken()?;
let Input {
session,
data: input,
} = source_mut.take_input::<InputT>()?;
let BranchingActivatorStorage::<F>(activator) = source_mut.get().copied().or_broken()?;
let activation = activator(input);
Outputs::activate(session, activation, source, world, roster)
}
fn cleanup(mut clean: OperationCleanup) -> OperationResult {
clean.cleanup_inputs::<InputT>()?;
clean.cleanup_disposals()?;
clean.notify_cleaned()
}
fn is_reachable(mut reachability: OperationReachability) -> ReachabilityResult {
if reachability.has_input::<InputT>()? {
return Ok(true);
}
SingleInputStorage::is_reachable(&mut reachability)
}
}
pub trait Branchable {
type Activation;
fn activate<'a>(
session: Entity,
activation: Self::Activation,
source: Entity,
world: &'a mut World,
roster: &'a mut OperationRoster,
) -> OperationResult;
}
pub type BranchResult<T> = Result<T, Option<anyhow::Error>>;
impl<A, B> Branchable for (A, B)
where
A: 'static + Send + Sync,
B: 'static + Send + Sync,
{
type Activation = (BranchResult<A>, BranchResult<B>);
fn activate<'a>(
session: Entity,
(a, b): Self::Activation,
source: Entity,
world: &'a mut World,
roster: &'a mut OperationRoster,
) -> OperationResult {
let targets = world.get::<ForkTargetStorage>(source).or_broken()?;
#[allow(clippy::get_first)]
let target_a = *targets.0.get(0).or_broken()?;
let target_b = *targets.0.get(1).or_broken()?;
let mut target_a_mut = world.get_entity_mut(target_a).or_broken()?;
match a {
Ok(a) => target_a_mut.give_input(session, a, roster)?,
Err(reason) => {
let disposal = Disposal::branching(source, target_a, reason);
target_a_mut.emit_disposal(session, disposal, roster);
}
}
let mut target_b_mut = world.get_entity_mut(target_b).or_broken()?;
match b {
Ok(b) => target_b_mut.give_input(session, b, roster)?,
Err(reason) => {
let disposal = Disposal::branching(source, target_b, reason);
target_b_mut.emit_disposal(session, disposal, roster);
}
}
Ok(())
}
}
#[derive(ThisError, Debug)]
#[error("An Ok value was received, so the Err branch is being disposed")]
pub struct OkInput;
#[derive(ThisError, Debug)]
#[error("An Err value was received, so the Ok branch is being disposed")]
pub struct ErrInput;
fn branch_result<T, E>(input: Result<T, E>) -> (BranchResult<T>, BranchResult<E>) {
match input {
Ok(value) => (Ok(value), Err(Some(OkInput.into()))),
Err(err) => (Err(Some(ErrInput.into())), Ok(err)),
}
}
#[derive(ThisError, Debug)]
#[error("A Some value was received, so the None branch is being disposed")]
pub struct SomeInput;
#[derive(ThisError, Debug)]
#[error("A None value was received, so the Some branch is being disposed")]
pub struct NoneInput;
fn branch_option<T>(input: Option<T>) -> (BranchResult<T>, BranchResult<()>) {
match input {
Some(value) => (Ok(value), Err(Some(SomeInput.into()))),
None => (Err(Some(NoneInput.into())), Ok(())),
}
}