M Cargo.lock => Cargo.lock +49 -3
@@ 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",
@@ 1058,6 1058,15 @@ dependencies = [
]
[[package]]
+name = "bevy_tungstenite_stk"
+version = "0.1.0"
+dependencies = [
+ "bevy",
+ "crossbeam-channel",
+ "tungstenite",
+]
+
+[[package]]
name = "bevy_twite"
version = "1.0.0"
source = "git+https://gitlab.com/ghostlyzsh/twite.git#d6f1b2a46b84048c239d8667f499977d9dc40e5b"
@@ 2117,7 2126,7 @@ dependencies = [
"futures-core",
"futures-sink",
"futures-util",
- "http",
+ "http 0.2.11",
"indexmap 2.1.0",
"slab",
"tokio",
@@ 2206,6 2215,17 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 3598,6 3618,7 @@ version = "0.0.1"
dependencies = [
"bevy",
"bevy_rapier2d",
+ "bevy_tungstenite_stk",
"bevy_twite",
"hex",
"hmac",
@@ 4011,6 4032,25 @@ dependencies = [
]
[[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"
source = "git+https://gitlab.com/ghostlyzsh/twite.git#d6f1b2a46b84048c239d8667f499977d9dc40e5b"
@@ 4072,6 4112,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
M Cargo.toml => Cargo.toml +2 -1
@@ 3,7 3,8 @@ members = [
"server",
"starkingdoms-backplane",
"starkingdoms-common",
- "savefile_decoder"
+ "savefile_decoder",
+ "bevy_tungstenite_stk"
]
resolver = "2"
A bevy_tungstenite_stk/Cargo.toml => bevy_tungstenite_stk/Cargo.toml +11 -0
@@ 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
A bevy_tungstenite_stk/src/lib.rs => bevy_tungstenite_stk/src/lib.rs +192 -0
@@ 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::<WsEvent>();
+ 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<RwLock<HashMap<SocketAddr, std::sync::mpsc::Sender<WsEvent>>>>);
+
+#[derive(Resource)]
+pub struct StkTungsteniteServerConfig {
+ pub addr: IpAddr,
+ pub port: u16,
+}
+
+#[derive(Resource)]
+pub struct Rx<A>(Receiver<A>);
+
+#[derive(Resource)]
+pub struct Tx<A>(Sender<A>);
+
+impl StkTungsteniteServerPlugin {
+ pub fn init_server(config: Res<StkTungsteniteServerConfig>, mut commands: Commands) {
+ let listener = TcpListener::bind(SocketAddr::from((config.addr, config.port)))
+ .expect("Failed to bind");
+
+ let (tx, rx) = unbounded::<WsEvent>();
+
+ 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<Clients>,
+ game_receiver: Res<Rx<WsEvent>>,
+ mut event_reader: Local<ManualEventReader<WsEvent>>,
+ mut events: ResMut<Events<WsEvent>>,
+ ) {
+ 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);
+ }
+ }
+}
M server/Cargo.toml => server/Cargo.toml +1 -0
@@ 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 = []
M server/src/main.rs => server/src/main.rs +76 -63
@@ 266,8 266,8 @@ fn on_message(
rng.gen::<f32>() * std::f32::consts::PI * 2.
};
let mut transform = Transform::from_xyz(
- angle.cos() * 1100.0 / SCALE,
- angle.sin() * 1100.0 / SCALE,
+ angle.cos() * 1500.0 / SCALE,
+ angle.sin() * 1500.0 / SCALE,
0.0,
);
transform.rotate_z(angle);
@@ 980,24 980,52 @@ fn load_savefile(
offset = Vec2::new(-53., -53.);
angle_offset = -PI / 2.;
}
- let mut module = commands.spawn(PartBundle {
- transform: TransformBundle::from(
- Transform::from_xyz(
- p_pos.x + offset.x / SCALE * angle.cos(),
- p_pos.y + offset.y / SCALE * angle.sin(),
- 0.,
- )
- .with_rotation(Quat::from_euler(
- EulerRot::ZYX,
- angle + angle_offset,
- 0.,
- 0.,
- )),
- ),
- part_type: child.part_type.into(),
- flags: PartFlags { attached: true },
+
+ let module_id = {
+ let module = commands.spawn(PartBundle {
+ transform: TransformBundle::from(
+ Transform::from_xyz(
+ p_pos.x + offset.x / SCALE * angle.cos(),
+ p_pos.y + offset.y / SCALE * angle.sin(),
+ 0.,
+ )
+ .with_rotation(Quat::from_euler(
+ EulerRot::ZYX,
+ angle + angle_offset,
+ 0.,
+ 0.,
+ )),
+ ),
+ part_type: child.part_type.into(),
+ flags: PartFlags { attached: true },
+ });
+ module.id()
+ };
+
+ let 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,
});
- let module_id = module.id();
+ //module.5.attached = true;
+ ret[i] = Some(module.id());
+
module
.insert(RigidBody::Dynamic)
.with_children(|children| {
@@ 1018,52 1046,39 @@ 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),
- parent: Some(module.id()),
- children: [None, None, None, None],
- });
let joint = PrismaticJointBuilder::new(Vec2::new(0., 1.))
.local_anchor1(Vec2::new(0., 0.))
.local_anchor2(Vec2::new(0., 0.))
.motor_position(0., 150., 10.)
.limits([0., 50. / SCALE])
.build();
- let mut suspension = commands.spawn(PartBundle {
- transform: TransformBundle::from(
- Transform::from_xyz(
- p_pos.x + offset.x / SCALE * angle.cos(),
- p_pos.y + offset.y / SCALE * angle.sin(),
- 0.,
- )
- .with_rotation(Quat::from_euler(
- EulerRot::ZYX,
- angle + angle_offset,
- 0.,
- 0.,
- )),
- ),
- part_type: PartType::LandingThrusterSuspension,
- flags: PartFlags { attached: false },
- });
+ let mut suspension = commands.spawn_empty();
suspension
+ .insert(PartBundle {
+ transform: TransformBundle::from(
+ Transform::from_xyz(
+ p_pos.x + offset.x / SCALE * angle.cos(),
+ p_pos.y + offset.y / SCALE * angle.sin(),
+ 0.,
+ )
+ .with_rotation(Quat::from_euler(
+ EulerRot::ZYX,
+ angle + angle_offset,
+ 0.,
+ 0.,
+ )),
+ ),
+ part_type: PartType::LandingThrusterSuspension,
+ flags: PartFlags { attached: false },
+ })
.insert(RigidBody::Dynamic)
.with_children(|children| {
children
@@ 1089,16 1104,14 @@ fn load_savefile(
parent: Some(module_id),
children: [None, None, None, None],
});
- children[2] = Some(suspension.id());
+ let suspension_id = suspension.id();
+ let mut module = commands.entity(module_id);
+ module.insert(Attach {
+ associated_player: Some(player_id),
+ parent: Some(module.id()),
+ children: [None, None, Some(suspension_id), None],
+ });
}
- 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;
M server/src/packet.rs => server/src/packet.rs +35 -0
@@ 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 <https://www.gnu.org/licenses/>.
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<Message> 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<Message> for Packet {
+ type Error = MsgFromError;
+
+ fn try_from(value: Message) -> Result<Self, Self::Error> {
+ 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)
+ }
+ }
+ }
+}
M starkingdoms-backplane/src/main.rs => starkingdoms-backplane/src/main.rs +1 -1
@@ 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(