~starkingdoms/starkingdoms

76100ca42516d88511febf1b8ff4c302410eab87 — ghostly_zsh 8 months ago 4174a42
networking, parts rendering
M crates/client/src/components.rs => crates/client/src/components.rs +26 -5
@@ 1,7 1,8 @@
use bevy_ecs::{component::Component, system::Resource};
use nalgebra::{Matrix4, Rotation3, Scale3, Translation3};
use bevy_ecs::{bundle::Bundle, component::Component, event::Event, system::Resource};
use nalgebra::{Matrix4, Rotation2, Scale3, Translation3};
use starkingdoms_common::packet::Packet;

#[derive(Component)]
#[derive(Component, Debug)]
pub struct Texture {
    pub name: String,
}


@@ 9,15 10,21 @@ pub struct Texture {
#[derive(Component, Debug)]
pub struct Transform {
    pub translation: Translation3<f32>,
    pub rotation: Rotation3<f32>,
    pub rotation: Rotation2<f32>,
    pub scale: Scale3<f32>,
}
impl Transform {
    pub fn to_matrix(&self) -> Matrix4<f32> {
        self.translation.to_homogeneous() * self.rotation.to_homogeneous() * self.scale.to_homogeneous()
        self.translation.to_homogeneous() * self.rotation.to_homogeneous().to_homogeneous() * self.scale.to_homogeneous()
    }
}

#[derive(Bundle, Debug)]
pub struct SpriteBundle {
    pub transform: Transform,
    pub texture: Texture,
}

#[derive(Resource, Debug)]
pub struct Camera {
    pub x: f32,


@@ 26,3 33,17 @@ pub struct Camera {
    pub width: u32, // screen width (these are for aspect ratio)
    pub height: u32, // screen height
}

#[derive(Component, Debug, Clone, Copy)]
pub struct Player;
#[derive(Component, Debug, Clone, Copy)]
pub struct Planet;
#[derive(Component, Debug, Clone, Copy)]
pub struct Part;
#[derive(Component, Debug, Clone, Copy)]
pub struct ServerId(pub u32);

#[derive(Event, Clone, PartialEq)]
pub struct SendPacket(pub Packet);
#[derive(Event, Clone, PartialEq)]
pub struct RecvPacket(pub Packet);

M crates/client/src/lib.rs => crates/client/src/lib.rs +13 -6
@@ 1,7 1,9 @@
use bevy_ecs::world::World;
use components::{Camera, Texture, Transform};
use bevy_ecs::{event::Events, world::World};
use components::{Camera, Part, Player, RecvPacket, SendPacket, Texture, Transform};
use nalgebra::{Rotation2, Rotation3, Scale2, Scale3, Translation2, Translation3, Vector3};
use networking::ws::Ws;
use rendering::{assets::Assets, App};
use starkingdoms_common::packet::Packet;
use tracing::info;
use winit::event_loop::{ControlFlow, EventLoop};



@@ 14,6 16,7 @@ pub mod platform;

pub mod rendering;
pub mod components;
pub mod networking;

// Hi, you've found the real main function! This is called AFTER platform-specific initialization code.
pub fn start() {


@@ 39,15 42,19 @@ pub fn start() {
    });

    world.insert_resource(Assets::new());
    world.insert_resource(Ws::new());

    let mut send_packet_events = Events::<SendPacket>::default();
    let recv_packet_events = Events::<RecvPacket>::default();

    world.spawn((Transform {
        translation: Translation3::new(0.0, 0.0, 0.0),
        rotation: Rotation3::from_axis_angle(&Vector3::z_axis(), 0.0),
        scale: Scale3::new(20.0, 20.0, 1.0),
    }, Texture { name: "hearty.svg".to_string() }));
        rotation: Rotation2::new(0.0),
        scale: Scale3::new(25.0, 25.0, 1.0),
    }, Texture { name: "hearty.svg".to_string() }, Player, Part));

    let event_loop = EventLoop::new().unwrap();
    event_loop.set_control_flow(ControlFlow::Wait);

    event_loop.run_app(&mut App::new(world)).unwrap();
    event_loop.run_app(&mut App::new(world, send_packet_events, recv_packet_events)).unwrap();
}

A crates/client/src/networking/mod.rs => crates/client/src/networking/mod.rs +114 -0
@@ 0,0 1,114 @@
use std::collections::HashMap;

use bevy_ecs::{entity::Entity, event::Events, query::With, world::World};
use nalgebra::{Rotation2, Scale2, Scale3, Translation3};
use starkingdoms_common::{packet::Packet, PartType, PlanetType};

use crate::components::{Camera, Part, Planet, Player, RecvPacket, SendPacket, ServerId, SpriteBundle, Texture, Transform};

#[cfg(target_arch = "wasm32")]
#[path = "ws_wasm.rs"]
pub mod ws;
#[cfg(not(target_arch = "wasm32"))]
#[path = "ws_native.rs"]
pub mod ws;

pub fn process_packets(
    world: &mut World,
    send_packet_events: &mut Events<SendPacket>,
    recv_packet_events: &mut Events<RecvPacket>,
    planet_types: &mut HashMap<PlanetType, (Entity, u32)>,
) {
    use Packet::*;
    let mut recv_cursor = recv_packet_events.get_cursor();
    for recv in recv_cursor.read(&recv_packet_events) {
        match &recv.0 {
            SpawnPlayer { id, username } => {
                let mut player_query = world.query_filtered::<Entity, With<Player>>();
                let entity = player_query.single(world);
                world.entity_mut(entity).insert(ServerId(*id));
            }
            SpawnPart { id, part } => {
                use PartType::*;
                world.spawn((Transform {
                    translation: Translation3::new(part.transform.x, part.transform.y, 0.0),
                    rotation: Rotation2::new(part.transform.rot),
                    scale: Scale3::new(25.0, 25.0, 1.0),
                }, Texture {
                    name: match part.part_type {
                        Placeholder => panic!("AHHHH PLACEHOLDER PANIC"),
                        Hearty => "hearty.svg",
                        Cargo => "cargo_off.svg",
                        Hub => "hub_off.svg",
                        LandingThruster => "landingthruster_off.svg",
                        LandingThrusterSuspension => "landingleg.svg",
                    }.to_string()
                }, ServerId(*id), Part));
                tracing::info!("here");
            }
            PartPositions { parts } => {
                for (id, part) in parts {
                    if part.part_type == PartType::Hearty {
                        let mut player_query = world.query_filtered::<&ServerId, With<Player>>();
                        let server_id = player_query.single(world);
                        if server_id.0 == *id {
                            let mut camera = world.resource_mut::<Camera>();
                            camera.x = -part.transform.x;
                            camera.y = -part.transform.y;
                        }
                    }
                    let mut part_query = world.query_filtered::<(&ServerId, &mut Transform), With<Part>>();
                    for (server_id, mut transform) in part_query.iter_mut(world) {
                        if server_id.0 == *id {
                            transform.translation.x = part.transform.x;
                            transform.translation.y = part.transform.y;
                            transform.rotation = Rotation2::new(part.transform.rot);
                        }
                    }
                }
            }
            PlanetPositions { planets } => {
                for (server_id, planet) in planets {
                    let mut planet_query = world.query_filtered::<&mut Transform, With<Planet>>();
                    if !planet_types.contains_key(&planet.planet_type) {
                        let entity = world.spawn(SpriteBundle {
                            transform: Transform {
                                translation: Translation3::new(planet.transform.x, planet.transform.y, 0.0),
                                rotation: Rotation2::new(planet.transform.rot),
                                scale: Scale3::new(planet.radius, planet.radius, 1.0),
                            },
                            texture: Texture {
                                name: match planet.planet_type {
                                    /*PlanetType::Sun => "sun.svg",
                                    PlanetType::Mercury => "mercury.svg",
                                    PlanetType::Venus => "venus.svg",
                                    PlanetType::Earth => "earth.svg",
                                    PlanetType::Moon => "moon.svg",
                                    PlanetType::Mars => "mars.svg",
                                    PlanetType::Jupiter => "jupiter.svg",
                                    PlanetType::Saturn => "saturn.svg",
                                    PlanetType::Uranus => "uranus.svg",
                                    PlanetType::Neptune => "neptune.svg",
                                    PlanetType::Pluto => "pluto.svg",*/
                                    PlanetType::Sun => "sun.svg",
                                    PlanetType::Mercury => "moon.svg",
                                    PlanetType::Venus => "mars.svg",
                                    PlanetType::Earth => "earth.svg",
                                    PlanetType::Moon => "moon.svg",
                                    PlanetType::Mars => "mars.svg",
                                    PlanetType::Jupiter => "sun.svg",
                                    PlanetType::Saturn => "moon.svg",
                                    PlanetType::Uranus => "sun.svg",
                                    PlanetType::Neptune => "mars.svg",
                                    PlanetType::Pluto => "earth.svg",
                                }.to_string()
                            },
                        });
                        planet_types.insert(planet.planet_type, (entity.id(), *server_id));
                    }
                }
            }
            _ => {}
        }
    }
}

A crates/client/src/networking/ws_native.rs => crates/client/src/networking/ws_native.rs +61 -0
@@ 0,0 1,61 @@
use std::{net::TcpStream, sync::{Arc, Mutex}};

use bevy_ecs::system::Resource;
use crossbeam::channel::{unbounded, Receiver, Sender};
use starkingdoms_common::packet::{MsgFromError, Packet};
use tungstenite::{connect, stream::MaybeTlsStream, Message, WebSocket};

pub trait PacketMessageConvert {
    fn from_message(value: &Message) -> Result<Packet, MsgFromError>;
    fn into_message(&self) -> Message;
}

impl PacketMessageConvert for Packet {
    fn from_message(value: &Message) -> Result<Packet, MsgFromError> {
        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)
            }
        }
    }
    fn into_message(&self) -> Message {
        Message::Text(serde_json::to_string(self).expect("failed to serialize packet to json").into())
    }
}

#[derive(Resource, Debug)]
pub struct Ws {
    socket: Arc<Mutex<WebSocket<MaybeTlsStream<TcpStream>>>>,
    pub sender: Sender<Packet>,
    pub receiver: Receiver<Packet>,
    packet_receiver: Receiver<Packet>,
}
impl Ws {
    pub fn new() -> Self {
        let (socket, _) = connect("ws://localhost:3000").expect("Failed to connect to server");
        let socket = Arc::new(Mutex::new(socket));
        let (packet_sender, receiver) = unbounded();
        let (sender, packet_receiver) = unbounded();
        let socket_clone = socket.clone();
        std::thread::spawn(move || {
            let socket = socket_clone;
            loop {
                let message = socket.lock().unwrap().read().expect("Failed to reading message");
                let packet = Packet::from_message(&message).expect("Server sent invalid packet");
                packet_sender.send(packet).expect("Couldn't send packet to server");
            }
        });
        Ws {
            socket,
            sender,
            receiver,
            packet_receiver,
        }
    }
    pub fn send_packet(&mut self, packet: &Packet) {
        self.socket.lock().unwrap().send(packet.into_message()).expect("Couldn't send packet to server");
    }
}

A crates/client/src/networking/ws_wasm.rs => crates/client/src/networking/ws_wasm.rs +78 -0
@@ 0,0 1,78 @@
use std::thread::yield_now;

use bevy_ecs::system::Resource;
//use crossbeam::channel::{unbounded, Receiver, Sender};
use futures::{channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, SinkExt};
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use wasm_bindgen_futures::spawn_local;
use web_sys::{MessageEvent, WebSocket};
use starkingdoms_common::packet::Packet;

const PORT: u16 = 3000;

#[derive(Debug)]
pub struct Socket(WebSocket);
unsafe impl Send for Socket {}
unsafe impl Sync for Socket {}

#[derive(Resource, Debug)]
pub struct Ws {
    socket: Socket,
    pub sender: UnboundedSender<Packet>,
    pub receiver: UnboundedReceiver<Packet>,
    packet_receiver: UnboundedReceiver<Packet>,
}

impl Ws {
    pub fn new() -> Self {
        let window = web_sys::window().unwrap();
        let ws = WebSocket::new(&format!("ws://{}:{}",
                window.location().hostname().unwrap(), PORT)).expect("Couldn't connect to server");
        let (packet_sender, receiver) = unbounded();
        //let packet_sender = Rc::new(RwLock::new(packet_sender));
        let (sender, packet_receiver) = unbounded();

        let ws_clone = ws.clone();
        let onopen_callback = Closure::<dyn FnMut()>::new(move || {
            let packet = Packet::ClientLogin {
                username: String::new(),
                save: None,
                jwt: None,
            };

            ws_clone.send_with_str(&serde_json::to_string(&packet).expect("Couldn't convert packet to string")).expect("Failed to send packet");
        });
        ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
        onopen_callback.forget();
        let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
            //tracing::error!("{}", ws.ready_state());
            let data = e.data().as_string().expect("Expected string, found some other type");
            let data: Packet = serde_json::from_str(&data).expect("Received invalid json from server");
            let mut sender_clone = packet_sender.clone();
            spawn_local(async move {
                sender_clone.send(data).await.expect("Couldn't transmit packet to client");
            });
        });
        ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
        onmessage_callback.forget();
        Ws {
            socket: Socket(ws),
            sender,
            receiver,
            packet_receiver,
        }
    }
    pub fn send_all_packets_from_channel(&mut self) {
        //for packet in self.packet_receiver.iter() {
        while let Ok(Some(packet)) = self.packet_receiver.try_next() {
            self.socket.0.send_with_str(&serde_json::to_string(&packet).expect("Couldn't convert packet to json")).expect("Couldn't send packet to server");
        }
    }
    pub fn send_packet(&mut self, packet: Packet) {
        let socket = self.socket.0.clone();
        spawn_local(async move {
            //while socket.ready_state() != 1 {  }
            socket.send_with_str(&serde_json::to_string(&packet).expect("Couldn't convert packet to json")).expect("Couldn't send packet to server");
        });
    }
}

M crates/client/src/rendering/assets_native.rs => crates/client/src/rendering/assets_native.rs +43 -2
@@ 1,4 1,14 @@
use std::io::Read;

use bevy_ecs::system::Resource;
use resvg::{tiny_skia, usvg};

#[derive(Debug, Clone)]
pub struct ImgData {
    pub bytes: Vec<u8>,
    pub width: u32,
    pub height: u32,
}

#[derive(Resource)]
pub struct Assets {


@@ 8,8 18,39 @@ impl Assets {
    pub fn new() -> Self {
        Assets {  }
    }
    pub fn get(&self, local_path: impl Into<String>) -> Option<Vec<u8>> {
        std::fs::read(format!("src/textures/{}", local_path.into())).ok()
    pub fn get(&self, local_path: impl Into<String>) -> Option<ImgData> {
        let local_path = local_path.into();
        let bytes = std::fs::read(format!("src/assets/{}", local_path)).unwrap();
        if local_path.ends_with(".svg") {
            let opt = usvg::Options {
                default_size: usvg::Size::from_wh(20.0, 20.0).unwrap(),
                ..Default::default()
            };
            let tree = usvg::Tree::from_data(&bytes, &opt).expect(&format!("Couldn't parse svg {}", local_path));
            let tree_size = tree.size().to_int_size();
            let size = usvg::Size::from_wh(200.0, 200.0).unwrap().to_int_size();
            assert!(size.width() > 0 && size.height() > 0);
            let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).expect("Failed to construct pixmap");
            resvg::render(&tree, tiny_skia::Transform::from_scale((size.width() as f32)/(tree_size.height() as f32), (size.height() as f32)/(tree_size.height() as f32)), &mut pixmap.as_mut());
            let data = ImgData {
                bytes: pixmap.data().to_vec(),
                width: size.width(),
                height: size.height(),
            };
            
            Some(data)
        } else if local_path.ends_with(".png") {
            let img = image::load_from_memory(&bytes).unwrap();
            let rgba = img.to_rgba8();
            let data = ImgData {
                bytes: rgba.bytes().map(|byte| byte.unwrap()).collect::<Vec<u8>>(),
                width: rgba.width(),
                height: rgba.height(),
            };
            Some(data)
        } else {
            panic!("Unsupported sprite type");
        }
    }
}


M crates/client/src/rendering/assets_wasm.rs => crates/client/src/rendering/assets_wasm.rs +1 -1
@@ 66,7 66,7 @@ impl Assets {
                    };
                    let tree = usvg::Tree::from_data(&response.bytes, &opt).expect(&format!("Couldn't parse svg {}", local_path_clone));
                    let tree_size = tree.size().to_int_size();
                    let size = usvg::Size::from_wh(200.0, 200.0).unwrap().to_int_size();
                    let size = usvg::Size::from_wh(500.0, 500.0).unwrap().to_int_size();
                    assert!(size.width() > 0 && size.height() > 0);
                    let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).expect("Failed to construct pixmap");
                    resvg::render(&tree, tiny_skia::Transform::from_scale((size.width() as f32)/(tree_size.height() as f32), (size.height() as f32)/(tree_size.height() as f32)), &mut pixmap.as_mut());

M crates/client/src/rendering/mod.rs => crates/client/src/rendering/mod.rs +57 -3
@@ 3,6 3,8 @@ use std::num::NonZeroU32;
use std::sync::Arc;

use assets::Assets;
use bevy_ecs::entity::Entity;
use bevy_ecs::event::Events;
use bevy_ecs::world::World;
use egui_glow::EguiGlow;
use glow::{HasContext, PixelUnpackData};


@@ 12,16 14,20 @@ use glutin::surface::{Surface, WindowSurface, GlSurface, SwapInterval};
use glutin::{config::{ConfigTemplateBuilder, GlConfig}, context::{ContextApi, ContextAttributesBuilder, PossiblyCurrentContext}, display::GetGlDisplay, prelude::{GlDisplay, NotCurrentGlContext}};
#[cfg(not(target_arch = "wasm32"))]
use glutin_winit::{DisplayBuilder, GlWindow};
use starkingdoms_common::PlanetType;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::{prelude::Closure, JsCast};
#[cfg(target_arch = "wasm32")]
use web_sys::{Event, HtmlCanvasElement};
use winit::event::MouseScrollDelta;
use winit::event_loop::ControlFlow;
#[cfg(target_arch = "wasm32")]
use winit::platform::web::{WindowAttributesExtWebSys, WindowExtWebSys};
use winit::{application::ApplicationHandler, dpi::LogicalSize, event::WindowEvent, event_loop::ActiveEventLoop, raw_window_handle::HasWindowHandle, window::{Window, WindowAttributes}};

use crate::components::{Camera, Texture, Transform};
use crate::components::{Camera, RecvPacket, SendPacket, Texture, Transform};
use crate::networking::process_packets;
use crate::networking::ws::Ws;
#[cfg(not(target_arch="wasm32"))]
#[path = "assets_native.rs"]
pub mod assets;


@@ 45,6 51,10 @@ pub struct App {
    egui_glow: Option<EguiGlow>,
    gl: Option<Arc<glow::Context>>,
    textures: HashMap<String, glow::Texture>,

    send_packet_events: Events<SendPacket>,
    recv_packet_events: Events<RecvPacket>,
    planet_types: HashMap<PlanetType, (Entity, u32)>, // (world entity, server id)
}

const VERTICES: [f32; 16] = [


@@ 59,9 69,15 @@ const INDICES: [u32; 6] = [
];

impl App {
    pub fn new(world: World) -> Self {
    pub fn new(
        world: World,
        send_packet_events: Events<SendPacket>,
        recv_packet_events: Events<RecvPacket>
    ) -> Self {
        Self {
            world,
            send_packet_events,
            recv_packet_events,
            ..Default::default()
        }
    }


@@ 178,7 194,7 @@ impl ApplicationHandler for App {
            gl.enable_vertex_attrib_array(1);


            gl.clear_color(0.0, 0.0, 0.0, 0.0);
            gl.clear_color(0.7, 0.7, 0.7, 1.0);
            gl.viewport(0, 0, window.inner_size().width as i32, window.inner_size().height as i32);
            gl.enable(glow::BLEND);
            gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);


@@ 225,13 241,44 @@ impl ApplicationHandler for App {
                    self.gl.as_ref().unwrap().viewport(0, 0, size.width as i32, size.height as i32);
                }
            }
            WindowEvent::MouseWheel { delta, .. } => {
                let mut camera = self.world.get_resource_mut::<Camera>().unwrap();
                let raw_delta = match delta {
                    MouseScrollDelta::PixelDelta(pos) => {
                        pos.y as f32
                    }
                    MouseScrollDelta::LineDelta(y, .. ) => {
                        y
                    }
                };
                let delta = 1.1;
                if raw_delta < 0.0 {
                    camera.zoom *= 1.0 / delta;
                } else {

                    camera.zoom *= delta;
                }
            }
            _ => {}
        }
        let event_response = self.egui_glow.as_mut().unwrap()
            .on_window_event(self.window.as_ref().unwrap(), &event);
    }
    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        let mut ws = self.world.get_resource_mut::<Ws>().expect("Failed to get Ws resource");
        #[cfg(target_arch = "wasm32")]
        while let Ok(Some(packet)) = ws.receiver.try_next() {
            self.recv_packet_events.send(RecvPacket(packet));
        }
        #[cfg(not(target_arch = "wasm32"))]
        for packet in ws.receiver.iter() {
            self.recv_packet_events.send(RecvPacket(packet));
        }
        self.send_packet_events.update();
        self.recv_packet_events.update();

        process_packets(&mut self.world, &mut self.send_packet_events, &mut self.recv_packet_events, &mut self.planet_types);

        let window = self.window.as_ref().unwrap();
        let gl = self.gl.as_ref().unwrap();



@@ 288,6 335,7 @@ impl ApplicationHandler for App {
                        glow::UNSIGNED_BYTE, PixelUnpackData::Slice(Some(&image.bytes)));
                    gl.generate_mipmap(glow::TEXTURE_2D);

                    tracing::info!("{}", texture.name);
                    self.textures.insert(texture.name.clone(), texture_object);
                }
                // now the texture must exist


@@ 306,6 354,12 @@ impl ApplicationHandler for App {
        #[cfg(not(target_arch = "wasm32"))]
        self.gl_surface.as_ref().unwrap().swap_buffers(self.gl_context.as_ref().unwrap()).unwrap();

        let mut ws = self.world.get_resource_mut::<Ws>().expect("Failed to get Ws resource");
        let mut send_event_cursor = self.send_packet_events.get_cursor();
        for event in send_event_cursor.read(&self.send_packet_events) {
            ws.send_packet(event.0.clone());
        }

        event_loop.set_control_flow(ControlFlow::WaitUntil(web_time::Instant::now().checked_add(web_time::Duration::from_millis(16)).unwrap()));
    }
}

M crates/server/src/player/client_login.rs => crates/server/src/player/client_login.rs +7 -5
@@ 314,11 314,13 @@ pub fn packet_stream(
            flags: ProtoPartFlags { attached: false },
        },
    ));
    let packet = Packet::PartPositions { parts };
    event_queue.push(WsEvent::Send {
        to: *from,
        message: packet.into_message(),
    });
    for part in parts {
        let packet = Packet::SpawnPart { id: part.0, part: part.1 };
        event_queue.push(WsEvent::Send {
            to: *from,
            message: packet.into_message(),
        });
    }

    // and send the welcome message :)
    let packet = Packet::Message {