Formatting and minor code changes

This commit is contained in:
Lol3rrr
2024-10-18 13:11:10 +02:00
parent 835b4484dc
commit 6c098b412a
9 changed files with 198 additions and 135 deletions

View File

@@ -11,13 +11,24 @@ fn endofgame_nuke() {
let expected = endofgame::EndOfGame { let expected = endofgame::EndOfGame {
map: "de_nuke".to_owned(), map: "de_nuke".to_owned(),
teams: [(2, endofgame::TeamInfo { teams: [
end_score: 13, (
start_side: "CT".into(), 2,
}), (3, endofgame::TeamInfo { endofgame::TeamInfo {
end_score: 8, end_score: 13,
start_side: "TERRORIST".into() start_side: "CT".into(),
})].into_iter().collect(), },
),
(
3,
endofgame::TeamInfo {
end_score: 8,
start_side: "TERRORIST".into(),
},
),
]
.into_iter()
.collect(),
players: vec![ players: vec![
( (
endofgame::PlayerInfo { endofgame::PlayerInfo {

View File

@@ -36,10 +36,33 @@ pub static ANALYSIS_METHODS: std::sync::LazyLock<[std::sync::Arc<dyn Analysis +
] ]
}); });
pub async fn poll_next_task( #[derive(Debug)]
upload_folder: &std::path::Path, pub enum TaskError<AE> {
Diesel(diesel::result::Error),
RunningAction(AE),
}
impl<AE> From<diesel::result::Error> for TaskError<AE> {
fn from(value: diesel::result::Error) -> Self {
Self::Diesel(value)
}
}
pub async fn poll_next_task<A, AE>(
upload_folder: impl Into<std::path::PathBuf>,
db_con: &mut diesel_async::pg::AsyncPgConnection, db_con: &mut diesel_async::pg::AsyncPgConnection,
) -> Result<AnalysisInput, ()> { action: A,
) -> Result<(), TaskError<AE>>
where
A: Fn(
AnalysisInput,
&mut diesel_async::pg::AsyncPgConnection,
)
-> core::pin::Pin<Box<(dyn core::future::Future<Output = Result<(), AE>> + Send + '_)>>
+ Send
+ Clone
+ Sync,
{
let query = crate::schema::analysis_queue::dsl::analysis_queue let query = crate::schema::analysis_queue::dsl::analysis_queue
.order(crate::schema::analysis_queue::dsl::created_at.asc()) .order(crate::schema::analysis_queue::dsl::created_at.asc())
.limit(1) .limit(1)
@@ -47,50 +70,53 @@ pub async fn poll_next_task(
.for_update() .for_update()
.skip_locked(); .skip_locked();
let upload_folder: std::path::PathBuf = upload_folder.into();
loop { loop {
let upload_folder = upload_folder.clone();
let action = action.clone();
let result = db_con let result = db_con
.build_transaction() .build_transaction()
.run::<_, diesel::result::Error, _>(|conn| { .run::<_, TaskError<AE>, _>(|conn| {
Box::pin(async move { Box::pin(async move {
let mut results: Vec<crate::models::AnalysisTask> = query.load(conn).await?; let mut results: Vec<crate::models::AnalysisTask> = query.load(conn).await?;
let final_result = match results.pop() { let task = match results.pop() {
Some(r) => r, Some(r) => r,
None => return Ok(None), None => return Ok(None),
}; };
let input = AnalysisInput {
path: upload_folder
.join(&task.steam_id)
.join(format!("{}.dem", task.demo_id)),
steamid: task.steam_id.clone(),
demoid: task.demo_id.clone(),
};
let tmp = action(input, &mut *conn);
tmp.await.map_err(|e| TaskError::RunningAction(e))?;
let delete_query = let delete_query =
diesel::dsl::delete(crate::schema::analysis_queue::dsl::analysis_queue) diesel::dsl::delete(crate::schema::analysis_queue::dsl::analysis_queue)
.filter( .filter(crate::schema::analysis_queue::dsl::demo_id.eq(task.demo_id))
crate::schema::analysis_queue::dsl::demo_id .filter(crate::schema::analysis_queue::dsl::steam_id.eq(task.steam_id));
.eq(final_result.demo_id.clone()),
)
.filter(
crate::schema::analysis_queue::dsl::steam_id
.eq(final_result.steam_id.clone()),
);
delete_query.execute(conn).await?; delete_query.execute(conn).await?;
Ok(Some(final_result)) Ok(Some(()))
}) })
}) })
.await; .await;
match result { match result {
Ok(Some(r)) => { Ok(Some(())) => {
return Ok(AnalysisInput { return Ok(());
path: upload_folder
.join(&r.steam_id)
.join(format!("{}.dem", r.demo_id)),
steamid: r.steam_id,
demoid: r.demo_id,
});
} }
Ok(None) => { Ok(None) => {
tokio::time::sleep(std::time::Duration::from_secs(5)).await; tokio::time::sleep(std::time::Duration::from_secs(5)).await;
} }
Err(e) => { Err(e) => {
tracing::error!("Getting Task from Postgres: {:?}", e); return Err(e);
return Err(());
} }
}; };
} }

View File

@@ -456,7 +456,11 @@ async fn perround(
.map(|dteam| common::demo_analysis::PerRoundTeam { .map(|dteam| common::demo_analysis::PerRoundTeam {
name: dteam.start_name, name: dteam.start_name,
number: dteam.team as u32, number: dteam.team as u32,
players: players.iter().filter(|p| p.team == dteam.team).map(|p| p.name.clone()).collect(), players: players
.iter()
.filter(|p| p.team == dteam.team)
.map(|p| p.name.clone())
.collect(),
}) })
.collect(); .collect();

View File

@@ -64,7 +64,8 @@ pub async fn run_api(
let serve_dir = option_env!("FRONTEND_DIST_DIR").unwrap_or("../frontend/dist/"); let serve_dir = option_env!("FRONTEND_DIST_DIR").unwrap_or("../frontend/dist/");
tracing::debug!("Serving static files from {:?}", serve_dir); tracing::debug!("Serving static files from {:?}", serve_dir);
let steam_callback_base_url = std::env::var("BASE_URL").unwrap_or("http://localhost:3000".to_owned()); let steam_callback_base_url =
std::env::var("BASE_URL").unwrap_or("http://localhost:3000".to_owned());
tracing::debug!("Base-URL: {:?}", steam_callback_base_url); tracing::debug!("Base-URL: {:?}", steam_callback_base_url);
let router = axum::Router::new() let router = axum::Router::new()
@@ -93,68 +94,63 @@ pub async fn run_api(
#[tracing::instrument(skip(upload_folder))] #[tracing::instrument(skip(upload_folder))]
pub async fn run_analysis(upload_folder: impl Into<std::path::PathBuf>) { pub async fn run_analysis(upload_folder: impl Into<std::path::PathBuf>) {
use diesel::prelude::*; use diesel::prelude::*;
use diesel_async::{AsyncConnection, RunQueryDsl}; use diesel_async::RunQueryDsl;
let upload_folder: std::path::PathBuf = upload_folder.into(); let upload_folder: std::path::PathBuf = upload_folder.into();
loop { loop {
let mut db_con = db_connection().await; let mut db_con = db_connection().await;
let input = match crate::analysis::poll_next_task(&upload_folder, &mut db_con).await {
Ok(i) => i,
Err(e) => {
tracing::error!("Polling for next Task: {:?}", e);
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
continue;
}
};
let demo_id = input.demoid.clone(); let res = crate::analysis::poll_next_task(
&upload_folder,
let mut store_result_fns = Vec::new(); &mut db_con,
for analysis in analysis::ANALYSIS_METHODS.iter().map(|a| a.clone()) { move |input: analysis::AnalysisInput, db_con: &mut diesel_async::AsyncPgConnection| {
let input = input.clone();
let store_result =
match tokio::task::spawn_blocking(move || analysis.analyse(input)).await {
Ok(Ok(r)) => r,
Ok(Err(e)) => {
tracing::error!("Analysis failed: {:?}", e);
continue;
}
Err(e) => {
tracing::error!("Joining Task: {:?}", e);
continue;
}
};
store_result_fns.push(store_result);
}
let mut db_con = crate::db_connection().await;
let update_process_info =
diesel::dsl::update(crate::schema::processing_status::dsl::processing_status)
.set(crate::schema::processing_status::dsl::info.eq(1))
.filter(crate::schema::processing_status::dsl::demo_id.eq(demo_id));
let store_res = db_con
.transaction::<'_, '_, '_, _, diesel::result::Error, _>(|conn| {
Box::pin(async move { Box::pin(async move {
for store_fn in store_result_fns { let demo_id = input.demoid.clone();
store_fn(conn).await?;
}
update_process_info.execute(conn).await?;
Ok(()) let mut store_result_fns = Vec::new();
for analysis in analysis::ANALYSIS_METHODS.iter().map(|a| a.clone()) {
let input = input.clone();
let store_result = match tokio::task::spawn_blocking(move || {
analysis.analyse(input)
})
.await
{
Ok(Ok(r)) => r,
Ok(Err(e)) => {
tracing::error!("Analysis failed: {:?}", e);
continue;
}
Err(e) => {
tracing::error!("Joining Task: {:?}", e);
continue;
}
};
store_result_fns.push(store_result);
}
let update_process_info = diesel::dsl::update(
crate::schema::processing_status::dsl::processing_status,
)
.set(crate::schema::processing_status::dsl::info.eq(1))
.filter(crate::schema::processing_status::dsl::demo_id.eq(demo_id));
for store_fn in store_result_fns {
store_fn(db_con).await.map_err(|e| ())?;
}
update_process_info.execute(db_con).await.map_err(|e| ())?;
Ok::<(), ()>(())
}) })
}) },
.await; )
match store_res { .await;
Ok(_) => {
tracing::info!("Stored analysis results"); if let Err(e) = res {
} tracing::error!("Polling for next Task: {:?}", e);
Err(e) => { tokio::time::sleep(std::time::Duration::from_secs(30)).await;
tracing::error!("Failed to store results: {:?}", e); continue;
} }
};
} }
} }

View File

@@ -2,8 +2,8 @@ use leptos::*;
use leptos_router::{Outlet, A}; use leptos_router::{Outlet, A};
pub mod heatmap; pub mod heatmap;
pub mod scoreboard;
pub mod perround; pub mod perround;
pub mod scoreboard;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct CurrentDemoName(ReadSignal<String>); struct CurrentDemoName(ReadSignal<String>);
@@ -31,10 +31,10 @@ pub fn demo() -> impl leptos::IntoView {
}, },
); );
let rerun_analysis = create_action(move |_: &()| { let rerun_analysis = create_action(move |_: &()| async move {
async move { let _ = reqwasm::http::Request::get(&format!("/api/demos/{}/reanalyse", id()))
let _ = reqwasm::http::Request::get(&format!("/api/demos/{}/reanalyse", id())).send().await; .send()
} .await;
}); });
let map = move || match demo_info.get() { let map = move || match demo_info.get() {

View File

@@ -4,19 +4,17 @@ use super::CurrentDemoName;
#[leptos::component] #[leptos::component]
pub fn heatmaps() -> impl leptos::IntoView { pub fn heatmaps() -> impl leptos::IntoView {
let heatmaps_resource = let heatmaps_resource = create_resource(leptos_router::use_params_map(), |params| async move {
create_resource(leptos_router::use_params_map(), |params| async move { let id = params.get("id").unwrap();
let id = params.get("id").unwrap();
let res = let res = reqwasm::http::Request::get(&format!("/api/demos/{}/analysis/heatmap", id))
reqwasm::http::Request::get(&format!("/api/demos/{}/analysis/heatmap", id)) .send()
.send() .await
.await .unwrap();
.unwrap(); res.json::<Vec<common::demo_analysis::PlayerHeatmap>>()
res.json::<Vec<common::demo_analysis::PlayerHeatmap>>() .await
.await .unwrap()
.unwrap() });
});
let style = stylers::style! { let style = stylers::style! {
"Heatmap-Wrapper", "Heatmap-Wrapper",
@@ -111,17 +109,27 @@ fn heatmap_view(heatmaps: Vec<common::demo_analysis::PlayerHeatmap>) -> impl lep
let player = players.get(idx).unwrap(); let player = players.get(idx).unwrap();
set_value(heatmaps.iter().filter(|h| &h.name == player).cloned().collect()); set_value(
heatmaps
.iter()
.filter(|h| &h.name == player)
.cloned()
.collect(),
);
set_idx(idx); set_idx(idx);
}; };
let players = og_players; let players = og_players;
let select_values = move || { let select_values = move || {
players.iter().enumerate().map(|(idx, name)| { players
view! { .iter()
<option value={idx}>{ format!("{}", name) }</option> .enumerate()
} .map(|(idx, name)| {
}).collect::<Vec<_>>() view! {
<option value={idx}>{ format!("{}", name) }</option>
}
})
.collect::<Vec<_>>()
}; };
view! { view! {

View File

@@ -14,19 +14,17 @@ fn to_coloumn(idx: usize) -> usize {
#[leptos::component] #[leptos::component]
pub fn per_round() -> impl leptos::IntoView { pub fn per_round() -> impl leptos::IntoView {
let perround_resource = let perround_resource = create_resource(leptos_router::use_params_map(), |params| async move {
create_resource(leptos_router::use_params_map(), |params| async move { let id = params.get("id").unwrap();
let id = params.get("id").unwrap();
let res = let res = reqwasm::http::Request::get(&format!("/api/demos/{}/analysis/perround", id))
reqwasm::http::Request::get(&format!("/api/demos/{}/analysis/perround", id)) .send()
.send() .await
.await .unwrap();
.unwrap(); res.json::<common::demo_analysis::PerRoundResult>()
res.json::<common::demo_analysis::PerRoundResult>() .await
.await .unwrap()
.unwrap() });
});
let style = stylers::style! { let style = stylers::style! {
"PerRound", "PerRound",
@@ -73,7 +71,10 @@ pub fn per_round() -> impl leptos::IntoView {
let events_list = move || { let events_list = move || {
let round_index = round(); let round_index = round();
let data = perround_resource.get(); let data = perround_resource.get();
let current_round = data.as_ref().map(|rs| rs.rounds.get(round_index).cloned()).flatten(); let current_round = data
.as_ref()
.map(|rs| rs.rounds.get(round_index).cloned())
.flatten();
let teams = data.as_ref().map(|rs| rs.teams.clone()); let teams = data.as_ref().map(|rs| rs.teams.clone());
match (current_round, teams) { match (current_round, teams) {
@@ -171,13 +172,22 @@ pub fn per_round() -> impl leptos::IntoView {
None => return view! {}.into_view(), None => return view! {}.into_view(),
}; };
let upper = perround_teams.iter().find(|t| t.name == "CT").map(|t| t.number).unwrap_or(0); let upper = perround_teams
let lower = perround_teams.iter().find(|t| t.name == "TERRORIST").map(|t| t.number).unwrap_or(0); .iter()
.find(|t| t.name == "CT")
.map(|t| t.number)
.unwrap_or(0);
let lower = perround_teams
.iter()
.find(|t| t.name == "TERRORIST")
.map(|t| t.number)
.unwrap_or(0);
view! { view! {
<span style="grid-column: 1; grid-row: 1">Team { upper }</span> <span style="grid-column: 1; grid-row: 1">Team { upper }</span>
<span style="grid-column: 1; grid-row: 3">Team { lower }</span> <span style="grid-column: 1; grid-row: 3">Team { lower }</span>
}.into_view() }
.into_view()
}; };
view! { view! {

View File

@@ -25,8 +25,10 @@ pub fn scoreboard() -> impl leptos::IntoView {
.get() .get()
.into_iter() .into_iter()
.flat_map(|v| v.teams.into_iter()) .flat_map(|v| v.teams.into_iter())
.map(|(team, players)| view! { .map(|(team, players)| {
<TeamScoreboard value=players team_name=format!("Team {}", team) /> view! {
<TeamScoreboard value=players team_name=format!("Team {}", team) />
}
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
@@ -46,7 +48,10 @@ mod orderings {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Ordering { pub struct Ordering {
name: SelectedStat, name: SelectedStat,
pub sort_fn: fn(p1: &common::demo_analysis::ScoreBoardPlayer, p2: &common::demo_analysis::ScoreBoardPlayer) -> core::cmp::Ordering, pub sort_fn: fn(
p1: &common::demo_analysis::ScoreBoardPlayer,
p2: &common::demo_analysis::ScoreBoardPlayer,
) -> core::cmp::Ordering,
} }
impl Ordering { impl Ordering {
@@ -89,7 +94,10 @@ mod orderings {
} }
#[leptos::component] #[leptos::component]
fn team_scoreboard(value: Vec<common::demo_analysis::ScoreBoardPlayer>, team_name: String) -> impl IntoView { fn team_scoreboard(
value: Vec<common::demo_analysis::ScoreBoardPlayer>,
team_name: String,
) -> impl IntoView {
let (ordering, set_ordering) = create_signal::<orderings::Ordering>(orderings::DAMAGE); let (ordering, set_ordering) = create_signal::<orderings::Ordering>(orderings::DAMAGE);
let style = stylers::style! { let style = stylers::style! {