use std::rc::Weak;
use crate::errors::QlResult;
pub use crate::patterns::observable::AsObservable;
use crate::patterns::observable::{Observable, Observer, ResetThenNotify};
use crate::require;
use crate::shared::{Shared, SharedMut, shared_mut};
pub struct Link<T: ?Sized> {
current: Option<Shared<T>>,
weak: Option<Weak<T>>,
observable: Shared<Observable>,
forwarder: SharedMut<ResetThenNotify>,
}
impl<T: ?Sized> Link<T> {
fn resolved(&self) -> Option<Shared<T>> {
match &self.current {
Some(strong) => Some(strong.clone()),
None => self.weak.as_ref().and_then(Weak::upgrade),
}
}
fn is_empty(&self) -> bool {
self.resolved().is_none()
}
}
impl<T: AsObservable + ?Sized> Link<T> {
fn new(pointee: Option<Shared<T>>) -> Self {
let observable = Shared::new(Observable::new());
let forwarder = ResetThenNotify::forwarding(Shared::clone(&observable));
let mut link = Link {
current: None,
weak: None,
observable,
forwarder,
};
link.link_to(pointee);
link
}
fn link_to(&mut self, pointee: Option<Shared<T>>) -> Shared<Observable> {
let forwarder = self.forwarder.clone() as SharedMut<dyn Observer>;
if let Some(old) = &self.current {
old.observable().unregister_observer(&forwarder);
}
if let Some(new) = &pointee {
new.observable().register_observer(&forwarder);
}
self.current = pointee;
self.weak = None;
self.observable.clone()
}
fn link_weak(&mut self, pointee: Weak<T>) -> Shared<Observable> {
let forwarder = self.forwarder.clone() as SharedMut<dyn Observer>;
if let Some(old) = &self.current {
old.observable().unregister_observer(&forwarder);
}
self.current = None;
self.weak = Some(pointee);
self.observable.clone()
}
}
pub struct Handle<T: ?Sized> {
link: SharedMut<Link<T>>,
}
impl<T: AsObservable + ?Sized> Handle<T> {
pub fn empty() -> Self {
Handle {
link: shared_mut(Link::new(None)),
}
}
pub fn new(pointee: Shared<T>) -> Self {
Handle {
link: shared_mut(Link::new(Some(pointee))),
}
}
}
impl<T: ?Sized> Handle<T> {
pub fn current_link(&self) -> QlResult<Shared<T>> {
let link = self.link.borrow();
require!(!link.is_empty(), "empty Handle cannot be dereferenced");
Ok(link.resolved().unwrap())
}
pub fn is_empty(&self) -> bool {
self.link.borrow().is_empty()
}
pub fn register_observer(&self, observer: &SharedMut<dyn Observer>) -> bool {
self.link.borrow().observable.register_observer(observer)
}
pub fn points_to_same_link(&self, other: &Handle<T>) -> bool {
SharedMut::ptr_eq(&self.link, &other.link)
}
}
impl<T: ?Sized> Clone for Handle<T> {
fn clone(&self) -> Self {
Handle {
link: SharedMut::clone(&self.link),
}
}
}
pub struct RelinkableHandle<T: ?Sized> {
handle: Handle<T>,
}
impl<T: AsObservable + ?Sized> RelinkableHandle<T> {
pub fn empty() -> Self {
RelinkableHandle {
handle: Handle::empty(),
}
}
pub fn new(pointee: Shared<T>) -> Self {
RelinkableHandle {
handle: Handle::new(pointee),
}
}
pub fn link_to(&self, pointee: Shared<T>) {
let observable = self.handle.link.borrow_mut().link_to(Some(pointee));
observable.notify_observers();
}
pub fn link_to_weak(&self, pointee: Weak<T>) {
let observable = self.handle.link.borrow_mut().link_weak(pointee);
observable.notify_observers();
}
pub fn reset(&self) {
let observable = self.handle.link.borrow_mut().link_to(None);
observable.notify_observers();
}
}
impl<T: ?Sized> RelinkableHandle<T> {
pub fn handle(&self) -> Handle<T> {
self.handle.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::quotes::{Quote, SimpleQuote};
use crate::shared::{shared, shared_mut};
#[derive(Default)]
struct Flag {
up: bool,
}
impl Observer for Flag {
fn update(&mut self) {
self.up = true;
}
}
struct Pointee {
value: i32,
observable: Observable,
}
impl Pointee {
fn new(value: i32) -> Shared<Pointee> {
shared(Pointee {
value,
observable: Observable::new(),
})
}
}
impl AsObservable for Pointee {
fn observable(&self) -> &Observable {
&self.observable
}
}
#[test]
fn empty_handle_cannot_be_dereferenced() {
let h: Handle<Pointee> = Handle::empty();
assert!(h.is_empty());
assert!(h.current_link().is_err());
}
#[test]
fn handle_dereferences_to_pointee() {
let h = Handle::new(Pointee::new(42));
assert!(!h.is_empty());
assert_eq!(h.current_link().unwrap().value, 42);
}
#[test]
fn copies_share_one_link() {
let h = Handle::new(Pointee::new(1));
let copy = h.clone();
assert!(h.points_to_same_link(©));
}
#[test]
fn relink_propagates_notification_to_observers() {
let rh = RelinkableHandle::new(Pointee::new(1));
let observed = rh.handle();
let flag = shared_mut(Flag::default());
observed.register_observer(&(flag.clone() as SharedMut<dyn Observer>));
rh.link_to(Pointee::new(2));
assert!(flag.borrow().up, "relink should notify observers");
assert_eq!(observed.current_link().unwrap().value, 2);
}
#[test]
fn reset_empties_and_notifies() {
let rh = RelinkableHandle::new(Pointee::new(1));
let observed = rh.handle();
let flag = shared_mut(Flag::default());
observed.register_observer(&(flag.clone() as SharedMut<dyn Observer>));
rh.reset();
assert!(flag.borrow().up);
assert!(observed.is_empty());
}
struct Reader {
handle: Handle<Pointee>,
seen: Option<i32>,
}
impl Observer for Reader {
fn update(&mut self) {
self.seen = self.handle.current_link().ok().map(|p| p.value);
}
}
#[test]
fn observer_may_read_handle_during_relink() {
let rh = RelinkableHandle::new(Pointee::new(1));
let reader = shared_mut(Reader {
handle: rh.handle(),
seen: None,
});
rh.handle()
.register_observer(&(reader.clone() as SharedMut<dyn Observer>));
rh.link_to(Pointee::new(2));
assert_eq!(reader.borrow().seen, Some(2));
}
#[test]
#[allow(clippy::approx_constant)]
fn pointee_change_notifies_handle_observers() {
let quote = shared(SimpleQuote::new(0.0));
let h: Handle<SimpleQuote> = Handle::new(quote.clone());
let flag = shared_mut(Flag::default());
h.register_observer(&(flag.clone() as SharedMut<dyn Observer>));
quote.set_value(3.14);
assert!(
flag.borrow().up,
"observer was not notified of quote change"
);
}
#[test]
#[allow(clippy::approx_constant)]
fn observable_handle_forwards_pointee_changes_and_relinks() {
let me1 = shared(SimpleQuote::new(0.0));
let h: RelinkableHandle<dyn Quote> = RelinkableHandle::new(me1.clone());
let f = shared_mut(Flag::default());
h.handle()
.register_observer(&(f.clone() as SharedMut<dyn Observer>));
me1.set_value(3.14);
assert!(f.borrow().up, "observer was not notified of quote change");
f.borrow_mut().up = false;
let me2 = shared(SimpleQuote::new(0.0));
h.link_to(me2.clone());
assert!(f.borrow().up, "observer was not notified of relink");
f.borrow_mut().up = false;
me1.set_value(1.0);
assert!(
!f.borrow().up,
"detached pointee must no longer notify handle observers"
);
me2.set_value(2.0);
assert!(
f.borrow().up,
"new pointee change must notify handle observers"
);
}
#[test]
fn reset_unhooks_the_old_pointee() {
let quote = shared(SimpleQuote::new(1.0));
let rh: RelinkableHandle<dyn Quote> = RelinkableHandle::new(quote.clone());
let flag = shared_mut(Flag::default());
rh.handle()
.register_observer(&(flag.clone() as SharedMut<dyn Observer>));
rh.reset();
flag.borrow_mut().up = false;
quote.set_value(2.0);
assert!(
!flag.borrow().up,
"reset must drop the pointee subscription"
);
}
#[test]
fn linking_an_empty_handle_starts_forwarding() {
let rh: RelinkableHandle<dyn Quote> = RelinkableHandle::empty();
let flag = shared_mut(Flag::default());
rh.handle()
.register_observer(&(flag.clone() as SharedMut<dyn Observer>));
let quote = shared(SimpleQuote::new(0.0));
rh.link_to(quote.clone());
flag.borrow_mut().up = false;
quote.set_value(1.0);
assert!(
flag.borrow().up,
"pointee linked after construction must forward"
);
}
struct Cacher {
handle: Handle<SimpleQuote>,
seen: Option<f64>,
}
impl Observer for Cacher {
fn update(&mut self) {
self.seen = self.handle.current_link().ok().and_then(|q| q.value().ok());
}
}
struct Normalizer {
quote: Shared<SimpleQuote>,
}
impl Observer for Normalizer {
fn update(&mut self) {
if let Ok(v) = self.quote.value()
&& v > 1.0
{
self.quote.set_value(1.0);
}
}
}
#[test]
fn write_back_during_forwarded_notification_reaches_handle_observers() {
let quote = shared(SimpleQuote::new(0.5));
let h: Handle<SimpleQuote> = Handle::new(quote.clone());
let cacher = shared_mut(Cacher {
handle: h.clone(),
seen: None,
});
h.register_observer(&(cacher.clone() as SharedMut<dyn Observer>));
let normalizer = shared_mut(Normalizer {
quote: quote.clone(),
});
h.register_observer(&(normalizer.clone() as SharedMut<dyn Observer>));
quote.set_value(2.0);
assert_eq!(quote.value().unwrap(), 1.0);
assert_eq!(
cacher.borrow().seen,
Some(1.0),
"handle observers must see the written-back value, not the stale one"
);
}
#[test]
fn weak_link_resolves_without_owning_or_observing() {
let quote = shared(SimpleQuote::new(1.0));
let rh: RelinkableHandle<dyn Quote> = RelinkableHandle::empty();
rh.link_to_weak(Shared::downgrade(&(quote.clone() as Shared<dyn Quote>)));
assert_eq!(rh.handle().current_link().unwrap().value().unwrap(), 1.0);
let flag = shared_mut(Flag::default());
rh.handle()
.register_observer(&(flag.clone() as SharedMut<dyn Observer>));
quote.set_value(2.0);
assert!(
!flag.borrow().up,
"a weak link must not observe its pointee"
);
drop(quote);
assert!(
rh.handle().is_empty(),
"a weak link must not keep the pointee alive"
);
}
#[test]
fn dropped_handle_stops_forwarding() {
let quote = shared(SimpleQuote::new(0.0));
{
let _h: Handle<SimpleQuote> = Handle::new(quote.clone());
}
assert_eq!(quote.set_value(1.0), Some(1.0));
}
}