Add Heatmaps to UI
Add Heatmap analysis to website as well as a basic UI for viewing the Heatmaps. There are still issues, like some players not getting a heatmap assigned and heatmaps including data from warmup etc.
This commit is contained in:
@@ -63,7 +63,7 @@ pub async fn poll_next_task(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AnalysisInput {
|
||||
pub steamid: String,
|
||||
pub demoid: i64,
|
||||
@@ -120,3 +120,29 @@ pub fn analyse_base(input: AnalysisInput) -> BaseInfo {
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(input))]
|
||||
pub fn analyse_heatmap(input: AnalysisInput) -> std::collections::HashMap<String, analysis::heatmap::HeatMap> {
|
||||
tracing::info!("Generating HEATMAPs");
|
||||
|
||||
let file = std::fs::File::open(&input.path).unwrap();
|
||||
let mmap = unsafe { memmap2::MmapOptions::new().map(&file).unwrap() };
|
||||
|
||||
let config = analysis::heatmap::Config {
|
||||
cell_size: 5.0,
|
||||
};
|
||||
let (heatmaps, players) = analysis::heatmap::parse(&config, &mmap).unwrap();
|
||||
|
||||
tracing::info!("Got {} Heatmaps", heatmaps.len());
|
||||
heatmaps.into_iter().filter_map(|(userid, heatmap)| {
|
||||
let player = match players.get(&userid) {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
tracing::warn!("Could not find player: {:?}", userid);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some((player.xuid.to_string(), heatmap))
|
||||
}).collect()
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ pub mod steam {
|
||||
) -> Result<axum::response::Redirect, axum::http::StatusCode> {
|
||||
let url = state.openid.get_redirect_url();
|
||||
|
||||
tracing::info!("Redirecting to {:?}", url);
|
||||
|
||||
Ok(axum::response::Redirect::to(url))
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ where
|
||||
.route("/:id/info", axum::routing::get(info))
|
||||
.route("/:id/analysis/scoreboard", axum::routing::get(scoreboard))
|
||||
.route("/:id/reanalyse", axum::routing::get(analyise))
|
||||
.route("/:id/analysis/heatmap", axum::routing::get(heatmap))
|
||||
.with_state(Arc::new(DemoState {
|
||||
upload_folder: upload_folder.into(),
|
||||
}))
|
||||
@@ -239,3 +240,43 @@ async fn scoreboard(
|
||||
team2,
|
||||
}))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(session))]
|
||||
async fn heatmap(
|
||||
session: UserSession,
|
||||
Path(demo_id): Path<i64>,
|
||||
) -> Result<axum::response::Json<Vec<common::demo_analysis::PlayerHeatmap>>, axum::http::StatusCode> {
|
||||
use base64::prelude::Engine;
|
||||
|
||||
let query = crate::schema::demo_players::dsl::demo_players
|
||||
.inner_join(crate::schema::demo_heatmaps::dsl::demo_heatmaps.on(
|
||||
crate::schema::demo_players::dsl::steam_id.eq(crate::schema::demo_heatmaps::dsl::steam_id)
|
||||
.and(crate::schema::demo_players::dsl::demo_id.eq(crate::schema::demo_heatmaps::dsl::demo_id))
|
||||
)).filter(crate::schema::demo_players::dsl::demo_id.eq(demo_id));
|
||||
|
||||
let mut db_con = crate::db_connection().await;
|
||||
|
||||
let result: Vec<(crate::models::DemoPlayer, crate::models::DemoPlayerHeatmap)> = match query.load(&mut db_con).await {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
tracing::error!("Querying DB: {:?}", e);
|
||||
return Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR);;
|
||||
}
|
||||
};
|
||||
|
||||
let data: Vec<common::demo_analysis::PlayerHeatmap> = result.into_iter().map(|(player, heatmap)| {
|
||||
let mut heatmap: analysis::heatmap::HeatMap = serde_json::from_str(&heatmap.data).unwrap();
|
||||
heatmap.shrink();
|
||||
let h_image = heatmap.as_image();
|
||||
|
||||
let mut buffer = std::io::Cursor::new(Vec::new());
|
||||
h_image.write_to(&mut buffer, image::ImageFormat::Png).unwrap();
|
||||
|
||||
common::demo_analysis::PlayerHeatmap {
|
||||
name: player.name,
|
||||
png_data: base64::prelude::BASE64_STANDARD.encode(buffer.into_inner()),
|
||||
}
|
||||
}).collect();
|
||||
|
||||
Ok(axum::Json(data))
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ pub async fn run_api(
|
||||
"/api/",
|
||||
crate::api::router(crate::api::RouterConfig {
|
||||
steam_api_key: steam_api_key.into(),
|
||||
steam_callback_base_url: "http://192.168.0.156:3000".into(),
|
||||
steam_callback_base_url: "http://localhost:3000".into(),
|
||||
// steam_callback_base_url: "http://localhost:3000".into(),
|
||||
steam_callback_path: "/api/steam/callback".into(),
|
||||
upload_dir: upload_folder.clone(),
|
||||
@@ -101,15 +101,18 @@ pub async fn run_analysis(upload_folder: impl Into<std::path::PathBuf>) {
|
||||
|
||||
let demo_id = input.demoid;
|
||||
|
||||
let result = tokio::task::spawn_blocking(move || crate::analysis::analyse_base(input))
|
||||
let base_input = input.clone();
|
||||
let base_result = tokio::task::spawn_blocking(move || crate::analysis::analyse_base(base_input))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tracing::debug!("Analysis-Result: {:?}", result);
|
||||
let heatmap_result = tokio::task::spawn_blocking(move || crate::analysis::analyse_heatmap(input))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut db_con = crate::db_connection().await;
|
||||
|
||||
let (player_info, player_stats): (Vec<_>, Vec<_>) = result
|
||||
let (player_info, player_stats): (Vec<_>, Vec<_>) = base_result
|
||||
.players
|
||||
.into_iter()
|
||||
.map(|(info, stats)| {
|
||||
@@ -133,9 +136,19 @@ pub async fn run_analysis(upload_folder: impl Into<std::path::PathBuf>) {
|
||||
})
|
||||
.unzip();
|
||||
|
||||
let player_heatmaps: Vec<_> = heatmap_result.into_iter().map(|(player, heatmap)| {
|
||||
tracing::trace!("HeatMap for Player: {:?}", player);
|
||||
|
||||
crate::models::DemoPlayerHeatmap {
|
||||
demo_id,
|
||||
steam_id: player,
|
||||
data: serde_json::to_string(&heatmap).unwrap(),
|
||||
}
|
||||
}).collect();
|
||||
|
||||
let demo_info = crate::models::DemoInfo {
|
||||
demo_id,
|
||||
map: result.map,
|
||||
map: base_result.map,
|
||||
};
|
||||
|
||||
let store_demo_info_query =
|
||||
@@ -173,6 +186,11 @@ pub async fn run_analysis(upload_folder: impl Into<std::path::PathBuf>) {
|
||||
crate::schema::demo_player_stats::dsl::damage,
|
||||
)),
|
||||
));
|
||||
let store_demo_player_heatmaps_query = diesel::dsl::insert_into(crate::schema::demo_heatmaps::dsl::demo_heatmaps)
|
||||
.values(player_heatmaps)
|
||||
.on_conflict((crate::schema::demo_heatmaps::dsl::demo_id, crate::schema::demo_heatmaps::dsl::steam_id))
|
||||
.do_update()
|
||||
.set((crate::schema::demo_heatmaps::dsl::data.eq(diesel::upsert::excluded(crate::schema::demo_heatmaps::dsl::data))));
|
||||
let update_process_info =
|
||||
diesel::dsl::update(crate::schema::processing_status::dsl::processing_status)
|
||||
.set(crate::schema::processing_status::dsl::info.eq(1))
|
||||
@@ -184,6 +202,7 @@ pub async fn run_analysis(upload_folder: impl Into<std::path::PathBuf>) {
|
||||
store_demo_info_query.execute(conn).await?;
|
||||
store_demo_players_query.execute(conn).await?;
|
||||
store_demo_player_stats_query.execute(conn).await?;
|
||||
store_demo_player_heatmaps_query.execute(conn).await?;
|
||||
update_process_info.execute(conn).await?;
|
||||
Ok(())
|
||||
})
|
||||
@@ -191,7 +210,6 @@ pub async fn run_analysis(upload_folder: impl Into<std::path::PathBuf>) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// TODO
|
||||
// Remove task from queue
|
||||
tracing::info!("Stored analysis results");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ async fn main() {
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(tracing_subscriber::filter::filter_fn(|meta| {
|
||||
meta.target().contains("backend")
|
||||
|| meta.target().contains("analysis")
|
||||
}));
|
||||
tracing::subscriber::set_global_default(registry).unwrap();
|
||||
|
||||
|
||||
@@ -79,3 +79,12 @@ pub struct AnalysisTask {
|
||||
pub demo_id: i64,
|
||||
pub steam_id: String,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Selectable, Insertable, Debug)]
|
||||
#[diesel(table_name = crate::schema::demo_heatmaps)]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
pub struct DemoPlayerHeatmap {
|
||||
pub demo_id: i64,
|
||||
pub steam_id: String,
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
@@ -8,6 +8,14 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
demo_heatmaps (demo_id, steam_id) {
|
||||
demo_id -> Int8,
|
||||
steam_id -> Text,
|
||||
data -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
demo_info (demo_id) {
|
||||
demo_id -> Int8,
|
||||
@@ -66,6 +74,7 @@ diesel::table! {
|
||||
}
|
||||
|
||||
diesel::joinable!(analysis_queue -> demos (demo_id));
|
||||
diesel::joinable!(demo_heatmaps -> demo_info (demo_id));
|
||||
diesel::joinable!(demo_info -> demos (demo_id));
|
||||
diesel::joinable!(demo_player_stats -> demo_info (demo_id));
|
||||
diesel::joinable!(demo_players -> demo_info (demo_id));
|
||||
@@ -73,6 +82,7 @@ diesel::joinable!(processing_status -> demos (demo_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
analysis_queue,
|
||||
demo_heatmaps,
|
||||
demo_info,
|
||||
demo_player_stats,
|
||||
demo_players,
|
||||
|
||||
Reference in New Issue
Block a user