use core::{
any::{Any, type_name},
cell::RefCell,
fmt::Debug,
marker::PhantomData,
mem::ManuallyDrop,
ops::{
Add, BitAnd, BitOr, BitXor, Deref, DerefMut, Div, Mul, Neg, Not, RangeBounds, Rem, Shl,
Shr, Sub,
},
panic::Location,
};
use alloc::{boxed::Box, rc::Rc};
use async_channel::{Sender, unbounded};
use executor_core::{LocalExecutor, Task};
use num_traits::Signed;
use crate::{
Computed, Signal, SignalIdentity,
watcher::{BoxWatcherGuard, Context, WatcherManager},
};
use nami_core::observe::Origin;
pub use nami_core::CustomBinding;
pub struct Binding<T: 'static>(Box<dyn BindingImpl<Output = T>>);
trait BindingImpl: crate::signal::ComputedImpl {
fn set(&self, value: Self::Output);
fn cloned_binding(&self) -> Binding<Self::Output>;
}
impl<T: CustomBinding + Clone + 'static> BindingImpl for T {
fn set(&self, value: Self::Output) {
<T as CustomBinding>::set(self, value);
}
fn cloned_binding(&self) -> Binding<Self::Output> {
Binding::custom(self.clone())
}
}
impl<T> Debug for Binding<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(type_name::<Self>())
}
}
impl<T: 'static + Clone> Binding<T> {
#[track_caller]
pub fn container(value: T) -> Self {
Self::custom(Container::new(value))
}
}
impl<T: Default + Clone + 'static> Default for Binding<T> {
#[track_caller]
fn default() -> Self {
Self::container(T::default())
}
}
#[track_caller]
pub fn binding<T: 'static + Clone>(value: impl Into<T>) -> Binding<T> {
Binding::container(value.into())
}
impl_signal_binary_ops!(Binding<T>, [T], T);
#[must_use]
#[derive(Debug)]
pub struct BindingMutGuard<'a, T: 'static> {
binding: &'a Binding<T>,
value: ManuallyDrop<T>,
dirty: bool,
}
impl<'a, T> BindingMutGuard<'a, T> {
pub fn new(binding: &'a Binding<T>) -> Self {
Self {
value: ManuallyDrop::new(binding.get()),
binding,
dirty: false,
}
}
}
impl<T> Deref for BindingMutGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T> DerefMut for BindingMutGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.dirty = true;
&mut self.value
}
}
impl<T: 'static> Drop for BindingMutGuard<'_, T> {
fn drop(&mut self) {
if self.dirty {
let value = unsafe { ManuallyDrop::take(&mut self.value) };
self.binding.set(value);
} else {
unsafe { ManuallyDrop::drop(&mut self.value) }
}
}
}
impl<T: 'static> Binding<T> {
pub fn custom(custom: impl CustomBinding<Output = T> + Clone + 'static) -> Self {
Self(Box::new(custom))
}
#[must_use]
pub fn get(&self) -> T {
self.0.compute()
}
pub fn get_mut(&self) -> BindingMutGuard<'_, T> {
BindingMutGuard::new(self)
}
pub fn set(&self, value: T) {
self.0.set(value);
}
#[must_use]
pub fn take(&self) -> T
where
T: Default + Clone,
{
self.with_mut(|v| core::mem::take(v))
}
pub fn set_from(&self, value: impl Into<T>) {
self.0.set(value.into());
}
fn as_container(&self) -> Option<&Container<T>> {
let any = (self.0.as_ref()) as &dyn BindingImpl<Output = T> as &dyn Any;
any.downcast_ref::<Container<T>>()
}
pub fn with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R
where
T: Clone,
{
if let Some(container) = self.as_container() {
let mut value = container.value.borrow_mut();
let result = f(&mut *value);
let updated = value.clone();
drop(value);
if !container.watchers.is_empty() {
let context = Context::from(updated);
container.watchers.notify(&context);
}
result
} else {
let mut guard = self.get_mut();
f(&mut *guard)
}
}
#[track_caller]
pub fn mapping<Output, Getter, Setter>(
source: &Self,
getter: Getter,
setter: Setter,
) -> Binding<Output>
where
Getter: 'static + Clone + Fn(T) -> Output,
Setter: 'static + Clone + Fn(&Self, Output),
{
Binding::custom(Mapping {
binding: source.clone(),
getter,
setter,
discriminator: SignalIdentity::call_site_discriminator::<(Getter, Setter, Output)>(
Location::caller(),
),
_marker: PhantomData,
})
}
#[must_use]
pub fn filter(&self, filter: impl 'static + Clone + Fn(&T) -> bool) -> Self
where
T: 'static,
{
Self::mapping(
self,
|value| value,
move |binding, value| {
if filter(&value) {
binding.set(value);
}
},
)
}
pub fn condition(&self, condition: impl 'static + Clone + Fn(&T) -> bool) -> Binding<bool>
where
T: 'static,
{
Self::mapping(self, move |value| condition(&value), move |_, _| {})
}
pub fn equal_to(&self, other: T) -> Binding<bool>
where
T: Clone + PartialEq + 'static,
{
Self::mapping(self, move |value| value == other, move |_, _| {})
}
}
type Job<T> = Box<dyn FnOnce(&mut Binding<T>) + 'static + Send>;
#[derive(Debug)]
pub struct BindingMailbox<T: 'static> {
sender: Sender<Job<T>>,
}
impl<T: 'static> BindingMailbox<T> {
pub fn handle(&self, job: impl FnOnce(&mut Binding<T>) + 'static + Send) {
self.sender
.try_send(Box::new(job))
.expect("BindingMailbox::handle failed to enqueue job");
}
pub async fn get(&self) -> T
where
T: Clone + Send,
{
let (sender, receiver) = unbounded();
self.handle(move |binding| {
sender
.try_send(binding.get())
.expect("BindingMailbox::get failed to send response");
});
match receiver.recv().await {
Ok(value) => value,
Err(error) => panic!("BindingMailbox::get response channel closed: {error}"),
}
}
pub async fn get_as<T2>(&self) -> T2
where
T2: Send + 'static + From<T>,
{
let (sender, receiver) = unbounded();
self.handle(move |binding| {
sender
.try_send(binding.get().into())
.expect("BindingMailbox::get_as failed to send response");
});
match receiver.recv().await {
Ok(value) => value,
Err(error) => panic!("BindingMailbox::get_as response channel closed: {error}"),
}
}
pub async fn set(&self, value: impl Into<T> + Send + 'static) {
let (sender, receiver) = unbounded();
self.handle(move |binding| {
let value = value.into();
binding.set(value);
sender
.try_send(())
.expect("BindingMailbox::set failed to send ack");
});
receiver
.recv()
.await
.expect("BindingMailbox::set ack channel closed");
}
}
impl<T: 'static> Binding<T> {
pub fn mailbox_with_executor<E: LocalExecutor>(&self, executor: E) -> BindingMailbox<T> {
let (sender, receiver) = unbounded::<Job<T>>();
{
let mut binding = self.clone();
executor
.spawn_local(async move {
while let Ok(job) = receiver.recv().await {
job(&mut binding);
}
})
.detach();
}
BindingMailbox { sender }
}
#[cfg(feature = "std")]
#[must_use]
pub fn mailbox(&self) -> BindingMailbox<T> {
self.mailbox_with_executor(executor_core::DefaultExecutor)
}
}
impl<T: PartialOrd + 'static> Binding<T> {
#[must_use]
pub fn range(&self, range: impl RangeBounds<T> + Clone + 'static) -> Self {
self.filter(move |value| range.contains(value))
}
#[must_use]
pub fn clamp(&self, range: impl RangeBounds<T> + Clone + 'static) -> Self
where
T: Clone,
{
fn clamp_value<T, R>(range: &R, value: T) -> T
where
T: Clone + PartialOrd,
R: RangeBounds<T>,
{
if let core::ops::Bound::Included(min) = range.start_bound()
&& value < min.clone()
{
return min.clone();
}
if let core::ops::Bound::Included(max) = range.end_bound()
&& value > max.clone()
{
return max.clone();
}
value
}
let read_range = range.clone();
let write_range = range;
Self::mapping(
self,
move |value| clamp_value(&read_range, value),
move |binding, value| {
let clamped = clamp_value(&write_range, value);
binding.set(clamped);
},
)
}
}
impl<T: Signed> Binding<T> {
#[must_use]
pub fn sign(&self) -> Binding<bool> {
Self::mapping(
self,
move |value| !value.is_negative(),
move |binding, value| {
let current = binding.get();
if value {
binding.set(current.abs());
} else {
binding.set(-current.abs());
}
},
)
}
}
macro_rules! impl_binding {
( $( #[$meta:meta] )* $ty:ident ) => {
impl Binding<$ty> {
$( #[$meta] )*
#[must_use]
#[track_caller]
pub fn $ty(value: $ty) -> Self {
Self::container(value)
}
}
};
}
impl_binding!(
u32
);
impl_binding!(
u64
);
impl_binding!(
usize
);
impl_binding!(
i32
);
impl_binding!(
i64
);
impl_binding!(
isize
);
impl_binding!(
f32
);
impl_binding!(
f64
);
impl_binding!(
bool
);
impl<T: Clone> Binding<T> {
pub fn append<Ele>(&self, ele: Ele)
where
T: Extend<Ele>,
{
self.with_mut(|value| {
value.extend(core::iter::once(ele));
});
}
}
macro_rules! ops {
($trait:ident, $method:ident, $op:tt) => {
impl<T: $trait<Output = T> + Clone + 'static> Binding<T> {
#[doc = concat!("Applies the `", stringify!($op), "` operation to the binding's current value and the provided value.")]
pub fn $method(&self, other: T) {
self.with_mut(|value| {
*value = value.clone() $op other;
});
}
}
};
}
ops!(Add, add_assign, +);
ops!(Sub, sub_assign, -);
ops!(Mul, mul_assign, *);
ops!(Div, div_assign, /);
ops!(Rem, rem_assign, %);
ops!(BitAnd, bitand_assign, &);
ops!(BitOr, bitor_assign, |);
ops!(BitXor, bitxor_assign, ^);
ops!(Shl, shl_assign, <<);
ops!(Shr, shr_assign, >>);
impl<T> Binding<Option<T>> {
pub fn unwrap_or_else(&self, default: impl 'static + Clone + Fn() -> T) -> Binding<T>
where
T: Clone + 'static,
{
Self::mapping(
self,
move |value| value.unwrap_or_else(&default),
move |binding, value| {
binding.set(Some(value));
},
)
}
pub fn unwrap_or(&self, default: T) -> Binding<T>
where
T: Clone + 'static,
{
self.unwrap_or_else(move || default.clone())
}
pub fn unwrap_or_default(&self) -> Binding<T>
where
T: Default + Clone + 'static,
{
self.unwrap_or_else(T::default)
}
pub fn some_equal_to(&self, equal: T) -> Binding<bool>
where
T: Eq + Clone + 'static,
{
Self::mapping(
self,
{
let equal = equal.clone();
move |value| value.as_ref().is_some_and(|value| *value == equal)
},
move |binding, value| {
if value {
binding.set(Some(equal.clone()));
}
},
)
}
}
impl Binding<bool> {
pub fn toggle(&self) {
self.with_mut(|v| {
*v = !*v;
});
}
pub fn then<T>(&self, if_true: T) -> Binding<Option<T>>
where
T: Clone + 'static,
{
Self::mapping(
self,
move |value| {
if value { Some(if_true.clone()) } else { None }
},
move |binding, value| {
binding.set(value.is_some());
},
)
}
pub fn then_some<T>(&self, if_true: T) -> Binding<Option<T>>
where
T: Clone + 'static,
{
Self::mapping(
self,
move |value| {
if value { Some(if_true.clone()) } else { None }
},
move |binding, value| {
binding.set(value.is_some());
},
)
}
pub fn bidirectional_select<T>(&self, if_true: T, if_false: T) -> Binding<T>
where
T: Eq + Clone + 'static,
{
let if_true_clone = if_true.clone();
Self::mapping(
self,
move |value| {
if value {
if_true.clone()
} else {
if_false.clone()
}
},
move |binding, value| {
binding.set(value == if_true_clone);
},
)
}
#[must_use]
pub fn reverse(&self) -> Self {
Self::mapping(
self,
|value| !value,
move |binding, value| {
binding.set(!value);
},
)
}
}
impl Not for Binding<bool> {
type Output = Self;
fn not(self) -> Self::Output {
self.reverse()
}
}
impl<T> Binding<T>
where
T: Clone + Neg<Output = T> + 'static,
{
#[must_use]
pub fn negate(&self) -> Self {
Self::mapping(self, core::ops::Neg::neg, move |binding, value| {
binding.set(value.neg());
})
}
}
impl<T> Neg for Binding<T>
where
T: Clone + Neg<Output = T> + 'static,
{
type Output = Self;
fn neg(self) -> Self::Output {
self.negate()
}
}
impl<T> Clone for Binding<T> {
fn clone(&self) -> Self {
self.0.cloned_binding()
}
}
#[derive(Debug, Clone)]
pub struct Container<T: 'static> {
value: Rc<RefCell<T>>,
watchers: WatcherManager<T>,
}
impl<T> From<T> for Container<T>
where
T: 'static + Clone,
{
#[track_caller]
fn from(value: T) -> Self {
Self::new(value)
}
}
impl<T: 'static + Clone + Default> Default for Container<T> {
#[track_caller]
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: 'static + Clone> Container<T> {
#[track_caller]
pub fn new(value: T) -> Self {
let value = Rc::new(RefCell::new(value));
let origin = Origin::capture::<Self>(SignalIdentity::from_rc(&value));
Self {
value,
watchers: WatcherManager::with_origin(origin),
}
}
}
impl<T: 'static + Clone> Signal for Container<T> {
type Output = T;
type Guard = BoxWatcherGuard;
fn get(&self) -> Self::Output {
self.value.borrow().deref().clone()
}
fn identity(&self) -> Option<SignalIdentity> {
Some(SignalIdentity::from_rc(&self.value))
}
fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard {
Box::new(self.watchers.register_as_guard(watcher))
}
}
impl<T: 'static + Clone> CustomBinding for Container<T> {
fn set(&self, value: T) {
self.value.replace(value.clone());
if self.watchers.is_empty() {
return;
}
let context = Context::from(value);
self.watchers.notify(&context);
}
}
impl<T: 'static> Signal for Binding<T> {
type Output = T;
type Guard = BoxWatcherGuard;
fn get(&self) -> Self::Output {
self.get()
}
fn identity(&self) -> Option<SignalIdentity> {
self.0.identity()
}
fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard {
Box::new(self.0.add_watcher(Rc::new(watcher)))
}
}
struct Mapping<Input: 'static, Output, Getter, Setter> {
binding: Binding<Input>,
getter: Getter,
setter: Setter,
discriminator: usize,
_marker: PhantomData<Output>,
}
impl<Input, Output, Getter: Clone, Setter: Clone> Clone for Mapping<Input, Output, Getter, Setter> {
fn clone(&self) -> Self {
Self {
binding: self.binding.clone(),
getter: self.getter.clone(),
setter: self.setter.clone(),
discriminator: self.discriminator,
_marker: PhantomData,
}
}
}
impl<Input, Output, Getter, Setter> Signal for Mapping<Input, Output, Getter, Setter>
where
Input: 'static,
Output: 'static,
Getter: 'static + Clone + Fn(Input) -> Output,
Setter: 'static + Clone,
{
type Output = Output;
type Guard = <Binding<Input> as Signal>::Guard;
fn get(&self) -> Self::Output {
(self.getter)(self.binding.get())
}
fn identity(&self) -> Option<SignalIdentity> {
self.binding
.identity()
.map(|identity| identity.with_discriminator(self.discriminator))
}
fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard {
let getter = self.getter.clone();
self.binding.watch(move |context| {
let context = context.map(&(getter));
watcher(context);
})
}
}
impl<Input, Output, Getter, Setter> CustomBinding for Mapping<Input, Output, Getter, Setter>
where
Input: 'static,
Output: 'static,
Getter: 'static + Clone + Fn(Input) -> Output,
Setter: 'static + Clone + Fn(&Binding<Input>, Output),
{
fn set(&self, value: Output) {
(self.setter)(&self.binding, value);
}
}
impl<T> From<Binding<T>> for Computed<T> {
fn from(val: Binding<T>) -> Self {
let boxed = val.0 as Box<_>;
Self(boxed)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::{string::String, vec, vec::Vec};
#[test]
fn test_binding_into_conversion() {
let text: Binding<String> = binding("hello");
assert_eq!(text.get(), "hello");
let number: Binding<i32> = binding(42);
assert_eq!(number.get(), 42);
let items: Binding<Vec<i32>> = binding(vec![1, 2, 3]);
assert_eq!(items.get(), vec![1, 2, 3]);
let count: Binding<i64> = binding(100i32);
assert_eq!(count.get(), 100i64);
}
#[test]
fn test_binding_operations() {
let text: Binding<String> = binding("initial");
text.set_from("updated"); assert_eq!(text.get(), "updated");
let counter: Binding<i32> = binding(0);
counter.add_assign(5);
assert_eq!(counter.get(), 5);
counter.sub_assign(2);
assert_eq!(counter.get(), 3);
}
#[test]
fn test_set_with_into_conversion() {
let text: Binding<String> = binding(String::new());
text.set_from("hello");
assert_eq!(text.get(), "hello");
text.set(String::from("world"));
assert_eq!(text.get(), "world");
let number: Binding<i64> = binding(0i64);
number.set(42); assert_eq!(number.get(), 42i64);
number.set(100); assert_eq!(number.get(), 100i64);
}
#[test]
fn test_with_mut_allows_nested_get() {
use alloc::rc::Rc;
use core::cell::RefCell;
let binding: Binding<i32> = binding(0);
let watcher_binding = binding.clone();
let reader_binding = binding.clone();
let notifications = Rc::new(RefCell::new(0usize));
let notifications_clone = notifications.clone();
let _guard = watcher_binding.watch(move |_| {
let _ = reader_binding.get();
*notifications_clone.borrow_mut() += 1;
});
binding.with_mut(|value| *value += 1);
assert_eq!(binding.get(), 1);
assert_eq!(*notifications.borrow(), 1);
}
#[test]
fn test_binding_sign() {
let number = binding(10i32);
let sign = number.sign();
assert!(sign.get(), "Positive number should have positive sign");
number.set(-10);
assert!(!sign.get(), "Negative number should have negative sign");
number.set(0);
assert!(sign.get(), "Zero should have positive sign");
number.set(20);
assert_eq!(number.get(), 20);
sign.set(false); assert_eq!(
number.get(),
-20,
"Setting sign to false should make number negative"
);
number.set(-30);
assert_eq!(number.get(), -30);
sign.set(true); assert_eq!(
number.get(),
30,
"Setting sign to true should make number positive"
);
let is_positive = number.sign();
number.set(-5);
assert!(!is_positive.get());
number.set(5);
assert!(is_positive.get());
}
#[test]
fn test_binding_clamp_enforces_range_on_set() {
let source: Binding<i32> = binding(5);
let clamped = source.clamp(0..=10);
clamped.set(-42);
assert_eq!(
source.get(),
0,
"values below range should clamp to lower bound"
);
clamped.set(42);
assert_eq!(
source.get(),
10,
"values above range should clamp to upper bound"
);
clamped.set(7);
assert_eq!(
source.get(),
7,
"in-range values should pass through unchanged"
);
}
#[test]
fn test_get_mut_without_mutation_does_not_notify() {
use alloc::rc::Rc;
use core::cell::RefCell;
let binding: Binding<i32> = binding(1);
let notifications = Rc::new(RefCell::new(Vec::new()));
let notifications_clone = notifications.clone();
let _guard = binding.watch(move |ctx| {
notifications_clone.borrow_mut().push(ctx.into_value());
});
{
let _unused_guard = binding.get_mut();
}
assert!(
notifications.borrow().is_empty(),
"Dropping guard without mutation should not notify watchers"
);
}
}