Skip to main content

topcoat_runtime/surrogate/
signal.rs

1use std::ops::Deref;
2
3use ref_cast::RefCast;
4use serde::Serialize;
5
6use crate::{
7    Signal, StrSurrogate, Surrogated, impl_surrogate, impl_surrogate_mut, impl_surrogate_ref,
8};
9
10#[derive(RefCast)]
11#[repr(transparent)]
12pub struct SignalSurrogate<T>(Signal<T>);
13
14impl<T> SignalSurrogate<T> {
15    #[inline]
16    pub(crate) const fn new(v: Signal<T>) -> Self {
17        Self(v)
18    }
19}
20
21impl<T> SignalSurrogate<T>
22where
23    for<'b> &'b T: Surrogated,
24{
25    pub fn read(&self) -> <&T as Surrogated>::Surrogate {
26        self.0.read().into_surrogate()
27    }
28}
29
30impl<T> SignalSurrogate<T>
31where
32    T: Surrogated + Clone,
33{
34    pub fn get(&self) -> <T as Surrogated>::Surrogate {
35        self.0.get().into_surrogate()
36    }
37}
38
39impl<T> SignalSurrogate<T>
40where
41    T: Surrogated,
42{
43    /// Writes a new value to the signal.
44    ///
45    /// # Panics
46    ///
47    /// Always panics; signal writes can only occur in client-side expressions.
48    #[track_caller]
49    pub fn set(&self, _v: T::Surrogate) {
50        write_in_browser_only();
51    }
52}
53
54impl SignalSurrogate<bool> {
55    /// Replaces the value with its negation.
56    ///
57    /// # Panics
58    ///
59    /// Always panics; signal writes can only occur in client-side expressions.
60    #[track_caller]
61    pub fn toggle(&self) {
62        write_in_browser_only();
63    }
64}
65
66impl SignalSurrogate<f64> {
67    /// Adds one to the value.
68    ///
69    /// # Panics
70    ///
71    /// Always panics; signal writes can only occur in client-side expressions.
72    #[track_caller]
73    pub fn increment(&self) {
74        write_in_browser_only();
75    }
76
77    /// Subtracts one from the value.
78    ///
79    /// # Panics
80    ///
81    /// Always panics; signal writes can only occur in client-side expressions.
82    #[track_caller]
83    pub fn decrement(&self) {
84        write_in_browser_only();
85    }
86}
87
88impl SignalSurrogate<String> {
89    /// Appends a string to the end of the value.
90    ///
91    /// The argument is anything that dereferences to a string, so both a
92    /// borrowed `&str` and an owned `String` work. The owned form is what an
93    /// event field yields: `Event::target.value` is a `String`, so
94    /// `message.push_str(e.target.value)` is the common call.
95    ///
96    /// # Panics
97    ///
98    /// Always panics; signal writes can only occur in client-side expressions.
99    #[track_caller]
100    pub fn push_str(&self, _s: impl Deref<Target = StrSurrogate>) {
101        write_in_browser_only();
102    }
103}
104
105/// The panic shared by every signal write evaluated on the server.
106#[track_caller]
107fn write_in_browser_only() -> ! {
108    panic!("expressions in which a signal is written to cannot be run server-side");
109}
110
111impl_surrogate!({T} Signal<T>, SignalSurrogate<T>);
112impl_surrogate_ref!({T} Signal<T>, SignalSurrogate<T>);
113impl_surrogate_mut!({T} Signal<T>, SignalSurrogate<T>);
114
115impl<T> Serialize for SignalSurrogate<T> {
116    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
117    where
118        S: serde::Serializer,
119    {
120        #[derive(Serialize)]
121        struct TaggedSignal {
122            t: &'static str,
123            id: std::string::String,
124        }
125
126        TaggedSignal {
127            t: "Signal",
128            id: self.0.id().to_string(),
129        }
130        .serialize(serializer)
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    #[should_panic(expected = "cannot be run server-side")]
140    fn toggle_panics_server_side() {
141        SignalSurrogate::new(Signal::new(false)).toggle();
142    }
143
144    #[test]
145    #[should_panic(expected = "cannot be run server-side")]
146    fn increment_panics_server_side() {
147        SignalSurrogate::new(Signal::new(0.0)).increment();
148    }
149
150    #[test]
151    #[should_panic(expected = "cannot be run server-side")]
152    fn decrement_panics_server_side() {
153        SignalSurrogate::new(Signal::new(0.0)).decrement();
154    }
155
156    #[test]
157    #[should_panic(expected = "cannot be run server-side")]
158    fn push_str_panics_server_side() {
159        SignalSurrogate::new(Signal::new(String::new())).push_str(StrSurrogate::ref_cast(""));
160    }
161}