bladeink/
variables_state.rs

1use std::{
2    cell::RefCell,
3    collections::{HashMap, HashSet},
4    rc::Rc,
5};
6
7use serde_json::Map;
8
9use crate::{
10    callstack::CallStack,
11    json::{json_read, json_write},
12    list_definitions_origin::ListDefinitionsOrigin,
13    state_patch::StatePatch,
14    story_error::StoryError,
15    value::Value,
16    value_type::{ValueType, VariablePointerValue},
17    variable_assigment::VariableAssignment,
18};
19
20#[derive(Clone)]
21pub(crate) struct VariablesState {
22    pub global_variables: HashMap<String, Rc<Value>>,
23    pub default_global_variables: HashMap<String, Rc<Value>>,
24    pub batch_observing_variable_changes: bool,
25    pub callstack: Rc<RefCell<CallStack>>,
26    pub changed_variables_for_batch_obs: Option<HashSet<String>>,
27    pub patch: Option<StatePatch>,
28    list_defs_origin: Rc<ListDefinitionsOrigin>,
29}
30
31impl VariablesState {
32    pub fn new(
33        callstack: Rc<RefCell<CallStack>>,
34        list_defs_origin: Rc<ListDefinitionsOrigin>,
35    ) -> VariablesState {
36        VariablesState {
37            global_variables: HashMap::new(),
38            default_global_variables: HashMap::new(),
39            batch_observing_variable_changes: false,
40            callstack,
41            changed_variables_for_batch_obs: None,
42            patch: None,
43            list_defs_origin,
44        }
45    }
46
47    pub fn start_variable_observation(&mut self) {
48        self.batch_observing_variable_changes = true;
49        self.changed_variables_for_batch_obs = Some(HashSet::new());
50    }
51
52    pub fn complete_variable_observation(&mut self) -> HashMap<String, ValueType> {
53        self.batch_observing_variable_changes = false;
54
55        let mut changed_vars = HashMap::with_capacity(0);
56
57        // Finished observing variables in a batch - now send
58        // notifications for changed variables all in one go.
59        if let Some(changed_variables_for_batch_obs) = self.changed_variables_for_batch_obs.take() {
60            for variable_name in changed_variables_for_batch_obs {
61                let current_value = self.global_variables.get(&variable_name).unwrap();
62
63                changed_vars.insert(variable_name, current_value.value.clone());
64            }
65        }
66
67        // Patch may still be active - e.g. if we were in the middle of a background save
68        if let Some(patch) = &self.patch {
69            for variable_name in patch.changed_variables.iter() {
70                if let Some(patched_val) = patch.get_global(variable_name) {
71                    changed_vars.insert(variable_name.to_string(), patched_val.value.clone());
72                }
73            }
74        }
75
76        changed_vars
77    }
78
79    pub fn snapshot_default_globals(&mut self) {
80        for (k, v) in self.global_variables.iter() {
81            self.default_global_variables.insert(k.clone(), v.clone());
82        }
83    }
84
85    pub fn apply_patch(&mut self) {
86        for (name, value) in self.patch.as_ref().unwrap().globals.iter() {
87            self.global_variables.insert(name.clone(), value.clone());
88        }
89
90        if let Some(changed_variables) = &mut self.changed_variables_for_batch_obs {
91            for name in self.patch.as_ref().unwrap().changed_variables.iter() {
92                changed_variables.insert(name.clone());
93            }
94        }
95
96        self.patch = None;
97    }
98
99    pub fn assign(
100        &mut self,
101        var_ass: &VariableAssignment,
102        value: Rc<Value>,
103    ) -> Result<(), StoryError> {
104        let mut name = var_ass.variable_name.to_string();
105        let mut context_index = -1;
106        let mut set_global;
107
108        // Are we assigning to a global variable?
109        if var_ass.is_new_declaration {
110            set_global = var_ass.is_global;
111        } else {
112            set_global = self.global_variable_exists_with_name(&name);
113        }
114
115        let mut value = value;
116        // Constructing new variable pointer reference
117        if var_ass.is_new_declaration {
118            if let Some(var_pointer) = Value::get_value::<&VariablePointerValue>(value.as_ref()) {
119                value = self.resolve_variable_pointer(var_pointer);
120            }
121        } else {
122            // Assign to an existing variable pointer
123            // Then assign to the variable that the pointer is pointing to by name.
124            // De-reference variable reference to point to
125            loop {
126                let existing_pointer = self.get_raw_variable_with_name(&name, context_index);
127
128                match existing_pointer {
129                    Some(existing_pointer) => {
130                        match Value::get_value::<&VariablePointerValue>(existing_pointer.as_ref()) {
131                            Some(pv) => {
132                                name = pv.variable_name.to_string();
133                                context_index = pv.context_index;
134                                set_global = context_index == 0;
135                            }
136                            None => break,
137                        }
138                    }
139                    None => break,
140                }
141            }
142        }
143
144        if set_global {
145            self.set_global(&name, value);
146        } else {
147            self.callstack.borrow_mut().set_temporary_variable(
148                name,
149                value,
150                var_ass.is_new_declaration,
151                context_index,
152            )?;
153        }
154
155        Ok(())
156    }
157
158    pub fn global_variable_exists_with_name(&self, name: &str) -> bool {
159        self.global_variables.contains_key(name) || self.default_global_variables.contains_key(name)
160    }
161
162    // Given a variable pointer with just the name of the target known, resolve
163    // to a variable
164    // pointer that more specifically points to the exact instance: whether it's
165    // global,
166    // or the exact position of a temporary on the callstack.
167    fn resolve_variable_pointer(&self, var_pointer: &VariablePointerValue) -> Rc<Value> {
168        let mut context_index = var_pointer.context_index;
169        if context_index == -1 {
170            context_index = self.get_context_index_of_variable_named(&var_pointer.variable_name);
171        }
172
173        let value_of_variable_pointed_to =
174            self.get_raw_variable_with_name(&var_pointer.variable_name, context_index);
175        // Extra layer of indirection:
176        // When accessing a pointer to a pointer (e.g. when calling nested or
177        // recursive functions that take a variable references, ensure we don't
178        // create
179        // a chain of indirection by just returning the final target.
180        if let Some(value_of_variable_pointed_to) = value_of_variable_pointed_to {
181            if Value::get_value::<&VariablePointerValue>(value_of_variable_pointed_to.as_ref())
182                .is_some()
183            {
184                return value_of_variable_pointed_to;
185            }
186        }
187
188        Rc::new(Value::new_variable_pointer(
189            &var_pointer.variable_name,
190            context_index,
191        ))
192    }
193
194    // returns true if the value changed and we should notify variable observers
195    pub fn set(&mut self, variable_name: &str, value_type: ValueType) -> Result<bool, StoryError> {
196        if !self.default_global_variables.contains_key(variable_name) {
197            return Err(StoryError::BadArgument(format!(
198                "Cannot assign to a variable {} that hasn't been declared in the story",
199                variable_name
200            )));
201        }
202
203        let val = Value::new_value_type(value_type);
204
205        let notify = self.set_global(variable_name, Rc::new(val));
206
207        Ok(notify)
208    }
209
210    pub fn get(&self, variable_name: &str) -> Option<ValueType> {
211        if self.patch.is_some() {
212            if let Some(var) = self.patch.as_ref().unwrap().get_global(variable_name) {
213                return Some(var.value.clone());
214            }
215        }
216
217        // Search main dictionary first.
218        // If it's not found, it might be because the story content has changed,
219        // and the original default value hasn't be instantiated.
220        // Should really warn somehow, but it's difficult to see how...!
221        if let Some(var_contents) = self.global_variables.get(variable_name) {
222            return Some(var_contents.value.clone());
223        } else if let Some(var_contents) = self.default_global_variables.get(variable_name) {
224            return Some(var_contents.value.clone());
225        }
226
227        None
228    }
229
230    // Make copy of the variable pointer so we're not using the value direct
231    // from
232    // the runtime. Temporary must be local to the current scope.
233    // 0 if named variable is global
234    // 1+ if named variable is a temporary in a particular call stack element
235    fn get_context_index_of_variable_named(&self, var_name: &str) -> i32 {
236        if self.global_variable_exists_with_name(var_name) {
237            return 0;
238        }
239
240        return self.callstack.borrow().get_current_element_index();
241    }
242
243    fn get_raw_variable_with_name(&self, name: &str, context_index: i32) -> Option<Rc<Value>> {
244        // 0 context = global
245        if context_index == 0 || context_index == -1 {
246            if let Some(patch) = &self.patch {
247                if let Some(global) = patch.get_global(name) {
248                    return Some(global);
249                }
250            }
251
252            if let Some(global) = self.global_variables.get(name) {
253                return Some(global.clone());
254            }
255
256            // Getting variables can actually happen during globals set up since you can do
257            // VAR x = A_LIST_ITEM
258            // So _default_global_variables may be None.
259            // We need to do this check though in case a new global is added, so we need to
260            // revert to the default globals dictionary since an initial value hasn't yet
261            // been set.
262
263            if let Some(default_global) = self.default_global_variables.get(name) {
264                return Some(default_global.clone());
265            }
266
267            if let Some(list_item_value) =
268                self.list_defs_origin.find_single_item_list_with_name(name)
269            {
270                return Some(list_item_value.clone());
271            }
272        }
273
274        // Temporary
275        let var_value = self
276            .callstack
277            .borrow()
278            .get_temporary_variable_with_name(name, context_index);
279
280        var_value
281    }
282
283    // Returns true if global var has changed and we need to notify observers
284    fn set_global(&mut self, name: &str, value: Rc<Value>) -> bool {
285        let mut old_value: Option<Rc<Value>> = None;
286
287        if let Some(patch) = &self.patch {
288            old_value = patch.get_global(name);
289        }
290
291        if old_value.is_none() {
292            old_value = self.global_variables.get(name).cloned();
293        }
294
295        if let Some(old_value) = &old_value {
296            Value::retain_list_origins_for_assignment(old_value.as_ref(), value.as_ref());
297        }
298
299        if let Some(patch) = &mut self.patch {
300            patch.set_global(name, value.clone());
301        } else {
302            self.global_variables
303                .insert(name.to_string(), value.clone());
304        }
305
306        if old_value.is_none() || !Rc::ptr_eq(old_value.as_ref().unwrap(), &value) {
307            if self.batch_observing_variable_changes {
308                if let Some(patch) = &mut self.patch {
309                    patch.add_changed_variable(name);
310                } else if let Some(changed_variables) = &mut self.changed_variables_for_batch_obs {
311                    changed_variables.insert(name.to_string());
312                }
313            } else {
314                return true;
315            }
316        }
317
318        false
319    }
320
321    pub fn get_variable_with_name(&self, name: &str, context_index: i32) -> Option<Rc<Value>> {
322        let var_value = self.get_raw_variable_with_name(name, context_index);
323        // Get value from pointer?
324        if let Some(vv) = var_value.clone() {
325            if let Some(var_pointer) = Value::get_value::<&VariablePointerValue>(vv.as_ref()) {
326                return self.value_at_variable_pointer(var_pointer);
327            }
328        }
329
330        var_value
331    }
332
333    fn value_at_variable_pointer(&self, pointer: &VariablePointerValue) -> Option<Rc<Value>> {
334        self.get_variable_with_name(&pointer.variable_name, pointer.context_index)
335    }
336
337    pub fn set_callstack(&mut self, callstack: Rc<RefCell<CallStack>>) {
338        self.callstack = callstack;
339    }
340
341    pub(crate) fn write_json(&self) -> Result<serde_json::Value, StoryError> {
342        let mut jobj: Map<String, serde_json::Value> = Map::new();
343
344        for (name, val) in self.global_variables.iter() {
345            // Don't write out values that are the same as the default global values
346            let default_val = self.default_global_variables.get(name);
347            if let Some(default_val) = default_val {
348                if self.val_equal(val, default_val) {
349                    continue;
350                }
351            }
352
353            jobj.insert(name.clone(), json_write::write_rtobject(val.clone())?);
354        }
355
356        Ok(serde_json::Value::Object(jobj))
357    }
358
359    fn val_equal(&self, val: &Value, default_val: &Value) -> bool {
360        match &val.value {
361            ValueType::Bool(val) => match default_val.value {
362                ValueType::Bool(default_val) => *val == default_val,
363                _ => false,
364            },
365            ValueType::Int(val) => match default_val.value {
366                ValueType::Int(default_val) => *val == default_val,
367                _ => false,
368            },
369            ValueType::Float(val) => match default_val.value {
370                ValueType::Float(default_val) => *val == default_val,
371                _ => false,
372            },
373            ValueType::List(val) => match &default_val.value {
374                ValueType::List(default_val) => *val == *default_val,
375                _ => false,
376            },
377            ValueType::String(val) => match &default_val.value {
378                ValueType::String(default_val) => val.string.eq(&default_val.string),
379                _ => false,
380            },
381            ValueType::DivertTarget(val) => match &default_val.value {
382                ValueType::DivertTarget(default_val) => *val == *default_val,
383                _ => false,
384            },
385            ValueType::VariablePointer(val) => match &default_val.value {
386                ValueType::VariablePointer(default_val) => *val == *default_val,
387                _ => false,
388            },
389        }
390    }
391
392    pub(crate) fn load_json(
393        &mut self,
394        jobj: &Map<String, serde_json::Value>,
395    ) -> Result<(), StoryError> {
396        self.global_variables.clear();
397
398        for (k, v) in self.default_global_variables.iter() {
399            let loaded_token = jobj.get(k);
400
401            if let Some(loaded_token) = loaded_token {
402                self.global_variables.insert(
403                    k.to_string(),
404                    json_read::jtoken_to_runtime_object(loaded_token, None)?
405                        .into_any()
406                        .downcast::<Value>()
407                        .unwrap(),
408                );
409            } else {
410                self.global_variables.insert(k.clone(), v.clone());
411            }
412        }
413
414        Ok(())
415    }
416}