smol bot
Diffstat (limited to 'src/bot/map.rs')
| -rw-r--r-- | src/bot/map.rs | 171 |
1 files changed, 110 insertions, 61 deletions
diff --git a/src/bot/map.rs b/src/bot/map.rs index 3ea848b..4188fc9 100644 --- a/src/bot/map.rs +++ b/src/bot/map.rs @@ -1,72 +1,121 @@ use anyhow::Result; use mindus::{data::map::ReadError, *}; -use poise::serenity_prelude::*; -use std::time::Instant; +use poise::{serenity_prelude::*, CreateReply}; +use std::{ + ops::ControlFlow, + time::{Duration, Instant}, +}; use super::{strip_colors, SUCCESS}; -pub async fn with(msg: &Message, c: &serenity::client::Context) -> Result<()> { - let auth = msg.author_nick(c).await.unwrap_or(msg.author.name.clone()); - for a in &msg.attachments { - if a.filename.ends_with("msav") { - let s = a.download().await?; - let then = Instant::now(); +fn string((x, f): (ReadError, &str)) -> String { + match x { + ReadError::Decompress(_) | ReadError::Header(_) => { + format!("not a map.") + } + ReadError::NoSuchBlock(b) => { + format!("couldnt find block `{b}`. error originates from `{f}`") + } + ReadError::Version(v) => { + format!( + "unsupported version: `{v}`. supported versions: `7`. error originates from `{f}`", + ) + } + ReadError::Read(r) => { + format!("failed to read map. error: `{r}`. originates from `{f}`") + } + ReadError::ReadState(r) => { + format!("failed to read dyn data in map. error: `{r}`. originates from `{f}`") + } + } +} + +pub async fn download(a: &Attachment) -> Result<(Result<Map, (ReadError, &str)>, Duration)> { + let s = a.download().await?; + let then = Instant::now(); - macro_rules! dang { - ($fmt:literal $(, $args:expr)+) => {{ - msg.reply_ping(c, format!($fmt $(, $args)+)).await?; - return Ok(()); - }}; - } - // could ignore, but i think if you have a msav, you dont want to ignore failures. - let m = match Map::deserialize(&mut mindus::data::DataRead::new(&s)) { - Ok(m) => m, - Err(ReadError::Decompress(_) | ReadError::Header(_)) => { - dang!("`{}` is not a map.", a.filename) - } - Err(ReadError::NoSuchBlock(b)) => dang!( - "couldnt find block `{b}`. error originates from `{}`", - a.filename - ), - Err(ReadError::Version(v)) => { - dang!( - "unsupported version: `{v}`. supported versions: `7`. error originates from `{}`", - a.filename - ) - } - Err(ReadError::Read(r)) => { - dang!( - "failed to read map. error: `{r}`. originates from `{}`", - a.filename - ) - } - Err(ReadError::ReadState(r)) => { - dang!( - "failed to read dyn data in map. error: `{r}`. originates from `{}`", - a.filename - ) - } - }; - let t = msg.channel_id.start_typing(&c.http); - let deser_took = then.elapsed(); - let name = strip_colors(m.tags.get("name").or(m.tags.get("mapname")).unwrap()); - let (render_took, compression_took, total, png) = - tokio::task::spawn_blocking(move || { - let render_took = Instant::now(); - let i = m.render(); - let render_took = render_took.elapsed(); - let compression_took = Instant::now(); - let png = super::png(i); - let compression_took = compression_took.elapsed(); - let total = then.elapsed(); - (render_took, compression_took, total, png) - }) - .await?; - t.stop(); - msg.channel_id.send_message(c,CreateMessage::new().add_file(CreateAttachment::bytes(png,"map.png")).embed(CreateEmbed::new().title(&name).footer(CreateEmbedFooter::new(format!("render of {name} (requested by {auth}) took: {:.3}s (deser: {}ms, render: {:.3}s, compression: {:.3}s)", total.as_secs_f32(), deser_took.as_millis(), render_took.as_secs_f32(), compression_took.as_secs_f32()))).attachment("map.png").color(SUCCESS))).await?; - return Ok(()); + // could ignore, but i think if you have a msav, you dont want to ignore failures. + Ok(( + Map::deserialize(&mut mindus::data::DataRead::new(&s)).map_err(|x| (x, &*a.filename)), + then.elapsed(), + )) +} + +pub async fn scour(m: &Message) -> Result<Option<(Result<Map, (ReadError, &str)>, Duration)>> { + for a in &m.attachments { + if a.filename.ends_with("msav") { + return Ok(Some(download(a).await?)); } } + Ok(None) +} + +pub async fn reply(a: &Attachment) -> Result<ControlFlow<CreateReply, String>> { + let (m, deser_took) = match download(a).await? { + (Err(e), _) => return Ok(ControlFlow::Continue(string(e))), + (Ok(m), deser_took) => (m, deser_took), + }; + let name = strip_colors(m.tags.get("name").or(m.tags.get("mapname")).unwrap()); + let ( + Timings { + deser_took, + render_took, + compression_took, + total, + }, + png, + ) = render(m, deser_took).await; + Ok(ControlFlow::Break(CreateReply::default().attachment(CreateAttachment::bytes(png,"map.png")).embed(CreateEmbed::new().title(&name).footer(CreateEmbedFooter::new(format!("render of {name} took: {:.3}s (deser: {}ms, render: {:.3}s, compression: {:.3}s)", total.as_secs_f32(), deser_took.as_millis(), render_took.as_secs_f32(), compression_took.as_secs_f32()))).attachment("map.png").color(SUCCESS)))) +} +struct Timings { + deser_took: Duration, + render_took: Duration, + compression_took: Duration, + total: Duration, +} +async fn render(m: Map, deser_took: Duration) -> (Timings, Vec<u8>) { + tokio::task::spawn_blocking(move || { + let render_took = Instant::now(); + let i = m.render(); + let render_took = render_took.elapsed(); + let compression_took = Instant::now(); + let png = super::png(i); + let compression_took = compression_took.elapsed(); + let total = deser_took + render_took + compression_took; + ( + Timings { + deser_took, + render_took, + compression_took, + total, + }, + png, + ) + }) + .await + .unwrap() +} + +pub async fn with(msg: &Message, c: &serenity::client::Context) -> Result<()> { + let auth = msg.author_nick(c).await.unwrap_or(msg.author.name.clone()); + let (m, deser_took) = match scour(msg).await? { + None => return Ok(()), + Some((Err(e), _)) => return Ok(drop(msg.reply(c, string(e)).await?)), + Some((Ok(m), deser_took)) => (m, deser_took), + }; + let t = msg.channel_id.start_typing(&c.http); + let name = strip_colors(m.tags.get("name").or(m.tags.get("mapname")).unwrap()); + let ( + Timings { + deser_took, + render_took, + compression_took, + total, + }, + png, + ) = render(m, deser_took).await; + t.stop(); + msg.channel_id.send_message(c,CreateMessage::new().add_file(CreateAttachment::bytes(png,"map.png")).embed(CreateEmbed::new().title(&name).footer(CreateEmbedFooter::new(format!("render of {name} (requested by {auth}) took: {:.3}s (deser: {}ms, render: {:.3}s, compression: {:.3}s)", total.as_secs_f32(), deser_took.as_millis(), render_took.as_secs_f32(), compression_took.as_secs_f32()))).attachment("map.png").color(SUCCESS))).await?; Ok(()) } |