html terminal
add a /view endpoint
bendn 2023-08-05
parent 3fda5ac · commit a2440c5
-rw-r--r--html-src/index.html33
-rw-r--r--src/bot/maps.rs137
-rw-r--r--src/bot/mod.rs2
-rw-r--r--src/server.rs27
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(&REG).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(&REG).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)