M client/src/lib.rs => client/src/lib.rs +20 -7
@@ 2,11 2,11 @@ use std::error::Error;
use std::str::FromStr;
use futures::stream::{SplitSink, SplitStream};
-use futures::StreamExt;
+use futures::{StreamExt, TryFutureExt};
use log::{debug, error, info, Level, trace, warn};
use wasm_bindgen::prelude::*;
use ws_stream_wasm::{WsErr, WsMessage, WsMeta, WsStream};
-use starkingdoms_protocol::{Planet, State};
+use starkingdoms_protocol::{ProtocolPlanet, State};
use starkingdoms_protocol::PROTOCOL_VERSION;
use starkingdoms_protocol::MessageS2C;
use starkingdoms_protocol::MessageC2S;
@@ 20,6 20,7 @@ use futures::FutureExt;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Window};
use starkingdoms_protocol::GoodbyeReason::PingPongTimeout;
+use crate::rendering::Renderer;
use crate::rendering::renderer::WebRenderer;
use crate::textures::loader::TextureLoader;
use crate::textures::{TextureManager, TextureSize};
@@ 39,7 40,7 @@ extern {
#[derive(Debug)]
pub struct Client {
pub client_data: Option<ClientData>,
- pub planets: Vec<Planet>,
+ pub planets: Vec<ProtocolPlanet>,
pub x: f64,
pub y: f64
}
@@ 50,7 51,8 @@ pub struct ClientData {
pub tx: SplitSink<WsStream, WsMessage>,
pub rx: SplitStream<WsStream>,
pub pong_timeout: u64,
- pub textures: TextureLoader
+ pub textures: TextureLoader,
+ pub renderer: WebRenderer
}
pub const PONG_MAX_TIMEOUT: u64 = 5;
@@ 81,7 83,7 @@ pub async fn rust_init(gateway: &str, username: &str, texture_size: &str) -> Res
let textures = TextureLoader::load(TextureSize::from_str(texture_size).unwrap()).map_err(|e| e.to_string())?;
- match main(gateway, username, 1, textures).await {
+ match main(gateway, username, 1, textures, WebRenderer::get("canvas").await.unwrap()).await {
Ok(c) => c,
Err(e) => {
error!("Error initializing gateway client: {}", e);
@@ 95,7 97,7 @@ pub async fn rust_init(gateway: &str, username: &str, texture_size: &str) -> Res
}
#[async_recursion(?Send)]
-pub async fn main(gateway: &str, username: &str, backoff: i32, textures: TextureLoader) -> Result<(), Box<dyn Error>> {
+pub async fn main(gateway: &str, username: &str, backoff: i32, textures: TextureLoader, renderer: WebRenderer) -> Result<(), Box<dyn Error>> {
if backoff != 1 {
info!("Backing off connection: waiting {} seconds", backoff * backoff);
wait_for(sleep(backoff * backoff * 1000)).await;
@@ 119,7 121,7 @@ pub async fn main(gateway: &str, username: &str, backoff: i32, textures: Texture
Err(e) => {
return match e {
WsErr::ConnectionFailed { .. } => {
- main(gateway, username, backoff + 1, textures).await
+ main(gateway, username, backoff + 1, textures, renderer).await
},
_ => {
Err(e.into())
@@ 136,6 138,7 @@ pub async fn main(gateway: &str, username: &str, backoff: i32, textures: Texture
rx,
pong_timeout: (js_sys::Date::now() as u64 / 1000) + 5,
textures,
+ renderer
};
trace!("Split stream, handshaking with server");
@@ 286,4 289,14 @@ pub fn get_texture(texture_id: &str) -> Option<String> {
} else {
None
}
+}
+
+#[wasm_bindgen]
+pub async fn render_frame(delta: f64) -> Result<(), JsError> {
+ let client = CLIENT.read().unwrap();
+ if let Some(client_data) = &client.client_data {
+ Ok(client_data.renderer.render_frame(delta).await.map_err(|e| JsError::new(&format!("{}", e)))?)
+ } else {
+ Err(JsError::new("Client not yet initialized"))
+ }
}=
\ No newline at end of file
M client/src/rendering/mod.rs => client/src/rendering/mod.rs +1 -0
@@ 6,4 6,5 @@ pub mod renderer;
#[async_trait]
pub trait Renderer {
async fn get(canvas_element_id: &str) -> Result<Self, Box<dyn Error>> where Self: Sized;
+ async fn render_frame(&self, time_delta_ms: f64) -> Result<(), Box<dyn Error>>;
}=
\ No newline at end of file
M client/src/rendering/renderer.rs => client/src/rendering/renderer.rs +12 -7
@@ 4,22 4,27 @@ use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
use crate::rendering::Renderer;
use wasm_bindgen::JsCast;
+#[derive(Debug)]
pub struct WebRenderer {
- canvas: HtmlCanvasElement,
- context: CanvasRenderingContext2d
+ canvas_element_id: String
}
#[async_trait]
impl Renderer for WebRenderer {
async fn get(canvas_element_id: &str) -> Result<Self, Box<dyn Error>> {
+ Ok(Self {
+ canvas_element_id: canvas_element_id.to_string()
+ })
+ }
+
+ async fn render_frame(&self, time_delta_ms: f64) -> Result<(), Box<dyn Error>> {
+ // TODO
+ // time_delta_ms is the delta, in ms, from when the last render_frame was called by the browser
let window = web_sys::window().ok_or("window needs to exist")?;
let document = window.document().ok_or("window.document needs to exist")?;
- let canvas_element = document.get_element_by_id(canvas_element_id).ok_or("canvas element does not exist")?;
+ let canvas_element = document.get_element_by_id(&self.canvas_element_id).ok_or("canvas element does not exist")?;
let typed_canvas_element: HtmlCanvasElement = canvas_element.dyn_into::<web_sys::HtmlCanvasElement>().map_err(|_| ()).unwrap();
let context = typed_canvas_element.get_context("2d").unwrap().unwrap().dyn_into::<CanvasRenderingContext2d>().unwrap();
- Ok(Self {
- canvas: typed_canvas_element,
- context
- })
+ Ok(())
}
}=
\ No newline at end of file
M web/play.html => web/play.html +17 -4
@@ 2,10 2,8 @@
<html lang="en-US">
<head>
<title>StarKingdoms.TK</title>
- <link rel="stylesheet" href="/static/css/stylemain.css"></link>
- <link rel="favicon" href="/static/img/favicon.ico"></link>
+ <link rel="stylesheet" href="/static/css/play.css"></link>
<meta charset="utf-8">
- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
@@ 27,7 25,7 @@
<script type="module">
// If you're getting build errors here | you need to run `just build_client_bundle` first, to compile client code
// v
- import init, { rust_init, send_chat, update_socket, set_status, get_texture } from "./dist/starkingdoms_client.js";
+ import init, { rust_init, send_chat, update_socket, set_status, get_texture, render_frame } from "./dist/starkingdoms_client.js";
init().then(() => {
const urlSearchParams = new URLSearchParams(window.location.search);
@@ 45,6 43,20 @@
});
}, 5);
+ let start;
+ function animateFrame(time) {
+ if (start === undefined) {
+ start = time;
+ }
+ let delta = time - start;
+
+ render_frame(delta);
+ start = time;
+ requestAnimationFrame(animateFrame)
+ }
+
+ requestAnimationFrame(animateFrame);
+
if (urlSearchParams.has("showTextures")) {
let textures = ["autoplr_cfg", "autoplr_error", "autoplr_on", "cargo_off", "cargo_on", "earth", "ecothruster_on", "hearty", "hub_off", "hub_on", "landingleg", "landingthruster_off", "landingthruster_on", "powerhub_off", "powerhub_on", "superthruster_off", "superthruster_on", "thruster_off", "thruster_on"];
@@ 53,6 65,7 @@
let fieldset_elem = document.createElement("fieldset");
fieldset_elem.style = "width: min-content;";
+ fieldset_elem.classList.add("texturebox")
let legend_elem = document.createElement("legend");
legend_elem.innerText = texture;
A web/static/css/play.css => web/static/css/play.css +4 -0
@@ 0,0 1,4 @@
+.texturebox {
+ display: inline;
+ margin: 5px;
+}<
\ No newline at end of file