~starkingdoms/starkingdoms

2256dfd8c5a4f429e15cc8f33ba6cadd6b203f65 — core 1 year, 11 months ago 68ea235
start of bevy_tungstenite_stk, maybe fix loading
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 +57 -44
@@ 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;

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(

M starkingdoms-client/src/components/ui/Checkbox.svelte => starkingdoms-client/src/components/ui/Checkbox.svelte +0 -1
@@ 55,7 55,6 @@
    This sort of styling will replace the entire css/ directory eventually.
  */


  .svcmp-Checkbox {
    appearance: none;
    background: transparent;