on 00a42e3 help sign in
dodrio/src/cached.rs
163 lines 4.9 kB history raw
1use crate::{Node, Render};
2use bumpalo::Bump;
3use std::cell::RefCell;
4use std::mem;
5use std::ops::{Deref, DerefMut};
7// TODO: This implementation will only cache the rendering (generation of the
8// virtual DOM) but not the diffing of the cached subtree. We could skip diffing
9// for cached tree by adding `fn is_cached(&self) -> bool` to `Node` that we can
10// check during diffing. This comes at the cost of bloating `Node`'s size (since
11// we don't have any padding to sneak an extra `bool` field into). We should
12// investigate whether it is worth adding this or not.
14/// A renderable that supports caching for when rendering is expensive but can
15/// generate the same DOM tree.
16#[derive(Debug)]
17pub struct Cached<R> {
18 inner: R,
19 bump: bumpalo::Bump,
20 // Actually a self-reference into `self.bump`. Safe because we ensure that
21 // whenever we hand out a cached node, we use a lifetime that cannot outlive
22 // its owning `Cached<R>`.
23 cached: RefCell<Option<Node<'static>>>,
26impl<R> Cached<R> {
27 /// Construct a new `Cached<R>` of an inner `R`.
28 ///
29 /// # Example
30 ///
31 /// ```
32 /// use bumpalo::Bump;
33 /// use dodrio::{Cached, Node, Render};
34 ///
35 /// pub struct Counter {
36 /// count: u32,
37 /// }
38 ///
39 /// impl Render for Counter {
40 /// fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
41 /// where
42 /// 'a: 'bump
43 /// {
44 /// // ...
45 /// # unimplemented!()
46 /// }
47 /// }
48 ///
49 /// // Create a render-able counter.
50 /// let counter = Counter { count: 0 };
51 ///
52 /// // And cache its rendering!
53 /// let cached_counter = Cached::new(counter);
54 /// ```
55 #[inline]
56 pub fn new(inner: R) -> Cached<R> {
57 let bump = Bump::new();
58 let cached = RefCell::new(None);
59 Cached {
60 inner,
61 bump,
62 cached,
63 }
64 }
66 /// Invalidate the cached rendering.
67 ///
68 /// This method should be called whenever the inner `R` must be re-rendered,
69 /// and the cached `Node` from the last time `R::render` was invoked can no
70 /// longer be re-used.
71 ///
72 /// # Example
73 ///
74 /// The `Cached<Hello>` component must have its cache invalidated whenever
75 /// the `who` string is changed, or else the cached rendering whill keep
76 /// displaying greetings to old `who`s.
77 ///
78 /// ```
79 /// use bumpalo::Bump;
80 /// use dodrio::{Cached, Node, Render};
81 ///
82 /// /// A component that renders to "<p>Hello, {who}!</p>"
83 /// pub struct Hello {
84 /// who: String
85 /// }
86 ///
87 /// impl Render for Hello {
88 /// fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
89 /// where
90 /// 'a: 'bump,
91 /// {
92 /// use dodrio::builder::*;
93 /// p(bump)
94 /// .children([text("Hello, "), text(&self.who), text("!")])
95 /// .finish()
96 /// }
97 /// }
98 ///
99 /// /// Whenever a `Cached<Hello>`'s `who` is updated, we need to invalidate the
100 /// /// cache so that we don't keep displaying greetings to old `who`s.
101 /// pub fn set_who(hello: &mut Cached<Hello>, who: String) {
102 /// hello.who = who;
103 /// Cached::invalidate(hello);
104 /// }
105 /// ```
106 #[inline]
107 pub fn invalidate(cached: &mut Self) {
108 *cached.cached.borrow_mut() = None;
109 }
111 /// Convert a `Cached<R>` back into a plain `R`.
112 #[inline]
113 pub fn into_inner(cached: Cached<R>) -> R {
114 cached.inner
115 }
118impl<R> Deref for Cached<R> {
119 type Target = R;
121 fn deref(&self) -> &R {
122 &self.inner
123 }
126impl<R> DerefMut for Cached<R> {
127 fn deref_mut(&mut self) -> &mut R {
128 &mut self.inner
129 }
132unsafe fn extend_node_lifetime<'a>(node: Node<'a>) -> Node<'static> {
133 mem::transmute(node)
136impl<R: Render> Render for Cached<R> {
137 fn render<'a, 'bump>(&'a self, _: &'bump Bump) -> Node<'bump>
138 where
139 'a: 'bump,
140 {
141 let mut cached = self.cached.borrow_mut();
143 if let Some(cached) = cached.as_ref() {
144 // The cached node is actually a self-reference, so it has the `'a`
145 // lifetime.
146 let cached: Node<'a> = cached.clone();
148 // But the `'a` lifetime outlives `'bump`, so we can safely convert
149 // it to the narrower `'bump` lifetime.
150 let cached: Node<'bump> = cached;
152 // Return the cached rendering!
153 return cached;
154 }
156 // We don't have a cached node. Render into our own `Bump`, cache it for
157 // future renders, and return it. Same lifetimes as above.
158 let node: Node<'a> = self.inner.render(&self.bump);
159 *cached = Some(unsafe { extend_node_lifetime(node.clone()) });
160 let node: Node<'bump> = node;
161 node
162 }