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/// The selected entity as reported to the page: display data only. The
24/// worker keeps the live selection and hands it to the custom message
25/// handler; nothing here resolves back into an entity.
26#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
27pub struct SelectedEntity {
28    /// Raw entity index without its generation, for display.
29    pub id: u32,
30    /// The entity name, empty when unnamed.
31    pub name: String,
32}
33
34#[derive(Clone, Debug, Serialize, Deserialize)]
35pub enum ToWorker {
36    Init {
37        width: f32,
38        height: f32,
39    },
40    Resize {
41        width: f32,
42        height: f32,
43    },
44    PointerMove {
45        x: f32,
46        y: f32,
47    },
48    PointerButton {
49        button: u8,
50        pressed: bool,
51    },
52    Wheel {
53        delta: f32,
54    },
55    Touch {
56        id: u64,
57        phase: TouchPhase,
58        x: f32,
59        y: f32,
60    },
61    Key {
62        code: String,
63        pressed: bool,
64        text: Option<String>,
65    },
66    Pick {
67        x: f32,
68        y: f32,
69    },
70    Custom(Value),
71}
72
73#[derive(Clone, Debug, Serialize, Deserialize)]
74pub enum FromWorker {
75    Ready { adapter: String },
76    Stats { fps: f32, entity_count: u32 },
77    Selected { detail: Option<SelectedEntity> },
78    Custom(Value),
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    fn to_worker_roundtrips(message: ToWorker) {
86        let value = serde_json::to_value(&message).expect("serialize");
87        let back: ToWorker = serde_json::from_value(value).expect("deserialize");
88        assert_eq!(format!("{message:?}"), format!("{back:?}"));
89    }
90
91    #[test]
92    fn to_worker_survives_the_wire() {
93        to_worker_roundtrips(ToWorker::Init {
94            width: 800.0,
95            height: 600.0,
96        });
97        to_worker_roundtrips(ToWorker::PointerButton {
98            button: 2,
99            pressed: true,
100        });
101        to_worker_roundtrips(ToWorker::Touch {
102            id: 7,
103            phase: TouchPhase::Moved,
104            x: 1.0,
105            y: 2.0,
106        });
107        to_worker_roundtrips(ToWorker::Key {
108            code: "KeyW".to_string(),
109            pressed: true,
110            text: Some("w".to_string()),
111        });
112        to_worker_roundtrips(ToWorker::Custom(serde_json::json!({ "SpawnCube": null })));
113    }
114
115    #[test]
116    fn from_worker_survives_the_wire() {
117        let message = FromWorker::Selected {
118            detail: Some(SelectedEntity {
119                id: 3,
120                name: "Cube 3".to_string(),
121            }),
122        };
123        let value = serde_json::to_value(&message).expect("serialize");
124        let back: FromWorker = serde_json::from_value(value).expect("deserialize");
125        assert_eq!(format!("{message:?}"), format!("{back:?}"));
126    }
127}