1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use std::cell::{BorrowMutError, RefCell};
use std::fmt::{self, Display};
use std::mem::ManuallyDrop;
use std::rc::{Rc, Weak};

use crate::stateful::{Inner, ShouldRender};
use crate::Html;

/// Error type returned by [`Hook::update`](Hook::update).
#[derive(Debug)]
pub enum UpdateError {
    /// Returned if the state has already been dropped, happens if the attempted
    /// update is applied to a component that has been removed from view.
    StateDropped,

    /// Attempted update while the state is mutably borrowed for another update.
    CycleDetected,
}

impl From<BorrowMutError> for UpdateError {
    fn from(_: BorrowMutError) -> Self {
        UpdateError::CycleDetected
    }
}

impl Display for UpdateError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            UpdateError::StateDropped => f.write_str("Could not update state: State was dropped"),
            UpdateError::CycleDetected => {
                f.write_str("Cycle detected: Attempting to update state during an ongoing update")
            }
        }
    }
}

impl std::error::Error for UpdateError {}

struct HookVTable<S> {
    state: unsafe fn(*const ()) -> Option<*const RefCell<S>>,
    rerender: unsafe fn(&S, *const ()),
    clone: unsafe fn(*const ()) -> *const (),
    drop: unsafe fn(*const ()),
}

pub struct Hook<S: 'static> {
    inner: *const (),
    vtable: &'static HookVTable<S>,
}

impl<S> Hook<S>
where
    S: 'static,
{
    pub(super) fn new<H>(inner: &Rc<Inner<S, H::Product>>) -> Self
    where
        H: Html,
    {
        let inner = Rc::downgrade(inner).into_raw() as *const ();

        Hook {
            inner,
            vtable: &HookVTable {
                state: |inner| unsafe {
                    let inner = inner as *const Inner<S, H::Product>;
                    let weak = ManuallyDrop::new(Weak::from_raw(inner));

                    if weak.strong_count() > 0 {
                        Some(&(*inner).state)
                    } else {
                        None
                    }
                },
                rerender: |state, inner| unsafe {
                    let inner = inner as *const Inner<S, H::Product>;

                    (*inner).rerender(state);
                },
                clone: |inner| {
                    let weak = ManuallyDrop::new(unsafe {
                        Weak::from_raw(inner as *const Inner<S, H::Product>)
                    });

                    Weak::into_raw((*weak).clone()) as *const ()
                },
                drop: |inner| unsafe {
                    Weak::from_raw(inner as *const Inner<S, H::Product>);
                },
            },
        }
    }
}

impl<S> Hook<S>
where
    S: 'static,
{
    pub fn update<R>(&self, mutator: impl FnOnce(&mut S) -> R) -> Result<(), UpdateError>
    where
        R: Into<ShouldRender>,
    {
        let state = unsafe { (self.vtable.state)(self.inner) }.ok_or(UpdateError::StateDropped)?;
        let mut state = unsafe { (*state).try_borrow_mut()? };
        let result = mutator(&mut state);

        if result.into().should_render() {
            unsafe { (self.vtable.rerender)(&state, self.inner) }
        }

        Ok(())
    }
}

impl<S> Clone for Hook<S>
where
    S: 'static,
{
    fn clone(&self) -> Self {
        let inner = unsafe { (self.vtable.clone)(self.inner) };

        Hook {
            inner,
            vtable: self.vtable,
        }
    }
}

impl<S> Drop for Hook<S>
where
    S: 'static,
{
    fn drop(&mut self) {
        unsafe { (self.vtable.drop)(self.inner) }
    }
}