tangled
alpha
login
or
join now
eldridge.cam
/
cartography
0
fork
atom
Trading card city builder game?
0
fork
atom
overview
issues
pulls
pipelines
experimentation and refactoring of client side websocket
eldridge.cam
1 month ago
57207b58
0b1768bb
verified
This commit was signed with the committer's
known signature
.
eldridge.cam
SSH Key Fingerprint:
SHA256:MAgO4sya2MgvdgUjSGKAO0lQ9X2HQp1Jb+x/Tpeeims=
0/0
Waiting for spindle ...
+143
-26
6 changed files
expand all
collapse all
unified
split
Cargo.toml
src
api
ws.rs
app
hooks
mod.rs
use_custom_websocket.rs
use_game_websocket.rs
play.rs
+3
-2
Cargo.toml
···
21
21
serde_json = "1.0.149"
22
22
sqlx = { version = "0.8.6", features = ["runtime-tokio", "postgres"], optional = true }
23
23
tokio = { version = "1.49.0", features = ["macros"] }
24
24
-
tokio-stream = "0.1.18"
24
24
+
tokio-stream = { version = "0.1.18", features = ["sync"] }
25
25
tracing = "0.1.44"
26
26
uuid = { version = "1.20.0", features = ["serde", "v7"] }
27
27
···
31
31
desktop = ["dioxus/desktop", "tokio/rt", "uuid/js"]
32
32
mobile = ["dioxus/mobile", "tokio/rt", "uuid/js"]
33
33
server = ["dioxus/server", "tokio/rt-multi-thread", "dep:sqlx", "dep:axum"]
34
34
-
gloo = ["dep:gloo"]
34
34
+
35
35
+
messagepack = []
+17
-2
src/api/ws.rs
···
18
18
19
19
#[derive(Serialize, Deserialize, Clone, Debug)]
20
20
pub struct ProtocolV1Message<T> {
21
21
-
id: Uuid,
21
21
+
pub id: Uuid,
22
22
#[serde(flatten)]
23
23
-
data: T,
23
23
+
pub data: T,
24
24
+
}
25
25
+
26
26
+
impl<T> ProtocolV1Message<T> {
27
27
+
pub fn new(data: T) -> Self {
28
28
+
Self {
29
29
+
id: Uuid::now_v7(),
30
30
+
data,
31
31
+
}
32
32
+
}
33
33
+
}
34
34
+
35
35
+
impl<T> From<T> for ProtocolV1Message<T> {
36
36
+
fn from(value: T) -> Self {
37
37
+
Self::new(value)
38
38
+
}
24
39
}
25
40
26
41
pub const JSON_PROTOCOL: &str = "v1-json.cartography.app";
+1
src/app/hooks/mod.rs
···
1
1
pub mod use_custom_websocket;
2
2
+
pub mod use_game_websocket;
+92
-19
src/app/hooks/use_custom_websocket.rs
···
1
1
-
use crate::actor::player_socket::{Request, Response};
2
2
-
use crate::api::{self, ws::ProtocolV1Message};
1
1
+
use std::marker::PhantomData;
2
2
+
3
3
use dioxus::fullstack::{get_server_url, WebsocketState};
4
4
use dioxus::prelude::*;
5
5
use futures::stream::{SplitSink, SplitStream};
···
8
8
use gloo::net::websocket::futures::WebSocket;
9
9
#[cfg(feature = "web")]
10
10
use gloo::net::websocket::Message;
11
11
+
use serde::de::DeserializeOwned;
12
12
+
use serde::Serialize;
11
13
use tokio::sync::Mutex;
12
14
13
15
#[cfg(feature = "web")]
···
17
19
receiver: Mutex<SplitStream<WebSocket>>,
18
20
}
19
21
20
20
-
#[derive(Copy, Clone)]
21
21
-
pub struct UseCustomWebsocket {
22
22
+
pub struct UseCustomWebsocket<In, Out, Enc> {
22
23
waker: UseWaker<()>,
23
24
#[cfg(feature = "web")]
24
25
connection: Resource<anyhow::Result<CustomWebSocket>>,
25
26
status: Signal<WebsocketState>,
26
27
status_read: ReadSignal<WebsocketState>,
28
28
+
_pd: PhantomData<(In, Out, Enc)>,
27
29
}
28
30
29
29
-
pub fn use_custom_websocket(path: &'static str) -> UseCustomWebsocket {
31
31
+
pub struct WebsocketSender<In, Enc> {
32
32
+
#[cfg(feature = "web")]
33
33
+
sender: Mutex<SplitSink<WebSocket, Message>>,
34
34
+
status: Signal<WebsocketState>,
35
35
+
status_read: ReadSignal<WebsocketState>,
36
36
+
_pd: PhantomData<(In, Enc)>,
37
37
+
}
38
38
+
39
39
+
pub struct WebsocketReceiver<Out, Enc> {
40
40
+
#[cfg(feature = "web")]
41
41
+
receiver: Mutex<SplitStream<WebSocket>>,
42
42
+
status: Signal<WebsocketState>,
43
43
+
status_read: ReadSignal<WebsocketState>,
44
44
+
_pd: PhantomData<(Out, Enc)>,
45
45
+
}
46
46
+
47
47
+
impl<In, Out, Enc> Copy for UseCustomWebsocket<In, Out, Enc> {}
48
48
+
impl<In, Out, Enc> Clone for UseCustomWebsocket<In, Out, Enc> {
49
49
+
fn clone(&self) -> Self {
50
50
+
*self
51
51
+
}
52
52
+
}
53
53
+
54
54
+
pub fn use_custom_websocket<
55
55
+
In: Serialize + DeserializeOwned,
56
56
+
Out: Serialize + DeserializeOwned,
57
57
+
Enc: Encoding,
58
58
+
>(
59
59
+
path: &'static str,
60
60
+
protocols: &'static [&'static str],
61
61
+
) -> UseCustomWebsocket<In, Out, Enc> {
30
62
let mut waker = use_waker();
31
63
#[cfg(feature = "web")]
32
64
let mut status = use_signal(|| WebsocketState::Connecting);
···
38
70
let connection = use_resource(move || async move {
39
71
let socket = match gloo::net::websocket::futures::WebSocket::open_with_protocols(
40
72
&format!("{}/{}", get_server_url(), path),
41
41
-
&[api::ws::JSON_PROTOCOL, api::ws::MESSAGEPACK_PROTOCOL],
73
73
+
protocols,
42
74
) {
43
75
Ok(socket) => {
44
76
status.set(WebsocketState::Open);
···
68
100
connection,
69
101
status,
70
102
status_read,
103
103
+
_pd: PhantomData,
71
104
}
72
105
}
73
106
74
74
-
impl UseCustomWebsocket {
107
107
+
pub trait Encoding {
108
108
+
#[cfg(feature = "web")]
109
109
+
fn encode<T: Serialize>(message: &T) -> anyhow::Result<Message>;
110
110
+
111
111
+
#[cfg(feature = "web")]
112
112
+
fn decode<T: DeserializeOwned>(message: Message) -> anyhow::Result<T>;
113
113
+
}
114
114
+
115
115
+
#[allow(dead_code)]
116
116
+
pub struct Json;
117
117
+
118
118
+
impl Encoding for Json {
119
119
+
#[cfg(feature = "web")]
120
120
+
fn encode<T: Serialize>(message: &T) -> anyhow::Result<Message> {
121
121
+
Ok(serde_json::to_string(message).map(Message::Text)?)
122
122
+
}
123
123
+
124
124
+
#[cfg(feature = "web")]
125
125
+
fn decode<T: DeserializeOwned>(message: Message) -> anyhow::Result<T> {
126
126
+
match message {
127
127
+
Message::Text(text) => Ok(serde_json::from_str(&text)?),
128
128
+
Message::Bytes(_) => anyhow::bail!("expected text message"),
129
129
+
}
130
130
+
}
131
131
+
}
132
132
+
133
133
+
#[allow(dead_code)]
134
134
+
pub struct MessagePack;
135
135
+
136
136
+
impl Encoding for MessagePack {
137
137
+
#[cfg(feature = "web")]
138
138
+
fn encode<T: Serialize>(message: &T) -> anyhow::Result<Message> {
139
139
+
Ok(rmp_serde::to_vec(message).map(Message::Bytes)?)
140
140
+
}
141
141
+
142
142
+
#[cfg(feature = "web")]
143
143
+
fn decode<T: DeserializeOwned>(message: Message) -> anyhow::Result<T> {
144
144
+
match message {
145
145
+
Message::Bytes(bytes) => Ok(rmp_serde::from_slice(&bytes)?),
146
146
+
Message::Text(_) => anyhow::bail!("expected binary message"),
147
147
+
}
148
148
+
}
149
149
+
}
150
150
+
151
151
+
impl<In: Serialize + DeserializeOwned, Out: Serialize + DeserializeOwned, Enc: Encoding>
152
152
+
UseCustomWebsocket<In, Out, Enc>
153
153
+
{
75
154
#[cfg(not(feature = "web"))]
76
155
pub async fn connect(&self) -> WebsocketState {
77
156
WebsocketState::FailedToConnect
···
90
169
}
91
170
92
171
#[cfg(not(feature = "web"))]
93
93
-
pub async fn send(&self, msg: ProtocolV1Message<Request>) -> anyhow::Result<()> {
172
172
+
pub async fn send(&self, msg: In) -> anyhow::Result<()> {
94
173
Err(anyhow::anyhow!("not implemented on this platform"))
95
174
}
96
175
97
176
#[cfg(not(feature = "web"))]
98
98
-
pub async fn recv(&self) -> anyhow::Result<ProtocolV1Message<Response>> {
177
177
+
pub async fn recv(&self) -> anyhow::Result<Out> {
99
178
Err(anyhow::anyhow!("not implemented on this platform"))
100
179
}
101
180
102
181
#[cfg(feature = "web")]
103
103
-
pub async fn send(&self, msg: ProtocolV1Message<Request>) -> anyhow::Result<()> {
182
182
+
pub async fn send(&self, msg: In) -> anyhow::Result<()> {
104
183
self.connect().await;
105
184
106
185
let connection = self.connection.as_ref();
···
109
188
.ok_or_else(|| anyhow::anyhow!("websocket closed away"))?
110
189
.as_ref()
111
190
.map_err(|err| anyhow::anyhow!("{err}"))?;
112
112
-
113
113
-
let msg = match connection.protocol.as_str() {
114
114
-
api::ws::MESSAGEPACK_PROTOCOL => rmp_serde::to_vec(&msg).map(Message::Bytes)?,
115
115
-
_ => serde_json::to_string(&msg).map(Message::Text)?,
116
116
-
};
117
117
-
191
191
+
let msg = Enc::encode(&msg)?;
118
192
connection.sender.lock().await.send(msg).await?;
119
193
120
194
Ok(())
121
195
}
122
196
123
197
#[cfg(feature = "web")]
124
124
-
pub async fn recv(&self) -> anyhow::Result<ProtocolV1Message<Response>> {
198
198
+
pub async fn recv(&self) -> anyhow::Result<Out> {
125
199
self.connect().await;
126
200
127
201
let connection = self.connection.as_ref();
···
133
207
134
208
let mut recv = connection.receiver.lock().await;
135
209
match recv.next().await {
136
136
-
Some(Ok(Message::Text(text))) => Ok(serde_json::from_str(&text)?),
137
137
-
Some(Ok(Message::Bytes(bytes))) => Ok(rmp_serde::from_read(&*bytes)?),
210
210
+
Some(Ok(msg)) => Ok(Enc::decode(msg)?),
138
211
Some(Err(e)) => Err(e.into()),
139
212
None => anyhow::bail!("closed away"),
140
213
}
+18
src/app/hooks/use_game_websocket.rs
···
1
1
+
use crate::actor::player_socket::{Request, Response};
2
2
+
use crate::api::ws::{self, ProtocolV1Message};
3
3
+
use crate::app::hooks::use_custom_websocket::{self, UseCustomWebsocket, use_custom_websocket};
4
4
+
5
5
+
#[cfg(not(feature = "messagepack"))]
6
6
+
type Encoding = use_custom_websocket::Json;
7
7
+
#[cfg(not(feature = "messagepack"))]
8
8
+
const PROTOCOL: &str = ws::JSON_PROTOCOL;
9
9
+
10
10
+
#[cfg(feature = "messagepack")]
11
11
+
type Encoding = use_custom_websocket::MessagePack;
12
12
+
#[cfg(feature = "messagepack")]
13
13
+
const PROTOCOL: &str = ws::MESSAGEPACK_PROTOCOL;
14
14
+
15
15
+
pub fn use_game_websocket()
16
16
+
-> UseCustomWebsocket<ProtocolV1Message<Request>, ProtocolV1Message<Response>, Encoding> {
17
17
+
use_custom_websocket("play/ws", &[PROTOCOL])
18
18
+
}
+12
-3
src/app/play.rs
···
1
1
-
use crate::app::hooks::use_custom_websocket::use_custom_websocket;
1
1
+
use crate::actor::player_socket::Request;
2
2
+
use crate::app::hooks::use_game_websocket::use_game_websocket;
2
3
use dioxus::prelude::*;
3
4
4
5
#[component]
5
6
pub fn Play() -> Element {
6
6
-
let socket = use_custom_websocket("play/ws");
7
7
+
let socket = use_game_websocket();
8
8
+
9
9
+
use_future(move || async move {
10
10
+
socket
11
11
+
.send(Request::Authenticate("foxfriends".to_owned()).into())
12
12
+
.await
13
13
+
.unwrap();
14
14
+
});
7
15
16
16
+
#[cfg(feature = "web")]
8
17
use_future(move || async move {
9
18
while let Ok(msg) = socket.recv().await {
10
10
-
dbg!(msg);
19
19
+
gloo::console::log!("msg:", format!("{:?}", msg));
11
20
}
12
21
});
13
22