use super::{
graph::{
LazySignal, SignalHandle, SignalHandles, SignalSystem, apply_schedule_to_signal, downcast_any_clone,
lazy_signal_from_system, pipe_signal, poll_signal, register_signal, trigger_signal_subgraph,
},
signal::{self, Signal, SignalExt},
signal_vec::{ReplayOnce, Replayable, SignalVec, VecDiff},
utils::LazyEntity,
};
use crate::prelude::clone;
use alloc::collections::BTreeMap;
use bevy_ecs::{prelude::*, schedule::ScheduleLabel};
use bevy_platform::{
prelude::*,
sync::{Arc, LazyLock, Mutex},
};
use core::{
fmt,
marker::PhantomData,
ops::Deref,
sync::atomic::{self, AtomicUsize},
};
use dyn_clone::{DynClone, clone_trait_object};
#[allow(missing_docs)]
pub enum MapDiff<K, V> {
Replace { entries: Vec<(K, V)> },
Insert { key: K, value: V },
Update { key: K, value: V },
Remove { key: K },
Clear,
}
impl<K, V> Clone for MapDiff<K, V>
where
K: Clone,
V: Clone,
{
fn clone(&self) -> Self {
match self {
MapDiff::Replace { entries } => MapDiff::Replace {
entries: entries.clone(),
},
MapDiff::Insert { key, value } => MapDiff::Insert {
key: key.clone(),
value: value.clone(),
},
MapDiff::Update { key, value } => MapDiff::Update {
key: key.clone(),
value: value.clone(),
},
MapDiff::Remove { key } => MapDiff::Remove { key: key.clone() },
MapDiff::Clear => MapDiff::Clear,
}
}
}
impl<K, V> fmt::Debug for MapDiff<K, V>
where
K: fmt::Debug,
V: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MapDiff::Replace { entries } => f.debug_struct("Replace").field("entries", entries).finish(),
MapDiff::Insert { key, value } => f
.debug_struct("Insert")
.field("key", key)
.field("value", value)
.finish(),
MapDiff::Update { key, value } => f
.debug_struct("Update")
.field("key", key)
.field("value", value)
.finish(),
MapDiff::Remove { key } => f.debug_struct("Remove").field("key", key).finish(),
MapDiff::Clear => f.debug_struct("Clear").finish(),
}
}
}
impl<K, V> MapDiff<K, V> {
pub fn map_value<O, F>(self, mut callback: F) -> MapDiff<K, O>
where
F: FnMut(V) -> O,
{
match self {
MapDiff::Replace { entries } => MapDiff::Replace {
entries: entries.into_iter().map(|(k, v)| (k, callback(v))).collect(),
},
MapDiff::Insert { key, value } => MapDiff::Insert {
key,
value: callback(value),
},
MapDiff::Update { key, value } => MapDiff::Update {
key,
value: callback(value),
},
MapDiff::Remove { key } => MapDiff::Remove { key },
MapDiff::Clear => MapDiff::Clear {},
}
}
}
pub trait SignalMap: Send + Sync + 'static {
#[allow(missing_docs)]
type Key;
#[allow(missing_docs)]
type Value;
fn register_boxed_signal_map(self: Box<Self>, world: &mut World) -> SignalHandle;
fn register_signal_map(self, world: &mut World) -> SignalHandle
where
Self: Sized,
{
self.boxed().register_boxed_signal_map(world)
}
}
impl<K: 'static, V: 'static> SignalMap for Box<dyn SignalMap<Key = K, Value = V> + Send + Sync> {
type Key = K;
type Value = V;
fn register_boxed_signal_map(self: Box<Self>, world: &mut World) -> SignalHandle {
(*self).register_boxed_signal_map(world)
}
}
pub trait SignalMapDynClone: SignalMap + DynClone {}
clone_trait_object!(<K, V> SignalMapDynClone<Key = K, Value = V>);
impl<T: SignalMap + Clone + 'static> SignalMapDynClone for T {}
impl<K: 'static, V: 'static> SignalMap for Box<dyn SignalMapDynClone<Key = K, Value = V> + Send + Sync> {
type Key = K;
type Value = V;
fn register_boxed_signal_map(self: Box<Self>, world: &mut World) -> SignalHandle {
(*self).register_boxed_signal_map(world)
}
}
pub struct ForEach<Upstream, O> {
upstream: Upstream,
signal: LazySignal,
_marker: PhantomData<fn() -> O>,
}
impl<Upstream, O> Clone for ForEach<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 ForEach<Upstream, O>
where
Upstream: SignalMap,
O: 'static,
{
type Item = O;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
let SignalHandle(upstream) = self.upstream.register_signal_map(world);
let signal = self.signal.register(world);
pipe_signal(world, upstream, signal);
signal.into()
}
}
pub struct MapValue<Upstream, O> {
signal: LazySignal,
_marker: PhantomData<fn() -> (Upstream, O)>,
}
impl<Upstream, O> Clone for MapValue<Upstream, O> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, O> SignalMap for MapValue<Upstream, O>
where
Upstream: SignalMap,
O: 'static,
{
type Key = Upstream::Key;
type Value = O;
fn register_boxed_signal_map(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct MapValueSignal<Upstream, S: Signal> {
signal: LazySignal,
_marker: PhantomData<fn() -> (Upstream, S)>,
}
impl<Upstream, S: Signal> Clone for MapValueSignal<Upstream, S> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Upstream, S: Signal> SignalMap for MapValueSignal<Upstream, S>
where
Upstream: SignalMap,
S: Signal + 'static,
S::Item: Clone + Send + Sync + 'static,
{
type Key = Upstream::Key;
type Value = S::Item;
fn register_boxed_signal_map(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct Key<Upstream>
where
Upstream: SignalMap,
{
inner: ForEach<Upstream, Option<Upstream::Value>>,
}
impl<Upstream> Clone for Key<Upstream>
where
Upstream: SignalMap + Clone,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<Upstream> Signal for Key<Upstream>
where
Upstream: SignalMap,
Upstream::Key: 'static,
Upstream::Value: 'static,
{
type Item = Option<Upstream::Value>;
fn register_boxed_signal(self: Box<Self>, world: &mut World) -> SignalHandle {
self.inner.register_signal(world)
}
}
cfg_if::cfg_if! {
if #[cfg(feature = "tracing")] {
pub struct Debug<Upstream>
where
Upstream: SignalMap,
{
#[allow(clippy::type_complexity)]
signal: ForEach<Upstream, Vec<MapDiff<Upstream::Key, Upstream::Value>>>,
}
impl<Upstream> Clone for Debug<Upstream>
where
Upstream: SignalMap + Clone,
{
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
}
}
}
impl<Upstream> SignalMap for Debug<Upstream>
where
Upstream: SignalMap,
{
type Key = Upstream::Key;
type Value = Upstream::Value;
fn register_boxed_signal_map(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world)
}
}
}
}
pub struct SignalVecKeys<K> {
signal: LazySignal,
_marker: PhantomData<fn() -> K>,
}
impl<K> Clone for SignalVecKeys<K> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<K> SignalVec for SignalVecKeys<K>
where
K: 'static,
{
type Item = K;
fn register_boxed_signal_vec(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
pub struct SignalVecEntries<K, V> {
signal: LazySignal,
_marker: PhantomData<fn() -> (K, V)>,
}
impl<K, V> Clone for SignalVecEntries<K, V> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<K, V> SignalVec for SignalVecEntries<K, V>
where
K: 'static,
V: 'static,
{
type Item = (K, V);
fn register_boxed_signal_vec(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
#[allow(missing_docs)]
pub enum SignalMapEither<L, R>
where
L: SignalMap,
R: SignalMap,
{
Left(L),
Right(R),
}
impl<L, R> Clone for SignalMapEither<L, R>
where
L: SignalMap + Clone,
R: SignalMap + Clone,
{
fn clone(&self) -> Self {
match self {
Self::Left(left) => Self::Left(left.clone()),
Self::Right(right) => Self::Right(right.clone()),
}
}
}
impl<K, V, L, R> SignalMap for SignalMapEither<L, R>
where
L: SignalMap<Key = K, Value = V>,
R: SignalMap<Key = K, Value = V>,
{
type Key = K;
type Value = V;
fn register_boxed_signal_map(self: Box<Self>, world: &mut World) -> SignalHandle {
match *self {
SignalMapEither::Left(left) => left.register_signal_map(world),
SignalMapEither::Right(right) => right.register_signal_map(world),
}
}
}
pub trait IntoSignalMapEither: Sized
where
Self: SignalMap,
{
fn left_either<R>(self) -> SignalMapEither<Self, R>
where
R: SignalMap,
{
SignalMapEither::Left(self)
}
fn right_either<L>(self) -> SignalMapEither<L, Self>
where
L: SignalMap,
{
SignalMapEither::Right(self)
}
}
impl<T: SignalMap> IntoSignalMapEither for T {}
pub trait SignalMapExt: SignalMap {
fn for_each<O, IOO, F, M>(self, system: F) -> ForEach<Self, O>
where
Self: Sized,
Self::Key: Send + Sync + 'static,
Self::Value: Send + Sync + 'static,
O: Clone + Send + Sync + 'static,
IOO: Into<Option<O>> + 'static,
F: IntoSystem<In<Vec<MapDiff<Self::Key, Self::Value>>>, IOO, M> + Send + Sync + 'static,
{
ForEach {
upstream: self,
signal: lazy_signal_from_system(system),
_marker: PhantomData,
}
}
fn map_value<O, F, M>(self, system: F) -> MapValue<Self, O>
where
Self: Sized,
Self::Key: Clone + Send + Sync + 'static,
Self::Value: Send + Sync + 'static,
O: Clone + Send + Sync + 'static,
F: IntoSystem<In<Self::Value>, O, M> + Send + Sync + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let system_id = world.register_system(system);
let upstream_handle = self.register_signal_map(world);
let processor_logic = move |In(diffs): In<Vec<MapDiff<Self::Key, Self::Value>>>, world: &mut World| {
let mut out_diffs = Vec::with_capacity(diffs.len());
for diff in diffs {
let new_diff = diff.map_value(|v| world.run_system_with(system_id, v).unwrap());
out_diffs.push(new_diff);
}
if out_diffs.is_empty() { None } else { Some(out_diffs) }
};
let processor_handle =
lazy_signal_from_system::<_, Vec<MapDiff<Self::Key, O>>, _, _, _>(processor_logic).register(world);
world.entity_mut(*processor_handle).add_child(system_id.entity());
pipe_signal(world, *upstream_handle, processor_handle);
processor_handle
});
MapValue {
signal,
_marker: PhantomData,
}
}
fn map_value_signal<S, F, M>(self, system: F) -> MapValueSignal<Self, S>
where
Self: Sized,
Self::Key: Ord + Clone + Send + Sync + 'static,
Self::Value: Send + Sync + 'static,
S: Signal + Clone + 'static,
S::Item: Clone + Send + Sync + 'static,
F: IntoSystem<In<Self::Value>, S, M> + Send + Sync + 'static,
{
#[derive(Component)]
struct QueuedMapDiffs<K, V>(Vec<MapDiff<K, V>>);
let signal = LazySignal::new(move |world: &mut World| {
let factory_system_id = world.register_system(system);
let output_signal_entity = LazyEntity::new();
let output_signal = *signal::from_system::<Vec<MapDiff<Self::Key, S::Item>>, _, _, _>(
clone!((output_signal_entity) move |In(_), world: &mut World| {
let mut diffs = world.get_mut::<QueuedMapDiffs<Self::Key, S::Item>>(*output_signal_entity).unwrap();
if diffs.0.is_empty() {
None
} else {
Some(diffs.0.drain(..).collect())
}
}),
)
.register(world);
output_signal_entity.set(*output_signal);
fn spawn_processor<K: Clone + Send + Sync + 'static, V: Clone + Send + Sync + 'static>(
world: &mut World,
output_signal: SignalSystem,
key: K,
inner_signal: impl Signal<Item = V> + Clone + 'static,
) -> (SignalHandle, SignalSystem, V) {
let inner_signal_id = inner_signal.clone().register(world);
let temp_handle = inner_signal.clone().first().register(world);
let initial_value = poll_signal(world, *temp_handle)
.and_then(downcast_any_clone::<V>)
.expect("map_value_signal's inner signal must emit an initial value");
temp_handle.cleanup(world);
let processor_handle = inner_signal
.map(move |In(value): In<V>, world: &mut World| {
world
.get_mut::<QueuedMapDiffs<K, V>>(*output_signal)
.unwrap()
.0
.push(MapDiff::Update {
key: key.clone(),
value,
});
trigger_signal_subgraph(world, [output_signal], Box::new(()));
})
.register(world);
(processor_handle, *inner_signal_id, initial_value)
}
#[derive(Component)]
struct ManagerState<K, S: Signal> {
signals: BTreeMap<K, (SignalHandle, SignalSystem)>,
_phantom: PhantomData<S>,
}
let manager_system_logic = move |In(diffs): In<Vec<MapDiff<Self::Key, Self::Value>>>, world: &mut World| {
let mut new_map_diffs = Vec::new();
for diff in diffs {
match diff {
MapDiff::Replace { entries } => {
let old_signals = {
let mut state = world.get_mut::<ManagerState<Self::Key, S>>(*output_signal).unwrap();
core::mem::take(&mut state.signals)
};
for (_, (handle, _)) in old_signals {
handle.cleanup(world);
}
let mut new_signals = BTreeMap::new();
let mut new_entries_for_diff = Vec::with_capacity(entries.len());
for (key, value) in entries {
if let Ok(inner_signal) = world.run_system_with(factory_system_id, value) {
let (handle, id, initial_value) =
spawn_processor(world, output_signal, key.clone(), inner_signal);
new_signals.insert(key.clone(), (handle, id));
new_entries_for_diff.push((key, initial_value));
}
}
world
.get_mut::<ManagerState<Self::Key, S>>(*output_signal)
.unwrap()
.signals = new_signals;
if !new_entries_for_diff.is_empty() {
new_map_diffs.push(MapDiff::Replace {
entries: new_entries_for_diff,
});
}
}
MapDiff::Insert { key, value } => {
if let Ok(inner_signal) = world.run_system_with(factory_system_id, value) {
let (handle, id, initial_value) =
spawn_processor(world, output_signal, key.clone(), inner_signal);
let old_handle = {
let mut state =
world.get_mut::<ManagerState<Self::Key, S>>(*output_signal).unwrap();
state.signals.insert(key.clone(), (handle, id))
};
if let Some((old_handle, _)) = old_handle {
old_handle.cleanup(world);
}
new_map_diffs.push(MapDiff::Insert {
key,
value: initial_value,
});
}
}
MapDiff::Update { key, value } => {
if let Ok(new_inner_signal) = world.run_system_with(factory_system_id, value) {
let new_inner_id = new_inner_signal.clone().register(world);
let old_inner_id_opt = {
let state = world.get::<ManagerState<Self::Key, S>>(*output_signal).unwrap();
state.signals.get(&key).map(|(_, id)| *id)
};
if old_inner_id_opt == Some(*new_inner_id) {
new_inner_id.cleanup(world);
continue;
}
let (new_processor_handle, new_processor_id, initial_value) =
spawn_processor(world, output_signal, key.clone(), new_inner_signal);
let old_processor_handle = {
let mut state =
world.get_mut::<ManagerState<Self::Key, S>>(*output_signal).unwrap();
state
.signals
.insert(key.clone(), (new_processor_handle, new_processor_id))
};
if let Some((old_handle, _)) = old_processor_handle {
old_handle.cleanup(world);
}
new_inner_id.cleanup(world);
new_map_diffs.push(MapDiff::Update {
key,
value: initial_value,
});
}
}
MapDiff::Remove { key } => {
let old_handle = {
let mut state = world.get_mut::<ManagerState<Self::Key, S>>(*output_signal).unwrap();
state.signals.remove(&key)
};
if let Some((handle, _)) = old_handle {
handle.cleanup(world);
}
new_map_diffs.push(MapDiff::Remove { key });
}
MapDiff::Clear => {
let old_signals = {
let mut state = world.get_mut::<ManagerState<Self::Key, S>>(*output_signal).unwrap();
if state.signals.is_empty() {
BTreeMap::new()
} else {
core::mem::take(&mut state.signals)
}
};
if !old_signals.is_empty() {
for (_, (handle, _)) in old_signals {
handle.cleanup(world);
}
new_map_diffs.push(MapDiff::Clear);
}
}
}
}
if !new_map_diffs.is_empty() {
world
.get_mut::<QueuedMapDiffs<Self::Key, S::Item>>(*output_signal)
.unwrap()
.0
.extend(new_map_diffs);
}
trigger_signal_subgraph(world, [output_signal], Box::new(()));
};
let manager_handle = self.for_each(manager_system_logic).register(world);
world
.entity_mut(*output_signal)
.insert((
ManagerState::<Self::Key, S> {
signals: BTreeMap::new(),
_phantom: PhantomData,
},
QueuedMapDiffs::<Self::Key, S::Item>(vec![]),
))
.add_child(factory_system_id.entity())
.insert(SignalHandles::from([manager_handle]));
output_signal
});
MapValueSignal {
signal,
_marker: PhantomData,
}
}
fn key(self, key: Self::Key) -> Key<Self>
where
Self: Sized,
Self::Key: PartialEq + Send + Sync + 'static,
Self::Value: Clone + Send + Sync + 'static,
{
Key {
inner: self.for_each(
move |In(diffs): In<Vec<MapDiff<Self::Key, Self::Value>>>, mut state: Local<Option<Self::Value>>| {
let mut changed = false;
let mut new_value = (*state).clone();
for diff in diffs {
match diff {
MapDiff::Replace { entries } => {
new_value = entries.into_iter().find(|(k, _)| *k == key).map(|(_, v)| v);
changed = true;
}
MapDiff::Insert { key: k, value } | MapDiff::Update { key: k, value } => {
if k == key {
new_value = Some(value);
changed = true;
}
}
MapDiff::Remove { key: k } => {
if k == key {
new_value = None;
changed = true;
}
}
MapDiff::Clear => {
new_value = None;
changed = true;
}
}
}
if changed {
*state = new_value.clone();
Some(new_value)
} else {
None
}
},
),
}
}
#[cfg(feature = "tracing")]
#[track_caller]
fn debug(self) -> Debug<Self>
where
Self: Sized,
Self::Key: fmt::Debug + Clone + Send + Sync + 'static,
Self::Value: fmt::Debug + Clone + Send + Sync + 'static,
{
let location = core::panic::Location::caller();
Debug {
signal: self.for_each(move |In(item)| {
bevy_log::debug!("[{}] {:#?}", location, item);
item
}),
}
}
fn boxed(self) -> Box<dyn SignalMap<Key = Self::Key, Value = Self::Value>>
where
Self: Sized,
{
Box::new(self)
}
fn boxed_clone(self) -> Box<dyn SignalMapDynClone<Key = Self::Key, Value = Self::Value> + Send + Sync>
where
Self: Sized + Clone,
{
Box::new(self)
}
fn schedule<Sched: ScheduleLabel + Default + 'static>(self) -> ScheduledMap<Sched, Self::Key, Self::Value>
where
Self: Sized + 'static,
{
let signal = LazySignal::new(move |world: &mut World| {
let handle = self.register_signal_map(world);
apply_schedule_to_signal(world, *handle, Sched::default().intern());
*handle
});
ScheduledMap {
signal,
_marker: PhantomData,
}
}
fn register(self, world: &mut World) -> SignalHandle
where
Self: Sized,
{
self.register_signal_map(world)
}
}
impl<T: ?Sized> SignalMapExt for T where T: SignalMap {}
pub struct ScheduledMap<Sched, K, V> {
signal: LazySignal,
#[allow(clippy::type_complexity)]
_marker: PhantomData<fn() -> (Sched, K, V)>,
}
impl<Sched, K, V> Clone for ScheduledMap<Sched, K, V> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<Sched: 'static, K: Send + Sync + 'static, V: Send + Sync + 'static> SignalMap for ScheduledMap<Sched, K, V> {
type Key = K;
type Value = V;
fn register_boxed_signal_map(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
static STALE_MUTABLE_BTREE_MAPS: LazyLock<Mutex<Vec<Entity>>> = LazyLock::new(Mutex::default);
pub(crate) fn despawn_stale_mutable_btree_maps(world: &mut World) {
let queue = STALE_MUTABLE_BTREE_MAPS.lock().unwrap().drain(..).collect::<Vec<_>>();
for entity in queue {
world.despawn(entity);
}
}
pub struct MutableBTreeMapReadGuard<'s, K, V> {
guard: &'s MutableBTreeMapData<K, V>,
}
impl<'s, K, V> Deref for MutableBTreeMapReadGuard<'s, K, V> {
type Target = BTreeMap<K, V>;
fn deref(&self) -> &Self::Target {
&self.guard.map
}
}
pub struct MutableBTreeMapWriteGuard<'s, K, V> {
guard: Mut<'s, MutableBTreeMapData<K, V>>,
}
impl<'s, K, V> Deref for MutableBTreeMapWriteGuard<'s, K, V> {
type Target = BTreeMap<K, V>;
fn deref(&self) -> &Self::Target {
&self.guard.map
}
}
impl<'a, K, V> MutableBTreeMapWriteGuard<'a, K, V>
where
K: Ord + Clone,
V: Clone,
{
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
let diff = if self.guard.map.contains_key(&key) {
MapDiff::Update {
key: key.clone(),
value: value.clone(),
}
} else {
MapDiff::Insert {
key: key.clone(),
value: value.clone(),
}
};
let old = self.guard.map.insert(key, value);
self.guard.pending_diffs.push(diff);
old
}
pub fn remove(&mut self, key: &K) -> Option<V> {
let old = self.guard.map.remove(key);
if old.is_some() {
self.guard.pending_diffs.push(MapDiff::Remove { key: key.clone() });
}
old
}
pub fn clear(&mut self) {
if !self.guard.map.is_empty() {
self.guard.map.clear();
self.guard.pending_diffs.push(MapDiff::Clear);
}
}
pub fn replace<T>(&mut self, entries: T)
where
BTreeMap<K, V>: From<T>,
{
self.guard.map = entries.into();
let entries = self.guard.map.clone().into_iter().collect();
self.guard.pending_diffs.push(MapDiff::Replace { entries });
}
}
#[derive(Component)]
pub struct MutableBTreeMapData<K, V> {
map: BTreeMap<K, V>,
pending_diffs: Vec<MapDiff<K, V>>,
broadcaster: LazySignal,
}
pub struct MutableBTreeMap<K, V> {
entity: Entity,
references: Arc<AtomicUsize>,
_marker: PhantomData<fn() -> (K, V)>,
}
impl<K, V> Clone for MutableBTreeMap<K, V> {
fn clone(&self) -> Self {
self.references.fetch_add(1, atomic::Ordering::Relaxed);
Self {
entity: self.entity,
references: self.references.clone(),
_marker: PhantomData,
}
}
}
impl<K, V> Drop for MutableBTreeMap<K, V> {
fn drop(&mut self) {
if self.references.fetch_sub(1, atomic::Ordering::Relaxed) == 1 {
STALE_MUTABLE_BTREE_MAPS.lock().unwrap().push(self.entity);
}
}
}
pub struct Source<K, V> {
signal: LazySignal,
_marker: PhantomData<fn() -> (K, V)>,
}
impl<K, V> Clone for Source<K, V> {
fn clone(&self) -> Self {
Self {
signal: self.signal.clone(),
_marker: PhantomData,
}
}
}
impl<K, V> SignalMap for Source<K, V>
where
K: 'static,
V: 'static,
{
type Key = K;
type Value = V;
fn register_boxed_signal_map(self: Box<Self>, world: &mut World) -> SignalHandle {
self.signal.register(world).into()
}
}
#[derive(Component)]
pub(crate) struct MapReplayTrigger(Box<dyn Fn(&mut World) + Send + Sync>);
impl Replayable for MapReplayTrigger {
fn trigger(&self) -> &(dyn Fn(&mut World) + Send + Sync) {
&self.0
}
}
fn new_mutable_btree_map_data<K, V>(map: BTreeMap<K, V>) -> (MutableBTreeMapData<K, V>, LazyEntity)
where
K: Ord + Clone + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
let data_entity = LazyEntity::new();
let broadcaster = LazySignal::new(clone!((data_entity) move |world: &mut World| {
let source_system = move |In(_), mut mutable_btree_map_datas: Query<&mut MutableBTreeMapData<K, V>>| {
let mut data = mutable_btree_map_datas.get_mut(*data_entity).unwrap();
if data.pending_diffs.is_empty() {
None
} else {
Some(core::mem::take(&mut data.pending_diffs))
}
};
register_signal::<(), Vec<MapDiff<K, V>>, _, _, _>(world, source_system)
}));
(
MutableBTreeMapData {
map,
pending_diffs: Vec::new(),
broadcaster,
},
data_entity,
)
}
impl<K, V> MutableBTreeMap<K, V> {
pub fn builder() -> MutableBTreeMapBuilder<K, V> {
MutableBTreeMapBuilder::new()
}
pub fn read<'s>(
&self,
mutable_btree_map_data_reader: impl ReadMutableBTreeMapData<'s, K, V>,
) -> MutableBTreeMapReadGuard<'s, K, V>
where
K: Send + Sync + 'static,
V: Send + Sync + 'static,
{
MutableBTreeMapReadGuard {
guard: mutable_btree_map_data_reader.read(self.entity),
}
}
pub fn write<'w>(
&self,
mutable_btree_map_data_writer: impl WriteMutableBTreeMapData<'w, K, V>,
) -> MutableBTreeMapWriteGuard<'w, K, V>
where
K: Send + Sync + 'static,
V: Send + Sync + 'static,
{
MutableBTreeMapWriteGuard {
guard: mutable_btree_map_data_writer.write(self.entity),
}
}
pub fn signal_map(&self) -> Source<K, V>
where
K: Clone + Ord + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
let replay_lazy_signal = LazySignal::new(clone!((self => self_) move |world: &mut World| {
let broadcaster_system = world.get::<MutableBTreeMapData<K, V>>(self_.entity).unwrap().broadcaster.clone().register(world);
let was_initially_empty = self_.read(&*world).is_empty();
let replay_entity = LazyEntity::new();
let replay_system = clone!((self_, replay_entity) move |In(upstream_diffs): In<Vec<MapDiff<K, V>>>, replay_onces: Query<&ReplayOnce>, mutable_btree_map_datas: Query<&MutableBTreeMapData<K, V>>, mut has_replayed: Local<bool>| {
if replay_onces.contains(*replay_entity) {
let first_replay = !core::mem::replace(&mut *has_replayed, true);
if first_replay && was_initially_empty {
if upstream_diffs.is_empty() {
None
} else {
Some(upstream_diffs)
}
} else {
let current_map = self_.read(&mutable_btree_map_datas);
if current_map.is_empty() {
None
} else {
Some(vec![MapDiff::Replace { entries: current_map.iter().map(|(k, v)| (k.clone(), v.clone())).collect() }])
}
}
} else if upstream_diffs.is_empty() { None } else { Some(upstream_diffs) }
});
let replay_signal = register_signal::<_, Vec<MapDiff<K, V>>, _, _, _>(world, replay_system);
replay_entity.set(*replay_signal);
let trigger = Box::new(move |world: &mut World| {
trigger_signal_subgraph(world, [replay_signal], Box::new(Vec::<MapDiff<K, V>>::new()));
});
world.entity_mut(*replay_signal).insert((MapReplayTrigger(trigger), ReplayOnce));
pipe_signal(world, broadcaster_system, replay_signal);
replay_signal
}));
Source {
signal: replay_lazy_signal,
_marker: PhantomData,
}
}
pub fn signal_vec_keys(&self) -> SignalVecKeys<K>
where
K: Ord + Clone + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
let upstream = self.signal_map();
let lazy_signal = LazySignal::new(move |world: &mut World| {
let upstream_handle = upstream.register_signal_map(world);
let processor_logic = move |In(diffs): In<Vec<MapDiff<K, V>>>, mut keys: Local<Vec<K>>| {
let mut out_diffs = Vec::new();
for diff in diffs {
match diff {
MapDiff::Replace { entries } => {
*keys = entries.into_iter().map(|(k, _)| k).collect();
out_diffs.push(VecDiff::Replace { values: keys.clone() });
}
MapDiff::Insert { key, .. } => {
let index = keys.binary_search(&key).unwrap_err();
keys.insert(index, key.clone());
out_diffs.push(VecDiff::InsertAt { index, value: key });
}
MapDiff::Update { .. } => {
}
MapDiff::Remove { key } => {
if let Ok(index) = keys.binary_search(&key) {
keys.remove(index);
out_diffs.push(VecDiff::RemoveAt { index });
}
}
MapDiff::Clear => {
keys.clear();
out_diffs.push(VecDiff::Clear);
}
}
}
if out_diffs.is_empty() { None } else { Some(out_diffs) }
};
let processor_handle =
lazy_signal_from_system::<_, Vec<VecDiff<K>>, _, _, _>(processor_logic).register(world);
pipe_signal(world, *upstream_handle, processor_handle);
processor_handle
});
SignalVecKeys {
signal: lazy_signal,
_marker: PhantomData,
}
}
pub fn signal_vec_entries(&self) -> SignalVecEntries<K, V>
where
K: Ord + Clone + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
let upstream = self.signal_map();
let lazy_signal = LazySignal::new(move |world: &mut World| {
let upstream_handle = upstream.register_signal_map(world);
let processor_logic = move |In(diffs): In<Vec<MapDiff<K, V>>>, mut keys: Local<Vec<K>>| {
let mut out_diffs = Vec::new();
for diff in diffs {
match diff {
MapDiff::Replace { entries } => {
*keys = entries.iter().map(|(k, _)| k.clone()).collect();
out_diffs.push(VecDiff::Replace { values: entries });
}
MapDiff::Insert { key, value } => {
let index = keys.binary_search(&key).unwrap_err();
keys.insert(index, key.clone());
out_diffs.push(VecDiff::InsertAt {
index,
value: (key, value),
});
}
MapDiff::Update { key, value } => {
if let Ok(index) = keys.binary_search(&key) {
out_diffs.push(VecDiff::UpdateAt {
index,
value: (key, value),
});
}
}
MapDiff::Remove { key } => {
if let Ok(index) = keys.binary_search(&key) {
keys.remove(index);
out_diffs.push(VecDiff::RemoveAt { index });
}
}
MapDiff::Clear => {
keys.clear();
out_diffs.push(VecDiff::Clear);
}
}
}
if out_diffs.is_empty() { None } else { Some(out_diffs) }
};
let processor_handle =
lazy_signal_from_system::<_, Vec<VecDiff<(K, V)>>, _, _, _>(processor_logic).register(world);
pipe_signal(world, *upstream_handle, processor_handle);
processor_handle
});
SignalVecEntries {
signal: lazy_signal,
_marker: PhantomData,
}
}
}
impl<K, V> From<&mut World> for MutableBTreeMap<K, V>
where
K: Ord + Clone + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
fn from(world: &mut World) -> Self {
let (data, data_entity) = new_mutable_btree_map_data::<K, V>(BTreeMap::new());
let entity = world.spawn(data).id();
data_entity.set(entity);
Self {
entity,
references: Arc::new(AtomicUsize::new(1)),
_marker: PhantomData,
}
}
}
impl<K, V> FromWorld for MutableBTreeMap<K, V>
where
K: Ord + Clone + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
fn from_world(world: &mut World) -> Self {
world.into()
}
}
pub struct MutableBTreeMapBuilder<K, V>(BTreeMap<K, V>);
impl<K, V> Default for MutableBTreeMapBuilder<K, V> {
fn default() -> Self {
Self::new()
}
}
impl<K, V> MutableBTreeMapBuilder<K, V> {
pub fn new() -> Self {
Self(BTreeMap::new())
}
}
impl<K: Ord, V> MutableBTreeMapBuilder<K, V> {
pub fn values(mut self, values: impl Into<BTreeMap<K, V>>) -> Self {
self.0 = values.into();
self
}
pub fn with_values(mut self, f: impl FnOnce(&mut BTreeMap<K, V>)) -> Self {
f(&mut self.0);
self
}
}
impl<K, V, A> From<A> for MutableBTreeMapBuilder<K, V>
where
BTreeMap<K, V>: From<A>,
{
fn from(value: A) -> Self {
Self(value.into())
}
}
impl<K, V> MutableBTreeMapBuilder<K, V>
where
K: Ord + Clone + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
pub fn spawn(self, world: &mut World) -> MutableBTreeMap<K, V> {
let (data, data_entity) = new_mutable_btree_map_data::<K, V>(self.0);
let entity = world.spawn(data).id();
data_entity.set(entity);
MutableBTreeMap {
entity,
references: Arc::new(AtomicUsize::new(1)),
_marker: PhantomData,
}
}
pub fn spawnc(self, commands: &mut Commands) -> MutableBTreeMap<K, V> {
let (data, data_entity) = new_mutable_btree_map_data::<K, V>(self.0);
let entity = commands.spawn(data).id();
data_entity.set(entity);
MutableBTreeMap {
entity,
references: Arc::new(AtomicUsize::new(1)),
_marker: PhantomData,
}
}
}
impl<K, V> From<&mut Commands<'_, '_>> for MutableBTreeMap<K, V>
where
K: Ord + Clone + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
fn from(commands: &mut Commands) -> Self {
let (data, data_entity) = new_mutable_btree_map_data::<K, V>(BTreeMap::new());
let entity = commands.spawn(data).id();
data_entity.set(entity);
Self {
entity,
references: Arc::new(AtomicUsize::new(1)),
_marker: PhantomData,
}
}
}
pub trait ReadMutableBTreeMapData<'s, K, V>
where
K: Send + Sync,
V: Send + Sync,
{
#[allow(missing_docs)]
fn read(self, entity: Entity) -> &'s MutableBTreeMapData<K, V>;
}
impl<'s, K, V> ReadMutableBTreeMapData<'s, K, V> for &'s Query<'_, 's, &MutableBTreeMapData<K, V>>
where
K: Send + Sync + 'static,
V: Send + Sync + 'static,
{
fn read(self, entity: Entity) -> &'s MutableBTreeMapData<K, V> {
self.get(entity).unwrap()
}
}
impl<'s, K, V> ReadMutableBTreeMapData<'s, K, V> for &'s World
where
K: Send + Sync + 'static,
V: Send + Sync + 'static,
{
fn read(self, entity: Entity) -> &'s MutableBTreeMapData<K, V> {
self.get(entity).unwrap()
}
}
pub trait WriteMutableBTreeMapData<'w, K, V>
where
K: Send + Sync,
V: Send + Sync,
{
#[allow(missing_docs)]
fn write(self, entity: Entity) -> Mut<'w, MutableBTreeMapData<K, V>>;
}
impl<'a, 'w, 's, K, V> WriteMutableBTreeMapData<'a, K, V> for &'a mut Query<'w, 's, &mut MutableBTreeMapData<K, V>>
where
K: Send + Sync + 'static,
V: Send + Sync + 'static,
{
fn write(self, entity: Entity) -> Mut<'a, MutableBTreeMapData<K, V>> {
self.get_mut(entity).unwrap()
}
}
impl<'w, K, V> WriteMutableBTreeMapData<'w, K, V> for &'w mut World
where
K: Send + Sync + 'static,
V: Send + Sync + 'static,
{
fn write(self, entity: Entity) -> Mut<'w, MutableBTreeMapData<K, V>> {
self.get_mut(entity).unwrap()
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::{JonmoPlugin, signal_vec::SignalVecExt};
use bevy::prelude::*;
#[derive(Resource, Default, Debug)]
struct SignalMapOutput<K, V>(Vec<MapDiff<K, V>>)
where
K: Send + Sync + 'static + Clone + fmt::Debug,
V: Send + Sync + 'static + Clone + 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 + fmt::Debug,
V: Send + Sync + 'static + Clone + 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 + fmt::Debug,
V: Send + Sync + 'static + Clone + fmt::Debug,
{
world
.get_resource_mut::<SignalMapOutput<K, V>>()
.map(|mut res| core::mem::take(&mut res.0))
.unwrap_or_default()
}
fn create_test_app() -> App {
cleanup(true);
let mut app = App::new();
app.add_plugins((MinimalPlugins, JonmoPlugin::default()));
app
}
impl<K: PartialEq, V: PartialEq> PartialEq for MapDiff<K, V> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Replace { entries: l_entries }, Self::Replace { entries: r_entries }) => l_entries == r_entries,
(
Self::Insert {
key: l_key,
value: l_value,
},
Self::Insert {
key: r_key,
value: r_value,
},
) => l_key == r_key && l_value == r_value,
(
Self::Update {
key: l_key,
value: l_value,
},
Self::Update {
key: r_key,
value: r_value,
},
) => l_key == r_key && l_value == r_value,
(Self::Remove { key: l_key }, Self::Remove { key: r_key }) => l_key == r_key,
(Self::Clear, Self::Clear) => true,
_ => false,
}
}
}
pub(crate) fn cleanup(vecs_too: bool) {
STALE_MUTABLE_BTREE_MAPS.lock().unwrap().clear();
if vecs_too {
crate::signal_vec::tests::cleanup(false);
}
}
#[test]
fn test_for_each() {
{
let mut app = create_test_app();
app.init_resource::<SignalOutput<BTreeMap<u32, String>>>();
let source_map = (MutableBTreeMap::builder().values([(1, "one".to_string()), (2, "two".to_string())]))
.spawn(app.world_mut());
let reconstructor_system =
|In(diffs): In<Vec<MapDiff<u32, String>>>, mut state: Local<BTreeMap<u32, String>>| {
for diff in diffs {
match diff {
MapDiff::Replace { entries } => {
*state = entries.into_iter().collect();
}
MapDiff::Insert { key, value } | MapDiff::Update { key, value } => {
state.insert(key, value);
}
MapDiff::Remove { key } => {
state.remove(&key);
}
MapDiff::Clear => {
state.clear();
}
}
}
state.clone()
};
let handle = source_map
.signal_map()
.for_each(reconstructor_system)
.map(capture_output::<BTreeMap<u32, String>>)
.register(app.world_mut());
app.update();
let expected_initial_state: BTreeMap<_, _> =
[(1, "one".to_string()), (2, "two".to_string())].into_iter().collect();
assert_eq!(
get_output::<BTreeMap<u32, String>>(app.world_mut()),
Some(expected_initial_state.clone()),
"Initial state was not reconstructed correctly"
);
{
let mut writer = source_map.write(app.world_mut());
writer.insert(3, "three".to_string());
writer.insert(1, "one_v2".to_string());
writer.remove(&2);
}
app.update();
let expected_batched_state: BTreeMap<_, _> = [(1, "one_v2".to_string()), (3, "three".to_string())]
.into_iter()
.collect();
assert_eq!(
get_output::<BTreeMap<u32, String>>(app.world_mut()),
Some(expected_batched_state.clone()),
"State after batched mutations was not reconstructed correctly"
);
source_map.write(app.world_mut()).clear();
app.update();
let expected_cleared_state: BTreeMap<u32, String> = BTreeMap::new();
assert_eq!(
get_output::<BTreeMap<u32, String>>(app.world_mut()),
Some(expected_cleared_state.clone()),
"State after Clear was not reconstructed correctly"
);
handle.cleanup(app.world_mut());
}
cleanup(true);
}
#[test]
fn test_map_value() {
{
let mut app = create_test_app();
app.init_resource::<SignalMapOutput<u32, String>>();
let source_map = (MutableBTreeMap::builder().values([(1, 10), (2, 20)])).spawn(app.world_mut());
let mapping_system = |In(val): In<i32>| format!("Val:{val}");
let mapped_signal = source_map.signal_map().map_value(mapping_system);
let handle = mapped_signal
.for_each(capture_map_output::<u32, String>)
.register(app.world_mut());
app.update();
let diffs = get_and_clear_map_output::<u32, String>(app.world_mut());
assert_eq!(diffs.len(), 1, "Initial state should produce one Replace diff");
assert_eq!(
diffs[0],
MapDiff::Replace {
entries: vec![(1, "Val:10".to_string()), (2, "Val:20".to_string())]
},
"Initial Replace diff has incorrect mapped values"
);
source_map.write(app.world_mut()).insert(3, 30);
app.update();
let diffs = get_and_clear_map_output::<u32, String>(app.world_mut());
assert_eq!(diffs.len(), 1, "Insert should produce one diff");
assert_eq!(
diffs[0],
MapDiff::Insert {
key: 3,
value: "Val:30".to_string(),
},
"Insert diff has incorrect mapped value"
);
source_map.write(app.world_mut()).insert(1, 15);
app.update();
let diffs = get_and_clear_map_output::<u32, String>(app.world_mut());
assert_eq!(diffs.len(), 1, "Update should produce one diff");
assert_eq!(
diffs[0],
MapDiff::Update {
key: 1,
value: "Val:15".to_string(),
},
"Update diff has incorrect mapped value"
);
source_map.write(app.world_mut()).remove(&2);
app.update();
let diffs = get_and_clear_map_output::<u32, String>(app.world_mut());
assert_eq!(diffs.len(), 1, "Remove should produce one diff");
assert_eq!(
diffs[0],
MapDiff::Remove { key: 2 },
"Remove diff was not propagated correctly"
);
source_map.write(app.world_mut()).clear();
app.update();
let diffs = get_and_clear_map_output::<u32, String>(app.world_mut());
assert_eq!(diffs.len(), 1, "Clear should produce one diff");
assert_eq!(diffs[0], MapDiff::Clear, "Clear diff was not propagated correctly");
handle.cleanup(app.world_mut());
}
cleanup(true);
}
#[test]
fn test_map_value_signal() {
{
let mut app = create_test_app();
app.init_resource::<SignalMapOutput<u32, Name>>();
let entity_a = app.world_mut().spawn(Name::new("Alice")).id();
let entity_b = app.world_mut().spawn(Name::new("Bob")).id();
let entity_map = (MutableBTreeMap::builder().values([(1, entity_a), (2, entity_b)])).spawn(app.world_mut());
let factory_system = |In(entity): In<Entity>| signal::from_component::<Name>(entity).dedupe();
let name_map_signal = entity_map.signal_map().map_value_signal(factory_system);
let handle = name_map_signal
.for_each(capture_map_output::<u32, Name>)
.register(app.world_mut());
app.update();
let diffs = get_and_clear_map_output::<u32, Name>(app.world_mut());
assert_eq!(diffs.len(), 1, "Initial state should produce one Replace diff");
assert_eq!(
diffs[0],
MapDiff::Replace {
entries: vec![(1, Name::new("Alice")), (2, Name::new("Bob"))]
},
"Initial state is incorrect"
);
*app.world_mut().get_mut::<Name>(entity_a).unwrap() = Name::new("Alicia");
app.update();
let diffs = get_and_clear_map_output::<u32, Name>(app.world_mut());
assert_eq!(diffs.len(), 1, "Name change should produce one Update diff");
assert_eq!(
diffs[0],
MapDiff::Update {
key: 1,
value: Name::new("Alicia"),
},
"Update diff is incorrect"
);
app.update();
let diffs = get_and_clear_map_output::<u32, Name>(app.world_mut());
assert!(diffs.is_empty(), "No change should produce no diffs");
let entity_c = app.world_mut().spawn(Name::new("Charlie")).id();
entity_map.write(app.world_mut()).insert(3, entity_c);
app.update();
let diffs = get_and_clear_map_output::<u32, Name>(app.world_mut());
assert_eq!(diffs.len(), 1, "Insert should produce one Insert diff");
assert_eq!(
diffs[0],
MapDiff::Insert {
key: 3,
value: Name::new("Charlie"),
},
"Insert diff is incorrect"
);
entity_map.write(app.world_mut()).remove(&2);
app.update();
let diffs = get_and_clear_map_output::<u32, Name>(app.world_mut());
assert_eq!(diffs.len(), 1, "Remove should produce one Remove diff");
assert_eq!(diffs[0], MapDiff::Remove { key: 2 }, "Remove diff is incorrect");
let entity_d = app.world_mut().spawn(Name::new("David")).id();
entity_map.write(app.world_mut()).insert(1, entity_d);
app.update();
let diffs = get_and_clear_map_output::<u32, Name>(app.world_mut());
assert_eq!(diffs.len(), 1, "Source map update should produce one Update diff");
assert_eq!(
diffs[0],
MapDiff::Update {
key: 1,
value: Name::new("David"),
},
"Update-to-new-entity diff is incorrect"
);
*app.world_mut().get_mut::<Name>(entity_a).unwrap() = Name::new("Alicia-v2");
app.update();
let diffs = get_and_clear_map_output::<u32, Name>(app.world_mut());
assert!(
diffs.is_empty(),
"Update on old, replaced entity should not produce a diff"
);
*app.world_mut().get_mut::<Name>(entity_d).unwrap() = Name::new("Dave");
app.update();
let diffs = get_and_clear_map_output::<u32, Name>(app.world_mut());
assert_eq!(diffs.len(), 1, "Update on new entity should produce a diff");
assert_eq!(
diffs[0],
MapDiff::Update {
key: 1,
value: Name::new("Dave"),
},
"Update on new entity is incorrect"
);
entity_map.write(app.world_mut()).clear();
app.update();
let diffs = get_and_clear_map_output::<u32, Name>(app.world_mut());
assert_eq!(diffs.len(), 1, "Clear should produce one Clear diff");
assert_eq!(diffs[0], MapDiff::Clear, "Clear diff is incorrect");
handle.cleanup(app.world_mut());
}
cleanup(true);
}
#[derive(Resource, Default, Debug)]
struct SignalOutput<T>(Option<T>)
where
T: Send + Sync + 'static + Clone + fmt::Debug;
fn capture_output<T>(In(value): In<T>, mut output: ResMut<SignalOutput<T>>)
where
T: Send + Sync + 'static + Clone + fmt::Debug,
{
output.0 = Some(value);
}
fn get_output<T: Send + Sync + 'static + Clone + fmt::Debug>(world: &mut World) -> Option<T> {
world.get_resource::<SignalOutput<T>>().and_then(|res| res.0.clone())
}
fn clear_output<T: Send + Sync + 'static + Clone + fmt::Debug>(world: &mut World) {
if let Some(mut res) = world.get_resource_mut::<SignalOutput<T>>() {
res.0 = None;
}
}
#[test]
fn test_key() {
{
let mut app = create_test_app();
app.init_resource::<SignalOutput<Option<String>>>();
let source_map = (MutableBTreeMap::builder().values([(1, "one".to_string()), (2, "two".to_string())]))
.spawn(app.world_mut());
let key_to_track = 2;
let key_signal = source_map.signal_map().key(key_to_track);
let handle = key_signal
.map(capture_output::<Option<String>>)
.register(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<String>>(app.world_mut()),
Some(Some("two".to_string())),
"Initial value for present key is incorrect"
);
source_map
.write(app.world_mut())
.insert(key_to_track, "two_v2".to_string());
app.update();
assert_eq!(
get_output::<Option<String>>(app.world_mut()),
Some(Some("two_v2".to_string())),
"Update to tracked key did not emit correctly"
);
clear_output::<Option<String>>(app.world_mut());
source_map.write(app.world_mut()).insert(1, "one_v2".to_string());
app.update();
assert_eq!(
get_output::<Option<String>>(app.world_mut()),
None,
"Signal emitted when a different key was updated"
);
source_map.write(app.world_mut()).remove(&key_to_track);
app.update();
assert_eq!(
get_output::<Option<String>>(app.world_mut()),
Some(None),
"Removing the tracked key did not emit None"
);
clear_output::<Option<String>>(app.world_mut());
app.update();
assert_eq!(
get_output::<Option<String>>(app.world_mut()),
None,
"Signal emitted when key remained absent"
);
source_map
.write(app.world_mut())
.insert(key_to_track, "two_reborn".to_string());
app.update();
assert_eq!(
get_output::<Option<String>>(app.world_mut()),
Some(Some("two_reborn".to_string())),
"Re-inserting the tracked key did not emit its value"
);
source_map.write(app.world_mut()).clear();
app.update();
assert_eq!(
get_output::<Option<String>>(app.world_mut()),
Some(None),
"Clearing the map did not emit None for the tracked key"
);
handle.cleanup(app.world_mut());
}
cleanup(true);
}
#[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()
}
fn apply_diffs_to_vec<T: Clone>(vec: &mut Vec<T>, diffs: Vec<VecDiff<T>>) {
for diff in diffs {
diff.apply_to_vec(vec);
}
}
#[test]
fn test_signal_vec_keys() {
{
let mut app = create_test_app();
app.init_resource::<SignalVecOutput<u32>>();
let source_map = (MutableBTreeMap::builder().values([(3, 'c'), (1, 'a'), (4, 'd')])).spawn(app.world_mut());
let keys_signal = source_map.signal_vec_keys();
let handle = keys_signal
.for_each(capture_vec_output::<u32>)
.register(app.world_mut());
let mut current_keys: Vec<u32> = vec![];
app.update();
let diffs = get_and_clear_vec_output::<u32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Initial update should produce one Replace diff.");
assert_eq!(
diffs[0],
VecDiff::Replace { values: vec![1, 3, 4] },
"Initial state should be a Replace with sorted keys."
);
apply_diffs_to_vec(&mut current_keys, diffs);
assert_eq!(current_keys, vec![1, 3, 4]);
source_map.write(app.world_mut()).insert(2, 'b');
app.update();
let diffs = get_and_clear_vec_output::<u32>(app.world_mut());
assert_eq!(diffs.len(), 1);
assert_eq!(diffs[0], VecDiff::InsertAt { index: 1, value: 2 });
apply_diffs_to_vec(&mut current_keys, diffs);
assert_eq!(current_keys, vec![1, 2, 3, 4]);
source_map.write(app.world_mut()).insert(0, 'z');
app.update();
let diffs = get_and_clear_vec_output::<u32>(app.world_mut());
assert_eq!(diffs.len(), 1);
assert_eq!(diffs[0], VecDiff::InsertAt { index: 0, value: 0 });
apply_diffs_to_vec(&mut current_keys, diffs);
assert_eq!(current_keys, vec![0, 1, 2, 3, 4]);
source_map.write(app.world_mut()).insert(3, 'C'); app.update();
let diffs = get_and_clear_vec_output::<u32>(app.world_mut());
assert!(diffs.is_empty(), "Updating a value should not produce a key diff.");
assert_eq!(current_keys, vec![0, 1, 2, 3, 4]);
source_map.write(app.world_mut()).remove(&3);
app.update();
let diffs = get_and_clear_vec_output::<u32>(app.world_mut());
assert_eq!(diffs.len(), 1);
assert_eq!(diffs[0], VecDiff::RemoveAt { index: 3 }); apply_diffs_to_vec(&mut current_keys, diffs);
assert_eq!(current_keys, vec![0, 1, 2, 4]);
{
let mut writer = source_map.write(app.world_mut());
writer.remove(&1); writer.insert(5, 'e'); }
app.update();
let diffs = get_and_clear_vec_output::<u32>(app.world_mut());
assert_eq!(
diffs,
vec![VecDiff::RemoveAt { index: 1 }, VecDiff::InsertAt { index: 3, value: 5 }],
"Batched diffs were not processed correctly."
);
apply_diffs_to_vec(&mut current_keys, diffs);
assert_eq!(
current_keys,
vec![0, 2, 4, 5],
"State after batched diffs is incorrect."
);
source_map.write(app.world_mut()).clear();
app.update();
let diffs = get_and_clear_vec_output::<u32>(app.world_mut());
assert_eq!(diffs.len(), 1);
assert_eq!(diffs[0], VecDiff::Clear);
apply_diffs_to_vec(&mut current_keys, diffs);
assert!(current_keys.is_empty());
handle.cleanup(app.world_mut());
}
cleanup(true);
}
#[test]
fn test_signal_vec_entries() {
{
let mut app = create_test_app();
app.init_resource::<SignalVecOutput<(u32, char)>>();
let source_map = (MutableBTreeMap::builder().values([(3, 'c'), (1, 'a'), (4, 'd')])).spawn(app.world_mut());
let entries_signal = source_map.signal_vec_entries();
let handle = entries_signal
.for_each(capture_vec_output::<(u32, char)>)
.register(app.world_mut());
let mut current_entries: Vec<(u32, char)> = vec![];
app.update();
let diffs = get_and_clear_vec_output::<(u32, char)>(app.world_mut());
assert_eq!(diffs.len(), 1, "Initial update should produce one Replace diff.");
assert_eq!(
diffs[0],
VecDiff::Replace {
values: vec![(1, 'a'), (3, 'c'), (4, 'd')]
},
"Initial state should be a Replace with sorted entries."
);
apply_diffs_to_vec(&mut current_entries, diffs);
assert_eq!(current_entries, vec![(1, 'a'), (3, 'c'), (4, 'd')]);
source_map.write(app.world_mut()).insert(2, 'b');
app.update();
let diffs = get_and_clear_vec_output::<(u32, char)>(app.world_mut());
assert_eq!(diffs.len(), 1);
assert_eq!(
diffs[0],
VecDiff::InsertAt {
index: 1,
value: (2, 'b')
}
);
apply_diffs_to_vec(&mut current_entries, diffs);
assert_eq!(current_entries, vec![(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]);
source_map.write(app.world_mut()).insert(3, 'C'); app.update();
let diffs = get_and_clear_vec_output::<(u32, char)>(app.world_mut());
assert_eq!(diffs.len(), 1);
assert_eq!(
diffs[0],
VecDiff::UpdateAt {
index: 2, value: (3, 'C')
}
);
apply_diffs_to_vec(&mut current_entries, diffs);
assert_eq!(current_entries, vec![(1, 'a'), (2, 'b'), (3, 'C'), (4, 'd')]);
source_map.write(app.world_mut()).remove(&1);
app.update();
let diffs = get_and_clear_vec_output::<(u32, char)>(app.world_mut());
assert_eq!(diffs.len(), 1);
assert_eq!(diffs[0], VecDiff::RemoveAt { index: 0 }); apply_diffs_to_vec(&mut current_entries, diffs);
assert_eq!(current_entries, vec![(2, 'b'), (3, 'C'), (4, 'd')]);
{
let mut writer = source_map.write(app.world_mut());
writer.remove(&4); writer.insert(0, 'z'); }
app.update();
let diffs = get_and_clear_vec_output::<(u32, char)>(app.world_mut());
assert_eq!(
diffs,
vec![
VecDiff::RemoveAt { index: 2 }, VecDiff::InsertAt {
index: 0,
value: (0, 'z')
} ],
"Batched diffs were not processed correctly."
);
apply_diffs_to_vec(&mut current_entries, diffs);
assert_eq!(
current_entries,
vec![(0, 'z'), (2, 'b'), (3, 'C')],
"State after batched diffs is incorrect."
);
source_map.write(app.world_mut()).clear();
app.update();
let diffs = get_and_clear_vec_output::<(u32, char)>(app.world_mut());
assert_eq!(diffs.len(), 1);
assert_eq!(diffs[0], VecDiff::Clear);
apply_diffs_to_vec(&mut current_entries, diffs);
assert!(current_entries.is_empty());
handle.cleanup(app.world_mut());
}
cleanup(true);
}
#[test]
fn test_empty_map_first_insert() {
{
let mut app = create_test_app();
app.init_resource::<SignalMapOutput<String, i32>>();
let source_map = MutableBTreeMap::from(app.world_mut());
let signal = source_map.signal_map();
let handle = signal.for_each(capture_map_output).register(app.world_mut());
source_map.write(app.world_mut()).insert("a".to_string(), 42);
app.update();
let diffs = get_and_clear_map_output::<String, i32>(app.world_mut());
assert_eq!(
diffs.len(),
1,
"Expected exactly one diff for first insert to empty map"
);
assert_eq!(
diffs[0],
MapDiff::Insert {
key: "a".to_string(),
value: 42
},
"Expected an Insert diff, not a Replace"
);
source_map.write(app.world_mut()).insert("b".to_string(), 99);
app.update();
let diffs = get_and_clear_map_output::<String, i32>(app.world_mut());
assert_eq!(diffs.len(), 1);
assert_eq!(
diffs[0],
MapDiff::Insert {
key: "b".to_string(),
value: 99
}
);
handle.cleanup(app.world_mut());
}
cleanup(true);
}
#[test]
fn test_nonempty_map_initial_replace() {
{
let mut app = create_test_app();
app.init_resource::<SignalMapOutput<String, i32>>();
let source_map = MutableBTreeMap::builder()
.values([("x".to_string(), 1), ("y".to_string(), 2), ("z".to_string(), 3)])
.spawn(app.world_mut());
let signal = source_map.signal_map();
let handle = signal.for_each(capture_map_output).register(app.world_mut());
app.update();
let diffs = get_and_clear_map_output::<String, i32>(app.world_mut());
assert_eq!(diffs.len(), 1, "Expected exactly one diff for initial state");
assert_eq!(
diffs[0],
MapDiff::Replace {
entries: vec![("x".to_string(), 1), ("y".to_string(), 2), ("z".to_string(), 3),]
},
"Expected a Replace diff with initial entries"
);
source_map.write(app.world_mut()).insert("w".to_string(), 4);
app.update();
let diffs = get_and_clear_map_output::<String, i32>(app.world_mut());
assert_eq!(diffs.len(), 1);
assert_eq!(
diffs[0],
MapDiff::Insert {
key: "w".to_string(),
value: 4
}
);
handle.cleanup(app.world_mut());
}
cleanup(true);
}
}