use std::cell::Cell;
use crate::errors::QlResult;
use crate::fail;
use crate::handle::RelinkableHandle;
use crate::patterns::observable::{AsObservable, Observable};
use crate::shared::shared;
use crate::types::Real;
use super::Quote;
#[derive(Default)]
pub struct SimpleQuote {
value: Cell<Option<Real>>,
observable: Observable,
}
impl SimpleQuote {
pub fn new(value: impl Into<Option<Real>>) -> Self {
SimpleQuote {
value: Cell::new(value.into()),
observable: Observable::new(),
}
}
pub fn set_value(&self, value: impl Into<Option<Real>>) -> Option<Real> {
let new = value.into();
let old = self.value.get();
if new != old {
self.value.set(new);
self.observable.notify_observers();
}
match (new, old) {
(Some(n), Some(o)) => Some(n - o),
_ => None,
}
}
pub fn reset(&self) {
self.set_value(None);
}
}
impl AsObservable for SimpleQuote {
fn observable(&self) -> &Observable {
&self.observable
}
}
impl Quote for SimpleQuote {
fn value(&self) -> QlResult<Real> {
match self.value.get() {
Some(value) => Ok(value),
None => fail!("invalid SimpleQuote"),
}
}
fn is_valid(&self) -> bool {
self.value.get().is_some()
}
}
pub fn make_quote_handle(value: Real) -> RelinkableHandle<dyn Quote> {
RelinkableHandle::new(shared(SimpleQuote::new(value)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::handle::Handle;
use crate::patterns::observable::Observer;
use crate::shared::{Shared, SharedMut, shared_mut};
use crate::test_support::{Flag, as_observer};
#[test]
#[allow(clippy::approx_constant)]
fn observable_quote_notifies_on_set_value() {
let me = shared(SimpleQuote::new(0.0));
let f = Flag::new();
me.observable().register_observer(&as_observer(&f));
me.set_value(3.14);
assert!(Flag::is_up(&f), "observer was not notified of quote change");
assert_eq!(me.value().unwrap(), 3.14);
}
#[test]
fn observable_handle_notifies_on_relink() {
let me1: Shared<SimpleQuote> = shared(SimpleQuote::new(0.0));
let h: RelinkableHandle<dyn Quote> = RelinkableHandle::new(me1);
let f = Flag::new();
h.handle().register_observer(&as_observer(&f));
let me2: Shared<dyn Quote> = shared(SimpleQuote::new(0.0));
h.link_to(me2);
assert!(Flag::is_up(&f), "observer was not notified of relink");
}
#[test]
fn set_value_returns_difference_and_skips_no_op_notifications() {
let me = SimpleQuote::new(1.0);
let f = Flag::new();
me.observable().register_observer(&as_observer(&f));
assert_eq!(me.set_value(3.0), Some(2.0));
assert!(Flag::is_up(&f));
Flag::lower(&f);
assert_eq!(me.set_value(3.0), Some(0.0));
assert!(!Flag::is_up(&f), "unchanged value must not notify");
}
#[test]
fn invalid_quote_reports_and_errors() {
let me = SimpleQuote::default();
assert!(!me.is_valid());
let err = me.value().unwrap_err();
assert_eq!(err.message(), "invalid SimpleQuote");
me.set_value(1.0);
assert!(me.is_valid());
assert_eq!(me.value().unwrap(), 1.0);
}
#[test]
fn reset_invalidates_and_notifies() {
let me = SimpleQuote::new(1.0);
let f = Flag::new();
me.observable().register_observer(&as_observer(&f));
me.reset();
assert!(!me.is_valid());
assert!(Flag::is_up(&f));
Flag::lower(&f);
me.reset();
assert!(
!Flag::is_up(&f),
"resetting an invalid quote must not notify"
);
}
#[test]
fn valid_to_invalid_transitions_report_no_difference() {
let me = SimpleQuote::default();
assert_eq!(me.set_value(2.0), None);
assert_eq!(me.set_value(None), None);
}
#[test]
fn quote_is_usable_through_a_handle() {
let me = shared(SimpleQuote::new(0.25));
let h: Handle<dyn Quote> = Handle::new(me.clone());
let linked = h.current_link().unwrap();
assert!(linked.is_valid());
assert_eq!(linked.value().unwrap(), 0.25);
me.set_value(0.5);
assert_eq!(h.current_link().unwrap().value().unwrap(), 0.5);
}
#[test]
fn make_quote_handle_builds_a_linked_relinkable_handle() {
let h = make_quote_handle(0.03);
assert!(!h.handle().is_empty());
assert_eq!(h.handle().current_link().unwrap().value().unwrap(), 0.03);
}
struct Reader {
quote: Shared<SimpleQuote>,
seen: Option<Real>,
}
impl Observer for Reader {
fn update(&mut self) {
self.seen = self.quote.value().ok();
}
}
#[test]
fn observer_may_read_quote_during_notification() {
let me = shared(SimpleQuote::new(1.0));
let reader = shared_mut(Reader {
quote: me.clone(),
seen: None,
});
me.observable()
.register_observer(&(reader.clone() as SharedMut<dyn Observer>));
me.set_value(2.0);
assert_eq!(reader.borrow().seen, Some(2.0));
}
struct Normalizer {
quote: Shared<SimpleQuote>,
cap: Real,
}
impl Observer for Normalizer {
fn update(&mut self) {
if let Ok(v) = self.quote.value()
&& v > self.cap
{
self.quote.set_value(self.cap);
}
}
}
#[test]
fn observer_may_write_back_into_quote_during_notification() {
let me = shared(SimpleQuote::new(0.5));
let normalizer = shared_mut(Normalizer {
quote: me.clone(),
cap: 1.0,
});
me.observable()
.register_observer(&(normalizer.clone() as SharedMut<dyn Observer>));
me.set_value(2.0);
assert_eq!(me.value().unwrap(), 1.0);
}
}