1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::{cell::RefCell, rc::Rc};
use crate::{story::Story, story_error::StoryError, value_type::ValueType};
/// Defines the method that will be called when an observed global variable
/// changes.
pub trait VariableObserver {
fn changed(&mut self, variable_name: &str, value: &ValueType);
}
/// # Variable Observers
/// Methods dealing with variable observer callbacks that will be called while
/// the [`Story`] is processing.
impl Story {
/// When the specified global variable changes it's value, the observer will
/// be called to notify it of the change. Note that if the value changes
/// multiple times within the ink, the observer will only be called
/// once, at the end of the ink's evaluation. If, during the evaluation,
/// it changes and then changes back again to its original value, it
/// will still be called. Note that the observer will also be fired if
/// the value of the variable is changed externally to the ink, by
/// directly setting a value in
/// [`story.set_variable`](Story::set_variable).
pub fn observe_variable(
&mut self,
variable_name: &str,
observer: Rc<RefCell<dyn VariableObserver>>,
) -> Result<(), StoryError> {
self.if_async_we_cant("observe a new variable")?;
if !self
.get_state()
.variables_state
.global_variable_exists_with_name(variable_name)
{
return Err(StoryError::BadArgument(format!(
"Cannot observe variable '{variable_name}' because it wasn't declared in the ink story."
)));
}
match self.variable_observers.get_mut(variable_name) {
Some(v) => {
v.push(observer);
}
None => {
let v: Vec<Rc<RefCell<dyn VariableObserver>>> = vec![observer];
self.variable_observers.insert(variable_name.to_string(), v);
}
}
Ok(())
}
/// Removes a variable observer, to stop getting variable change
/// notifications. If you pass a specific variable name, it will stop
/// observing that particular one. If you pass None, then the observer
/// will be removed from all variables that it's subscribed to.
pub fn remove_variable_observer(
&mut self,
observer: &Rc<RefCell<dyn VariableObserver>>,
specific_variable_name: Option<&str>,
) -> Result<(), StoryError> {
self.if_async_we_cant("remove a variable observer")?;
// Remove observer for this specific variable
match specific_variable_name {
Some(specific_variable_name) => {
if let Some(v) = self.variable_observers.get_mut(specific_variable_name) {
let index = v.iter().position(|x| Rc::ptr_eq(x, observer)).unwrap();
v.remove(index);
if v.is_empty() {
self.variable_observers.remove(specific_variable_name);
}
}
}
None => {
// Remove observer for all variables
let mut keys_to_remove = Vec::new();
for (k, v) in self.variable_observers.iter_mut() {
let index = v.iter().position(|x| Rc::ptr_eq(x, observer)).unwrap();
v.remove(index);
if v.is_empty() {
keys_to_remove.push(k.to_string());
}
}
for key_to_remove in keys_to_remove.iter() {
self.variable_observers.remove(key_to_remove);
}
}
}
Ok(())
}
pub(crate) fn notify_variable_changed(&self, variable_name: &str, value: &ValueType) {
let observers = self.variable_observers.get(variable_name);
if let Some(observers) = observers {
for o in observers.iter() {
o.borrow_mut().changed(variable_name, value);
}
}
}
}