Fix formatting and implement the end of game score display

This commit is contained in:
Lol3rrr
2024-10-14 21:20:26 +02:00
parent 6f255faeb3
commit a2be4c2167
13 changed files with 401 additions and 200 deletions

View File

@@ -107,6 +107,13 @@ pub struct AnalysisInput {
pub struct BaseInfo { pub struct BaseInfo {
pub map: String, pub map: String,
pub players: Vec<(BasePlayerInfo, BasePlayerStats)>, pub players: Vec<(BasePlayerInfo, BasePlayerStats)>,
pub teams: Vec<(u32, BaseTeamInfo)>
}
#[derive(Debug)]
pub struct BaseTeamInfo {
pub score: usize,
pub name: String,
} }
#[derive(Debug)] #[derive(Debug)]

View File

@@ -10,7 +10,24 @@ impl BaseAnalysis {
impl Analysis for BaseAnalysis { impl Analysis for BaseAnalysis {
#[tracing::instrument(name = "Base", skip(self, input))] #[tracing::instrument(name = "Base", skip(self, input))]
fn analyse(&self, input: AnalysisInput) -> Result<Box<dyn FnOnce(&mut diesel_async::pg::AsyncPgConnection) -> core::pin::Pin<Box<(dyn core::future::Future<Output = Result<(), diesel::result::Error>> + Send + '_)>> + Send>, ()> { tracing::info!("Performing Base analysis"); fn analyse(
&self,
input: AnalysisInput,
) -> Result<
Box<
dyn FnOnce(
&mut diesel_async::pg::AsyncPgConnection,
) -> core::pin::Pin<
Box<
(dyn core::future::Future<Output = Result<(), diesel::result::Error>>
+ Send
+ '_),
>,
> + Send,
>,
(),
> {
tracing::info!("Performing Base analysis");
let file = std::fs::File::open(&input.path).unwrap(); let file = std::fs::File::open(&input.path).unwrap();
let mmap = unsafe { memmap2::MmapOptions::new().map(&file).unwrap() }; let mmap = unsafe { memmap2::MmapOptions::new().map(&file).unwrap() };
@@ -19,20 +36,33 @@ impl Analysis for BaseAnalysis {
let base_result = BaseInfo { let base_result = BaseInfo {
map: result.map, map: result.map,
players: result.players.into_iter().map(|(info, stats)| { players: result
(BasePlayerInfo { .players
name: info.name, .into_iter()
steam_id: info.steam_id, .map(|(info, stats)| {
team: info.team, (
ingame_id: info.ingame_id, BasePlayerInfo {
color: info.color, name: info.name,
}, BasePlayerStats { steam_id: info.steam_id,
kills: stats.kills, team: info.team,
assists: stats.assists, ingame_id: info.ingame_id,
damage: stats.damage, color: info.color,
deaths: stats.deaths, },
BasePlayerStats {
kills: stats.kills,
assists: stats.assists,
damage: stats.damage,
deaths: stats.deaths,
},
)
}) })
}).collect() .collect(),
teams: result.teams.into_iter().map(|(numb, val)| {
(numb, BaseTeamInfo {
name: val.name,
score: val.score,
})
}).collect(),
}; };
let (player_info, player_stats): (Vec<_>, Vec<_>) = base_result let (player_info, player_stats): (Vec<_>, Vec<_>) = base_result
@@ -64,48 +94,68 @@ impl Analysis for BaseAnalysis {
map: base_result.map, map: base_result.map,
}; };
let demo_teams: Vec<crate::models::DemoTeam> = base_result.teams.into_iter().map(|(numb, info)| {
crate::models::DemoTeam {
demo_id: input.demoid.clone(),
team: numb as i16,
end_score: info.score as i16,
start_name: info.name,
}
}).collect();
Ok(Box::new(move |connection| { Ok(Box::new(move |connection| {
let store_demo_info_query = let store_demo_info_query =
diesel::dsl::insert_into(crate::schema::demo_info::dsl::demo_info) diesel::dsl::insert_into(crate::schema::demo_info::dsl::demo_info)
.values(demo_info) .values(demo_info)
.on_conflict(crate::schema::demo_info::dsl::demo_id) .on_conflict(crate::schema::demo_info::dsl::demo_id)
.do_update() .do_update()
.set( .set(
crate::schema::demo_info::dsl::map crate::schema::demo_info::dsl::map
.eq(diesel::upsert::excluded(crate::schema::demo_info::dsl::map)), .eq(diesel::upsert::excluded(crate::schema::demo_info::dsl::map)),
); );
let store_demo_players_query = let store_demo_players_query =
diesel::dsl::insert_into(crate::schema::demo_players::dsl::demo_players) diesel::dsl::insert_into(crate::schema::demo_players::dsl::demo_players)
.values(player_info) .values(player_info)
.on_conflict_do_nothing(); .on_conflict_do_nothing();
let store_demo_player_stats_query = let store_demo_player_stats_query =
diesel::dsl::insert_into(crate::schema::demo_player_stats::dsl::demo_player_stats) diesel::dsl::insert_into(crate::schema::demo_player_stats::dsl::demo_player_stats)
.values(player_stats) .values(player_stats)
.on_conflict(( .on_conflict((
crate::schema::demo_player_stats::dsl::demo_id, crate::schema::demo_player_stats::dsl::demo_id,
crate::schema::demo_player_stats::dsl::steam_id, crate::schema::demo_player_stats::dsl::steam_id,
)) ))
.do_update() .do_update()
.set(( .set((
crate::schema::demo_player_stats::dsl::deaths.eq(diesel::upsert::excluded( crate::schema::demo_player_stats::dsl::deaths.eq(diesel::upsert::excluded(
crate::schema::demo_player_stats::dsl::deaths, crate::schema::demo_player_stats::dsl::deaths,
)), )),
crate::schema::demo_player_stats::dsl::kills.eq(diesel::upsert::excluded( crate::schema::demo_player_stats::dsl::kills.eq(diesel::upsert::excluded(
crate::schema::demo_player_stats::dsl::kills, crate::schema::demo_player_stats::dsl::kills,
)), )),
crate::schema::demo_player_stats::dsl::assists.eq(diesel::upsert::excluded( crate::schema::demo_player_stats::dsl::assists.eq(
crate::schema::demo_player_stats::dsl::assists, diesel::upsert::excluded(
)), crate::schema::demo_player_stats::dsl::assists,
crate::schema::demo_player_stats::dsl::damage.eq(diesel::upsert::excluded( ),
crate::schema::demo_player_stats::dsl::damage, ),
)), crate::schema::demo_player_stats::dsl::damage.eq(diesel::upsert::excluded(
crate::schema::demo_player_stats::dsl::damage,
)),
));
let store_demo_teams = diesel::dsl::insert_into(crate::schema::demo_teams::dsl::demo_teams)
.values(demo_teams).on_conflict((crate::schema::demo_teams::dsl::demo_id, crate::schema::demo_teams::dsl::team))
.do_update()
.set((
crate::schema::demo_teams::dsl::start_name.eq(diesel::upsert::excluded(crate::schema::demo_teams::dsl::start_name)),
crate::schema::demo_teams::dsl::end_score.eq(diesel::upsert::excluded(crate::schema::demo_teams::dsl::end_score)),
)); ));
Box::pin(async move { Box::pin(async move {
store_demo_info_query.execute(connection).await?; store_demo_info_query.execute(connection).await?;
store_demo_players_query.execute(connection).await?; store_demo_players_query.execute(connection).await?;
store_demo_player_stats_query.execute(connection).await?; store_demo_player_stats_query.execute(connection).await?;
store_demo_teams.execute(connection).await?;
Ok(()) Ok(())
}) })

View File

@@ -10,47 +10,77 @@ impl HeatmapAnalysis {
impl Analysis for HeatmapAnalysis { impl Analysis for HeatmapAnalysis {
#[tracing::instrument(name = "Heatmap", skip(self, input))] #[tracing::instrument(name = "Heatmap", skip(self, input))]
fn analyse(&self, input: AnalysisInput) -> Result<Box<dyn FnOnce(&mut diesel_async::pg::AsyncPgConnection) -> core::pin::Pin<Box<(dyn core::future::Future<Output = Result<(), diesel::result::Error>> + Send + '_)>> + Send>, ()> { fn analyse(
&self,
input: AnalysisInput,
) -> Result<
Box<
dyn FnOnce(
&mut diesel_async::pg::AsyncPgConnection,
) -> core::pin::Pin<
Box<
(dyn core::future::Future<Output = Result<(), diesel::result::Error>>
+ Send
+ '_),
>,
> + Send,
>,
(),
> {
tracing::info!("Generating HEATMAPs"); tracing::info!("Generating HEATMAPs");
let file = std::fs::File::open(&input.path).unwrap(); let file = std::fs::File::open(&input.path).unwrap();
let mmap = unsafe { memmap2::MmapOptions::new().map(&file).unwrap() }; let mmap = unsafe { memmap2::MmapOptions::new().map(&file).unwrap() };
let config = analysis::heatmap::Config { let config = analysis::heatmap::Config { cell_size: 5.0 };
cell_size: 5.0, let result = analysis::heatmap::parse(&config, &mmap).unwrap();
};
let result = analysis::heatmap::parse(&config, &mmap).unwrap();
tracing::info!("Got {} Entity-Heatmaps", result.player_heatmaps.len()); tracing::info!("Got {} Entity-Heatmaps", result.player_heatmaps.len());
let heatmap_result: Vec<_> = result.player_heatmaps.into_iter().filter_map(|((userid, team), heatmap)| { let heatmap_result: Vec<_> = result
let player = match result.player_info.get(&userid) { .player_heatmaps
Some(p) => p, .into_iter()
None => { .filter_map(|((userid, team), heatmap)| {
tracing::warn!("Could not find player: {:?}", userid); let player = match result.player_info.get(&userid) {
return None; Some(p) => p,
} None => {
}; tracing::warn!("Could not find player: {:?}", userid);
return None;
}
};
Some(((player.xuid.to_string(), team), heatmap)) Some(((player.xuid.to_string(), team), heatmap))
}).collect(); })
.collect();
let player_heatmaps: Vec<_> = heatmap_result.into_iter().map(|((player, team), heatmap)| { let player_heatmaps: Vec<_> = heatmap_result
tracing::trace!("HeatMap for Player: {:?} in Team {:?}", player, team); .into_iter()
.map(|((player, team), heatmap)| {
tracing::trace!("HeatMap for Player: {:?} in Team {:?}", player, team);
crate::models::DemoPlayerHeatmap { crate::models::DemoPlayerHeatmap {
demo_id: input.demoid.clone(), demo_id: input.demoid.clone(),
steam_id: player, steam_id: player,
team, team,
data: serde_json::to_string(&heatmap).unwrap(), data: serde_json::to_string(&heatmap).unwrap(),
} }
}).collect(); })
.collect();
Ok(Box::new(move |connection| { Ok(Box::new(move |connection| {
let store_demo_player_heatmaps_query = diesel::dsl::insert_into(crate::schema::demo_heatmaps::dsl::demo_heatmaps) let store_demo_player_heatmaps_query =
.values(player_heatmaps) diesel::dsl::insert_into(crate::schema::demo_heatmaps::dsl::demo_heatmaps)
.on_conflict((crate::schema::demo_heatmaps::dsl::demo_id, crate::schema::demo_heatmaps::dsl::steam_id, crate::schema::demo_heatmaps::dsl::team)) .values(player_heatmaps)
.do_update() .on_conflict((
.set(crate::schema::demo_heatmaps::dsl::data.eq(diesel::upsert::excluded(crate::schema::demo_heatmaps::dsl::data))); crate::schema::demo_heatmaps::dsl::demo_id,
crate::schema::demo_heatmaps::dsl::steam_id,
crate::schema::demo_heatmaps::dsl::team,
))
.do_update()
.set(
crate::schema::demo_heatmaps::dsl::data.eq(diesel::upsert::excluded(
crate::schema::demo_heatmaps::dsl::data,
)),
);
Box::pin(async move { Box::pin(async move {
store_demo_player_heatmaps_query.execute(connection).await?; store_demo_player_heatmaps_query.execute(connection).await?;

View File

@@ -10,22 +10,41 @@ impl PerRoundAnalysis {
impl Analysis for PerRoundAnalysis { impl Analysis for PerRoundAnalysis {
#[tracing::instrument(name = "PerRoundAnalysis", skip(self, input))] #[tracing::instrument(name = "PerRoundAnalysis", skip(self, input))]
fn analyse(&self, input: AnalysisInput) -> Result<Box<dyn FnOnce(&mut diesel_async::pg::AsyncPgConnection) -> core::pin::Pin<Box<(dyn core::future::Future<Output = Result<(), diesel::result::Error>> + Send + '_)>> + Send>, ()> { fn analyse(
&self,
input: AnalysisInput,
) -> Result<
Box<
dyn FnOnce(
&mut diesel_async::pg::AsyncPgConnection,
) -> core::pin::Pin<
Box<
(dyn core::future::Future<Output = Result<(), diesel::result::Error>>
+ Send
+ '_),
>,
> + Send,
>,
(),
> {
let file = std::fs::File::open(&input.path).unwrap(); let file = std::fs::File::open(&input.path).unwrap();
let mmap = unsafe { memmap2::MmapOptions::new().map(&file).unwrap() }; let mmap = unsafe { memmap2::MmapOptions::new().map(&file).unwrap() };
let result = analysis::perround::parse(&mmap).unwrap(); let result = analysis::perround::parse(&mmap).unwrap();
let values: Vec<crate::models::DemoRound> = result.rounds.into_iter().enumerate().map(|(i, r)| { let values: Vec<crate::models::DemoRound> = result
crate::models::DemoRound { .rounds
.into_iter()
.enumerate()
.map(|(i, r)| crate::models::DemoRound {
demo_id: input.demoid.clone(), demo_id: input.demoid.clone(),
round_number: i as i16, round_number: i as i16,
start_tick: r.start as i64, start_tick: r.start as i64,
end_tick: r.end as i64, end_tick: r.end as i64,
win_reason: serde_json::to_string(&r.winreason).unwrap(), win_reason: serde_json::to_string(&r.winreason).unwrap(),
events: serde_json::to_value(&r.events).unwrap(), events: serde_json::to_value(&r.events).unwrap(),
} })
}).collect(); .collect();
Ok(Box::new(move |connection| { Ok(Box::new(move |connection| {
Box::pin(async move { Box::pin(async move {

View File

@@ -41,28 +41,46 @@ async fn list(
tracing::info!("SteamID: {:?}", steam_id); tracing::info!("SteamID: {:?}", steam_id);
let query = crate::schema::demos::dsl::demos let query = crate::schema::demos::dsl::demos
.inner_join(crate::schema::demo_info::table.on( .inner_join(
crate::schema::demos::dsl::demo_id.eq(crate::schema::demo_info::dsl::demo_id), crate::schema::demo_info::table
)) .on(crate::schema::demos::dsl::demo_id.eq(crate::schema::demo_info::dsl::demo_id)),
)
.inner_join(
crate::schema::demo_teams::table
.on(crate::schema::demos::dsl::demo_id.eq(crate::schema::demo_teams::dsl::demo_id)),
)
.select(( .select((
crate::models::Demo::as_select(), crate::models::Demo::as_select(),
crate::models::DemoInfo::as_select(), crate::models::DemoInfo::as_select(),
crate::models::DemoTeam::as_select(),
)) ))
.filter(crate::schema::demos::dsl::steam_id.eq(steam_id.to_string())); .filter(crate::schema::demos::dsl::steam_id.eq(steam_id.to_string()));
let results: Vec<(crate::models::Demo, crate::models::DemoInfo)> = let results: Vec<(crate::models::Demo, crate::models::DemoInfo, crate::models::DemoTeam)> =
query.load(&mut crate::db_connection().await).await.unwrap(); query.load(&mut crate::db_connection().await).await.unwrap();
Ok(axum::response::Json( let mut demos = std::collections::HashMap::new();
results for (demo, info, team) in results.into_iter() {
.into_iter() let entry = demos.entry(demo.demo_id.clone()).or_insert(common::BaseDemoInfo {
.map(|(demo, info)| { id: demo.demo_id,
common::BaseDemoInfo { map: info.map,
id: demo.demo_id, team2_score: 0,
map: info.map, team3_score: 0,
} });
})
.collect::<Vec<_>>(), if team.team == 2 {
)) entry.team2_score = team.end_score;
} else if team.team == 3 {
entry.team3_score = team.end_score;
} else {
tracing::warn!("Unknown Team: {:?}", team);
}
}
// TODO
// Sort this
let mut output = demos.into_values().collect::<Vec<_>>();
Ok(axum::response::Json(output))
} }
#[tracing::instrument(skip(state, session, form))] #[tracing::instrument(skip(state, session, form))]
@@ -82,7 +100,10 @@ async fn upload(
Some(c) => c, Some(c) => c,
None => { None => {
tracing::error!("Getting File content from request"); tracing::error!("Getting File content from request");
return Err((axum::http::StatusCode::BAD_REQUEST, "Failed to get file-content from upload")); return Err((
axum::http::StatusCode::BAD_REQUEST,
"Failed to get file-content from upload",
));
} }
}; };
@@ -106,30 +127,36 @@ async fn upload(
// Turn all of this into a single transaction // Turn all of this into a single transaction
let db_trans_result = db_con.build_transaction().run(|c| { let db_trans_result = db_con
Box::pin(async move { .build_transaction()
let query = .run(|c| {
diesel::dsl::insert_into(crate::schema::demos::dsl::demos).values(crate::models::NewDemo { Box::pin(async move {
demo_id: demo_id.clone(), let query = diesel::dsl::insert_into(crate::schema::demos::dsl::demos).values(
steam_id: steam_id.to_string(), crate::models::NewDemo {
}); demo_id: demo_id.clone(),
query.execute(c).await?; steam_id: steam_id.to_string(),
},
);
query.execute(c).await?;
let queue_query = diesel::dsl::insert_into(crate::schema::analysis_queue::dsl::analysis_queue) let queue_query =
.values(crate::models::AddAnalysisTask { diesel::dsl::insert_into(crate::schema::analysis_queue::dsl::analysis_queue)
demo_id: demo_id.clone(), .values(crate::models::AddAnalysisTask {
steam_id: steam_id.to_string(), demo_id: demo_id.clone(),
}); steam_id: steam_id.to_string(),
queue_query.execute(c).await?; });
queue_query.execute(c).await?;
let processing_query = let processing_query = diesel::dsl::insert_into(
diesel::dsl::insert_into(crate::schema::processing_status::dsl::processing_status) crate::schema::processing_status::dsl::processing_status,
.values(crate::models::ProcessingStatus { demo_id, info: 0 }); )
processing_query.execute(c).await?; .values(crate::models::ProcessingStatus { demo_id, info: 0 });
processing_query.execute(c).await?;
Ok::<(), diesel::result::Error>(()) Ok::<(), diesel::result::Error>(())
})
}) })
}).await; .await;
if let Err(e) = db_trans_result { if let Err(e) = db_trans_result {
tracing::error!("Inserting data into db: {:?}", e); tracing::error!("Inserting data into db: {:?}", e);
@@ -267,12 +294,14 @@ async fn scoreboard(
async fn heatmap( async fn heatmap(
session: UserSession, session: UserSession,
Path(demo_id): Path<String>, Path(demo_id): Path<String>,
) -> Result<axum::response::Json<Vec<common::demo_analysis::PlayerHeatmap>>, axum::http::StatusCode> { ) -> Result<axum::response::Json<Vec<common::demo_analysis::PlayerHeatmap>>, axum::http::StatusCode>
{
use base64::prelude::Engine; use base64::prelude::Engine;
let mut db_con = crate::db_connection().await; let mut db_con = crate::db_connection().await;
let demo_info_query = crate::schema::demo_info::dsl::demo_info.filter(crate::schema::demo_info::dsl::demo_id.eq(demo_id.clone())); let demo_info_query = crate::schema::demo_info::dsl::demo_info
.filter(crate::schema::demo_info::dsl::demo_id.eq(demo_id.clone()));
let demo_info: crate::models::DemoInfo = match demo_info_query.first(&mut db_con).await { let demo_info: crate::models::DemoInfo = match demo_info_query.first(&mut db_con).await {
Ok(i) => i, Ok(i) => i,
Err(e) => { Err(e) => {
@@ -282,18 +311,26 @@ async fn heatmap(
}; };
let query = crate::schema::demo_players::dsl::demo_players let query = crate::schema::demo_players::dsl::demo_players
.inner_join(crate::schema::demo_heatmaps::dsl::demo_heatmaps.on( .inner_join(
crate::schema::demo_players::dsl::steam_id.eq(crate::schema::demo_heatmaps::dsl::steam_id) crate::schema::demo_heatmaps::dsl::demo_heatmaps.on(
.and(crate::schema::demo_players::dsl::demo_id.eq(crate::schema::demo_heatmaps::dsl::demo_id)) crate::schema::demo_players::dsl::steam_id
)).filter(crate::schema::demo_players::dsl::demo_id.eq(demo_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 result: Vec<(crate::models::DemoPlayer, crate::models::DemoPlayerHeatmap)> = match query.load(&mut db_con).await { let result: Vec<(crate::models::DemoPlayer, crate::models::DemoPlayerHeatmap)> =
Ok(d) => d, match query.load(&mut db_con).await {
Err(e) => { Ok(d) => d,
tracing::error!("Querying DB: {:?}", e); Err(e) => {
return Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR); tracing::error!("Querying DB: {:?}", e);
} return Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR);
}; }
};
let demo_map = &demo_info.map; let demo_map = &demo_info.map;
let minimap_coords = match MINIMAP_COORDINATES.get(demo_map) { let minimap_coords = match MINIMAP_COORDINATES.get(demo_map) {
@@ -304,21 +341,30 @@ async fn heatmap(
} }
}; };
let data: Vec<common::demo_analysis::PlayerHeatmap> = result.into_iter().map(|(player, heatmap)| { let data: Vec<common::demo_analysis::PlayerHeatmap> = result
let team = heatmap.team.clone(); .into_iter()
let mut heatmap: analysis::heatmap::HeatMap = serde_json::from_str(&heatmap.data).unwrap(); .map(|(player, heatmap)| {
heatmap.fit(minimap_coords.x_coord(0.0)..minimap_coords.x_coord(1024.0), minimap_coords.y_coord(1024.0)..minimap_coords.y_coord(0.0)); let team = heatmap.team.clone();
let h_image = heatmap.as_image(); let mut heatmap: analysis::heatmap::HeatMap =
serde_json::from_str(&heatmap.data).unwrap();
heatmap.fit(
minimap_coords.x_coord(0.0)..minimap_coords.x_coord(1024.0),
minimap_coords.y_coord(1024.0)..minimap_coords.y_coord(0.0),
);
let h_image = heatmap.as_image();
let mut buffer = std::io::Cursor::new(Vec::new()); let mut buffer = std::io::Cursor::new(Vec::new());
h_image.write_to(&mut buffer, image::ImageFormat::Png).unwrap(); h_image
.write_to(&mut buffer, image::ImageFormat::Png)
.unwrap();
common::demo_analysis::PlayerHeatmap { common::demo_analysis::PlayerHeatmap {
name: player.name, name: player.name,
team, team,
png_data: base64::prelude::BASE64_STANDARD.encode(buffer.into_inner()), png_data: base64::prelude::BASE64_STANDARD.encode(buffer.into_inner()),
} }
}).collect(); })
.collect();
Ok(axum::Json(data)) Ok(axum::Json(data))
} }
@@ -328,23 +374,38 @@ async fn perround(
session: UserSession, session: UserSession,
Path(demo_id): Path<String>, Path(demo_id): Path<String>,
) -> Result<axum::response::Json<Vec<common::demo_analysis::DemoRound>>, axum::http::StatusCode> { ) -> Result<axum::response::Json<Vec<common::demo_analysis::DemoRound>>, axum::http::StatusCode> {
let rounds_query = crate::schema::demo_round::dsl::demo_round.filter(crate::schema::demo_round::dsl::demo_id.eq(demo_id.clone())); let rounds_query = crate::schema::demo_round::dsl::demo_round
let round_players_query = crate::schema::demo_players::dsl::demo_players.filter(crate::schema::demo_players::dsl::demo_id.eq(demo_id)); .filter(crate::schema::demo_round::dsl::demo_id.eq(demo_id.clone()));
let round_players_query = crate::schema::demo_players::dsl::demo_players
.filter(crate::schema::demo_players::dsl::demo_id.eq(demo_id));
let mut db_con = crate::db_connection().await; let mut db_con = crate::db_connection().await;
let raw_rounds: Vec<crate::models::DemoRound> = rounds_query.load(&mut db_con).await.unwrap(); let raw_rounds: Vec<crate::models::DemoRound> = rounds_query.load(&mut db_con).await.unwrap();
let players: Vec<crate::models::DemoPlayer> = round_players_query.load(&mut db_con).await.unwrap(); let players: Vec<crate::models::DemoPlayer> =
round_players_query.load(&mut db_con).await.unwrap();
let mut result = Vec::with_capacity(raw_rounds.len()); let mut result = Vec::with_capacity(raw_rounds.len());
for raw_round in raw_rounds.into_iter() { for raw_round in raw_rounds.into_iter() {
let reason = match serde_json::from_str(&raw_round.win_reason) { let reason = match serde_json::from_str(&raw_round.win_reason) {
Ok(analysis::perround::WinReason::StillInProgress) => common::demo_analysis::RoundWinReason::StillInProgress, Ok(analysis::perround::WinReason::StillInProgress) => {
Ok(analysis::perround::WinReason::TKilled) => common::demo_analysis::RoundWinReason::TKilled, common::demo_analysis::RoundWinReason::StillInProgress
Ok(analysis::perround::WinReason::CTKilled) => common::demo_analysis::RoundWinReason::CTKilled, }
Ok(analysis::perround::WinReason::BombDefused) => common::demo_analysis::RoundWinReason::BombDefused, Ok(analysis::perround::WinReason::TKilled) => {
Ok(analysis::perround::WinReason::BombExploded) => common::demo_analysis::RoundWinReason::BombExploded, common::demo_analysis::RoundWinReason::TKilled
Ok(analysis::perround::WinReason::TimeRanOut) => common::demo_analysis::RoundWinReason::TimeRanOut, }
Ok(analysis::perround::WinReason::CTKilled) => {
common::demo_analysis::RoundWinReason::CTKilled
}
Ok(analysis::perround::WinReason::BombDefused) => {
common::demo_analysis::RoundWinReason::BombDefused
}
Ok(analysis::perround::WinReason::BombExploded) => {
common::demo_analysis::RoundWinReason::BombExploded
}
Ok(analysis::perround::WinReason::TimeRanOut) => {
common::demo_analysis::RoundWinReason::TimeRanOut
}
Ok(other) => { Ok(other) => {
tracing::error!("Unknown Mapping {:?}", other); tracing::error!("Unknown Mapping {:?}", other);
return Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR); return Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR);
@@ -355,27 +416,38 @@ async fn perround(
} }
}; };
let parsed_events: Vec<analysis::perround::RoundEvent> = serde_json::from_value(raw_round.events).unwrap(); let parsed_events: Vec<analysis::perround::RoundEvent> =
let events: Vec<_> = parsed_events.into_iter().map(|event| { serde_json::from_value(raw_round.events).unwrap();
match event { let events: Vec<_> = parsed_events
analysis::perround::RoundEvent::BombPlanted => common::demo_analysis::RoundEvent::BombPlanted, .into_iter()
analysis::perround::RoundEvent::BombDefused => common::demo_analysis::RoundEvent::BombDefused, .map(|event| match event {
analysis::perround::RoundEvent::BombPlanted => {
common::demo_analysis::RoundEvent::BombPlanted
}
analysis::perround::RoundEvent::BombDefused => {
common::demo_analysis::RoundEvent::BombDefused
}
analysis::perround::RoundEvent::Kill { attacker, died } => { analysis::perround::RoundEvent::Kill { attacker, died } => {
let attacker_name = players.iter().find(|p| p.steam_id == attacker.to_string()).map(|p| p.name.clone()).unwrap(); let attacker_name = players
let died_name = players.iter().find(|p| p.steam_id == died.to_string()).map(|p| p.name.clone()).unwrap(); .iter()
.find(|p| p.steam_id == attacker.to_string())
.map(|p| p.name.clone())
.unwrap();
let died_name = players
.iter()
.find(|p| p.steam_id == died.to_string())
.map(|p| p.name.clone())
.unwrap();
common::demo_analysis::RoundEvent::Killed { common::demo_analysis::RoundEvent::Killed {
attacker: attacker_name, attacker: attacker_name,
died: died_name, died: died_name,
} }
} }
} })
}).collect(); .collect();
result.push(common::demo_analysis::DemoRound { result.push(common::demo_analysis::DemoRound { reason, events });
reason,
events,
});
} }
Ok(axum::Json(result)) Ok(axum::Json(result))

View File

@@ -76,12 +76,12 @@ pub async fn run_api(
}), }),
) )
.layer(session_layer) .layer(session_layer)
.nest_service( .nest_service("/", tower_http::services::ServeDir::new(serve_dir));
"/",
tower_http::services::ServeDir::new(serve_dir),
);
let listen_addr = std::net::SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)), 3000); let listen_addr = std::net::SocketAddr::new(
std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
3000,
);
tracing::info!("Listening on Addr: {:?}", listen_addr); tracing::info!("Listening on Addr: {:?}", listen_addr);
let listener = tokio::net::TcpListener::bind(listen_addr).await.unwrap(); let listener = tokio::net::TcpListener::bind(listen_addr).await.unwrap();
@@ -110,17 +110,18 @@ pub async fn run_analysis(upload_folder: impl Into<std::path::PathBuf>) {
let mut store_result_fns = Vec::new(); let mut store_result_fns = Vec::new();
for analysis in analysis::ANALYSIS_METHODS.iter().map(|a| a.clone()) { for analysis in analysis::ANALYSIS_METHODS.iter().map(|a| a.clone()) {
let input = input.clone(); let input = input.clone();
let store_result = match tokio::task::spawn_blocking(move || analysis.analyse(input)).await { let store_result =
Ok(Ok(r)) => r, match tokio::task::spawn_blocking(move || analysis.analyse(input)).await {
Ok(Err(e)) => { Ok(Ok(r)) => r,
tracing::error!("Analysis failed: {:?}", e); Ok(Err(e)) => {
continue; tracing::error!("Analysis failed: {:?}", e);
} continue;
Err(e) => { }
tracing::error!("Joining Task: {:?}", e); Err(e) => {
continue; tracing::error!("Joining Task: {:?}", e);
} continue;
}; }
};
store_result_fns.push(store_result); store_result_fns.push(store_result);
} }

View File

@@ -26,8 +26,7 @@ async fn main() {
let registry = tracing_subscriber::Registry::default() let registry = tracing_subscriber::Registry::default()
.with(tracing_subscriber::fmt::layer()) .with(tracing_subscriber::fmt::layer())
.with(tracing_subscriber::filter::filter_fn(|meta| { .with(tracing_subscriber::filter::filter_fn(|meta| {
meta.target().contains("backend") meta.target().contains("backend") || meta.target().contains("analysis")
|| meta.target().contains("analysis")
})); }));
tracing::subscriber::set_global_default(registry).unwrap(); tracing::subscriber::set_global_default(registry).unwrap();

View File

@@ -23,7 +23,7 @@ pub struct NewDemo {
pub struct Demo { pub struct Demo {
pub steam_id: String, pub steam_id: String,
pub demo_id: String, pub demo_id: String,
pub uploaded_at: diesel::data_types::PgTimestamp pub uploaded_at: diesel::data_types::PgTimestamp,
} }
#[derive(Queryable, Selectable, Insertable, Debug)] #[derive(Queryable, Selectable, Insertable, Debug)]
@@ -65,6 +65,16 @@ pub struct DemoPlayerStats {
pub assists: i16, pub assists: i16,
} }
#[derive(Queryable, Selectable, Insertable, Debug)]
#[diesel(table_name = crate::schema::demo_teams)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct DemoTeam {
pub demo_id: String,
pub team: i16,
pub end_score: i16,
pub start_name: String,
}
#[derive(Queryable, Selectable, Insertable, Debug)] #[derive(Queryable, Selectable, Insertable, Debug)]
#[diesel(table_name = crate::schema::processing_status)] #[diesel(table_name = crate::schema::processing_status)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]

View File

@@ -56,6 +56,15 @@ diesel::table! {
} }
} }
diesel::table! {
demo_teams (demo_id, team) {
demo_id -> Text,
team -> Int2,
end_score -> Int2,
start_name -> Text,
}
}
diesel::table! { diesel::table! {
demos (steam_id, demo_id) { demos (steam_id, demo_id) {
steam_id -> Text, steam_id -> Text,
@@ -79,13 +88,6 @@ diesel::table! {
} }
} }
diesel::table! {
user_demos (steam_id, demo_id) {
steam_id -> Text,
demo_id -> Text,
}
}
diesel::table! { diesel::table! {
users (steamid) { users (steamid) {
steamid -> Text, steamid -> Text,
@@ -100,9 +102,9 @@ diesel::allow_tables_to_appear_in_same_query!(
demo_player_stats, demo_player_stats,
demo_players, demo_players,
demo_round, demo_round,
demo_teams,
demos, demos,
processing_status, processing_status,
sessions, sessions,
user_demos,
users, users,
); );

View File

@@ -2,6 +2,8 @@
pub struct BaseDemoInfo { pub struct BaseDemoInfo {
pub id: String, pub id: String,
pub map: String, pub map: String,
pub team2_score: i16,
pub team3_score: i16,
} }
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]

View File

@@ -18,7 +18,7 @@ pub fn demo_list_entry(demo: common::BaseDemoInfo) -> impl leptos::IntoView {
view! { view! {
<li> <li>
<A href=format!("demo/{}/scoreboard", demo.id)> <A href=format!("demo/{}/scoreboard", demo.id)>
<span>{demo.map} - {demo.id}</span> <span>{demo.map} - {demo.id} - {demo.team2_score}:{demo.team3_score}</span>
</A> </A>
</li> </li>
} }

View File

@@ -2,4 +2,5 @@
DROP TABLE processing_status; DROP TABLE processing_status;
DROP TABLE demo_player_stats; DROP TABLE demo_player_stats;
DROP TABLE demo_players; DROP TABLE demo_players;
DROP TABLE demo_teams;
DROP TABLE demo_info; DROP TABLE demo_info;

View File

@@ -27,3 +27,11 @@ CREATE TABLE IF NOT EXISTS demo_player_stats (
assists int2 NOT NULL, assists int2 NOT NULL,
PRIMARY KEY (demo_id, steam_id) PRIMARY KEY (demo_id, steam_id)
); );
CREATE TABLE IF NOT EXISTS demo_teams (
demo_id TEXT NOT NULL,
team int2 NOT NULL,
end_score int2 NOT NULL,
start_name TEXT NOT NULL,
PRIMARY KEY (demo_id, team)
);