Skip to main content

fission_core/
state.rs

1use crate::action::{Action, ActionId, GlobalState};
2use anyhow::{anyhow, Result};
3use std::any::{Any, TypeId};
4use std::collections::HashMap;
5use std::fmt;
6use std::sync::{Arc, Mutex};
7
8#[derive(Default)]
9pub struct StateMap {
10    pub states: HashMap<TypeId, Box<dyn GlobalState>>,
11}
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash)]
14pub struct LocalStateKey {
15    component: &'static str,
16    field: &'static str,
17    key_path: Vec<String>,
18    ordinal: usize,
19}
20
21impl LocalStateKey {
22    pub(crate) fn new_scoped(
23        component: &'static str,
24        field: &'static str,
25        key_path: Vec<String>,
26        ordinal: usize,
27    ) -> Self {
28        Self {
29            component,
30            field,
31            key_path,
32            ordinal,
33        }
34    }
35
36    pub fn component(&self) -> &'static str {
37        self.component
38    }
39
40    pub fn field(&self) -> &'static str {
41        self.field
42    }
43
44    pub fn ordinal(&self) -> usize {
45        self.ordinal
46    }
47
48    pub fn explicit_key(&self) -> Option<&str> {
49        self.key_path.last().map(String::as_str)
50    }
51
52    pub fn key_path(&self) -> &[String] {
53        &self.key_path
54    }
55
56    pub fn action_id<A: Action>(&self) -> ActionId {
57        ActionId::from_name(&format!(
58            "fission.local_state.v1:{}:{}:{}:{}:{}",
59            self.component,
60            self.field,
61            self.key_path.join("/"),
62            self.ordinal,
63            A::static_id().as_u128()
64        ))
65    }
66}
67
68type BoxedLocalValue = Box<dyn Any + Send + Sync>;
69
70#[derive(Clone, Default)]
71pub struct LocalStateStore {
72    values: Arc<Mutex<HashMap<LocalStateKey, BoxedLocalValue>>>,
73}
74
75impl fmt::Debug for LocalStateStore {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        let len = self.values.lock().map(|values| values.len()).unwrap_or(0);
78        f.debug_struct("LocalStateStore")
79            .field("len", &len)
80            .finish()
81    }
82}
83
84impl GlobalState for LocalStateStore {}
85
86impl LocalStateStore {
87    pub fn get_or_insert_with<T>(&self, key: LocalStateKey, make_default: impl FnOnce() -> T) -> T
88    where
89        T: Clone + Send + Sync + 'static,
90    {
91        let mut values = self
92            .values
93            .lock()
94            .expect("Fission local widget state store mutex poisoned");
95        let entry = values
96            .entry(key)
97            .or_insert_with(|| Box::new(make_default()) as BoxedLocalValue);
98        entry
99            .downcast_ref::<T>()
100            .unwrap_or_else(|| {
101                panic!(
102                    "Fission local widget state type mismatch: requested `{}` for an existing field with a different type",
103                    std::any::type_name::<T>()
104                )
105            })
106            .clone()
107    }
108
109    pub fn update<T>(&self, key: &LocalStateKey, update: impl FnOnce(&mut T)) -> Result<()>
110    where
111        T: Send + Sync + 'static,
112    {
113        let mut values = self
114            .values
115            .lock()
116            .map_err(|_| anyhow!("Fission local widget state store mutex poisoned"))?;
117        let value = values.get_mut(key).ok_or_else(|| {
118            anyhow!(
119                "Fission local widget state field `{}` on `{}` was not found",
120                key.field,
121                key.component
122            )
123        })?;
124        let value = value.downcast_mut::<T>().ok_or_else(|| {
125            anyhow!(
126                "Fission local widget state type mismatch while updating `{}` on `{}`",
127                key.field,
128                key.component
129            )
130        })?;
131        update(value);
132        Ok(())
133    }
134
135    pub fn len(&self) -> usize {
136        self.values
137            .lock()
138            .map(|values| values.len())
139            .unwrap_or_default()
140    }
141
142    pub(crate) fn retain_active(&self, active: &std::collections::HashSet<LocalStateKey>) {
143        let mut values = self
144            .values
145            .lock()
146            .expect("Fission local widget state store mutex poisoned");
147        values.retain(|key, _| active.contains(key));
148    }
149}
150
151#[derive(Clone, Debug)]
152pub struct StateField<T> {
153    key: LocalStateKey,
154    value: T,
155}
156
157impl<T> StateField<T>
158where
159    T: Clone + Send + Sync + 'static,
160{
161    pub fn new(component: &'static str, field: &'static str, value: T) -> Self {
162        Self::new_with(component, field, || value)
163    }
164
165    pub fn new_with(
166        component: &'static str,
167        field: &'static str,
168        make_default: impl FnOnce() -> T,
169    ) -> Self {
170        crate::build::resolve_local_state(component, field, make_default)
171    }
172
173    pub(crate) fn resolved(key: LocalStateKey, value: T) -> Self {
174        Self { key, value }
175    }
176
177    pub fn get(&self) -> T {
178        self.value.clone()
179    }
180
181    pub fn component(&self) -> &'static str {
182        self.key.component()
183    }
184
185    pub fn field(&self) -> &'static str {
186        self.key.field()
187    }
188
189    pub fn key(&self) -> &LocalStateKey {
190        &self.key
191    }
192
193    pub fn action_id<A: Action>(&self) -> ActionId {
194        self.key.action_id::<A>()
195    }
196}