html terminal
add a /view endpoint
| -rw-r--r-- | html-src/index.html | 33 | ||||
| -rw-r--r-- | src/bot/maps.rs | 137 | ||||
| -rw-r--r-- | src/bot/mod.rs | 2 | ||||
| -rw-r--r-- | src/server.rs | 27 |
4 files changed, 142 insertions, 57 deletions
diff --git a/html-src/index.html b/html-src/index.html index bbf9f63..3a3b67a 100644 --- a/html-src/index.html +++ b/html-src/index.html @@ -12,24 +12,34 @@ background-attachment: fixed; background-position: center; background-repeat: no-repeat; + background-color: #3E3B3B; } h1 { text-align: center; color: wheat; - font-family: 'Ubuntu', sans-serif; + font-family: 'Ubuntu', 'Helvetica', 'Arial', sans-serif; font-weight: bolder; font-size: 5em; - vertical-align: middle; + user-select: none; + margin: 0px !important; + text-shadow: -8px -0px 0 #3E3B3B, 0px -0px 0 #3E3B3B, -8px 0px 0 #3E3B3B, 0px 0px 0 #3E3B3B; } - .text-holder { - height: 200px; - width: 400px; + h2 { + font-family: 'Ubuntu', 'Helvetica', 'Arial', sans-serif; + font-size: 1.2em; + text-align: center; + color: wheat; + user-select: none; + text-shadow: -1px -1px 0 #3E3B3B, 1px -1px 0 #3E3B3B, -1px 1px 0 #3E3B3B, 1px 1px 0 #3E3B3B; + } + .text-holder { + align-self: center; position: fixed; - top: 50%; - left: 50%; + top: 20%; + left: 80%; margin-top: -100px; margin-left: -200px; @@ -41,7 +51,14 @@ <body> <div class="text-holder"> <h1>Plague</h1> + <h2> + When attack and defense combine<br> + Plague, a chaotic pvp gamemode<br> + One team, a desperate race to procure units<br> + The other, in a dance of constantly evolving defence<br> + A battlefield ever changing; Strategies never constant + </h2> </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/src/bot/maps.rs b/src/bot/maps.rs index 65690aa..8b5976e 100644 --- a/src/bot/maps.rs +++ b/src/bot/maps.rs @@ -5,10 +5,13 @@ use mindus::*; use oxipng::*; use poise::serenity_prelude::*; use std::borrow::Cow; -use std::sync::LazyLock; -use std::time::Instant; -use tokio::sync::broadcast; -use tokio::sync::OnceCell; +use std::sync::{ + atomic::{AtomicU64, Ordering::Relaxed}, + LazyLock, +}; +use std::time::{Duration, Instant, SystemTime}; +use tokio::sync::broadcast::{self, Sender}; +use tokio::sync::{MutexGuard, OnceCell}; pub struct Maps; impl Maps { pub async fn find(map: &str, stdin: &broadcast::Sender<String>) -> usize { @@ -62,52 +65,104 @@ pub async fn list(ctx: Context<'_>) -> Result<()> { Ok(()) } -static REG: LazyLock<mindus::block::BlockRegistry> = LazyLock::new(build_registry); +pub struct RenderInfo { + deserialization: Duration, + render: Duration, + compression: Duration, + total: Duration, + name: String, +} +pub static MAP_IMAGE: MapImage = MapImage(Mutex::const_new(vec![]), AtomicU64::new(0)); +pub struct MapImage(Mutex<Vec<u8>>, AtomicU64); +impl MapImage { + /// procure the map image. + pub async fn get( + &self, + stdin: &Sender<String>, + // returning a guard is questionable + ) -> Result<(MutexGuard<Vec<u8>>, Option<RenderInfo>)> { + let mut lock = self.0.lock().await; + // me in a million years when its 1901 and we never get a new render + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + Ok( + if self + .1 + .fetch_update(Relaxed, Relaxed, |then| (now > then + 70).then(|| now)) + .is_err() + { + (lock, None) + } else { + send!(stdin, "save 0")?; + let _ = get_nextblock().await; + + // parsing the thing doesnt negate the need for a env var sooo + let o = std::fs::read(std::env::var("SAVE_PATH").unwrap())?; + let then = Instant::now(); + static REG: LazyLock<mindus::block::BlockRegistry> = LazyLock::new(build_registry); + let m = MapSerializer(®).deserialize(&mut mindus::data::DataRead::new(&o))?; + let deser_took = then.elapsed(); + let name = m.tags.get("mapname").unwrap().to_owned(); + let render_took = Instant::now(); + let i = m.render(); + let render_took = render_took.elapsed(); + let compression_took = Instant::now(); + // TODO make render() return RgbImage + let i = RawImage::new( + i.width(), + i.height(), + ColorType::RGB { + transparent_color: None, + }, + BitDepth::Eight, + image::DynamicImage::ImageRgba8(i).to_rgb8().to_vec(), + ) + .unwrap(); + *lock = i + .create_optimized_png(&oxipng::Options { + filter: indexset! { RowFilter::None }, + bit_depth_reduction: false, + color_type_reduction: false, + palette_reduction: false, + grayscale_reduction: false, + ..oxipng::Options::from_preset(0) + }) + .unwrap(); + let compression_took = compression_took.elapsed(); + let total = then.elapsed(); + ( + lock, + Some(RenderInfo { + deserialization: deser_took, + render: render_took, + compression: compression_took, + name, + total, + }), + ) + }, + ) + } +} #[poise::command(slash_command, prefix_command, category = "Info")] /// look at the current game. pub async fn view(ctx: Context<'_>) -> Result<()> { let _ = ctx.defer_or_broadcast().await; - send!(ctx.data().stdin, "save 0")?; - let _ = get_nextblock().await; - - // parsing the thing doesnt negate the need for a env var sooo - let o = std::fs::read(std::env::var("SAVE_PATH").unwrap())?; - let then = Instant::now(); - let m = MapSerializer(®).deserialize(&mut mindus::data::DataRead::new(&o))?; - let deser_took = then.elapsed(); - let name = m.tags.get("mapname").map_or("<unknown>", |v| v); - let render_took = Instant::now(); - let i = m.render(); - let render_took = render_took.elapsed(); - let compression_took = Instant::now(); - // TODO make render() return RgbImage - let i = RawImage::new( - i.width(), - i.height(), - ColorType::RGB { - transparent_color: None, - }, - BitDepth::Eight, - image::DynamicImage::ImageRgba8(i).to_rgb8().to_vec(), - ) - .unwrap(); - let b = i - .create_optimized_png(&oxipng::Options { - filter: indexset! { RowFilter::None }, - ..oxipng::Options::from_preset(0) - }) - .unwrap(); - use super::status::{humanize_bytes as human, Size}; - let size = human(Size::B(b.len() as f64)); - let compression_took = compression_took.elapsed(); - let took = then.elapsed(); + let (i, info) = MAP_IMAGE.get(&ctx.data().stdin).await?; poise::send_reply(ctx, |m| { m.attachment(AttachmentType::Bytes { - data: Cow::Owned(b), + data: Cow::Borrowed(&i), filename: "0.png".to_string(), }) - .embed(|e| e.attachment("0.png").color(SUCCESS).footer(|f| f.text(format!("render of {name} took: {:.2}s (deser: {}ms, render: {:.2}s, compression: {:.2}s ({size}))", took.as_secs_f32(), deser_took.as_millis(), render_took.as_secs_f32(), compression_took.as_secs_f32())))) + .embed(|e| { + if let Some(RenderInfo { deserialization, render, compression, total, name }) = info { + e.footer(|f| f.text(format!("render of {name} took: {:.2}s (deser: {}ms, render: {:.2}s, compression: {:.2}s)", total.as_secs_f32(), deserialization.as_millis(), render.as_secs_f32(), compression.as_secs_f32()))); + } + e.attachment("0.png").color(SUCCESS) + }) }) .await?; Ok(()) diff --git a/src/bot/mod.rs b/src/bot/mod.rs index 4289a4b..64c0486 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -2,7 +2,7 @@ mod admin; mod bans; mod config; mod js; -mod maps; +pub mod maps; mod player; mod schematic; mod status; diff --git a/src/server.rs b/src/server.rs index 14f080c..5596564 100644 --- a/src/server.rs +++ b/src/server.rs @@ -2,7 +2,7 @@ use crate::bot::Bot; use crate::process::Process; use axum::{ http::header::CONTENT_TYPE, - response::{AppendHeaders, Html}, + response::{AppendHeaders, Html, IntoResponse}, routing::get, Router, Server as AxumServer, }; @@ -41,16 +41,28 @@ macro_rules! html { macro_rules! png { ($file:expr) => { get(|| async { - { - ( - AppendHeaders([(CONTENT_TYPE, "image/png")]), - include_bytes!(concat!("../media/", stringify!($file), ".png")), - ) - } + ( + AppendHeaders([(CONTENT_TYPE, "image/png")]), + include_bytes!(concat!("../media/", stringify!($file), ".png")), + ) }) }; } +async fn map_view( + axum::extract::State(state): axum::extract::State<Arc<State>>, +) -> impl IntoResponse { + ( + AppendHeaders([(CONTENT_TYPE, "image/png")]), + crate::bot::maps::MAP_IMAGE + .get(&state.stdin) + .await + .unwrap() + .0 + .clone(), + ) +} + pub struct Server; impl Server { pub async fn spawn(addr: SocketAddr) { @@ -60,6 +72,7 @@ impl Server { .route("/", html!(index)) .route("/plaguess.png", png!(plaguess)) .route("/favicon.ico", png!(logo32)) + .route("/view", get(map_view)) .with_state(state.clone()); tokio::spawn(async move { AxumServer::bind(&addr) |