use crate::compat::HashMap;
use crate::compat::Mutex;
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>,
}
impl<T: Clone + Send + 'static> Binding<T> {
pub fn new(value: T) -> Self {
Self { inner: Arc::new(Mutex::new(BindingInner { value, listeners: HashMap::new() })) }
}
#[inline(always)]
pub fn get(&self) -> T {
self.inner.lock().unwrap_or_else(|e| e.into_inner()).value.clone()
}
pub fn set(&self, value: T) {
let mut listeners: Vec<(String, BoxedListener)>;
{
let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
inner.value = value;
listeners = core::mem::take(&mut inner.listeners).into_iter().collect();
}
for (key, ref mut listener) in &mut listeners {
listener.on_value_changed(key, "set");
}
{
let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
for (key, listener) in listeners {
inner.listeners.entry(key).or_insert(listener);
}
}
}
pub fn subscribe(&self, key: &str, listener: BoxedListener) {
self.inner
.lock()
.unwrap_or_else(|e| e.into_inner())
.listeners
.insert(key.to_string(), listener);
}
pub fn unsubscribe(&self, key: &str) {
self.inner.lock().unwrap_or_else(|e| e.into_inner()).listeners.remove(key);
}
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 listener_self_key = format!("__two_way_self_{:p}", Arc::as_ptr(&self.inner));
let listener_other_key = format!("__two_way_other_{:p}", Arc::as_ptr(&other.inner));
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,
{
self.inner.lock().unwrap_or_else(|e| e.into_inner()).value
}
pub fn listener_count(&self) -> usize {
self.inner.lock().unwrap_or_else(|e| e.into_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 val = self
.source
.upgrade()
.map(|source| source.lock().unwrap_or_else(|e| e.into_inner()).value.clone());
if let Some(val) = val {
if let Some(target) = self.target.upgrade() {
target.lock().unwrap_or_else(|e| e.into_inner()).set_no_notify(val);
}
}
self.syncing.store(false, Ordering::SeqCst);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compat::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 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| {
*rk.lock().unwrap() = key.to_string();
}));
b.subscribe("my_key", listener);
b.set(99);
assert_eq!(*received_key.lock().unwrap(), "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);
}
}