From 2256dfd8c5a4f429e15cc8f33ba6cadd6b203f65 Mon Sep 17 00:00:00 2001 From: core Date: Sun, 7 Jan 2024 19:41:33 -0500 Subject: [PATCH] start of bevy_tungstenite_stk, maybe fix loading --- Cargo.lock | 52 ++++- Cargo.toml | 3 +- bevy_tungstenite_stk/Cargo.toml | 11 + bevy_tungstenite_stk/src/lib.rs | 192 ++++++++++++++++++ server/Cargo.toml | 1 + server/src/main.rs | 101 +++++---- server/src/packet.rs | 35 ++++ starkingdoms-backplane/src/main.rs | 2 +- .../src/components/ui/Checkbox.svelte | 1 - 9 files changed, 348 insertions(+), 50 deletions(-) create mode 100644 bevy_tungstenite_stk/Cargo.toml create mode 100644 bevy_tungstenite_stk/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 434724268d03cd2cd96daba2621611ce061cadb1..809ca4b7ad96aaa59b28e2ac24d8c32f77ce957a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,7 +108,7 @@ dependencies = [ "flate2", "futures-core", "h2", - "http", + "http 0.2.11", "httparse", "httpdate", "itoa", @@ -143,7 +143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" dependencies = [ "bytestring", - "http", + "http 0.2.11", "regex", "serde", "tracing", @@ -1057,6 +1057,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bevy_tungstenite_stk" +version = "0.1.0" +dependencies = [ + "bevy", + "crossbeam-channel", + "tungstenite", +] + [[package]] name = "bevy_twite" version = "1.0.0" @@ -2117,7 +2126,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", "indexmap 2.1.0", "slab", "tokio", @@ -2205,6 +2214,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "httparse" version = "1.8.0" @@ -3598,6 +3618,7 @@ version = "0.0.1" dependencies = [ "bevy", "bevy_rapier2d", + "bevy_tungstenite_stk", "bevy_twite", "hex", "hmac", @@ -4010,6 +4031,25 @@ dependencies = [ "cc", ] +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.0.0", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "twite" version = "0.1.0" @@ -4071,6 +4111,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uuid" version = "1.6.1" diff --git a/Cargo.toml b/Cargo.toml index 8c0120b642a5889422b1aba4dd3525bae927abfb..a5ad54a397a686ab77eeb0d32333a298dd443c69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,8 @@ members = [ "server", "starkingdoms-backplane", "starkingdoms-common", - "savefile_decoder" + "savefile_decoder", + "bevy_tungstenite_stk" ] resolver = "2" diff --git a/bevy_tungstenite_stk/Cargo.toml b/bevy_tungstenite_stk/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..25fb8e2b9dd9c37ad2dd10736787f67363f39a1e --- /dev/null +++ b/bevy_tungstenite_stk/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bevy_tungstenite_stk" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bevy = { version = "0.12", default-features = false } +crossbeam-channel = "0.5" +tungstenite = "0.21" \ No newline at end of file diff --git a/bevy_tungstenite_stk/src/lib.rs b/bevy_tungstenite_stk/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..39b34f1abff3dd051a1b8d7ea88455db6680a78e --- /dev/null +++ b/bevy_tungstenite_stk/src/lib.rs @@ -0,0 +1,192 @@ +use bevy::app::{App, Plugin, PostUpdate, Startup}; +use bevy::ecs::event::ManualEventReader; +use bevy::log::warn; +use bevy::prelude::{Commands, Event, Events, Local, Res, ResMut, Resource}; +use crossbeam_channel::{unbounded, Receiver, Sender}; +use std::collections::HashMap; +use std::net::{IpAddr, SocketAddr, TcpListener}; +use std::sync::{Arc, RwLock}; +use std::thread; +use tungstenite::protocol::frame::coding::OpCode; +use tungstenite::protocol::Role; +use tungstenite::{Message, WebSocket}; + +pub use tungstenite; + +pub struct StkTungsteniteServerPlugin; + +impl Plugin for StkTungsteniteServerPlugin { + fn build(&self, app: &mut App) { + app.add_event::(); + app.add_systems(Startup, Self::init_server); + app.add_systems(PostUpdate, Self::event_listener); + } +} + +#[derive(Event, Clone, Debug)] +pub enum WsEvent { + Connection { from: SocketAddr }, + Close { addr: SocketAddr }, + Send { to: SocketAddr, message: Message }, + Recv { from: SocketAddr, message: Message }, + Broadcast { message: Message }, +} + +#[derive(Resource, Clone)] +pub struct Clients(Arc>>>); + +#[derive(Resource)] +pub struct StkTungsteniteServerConfig { + pub addr: IpAddr, + pub port: u16, +} + +#[derive(Resource)] +pub struct Rx(Receiver); + +#[derive(Resource)] +pub struct Tx(Sender); + +impl StkTungsteniteServerPlugin { + pub fn init_server(config: Res, mut commands: Commands) { + let listener = TcpListener::bind(SocketAddr::from((config.addr, config.port))) + .expect("Failed to bind"); + + let (tx, rx) = unbounded::(); + + let clients = Clients(Arc::new(RwLock::new(HashMap::new()))); + + commands.insert_resource(clients.clone()); + commands.insert_resource(Rx(rx.clone())); + commands.insert_resource(Tx(tx.clone())); + + let clients = clients.0.clone(); + + thread::spawn(move || { + loop { + let (stream, this_addr) = listener + .accept() + .expect("failed to accept incoming connection"); + + let upgraded = match tungstenite::accept(stream) { + Ok(up) => up, + Err(e) => { + warn!("error upgrading {}: {}", this_addr, e); + continue; + } + }; + + let (ltx, lrx) = std::sync::mpsc::channel(); + + { + // Lock block + let mut handle = clients.write().expect("failed to lock clients map"); + handle.insert(this_addr, ltx); + } // unlocked here + + tx.send(WsEvent::Connection { from: this_addr }) + .expect("failed to send event across channel"); + + thread::spawn({ + let fd_ref = upgraded + .get_ref() + .try_clone() + .expect("failed to clone tcpstream"); + let mut l_stream = WebSocket::from_raw_socket(fd_ref, Role::Server, None); + let l_gtx = tx.clone(); + move || { + for event in lrx.iter() { + match event { + WsEvent::Send { to, message } => { + if to == this_addr { + match l_stream.send(message) { + Ok(_) => (), + Err(e) => match e { + tungstenite::Error::AlreadyClosed + | tungstenite::Error::ConnectionClosed => { + l_gtx + .send(WsEvent::Close { addr: this_addr }) + .expect("failed to send on stream"); + } + e => Err(e).unwrap(), + }, + } + } + } + WsEvent::Close { addr } => { + if addr == this_addr { + l_stream.close(None).expect("failed to disconnect client"); + break; + } + } + _ => {} + } + } + } + }); + + thread::spawn({ + let fd_ref = upgraded + .get_ref() + .try_clone() + .expect("failed to clone tcpstream"); + let mut l_stream = WebSocket::from_raw_socket(fd_ref, Role::Server, None); + let l_gtx = tx.clone(); + move || loop { + let msg = l_stream.read().expect("failed to read message from stream"); + if let Message::Close(_) = msg { + l_stream.close(None).expect("failed to disconnect client"); + l_gtx.send(WsEvent::Close { addr: this_addr }).unwrap(); + break; + } + l_gtx + .send(WsEvent::Recv { + from: this_addr, + message: msg, + }) + .unwrap(); + } + }); + } + }); + } + + pub fn event_listener( + clients: Res, + game_receiver: Res>, + mut event_reader: Local>, + mut events: ResMut>, + ) { + let mut clients = clients.0.write().unwrap(); + for event in event_reader.read(&events) { + match event { + WsEvent::Send { to, .. } => { + if let Some(client) = clients.get_mut(to) { + client.send(event.clone()).expect("failed to forward event"); + } + } + WsEvent::Close { addr } => { + if let Some(client) = clients.get_mut(addr) { + client.send(event.clone()).expect("failed to forward event"); + } + clients.remove(addr); + } + WsEvent::Broadcast { message } => { + for (addr, client) in clients.iter() { + client + .send(WsEvent::Send { + to: *addr, + message: message.clone(), + }) + .expect("failed to forward event"); + } + } + _ => {} + } + } + + if let Ok(event) = game_receiver.0.try_recv() { + events.send(event); + } + } +} diff --git a/server/Cargo.toml b/server/Cargo.toml index 6788ba374de1fb8fa1547d1705f44fb5a0b2bbbb..6fed1a3d170c5a8e76dcbb44bceb8f1f21596de1 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -17,6 +17,7 @@ jwt = "0.16" sha2 = "0.10" hmac = "0.12" hex = "0.4" +bevy_tungstenite_stk = { version = "0.1", path = "../bevy_tungstenite_stk" } [features] default = [] diff --git a/server/src/main.rs b/server/src/main.rs index 5ac963153e167f2f20962ef130ce11209312f453..02af4756c4ad454ee9602732bd90e9039e1eafb5 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -271,27 +271,27 @@ fn on_message( 0.0, ); transform.rotate_z(angle); - let mut entity_id = commands - .spawn(PlayerBundle { - part: PartBundle { - part_type: PartType::Hearty, - transform: TransformBundle::from(transform), - flags: PartFlags { attached: false }, - }, - player: Player { - addr: *addr, - username: username.to_string(), - input: component::Input::default(), - selected: None, - save_eligibility: false, - }, - attach: Attach { - associated_player: None, - parent: None, - children: [None, None, None, None], - }, - }); - entity_id.insert(Collider::cuboid( + let mut entity_id = commands.spawn(PlayerBundle { + part: PartBundle { + part_type: PartType::Hearty, + transform: TransformBundle::from(transform), + flags: PartFlags { attached: false }, + }, + player: Player { + addr: *addr, + username: username.to_string(), + input: component::Input::default(), + selected: None, + save_eligibility: false, + }, + attach: Attach { + associated_player: None, + parent: None, + children: [None, None, None, None], + }, + }); + entity_id + .insert(Collider::cuboid( PART_HALF_SIZE / SCALE, PART_HALF_SIZE / SCALE, )) @@ -980,8 +980,9 @@ fn load_savefile( offset = Vec2::new(-53., -53.); angle_offset = -PI / 2.; } - let mut module = commands - .spawn(PartBundle { + + let module_id = { + let module = commands.spawn(PartBundle { transform: TransformBundle::from( Transform::from_xyz( p_pos.x + offset.x / SCALE * angle.cos(), @@ -998,8 +999,35 @@ fn load_savefile( part_type: child.part_type.into(), flags: PartFlags { attached: true }, }); - let module_id = module.id(); - module.insert(RigidBody::Dynamic) + module.id() + }; + + let mut children = if PartType::from(child.part_type) != PartType::LandingThruster { + load_savefile( + commands, + transform, + player_id, + module_id, + child.children.clone(), + attached_query, + part_query, + ) + } else { + [None, None, None, None] + }; + + let mut module = commands.entity(module_id); + + module.insert(Attach { + associated_player: Some(player_id), + parent: Some(parent), + children, + }); + //module.5.attached = true; + ret[i] = Some(module.id()); + + module + .insert(RigidBody::Dynamic) .with_children(|children| { children .spawn(Collider::cuboid(18.75 / SCALE, 23.4375 / SCALE)) @@ -1018,20 +1046,13 @@ fn load_savefile( .insert(ExternalImpulse::default()) .insert(Velocity::default()) .insert(ReadMassProperties::default()); - let mut children = if PartType::from(child.part_type) != PartType::LandingThruster { - load_savefile( - commands, - transform, - player_id, - module_id, - child.children.clone(), - attached_query, - part_query, - ) - } else { [None, None, None, None] }; + let joint = FixedJointBuilder::new() .local_anchor1(vec2(-53. / SCALE, 0. / SCALE)) .local_basis2(-PI / 2.); + + module.insert(ImpulseJoint::new(parent, joint)); + if PartType::from(child.part_type) == PartType::LandingThruster { module.insert(Attach { associated_player: Some(player_id), @@ -1089,14 +1110,6 @@ fn load_savefile( }); children[2] = Some(suspension.id()); } - module.insert(ImpulseJoint::new(parent, joint)); - module.insert(Attach { - associated_player: Some(player_id), - parent: Some(parent), - children, - }); - //module.5.attached = true; - ret[i] = Some(module.id()); } } return ret; diff --git a/server/src/packet.rs b/server/src/packet.rs index 478c29bcc922003462e4ece1210bc07615ef93ec..2239d348686d83a0ab3b12ba19f56806622b537e 100644 --- a/server/src/packet.rs +++ b/server/src/packet.rs @@ -1,3 +1,4 @@ +use std::fmt::{Display, Formatter}; // StarKingdoms.IO, a browser game about drifting through space // Copyright (C) 2023 ghostly_zsh, TerraMaster85, core // @@ -14,6 +15,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use crate::component::{PartType, PlanetType}; +use bevy_tungstenite_stk::tungstenite::Message; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] @@ -100,6 +102,7 @@ pub enum Packet { button: ButtonType, }, RequestSave {}, + _SpecialDisconnect {}, // clientbound SpawnPlayer { id: u32, @@ -136,3 +139,35 @@ pub enum Packet { payload: String, }, } + +impl Into for Packet { + fn into(self) -> Message { + Message::Text(serde_json::to_string(&self).expect("failed to serialize packet to json")) + } +} + +#[derive(Debug)] +pub enum MsgFromError { + InvalidMessageType, + JSONError(serde_json::Error), +} +impl Display for MsgFromError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl TryFrom for Packet { + type Error = MsgFromError; + + fn try_from(value: Message) -> Result { + match value { + Message::Text(s) => serde_json::from_str(&s).map_err(MsgFromError::JSONError), + Message::Binary(b) => serde_json::from_slice(&b).map_err(MsgFromError::JSONError), + Message::Close(_) => Ok(Packet::_SpecialDisconnect {}), + Message::Frame(_) | Message::Pong(_) | Message::Ping(_) => { + Err(MsgFromError::InvalidMessageType) + } + } + } +} diff --git a/starkingdoms-backplane/src/main.rs b/starkingdoms-backplane/src/main.rs index 55be6dd292a1b3767f5d43c7bb91e1eaf4345f4b..84bfbdbd42ef34fe05efe80e4431804e84becc1a 100644 --- a/starkingdoms-backplane/src/main.rs +++ b/starkingdoms-backplane/src/main.rs @@ -142,7 +142,7 @@ async fn main() { } } - let key = Hmac::new_from_slice(hex::decode(config.server.application_key).unwrap()).unwrap(); + let key = Hmac::new_from_slice(config.server.application_key.as_bytes()).unwrap(); let stk_epoch = UNIX_EPOCH + Duration::from_secs(1616260136); let id_generator = SnowflakeIdGenerator::with_epoch( diff --git a/starkingdoms-client/src/components/ui/Checkbox.svelte b/starkingdoms-client/src/components/ui/Checkbox.svelte index 634edf8d7c4e4e7015829339e966dbb5b2356376..d7ac3fef80b0b1166fe8ea9d7e60af40cc0a28b2 100644 --- a/starkingdoms-client/src/components/ui/Checkbox.svelte +++ b/starkingdoms-client/src/components/ui/Checkbox.svelte @@ -55,7 +55,6 @@ This sort of styling will replace the entire css/ directory eventually. */ - .svcmp-Checkbox { appearance: none; background: transparent;