fission-core 0.4.0

Core runtime, state, actions, effects, resources, input, and UI model for Fission
Documentation
use crate::action::{Action, ActionId, GlobalState};
use anyhow::{anyhow, Result};
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::fmt;
use std::sync::{Arc, Mutex};

#[derive(Default)]
pub struct StateMap {
    pub states: HashMap<TypeId, Box<dyn GlobalState>>,
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LocalStateKey {
    component: &'static str,
    field: &'static str,
    key_path: Vec<String>,
    ordinal: usize,
}

impl LocalStateKey {
    pub(crate) fn new_scoped(
        component: &'static str,
        field: &'static str,
        key_path: Vec<String>,
        ordinal: usize,
    ) -> Self {
        Self {
            component,
            field,
            key_path,
            ordinal,
        }
    }

    pub fn component(&self) -> &'static str {
        self.component
    }

    pub fn field(&self) -> &'static str {
        self.field
    }

    pub fn ordinal(&self) -> usize {
        self.ordinal
    }

    pub fn explicit_key(&self) -> Option<&str> {
        self.key_path.last().map(String::as_str)
    }

    pub fn key_path(&self) -> &[String] {
        &self.key_path
    }

    pub fn action_id<A: Action>(&self) -> ActionId {
        ActionId::from_name(&format!(
            "fission.local_state.v1:{}:{}:{}:{}:{}",
            self.component,
            self.field,
            self.key_path.join("/"),
            self.ordinal,
            A::static_id().as_u128()
        ))
    }
}

type BoxedLocalValue = Box<dyn Any + Send + Sync>;

#[derive(Clone, Default)]
pub struct LocalStateStore {
    values: Arc<Mutex<HashMap<LocalStateKey, BoxedLocalValue>>>,
}

impl fmt::Debug for LocalStateStore {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let len = self.values.lock().map(|values| values.len()).unwrap_or(0);
        f.debug_struct("LocalStateStore")
            .field("len", &len)
            .finish()
    }
}

impl GlobalState for LocalStateStore {}

impl LocalStateStore {
    pub fn get_or_insert_with<T>(&self, key: LocalStateKey, make_default: impl FnOnce() -> T) -> T
    where
        T: Clone + Send + Sync + 'static,
    {
        let mut values = self
            .values
            .lock()
            .expect("Fission local widget state store mutex poisoned");
        let entry = values
            .entry(key)
            .or_insert_with(|| Box::new(make_default()) as BoxedLocalValue);
        entry
            .downcast_ref::<T>()
            .unwrap_or_else(|| {
                panic!(
                    "Fission local widget state type mismatch: requested `{}` for an existing field with a different type",
                    std::any::type_name::<T>()
                )
            })
            .clone()
    }

    pub fn update<T>(&self, key: &LocalStateKey, update: impl FnOnce(&mut T)) -> Result<()>
    where
        T: Send + Sync + 'static,
    {
        let mut values = self
            .values
            .lock()
            .map_err(|_| anyhow!("Fission local widget state store mutex poisoned"))?;
        let value = values.get_mut(key).ok_or_else(|| {
            anyhow!(
                "Fission local widget state field `{}` on `{}` was not found",
                key.field,
                key.component
            )
        })?;
        let value = value.downcast_mut::<T>().ok_or_else(|| {
            anyhow!(
                "Fission local widget state type mismatch while updating `{}` on `{}`",
                key.field,
                key.component
            )
        })?;
        update(value);
        Ok(())
    }

    pub fn len(&self) -> usize {
        self.values
            .lock()
            .map(|values| values.len())
            .unwrap_or_default()
    }

    pub(crate) fn retain_active(&self, active: &std::collections::HashSet<LocalStateKey>) {
        let mut values = self
            .values
            .lock()
            .expect("Fission local widget state store mutex poisoned");
        values.retain(|key, _| active.contains(key));
    }
}

#[derive(Clone, Debug)]
pub struct StateField<T> {
    key: LocalStateKey,
    value: T,
}

impl<T> StateField<T>
where
    T: Clone + Send + Sync + 'static,
{
    pub fn new(component: &'static str, field: &'static str, value: T) -> Self {
        Self::new_with(component, field, || value)
    }

    pub fn new_with(
        component: &'static str,
        field: &'static str,
        make_default: impl FnOnce() -> T,
    ) -> Self {
        crate::build::resolve_local_state(component, field, make_default)
    }

    pub(crate) fn resolved(key: LocalStateKey, value: T) -> Self {
        Self { key, value }
    }

    pub fn get(&self) -> T {
        self.value.clone()
    }

    pub fn component(&self) -> &'static str {
        self.key.component()
    }

    pub fn field(&self) -> &'static str {
        self.key.field()
    }

    pub fn key(&self) -> &LocalStateKey {
        &self.key
    }

    pub fn action_id<A: Action>(&self) -> ActionId {
        self.key.action_id::<A>()
    }
}