summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2026-01-25 16:16:10 -0500
committerLLLL Colonq <llll@colonq>2026-01-25 16:16:10 -0500
commit836760c3c5f80674def4c0994c6b0d20f08ae148 (patch)
treed3b076004d9b397c26d8adb63733beed6e2aa7a4
parenteaf5f6d02d688856481f8267dff51586f0b715dc (diff)
Networking
-rw-r--r--.dir-locals.el2
-rw-r--r--.gitignore3
-rw-r--r--Cargo.lock268
-rw-r--r--crates/teleia/Cargo.toml3
-rw-r--r--crates/teleia/src/audio.rs63
-rw-r--r--crates/teleia/src/lib.rs5
-rw-r--r--crates/teleia/src/net.rs5
-rw-r--r--crates/teleia/src/net/client.rs2
-rw-r--r--crates/teleia/src/net/fig.rs (renamed from crates/teleia/src/fig.rs)0
-rw-r--r--crates/teleia/src/net/ws.rs10
-rw-r--r--crates/teleia/src/net/ws/client.rs9
-rw-r--r--crates/teleia/src/net/ws/client/native.rs119
-rw-r--r--crates/teleia/src/net/ws/client/wasm.rs (renamed from crates/teleia/src/net/client/wasm.rs)32
-rw-r--r--crates/teleia/src/net/ws/server.rs71
-rw-r--r--crates/teleia/src/save.rs3
-rw-r--r--crates/teleia/src/state.rs47
16 files changed, 548 insertions, 94 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
index 7d26985..9a84ba1 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -2,7 +2,7 @@
((eglot-workspace-configuration .
(:rust-analyzer
( :cargo
- ( :target "wasm32-unknown-unknown"
+ ( ;; :target "wasm32-unknown-unknown"
:targetDir t)
:hover
(:show (:fields 10))))))))
diff --git a/.gitignore b/.gitignore
index caf058f..7011f7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/.direnv
/target
/.cargo
-/dist \ No newline at end of file
+/dist
+/result \ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 2ef0a87..50375a1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -264,6 +264,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
name = "block-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -471,7 +480,7 @@ dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-graphics-types",
- "foreign-types",
+ "foreign-types 0.5.0",
"libc",
]
@@ -553,6 +562,15 @@ dependencies = [
]
[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "crc32fast"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -618,6 +636,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
name = "cursor-icon"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -633,6 +661,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
[[package]]
+name = "data-encoding"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
name = "directories"
version = "6.0.0"
source = "git+https://github.com/lcolonq/directories-rs#164536daab2c07265167715934081cb1942387b0"
@@ -782,6 +826,12 @@ dependencies = [
]
[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
name = "fdeflate"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -830,12 +880,21 @@ dependencies = [
[[package]]
name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared 0.1.1",
+]
+
+[[package]]
+name = "foreign-types"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
- "foreign-types-shared",
+ "foreign-types-shared 0.3.1",
]
[[package]]
@@ -851,11 +910,27 @@ dependencies = [
[[package]]
name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "foreign-types-shared"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
name = "gethostname"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -996,6 +1071,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
+name = "http"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1102,10 +1193,11 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.68"
+version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
+ "once_cell",
"wasm-bindgen",
]
@@ -1133,9 +1225,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.153"
+version = "0.2.179"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
[[package]]
name = "libloading"
@@ -1277,6 +1369,23 @@ dependencies = [
]
[[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
name = "ndk"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1474,6 +1583,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
+name = "openssl"
+version = "0.10.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
+dependencies = [
+ "bitflags 2.6.0",
+ "cfg-if",
+ "foreign-types 0.3.2",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1903,6 +2056,15 @@ dependencies = [
]
[[package]]
+name = "schannel"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1922,6 +2084,29 @@ dependencies = [
]
[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.6.0",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
name = "self_cell"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1966,6 +2151,17 @@ dependencies = [
]
[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2285,6 +2481,7 @@ dependencies = [
"strum",
"tobj",
"tracing-wasm",
+ "tungstenite",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
@@ -2302,6 +2499,19 @@ dependencies = [
]
[[package]]
+name = "tempfile"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
name = "thiserror"
version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2492,6 +2702,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
[[package]]
+name = "tungstenite"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "data-encoding",
+ "http",
+ "httparse",
+ "log",
+ "native-tls",
+ "rand",
+ "sha1",
+ "thiserror 1.0.57",
+ "utf-8",
+]
+
+[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2558,12 +2787,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2852,6 +3093,12 @@ dependencies = [
]
[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
name = "windows-result"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2897,6 +3144,15 @@ dependencies = [
]
[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/crates/teleia/Cargo.toml b/crates/teleia/Cargo.toml
index 66edbc8..18718a4 100644
--- a/crates/teleia/Cargo.toml
+++ b/crates/teleia/Cargo.toml
@@ -48,4 +48,5 @@ env_logger = "*" # configurable logging to stdout
glfw = { git = "https://github.com/lcolonq/glfw-rs", features = ["serde"] } # window management
kira = { version = "=0.9.6", default-features = false, features = ["cpal", "ogg", "wav"] } # audio
directories = { git = "https://github.com/lcolonq/directories-rs" } # standard system directories
-polling = "*" # interface to epoll \ No newline at end of file
+polling = "*" # interface to epoll
+tungstenite = { version = "*", features = ["native-tls"] } # websockets \ No newline at end of file
diff --git a/crates/teleia/src/audio.rs b/crates/teleia/src/audio.rs
index e95a86d..8106c20 100644
--- a/crates/teleia/src/audio.rs
+++ b/crates/teleia/src/audio.rs
@@ -1,6 +1,5 @@
use std::collections::HashMap;
-#[cfg(target_arch = "wasm32")]
use std::sync::{Arc, Mutex};
#[cfg(target_arch = "wasm32")]
@@ -71,7 +70,10 @@ impl Audio {
}
}
- pub fn play(&self, ctx: &Context, looping: Option<(Option<f64>, Option<f64>)>) -> Option<AudioPlayingHandle> {
+ pub fn play(&self,
+ ctx: &mut Context,
+ looping: Option<(Option<f64>, Option<f64>)>
+ ) -> Option<AudioPlayingHandle> {
let source = ctx.audio.create_buffer_source().ok()?;
if let Some(ab) = &*self.buffer.lock().unwrap() {
source.set_buffer(Some(&ab));
@@ -149,24 +151,54 @@ impl Context {
}
#[cfg(not(target_arch = "wasm32"))]
+pub struct AudioPlayingHandle {
+ handle: Arc<Mutex<kira::sound::static_sound::StaticSoundHandle>>
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl AudioPlayingHandle {
+ pub fn stop(&self, _ctx: &Context) {
+ self.handle.lock().unwrap().stop(kira::tween::Tween::default());
+ }
+ pub fn fade_out(&self, _ctx: &Context, time: f32) {
+ self.handle.lock().unwrap().stop(kira::tween::Tween {
+ duration: std::time::Duration::from_secs_f32(time + 0.5),
+ ..Default::default()
+ });
+ }
+}
+
+#[cfg(not(target_arch = "wasm32"))]
pub struct Audio {
data: kira::sound::static_sound::StaticSoundData,
}
#[cfg(not(target_arch = "wasm32"))]
impl Audio {
- pub fn new(_ctx: &Context, bytes: &'static [u8]) -> Self {
+ pub fn new(_ctx: &Context, bytes: &[u8]) -> Self {
Self {
- data: kira::sound::static_sound::StaticSoundData::from_cursor(std::io::Cursor::new(bytes))
+ data: kira::sound::static_sound::StaticSoundData::from_cursor(std::io::Cursor::new(bytes.to_owned()))
.expect("failed to decode audio"),
}
}
+ pub fn from_samples(_ctx: &Context, sample_rate: f32, samples: &[f32]) -> Self {
+ let frames: Vec<kira::Frame> = samples.iter().map(|f| kira::Frame { left: *f, right: *f }).collect();
+ Self {
+ data: kira::sound::static_sound::StaticSoundData {
+ sample_rate: sample_rate as u32,
+ frames: frames.into(),
+ settings: kira::sound::static_sound::StaticSoundSettings::default(),
+ slice: None,
+ },
+ }
+ }
+
pub fn play(
&self,
ctx: &mut Context,
looping: Option<(Option<f64>, Option<f64>)>
- ) -> Result<kira::sound::static_sound::StaticSoundHandle, String>
+ ) -> Option<AudioPlayingHandle>
{
let sd = if let Some((ss, se)) = looping {
let start = if let Some(s) = ss { s } else { 0.0 };
@@ -178,10 +210,7 @@ impl Audio {
} else {
self.data.clone()
};
- match ctx.manager.play(sd) {
- Ok(h) => Ok(h),
- Err(e) => Err(e.to_string()),
- }
+ ctx.manager.play(sd).ok().map(|h| AudioPlayingHandle { handle: Arc::new(Mutex::new(h)) })
}
}
@@ -189,7 +218,7 @@ impl Audio {
pub struct Assets {
pub ctx: Context,
pub audio: HashMap<String, Audio>,
- pub music_handle: Option<kira::sound::static_sound::StaticSoundHandle>,
+ pub music_handle: Option<AudioPlayingHandle>,
}
#[cfg(not(target_arch = "wasm32"))]
@@ -208,29 +237,29 @@ impl Assets {
pub fn play_sfx(&mut self, name: &str) {
if let Some(a) = self.audio.get(name) {
- if let Err(e) = a.play(&mut self.ctx, None) {
- log::warn!("failed to play sound {}: {}", name, e);
+ if a.play(&mut self.ctx, None).is_none() {
+ log::warn!("failed to play sound {}", name);
}
}
}
pub fn is_music_playing(&self) -> bool {
if let Some(mh) = &self.music_handle {
- mh.state() == kira::sound::PlaybackState::Playing
+ mh.handle.lock().unwrap().state() == kira::sound::PlaybackState::Playing
} else { false }
}
pub fn play_music(&mut self, name: &str, start: Option<f64>, end: Option<f64>) {
if let Some(s) = &mut self.music_handle {
- let _ = s.stop(kira::tween::Tween::default());
+ let _ = s.stop(&self.ctx);
}
if let Some(a) = self.audio.get(name) {
match a.play(&mut self.ctx, Some((start, end))) {
- Ok(h) => {
+ Some(h) => {
self.music_handle = Some(h);
},
- Err(e) => {
- log::warn!("failed to play music {}: {}", name, e);
+ _ => {
+ log::warn!("failed to play music {}", name);
}
}
}
diff --git a/crates/teleia/src/lib.rs b/crates/teleia/src/lib.rs
index 81bc617..5cdf777 100644
--- a/crates/teleia/src/lib.rs
+++ b/crates/teleia/src/lib.rs
@@ -1,3 +1,5 @@
+#![feature(try_blocks)]
+
pub mod utils;
pub mod ui;
pub mod context;
@@ -15,9 +17,6 @@ pub mod physics;
pub mod save;
pub mod level2d;
-#[cfg(not(target_arch = "wasm32"))]
-pub mod fig;
-
pub use utils::{erm, install_error_handler, Erm};
pub use audio::AudioPlayback;
pub use simple_eyre::eyre::WrapErr;
diff --git a/crates/teleia/src/net.rs b/crates/teleia/src/net.rs
index b9babe5..0fee34d 100644
--- a/crates/teleia/src/net.rs
+++ b/crates/teleia/src/net.rs
@@ -1 +1,4 @@
-pub mod client;
+pub mod ws;
+
+#[cfg(not(target_arch = "wasm32"))]
+pub mod fig;
diff --git a/crates/teleia/src/net/client.rs b/crates/teleia/src/net/client.rs
deleted file mode 100644
index fcd51b8..0000000
--- a/crates/teleia/src/net/client.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-#[cfg(target_arch = "wasm32")]
-pub mod wasm;
diff --git a/crates/teleia/src/fig.rs b/crates/teleia/src/net/fig.rs
index 9dfbea8..9dfbea8 100644
--- a/crates/teleia/src/fig.rs
+++ b/crates/teleia/src/net/fig.rs
diff --git a/crates/teleia/src/net/ws.rs b/crates/teleia/src/net/ws.rs
new file mode 100644
index 0000000..bedcc29
--- /dev/null
+++ b/crates/teleia/src/net/ws.rs
@@ -0,0 +1,10 @@
+pub mod client;
+
+#[cfg(not(target_arch = "wasm32"))]
+pub mod server;
+
+#[derive(Debug)]
+pub enum Message {
+ Binary(Vec<u8>),
+ Text(String),
+}
diff --git a/crates/teleia/src/net/ws/client.rs b/crates/teleia/src/net/ws/client.rs
new file mode 100644
index 0000000..ebd7681
--- /dev/null
+++ b/crates/teleia/src/net/ws/client.rs
@@ -0,0 +1,9 @@
+#[cfg(target_arch = "wasm32")]
+pub mod wasm;
+#[cfg(target_arch = "wasm32")]
+pub use wasm::*;
+
+#[cfg(not(target_arch = "wasm32"))]
+pub mod native;
+#[cfg(not(target_arch = "wasm32"))]
+pub use native::*;
diff --git a/crates/teleia/src/net/ws/client/native.rs b/crates/teleia/src/net/ws/client/native.rs
new file mode 100644
index 0000000..efefdf2
--- /dev/null
+++ b/crates/teleia/src/net/ws/client/native.rs
@@ -0,0 +1,119 @@
+use std::sync::mpsc::{Sender, Receiver, channel};
+use std::sync::{Arc, Mutex};
+use std::thread::spawn;
+
+use crate::net::ws::Message;
+use crate::{Erm, WrapErr};
+
+const KEY: usize = 42;
+
+pub struct Client {
+ channels: Arc<Mutex<Option<(Sender<Message>, Receiver<Message>)>>>,
+}
+
+impl Client {
+ pub fn new() -> Self {
+ Self {
+ channels: Arc::new(Mutex::new(None)),
+ }
+ }
+ pub fn connect(&mut self, url: &str) {
+ let (mut socket, _resp) = if let Ok(v) = tungstenite::connect(url) { v } else {
+ log::warn!("failed to connect to websocket: {}", url);
+ return;
+ };
+ let poller = match polling::Poller::new() {
+ Ok(v) => v,
+ Err(e) => {
+ log::warn!("failed to create poller: {}", e);
+ return;
+ }
+ };
+ match socket.get_ref() {
+ tungstenite::stream::MaybeTlsStream::Plain(s) => unsafe {
+ if let Err(e) = poller.add(s, polling::Event::readable(KEY)) {
+ log::warn!("failed to add event to poll: {}", e);
+ let _ = socket.close(None);
+ return;
+ };
+ },
+ tungstenite::stream::MaybeTlsStream::NativeTls(s) => unsafe {
+ if let Err(e) = poller.add(s.get_ref(), polling::Event::readable(KEY)) {
+ log::warn!("failed to add event to poll: {}", e);
+ let _ = socket.close(None);
+ return;
+ };
+ },
+ _ => {
+ log::warn!("unknown socket type; cannot poll!");
+ let _ = socket.close(None);
+ return;
+ },
+ }
+ let (outgoing_sender, outgoing_receiver) = channel();
+ let (incoming_sender, incoming_receiver) = channel();
+ *self.channels.lock().unwrap() = Some((outgoing_sender, incoming_receiver));
+ let channels_ref = self.channels.clone();
+ let mut events = polling::Events::new();
+ spawn(move || loop {
+ let res: Erm<()> = try {
+ // send all outgoing messages
+ while let Ok(msg) = outgoing_receiver.try_recv() {
+ match msg {
+ Message::Text(txt) => socket.send(tungstenite::Message::Text(txt))
+ .wrap_err("failed to send websocket text")?,
+ Message::Binary(bytes) => socket.send(tungstenite::Message::Binary(bytes))
+ .wrap_err("failed to send websocket bytes")?,
+ }
+ }
+ // wait until we've received data
+ events.clear();
+ poller.wait(&mut events, None).wrap_err("failed to wait on websocket poller")?;
+ for ev in events.iter() {
+ if ev.key == KEY {
+ if ev.readable { match socket.read().wrap_err("failed to recv on websocket")? {
+ tungstenite::Message::Text(txt) => incoming_sender.send(Message::Text(txt))
+ .wrap_err("failed to send incoming websocket message on channel")?,
+ tungstenite::Message::Binary(bytes) => incoming_sender.send(Message::Binary(bytes))
+ .wrap_err("failed to send incoming websocket message on channel")?,
+ tungstenite::Message::Ping(bs) => socket.send(tungstenite::Message::Pong(bs))
+ .wrap_err("failed to send websocket pong")?,
+ m => log::warn!("unhandled incoming websocket message: {}", m),
+ }}
+ match socket.get_ref() {
+ tungstenite::stream::MaybeTlsStream::Plain(s) =>
+ poller.modify(s, polling::Event::readable(KEY))
+ .wrap_err("failed to modify event to poll")?,
+ tungstenite::stream::MaybeTlsStream::NativeTls(s) =>
+ poller.modify(s.get_ref(), polling::Event::readable(KEY))
+ .wrap_err("failed to modify event to poll")?,
+ _ => log::warn!("unknown socket type; cannot modify polling!"),
+ }
+ }
+ }
+ };
+ if let Err(e) = res {
+ log::warn!("error in websocket thread: {}", e);
+ *channels_ref.lock().unwrap() = None;
+ return;
+ }
+ });
+ }
+ pub fn is_connected(&self) -> bool {
+ self.channels.lock().unwrap().is_some()
+ }
+ pub fn poll(&mut self) -> Option<Message> {
+ if let Some((_, incoming)) = &*self.channels.lock().unwrap() {
+ incoming.try_recv().ok()
+ } else { None }
+ }
+ pub fn send(&self, msg: Message) {
+ if let Some((outgoing, _)) = &mut *self.channels.lock().unwrap() {
+ if let Err(e) = outgoing.send(msg) {
+ log::warn!("failed to send websocket message on channel: {}", e);
+ }
+ } else {
+ log::warn!("tried to send message, but websocket is not connected");
+ }
+ }
+}
diff --git a/crates/teleia/src/net/client/wasm.rs b/crates/teleia/src/net/ws/client/wasm.rs
index d9936e5..a8942c4 100644
--- a/crates/teleia/src/net/client/wasm.rs
+++ b/crates/teleia/src/net/ws/client/wasm.rs
@@ -2,54 +2,60 @@ use std::{collections::VecDeque, sync::{Arc, Mutex}};
use wasm_bindgen::prelude::*;
-#[derive(Debug)]
-pub enum Message {
- Binary(Vec<u8>),
- Text(String),
-}
+use crate::net::ws::Message;
impl Message {
- pub fn from_messageevent(e: web_sys::MessageEvent) -> Self {
+ fn from_messageevent(e: web_sys::MessageEvent) -> Self {
if let Ok(abuf) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
let array = js_sys::Uint8Array::new(&abuf).to_vec();
Message::Binary(array)
} else if let Ok(txt) = e.data().dyn_into::<js_sys::JsString>() {
Message::Text(txt.into())
} else {
- panic!("received weird websocked message: {:?}", e);
+ panic!("received weird websocket message: {:?}", e);
}
}
}
pub struct Client {
- pub ws: Option<web_sys::WebSocket>,
- pub messages: Arc<Mutex<VecDeque<Message>>>,
+ ws: Arc<Mutex<Option<web_sys::WebSocket>>>,
+ messages: Arc<Mutex<VecDeque<Message>>>,
}
impl Client {
pub fn new() -> Self {
Self {
- ws: None,
+ ws: Arc::new(Mutex::new(None)),
messages: Arc::new(Mutex::new(VecDeque::new())),
}
}
pub fn connect(&mut self, url: &str) {
- let ws = web_sys::WebSocket::new(url).expect("failed to open websocket");
+ let ws_ref = self.ws.clone();
let messages_ref = self.messages.clone();
let cb: Closure<dyn Fn(web_sys::MessageEvent)> = Closure::new(move |e: web_sys::MessageEvent| {
let msg = Message::from_messageevent(e);
log::info!("incoming: {:?}", msg);
messages_ref.lock().unwrap().push_back(msg);
});
+ let close_cb: Closure<dyn Fn(web_sys::MessageEvent)> = Closure::new(move |_: web_sys::MessageEvent| {
+ log::info!("closed!");
+ *ws_ref.lock().unwrap() = None;
+ });
+ let ws = web_sys::WebSocket::new(url).expect("failed to open websocket");
ws.set_onmessage(Some(cb.as_ref().unchecked_ref()));
+ ws.set_onclose(Some(close_cb.as_ref().unchecked_ref()));
cb.forget();
- self.ws = Some(ws);
+ close_cb.forget();
+ *self.ws.lock().unwrap() = Some(ws);
+ }
+ pub fn is_connected(&self) -> bool {
+ self.ws.lock().unwrap().is_none()
}
pub fn poll(&mut self) -> Option<Message> {
self.messages.lock().unwrap().pop_front()
}
pub fn send(&self, msg: Message) {
- if let Some(ws) = &self.ws {
+ if let Some(ws) = &*self.ws.lock().unwrap() {
match msg {
Message::Text(txt) => {
if let Err(e) = ws.send_with_str(&txt) {
diff --git a/crates/teleia/src/net/ws/server.rs b/crates/teleia/src/net/ws/server.rs
new file mode 100644
index 0000000..60537a1
--- /dev/null
+++ b/crates/teleia/src/net/ws/server.rs
@@ -0,0 +1,71 @@
+use std::collections::HashMap;
+use std::net::{TcpListener, TcpStream};
+use std::sync::mpsc::{Sender, Receiver, channel};
+use std::sync::{Arc, Mutex};
+use std::thread::spawn;
+
+use crate::net::ws::Message;
+use crate::{Erm, WrapErr};
+
+const ACCEPT_KEY: usize = 1;
+
+type ClientId = usize;
+type ClientMessage = (ClientId, Message);
+
+pub struct Server {
+ clients: Arc<Mutex<HashMap<ClientId, tungstenite::WebSocket<TcpStream>>>>,
+ channels: Arc<Mutex<Option<(Sender<ClientMessage>, Receiver<ClientMessage>)>>>,
+}
+
+impl Server {
+ pub fn new() -> Self {
+ Self {
+ clients: Arc::new(Mutex::new(HashMap::new())),
+ channels: Arc::new(Mutex::new(None)),
+ }
+ }
+ pub fn start(&mut self, addr: &str) {
+ let clients_ref = self.clients.clone();
+ let (outgoing_sender, outgoing_receiver) = channel();
+ let (incoming_sender, incoming_receiver) = channel();
+ *self.channels.lock().unwrap() = Some((outgoing_sender, incoming_receiver));
+ let channels_ref = self.channels.clone();
+ let listener = TcpListener::bind(addr).expect("failed to bind server socket");
+ let poller = polling::Poller::new().expect("failed to create poller for websocket server");
+ unsafe {
+ poller.add(&listener, polling::Event::readable(ACCEPT_KEY))
+ .expect("failed to add poll server socket");
+ }
+ let mut events = polling::Events::new();
+ let mut next_id = ACCEPT_KEY + 1;
+ spawn(move || loop {
+ let res: Erm<()> = try {
+ events.clear();
+ poller.wait(&mut events, None).wrap_err("failed to wait on websocket server poller")?;
+ for ev in events.iter() {
+ if ev.key == ACCEPT_KEY {
+ let (sock, client_addr) = listener.accept().wrap_err("failed to accept on socket")?;
+ let id = next_id; next_id += 1;
+ let res = try {
+ unsafe { poller.add(&sock, polling::Event::readable(id))
+ .wrap_err("failed to add socket to poller")?; }
+ let conn = tungstenite::accept(sock).wrap_err("failed to upgrade to websocket")?;
+ clients_ref.lock().unwrap().insert(id, conn);
+ };
+ if let Err(e) = res {
+ log::warn!("error during connection from {}: {}", client_addr, e);
+ };
+ poller.modify(&listener, polling::Event::readable(ACCEPT_KEY))
+ .wrap_err("failed to modify event to poll")?;
+ } else {
+ }
+ }
+ };
+ if let Err(e) = res {
+ log::warn!("unhandled error in websocket thread, stopping server: {}", e);
+ *channels_ref.lock().unwrap() = None;
+ return;
+ }
+ });
+ }
+}
diff --git a/crates/teleia/src/save.rs b/crates/teleia/src/save.rs
index dea4332..60bbb43 100644
--- a/crates/teleia/src/save.rs
+++ b/crates/teleia/src/save.rs
@@ -1,3 +1,4 @@
+#[cfg(target_arch = "wasm32")]
use base64::prelude::*;
#[cfg(target_arch = "wasm32")]
@@ -32,7 +33,6 @@ pub fn save<W>(id: &str, data: &W) where W: serde::Serialize {
let _ = std::fs::create_dir_all(pd.data_dir());
let path = pd.data_dir().join("teleia.save");
let mut file = std::fs::File::create(&path).expect("failed to open save file");
- // serde_json::to_writer(file, data).expect("failed to write save file");
bincode::serde::encode_into_std_write(data, &mut file, bincode::config::standard())
.expect("failed to write save file");
}
@@ -43,6 +43,5 @@ pub fn load<W>(id: &str) -> Option<W> where W: serde::de::DeserializeOwned {
let _ = std::fs::create_dir_all(pd.data_dir());
let path = pd.data_dir().join("teleia.save");
let mut file = std::fs::File::open(&path).ok()?;
- // serde_json::from_reader(file).ok()
bincode::serde::decode_from_std_read(&mut file, bincode::config::standard()).ok()
}
diff --git a/crates/teleia/src/state.rs b/crates/teleia/src/state.rs
index 76be948..9c288a2 100644
--- a/crates/teleia/src/state.rs
+++ b/crates/teleia/src/state.rs
@@ -9,20 +9,6 @@ use crate::{audio, context, framebuffer, mesh, shader, utils};
const DELTA_TIME: f64 = 0.016; // todo
-// pub struct WinitWaker {}
-// impl WinitWaker {
-// fn new() -> Self { Self {} }
-// }
-// impl std::task::Wake for WinitWaker {
-// fn wake(self: std::sync::Arc<Self>) {}
-// }
-
-// pub struct Response {
-// pub url: String,
-// pub status: reqwest::StatusCode,
-// pub body: bytes::Bytes,
-// }
-
pub trait Game {
fn initialize(&self, ctx: &context::Context, st: &State) -> utils::Erm<()> { Ok(()) }
fn finalize(&self, ctx: &context::Context, st: &State) -> utils::Erm<()> { Ok(()) }
@@ -35,7 +21,6 @@ pub trait Game {
fn finish_title(&mut self, st: &mut State) {}
fn mouse_move(&mut self, ctx: &context::Context, st: &mut State, x: i32, y: i32) {}
fn mouse_press(&mut self, ctx: &context::Context, st: &mut State) {}
- // fn request_return(&mut self, ctx: &context::Context, st: &mut State, res: Response) {}
fn update(&mut self, ctx: &context::Context, st: &mut State) -> utils::Erm<()> { Ok(()) }
fn render(&mut self, ctx: &context::Context, st: &mut State) -> utils::Erm<()> { Ok(()) }
}
@@ -188,10 +173,6 @@ pub struct State {
pub lighting: (glam::Vec3, glam::Vec3, glam::Vec3),
pub point_lights: Vec<PointLight>,
- // pub waker_ctx: std::task::Context<'static>,
- // pub http_client: reqwest::Client,
- // pub request: Option<std::pin::Pin<Box<dyn std::future::Future<Output = reqwest::Result<Response>>>>>,
-
pub log: Vec<(u64, String)>,
}
@@ -258,10 +239,6 @@ impl State {
);
let mesh_square = mesh::Mesh::from_obj(ctx, include_bytes!("assets/meshes/square.obj"));
- // let waker = std::sync::Arc::new(WinitWaker::new());
- // let cwaker = Box::leak(Box::new(waker.into()));
- // let waker_ctx = std::task::Context::from_waker(cwaker);
-
let nextframe = now(ctx);
Self {
@@ -521,30 +498,6 @@ impl State {
self.rebinding = Some(k);
}
- // pub fn request<F>(&mut self, f: F)
- // where F: Fn(&reqwest::Client) -> reqwest::RequestBuilder
- // {
- // let builder = f(&self.http_client);
- // let fut = async {
- // let resp = builder.send().await?;
- // let url = resp.url().clone().to_string();
- // let status = resp.status().clone();
- // let body = resp.bytes().await?;
- // reqwest::Result::Ok(Response {
- // url,
- // status,
- // body,
- // })
- // };
- // self.request = Some(Box::pin(fut));
- // }
- // pub fn requesting(&self) -> bool { self.request.is_some() }
- // pub fn request_returned<G>(&mut self, ctx: &context::Context, game: &mut G, res: Response)
- // where G: Game
- // {
- // game.request_return(ctx, self, res);
- // }
-
pub fn run_update<G>(&mut self, ctx: &context::Context, game: &mut G) -> utils::Erm<()> where G: Game {
let now = now(ctx);
if now > self.nextframe {