bloom-core 0.1.2

A react-like framework for descriptive UIs. Renderer Agnostic but built with HTML in mind.
Documentation
use std::{any::Any, collections::HashMap, sync::Arc};

use crate::{
    component::AnyComponent,
    context::ContextMap,
    effect::{Cleanup, Effect},
    Element,
};

pub(crate) struct RenderContext<N> {
    pub(crate) parent: Arc<N>,
    pub(crate) sibling: Option<Arc<N>>,
    pub(crate) context: ContextMap,
}

impl<N> Clone for RenderContext<N> {
    fn clone(&self) -> Self {
        Self {
            parent: self.parent.clone(),
            sibling: self.sibling.clone(),
            context: self.context.clone(),
        }
    }
}

impl<N> RenderContext<N> {
    pub(crate) fn new(parent: Arc<N>, sibling: Option<Arc<N>>, context: ContextMap) -> Self {
        Self {
            parent,
            sibling,
            context,
        }
    }

    pub(crate) fn with_parent(&self, parent: Arc<N>) -> Self {
        Self {
            parent,
            sibling: None,
            context: self.context.clone(),
        }
    }

    pub(crate) fn with_parent_and_sibling(&self, parent: Arc<N>, sibling: Option<Arc<N>>) -> Self {
        Self {
            parent,
            sibling,
            context: self.context.clone(),
        }
    }

    pub(crate) fn with_sibling(&self, sibling: Option<Arc<N>>) -> Self {
        Self {
            parent: self.parent.clone(),
            sibling,
            context: self.context.clone(),
        }
    }

    pub(crate) fn with_context(&self, value: Arc<dyn Any + Send + Sync>) -> Self {
        let mut new_context = self.context.as_ref().clone();
        new_context.insert(value.type_id(), value);
        Self {
            parent: self.parent.clone(),
            sibling: self.sibling.clone(),
            context: Arc::new(new_context),
        }
    }

    pub(crate) fn with_sibling_and_context(
        &self,
        sibling: Option<Arc<N>>,
        value: Arc<dyn Any + Send + Sync>,
    ) -> Self {
        let mut new_context = self.context.as_ref().clone();
        new_context.insert(value.type_id(), value);
        Self {
            parent: self.parent.clone(),
            sibling,
            context: Arc::new(new_context),
        }
    }
}

pub(crate) enum RenderQueueItem<N, E, TN>
where
    N: From<String>,
{
    Create {
        current: *mut TN,
        ctx: RenderContext<N>,
    },
    Reload {
        current: *mut TN,
        ctx: RenderContext<N>,
    },
    Update {
        current: *mut TN,
        next: Element<N, E>,
        ctx: RenderContext<N>,
    },
    Remove {
        current: TN,
        parent: Arc<N>,
    },
}

pub(crate) struct RenderQueue<N, E, TN>
where
    N: From<String>,
{
    queue: Vec<RenderQueueItem<N, E, TN>>,
    effects: HashMap<*const (), Vec<(u64, Effect)>>,
    cleanups: HashMap<*const (), Vec<(u64, Cleanup)>>,
    clear_cleanups: Vec<*const ()>,
}

impl<N, E, TN> RenderQueue<N, E, TN>
where
    N: From<String>,
{
    pub(crate) fn new() -> Self {
        Self {
            queue: Vec::new(),
            effects: HashMap::new(),
            cleanups: HashMap::new(),
            clear_cleanups: Vec::new(),
        }
    }

    pub(crate) fn create(&mut self, current: &mut TN, ctx: RenderContext<N>) {
        self.queue.push(RenderQueueItem::Create {
            current: current as *mut TN,
            ctx,
        })
    }

    pub(crate) fn reload(&mut self, current: &mut TN, ctx: RenderContext<N>) {
        self.queue.push(RenderQueueItem::Reload { current, ctx })
    }

    pub(crate) fn update(&mut self, current: &mut TN, next: Element<N, E>, ctx: RenderContext<N>) {
        self.queue.push(RenderQueueItem::Update {
            current: current as *mut TN,
            next,
            ctx,
        })
    }

    pub(crate) fn remove(&mut self, current: TN, parent: Arc<N>) {
        self.queue.push(RenderQueueItem::Remove { current, parent })
    }

    pub(crate) fn next(&mut self) -> Option<RenderQueueItem<N, E, TN>> {
        self.queue.pop()
    }

    pub(crate) fn queue_effects(
        &mut self,
        component: &Arc<dyn AnyComponent<Node = N, Error = E> + Send + Sync>,
        effects: Vec<(u64, Effect)>,
    ) {
        self.effects.insert(
            component.as_ref() as *const dyn AnyComponent<Node = N, Error = E> as *const (),
            effects,
        );
    }

    pub(crate) fn queue_cleanups(
        &mut self,
        component: &Arc<dyn AnyComponent<Node = N, Error = E> + Send + Sync>,
    ) {
        self.clear_cleanups
            .push(component.as_ref() as *const dyn AnyComponent<Node = N, Error = E> as *const ());
    }

    pub(crate) fn move_cleanups(
        &mut self,
        old_component: &Arc<dyn AnyComponent<Node = N, Error = E> + Send + Sync>,
        new_component: &Arc<dyn AnyComponent<Node = N, Error = E> + Send + Sync>,
    ) {
        if let Some(cleanups) = self.cleanups.remove(
            &(old_component.as_ref() as *const dyn AnyComponent<Node = N, Error = E> as *const ()),
        ) {
            self.cleanups.insert(
                new_component.as_ref() as *const dyn AnyComponent<Node = N, Error = E> as *const (),
                cleanups,
            );
        }
    }

    pub(crate) fn run_effects(&mut self) {
        for component in self.clear_cleanups.drain(..) {
            if let Some(cleanups) = self.cleanups.remove(&component) {
                for (_, cleanup) in cleanups {
                    cleanup.run()
                }
            }
        }

        for (component, effects) in self.effects.drain() {
            let mut next_cleanups = Vec::with_capacity(effects.len());
            if let Some(cleanups) = self.cleanups.remove(&component) {
                for ((effect_hash, effect), (cleanup_hash, cleanup)) in
                    effects.into_iter().zip(cleanups.into_iter())
                {
                    if effect_hash == cleanup_hash {
                        next_cleanups.push((cleanup_hash, cleanup));
                    } else {
                        cleanup.run();
                        next_cleanups.push((effect_hash, effect.run()));
                    }
                }
            } else {
                for (effect_hash, effect) in effects {
                    next_cleanups.push((effect_hash, effect.run()));
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::Arc;

    #[test]
    fn basic_render_queue() {
        struct TreeNode {
            children: Vec<Box<TreeNode>>,
        }

        let mut root = TreeNode {
            children: Vec::new(),
        };

        let mut child = Box::new(TreeNode {
            children: Vec::new(),
        });

        let mut queue = RenderQueue::<String, (), TreeNode>::new();

        queue.reload(
            &mut root,
            RenderContext::new(Arc::new("".to_string()), None, Arc::default()),
        );

        let item = queue.next().unwrap();

        match item {
            RenderQueueItem::Reload { current, .. } => {
                assert_eq!(current as *const _, &root as *const _);

                queue.create(
                    child.as_mut(),
                    RenderContext::new(Arc::new("".to_string()), None, Arc::default()),
                );
                unsafe {
                    (&mut *current).children.push(child);
                }
            }
            _ => panic!("Unexpected item"),
        }

        let item = queue.next().unwrap();

        match item {
            RenderQueueItem::Create { current, .. } => {
                assert_eq!(current as *const _, &*root.children[0] as *const _);
            }
            _ => panic!("Unexpected item"),
        }
    }
}