use crate::compat::{format, lock, Box, HashMap, MiniToString, Mutex, String, Vec};
use crate::data_binding::traits::*;
use alloc::sync::{Arc, Weak};
use core::sync::atomic::{AtomicBool, Ordering};
pub struct Binding<T: Clone + Send + 'static> {
inner: Arc<Mutex<BindingInner<T>>>,
}
struct BindingInner<T: Clone + Send + 'static> {
value: T,
listeners: HashMap<String, BoxedListener>,
notifying: bool,
unsubscribed_during_notify: alloc::collections::BTreeSet<String>,
pending_replays: usize,
}
impl<T: Clone + Send + 'static> BindingInner<T> {
fn new(value: T) -> Self {
Self {
value,
listeners: HashMap::new(),
notifying: false,
unsubscribed_during_notify: alloc::collections::BTreeSet::new(),
pending_replays: 0,
}
}
}
impl<T: Clone + Send + 'static> Binding<T> {
pub fn new(value: T) -> Self {
Self { inner: Arc::new(Mutex::new(BindingInner::new(value))) }
}
#[inline(always)]
pub fn get(&self) -> T {
lock(&self.inner).value.clone()
}
pub fn set(&self, value: T) {
let mut listeners: Vec<(String, BoxedListener)>;
{
let mut inner = lock(&self.inner);
inner.value = value;
if inner.notifying {
inner.pending_replays = inner.pending_replays.saturating_add(1);
return;
}
inner.notifying = true;
inner.unsubscribed_during_notify.clear();
listeners = core::mem::take(&mut inner.listeners).into_iter().collect();
}
loop {
let mut guard = NotifyGuard { inner: &self.inner, listeners, armed: true };
for (key, ref mut listener) in &mut guard.listeners {
listener.on_value_changed(key, "set");
}
let next = {
let mut inner = lock(&self.inner);
for (key, listener) in guard.listeners.drain(..) {
if inner.unsubscribed_during_notify.contains(&key) {
continue;
}
inner.listeners.entry(key).or_insert(listener);
}
if inner.pending_replays > 0 {
inner.pending_replays -= 1;
inner.unsubscribed_during_notify.clear();
Some(core::mem::take(&mut inner.listeners).into_iter().collect::<Vec<_>>())
} else {
inner.notifying = false;
inner.unsubscribed_during_notify.clear();
None
}
};
guard.armed = false;
match next {
Some(next_listeners) => listeners = next_listeners,
None => break,
}
}
}
pub fn subscribe(&self, key: &str, listener: BoxedListener) {
lock(&self.inner).listeners.insert(key.to_string(), listener);
}
pub fn unsubscribe(&self, key: &str) {
let mut inner = lock(&self.inner);
inner.listeners.remove(key);
if inner.notifying {
inner.unsubscribed_during_notify.insert(key.to_string());
}
}
pub fn bind_to(&self, other: &Binding<T>)
where
T: PartialEq,
{
let syncing = Arc::new(AtomicBool::new(false));
let self_weak = Arc::downgrade(&self.inner);
let other_weak = Arc::downgrade(&other.inner);
let self_ptr = Arc::as_ptr(&self.inner);
let other_ptr = Arc::as_ptr(&other.inner);
let listener_self_key = format!("__two_way_{:p}_{:p}", self_ptr, other_ptr);
let listener_other_key = format!("__two_way_{:p}_{:p}", other_ptr, self_ptr);
self.subscribe(
&listener_self_key,
Box::new(TwoWayListener::new(syncing.clone(), self_weak.clone(), other_weak.clone())),
);
other.subscribe(
&listener_other_key,
Box::new(TwoWayListener::new(syncing, other_weak, self_weak)),
);
}
#[inline(always)]
pub fn get_copy(&self) -> T
where
T: Copy,
{
lock(&self.inner).value
}
pub fn listener_count(&self) -> usize {
lock(&self.inner).listeners.len()
}
}
impl<T: Clone + Send + 'static> BindingInner<T> {
fn set_no_notify(&mut self, value: T) {
self.value = value;
}
}
struct TwoWayListener<T: Clone + Send + 'static> {
syncing: Arc<AtomicBool>,
source: Weak<Mutex<BindingInner<T>>>,
target: Weak<Mutex<BindingInner<T>>>,
}
impl<T: Clone + Send + 'static> TwoWayListener<T> {
fn new(
syncing: Arc<AtomicBool>,
source: Weak<Mutex<BindingInner<T>>>,
target: Weak<Mutex<BindingInner<T>>>,
) -> Self {
Self { syncing, source, target }
}
}
impl<T: Clone + Send + 'static + PartialEq> BindingListener for TwoWayListener<T> {
fn on_value_changed(&mut self, _key: &str, _operation: &str) {
if self.syncing.swap(true, Ordering::SeqCst) {
return;
}
let _reset = SyncingGuard { flag: &self.syncing };
let val = self.source.upgrade().map(|source| lock(&source).value.clone());
if let Some(val) = val {
if let Some(target) = self.target.upgrade() {
lock(&target).set_no_notify(val);
}
}
}
}
struct SyncingGuard<'a> {
flag: &'a AtomicBool,
}
impl Drop for SyncingGuard<'_> {
fn drop(&mut self) {
self.flag.store(false, Ordering::SeqCst);
}
}
struct NotifyGuard<'a, T: Clone + Send + 'static> {
inner: &'a Mutex<BindingInner<T>>,
listeners: Vec<(String, BoxedListener)>,
armed: bool,
}
impl<T: Clone + Send + 'static> Drop for NotifyGuard<'_, T> {
fn drop(&mut self) {
if !self.armed {
return;
}
let mut inner = lock(self.inner);
for (key, listener) in self.listeners.drain(..) {
if inner.unsubscribed_during_notify.contains(&key) {
continue;
}
inner.listeners.entry(key).or_insert(listener);
}
inner.notifying = false;
inner.unsubscribed_during_notify.clear();
inner.pending_replays = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compat::{lock, Mutex};
use core::sync::atomic::AtomicI32;
#[test]
fn test_binding_get_set() {
let b = Binding::new(42);
assert_eq!(b.get(), 42);
b.set(100);
assert_eq!(b.get(), 100);
}
#[test]
fn syncing_guard_clears_flag_on_panic() {
let flag = AtomicBool::new(false);
assert!(!flag.swap(true, Ordering::SeqCst), "flag starts unset");
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _reset = SyncingGuard { flag: &flag };
panic!("boom inside the guarded section");
}));
assert!(result.is_err(), "the probe must panic");
assert!(
!flag.load(Ordering::SeqCst),
"the guard must clear `syncing` while unwinding, otherwise the binding is dead forever"
);
}
#[test]
fn syncing_guard_clears_flag_on_normal_exit() {
let flag = AtomicBool::new(false);
flag.store(true, Ordering::SeqCst);
{
let _reset = SyncingGuard { flag: &flag };
}
assert!(!flag.load(Ordering::SeqCst), "the guard must clear `syncing` on drop");
}
#[test]
fn a_panicking_listener_does_not_destroy_the_binding() {
let binding: Arc<Binding<i32>> = Arc::new(Binding::new(0));
let counter = Arc::new(AtomicI32::new(0));
let c = Arc::clone(&counter);
binding.subscribe(
"counter",
Box::new(FnListener::new(move |_key: &str, _op: &str| {
c.fetch_add(1, Ordering::SeqCst);
})),
);
let panicked = Arc::new(AtomicBool::new(false));
let p = Arc::clone(&panicked);
binding.subscribe(
"panner",
Box::new(FnListener::new(move |_key: &str, _op: &str| {
if !p.swap(true, Ordering::SeqCst) {
panic!("boom from the panner");
}
})),
);
assert_eq!(binding.listener_count(), 2, "two listeners are subscribed");
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
binding.set(1);
}));
assert!(result.is_err(), "the panner must panic on its first notification");
assert_eq!(binding.listener_count(), 2, "both listeners must be restored after a panic");
let before = counter.load(Ordering::SeqCst);
binding.set(2);
assert_eq!(
counter.load(Ordering::SeqCst),
before + 1,
"the next notification must reach the surviving listener"
);
}
#[test]
fn two_way_listener_propagates_and_rearms() {
let syncing = Arc::new(AtomicBool::new(false));
let source: Arc<Mutex<BindingInner<i32>>> = Arc::new(Mutex::new(BindingInner::new(7)));
let target: Arc<Mutex<BindingInner<i32>>> = Arc::new(Mutex::new(BindingInner::new(0)));
let mut listener = TwoWayListener::new(
Arc::clone(&syncing),
Arc::downgrade(&source),
Arc::downgrade(&target),
);
listener.on_value_changed("k", "set");
assert_eq!(lock(&target).value, 7, "the value must propagate source -> target");
assert!(!syncing.load(Ordering::SeqCst), "the guard must re-arm the listener");
lock(&source).value = 9;
listener.on_value_changed("k", "set");
assert_eq!(lock(&target).value, 9, "a later change must still propagate");
assert!(!syncing.load(Ordering::SeqCst));
}
#[test]
fn test_binding_listener_notification() {
let b = Binding::new("hello".to_string());
let notified = Arc::new(AtomicBool::new(false));
let n = notified.clone();
let listener = Box::new(FnListener::new(move |_key, _op| {
n.store(true, Ordering::SeqCst);
}));
b.subscribe("test", listener);
b.set("world".to_string());
assert!(notified.load(Ordering::SeqCst));
}
#[test]
fn test_binding_unsubscribe() {
let b = Binding::new(0);
let count = Arc::new(AtomicI32::new(0));
let c = count.clone();
let listener = Box::new(FnListener::new(move |_key, _op| {
c.fetch_add(1, Ordering::SeqCst);
}));
b.subscribe("test", listener);
b.set(1);
assert_eq!(count.load(Ordering::SeqCst), 1);
b.unsubscribe("test");
b.set(2);
assert_eq!(count.load(Ordering::SeqCst), 1);
}
#[test]
fn test_binding_multiple_listeners() {
let b = Binding::new(0);
let count_a = Arc::new(AtomicI32::new(0));
let count_b = Arc::new(AtomicI32::new(0));
let ca = count_a.clone();
b.subscribe(
"a",
Box::new(FnListener::new(move |_, _| {
ca.fetch_add(1, Ordering::SeqCst);
})),
);
let cb = count_b.clone();
b.subscribe(
"b",
Box::new(FnListener::new(move |_, _| {
cb.fetch_add(1, Ordering::SeqCst);
})),
);
b.set(1);
assert_eq!(count_a.load(Ordering::SeqCst), 1);
assert_eq!(count_b.load(Ordering::SeqCst), 1);
}
#[test]
fn test_binding_listener_receive_key() {
let b = Binding::new(0);
let received_key = Arc::new(Mutex::new(String::new()));
let rk = received_key.clone();
let listener = Box::new(FnListener::new(move |key, _op| {
*lock(&rk) = key.to_string();
}));
b.subscribe("my_key", listener);
b.set(99);
assert_eq!(*lock(&received_key), "my_key");
}
#[test]
fn test_binding_two_way_sync() {
let a = Binding::new(10);
let b = Binding::new(20);
a.bind_to(&b);
a.set(30);
assert_eq!(a.get(), 30);
assert_eq!(b.get(), 30);
b.set(50);
assert_eq!(a.get(), 50);
assert_eq!(b.get(), 50);
}
#[test]
fn test_binding_two_way_no_infinite_loop() {
let a = Binding::new(0);
let b = Binding::new(0);
let a_count = Arc::new(AtomicI32::new(0));
let b_count = Arc::new(AtomicI32::new(0));
let ac = a_count.clone();
a.subscribe(
"a_count",
Box::new(FnListener::new(move |_, _| {
ac.fetch_add(1, Ordering::SeqCst);
})),
);
let bc = b_count.clone();
b.subscribe(
"b_count",
Box::new(FnListener::new(move |_, _| {
bc.fetch_add(1, Ordering::SeqCst);
})),
);
a.bind_to(&b);
a.set(42);
assert_eq!(a_count.load(Ordering::SeqCst), 1);
assert_eq!(b_count.load(Ordering::SeqCst), 0);
b.set(100);
assert_eq!(a_count.load(Ordering::SeqCst), 1); assert_eq!(b_count.load(Ordering::SeqCst), 1); }
#[test]
fn test_binding_drop_safety() {
let a = Arc::new(Binding::new(10));
let b = Arc::new(Binding::new(20));
a.bind_to(&b);
drop(a);
b.set(99);
assert_eq!(b.get(), 99);
}
#[test]
fn two_way_binding_keeps_distinct_neighbors() {
let a = Binding::new(0);
let b = Binding::new(0);
let c = Binding::new(0);
a.bind_to(&b);
a.bind_to(&c);
assert_eq!(a.listener_count(), 2);
assert_eq!(b.listener_count(), 1);
assert_eq!(c.listener_count(), 1);
a.set(7);
assert_eq!(b.get(), 7);
assert_eq!(c.get(), 7);
b.set(9);
assert_eq!(a.get(), 9);
assert_eq!(c.get(), 7);
}
#[test]
fn repeated_bind_to_is_bounded() {
let a = Binding::new(0);
let b = Binding::new(0);
a.bind_to(&b);
a.bind_to(&b);
assert_eq!(a.listener_count(), 1);
assert_eq!(b.listener_count(), 1);
}
#[test]
fn two_way_cycle_is_bounded() {
let a = Binding::new(0);
let b = Binding::new(0);
let c = Binding::new(0);
a.bind_to(&b);
b.bind_to(&c);
c.bind_to(&a);
assert_eq!(a.listener_count(), 2);
assert_eq!(b.listener_count(), 2);
assert_eq!(c.listener_count(), 2);
a.set(1);
assert_eq!(b.get(), 1);
assert_eq!(c.get(), 1);
}
}
#[cfg(test)]
mod unsubscribe_during_notify_tests {
use super::*;
use crate::compat::lock;
use alloc::sync::Arc;
use core::sync::atomic::AtomicI32;
#[test]
fn a_listener_that_unsubscribes_itself_stays_removed() {
let binding: Arc<Binding<i32>> = Arc::new(Binding::new(0));
let weak = Arc::downgrade(&binding);
let calls = Arc::new(AtomicI32::new(0));
let counter = Arc::clone(&calls);
binding.subscribe(
"self_removing",
Box::new(FnListener::new(move |_key: &str, _value: &str| {
counter.fetch_add(1, Ordering::SeqCst);
if let Some(b) = weak.upgrade() {
b.unsubscribe("self_removing");
}
})),
);
binding.set(1);
assert_eq!(calls.load(Ordering::SeqCst), 1, "the listener must fire once");
assert_eq!(binding.listener_count(), 0, "it must not be restored after unsubscribing");
binding.set(2);
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"a listener that unsubscribed itself must not fire again"
);
}
#[test]
fn a_listener_removed_by_an_earlier_listener_does_not_fire() {
let binding: Arc<Binding<i32>> = Arc::new(Binding::new(0));
let victim_calls = Arc::new(AtomicI32::new(0));
let counter = Arc::clone(&victim_calls);
binding.subscribe(
"victim",
Box::new(FnListener::new(move |_key: &str, _value: &str| {
counter.fetch_add(1, Ordering::SeqCst);
})),
);
let weak = Arc::downgrade(&binding);
binding.subscribe(
"remover",
Box::new(FnListener::new(move |_key: &str, _value: &str| {
if let Some(b) = weak.upgrade() {
b.unsubscribe("victim");
}
})),
);
binding.set(1);
assert_eq!(binding.listener_count(), 1, "only the remover must remain");
assert_eq!(victim_calls.load(Ordering::SeqCst), 1, "it was notified this pass");
binding.set(2);
assert_eq!(
victim_calls.load(Ordering::SeqCst),
1,
"the removed listener must not be notified again"
);
}
#[test]
fn resubscribing_during_notify_keeps_the_new_listener() {
let binding: Arc<Binding<i32>> = Arc::new(Binding::new(0));
let old_calls = Arc::new(AtomicI32::new(0));
let new_calls = Arc::new(AtomicI32::new(0));
let old_counter = Arc::clone(&old_calls);
let weak = Arc::downgrade(&binding);
let new_counter = Arc::clone(&new_calls);
binding.subscribe(
"slot",
Box::new(FnListener::new(move |_key: &str, _value: &str| {
old_counter.fetch_add(1, Ordering::SeqCst);
if let Some(b) = weak.upgrade() {
let counter = Arc::clone(&new_counter);
b.subscribe(
"slot",
Box::new(FnListener::new(move |_key: &str, _value: &str| {
counter.fetch_add(1, Ordering::SeqCst);
})),
);
}
})),
);
binding.set(1);
assert_eq!(old_calls.load(Ordering::SeqCst), 1);
assert_eq!(binding.listener_count(), 1, "the slot holds exactly one listener");
binding.set(2);
assert_eq!(new_calls.load(Ordering::SeqCst), 1, "the replacement listener runs");
assert_eq!(old_calls.load(Ordering::SeqCst), 1, "the replaced listener does not");
}
#[test]
fn unsubscribing_outside_a_notification_is_not_remembered() {
let binding: Arc<Binding<i32>> = Arc::new(Binding::new(0));
binding.subscribe("a", Box::new(FnListener::new(|_: &str, _: &str| {})));
binding.subscribe("b", Box::new(FnListener::new(|_: &str, _: &str| {})));
binding.unsubscribe("a");
assert_eq!(binding.listener_count(), 1);
binding.subscribe("a", Box::new(FnListener::new(|_: &str, _: &str| {})));
binding.set(1);
assert_eq!(binding.listener_count(), 2, "both listeners must survive the pass");
let inner = lock(&binding.inner);
assert!(inner.unsubscribed_during_notify.is_empty(), "the set must be cleared");
}
#[test]
fn a_listener_that_unsubscribes_then_reenters_set_stays_removed() {
let binding: Arc<Binding<i32>> = Arc::new(Binding::new(0));
let calls = Arc::new(AtomicI32::new(0));
let counter = Arc::clone(&calls);
let weak = Arc::downgrade(&binding);
binding.subscribe(
"self_reentering",
Box::new(FnListener::new(move |_key: &str, _op: &str| {
counter.fetch_add(1, Ordering::SeqCst);
if let Some(b) = weak.upgrade() {
b.unsubscribe("self_reentering");
b.set(999);
}
})),
);
binding.set(1);
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"the listener fired exactly once for the outer set"
);
assert_eq!(
binding.listener_count(),
0,
"the self-unsubscribed listener must stay removed across the nested set"
);
binding.set(2);
assert_eq!(calls.load(Ordering::SeqCst), 1, "the next set must not revive the listener");
}
}
#[cfg(test)]
mod atomic_publish_tests {
use super::*;
use crate::compat::lock;
use alloc::sync::Arc;
use core::sync::atomic::AtomicI32;
#[test]
fn a_second_set_during_a_pass_is_still_delivered() {
use std::sync::Barrier;
let binding: Arc<Binding<i32>> = Arc::new(Binding::new(0));
let calls = Arc::new(AtomicI32::new(0));
let entered = Arc::new(Barrier::new(2));
let release = Arc::new(Barrier::new(2));
let counter = Arc::clone(&calls);
let entered_slot = Arc::clone(&entered);
let release_slot = Arc::clone(&release);
binding.subscribe(
"counter",
Box::new(FnListener::new(move |_key: &str, _op: &str| {
let n = counter.fetch_add(1, Ordering::SeqCst) + 1;
if n == 1 {
entered_slot.wait();
release_slot.wait();
}
})),
);
let worker_binding = Arc::clone(&binding);
let worker = std::thread::spawn(move || worker_binding.set(1));
entered.wait();
binding.set(2);
release.wait();
worker.join().expect("the first setter must not panic");
assert_eq!(
calls.load(Ordering::SeqCst),
2,
"both sets must reach the listener; a single delivery is the lost-notification defect"
);
assert_eq!(binding.get(), 2, "the last write wins for the value");
}
#[test]
fn a_reentrant_set_is_queued_and_replayed() {
let binding: Arc<Binding<i32>> = Arc::new(Binding::new(0));
let deliveries = Arc::new(AtomicI32::new(0));
let weak = Arc::downgrade(&binding);
let counter = Arc::clone(&deliveries);
binding.subscribe(
"reenter",
Box::new(FnListener::new(move |_key: &str, _op: &str| {
let n = counter.fetch_add(1, Ordering::SeqCst) + 1;
if n == 1 {
if let Some(b) = weak.upgrade() {
b.set(42);
}
}
})),
);
binding.set(1);
assert_eq!(
deliveries.load(Ordering::SeqCst),
2,
"the re-entrant set must be replayed, not merged away"
);
assert_eq!(binding.get(), 42);
assert_eq!(binding.listener_count(), 1, "the listener survives both passes");
}
#[test]
fn an_unsubscribe_is_honoured_across_a_queued_replay() {
let binding: Arc<Binding<i32>> = Arc::new(Binding::new(0));
let victim_calls = Arc::new(AtomicI32::new(0));
let counter = Arc::clone(&victim_calls);
binding.subscribe(
"victim",
Box::new(FnListener::new(move |_key: &str, _op: &str| {
counter.fetch_add(1, Ordering::SeqCst);
})),
);
let weak = Arc::downgrade(&binding);
let acted = Arc::new(AtomicI32::new(0));
let acted_slot = Arc::clone(&acted);
binding.subscribe(
"remover",
Box::new(FnListener::new(move |_key: &str, _op: &str| {
if acted_slot.fetch_add(1, Ordering::SeqCst) > 0 {
return;
}
if let Some(b) = weak.upgrade() {
b.unsubscribe("victim");
b.set(7);
}
})),
);
binding.set(1);
assert_eq!(binding.listener_count(), 1, "only the remover remains");
assert_eq!(victim_calls.load(Ordering::SeqCst), 1, "the victim ran in the first pass only");
binding.set(2);
assert_eq!(
victim_calls.load(Ordering::SeqCst),
1,
"the removed listener must not be revived by the replay or a later set"
);
}
#[test]
fn the_publish_window_is_atomic() {
let binding: Arc<Binding<i32>> = Arc::new(Binding::new(0));
let observed_notifying = Arc::new(AtomicI32::new(-1));
let weak = Arc::downgrade(&binding);
let flag = Arc::clone(&observed_notifying);
binding.subscribe(
"probe",
Box::new(FnListener::new(move |_key: &str, _op: &str| {
if let Some(b) = weak.upgrade() {
let empty = b.listener_count() == 0;
let notifying = lock(&b.inner).notifying;
flag.store(if empty && notifying { 1 } else { 0 }, Ordering::SeqCst);
}
})),
);
binding.set(5);
assert_eq!(binding.get(), 5);
assert_eq!(
observed_notifying.load(Ordering::SeqCst),
1,
"while the map is empty, `notifying` must be published in the same critical section"
);
assert_eq!(binding.listener_count(), 1, "the listener is restored after the pass");
}
}