use super::{
graph::{
LazySignal, SignalHandle, SignalHandles, SignalSystem, Upstream, UpstreamIter, apply_schedule_to_signal,
downcast_any_clone, lazy_signal_from_system, pipe_signal, poll_signal, trigger_signal_subgraph,
},
signal_map::{SignalMap, SignalMapExt},
signal_vec::{ReplayOnce, SignalVec, SignalVecExt, VecDiff, trigger_replay},
utils::{LazyEntity, ancestor_map},
};
use crate::prelude::clone;
use bevy_ecs::{prelude::*, schedule::ScheduleLabel, system::SystemState};
cfg_if::cfg_if! {
if #[cfg(feature = "tracing")] {
use core::fmt;
}
}
use bevy_platform::prelude::*;
cfg_if::cfg_if! {
if #[cfg(feature = "time")] {
use bevy_time::{Time, Timer, TimerMode};
use core::time::Duration;
}
}
use core::{marker::PhantomData, ops};
use dyn_clone::{DynClone, clone_trait_object};
pub trait Signal: Send + Sync + 'static {
type Item;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle;
fn register_signal(self, world: &mut World) -> SignalHandle
where
Self: Sized,
{
self.boxed().register_boxed_signal(world)
}
}
impl<U: 'static> Signal for Box<dyn Signal<Item = U> + Send + Sync> {
type Item = U;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
(*self).register_boxed_signal(world)
}
}
pub trait SignalDynClone: Signal + DynClone {}
clone_trait_object!(<T> SignalDynClone<Item = T>);
impl<T: Signal + Clone + 'static> SignalDynClone for T {}
impl<O: 'static> Signal for Box<dyn SignalDynClone<Item = O> + Send + Sync> {
type Item = O;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
(*self).register_boxed_signal(world)
}
}
pub type BoxedSignal<T> = Box<dyn SignalDynClone<Item = T> + Send + Sync>;
pub struct Source<O> {
signal: LazySignal,
_marker: PhantomData<fn() -> O>,
}
impl<O> Clone for Source<O> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<O> Signal for Source<O>
where
O: 'static,
{
type Item = O;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
SignalHandle::new(self.signal.register(world))
}
}
pub struct Map<Upstream, O> {
upstream: Upstream,
signal: LazySignal,
_marker: PhantomData<fn() -> O>,
}
impl<Upstream, O> Clone for Map<Upstream, O>
where
Upstream: Clone,
{
fn clone(&self) -> Self {
Self {
upstream: self.upstream.clone(),
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, O> Signal for Map<Upstream, O>
where
Upstream: Signal,
O: 'static,
{
type Item = O;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
let SignalHandle(upstream) = self.upstream.register(world);
let signal = self.signal.register(world);
pipe_signal(world, upstream, signal);
signal.into()
}
}
pub struct Scheduled<Sched, I> {
signal: LazySignal,
_marker: PhantomData<fn() -> (Sched, I)>,
}
impl<Sched, I> Clone for Scheduled<Sched, I> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Sched: 'static, I: 'static> Signal for Scheduled<Sched, I> {
type Item = I;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct MapComponent<Upstream, C> {
signal: Map<Upstream, C>,
}
impl<Upstream, C> Clone for MapComponent<Upstream, C>
where
Upstream: Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream, C> Signal for MapComponent<Upstream, C>
where
Upstream: Signal<Item = Entity>,
C: Component,
{
type Item = C;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
pub struct ComponentOption<Upstream, C> {
signal: Map<Upstream, Option<C>>,
}
impl<Upstream, C> Clone for ComponentOption<Upstream, C>
where
Upstream: Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream, C> Signal for ComponentOption<Upstream, C>
where
Upstream: Signal<Item = Entity>,
C: Component,
{
type Item = Option<C>;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
pub struct ComponentChanged<Upstream, C> {
signal: Map<Upstream, C>,
}
impl<Upstream, C> Clone for ComponentChanged<Upstream, C>
where
Upstream: Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream, C> Signal for ComponentChanged<Upstream, C>
where
Upstream: Signal<Item = Entity>,
C: Component,
{
type Item = C;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
pub struct HasComponent<Upstream, C> {
signal: Map<Upstream, bool>,
_marker: PhantomData<fn() -> C>,
}
impl<Upstream, C> Clone for HasComponent<Upstream, C>
where
Upstream: Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, C> Signal for HasComponent<Upstream, C>
where
Upstream: Signal<Item = Entity>,
C: Component,
{
type Item = bool;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
pub struct Dedupe<Upstream>
where
Upstream: Signal,
{
signal: Map<Upstream, Upstream::Item>,
}
impl<Upstream> Clone for Dedupe<Upstream>
where
Upstream: Signal + Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream> Signal for Dedupe<Upstream>
where
Upstream: Signal,
{
type Item = Upstream::Item;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
pub struct First<Upstream>
where
Upstream: Signal,
{
signal: Take<Upstream>,
}
impl<Upstream> Clone for First<Upstream>
where
Upstream: Signal + Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream> Signal for First<Upstream>
where
Upstream: Signal,
{
type Item = Upstream::Item;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
pub struct Take<Upstream>
where
Upstream: Signal,
{
signal: Map<Upstream, Upstream::Item>,
}
impl<Upstream> Clone for Take<Upstream>
where
Upstream: Signal + Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream> Signal for Take<Upstream>
where
Upstream: Signal,
{
type Item = Upstream::Item;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
pub struct Skip<Upstream>
where
Upstream: Signal,
{
signal: Filter<Upstream>,
}
impl<Upstream> Clone for Skip<Upstream>
where
Upstream: Signal + Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream> Signal for Skip<Upstream>
where
Upstream: Signal,
{
type Item = Upstream::Item;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
#[allow(clippy::type_complexity)]
pub struct Zip<Left, Right>
where
Left: Signal,
Right: Signal,
{
left_wrapper: Map<Left, (Option<Left::Item>, Option<Right::Item>)>,
right_wrapper: Map<Right, (Option<Left::Item>, Option<Right::Item>)>,
signal: LazySignal,
}
impl<Left, Right> Clone for Zip<Left, Right>
where
Left: Signal + Clone,
Right: Signal + Clone,
{
fn clone(&self) -> Self {
Self {
left_wrapper: self.left_wrapper.clone(),
right_wrapper: self.right_wrapper.clone(),
signal: self.signal.clone(),
}
}
}
impl<Left, Right> Signal for Zip<Left, Right>
where
Left: Signal,
Right: Signal,
{
type Item = (Left::Item, Right::Item);
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
let signal = self.signal.register(world);
let SignalHandle(left_upstream) = self.left_wrapper.register(world);
let SignalHandle(right_upstream) = self.right_wrapper.register(world);
pipe_signal(world, left_upstream, signal);
pipe_signal(world, right_upstream, signal);
SignalHandle(signal)
}
}
pub struct Eq<Upstream> {
signal: Map<Upstream, bool>,
}
impl<Upstream> Clone for Eq<Upstream>
where
Upstream: Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream> Signal for Eq<Upstream>
where
Upstream: Signal,
{
type Item = bool;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
pub struct Neq<Upstream> {
signal: Map<Upstream, bool>,
}
impl<Upstream> Clone for Neq<Upstream>
where
Upstream: Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream> Signal for Neq<Upstream>
where
Upstream: Signal,
Upstream::Item: PartialEq,
{
type Item = bool;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
pub struct Not<Upstream>
where
Upstream: Signal,
<Upstream as Signal>::Item: ops::Not,
<<Upstream as Signal>::Item as ops::Not>::Output: Clone,
{
signal: Map<Upstream, <Upstream::Item as ops::Not>::Output>,
}
impl<Upstream> Clone for Not<Upstream>
where
Upstream: Signal + Clone,
<Upstream as Signal>::Item: ops::Not,
<<Upstream as Signal>::Item as ops::Not>::Output: Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream> Signal for Not<Upstream>
where
Upstream: Signal,
<Upstream as Signal>::Item: ops::Not,
<<Upstream as Signal>::Item as ops::Not>::Output: Clone,
{
type Item = <Upstream::Item as ops::Not>::Output;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
pub struct Filter<Upstream> {
signal: LazySignal,
_marker: PhantomData<fn() -> Upstream>,
}
impl<Upstream> Clone for Filter<Upstream> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream> Signal for Filter<Upstream>
where
Upstream: Signal,
{
type Item = Upstream::Item;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct Flatten<Upstream>
where
Upstream: Signal,
Upstream::Item: Signal,
{
signal: LazySignal,
#[allow(clippy::type_complexity)]
_marker: PhantomData<fn() -> (Upstream, Upstream::Item)>,
}
impl<Upstream> Clone for Flatten<Upstream>
where
Upstream: Signal,
Upstream::Item: Signal,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream> Signal for Flatten<Upstream>
where
Upstream: Signal,
Upstream::Item: Signal,
<Upstream::Item as Signal>::Item: Clone,
{
type Item = <Upstream::Item as Signal>::Item;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct Switch<Upstream, Other>
where
Upstream: Signal,
Other: Signal,
Other::Item: Clone,
{
signal: Flatten<Map<Upstream, Other>>,
}
impl<Upstream, Other> Clone for Switch<Upstream, Other>
where
Upstream: Signal,
Other: Signal,
Other::Item: Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream, Other> Signal for Switch<Upstream, Other>
where
Upstream: Signal,
Other: Signal,
Other::Item: Clone,
{
type Item = Other::Item;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
pub struct SwitchSignalVec<Upstream, Switched>
where
Upstream: Signal,
Switched: SignalVec,
{
signal: LazySignal,
_marker: PhantomData<fn() -> (Upstream, Switched)>,
}
impl<Upstream, Switched> Clone for SwitchSignalVec<Upstream, Switched>
where
Upstream: Signal,
Switched: SignalVec,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, Switched> SignalVec for SwitchSignalVec<Upstream, Switched>
where
Upstream: Signal,
Switched: SignalVec,
Switched::Item: Clone + Send + Sync + 'static,
{
type Item = Switched::Item;
fn register_boxed_signal_vec(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct SwitchSignalMap<Upstream, Switched>
where
Upstream: Signal,
Switched: SignalMap,
{
signal: LazySignal,
_marker: PhantomData<fn() -> (Upstream, Switched)>,
}
impl<Upstream, Switched> Clone for SwitchSignalMap<Upstream, Switched>
where
Upstream: Signal,
Switched: SignalMap,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, Switched> SignalMap for SwitchSignalMap<Upstream, Switched>
where
Upstream: Signal,
Switched: SignalMap,
Switched::Key: Clone + Send + Sync + 'static,
Switched::Value: Clone + Send + Sync + 'static,
{
type Key = Switched::Key;
type Value = Switched::Value;
fn register_boxed_signal_map(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "time")] {
pub struct Throttle<Upstream>
where
Upstream: Signal,
{
signal: Map<Upstream, Upstream::Item>,
}
impl<Upstream> Clone for Throttle<Upstream>
where
Upstream: Signal + Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream> Signal for Throttle<Upstream>
where
Upstream: Signal,
{
type Item = Upstream::Item;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
}
}
pub struct MapBool<Upstream, O> {
signal: LazySignal,
_marker: PhantomData<fn() -> (Upstream, O)>,
}
impl<Upstream, O> Clone for MapBool<Upstream, O> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, O> Signal for MapBool<Upstream, O>
where
Upstream: Signal<Item = bool>,
O: 'static,
{
type Item = O;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct MapTrue<Upstream, O> {
signal: LazySignal,
_marker: PhantomData<fn() -> (Upstream, O)>,
}
impl<Upstream, O> Clone for MapTrue<Upstream, O> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, O> Signal for MapTrue<Upstream, O>
where
Upstream: Signal<Item = bool>,
O: 'static,
{
type Item = Option<O>;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct MapFalse<Upstream, O> {
signal: LazySignal,
_marker: PhantomData<fn() -> (Upstream, O)>,
}
impl<Upstream, O> Clone for MapFalse<Upstream, O> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, O> Signal for MapFalse<Upstream, O>
where
Upstream: Signal<Item = bool>,
O: 'static,
{
type Item = Option<O>;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct MapOption<Upstream, O> {
signal: LazySignal,
_marker: PhantomData<fn() -> (Upstream, O)>,
}
impl<Upstream, O> Clone for MapOption<Upstream, O> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, O> Signal for MapOption<Upstream, O>
where
Upstream: Signal,
O: 'static,
{
type Item = O;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct MapSome<Upstream, O> {
signal: LazySignal,
_marker: PhantomData<fn() -> (Upstream, O)>,
}
impl<Upstream, O> Clone for MapSome<Upstream, O> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, O> Signal for MapSome<Upstream, O>
where
Upstream: Signal,
O: 'static,
{
type Item = Option<O>;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct MapNone<Upstream, O> {
signal: LazySignal,
_marker: PhantomData<fn() -> (Upstream, O)>,
}
impl<Upstream, O> Clone for MapNone<Upstream, O> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, O> Signal for MapNone<Upstream, O>
where
Upstream: Signal,
O: 'static,
{
type Item = Option<O>;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct ToSignalVec<Upstream>
where
Upstream: Signal,
{
signal: LazySignal,
_marker: PhantomData<fn() -> Upstream>,
}
impl<Upstream> Clone for ToSignalVec<Upstream>
where
Upstream: Signal,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, T> SignalVec for ToSignalVec<Upstream>
where
Upstream: Signal<Item = Vec<T>>,
T: 'static,
{
type Item = T;
fn register_boxed_signal_vec(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "tracing")] {
pub struct Debug<Upstream>
where
Upstream: Signal,
{
signal: Map<Upstream, Upstream::Item>,
}
impl<Upstream> Clone for Debug<Upstream>
where
Upstream: Signal + Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream> Signal for Debug<Upstream>
where
Upstream: Signal,
{
type Item = Upstream::Item;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
}
}
pub fn from_system<O, IOO, F, M>(system: F) -> impl Signal<Item = O> + Clone
where
O: Clone + Send + Sync + 'static,
IOO: Into<Option<O>> + 'static,
F: IntoSystem<In<()>, IOO, M> + Send + Sync + 'static,
{
Source {
signal: lazy_signal_from_system(system),
_marker: PhantomData,
}
}
pub fn from_function<O, F>(mut f: F) -> impl Signal<Item = O> + Clone
where
O: Clone + Send + Sync + 'static,
F: FnMut() -> O + Send + Sync + 'static,
{
from_system(move |In(_)| f())
}
pub fn always<O>(value: O) -> impl Signal<Item = O> + Clone
where
O: Clone + Send + Sync + 'static,
{
from_system(move |In(_)| value.clone())
}
pub fn once<O>(value: O) -> impl Signal<Item = O> + Clone
where
O: Clone + Send + Sync + 'static,
{
from_system(move |In(_), mut emitted: Local<bool>| {
if *emitted {
None
} else {
*emitted = true;
Some(value.clone())
}
})
}
pub fn from_entity(entity: impl Into<Entity> + Send + Sync + 'static) -> impl Signal<Item = Entity> + Clone {
let mut entity = Some(entity);
from_system(move |In(_), mut cached: Local<Option<Entity>>| {
*cached.get_or_insert_with(|| entity.take().unwrap().into())
})
}
pub fn from_ancestor(
entity: impl Into<Entity> + Send + Sync + 'static,
generations: usize,
) -> impl Signal<Item = Entity> + Clone {
from_entity(entity).map(ancestor_map(generations))
}
pub fn from_parent(entity: impl Into<Entity> + Send + Sync + 'static) -> impl Signal<Item = Entity> + Clone {
from_ancestor(entity, 1)
}
pub fn from_component<C>(entity: impl Into<Entity> + Send + Sync + 'static) -> impl Signal<Item = C> + Clone
where
C: Component + Clone,
{
let mut entity = Some(entity);
from_system(move |In(_), mut cached: Local<Option<Entity>>, components: Query<&C>| {
let entity = *cached.get_or_insert_with(|| entity.take().unwrap().into());
components.get(entity).ok().cloned()
})
}
pub fn from_component_option<C>(
entity: impl Into<Entity> + Send + Sync + 'static,
) -> impl Signal<Item = Option<C>> + Clone
where
C: Component + Clone,
{
let mut entity = Some(entity);
from_system(move |In(_), mut cached: Local<Option<Entity>>, components: Query<&C>| {
let entity = *cached.get_or_insert_with(|| entity.take().unwrap().into());
Some(components.get(entity).ok().cloned())
})
}
pub fn from_component_changed<C>(entity: impl Into<Entity> + Send + Sync + 'static) -> impl Signal<Item = C> + Clone
where
C: Component + Clone,
{
let mut entity = Some(entity);
from_system(
move |In(_), mut cached: Local<Option<Entity>>, components: Query<&C, Changed<C>>| {
let entity = *cached.get_or_insert_with(|| entity.take().unwrap().into());
components.get(entity).ok().cloned()
},
)
}
pub fn from_resource<R>() -> impl Signal<Item = R> + Clone
where
R: Resource + Clone,
{
from_system(move |In(_), resource: Option<Res<R>>| resource.as_deref().cloned())
}
pub fn from_resource_option<R>() -> impl Signal<Item = Option<R>> + Clone
where
R: Resource + Clone,
{
from_system(move |In(_), resource: Option<Res<R>>| Some(resource.as_deref().cloned()))
}
pub fn from_resource_changed<R>() -> impl Signal<Item = R> + Clone
where
R: Resource + Clone,
{
from_system(move |In(_), resource: Option<Res<R>>| resource.filter(|r| r.is_changed()).map(|r| r.clone()))
}
pub fn option<S>(value: Option<S>) -> impl Signal<Item = Option<S::Item>> + Clone
where
S: Signal + Clone,
S::Item: Clone + Send + Sync + 'static,
{
match value {
Some(signal) => signal.map_in(Some).left_either(),
None => once(None).right_either(),
}
}
#[allow(missing_docs)]
pub enum SignalEither<L, R>
where
L: Signal,
R: Signal,
{
Left(L),
Right(R),
}
impl<L, R> Clone for SignalEither<L, R>
where
L: Signal + Clone,
R: Signal + Clone,
{
fn clone(&self) -> Self {
match self {
Self::Left(left) => Self::Left(left.clone()),
Self::Right(right) => Self::Right(right.clone()),
}
}
}
impl<T, L: Signal<Item = T>, R: Signal<Item = T>> Signal for SignalEither<L, R>
where
L: Signal<Item = T>,
R: Signal<Item = T>,
{
type Item = T;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
match *self {
SignalEither::Left(left) => left.register_signal(world),
SignalEither::Right(right) => right.register_signal(world),
}
}
}
pub trait IntoSignalEither: Sized
where
Self: Signal,
{
fn left_either<R>(self) -> SignalEither<Self, R>
where
R: Signal,
{
SignalEither::Left(self)
}
fn right_either<L>(self) -> SignalEither<L, Self>
where
L: Signal,
{
SignalEither::Right(self)
}
}
impl<T: Signal> IntoSignalEither for T {}
pub trait SignalExt: Signal {
fn map<O, IOO, F, M>(self, system: F) -> Map<Self, O>
where
Self: Sized,
Self::Item: 'static,
O: Clone + Send + Sync + 'static,
IOO: Into<Option<O>> + 'static,
F: IntoSystem<In<Self::Item>, IOO, M> + Send + Sync + 'static,
{
Map {
upstream: self,
signal: lazy_signal_from_system(system),
_marker: PhantomData,
}
}
fn map_in<O, IOO, F>(self, mut function: F) -> Map<Self, O>
where
Self: Sized,
Self::Item: 'static,
O: Clone + Send + Sync + 'static,
IOO: Into<Option<O>> + 'static,
F: FnMut(Self::Item) -> IOO + Send + Sync + 'static,
{
self.map(move |In(item)| function(item))
}
fn map_in_ref<O, IOO, F>(self, mut function: F) -> Map<Self, O>
where
Self: Sized,
Self::Item: 'static,
O: Clone + Send + Sync + 'static,
IOO: Into<Option<O>> + 'static,
F: FnMut(&Self::Item) -> IOO + Send + Sync + 'static,
{
self.map(move |In(item)| function(&item))
}
fn component<C>(self) -> MapComponent<Self, C>
where
Self: Sized,
Self: Signal<Item = Entity>,
C: Component + Clone,
{
MapComponent {
signal: self.map(|In(entity): In<Entity>, components: Query<&C>| components.get(entity).ok().cloned()),
}
}
fn component_option<C>(self) -> ComponentOption<Self, C>
where
Self: Sized,
Self: Signal<Item = Entity>,
C: Component + Clone,
{
ComponentOption {
signal: self
.map(|In(entity): In<Entity>, components: Query<&C>| Some(components.get(entity).ok().cloned())),
}
}
fn component_changed<C>(self) -> ComponentChanged<Self, C>
where
Self: Sized,
Self: Signal<Item = Entity>,
C: Component + Clone,
{
ComponentChanged {
signal: self
.map(|In(entity): In<Entity>, components: Query<&C, Changed<C>>| components.get(entity).ok().cloned()),
}
}
fn has_component<C>(self) -> HasComponent<Self, C>
where
Self: Sized,
Self: Signal<Item = Entity>,
C: Component,
{
HasComponent {
signal: self.map(|In(entity): In<Entity>, components: Query<&C>| components.contains(entity)),
_marker: PhantomData,
}
}
fn dedupe(self) -> Dedupe<Self>
where
Self: Sized,
Self::Item: PartialEq + Clone + Send + Sync + 'static,
{
Dedupe {
signal: self.map(|In(current): In<Self::Item>, mut cache: Local<Option<Self::Item>>| {
let mut changed = false;
if let Some(ref p) = *cache {
if *p != current {
changed = true;
}
} else {
changed = true;
}
if changed {
*cache = Some(current.clone());
Some(current)
} else {
None
}
}),
}
}
fn take(self, count: usize) -> Take<Self>
where
Self: Sized,
Self::Item: Clone + Send + Sync + 'static,
{
Take {
signal: self.map(move |In(item): In<Self::Item>, mut emitted: Local<usize>| {
if *emitted >= count {
None
} else {
*emitted += 1;
Some(item)
}
}),
}
}
fn skip(self, count: usize) -> Skip<Self>
where
Self: Sized,
Self::Item: Clone + Send + Sync + 'static,
{
Skip {
signal: self.filter(move |_: In<Self::Item>, mut skipped: Local<usize>| {
if *skipped < count {
*skipped += 1;
false
} else {
true
}
}),
}
}
fn first(self) -> First<Self>
where
Self: Sized,
Self::Item: Clone + Send + Sync + 'static,
{
First { signal: self.take(1) }
}
fn eq(self, value: Self::Item) -> Eq<Self>
where
Self: Sized,
Self::Item: PartialEq + Send + Sync,
{
Eq {
signal: self.map(move |In(item): In<Self::Item>| item == value),
}
}
fn neq(self, value: Self::Item) -> Neq<Self>
where
Self: Sized,
Self::Item: PartialEq + Send + Sync,
{
Neq {
signal: self.map(move |In(item): In<Self::Item>| item != value),
}
}
fn not(self) -> Not<Self>
where
Self: Sized,
<Self as Signal>::Item: ops::Not + 'static,
<<Self as Signal>::Item as ops::Not>::Output: Clone + Send + Sync,
{
Not {
signal: self.map(|In(item): In<Self::Item>| ops::Not::not(item)),
}
}
fn filter<M>(self, predicate: impl IntoSystem<In<Self::Item>, bool, M> + Send + Sync + 'static) -> Filter<Self>
where
Self: Sized,
Self::Item: Clone + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let system = world.register_system(predicate);
let SignalHandle(signal) = self
.map::<Self::Item, _, _, _>(move |In(item): In<Self::Item>, world: &mut World| {
match world.run_system_with(system, item.clone()) {
Ok(true) => Some(item),
Ok(false) | Err(_) => None,
}
})
.register(world);
world.entity_mut(*signal).add_child(system.entity());
signal
});
Filter {
signal,
_marker: PhantomData,
}
}
fn zip<Other>(self, other: Other) -> Zip<Self, Other>
where
Self: Sized,
Other: Signal,
Self::Item: Clone + Send + Sync + 'static,
Other::Item: Clone + Send + Sync + 'static,
{
let left_wrapper = self.map(|In(left): In<Self::Item>| (Some(left), None::<Other::Item>));
let right_wrapper = other.map(|In(right): In<Other::Item>| (None::<Self::Item>, Some(right)));
let signal = lazy_signal_from_system::<_, (Self::Item, Other::Item), _, _, _>(
#[allow(clippy::type_complexity)]
move |In((left_option, right_option)): In<(Option<Self::Item>, Option<Other::Item>)>,
mut left_cache: Local<Option<Self::Item>>,
mut right_cache: Local<Option<Other::Item>>| {
if left_option.is_some() {
*left_cache = left_option;
}
if right_option.is_some() {
*right_cache = right_option;
}
if left_cache.is_some() && right_cache.is_some() {
left_cache.clone().zip(right_cache.clone())
} else {
None
}
},
);
Zip {
left_wrapper,
right_wrapper,
signal,
}
}
fn flatten(self) -> Flatten<Self>
where
Self: Sized,
Self::Item: Signal + Clone + 'static,
<Self::Item as Signal>::Item: Clone + Send + Sync,
{
#[derive(Component)]
struct FlattenState<T> {
value: Option<T>,
}
let signal = LazySignal::new(move |world: &mut World| {
let reader_entity = LazyEntity::new();
let reader_system = *from_system::<<Self::Item as Signal>::Item, _, _, _>(
clone!((reader_entity) move |In(_), mut query: Query<&mut FlattenState<<Self::Item as Signal>::Item>>| {
query.get_mut(*reader_entity).unwrap().value.take()
}),
)
.register(world);
reader_entity.set(*reader_system);
world
.entity_mut(*reader_system)
.insert(FlattenState::<<Self::Item as Signal>::Item> { value: None });
let manager_system = self
.map(
move |In(inner_signal): In<Self::Item>,
world: &mut World,
mut active_forwarder: Local<Option<SignalHandle>>,
mut active_signal_id: Local<Option<SignalSystem>>| {
let new_signal_id = inner_signal.clone().register(world);
if Some(*new_signal_id) == *active_signal_id {
new_signal_id.cleanup(world);
return;
}
if let Some(old_handle) = active_forwarder.take() {
old_handle.cleanup(world);
}
let initial_value = poll_signal(world, *new_signal_id)
.and_then(downcast_any_clone::<<Self::Item as Signal>::Item>);
if let Some(value) = initial_value {
world
.get_mut::<FlattenState<<Self::Item as Signal>::Item>>(*reader_system)
.unwrap()
.value = Some(value);
}
let forwarder_handle = inner_signal
.map(move |In(value), world: &mut World| {
world
.get_mut::<FlattenState<<Self::Item as Signal>::Item>>(*reader_system)
.unwrap()
.value = Some(value);
trigger_signal_subgraph(world, [reader_system], Box::new(()));
})
.register(world);
*active_forwarder = Some(forwarder_handle);
*active_signal_id = Some(*new_signal_id);
trigger_signal_subgraph(world, [reader_system], Box::new(()));
},
)
.register(world);
world
.entity_mut(*reader_system)
.insert(SignalHandles::from([manager_system]));
reader_system
});
Flatten {
signal,
_marker: PhantomData,
}
}
fn switch<S, F, M>(self, switcher: F) -> Switch<Self, S>
where
Self: Sized,
Self::Item: 'static,
S: Signal + Clone + 'static,
S::Item: Clone + Send + Sync,
F: IntoSystem<In<Self::Item>, S, M> + Send + Sync + 'static,
{
Switch {
signal: self.map(switcher).flatten(),
}
}
fn switch_signal_vec<S, F, M>(self, switcher: F) -> SwitchSignalVec<Self, S>
where
Self: Sized,
S: SignalVec + Clone,
S::Item: Clone + Send + Sync + 'static,
F: IntoSystem<In<Self::Item>, S, M> + Send + Sync + 'static,
{
#[derive(Component)]
struct SwitcherQueue<T: Send + Sync + 'static>(Vec<VecDiff<T>>);
let signal = LazySignal::new(move |world: &mut World| {
let output_signal_entity = LazyEntity::new();
let output_signal = *from_system::<Vec<VecDiff<S::Item>>, _, _, _>(
clone!((output_signal_entity) move |In(_), mut q: Query<&mut SwitcherQueue<S::Item>>| {
let mut queue = q.get_mut(*output_signal_entity).unwrap();
if queue.0.is_empty() {
None
} else {
Some(core::mem::take(&mut queue.0))
}
}),
)
.register(world);
output_signal_entity.set(*output_signal);
world
.entity_mut(*output_signal)
.insert(SwitcherQueue(Vec::<VecDiff<S::Item>>::new()));
let manager_system =
move |In(inner_signal_vec): In<S>,
world: &mut World,
mut active_forwarder: Local<Option<SignalHandle>>,
mut active_signal: Local<Option<SignalSystem>>| {
let new_signal_handle = inner_signal_vec.clone().register_signal_vec(world);
let new_signal = *new_signal_handle;
if Some(new_signal) == *active_signal {
new_signal_handle.cleanup(world);
return;
}
if let Some(old_handle) = active_forwarder.take() {
old_handle.cleanup(world);
}
*active_signal = Some(new_signal);
let forwarder_logic = move |In(diffs): In<Vec<VecDiff<S::Item>>>, world: &mut World| {
if !diffs.is_empty() {
if let Some(mut queue) = world.get_mut::<SwitcherQueue<S::Item>>(*output_signal) {
queue.0.extend(diffs);
trigger_signal_subgraph(world, [output_signal], Box::new(()));
}
}
};
let new_forwarder_handle = inner_signal_vec.for_each(forwarder_logic).register(world);
*active_forwarder = Some(new_forwarder_handle);
let mut upstreams = SystemState::<Query<&Upstream>>::new(world);
let upstreams = upstreams.get(world);
let upstreams = UpstreamIter::new(&upstreams, new_signal).collect::<Vec<_>>();
for signal in [new_signal].into_iter().chain(upstreams.into_iter()) {
let entity = *signal;
if world.get::<super::signal_vec::VecReplayTrigger>(entity).is_some() {
world.entity_mut(entity).insert(ReplayOnce);
trigger_replay::<super::signal_vec::VecReplayTrigger>(world, entity);
break;
}
}
};
let manager_handle = self.map(switcher).map(manager_system).register(world);
world
.entity_mut(*output_signal)
.insert(SignalHandles::from([manager_handle]));
output_signal
});
SwitchSignalVec {
signal,
_marker: PhantomData,
}
}
fn switch_signal_map<S, F, M>(self, switcher: F) -> SwitchSignalMap<Self, S>
where
Self: Sized,
S: SignalMap + Clone,
S::Key: Clone + Send + Sync + 'static,
S::Value: Clone + Send + Sync + 'static,
F: IntoSystem<In<Self::Item>, S, M> + Send + Sync + 'static,
{
#[derive(Component)]
struct SwitcherQueue<K: Send + Sync + 'static, V: Send + Sync + 'static>(Vec<super::signal_map::MapDiff<K, V>>);
let signal = LazySignal::new(move |world: &mut World| {
let output_signal_entity = LazyEntity::new();
let output_signal = *from_system::<Vec<super::signal_map::MapDiff<S::Key, S::Value>>, _, _, _>(
clone!((output_signal_entity) move |In(_), mut q: Query<&mut SwitcherQueue<S::Key, S::Value>>| {
let mut queue = q.get_mut(*output_signal_entity).unwrap();
if queue.0.is_empty() {
None
} else {
Some(core::mem::take(&mut queue.0))
}
}),
)
.register(world);
output_signal_entity.set(*output_signal);
world
.entity_mut(*output_signal)
.insert(SwitcherQueue(Vec::<super::signal_map::MapDiff<S::Key, S::Value>>::new()));
let manager_system =
move |In(inner_signal_map): In<S>,
world: &mut World,
mut active_forwarder: Local<Option<SignalHandle>>,
mut active_signal: Local<Option<SignalSystem>>| {
let new_signal_handle = inner_signal_map.clone().register_signal_map(world);
let new_signal = *new_signal_handle;
if Some(new_signal) == *active_signal {
new_signal_handle.cleanup(world);
return;
}
if let Some(old_handle) = active_forwarder.take() {
old_handle.cleanup(world);
}
*active_signal = Some(new_signal);
let forwarder_logic = move |In(diffs): In<Vec<super::signal_map::MapDiff<S::Key, S::Value>>>,
world: &mut World| {
if !diffs.is_empty() {
if let Some(mut queue) = world.get_mut::<SwitcherQueue<S::Key, S::Value>>(*output_signal) {
queue.0.extend(diffs);
trigger_signal_subgraph(world, [output_signal], Box::new(()));
}
}
};
let new_forwarder_handle = inner_signal_map.for_each(forwarder_logic).register(world);
*active_forwarder = Some(new_forwarder_handle);
let mut upstreams = SystemState::<Query<&Upstream>>::new(world);
let upstreams = upstreams.get(world);
let upstreams = UpstreamIter::new(&upstreams, new_signal).collect::<Vec<_>>();
for signal in [new_signal].into_iter().chain(upstreams.into_iter()) {
let entity = *signal;
if world.get::<super::signal_map::MapReplayTrigger>(entity).is_some() {
world.entity_mut(entity).insert(ReplayOnce);
trigger_replay::<super::signal_map::MapReplayTrigger>(world, entity);
break;
}
}
};
let manager_handle = self.map(switcher).map(manager_system).register(world);
world
.entity_mut(*output_signal)
.insert(SignalHandles::from([manager_handle]));
output_signal
});
SwitchSignalMap {
signal,
_marker: PhantomData,
}
}
#[cfg(feature = "time")]
fn throttle(self, duration: Duration) -> Throttle<Self>
where
Self: Sized,
Self::Item: Clone + Send + Sync + 'static,
{
Throttle {
signal: self.map(
move |In(item): In<Self::Item>, time: Res<Time>, mut timer_option: Local<Option<Timer>>| {
match timer_option.as_mut() {
None => {
*timer_option = Some(Timer::new(duration, TimerMode::Once));
Some(item)
}
Some(timer) => {
if timer.tick(time.delta()).is_finished() {
timer.reset();
Some(item)
} else {
None
}
}
}
},
),
}
}
fn map_bool<O, IOO, TF, FF, TM, FM>(self, true_system: TF, false_system: FF) -> MapBool<Self, O>
where
Self: Sized,
Self: Signal<Item = bool>,
O: Clone + Send + Sync + 'static,
IOO: Into<Option<O>> + 'static,
TF: IntoSystem<In<()>, IOO, TM> + Send + Sync + 'static,
FF: IntoSystem<In<()>, IOO, FM> + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let true_system = world.register_system(true_system);
let false_system = world.register_system(false_system);
let SignalHandle(signal) = self
.map::<O, _, _, _>(move |In(item): In<Self::Item>, world: &mut World| {
world
.run_system_with(if item { true_system } else { false_system }, ())
.ok()
.and_then(Into::into)
})
.register(world);
world
.entity_mut(*signal)
.add_child(true_system.entity())
.add_child(false_system.entity());
signal
});
MapBool {
signal,
_marker: PhantomData,
}
}
fn map_bool_in<O, IOO, TF, FF>(self, mut true_fn: TF, mut false_fn: FF) -> MapBool<Self, O>
where
Self: Sized,
Self: Signal<Item = bool>,
O: Clone + Send + Sync + 'static,
IOO: Into<Option<O>> + 'static,
TF: FnMut() -> IOO + Send + Sync + 'static,
FF: FnMut() -> IOO + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let SignalHandle(signal) = self
.map::<O, _, _, _>(
move |In(item): In<Self::Item>| {
if item { true_fn().into() } else { false_fn().into() }
},
)
.register(world);
signal
});
MapBool {
signal,
_marker: PhantomData,
}
}
fn map_true<O, F, M>(self, system: F) -> MapTrue<Self, O>
where
Self: Sized,
Self: Signal<Item = bool>,
O: Clone + Send + Sync + 'static,
F: IntoSystem<In<()>, O, M> + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let true_system = world.register_system(system);
let SignalHandle(signal) = self
.map::<Option<O>, _, _, _>(move |In(item): In<Self::Item>, world: &mut World| {
if item {
Some(world.run_system_with(true_system, ()).ok())
} else {
Some(None)
}
})
.register(world);
world.entity_mut(*signal).add_child(true_system.entity());
signal
});
MapTrue {
signal,
_marker: PhantomData,
}
}
fn map_true_in<O, F>(self, mut function: F) -> MapTrue<Self, O>
where
Self: Sized,
Self: Signal<Item = bool>,
O: Clone + Send + Sync + 'static,
F: FnMut() -> O + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let SignalHandle(signal) = self
.map::<Option<O>, _, _, _>(
move |In(item): In<Self::Item>| {
if item { Some(Some(function())) } else { Some(None) }
},
)
.register(world);
signal
});
MapTrue {
signal,
_marker: PhantomData,
}
}
fn map_false<O, F, M>(self, system: F) -> MapFalse<Self, O>
where
Self: Sized,
Self: Signal<Item = bool>,
O: Clone + Send + Sync + 'static,
F: IntoSystem<In<()>, O, M> + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let false_system = world.register_system(system);
let SignalHandle(signal) = self
.map::<Option<O>, _, _, _>(move |In(item): In<Self::Item>, world: &mut World| {
if !item {
Some(world.run_system_with(false_system, ()).ok())
} else {
Some(None)
}
})
.register(world);
world.entity_mut(*signal).add_child(false_system.entity());
signal
});
MapFalse {
signal,
_marker: PhantomData,
}
}
fn map_false_in<O, F>(self, mut function: F) -> MapFalse<Self, O>
where
Self: Sized,
Self: Signal<Item = bool>,
O: Clone + Send + Sync + 'static,
F: FnMut() -> O + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let SignalHandle(signal) = self
.map::<Option<O>, _, _, _>(
move |In(item): In<Self::Item>| {
if !item { Some(Some(function())) } else { Some(None) }
},
)
.register(world);
signal
});
MapFalse {
signal,
_marker: PhantomData,
}
}
fn map_option<I, O, IOO, SF, NF, SM, NM>(self, some_system: SF, none_system: NF) -> MapOption<Self, O>
where
Self: Sized,
Self: Signal<Item = Option<I>>,
I: 'static,
O: Clone + Send + Sync + 'static,
IOO: Into<Option<O>> + 'static,
SF: IntoSystem<In<I>, IOO, SM> + Send + Sync + 'static,
NF: IntoSystem<In<()>, IOO, NM> + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let some_system = world.register_system(some_system);
let none_system = world.register_system(none_system);
let SignalHandle(signal) = self
.map::<O, _, _, _>(move |In(item): In<Self::Item>, world: &mut World| match item {
Some(value) => world.run_system_with(some_system, value).ok().and_then(Into::into),
None => world.run_system_with(none_system, ()).ok().and_then(Into::into),
})
.register(world);
world
.entity_mut(*signal)
.add_child(some_system.entity())
.add_child(none_system.entity());
signal
});
MapOption {
signal,
_marker: PhantomData,
}
}
fn map_option_in<I, O, IOO, SF, NF>(self, mut some_fn: SF, mut none_fn: NF) -> MapOption<Self, O>
where
Self: Sized,
Self: Signal<Item = Option<I>>,
I: 'static,
O: Clone + Send + Sync + 'static,
IOO: Into<Option<O>> + 'static,
SF: FnMut(I) -> IOO + Send + Sync + 'static,
NF: FnMut() -> IOO + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let SignalHandle(signal) = self
.map::<O, _, _, _>(move |In(item): In<Self::Item>| match item {
Some(value) => some_fn(value).into(),
None => none_fn().into(),
})
.register(world);
signal
});
MapOption {
signal,
_marker: PhantomData,
}
}
fn map_some<I, O, F, M>(self, system: F) -> MapSome<Self, O>
where
Self: Sized,
Self: Signal<Item = Option<I>>,
I: 'static,
O: Clone + Send + Sync + 'static,
F: IntoSystem<In<I>, O, M> + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let some_system = world.register_system(system);
let SignalHandle(signal) = self
.map::<Option<O>, _, _, _>(move |In(item): In<Self::Item>, world: &mut World| match item {
Some(value) => Some(world.run_system_with(some_system, value).ok()),
None => Some(None),
})
.register(world);
world.entity_mut(*signal).add_child(some_system.entity());
signal
});
MapSome {
signal,
_marker: PhantomData,
}
}
fn map_some_in<I, O, F>(self, mut function: F) -> MapSome<Self, O>
where
Self: Sized,
Self: Signal<Item = Option<I>>,
I: 'static,
O: Clone + Send + Sync + 'static,
F: FnMut(I) -> O + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let SignalHandle(signal) = self
.map::<Option<O>, _, _, _>(move |In(item): In<Self::Item>| match item {
Some(value) => Some(Some(function(value))),
None => Some(None),
})
.register(world);
signal
});
MapSome {
signal,
_marker: PhantomData,
}
}
fn map_none<I, O, F, M>(self, none_system: F) -> MapNone<Self, O>
where
Self: Sized,
Self: Signal<Item = Option<I>>,
I: 'static,
O: Clone + Send + Sync + 'static,
F: IntoSystem<In<()>, O, M> + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let none_system = world.register_system(none_system);
let SignalHandle(signal) = self
.map::<Option<O>, _, _, _>(move |In(item): In<Self::Item>, world: &mut World| match item {
Some(_) => Some(None),
None => Some(world.run_system_with(none_system, ()).ok()),
})
.register(world);
world.entity_mut(*signal).add_child(none_system.entity());
signal
});
MapNone {
signal,
_marker: PhantomData,
}
}
fn map_none_in<I, O, F>(self, mut function: F) -> MapNone<Self, O>
where
Self: Sized,
Self: Signal<Item = Option<I>>,
I: 'static,
O: Clone + Send + Sync + 'static,
F: FnMut() -> O + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let SignalHandle(signal) = self
.map::<Option<O>, _, _, _>(move |In(item): In<Self::Item>| match item {
Some(_) => Some(None),
None => Some(Some(function())),
})
.register(world);
signal
});
MapNone {
signal,
_marker: PhantomData,
}
}
fn to_signal_vec<T>(self) -> ToSignalVec<Self>
where
Self: Sized,
Self: Signal<Item = Vec<T>>,
T: PartialEq + Clone + Send + Sync + 'static,
{
let lazy_signal = LazySignal::new(move |world: &mut World| {
let handle = self
.dedupe()
.map(|In(items): In<Vec<T>>| vec![VecDiff::Replace { values: items }])
.register(world);
*handle
});
ToSignalVec {
signal: lazy_signal,
_marker: PhantomData,
}
}
#[cfg(feature = "tracing")]
#[track_caller]
fn debug(self) -> Debug<Self>
where
Self: Sized,
Self::Item: fmt::Debug + Clone + Send + Sync + 'static,
{
let location = core::panic::Location::caller();
Debug {
signal: self.map(move |In(item)| {
bevy_log::debug!("[{}] {:#?}", location, item);
item
}),
}
}
fn boxed(self) -> Box<dyn Signal<Item = Self::Item>>
where
Self: Sized,
{
Box::new(self)
}
fn boxed_clone(self) -> Box<dyn SignalDynClone<Item = Self::Item> + Send + Sync>
where
Self: Sized + Clone,
{
Box::new(self)
}
fn schedule<Sched: ScheduleLabel + Default + 'static>(self) -> Scheduled<Sched, Self::Item>
where
Self: Sized + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let handle = self.register_signal(world);
apply_schedule_to_signal(world, *handle, Sched::default().intern());
*handle
});
Scheduled {
signal,
_marker: PhantomData,
}
}
fn register(self, world: &mut World) -> SignalHandle
where
Self: Sized,
{
self.register_signal(world)
}
}
impl<T: ?Sized> SignalExt for T where T: Signal {}
#[macro_export]
macro_rules! eq {
($s1:expr, $s2:expr $(, $rest:expr)* $(,)?) => {
$crate::__signal_zip_and_map!($s1, $s2 $(, $rest)*; |val| {
$crate::eq!(@check val, $s1, $s2 $(, $rest)*)
})
};
(@check $val:expr, $a:expr, $b:expr) => {
$val.0 == $val.1
};
(@check $val:expr, $head:expr, $($tail:expr),+) => {
$val.1 == $val.0.1 && $crate::eq!(@check $val.0, $($tail),+)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __signal_zip_and_map {
($s1:expr $(,)?; |$val:ident| $body:block) => {
$s1.map(|In($val)| $body)
};
($s1:expr, $s2:expr $(, $rest:expr)* $(,)?; |$val:ident| $body:block) => {
$crate::__signal_zip_and_map!(@combine $s1, $s2 $(, $rest)*)
.map(|In($val @ $crate::__signal_zip_and_map!(@pattern $s1, $s2 $(, $rest)*))| $body)
};
(@combine $first:expr, $second:expr) => {
$first.zip($second)
};
(@combine $first:expr, $second:expr, $($rest:expr),+) => {
$crate::__signal_zip_and_map!(@combine $first.zip($second), $($rest),+)
};
(@pattern $s1:expr, $s2:expr $(, $rest:expr)*) => {
$crate::__signal_zip_and_map!(@pattern_helper (_, _) $(, $rest)*)
};
(@pattern_helper $acc:tt $(,)?) => { $acc };
(@pattern_helper $acc:tt, $head:expr $(, $tail:expr)*) => {
$crate::__signal_zip_and_map!(@pattern_helper ($acc, _) $(, $tail)*)
};
}
#[macro_export]
macro_rules! all {
($($args:expr),+ $(,)?) => {
$crate::__signal_reduce_binop!(&&; $($args),+)
};
}
#[macro_export]
macro_rules! any {
($($args:expr),+ $(,)?) => {
$crate::__signal_reduce_binop!(||; $($args),+)
};
}
#[macro_export]
macro_rules! distinct {
($($args:expr),+ $(,)?) => {
$crate::__signal_pairwise_all!(__signal_distinct_pair; $($args),+)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __signal_distinct_pair {
($a:expr, $b:expr) => {
$crate::eq!($a.clone(), $b.clone()).not()
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __signal_pairwise_all {
($pair:ident; $a:expr, $b:expr $(,)?) => {
$crate::$pair!($a, $b)
};
($pair:ident; $head:expr, $next:expr, $($tail:expr),+ $(,)?) => {
$crate::all!(
$crate::__signal_pairwise_all!(@with_head $pair; $head, $next, $($tail),+),
$crate::__signal_pairwise_all!($pair; $next, $($tail),+)
)
};
(@with_head $pair:ident; $head:expr, $last:expr $(,)?) => {
$crate::$pair!($head, $last)
};
(@with_head $pair:ident; $head:expr, $next:expr, $($tail:expr),+ $(,)?) => {
$crate::all!(
$crate::$pair!($head, $next),
$crate::__signal_pairwise_all!(@with_head $pair; $head, $($tail),+)
)
};
}
#[macro_export]
macro_rules! sum {
($($args:expr),+ $(,)?) => {
$crate::__signal_reduce_binop!(+; $($args),+)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __signal_reduce_binop {
($op:tt; $s1:expr $(,)?) => {
$s1
};
($op:tt; $s1:expr, $s2:expr $(, $rest:expr)* $(,)?) => {
$crate::__signal_zip_and_map!($s1, $s2 $(, $rest)*; |val| {
$crate::__signal_reduce_binop!(@apply $op, val, $s1, $s2 $(, $rest)*)
})
};
(@apply $op:tt, $val:expr, $a:expr, $b:expr) => {
$val.0 $op $val.1
};
(@apply $op:tt, $val:expr, $head:expr, $($tail:expr),+) => {
$val.1 $op $crate::__signal_reduce_binop!(@apply $op, $val.0, $($tail),+)
};
}
#[macro_export]
macro_rules! product {
($($args:expr),+ $(,)?) => {
$crate::__signal_reduce_binop!(*; $($args),+)
};
}
#[macro_export]
macro_rules! min {
($($args:expr),+ $(,)?) => {
$crate::__signal_reduce_cmp!(<; $($args),+)
};
}
#[macro_export]
macro_rules! max {
($($args:expr),+ $(,)?) => {
$crate::__signal_reduce_cmp!(>; $($args),+)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __signal_reduce_cmp {
($cmp:tt; $s1:expr $(,)?) => {
$s1
};
($cmp:tt; $s1:expr, $s2:expr $(, $rest:expr)* $(,)?) => {
$crate::__signal_zip_and_map!($s1, $s2 $(, $rest)*; |val| {
$crate::__signal_reduce_cmp!(@select $cmp, val, $s1, $s2 $(, $rest)*)
})
};
(@select $cmp:tt, $val:expr, $a:expr, $b:expr) => {
if $val.0 $cmp $val.1 { $val.0 } else { $val.1 }
};
(@select $cmp:tt, $val:expr, $head:expr, $($tail:expr),+) => {
{
let rhs = $crate::__signal_reduce_cmp!(@select $cmp, $val.0, $($tail),+);
if $val.1 $cmp rhs { $val.1 } else { rhs }
}
};
}
#[macro_export]
macro_rules! zip {
($s1:expr $(,)?) => {
$s1.map(|In(__v)| (__v,))
};
($s1:expr, $s2:expr $(,)?) => {
$s1.zip($s2)
};
($s1:expr, $s2:expr $(, $rest:expr)+ $(,)?) => {
$crate::__signal_zip_and_map!($s1, $s2 $(, $rest)+; |__v| {
$crate::__signal_zip_flatten!(__v; $s1, $s2 $(, $rest)+;)
})
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __signal_zip_flatten {
($val:expr; $($signals:expr),+;) => {
$crate::__signal_zip_flatten!(@collect $val; $($signals),+; ())
};
(@collect $val:expr; $s1:expr, $s2:expr; ($($acc:expr),*)) => {
($val.0, $val.1 $(, $acc)*)
};
(@collect $val:expr; $head:expr, $($tail:expr),+; ($($acc:expr),*)) => {
$crate::__signal_zip_flatten!(@collect $val.0; $($tail),+; ($val.1 $(, $acc)*))
};
}
pub use all;
pub use any;
pub use distinct;
pub use eq;
pub use max;
pub use min;
pub use product;
pub use sum;
pub use zip;
#[cfg(test)]
mod tests {
use crate::{
JonmoPlugin,
graph::{LazySignalHolder, SignalRegistrationCount},
prelude::{IntoSignalVecEither, SignalVecExt, clone},
signal::{self, BoxedSignal, SignalExt, Upstream},
signal_vec::{MutableVec, VecDiff},
utils::LazyEntity,
};
use core::{convert::identity, fmt};
use bevy::prelude::*;
use bevy_platform::sync::*;
use bevy_time::TimePlugin;
use core::time::Duration;
use test_log::test;
#[derive(Component, Clone, Debug, PartialEq, Reflect, Default, Resource)]
struct TestData(i32);
#[derive(Resource, Default, Debug)]
struct SignalOutput<T: Send + Sync + 'static + Clone + fmt::Debug>(Option<T>);
#[derive(Resource, Default, Debug)]
struct SignalOutputVec<T: Send + Sync + 'static + Clone + fmt::Debug>(Vec<T>);
fn create_test_app() -> App {
let mut app = App::new();
app.add_plugins((MinimalPlugins, JonmoPlugin::default()));
app.register_type::<TestData>();
app
}
fn capture_output<T: Send + Sync + 'static + Clone + fmt::Debug>(
In(value): In<T>,
mut output: ResMut<SignalOutput<T>>,
) {
#[cfg(feature = "tracing")]
bevy_log::debug!(
"Capture Output System: Received {:?}, updating resource from {:?} to Some({:?})",
value,
output.0,
value
);
output.0 = Some(value);
}
fn get_output<T: Send + Sync + 'static + Clone + fmt::Debug>(world: &World) -> Option<T> {
world.resource::<SignalOutput<T>>().0.clone()
}
fn capture_output_vec<T: Send + Sync + 'static + Clone + fmt::Debug>(
In(value): In<T>,
mut output: ResMut<SignalOutputVec<T>>,
) {
output.0.push(value);
}
fn get_and_clear_output_vec<T: Send + Sync + 'static + Clone + fmt::Debug>(world: &mut World) -> Vec<T> {
world
.get_resource_mut::<SignalOutputVec<T>>()
.map(|mut res| core::mem::take(&mut res.0))
.unwrap_or_default()
}
#[test]
fn test_map() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let signal = signal::from_system(|In(_)| 1)
.map(|In(x): In<i32>| x + 1)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(2));
signal.cleanup(app.world_mut());
}
#[test]
fn test_map_in() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<String>>();
let signal = signal::from_system(|In(_)| 10i32)
.map_in(|x: i32| format!("Value: {x}"))
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<String>(app.world()),
Some("Value: 10".to_string()),
"Infallible mapping from i32 to String failed."
);
signal.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<String>>().0 = None;
app.init_resource::<SignalOutput<i32>>();
let inputs = Arc::new(Mutex::new(vec![1, 2, 3, 4]));
let source_signal = signal::from_system(clone!((inputs) move |In(_)| {
inputs.lock().unwrap().pop()
}));
let signal = source_signal
.map_in(|x: i32| if x % 2 == 0 { Some(x * 10) } else { None })
.map(capture_output::<i32>)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(40), "Failed on first valid item.");
app.update();
assert_eq!(
get_output::<i32>(app.world()),
Some(40),
"Output should not change when closure returns None."
);
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(20), "Failed on second valid item.");
app.update();
assert_eq!(
get_output::<i32>(app.world()),
Some(20),
"Output should not change on subsequent None."
);
signal.cleanup(app.world_mut());
app.init_resource::<SignalOutput<()>>(); let call_count = Arc::new(Mutex::new(0));
let signal = signal::from_system(|In(_)| ())
.map_in(clone!((call_count) move |_: ()| {
*call_count.lock().unwrap() += 1;
}))
.map(capture_output)
.register(app.world_mut());
app.update();
app.update();
app.update();
assert_eq!(
*call_count.lock().unwrap(),
3,
"FnMut closure should be called on each update"
);
signal.cleanup(app.world_mut());
app.init_resource::<SignalOutput<i32>>();
let signal = signal::from_system(|In(_)| 123)
.map_in(|x| x)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(123));
signal.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<i32>>().0 = None;
app.update();
assert_eq!(
get_output::<i32>(app.world()),
None,
"Signal should not fire after cleanup"
);
}
#[test]
fn test_map_in_ref() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<String>>();
let signal = signal::from_system(|In(_)| 42i32)
.map_in_ref(ToString::to_string)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<String>(app.world()),
Some("42".to_string()),
"Infallible mapping by reference failed."
);
signal.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<String>>().0 = None;
let inputs = Arc::new(Mutex::new(vec!["one", "TWO", "three", "FOUR"]));
let source_signal = signal::from_system(clone!((inputs) move |In(_)| {
inputs.lock().unwrap().pop()
}));
let signal = source_signal
.map_in_ref(|s: &&'static str| {
if s.chars().all(char::is_uppercase) {
Some(s.to_lowercase())
} else {
None
}
})
.map(capture_output::<String>)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<String>(app.world()),
Some("four".to_string()),
"Failed on first valid item."
);
app.update();
assert_eq!(
get_output::<String>(app.world()),
Some("four".to_string()),
"Output should not change when closure returns None."
);
app.update();
assert_eq!(
get_output::<String>(app.world()),
Some("two".to_string()),
"Failed on second valid item."
);
app.update();
assert_eq!(
get_output::<String>(app.world()),
Some("two".to_string()),
"Output should not change on subsequent None."
);
signal.cleanup(app.world_mut());
app.init_resource::<SignalOutput<()>>();
let call_count = Arc::new(Mutex::new(0));
let signal = signal::from_system(|In(_)| 1i32)
.map_in_ref(clone!((call_count) move |_: &i32| {
*call_count.lock().unwrap() += 1;
}))
.map(capture_output)
.register(app.world_mut());
app.update();
app.update();
app.update();
assert_eq!(
*call_count.lock().unwrap(),
3,
"FnMut closure should be called on each update"
);
signal.cleanup(app.world_mut());
app.init_resource::<SignalOutput<i32>>();
let signal = signal::from_system(|In(_)| 123)
.map_in_ref(|x: &i32| *x)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(123));
signal.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<i32>>().0 = None;
app.update();
assert_eq!(
get_output::<i32>(app.world()),
None,
"Signal should not fire after cleanup"
);
}
#[test]
fn test_component() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<TestData>>();
let entity = app.world_mut().spawn(TestData(1)).id();
let signal = signal::from_entity(entity)
.component::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(1)));
signal.cleanup(app.world_mut());
}
#[test]
fn test_component_option() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<Option<TestData>>::default());
let entity_with = app.world_mut().spawn(TestData(1)).id();
let entity_without = app.world_mut().spawn_empty().id();
let signal = signal::from_entity(entity_with)
.component_option::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Option<TestData>>(app.world()), Some(Some(TestData(1))));
signal.cleanup(app.world_mut());
let signal = signal::from_entity(entity_without)
.component_option::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Option<TestData>>(app.world()), Some(None));
signal.cleanup(app.world_mut());
}
#[test]
fn test_has_component() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<bool>::default());
let entity_with = app.world_mut().spawn(TestData(1)).id();
let entity_without = app.world_mut().spawn_empty().id();
let signal = signal::from_entity(entity_with)
.has_component::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<bool>(app.world()), Some(true));
signal.cleanup(app.world_mut());
let signal = signal::from_entity(entity_without)
.has_component::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<bool>(app.world()), Some(false));
signal.cleanup(app.world_mut());
}
#[test]
fn test_component_changed() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<TestData>>();
let entity = app.world_mut().spawn(TestData(1)).id();
let signal = signal::from_entity(entity)
.component_changed::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(1)));
app.world_mut().resource_mut::<SignalOutput<TestData>>().0 = None;
app.update();
assert_eq!(
get_output::<TestData>(app.world()),
None,
"Signal should not fire when component hasn't changed"
);
app.world_mut().get_mut::<TestData>(entity).unwrap().0 = 2;
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(2)));
signal.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<TestData>>().0 = None;
let entity_without = app.world_mut().spawn_empty().id();
let signal = signal::from_entity(entity_without)
.component_changed::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<TestData>(app.world()),
None,
"Signal should not fire for entity without component"
);
signal.cleanup(app.world_mut());
}
#[test]
fn test_dedupe() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let counter = Arc::new(Mutex::new(0));
let values = Arc::new(Mutex::new(vec![1, 1, 2, 3, 3, 3, 4]));
let signal = signal::from_system(clone!((values) move |In(_)| {
let mut values_lock = values.lock().unwrap();
if values_lock.is_empty() {
None
} else {
Some(values_lock.remove(0))
}
}))
.dedupe()
.map(clone!((counter) move |In(val): In<i32>| {
*counter.lock().unwrap() += 1;
val
}))
.map(capture_output)
.register(app.world_mut());
for _ in 0..10 {
app.update();
}
assert_eq!(get_output::<i32>(app.world()), Some(4));
assert_eq!(*counter.lock().unwrap(), 4);
assert_eq!(values.lock().unwrap().len(), 0);
signal.cleanup(app.world_mut());
}
#[test]
fn test_first() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let counter = Arc::new(Mutex::new(0));
let values = Arc::new(Mutex::new(vec![10, 20, 30]));
let signal = signal::from_system(clone!((values) move |In(_)| {
let mut values_lock = values.lock().unwrap();
if values_lock.is_empty() {
None
} else {
Some(values_lock.remove(0))
}
}))
.first()
.map(clone!((counter) move |In(val): In<i32>| {
*counter.lock().unwrap() += 1;
val
}))
.map(capture_output)
.register(app.world_mut());
app.update();
app.update();
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(10));
assert_eq!(*counter.lock().unwrap(), 1);
assert_eq!(values.lock().unwrap().len(), 0);
signal.cleanup(app.world_mut());
}
#[test]
fn test_once() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let counter = Arc::new(Mutex::new(0));
let signal = signal::once(42)
.map(clone!((counter) move |In(val): In<i32>| {
*counter.lock().unwrap() += 1;
val
}))
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(42));
assert_eq!(*counter.lock().unwrap(), 1);
app.update();
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(42));
assert_eq!(*counter.lock().unwrap(), 1);
signal.cleanup(app.world_mut());
}
#[test]
fn test_take() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let counter = Arc::new(Mutex::new(0));
let values = Arc::new(Mutex::new(vec![10, 20, 30]));
let signal = signal::from_system(clone!((values) move |In(_)| {
let mut values_lock = values.lock().unwrap();
if values_lock.is_empty() {
None
} else {
Some(values_lock.remove(0))
}
}))
.take(2)
.map(clone!((counter) move |In(val): In<i32>| {
*counter.lock().unwrap() += 1;
val
}))
.map(capture_output)
.register(app.world_mut());
app.update();
app.update();
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(20));
assert_eq!(*counter.lock().unwrap(), 2);
assert_eq!(values.lock().unwrap().len(), 0);
signal.cleanup(app.world_mut());
}
#[test]
fn test_skip() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let counter = Arc::new(Mutex::new(0));
let values = Arc::new(Mutex::new(vec![10, 20, 30, 40, 50]));
let signal = signal::from_system(clone!((values) move |In(_)| {
let mut values_lock = values.lock().unwrap();
if values_lock.is_empty() {
None
} else {
Some(values_lock.remove(0))
}
}))
.skip(2) .map(clone!((counter) move |In(val): In<i32>| {
*counter.lock().unwrap() += 1;
val
}))
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), None);
app.update();
assert_eq!(get_output::<i32>(app.world()), None);
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(30));
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(40));
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(50));
assert_eq!(*counter.lock().unwrap(), 3);
assert_eq!(values.lock().unwrap().len(), 0);
signal.cleanup(app.world_mut());
}
#[test]
fn test_zip() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<(i32, &'static str)>>();
let signal = signal::from_system(move |In(_)| 10)
.zip(signal::from_system(move |In(_)| "hello"))
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<(i32, &'static str)>(app.world()), Some((10, "hello")));
signal.cleanup(app.world_mut());
}
#[test]
fn test_zip_emits_after_both_emit() {
let mut app = create_test_app();
app.init_resource::<SignalOutputVec<(i32, i32)>>();
let left = signal::from_system(|In(_), mut state: Local<i32>| {
*state += 1;
*state
});
let right = signal::from_system(|In(_)| 10).first();
let signal = left.zip(right).map(capture_output_vec).register(app.world_mut());
app.update();
assert_eq!(get_and_clear_output_vec::<(i32, i32)>(app.world_mut()), vec![(1, 10)]);
app.update();
assert_eq!(get_and_clear_output_vec::<(i32, i32)>(app.world_mut()), vec![(2, 10)]);
app.update();
assert_eq!(get_and_clear_output_vec::<(i32, i32)>(app.world_mut()), vec![(3, 10)]);
signal.cleanup(app.world_mut());
}
#[test]
fn test_chain_multi_level_same_frame() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let signal = signal::from_system(|In(_), mut state: Local<i32>| {
*state += 1;
*state
})
.map_in(|x: i32| x + 1)
.map_in(|x: i32| x * 2)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(4));
signal.cleanup(app.world_mut());
}
#[test]
fn test_fan_out_fan_in_zip() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<(i32, i32)>>();
let source = signal::from_system(|In(_), mut state: Local<i32>| {
*state += 1;
*state
});
let left = source.clone().map_in(|x: i32| x + 1);
let right = source.map_in(|x: i32| x + 10);
let signal = left.zip(right).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(get_output::<(i32, i32)>(app.world()), Some((2, 11)));
app.update();
assert_eq!(get_output::<(i32, i32)>(app.world()), Some((3, 12)));
signal.cleanup(app.world_mut());
}
#[test]
fn test_zip_macro() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<(i32,)>>();
let s1 = signal::from_system(|In(_)| 1);
let signal = zip!(s1).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(get_output::<(i32,)>(app.world()), Some((1,)));
signal.cleanup(app.world_mut());
app.init_resource::<SignalOutput<(i32, &'static str)>>();
let s1 = signal::from_system(|In(_)| 1);
let s2 = signal::from_system(|In(_)| "two");
let signal = zip!(s1, s2).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(get_output::<(i32, &'static str)>(app.world()), Some((1, "two")));
signal.cleanup(app.world_mut());
app.init_resource::<SignalOutput<(i32, &'static str, f64)>>();
let s1 = signal::from_system(|In(_)| 1);
let s2 = signal::from_system(|In(_)| "two");
let s3 = signal::from_system(|In(_)| 3.0);
let signal = zip!(s1, s2, s3).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(
get_output::<(i32, &'static str, f64)>(app.world()),
Some((1, "two", 3.0))
);
signal.cleanup(app.world_mut());
app.init_resource::<SignalOutput<(i32, &'static str, f64, bool)>>();
let s1 = signal::from_system(|In(_)| 1);
let s2 = signal::from_system(|In(_)| "two");
let s3 = signal::from_system(|In(_)| 3.0);
let s4 = signal::from_system(|In(_)| true);
let signal = zip!(s1, s2, s3, s4).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(
get_output::<(i32, &'static str, f64, bool)>(app.world()),
Some((1, "two", 3.0, true))
);
signal.cleanup(app.world_mut());
app.init_resource::<SignalOutput<(i32, i32, i32, i32, i32)>>();
let s1 = signal::from_system(|In(_)| 1);
let s2 = signal::from_system(|In(_)| 2);
let s3 = signal::from_system(|In(_)| 3);
let s4 = signal::from_system(|In(_)| 4);
let s5 = signal::from_system(|In(_)| 5);
let signal = zip!(s1, s2, s3, s4, s5).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(
get_output::<(i32, i32, i32, i32, i32)>(app.world()),
Some((1, 2, 3, 4, 5))
);
signal.cleanup(app.world_mut());
}
#[test]
fn test_flatten() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let signal_1 = signal::from_system(|In(_)| 1).boxed_clone();
let signal_2 = signal::from_system(|In(_)| 2).boxed_clone();
#[derive(Resource, Default)]
struct SignalSelector(bool);
app.init_resource::<SignalSelector>();
let signal = signal::from_system(
move |In(_), selector: Res<SignalSelector>| {
if selector.0 { signal_1.clone() } else { signal_2.clone() }
},
)
.flatten()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(2));
app.world_mut().resource_mut::<SignalSelector>().0 = true;
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(1));
signal.cleanup(app.world_mut());
}
#[test]
fn test_eq() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<bool>>();
let source = signal::from_system(|In(_)| 1);
let signal = source.clone().eq(1).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(get_output::<bool>(app.world()), Some(true));
signal.cleanup(app.world_mut());
let signal = source.eq(2).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(get_output::<bool>(app.world()), Some(false));
signal.cleanup(app.world_mut());
}
#[test]
fn test_neq() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<bool>>();
let source = signal::from_system(|In(_)| 1);
let signal = source.clone().neq(2).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(get_output::<bool>(app.world()), Some(true));
signal.cleanup(app.world_mut());
let signal = source.neq(1).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(get_output::<bool>(app.world()), Some(false));
signal.cleanup(app.world_mut());
}
#[test]
fn test_not() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<bool>>();
let signal = signal::from_system(|In(_)| true)
.not()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<bool>(app.world()), Some(false));
signal.cleanup(app.world_mut());
let signal = signal::from_system(|In(_)| false)
.not()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<bool>(app.world()), Some(true));
signal.cleanup(app.world_mut());
}
#[test]
fn test_filter() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let values = Arc::new(Mutex::new(vec![1, 2, 3, 4, 5, 6]));
let signal = signal::from_system(move |In(_)| {
let mut values_lock = values.lock().unwrap();
if values_lock.is_empty() {
None
} else {
Some(values_lock.remove(0))
}
})
.filter(|In(x): In<i32>| x % 2 == 0)
.map(capture_output)
.register(app.world_mut());
for _ in 0..10 {
app.update();
}
assert_eq!(get_output::<i32>(app.world()), Some(6));
signal.cleanup(app.world_mut());
}
#[test]
fn test_switch() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let signal_1 = signal::from_system(|In(_)| 1).boxed_clone();
let signal_2 = signal::from_system(|In(_)| 2).boxed_clone();
#[derive(Resource, Default)]
struct SwitcherToggle(bool);
app.init_resource::<SwitcherToggle>();
let signal = signal::from_system(move |In(_), mut toggle: ResMut<SwitcherToggle>| {
let current = toggle.0;
toggle.0 = !toggle.0;
current
})
.switch(
move |In(use_1): In<bool>| {
if use_1 { signal_1.clone() } else { signal_2.clone() }
},
)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(2));
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(1));
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(2));
signal.cleanup(app.world_mut());
}
#[derive(Resource, Default, Debug)]
struct SignalVecOutput<T: Send + Sync + 'static + Clone + fmt::Debug>(Vec<VecDiff<T>>);
fn capture_vec_output<T>(In(diffs): In<Vec<VecDiff<T>>>, mut output: ResMut<SignalVecOutput<T>>)
where
T: Send + Sync + 'static + Clone + fmt::Debug,
{
output.0.extend(diffs);
}
fn get_and_clear_vec_output<T: Send + Sync + 'static + Clone + fmt::Debug>(world: &mut World) -> Vec<VecDiff<T>> {
world
.get_resource_mut::<SignalVecOutput<T>>()
.map(|mut res| core::mem::take(&mut res.0))
.unwrap_or_default()
}
#[test]
fn test_switch_signal_vec() {
{
let mut app = create_test_app();
app.init_resource::<SignalVecOutput<i32>>();
let list_a = MutableVec::builder().values([10, 20]).spawn(app.world_mut());
let list_b = MutableVec::builder().values([100, 200, 300]).spawn(app.world_mut());
let signal_a = list_a.signal_vec().map_in(identity);
let signal_b = list_b.signal_vec();
#[derive(Resource, Clone, Copy, PartialEq, Debug)]
enum ListSelector {
A,
B,
}
app.insert_resource(ListSelector::A);
let control_signal = signal::from_system(|In(_), selector: Res<ListSelector>| *selector).dedupe();
let switched_signal = control_signal.dedupe().switch_signal_vec(
clone!(( signal_a, signal_b) move |In(selector): In<ListSelector>| match selector {
ListSelector:: A => signal_a.clone().left_either(),
ListSelector:: B => signal_b.clone().right_either(),
}),
);
let handle = switched_signal.for_each(capture_vec_output).register(app.world_mut());
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Initial update should produce one diff.");
assert_eq!(
diffs[0],
VecDiff::Replace { values: vec![10, 20] },
"Initial state should be a Replace with List A's contents."
);
list_a.write(app.world_mut()).push(30);
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Push to active list should produce one diff.");
assert_eq!(
diffs[0],
VecDiff::Push { value: 30 },
"Should forward Push diff from List A."
);
*app.world_mut().resource_mut::<ListSelector>() = ListSelector::B;
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Switching lists should produce one diff.");
assert_eq!(
diffs[0],
VecDiff::Replace {
values: vec![100, 200, 300]
},
"Switch should emit a Replace with List B's contents."
);
list_a.write(app.world_mut()).push(99);
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert!(diffs.is_empty(), "Should ignore diffs from the old, inactive list.");
list_b.write(app.world_mut()).remove(0);
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "RemoveAt from new active list should produce one diff.");
assert_eq!(
diffs[0],
VecDiff::RemoveAt { index: 0 },
"Should forward RemoveAt diff from List B."
);
*app.world_mut().resource_mut::<ListSelector>() = ListSelector::B;
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert!(
diffs.is_empty(),
"Re-selecting the same list should produce no diffs due to memoization."
);
*app.world_mut().resource_mut::<ListSelector>() = ListSelector::A;
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Switching back to A should produce one diff.");
assert_eq!(
diffs[0],
VecDiff::Replace {
values: vec![10, 20, 30, 99],
},
"Switching back should Replace with the current state of List A."
);
handle.cleanup(app.world_mut());
}
crate::signal_vec::tests::cleanup(true);
}
#[test]
fn test_switch_signal_vec_initially_empty() {
{
let mut app = create_test_app();
app.init_resource::<SignalVecOutput<i32>>();
let list_a: MutableVec<i32> = app.world_mut().into();
let list_b = MutableVec::builder().values([100, 200, 300]).spawn(app.world_mut());
let signal_a = list_a.signal_vec().map_in(identity);
let signal_b = list_b.signal_vec();
#[derive(Resource, Clone, Copy, PartialEq, Debug)]
enum ListSelector {
A,
B,
}
app.insert_resource(ListSelector::A);
let control_signal = signal::from_system(|In(_), selector: Res<ListSelector>| *selector).dedupe();
let switched_signal = control_signal.dedupe().switch_signal_vec(
clone!((signal_a, signal_b) move |In(selector): In<ListSelector>| match selector {
ListSelector::A => signal_a.clone().left_either(),
ListSelector::B => signal_b.clone().right_either(),
}),
);
let handle = switched_signal.for_each(capture_vec_output).register(app.world_mut());
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert!(
diffs.is_empty(),
"Initial update with empty list should produce no diffs, got: {:?}",
diffs
);
list_a.write(app.world_mut()).push(10);
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Push to active list should produce one diff.");
assert_eq!(
diffs[0],
VecDiff::Push { value: 10 },
"Should forward Push diff from List A."
);
list_a.write(app.world_mut()).push(20);
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Second push should produce one diff.");
assert_eq!(
diffs[0],
VecDiff::Push { value: 20 },
"Should forward second Push diff from List A."
);
*app.world_mut().resource_mut::<ListSelector>() = ListSelector::B;
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Switching to non-empty list should produce one diff.");
assert_eq!(
diffs[0],
VecDiff::Replace {
values: vec![100, 200, 300]
},
"Switch should emit a Replace with List B's contents."
);
*app.world_mut().resource_mut::<ListSelector>() = ListSelector::A;
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Switching back to A should produce one diff.");
assert_eq!(
diffs[0],
VecDiff::Replace { values: vec![10, 20] },
"Switching back should Replace with the current state of List A."
);
handle.cleanup(app.world_mut());
}
crate::signal_vec::tests::cleanup(true);
}
#[test]
fn test_switch_signal_map() {
use crate::signal_map::{IntoSignalMapEither, MapDiff, MutableBTreeMap, SignalMapExt};
#[derive(Resource, Default, core::fmt::Debug)]
struct SignalMapOutput<K, V>(Vec<MapDiff<K, V>>)
where
K: Send + Sync + 'static + Clone + core::fmt::Debug,
V: Send + Sync + 'static + Clone + core::fmt::Debug;
fn capture_map_output<K, V>(In(diffs): In<Vec<MapDiff<K, V>>>, mut output: ResMut<SignalMapOutput<K, V>>)
where
K: Send + Sync + 'static + Clone + core::fmt::Debug,
V: Send + Sync + 'static + Clone + core::fmt::Debug,
{
output.0.extend(diffs);
}
fn get_and_clear_map_output<K, V>(world: &mut World) -> Vec<MapDiff<K, V>>
where
K: Send + Sync + 'static + Clone + core::fmt::Debug,
V: Send + Sync + 'static + Clone + core::fmt::Debug,
{
world
.get_resource_mut::<SignalMapOutput<K, V>>()
.map(|mut res| core::mem::take(&mut res.0))
.unwrap_or_default()
}
{
let mut app = create_test_app();
app.init_resource::<SignalMapOutput<i32, i32>>();
let map_a = MutableBTreeMap::builder()
.values([(1, 10), (2, 20)])
.spawn(app.world_mut());
let map_b = MutableBTreeMap::builder()
.values([(1, 100), (2, 200), (3, 300)])
.spawn(app.world_mut());
let signal_a = map_a.signal_map().map_value(|In(x): In<i32>| x);
let signal_b = map_b.signal_map();
#[derive(Resource, Clone, Copy, PartialEq, Debug)]
enum MapSelector {
A,
B,
}
app.insert_resource(MapSelector::A);
let control_signal = signal::from_system(|In(_), selector: Res<MapSelector>| *selector).dedupe();
let switched_signal = control_signal.dedupe().switch_signal_map(
clone!((signal_a, signal_b) move |In(selector): In<MapSelector>| match selector {
MapSelector::A => signal_a.clone().left_either(),
MapSelector::B => signal_b.clone().right_either(),
}),
);
let handle = switched_signal.for_each(capture_map_output).register(app.world_mut());
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Initial update should produce one diff.");
assert!(
matches!(&diffs[0], MapDiff::Replace { entries } if entries == &vec![(1, 10), (2, 20)]),
"Initial state should be a Replace with Map A's contents."
);
map_a.write(app.world_mut()).insert(3, 30);
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Insert to active map should produce one diff.");
assert!(
matches!(&diffs[0], MapDiff::Insert { key: 3, value: 30 }),
"Should forward Insert diff from Map A."
);
*app.world_mut().resource_mut::<MapSelector>() = MapSelector::B;
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Switching maps should produce one diff.");
assert!(
matches!(&diffs[0], MapDiff::Replace { entries } if entries == &vec![(1, 100), (2, 200), (3, 300)]),
"Switch should emit a Replace with Map B's contents."
);
map_a.write(app.world_mut()).insert(4, 99);
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert!(diffs.is_empty(), "Should ignore diffs from the old, inactive map.");
map_b.write(app.world_mut()).remove(&1);
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Remove from new active map should produce one diff.");
assert!(
matches!(&diffs[0], MapDiff::Remove { key: 1 }),
"Should forward Remove diff from Map B."
);
*app.world_mut().resource_mut::<MapSelector>() = MapSelector::B;
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert!(
diffs.is_empty(),
"Re-selecting the same map should produce no diffs due to memoization."
);
*app.world_mut().resource_mut::<MapSelector>() = MapSelector::A;
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Switching back to A should produce one diff.");
assert!(
matches!(&diffs[0], MapDiff::Replace { entries } if entries == &vec![(1, 10), (2, 20), (3, 30), (4, 99)]),
"Switching back should Replace with the current state of Map A."
);
handle.cleanup(app.world_mut());
}
crate::signal_map::tests::cleanup(true);
}
#[test]
fn test_switch_signal_map_initially_empty() {
use crate::signal_map::{IntoSignalMapEither, MapDiff, MutableBTreeMap, SignalMapExt};
#[derive(Resource, Default, core::fmt::Debug)]
struct SignalMapOutput<K, V>(Vec<MapDiff<K, V>>)
where
K: Send + Sync + 'static + Clone + core::fmt::Debug,
V: Send + Sync + 'static + Clone + core::fmt::Debug;
fn capture_map_output<K, V>(In(diffs): In<Vec<MapDiff<K, V>>>, mut output: ResMut<SignalMapOutput<K, V>>)
where
K: Send + Sync + 'static + Clone + core::fmt::Debug,
V: Send + Sync + 'static + Clone + core::fmt::Debug,
{
output.0.extend(diffs);
}
fn get_and_clear_map_output<K, V>(world: &mut World) -> Vec<MapDiff<K, V>>
where
K: Send + Sync + 'static + Clone + core::fmt::Debug,
V: Send + Sync + 'static + Clone + core::fmt::Debug,
{
world
.get_resource_mut::<SignalMapOutput<K, V>>()
.map(|mut res| core::mem::take(&mut res.0))
.unwrap_or_default()
}
{
let mut app = create_test_app();
app.init_resource::<SignalMapOutput<i32, i32>>();
let map_a: MutableBTreeMap<i32, i32> = app.world_mut().into();
let map_b = MutableBTreeMap::builder()
.values([(1, 100), (2, 200), (3, 300)])
.spawn(app.world_mut());
let signal_a = map_a.signal_map().map_value(|In(x): In<i32>| x);
let signal_b = map_b.signal_map();
#[derive(Resource, Clone, Copy, PartialEq, Debug)]
enum MapSelector {
A,
B,
}
app.insert_resource(MapSelector::A);
let control_signal = signal::from_system(|In(_), selector: Res<MapSelector>| *selector).dedupe();
let switched_signal = control_signal.dedupe().switch_signal_map(
clone!((signal_a, signal_b) move |In(selector): In<MapSelector>| match selector {
MapSelector::A => signal_a.clone().left_either(),
MapSelector::B => signal_b.clone().right_either(),
}),
);
let handle = switched_signal.for_each(capture_map_output).register(app.world_mut());
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert!(
diffs.is_empty(),
"Initial update with empty map should produce no diffs, got: {:?}",
diffs
);
map_a.write(app.world_mut()).insert(1, 10);
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Insert to active map should produce one diff.");
assert!(
matches!(&diffs[0], MapDiff::Insert { key: 1, value: 10 }),
"Should forward Insert diff from Map A."
);
map_a.write(app.world_mut()).insert(2, 20);
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Second insert should produce one diff.");
assert!(
matches!(&diffs[0], MapDiff::Insert { key: 2, value: 20 }),
"Should forward second Insert diff from Map A."
);
*app.world_mut().resource_mut::<MapSelector>() = MapSelector::B;
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Switching to non-empty map should produce one diff.");
assert!(
matches!(&diffs[0], MapDiff::Replace { entries } if entries == &vec![(1, 100), (2, 200), (3, 300)]),
"Switch should emit a Replace with Map B's contents."
);
*app.world_mut().resource_mut::<MapSelector>() = MapSelector::A;
app.update();
let diffs = get_and_clear_map_output::<i32, i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Switching back to A should produce one diff.");
assert!(
matches!(&diffs[0], MapDiff::Replace { entries } if entries == &vec![(1, 10), (2, 20)]),
"Switching back should Replace with the current state of Map A."
);
handle.cleanup(app.world_mut());
}
crate::signal_map::tests::cleanup(true);
}
#[test]
fn test_throttle() {
let mut app = App::new();
app.add_plugins((MinimalPlugins.build().disable::<TimePlugin>(), JonmoPlugin::default()));
app.init_resource::<Time>(); app.init_resource::<SignalOutput<i32>>();
let source_signal = signal::from_system(|In(_), mut counter: Local<i32>| {
*counter += 1;
*counter
});
let handle = source_signal
.throttle(Duration::from_millis(100))
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output(app.world()),
Some(1),
"First value (1) should pass immediately."
);
app.world_mut().resource_mut::<SignalOutput<i32>>().0 = None;
app.world_mut()
.resource_mut::<Time>()
.advance_by(Duration::from_millis(50));
app.update(); assert_eq!(
get_output::<i32>(app.world()),
None,
"Value (2) emitted after 50ms should be blocked."
);
app.world_mut()
.resource_mut::<Time>()
.advance_by(Duration::from_millis(40));
app.update(); assert_eq!(
get_output::<i32>(app.world()),
None,
"Value (3) emitted after 90ms total should be blocked."
);
app.world_mut().entity(**handle);
app.world_mut()
.resource_mut::<Time>()
.advance_by(Duration::from_millis(20));
app.update(); assert_eq!(
get_output(app.world()),
Some(4),
"Value (4) should pass after total duration > 100ms."
);
app.world_mut().resource_mut::<SignalOutput<i32>>().0 = None;
app.world_mut()
.resource_mut::<Time>()
.advance_by(Duration::from_millis(10));
app.update();
assert_eq!(
get_output::<i32>(app.world()),
None,
"Value (5) immediately after a pass should be blocked again."
);
app.world_mut()
.resource_mut::<Time>()
.advance_by(Duration::from_millis(100));
app.update();
assert_eq!(
get_output(app.world()),
Some(6),
"Value (6) should pass again after another full duration."
);
handle.cleanup(app.world_mut());
}
#[test]
fn test_map_bool() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<&'static str>>();
let signal_true = signal::from_system(|In(_)| true)
.map_bool(|In(_)| "True Branch", |In(_)| "False Branch")
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<&'static str>(app.world()), Some("True Branch"));
signal_true.cleanup(app.world_mut());
let signal_false = signal::from_system(|In(_)| false)
.map_bool(|In(_)| "True Branch", |In(_)| "False Branch")
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<&'static str>(app.world()), Some("False Branch"));
signal_false.cleanup(app.world_mut());
}
#[test]
fn test_map_true() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<Option<&'static str>>>();
let source_true = signal::from_system(|In(_)| true);
let signal_true = source_true
.map_true(|In(_)| "Was True")
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<&'static str>>(app.world()),
Some(Some("Was True")),
"map_true should execute system and output Some(value) when input is true"
);
signal_true.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<Option<&'static str>>>().0 = None;
let source_false = signal::from_system(|In(_)| false);
let signal_false = source_false
.map_true(|In(_)| "Was True")
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<&'static str>>(app.world()),
Some(None),
"map_true should not execute system and output None when input is false"
);
signal_false.cleanup(app.world_mut());
}
#[test]
fn test_map_false() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<Option<&'static str>>>();
let source_false = signal::from_system(|In(_)| false);
let signal_false = source_false
.map_false(|In(_)| "Was False")
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<&'static str>>(app.world()),
Some(Some("Was False")),
"map_false should execute system and output Some(value) when input is false"
);
signal_false.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<Option<&'static str>>>().0 = None;
let source_true = signal::from_system(|In(_)| true);
let signal_true = source_true
.map_false(|In(_)| "Was False")
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<&'static str>>(app.world()),
Some(None),
"map_false should not execute system and output None when input is true"
);
signal_true.cleanup(app.world_mut());
}
#[test]
fn test_map_option() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<String>>();
let signal_some = signal::from_system(|In(_)| Some(42))
.map_option(
|In(value): In<i32>| format!("Some({value})"),
|In(_)| "None".to_string(),
)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<String>(app.world()), Some("Some(42)".to_string()));
signal_some.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<String>>().0 = None;
let signal_none = signal::from_system(|In(_)| None::<i32>)
.map_option(
|In(value): In<i32>| format!("Some({value})"),
|In(_)| "None".to_string(),
)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<String>(app.world()), Some("None".to_string()));
signal_none.cleanup(app.world_mut());
}
#[test]
fn test_map_some() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<Option<String>>>();
let source_some = signal::from_system(|In(_)| Some(42));
let signal_some = source_some
.map_some(|In(val): In<i32>| format!("Got {val}"))
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<String>>(app.world()),
Some(Some("Got 42".to_string())),
"map_some should execute system with inner value and output Some(new_value) for a Some input"
);
signal_some.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<Option<String>>>().0 = None;
let source_none = signal::from_system(|In(_)| None::<i32>);
let signal_none = source_none
.map_some(|In(val): In<i32>| format!("Got {val}"))
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<String>>(app.world()),
Some(None),
"map_some should not execute system and output None for a None input"
);
signal_none.cleanup(app.world_mut());
}
#[test]
fn test_map_none() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<Option<&'static str>>>();
let source_none = signal::from_system(|In(_)| None::<i32>);
let signal_none = source_none
.map_none(|In(_)| "Was None")
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<&'static str>>(app.world()),
Some(Some("Was None")),
"map_none should execute system and output Some(value) for a None input"
);
signal_none.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<Option<&'static str>>>().0 = None;
let source_some = signal::from_system(|In(_)| Some(42));
let signal_some = source_some
.map_none(|In(_)| "Was None")
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<&'static str>>(app.world()),
Some(None),
"map_none should not execute system and output None for a Some input"
);
signal_some.cleanup(app.world_mut());
}
#[test]
fn test_to_signal_vec() {
let mut app = create_test_app();
app.init_resource::<SignalVecOutput<i32>>();
let source_vec = Arc::new(Mutex::new(None::<Vec<i32>>));
let source_signal = signal::from_system(clone!((source_vec) move |In(_)| {
source_vec.lock().unwrap().take()
}));
let signal_vec = source_signal.to_signal_vec();
let handle = signal_vec.for_each(capture_vec_output::<i32>).register(app.world_mut());
app.update();
assert!(
get_and_clear_vec_output::<i32>(app.world_mut()).is_empty(),
"Should not emit anything when source is None"
);
*source_vec.lock().unwrap() = Some(vec![1, 2, 3]);
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Expected one diff on first emission");
if let VecDiff::Replace { values } = &diffs[0] {
assert_eq!(values, &vec![1, 2, 3]);
} else {
panic!("Expected a Replace diff, got {:?}", diffs[0]);
}
*source_vec.lock().unwrap() = Some(vec![1, 2, 3]);
app.update();
assert!(
get_and_clear_vec_output::<i32>(app.world_mut()).is_empty(),
"Should be deduplicated when source emits the same vector"
);
*source_vec.lock().unwrap() = Some(vec![4, 5]);
app.update();
let diffs = get_and_clear_vec_output::<i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Expected one diff on new vector emission");
if let VecDiff::Replace { values } = &diffs[0] {
assert_eq!(values, &vec![4, 5]);
} else {
panic!("Expected a Replace diff, got {:?}", diffs[0]);
}
handle.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_system() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let signal = signal::from_system(|In(_)| 42)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(42));
signal.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_function() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let counter = Arc::new(Mutex::new(0));
let signal = signal::from_function(clone!((counter) move || {
let mut c = counter.lock().unwrap();
*c += 1;
*c
}))
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(1));
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(2));
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(3));
signal.cleanup(app.world_mut());
}
#[test]
fn test_builder_always() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<i32>>();
let signal = signal::always(42).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(42));
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(42));
app.update();
assert_eq!(get_output::<i32>(app.world()), Some(42));
signal.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_entity() {
let mut app = create_test_app();
app.insert_resource(SignalOutput(Some(Entity::PLACEHOLDER)));
let test_entity = app.world_mut().spawn_empty().id();
let signal = signal::from_entity(test_entity)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Entity>(app.world()), Some(test_entity));
signal.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_lazy_entity() {
let mut app = create_test_app();
app.insert_resource(SignalOutput(Some(Entity::PLACEHOLDER)));
let lazy = LazyEntity::new();
let test_entity = app.world_mut().spawn_empty().id();
lazy.set(test_entity);
let signal = signal::from_entity(lazy).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(get_output::<Entity>(app.world()), Some(test_entity));
signal.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_ancestor() {
let mut app = create_test_app();
app.insert_resource(SignalOutput(Some(Entity::PLACEHOLDER)));
let grandparent = app.world_mut().spawn_empty().id();
let parent = app.world_mut().spawn_empty().id();
let child = app.world_mut().spawn_empty().id();
app.world_mut().entity_mut(grandparent).add_child(parent);
app.world_mut().entity_mut(parent).add_child(child);
let signal_self = signal::from_ancestor(child, 0)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Entity>(app.world()), Some(child));
signal_self.cleanup(app.world_mut());
let signal_parent = signal::from_ancestor(child, 1)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Entity>(app.world()), Some(parent));
signal_parent.cleanup(app.world_mut());
let signal_gp = signal::from_ancestor(child, 2)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Entity>(app.world()), Some(grandparent));
signal_gp.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<Entity>>().0 = None;
let signal_invalid = signal::from_ancestor(child, 3)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Entity>(app.world()),
None,
"Should terminate for invalid ancestor"
);
signal_invalid.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_ancestor_lazy() {
let mut app = create_test_app();
app.insert_resource(SignalOutput(Some(Entity::PLACEHOLDER)));
let lazy_child = LazyEntity::new();
let grandparent = app.world_mut().spawn_empty().id();
let parent = app.world_mut().spawn_empty().id();
let child = app.world_mut().spawn_empty().id();
app.world_mut().entity_mut(grandparent).add_child(parent);
app.world_mut().entity_mut(parent).add_child(child);
lazy_child.set(child);
let signal_parent = signal::from_ancestor(lazy_child.clone(), 1)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Entity>(app.world()), Some(parent));
signal_parent.cleanup(app.world_mut());
let signal_gp = signal::from_ancestor(lazy_child, 2)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Entity>(app.world()), Some(grandparent));
signal_gp.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_parent() {
let mut app = create_test_app();
app.insert_resource(SignalOutput(Some(Entity::PLACEHOLDER)));
let parent = app.world_mut().spawn_empty().id();
let child = app.world_mut().spawn_empty().id();
app.world_mut().entity_mut(parent).add_child(child);
let signal = signal::from_parent(child).map(capture_output).register(app.world_mut());
app.update();
assert_eq!(get_output::<Entity>(app.world()), Some(parent));
signal.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<Entity>>().0 = None;
let no_parent_entity = app.world_mut().spawn_empty().id();
let signal_no_parent = signal::from_parent(no_parent_entity)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Entity>(app.world()),
None,
"Should terminate for entity with no parent"
);
signal_no_parent.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_parent_lazy() {
let mut app = create_test_app();
app.insert_resource(SignalOutput(Some(Entity::PLACEHOLDER)));
let lazy_child = LazyEntity::new();
let parent = app.world_mut().spawn_empty().id();
let child = app.world_mut().spawn_empty().id();
app.world_mut().entity_mut(parent).add_child(child);
lazy_child.set(child);
let signal = signal::from_parent(lazy_child)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Entity>(app.world()), Some(parent));
signal.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_component() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<TestData>>();
let entity_with = app.world_mut().spawn(TestData(42)).id();
let signal_with = signal::from_component::<TestData>(entity_with)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(42)));
signal_with.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<TestData>>().0 = None;
let entity_without = app.world_mut().spawn_empty().id();
let signal_without = signal::from_component::<TestData>(entity_without)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<TestData>(app.world()),
None,
"Should terminate when component is missing"
);
signal_without.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_component_lazy() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<TestData>>();
let lazy = LazyEntity::new();
let entity_with = app.world_mut().spawn(TestData(42)).id();
lazy.set(entity_with);
let signal = signal::from_component::<TestData>(lazy)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(42)));
signal.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_component_option() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<Option<TestData>>>();
let entity_with = app.world_mut().spawn(TestData(42)).id();
let signal_with = signal::from_component_option::<TestData>(entity_with)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Option<TestData>>(app.world()), Some(Some(TestData(42))));
signal_with.cleanup(app.world_mut());
let entity_without = app.world_mut().spawn_empty().id();
let signal_without = signal::from_component_option::<TestData>(entity_without)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<TestData>>(app.world()),
Some(None),
"Should output Some(None) when component is missing"
);
signal_without.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_component_option_lazy() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<Option<TestData>>>();
let lazy = LazyEntity::new();
let entity_with = app.world_mut().spawn(TestData(42)).id();
lazy.set(entity_with);
let signal = signal::from_component_option::<TestData>(lazy)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Option<TestData>>(app.world()), Some(Some(TestData(42))));
signal.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_resource() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<TestData>>();
app.insert_resource(TestData(42));
let signal_with = signal::from_resource::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(42)));
signal_with.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<TestData>>().0 = None;
app.world_mut().remove_resource::<TestData>();
let signal_without = signal::from_resource::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<TestData>(app.world()),
None,
"Should terminate when resource is missing"
);
signal_without.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_resource_option() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<Option<TestData>>>();
app.insert_resource(TestData(42));
let signal_with = signal::from_resource_option::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Option<TestData>>(app.world()), Some(Some(TestData(42))));
signal_with.cleanup(app.world_mut());
app.world_mut().remove_resource::<TestData>();
let signal_without = signal::from_resource_option::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<TestData>>(app.world()),
Some(None),
"Should output Some(None) when resource is missing"
);
signal_without.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_component_changed() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<TestData>>();
let entity = app.world_mut().spawn(TestData(42)).id();
let signal = signal::from_component_changed::<TestData>(entity)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(42)));
app.world_mut().resource_mut::<SignalOutput<TestData>>().0 = None;
app.update();
assert_eq!(
get_output::<TestData>(app.world()),
None,
"Should terminate when component has not changed"
);
app.world_mut().entity_mut(entity).get_mut::<TestData>().unwrap().0 = 100;
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(100)));
signal.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<TestData>>().0 = None;
let entity_without = app.world_mut().spawn_empty().id();
let signal_without = signal::from_component_changed::<TestData>(entity_without)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<TestData>(app.world()),
None,
"Should terminate when component is missing"
);
signal_without.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_component_changed_lazy() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<TestData>>();
let lazy = LazyEntity::new();
let entity = app.world_mut().spawn(TestData(42)).id();
lazy.set(entity);
let signal = signal::from_component_changed::<TestData>(lazy)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(42)));
app.world_mut().resource_mut::<SignalOutput<TestData>>().0 = None;
app.update();
assert_eq!(
get_output::<TestData>(app.world()),
None,
"Should terminate when component has not changed"
);
app.world_mut().entity_mut(entity).get_mut::<TestData>().unwrap().0 = 100;
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(100)));
signal.cleanup(app.world_mut());
}
#[test]
fn test_builder_from_resource_changed() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<TestData>>();
app.insert_resource(TestData(42));
let signal = signal::from_resource_changed::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(42)));
app.world_mut().resource_mut::<SignalOutput<TestData>>().0 = None;
app.update();
assert_eq!(
get_output::<TestData>(app.world()),
None,
"Should terminate when resource has not changed"
);
app.world_mut().resource_mut::<TestData>().0 = 100;
app.update();
assert_eq!(get_output::<TestData>(app.world()), Some(TestData(100)));
signal.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<TestData>>().0 = None;
app.world_mut().remove_resource::<TestData>();
let signal_without = signal::from_resource_changed::<TestData>()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<TestData>(app.world()),
None,
"Should terminate when resource is missing"
);
signal_without.cleanup(app.world_mut());
}
#[test]
fn test_signal_option() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<Option<i32>>>();
let some_signal = crate::signal::option(Some(signal::from_system(|In(_)| 42)))
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<i32>>(app.world()),
Some(Some(42)),
"signal::option(Some(signal)) should output Some(value)"
);
some_signal.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<Option<i32>>>().0 = None;
let none_signal = crate::signal::option(None::<BoxedSignal<i32>>)
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<i32>>(app.world()),
Some(None),
"signal::option(None) should output None"
);
none_signal.cleanup(app.world_mut());
app.world_mut().resource_mut::<SignalOutput<Option<i32>>>().0 = None;
let counter = Arc::new(Mutex::new(0));
let dynamic_signal = crate::signal::option(Some(signal::from_system(clone!((counter) move |In(_)| {
let mut c = counter.lock().unwrap();
*c += 1;
*c
}))))
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(get_output::<Option<i32>>(app.world()), Some(Some(1)));
app.update();
assert_eq!(get_output::<Option<i32>>(app.world()), Some(Some(2)));
app.update();
assert_eq!(get_output::<Option<i32>>(app.world()), Some(Some(3)));
dynamic_signal.cleanup(app.world_mut());
}
#[test]
fn test_signal_option_with_switch() {
let mut app = create_test_app();
app.init_resource::<SignalOutput<Option<i32>>>();
let condition = Arc::new(Mutex::new(true));
let signal = signal::from_system(clone!((condition) move |In(_)| {
*condition.lock().unwrap()
}))
.map(move |In(use_signal): In<bool>| {
if use_signal {
crate::signal::option(Some(signal::from_system(|In(_)| 42)))
} else {
crate::signal::option(None)
}
})
.flatten()
.map(capture_output)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<i32>>(app.world()),
Some(Some(42)),
"Should output Some(42) when condition is true"
);
*condition.lock().unwrap() = false;
app.update();
assert_eq!(
get_output::<Option<i32>>(app.world()),
Some(None),
"Should output None when condition is false"
);
*condition.lock().unwrap() = true;
app.update();
assert_eq!(
get_output::<Option<i32>>(app.world()),
Some(Some(42)),
"Should output Some(42) when condition is true again"
);
signal.cleanup(app.world_mut());
}
#[test]
fn simple_signal_lazy_outlives_handle() {
let mut app = create_test_app();
let source_signal_struct = signal::from_system(|In(_)| 1);
let handle = source_signal_struct.clone().register(app.world_mut());
let system_entity = handle.0.entity();
assert!(
app.world().get_entity(system_entity).is_ok(),
"System entity should exist after registration"
);
assert!(
app.world().get::<LazySignalHolder>(system_entity).is_some(),
"LazySignalHolder should exist on system entity"
);
assert_eq!(
**app.world().get::<SignalRegistrationCount>(system_entity).unwrap(),
1,
"SignalRegistrationCount should be 1"
);
handle.cleanup(app.world_mut());
assert_eq!(
**app.world().get::<SignalRegistrationCount>(system_entity).unwrap(),
0,
"SignalRegistrationCount should be 0 after cleanup"
);
assert!(
app.world().get_entity(system_entity).is_ok(),
"System entity should still exist after handle cleanup (LazySignal struct alive)"
);
assert!(
app.world().get::<LazySignalHolder>(system_entity).is_some(),
"LazySignalHolder should still exist"
);
drop(source_signal_struct);
app.update();
app.update();
assert!(
app.world().get_entity(system_entity).is_err(),
"System entity persists because LazySignalHolder was not removed and its LazySignal did not trigger cleanup on its own drop"
);
}
#[test]
fn multiple_lazy_signal_clones_cleanup_behavior() {
let mut app = create_test_app();
let s1 = signal::from_system(|In(_)| 1);
let s2 = s1.clone();
let handle = s1.clone().register(app.world_mut());
let system_entity = handle.0.entity();
assert!(app.world().get_entity(system_entity).is_ok());
assert_eq!(**app.world().get::<SignalRegistrationCount>(system_entity).unwrap(), 1);
assert!(app.world().get::<LazySignalHolder>(system_entity).is_some());
handle.cleanup(app.world_mut());
assert_eq!(**app.world().get::<SignalRegistrationCount>(system_entity).unwrap(), 0);
assert!(app.world().get_entity(system_entity).is_ok());
assert!(app.world().get::<LazySignalHolder>(system_entity).is_some());
drop(s1);
app.update();
assert!(
app.world().get_entity(system_entity).is_ok(),
"Entity persists after s1 drop"
);
assert!(app.world().get::<LazySignalHolder>(system_entity).is_some());
drop(s2);
app.update();
assert!(
app.world().get_entity(system_entity).is_err(),
"Entity despawned after s2 drop, only holder left"
);
}
#[test]
fn multiple_handles_same_system() {
let mut app = create_test_app();
let source_signal_struct = signal::from_system(|In(_)| 1);
let handle1 = source_signal_struct.clone().register(app.world_mut());
let system_entity = handle1.0.entity();
assert_eq!(**app.world().get::<SignalRegistrationCount>(system_entity).unwrap(), 1);
let handle2 = source_signal_struct.clone().register(app.world_mut());
assert_eq!(system_entity, handle2.0.entity());
assert_eq!(**app.world().get::<SignalRegistrationCount>(system_entity).unwrap(), 2);
handle1.cleanup(app.world_mut());
assert_eq!(**app.world().get::<SignalRegistrationCount>(system_entity).unwrap(), 1);
assert!(app.world().get_entity(system_entity).is_ok());
assert!(app.world().get::<LazySignalHolder>(system_entity).is_some());
drop(source_signal_struct);
app.update();
assert!(app.world().get_entity(system_entity).is_ok());
assert!(app.world().get::<LazySignalHolder>(system_entity).is_some());
handle2.cleanup(app.world_mut());
app.update();
assert!(app.world().get_entity(system_entity).is_err());
}
#[test]
fn chained_signals_cleanup() {
let mut app = create_test_app();
let source_s = signal::from_system(|In(_)| 1);
let map_s = source_s.map(|In(val)| val + 1);
let handle = map_s.clone().register(app.world_mut());
let map_entity = handle.0.entity();
let source_entity = app
.world()
.get::<Upstream>(map_entity)
.unwrap()
.iter()
.next()
.unwrap()
.entity();
assert!(app.world().get_entity(map_entity).is_ok());
assert!(app.world().get_entity(source_entity).is_ok());
assert!(app.world().get::<LazySignalHolder>(map_entity).is_some());
assert!(app.world().get::<LazySignalHolder>(source_entity).is_some());
assert_eq!(**app.world().get::<SignalRegistrationCount>(map_entity).unwrap(), 1);
assert_eq!(**app.world().get::<SignalRegistrationCount>(source_entity).unwrap(), 1);
handle.cleanup(app.world_mut());
assert_eq!(**app.world().get::<SignalRegistrationCount>(map_entity).unwrap(), 0);
assert_eq!(**app.world().get::<SignalRegistrationCount>(source_entity).unwrap(), 0);
assert!(app.world().get_entity(map_entity).is_ok());
assert!(app.world().get_entity(source_entity).is_ok());
assert!(app.world().get::<LazySignalHolder>(map_entity).is_some());
assert!(app.world().get::<LazySignalHolder>(source_entity).is_some());
drop(map_s);
app.update();
assert!(app.world().get_entity(map_entity).is_err(), "Map entity persists");
assert!(app.world().get_entity(source_entity).is_err(), "Source entity persists");
}
#[test]
fn re_register_after_cleanup_while_lazy_alive() {
let mut app = create_test_app();
let source_signal_struct = signal::from_system(|In(_)| 1);
let handle1 = source_signal_struct.clone().register(app.world_mut());
let system_entity = handle1.0.entity();
assert_eq!(**app.world().get::<SignalRegistrationCount>(system_entity).unwrap(), 1);
handle1.cleanup(app.world_mut());
assert_eq!(**app.world().get::<SignalRegistrationCount>(system_entity).unwrap(), 0);
assert!(app.world().get_entity(system_entity).is_ok());
assert!(app.world().get::<LazySignalHolder>(system_entity).is_some());
let handle2 = source_signal_struct.clone().register(app.world_mut());
assert_eq!(system_entity, handle2.0.entity());
assert_eq!(**app.world().get::<SignalRegistrationCount>(system_entity).unwrap(), 1);
assert!(app.world().get::<LazySignalHolder>(system_entity).is_some());
drop(source_signal_struct);
app.update();
assert!(app.world().get_entity(system_entity).is_ok());
assert!(app.world().get::<LazySignalHolder>(system_entity).is_some());
handle2.cleanup(app.world_mut());
assert!(app.world().get_entity(system_entity).is_err());
}
#[test]
fn test_signal_eq() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<bool>::default());
let s1 = signal::from_system(|In(_)| 1);
let s2 = signal::from_system(|In(_)| 1);
let s3 = signal::from_system(|In(_)| 1);
let s4 = signal::from_system(|In(_)| 2);
let eq_signal = crate::signal::eq!(s1.clone(), s2.clone(), s3.clone());
eq_signal.map(capture_output::<bool>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<bool>>().0, Some(true));
let neq_signal = crate::signal::eq!(s1.clone(), s2.clone(), s4.clone());
neq_signal.map(capture_output::<bool>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<bool>>().0, Some(false));
}
#[test]
fn test_signal_and() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<bool>::default());
let t = signal::from_system(|In(_)| true);
let f = signal::from_system(|In(_)| false);
let all_signal = crate::signal::all!(t.clone(), t.clone(), t.clone());
all_signal.map(capture_output::<bool>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<bool>>().0, Some(true));
let all_signal = crate::signal::all!(t.clone(), f.clone(), t.clone());
all_signal.map(capture_output::<bool>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<bool>>().0, Some(false));
}
#[test]
fn test_signal_or() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<bool>::default());
let t = signal::from_system(|In(_)| true);
let f = signal::from_system(|In(_)| false);
let any_signal = crate::signal::any!(f.clone(), f.clone(), f.clone());
any_signal.map(capture_output::<bool>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<bool>>().0, Some(false));
let any_signal = crate::signal::any!(f.clone(), t.clone(), f.clone());
any_signal.map(capture_output::<bool>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<bool>>().0, Some(true));
}
#[test]
fn test_signal_distinct() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<bool>::default());
let s1 = signal::from_system(|In(_)| 1);
let s2 = signal::from_system(|In(_)| 2);
let s3 = signal::from_system(|In(_)| 3);
let distinct_signal = crate::signal::distinct!(s1.clone(), s2.clone(), s3.clone());
distinct_signal.map(capture_output::<bool>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<bool>>().0, Some(true));
let mut app = create_test_app();
app.insert_resource(SignalOutput::<bool>::default());
let s1 = signal::from_system(|In(_)| 1);
let s2 = signal::from_system(|In(_)| 2);
let distinct_signal = crate::signal::distinct!(s1.clone(), s2.clone(), s1.clone());
distinct_signal.map(capture_output::<bool>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<bool>>().0, Some(false));
}
#[test]
fn test_signal_sum() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<i32>::default());
let s1 = signal::from_system(|In(_)| 1);
let s2 = signal::from_system(|In(_)| 2);
let sum_signal = crate::signal::sum!(s1, s2);
sum_signal.map(capture_output::<i32>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(3));
let mut app = create_test_app();
app.insert_resource(SignalOutput::<i32>::default());
let s1 = signal::from_system(|In(_)| 1);
let s2 = signal::from_system(|In(_)| 2);
let s3 = signal::from_system(|In(_)| 3);
let s4 = signal::from_system(|In(_)| 4);
let sum_signal = crate::signal::sum!(s1, s2, s3, s4);
sum_signal.map(capture_output::<i32>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(10));
}
#[test]
fn test_signal_product() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<i32>::default());
let s1 = signal::from_system(|In(_)| 2);
let s2 = signal::from_system(|In(_)| 3);
let product_signal = crate::signal::product!(s1, s2);
product_signal.map(capture_output::<i32>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(6));
let mut app = create_test_app();
app.insert_resource(SignalOutput::<i32>::default());
let s1 = signal::from_system(|In(_)| 1);
let s2 = signal::from_system(|In(_)| 2);
let s3 = signal::from_system(|In(_)| 3);
let s4 = signal::from_system(|In(_)| 4);
let product_signal = crate::signal::product!(s1, s2, s3, s4);
product_signal.map(capture_output::<i32>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(24));
}
#[test]
fn test_signal_min() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<i32>::default());
let s1 = signal::from_system(|In(_)| 5);
let s2 = signal::from_system(|In(_)| 2);
let min_signal = crate::signal::min!(s1, s2);
min_signal.map(capture_output::<i32>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(2));
let mut app = create_test_app();
app.insert_resource(SignalOutput::<i32>::default());
let s1 = signal::from_system(|In(_)| 10);
let s2 = signal::from_system(|In(_)| 2);
let s3 = signal::from_system(|In(_)| 7);
let s4 = signal::from_system(|In(_)| 3);
let min_signal = crate::signal::min!(s1, s2, s3, s4);
min_signal.map(capture_output::<i32>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(2));
}
#[test]
fn test_signal_max() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<i32>::default());
let s1 = signal::from_system(|In(_)| 5);
let s2 = signal::from_system(|In(_)| 2);
let max_signal = crate::signal::max!(s1, s2);
max_signal.map(capture_output::<i32>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(5));
let mut app = create_test_app();
app.insert_resource(SignalOutput::<i32>::default());
let s1 = signal::from_system(|In(_)| 10);
let s2 = signal::from_system(|In(_)| 2);
let s3 = signal::from_system(|In(_)| 7);
let s4 = signal::from_system(|In(_)| 3);
let max_signal = crate::signal::max!(s1, s2, s3, s4);
max_signal.map(capture_output::<i32>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(10));
}
#[test]
fn test_map_bool_in() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<&'static str>::default());
let signal = signal::from_system({
move |In(_), mut state: Local<bool>| {
*state = !*state;
*state
}
})
.map_bool_in(|| "true", || "false")
.map(capture_output::<&'static str>);
signal.register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<&'static str>>().0, Some("true"));
app.update();
assert_eq!(app.world().resource::<SignalOutput<&'static str>>().0, Some("false"));
app.update();
assert_eq!(app.world().resource::<SignalOutput<&'static str>>().0, Some("true"));
}
#[test]
fn test_map_bool_in_with_optional_termination() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<i32>::default());
let signal = signal::from_system({
move |In(_), mut state: Local<bool>| {
*state = !*state;
*state
}
})
.map_bool_in(
|| Some(1), || None, )
.map(capture_output::<i32>);
signal.register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(1));
app.world_mut().resource_mut::<SignalOutput<i32>>().0 = None;
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, None);
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(1));
}
#[test]
fn test_map_true_in() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<Option<i32>>::default());
let signal = signal::from_system({
move |In(_), mut state: Local<bool>| {
*state = !*state;
*state
}
})
.map_true_in(|| 42);
signal.map(capture_output::<Option<i32>>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(Some(42)));
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(None));
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(Some(42)));
}
#[test]
fn test_map_false_in() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<Option<i32>>::default());
let signal = signal::from_system({
move |In(_), mut state: Local<bool>| {
*state = !*state;
*state
}
})
.map_false_in(|| 99);
signal.map(capture_output::<Option<i32>>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(None));
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(Some(99)));
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(None));
}
#[test]
fn test_map_option_in() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<i32>::default());
let signal = signal::from_system({
move |In(_), mut state: Local<Option<i32>>| {
*state = match *state {
None => Some(10),
Some(10) => Some(20),
Some(20) => None,
Some(_) => Some(10),
};
*state
}
})
.map_option_in(|value: i32| value * 2, || -1);
signal.map(capture_output::<i32>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(20));
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(40));
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(-1));
app.update();
assert_eq!(app.world().resource::<SignalOutput<i32>>().0, Some(20));
}
#[test]
fn test_map_some_in() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<Option<i32>>::default());
let signal = signal::from_system({
move |In(_), mut state: Local<Option<i32>>| {
*state = if state.is_some() { None } else { Some(42) };
*state
}
})
.map_some_in(|value: i32| value * 3);
signal.map(capture_output::<Option<i32>>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(Some(126)));
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(None));
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(Some(126)));
}
#[test]
fn test_map_none_in() {
let mut app = create_test_app();
app.insert_resource(SignalOutput::<Option<i32>>::default());
let signal = signal::from_system({
move |In(_), mut state: Local<Option<i32>>| {
*state = if state.is_some() { None } else { Some(100) };
*state
}
})
.map_none_in(|| 999);
signal.map(capture_output::<Option<i32>>).register(app.world_mut());
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(None));
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(Some(999)));
app.update();
assert_eq!(app.world().resource::<SignalOutput<Option<i32>>>().0, Some(None));
}
#[test]
fn multi_schedule_chain_tags_correctly() {
use crate::graph::{ScheduleTag, Upstream};
use bevy_app::{PostUpdate, Update};
use bevy_ecs::schedule::ScheduleLabel;
let mut app = App::new();
app.add_plugins((
MinimalPlugins,
crate::JonmoPlugin::new::<PostUpdate>().with_schedule::<Update>(),
));
let handle = signal::from_system(|In(_)| Some(1i32))
.map_in(|x: i32| x + 1)
.schedule::<Update>()
.map_in(|x: i32| x * 2)
.schedule::<PostUpdate>()
.map_in(|x: i32| x - 1)
.register(app.world_mut());
let map3_entity = **handle;
let world = app.world();
let map3_upstreams = world.get::<Upstream>(map3_entity).unwrap();
assert_eq!(map3_upstreams.len(), 1);
let map2_entity = **map3_upstreams.iter().next().unwrap();
let map2_upstreams = world.get::<Upstream>(map2_entity).unwrap();
assert_eq!(map2_upstreams.len(), 1);
let map1_entity = **map2_upstreams.iter().next().unwrap();
let map1_upstreams = world.get::<Upstream>(map1_entity).unwrap();
assert_eq!(map1_upstreams.len(), 1);
let source_entity = **map1_upstreams.iter().next().unwrap();
let source_tag = world.get::<ScheduleTag>(source_entity);
assert!(source_tag.is_some(), "source should have a schedule tag");
assert_eq!(source_tag.unwrap().0, Update.intern(), "source should be tagged Update");
let map1_tag = world.get::<ScheduleTag>(map1_entity);
assert!(map1_tag.is_some(), "map1 should have a schedule tag");
assert_eq!(
map1_tag.unwrap().0,
Update.intern(),
"map1 should be tagged Update (caller of schedule::<Update>)"
);
let map2_tag = world.get::<ScheduleTag>(map2_entity);
assert!(map2_tag.is_some(), "map2 should have a schedule tag");
assert_eq!(
map2_tag.unwrap().0,
PostUpdate.intern(),
"map2 should be tagged PostUpdate (caller of schedule::<PostUpdate>)"
);
let map3_tag = world.get::<ScheduleTag>(map3_entity);
assert!(map3_tag.is_some(), "map3 should have a schedule tag");
assert_eq!(
map3_tag.unwrap().0,
PostUpdate.intern(),
"map3 should inherit PostUpdate"
);
}
}