Skip to main content

nightshade_api/
wire.rs

1//! Leptos-free wire types spoken between a page and its render worker,
2//! compiled under either the `leptos` or the `offscreen` feature. Pixel
3//! quantities are physical surface pixels (CSS pixels times the device pixel
4//! ratio), origin at the canvas top-left. Application-defined messages ride
5//! the `Custom` variants as JSON values.
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10/// Envelope field carrying the serialized message in every `postMessage`.
11pub const MESSAGE_KEY: &str = "message";
12/// Envelope field carrying the transferred `OffscreenCanvas` (on `Init` only).
13pub const CANVAS_KEY: &str = "canvas";
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub enum TouchPhase {
17    Started,
18    Moved,
19    Ended,
20    Cancelled,
21}
22
23#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
24pub struct SelectedEntity {
25    pub id: u32,
26    pub name: String,
27}
28
29#[derive(Clone, Debug, Serialize, Deserialize)]
30pub enum ToWorker {
31    Init {
32        width: f32,
33        height: f32,
34    },
35    Resize {
36        width: f32,
37        height: f32,
38    },
39    PointerMove {
40        x: f32,
41        y: f32,
42    },
43    PointerButton {
44        button: u8,
45        pressed: bool,
46    },
47    Wheel {
48        delta: f32,
49    },
50    Touch {
51        id: u64,
52        phase: TouchPhase,
53        x: f32,
54        y: f32,
55    },
56    Key {
57        code: String,
58        pressed: bool,
59        text: Option<String>,
60    },
61    Pick {
62        x: f32,
63        y: f32,
64    },
65    Custom(Value),
66}
67
68#[derive(Clone, Debug, Serialize, Deserialize)]
69pub enum FromWorker {
70    Ready { adapter: String },
71    Stats { fps: f32, entity_count: u32 },
72    Selected { detail: Option<SelectedEntity> },
73    Custom(Value),
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    fn to_worker_roundtrips(message: ToWorker) {
81        let value = serde_json::to_value(&message).expect("serialize");
82        let back: ToWorker = serde_json::from_value(value).expect("deserialize");
83        assert_eq!(format!("{message:?}"), format!("{back:?}"));
84    }
85
86    #[test]
87    fn to_worker_survives_the_wire() {
88        to_worker_roundtrips(ToWorker::Init {
89            width: 800.0,
90            height: 600.0,
91        });
92        to_worker_roundtrips(ToWorker::PointerButton {
93            button: 2,
94            pressed: true,
95        });
96        to_worker_roundtrips(ToWorker::Touch {
97            id: 7,
98            phase: TouchPhase::Moved,
99            x: 1.0,
100            y: 2.0,
101        });
102        to_worker_roundtrips(ToWorker::Key {
103            code: "KeyW".to_string(),
104            pressed: true,
105            text: Some("w".to_string()),
106        });
107        to_worker_roundtrips(ToWorker::Custom(serde_json::json!({ "SpawnCube": null })));
108    }
109
110    #[test]
111    fn from_worker_survives_the_wire() {
112        let message = FromWorker::Selected {
113            detail: Some(SelectedEntity {
114                id: 3,
115                name: "Cube 3".to_string(),
116            }),
117        };
118        let value = serde_json::to_value(&message).expect("serialize");
119        let back: FromWorker = serde_json::from_value(value).expect("deserialize");
120        assert_eq!(format!("{message:?}"), format!("{back:?}"));
121    }
122}