Start with the analysis stuff
This commit is contained in:
131
backend/src/analysis.rs
Normal file
131
backend/src/analysis.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnalysisInput {
|
||||
pub steamid: String,
|
||||
pub demoid: i64,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BaseInfo {
|
||||
pub map: String,
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(input))]
|
||||
pub fn analyse_base(input: AnalysisInput) -> BaseInfo {
|
||||
tracing::info!("Performing Base analysis");
|
||||
|
||||
let huf = l_demoparser::second_pass::parser_settings::create_huffman_lookup_table();
|
||||
let settings = l_demoparser::first_pass::parser_settings::ParserInputs {
|
||||
wanted_players: Vec::new(),
|
||||
real_name_to_og_name: ahash::AHashMap::default(),
|
||||
wanted_player_props: vec!["X".to_string(), "team_num".to_string()],
|
||||
wanted_events: vec!["player_death".to_string(), "player_team".to_string(), "team_info".to_string(), "player_spawn".to_string(), "team_score".to_string(), "round_end".to_string(), "game_end".to_string(), "match_end_conditions".to_string(), "switch_team".to_string(), "player_given_c4".to_string()],
|
||||
wanted_other_props: vec![],
|
||||
parse_ents: true,
|
||||
wanted_ticks: Vec::new(),
|
||||
parse_projectiles: false,
|
||||
only_header: false,
|
||||
count_props: false,
|
||||
only_convars: false,
|
||||
huffman_lookup_table: &huf,
|
||||
order_by_steamid: false,
|
||||
};
|
||||
|
||||
let mut ds = l_demoparser::parse_demo::Parser::new(settings, l_demoparser::parse_demo::ParsingMode::ForceSingleThreaded);
|
||||
let file = std::fs::File::open(&input.path).unwrap();
|
||||
let mmap = unsafe { memmap2::MmapOptions::new().map(&file).unwrap() };
|
||||
let output = ds.parse_demo(&mmap).unwrap();
|
||||
|
||||
let header = output.header.as_ref().unwrap();
|
||||
|
||||
tracing::info!("Header: {:?}", header);
|
||||
|
||||
for event in output.game_events.iter() {
|
||||
match event.name.as_str() {
|
||||
"player_team" => {
|
||||
let team = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
|
||||
("team", Some(d)) => Some(d),
|
||||
("team", None) => {
|
||||
tracing::warn!("'team' field without data");
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let user_name = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
|
||||
("user_name", Some(d)) => Some(d),
|
||||
("user_name", None) => {
|
||||
tracing::warn!("'user_name' field without data");
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let steamid = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
|
||||
("user_steamid", Some(d)) => Some(d),
|
||||
("user_steamid", None) => {
|
||||
tracing::warn!("'user_steamid' field without data");
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
tracing::info!("'{:?}' ({:?}) -> {:?}", user_name, steamid, team);
|
||||
}
|
||||
"team_info" => {
|
||||
tracing::info!("Team Info: {:?}", event);
|
||||
}
|
||||
"player_spawn" => {
|
||||
// tracing::info!("Player Spawn: {:?}", event);
|
||||
}
|
||||
"team_score" => {
|
||||
tracing::info!("Team Score: {:?}", event);
|
||||
}
|
||||
"round_end" => {
|
||||
let winner = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
|
||||
("winner", Some(d)) => Some(d),
|
||||
("winner", None) => {
|
||||
tracing::warn!("'winner' field without data");
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let round = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
|
||||
("round", Some(d)) => Some(d),
|
||||
("round", None) => {
|
||||
tracing::warn!("'round' field without data");
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let reason = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
|
||||
("reason", Some(d)) => Some(d),
|
||||
("reason", None) => {
|
||||
tracing::warn!("'reason' field without data");
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
tracing::info!(?winner, ?round, ?reason, "Round End");
|
||||
}
|
||||
"game_end" => {
|
||||
tracing::info!("Game End: {:?}", event);
|
||||
}
|
||||
"match_end_conditions" => {
|
||||
tracing::info!("Match End Conditions: {:?}", event);
|
||||
}
|
||||
"switch_team" => {
|
||||
tracing::info!("Switch Team: {:?}", event);
|
||||
}
|
||||
"player_given_c4" => {
|
||||
tracing::info!("Player Given C4: {:?}", event);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
BaseInfo {
|
||||
map: header.get("map_name").cloned().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
@@ -7,15 +7,17 @@ pub mod demos {
|
||||
|
||||
struct DemoState {
|
||||
upload_folder: std::path::PathBuf,
|
||||
base_analysis: tokio::sync::mpsc::UnboundedSender<crate::analysis::AnalysisInput>
|
||||
}
|
||||
|
||||
pub fn router<P>(upload_folder: P) -> axum::Router where P: Into<std::path::PathBuf> {
|
||||
pub fn router<P>(upload_folder: P, base_analysis: tokio::sync::mpsc::UnboundedSender<crate::analysis::AnalysisInput>) -> axum::Router where P: Into<std::path::PathBuf> {
|
||||
axum::Router::new()
|
||||
.route("/list", axum::routing::get(list))
|
||||
.route("/upload", axum::routing::post(upload).layer(axum::extract::DefaultBodyLimit::max(500*1024*1024)))
|
||||
.route("/:id/info", axum::routing::get(info))
|
||||
.with_state(Arc::new(DemoState {
|
||||
upload_folder: upload_folder.into(),
|
||||
base_analysis,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -24,11 +26,12 @@ pub mod demos {
|
||||
let steam_id = session.data().steam_id.ok_or_else(|| axum::http::StatusCode::UNAUTHORIZED)?;
|
||||
tracing::info!("SteamID: {:?}", steam_id);
|
||||
|
||||
let query = crate::schema::demos::dsl::demos.filter(crate::schema::demos::dsl::steam_id.eq(steam_id.to_string()));
|
||||
let results: Vec<crate::models::Demo> = query.load(&mut crate::db_connection().await).await.unwrap();
|
||||
let query = crate::schema::demos::dsl::demos.inner_join(crate::schema::demo_info::dsl::demo_info).select((crate::models::Demo::as_select(), crate::models::DemoInfo::as_select())).filter(crate::schema::demos::dsl::steam_id.eq(steam_id.to_string()));
|
||||
let results: Vec<(crate::models::Demo, crate::models::DemoInfo)> = query.load(&mut crate::db_connection().await).await.unwrap();
|
||||
|
||||
Ok(axum::response::Json(results.into_iter().map(|demo| common::BaseDemoInfo {
|
||||
Ok(axum::response::Json(results.into_iter().map(|(demo, info)| common::BaseDemoInfo {
|
||||
id: demo.demo_id,
|
||||
map: info.map,
|
||||
}).collect::<Vec<_>>()))
|
||||
}
|
||||
|
||||
@@ -46,15 +49,29 @@ pub mod demos {
|
||||
}
|
||||
|
||||
let timestamp_secs = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs();
|
||||
let demo_id = timestamp_secs as i64;
|
||||
let demo_file_path = user_folder.join(format!("{}.dem", timestamp_secs));
|
||||
|
||||
tokio::fs::write(demo_file_path, file_content).await.unwrap();
|
||||
tokio::fs::write(&demo_file_path, file_content).await.unwrap();
|
||||
|
||||
let mut db_con = crate::db_connection().await;
|
||||
|
||||
let query = diesel::dsl::insert_into(crate::schema::demos::dsl::demos).values(crate::models::Demo {
|
||||
demo_id: timestamp_secs as i64,
|
||||
demo_id,
|
||||
steam_id: steam_id.to_string(),
|
||||
});
|
||||
query.execute(&mut crate::db_connection().await).await.unwrap();
|
||||
query.execute(&mut db_con).await.unwrap();
|
||||
|
||||
state.base_analysis.send(crate::analysis::AnalysisInput {
|
||||
steamid: steam_id.to_string(),
|
||||
demoid: demo_id,
|
||||
path: demo_file_path,
|
||||
});
|
||||
let processing_query = diesel::dsl::insert_into(crate::schema::processing_status::dsl::processing_status).values(crate::models::ProcessingStatus {
|
||||
demo_id,
|
||||
info: 0,
|
||||
});
|
||||
processing_query.execute(&mut db_con).await.unwrap();
|
||||
|
||||
Ok(axum::response::Redirect::to("/"))
|
||||
}
|
||||
@@ -189,9 +206,9 @@ pub mod user {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn router() -> axum::Router {
|
||||
pub fn router(base_analysis: tokio::sync::mpsc::UnboundedSender<crate::analysis::AnalysisInput>) -> axum::Router {
|
||||
axum::Router::new()
|
||||
.nest("/steam/", steam::router("http://localhost:3000", "/api/steam/callback"))
|
||||
.nest("/demos/", demos::router("uploads/"))
|
||||
.nest("/demos/", demos::router("uploads/", base_analysis))
|
||||
.nest("/user/", user::router())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ pub use usersession::{UserSessionData, UserSession};
|
||||
|
||||
pub mod diesel_sessionstore;
|
||||
|
||||
pub mod analysis;
|
||||
|
||||
pub async fn db_connection() -> diesel_async::AsyncPgConnection {
|
||||
use diesel_async::AsyncConnection;
|
||||
|
||||
|
||||
@@ -26,6 +26,34 @@ async fn main() {
|
||||
run_migrations(&mut backend::db_connection().await).await;
|
||||
tracing::info!("Completed Migrations");
|
||||
|
||||
let (base_analysis_tx, mut base_analysis_rx) = tokio::sync::mpsc::unbounded_channel::<backend::analysis::AnalysisInput>();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
while let Some(input) = base_analysis_rx.blocking_recv() {
|
||||
let demo_id = input.demoid;
|
||||
let result = backend::analysis::analyse_base(input);
|
||||
|
||||
dbg!(&result);
|
||||
|
||||
let handle = tokio::task::spawn(
|
||||
async move {
|
||||
let mut db_con = backend::db_connection().await;
|
||||
|
||||
let store_info_query = diesel::dsl::insert_into(backend::schema::demo_info::dsl::demo_info).values(backend::models::DemoInfo {
|
||||
demo_id,
|
||||
map: result.map,
|
||||
});
|
||||
let update_process_info = diesel::dsl::update(backend::schema::processing_status::dsl::processing_status).set(backend::schema::processing_status::dsl::info.eq(1)).filter(backend::schema::processing_status::dsl::demo_id.eq(demo_id));
|
||||
|
||||
tracing::trace!(?store_info_query, "Store demo info query");
|
||||
tracing::trace!(?update_process_info, "Update processing info query");
|
||||
|
||||
store_info_query.execute(&mut db_con).await.unwrap();
|
||||
update_process_info.execute(&mut db_con).await.unwrap();
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let session_store = backend::diesel_sessionstore::DieselStore::new();
|
||||
let session_layer = tower_sessions::SessionManagerLayer::new(session_store)
|
||||
.with_secure(false)
|
||||
@@ -38,7 +66,7 @@ async fn main() {
|
||||
}
|
||||
|
||||
let router = axum::Router::new()
|
||||
.nest("/api/", backend::api::router())
|
||||
.nest("/api/", backend::api::router(base_analysis_tx))
|
||||
.layer(session_layer)
|
||||
.nest_service("/", tower_http::services::ServeDir::new("frontend/dist/"));
|
||||
|
||||
|
||||
@@ -24,3 +24,19 @@ pub struct User {
|
||||
pub steamid: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Selectable, Insertable, Debug)]
|
||||
#[diesel(table_name = crate::schema::demo_info)]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
pub struct DemoInfo {
|
||||
pub demo_id: i64,
|
||||
pub map: String,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Selectable, Insertable, Debug)]
|
||||
#[diesel(table_name = crate::schema::processing_status)]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
pub struct ProcessingStatus {
|
||||
pub demo_id: i64,
|
||||
pub info: i16,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
demos (steam_id, demo_id) {
|
||||
demo_info (demo_id) {
|
||||
demo_id -> Int8,
|
||||
map -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
demos (demo_id) {
|
||||
steam_id -> Text,
|
||||
demo_id -> Int8,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
processing_status (demo_id) {
|
||||
demo_id -> Int8,
|
||||
info -> Int2,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
sessions (id) {
|
||||
id -> Text,
|
||||
@@ -22,8 +36,13 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(demo_info -> demos (demo_id));
|
||||
diesel::joinable!(processing_status -> demos (demo_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
demo_info,
|
||||
demos,
|
||||
processing_status,
|
||||
sessions,
|
||||
users,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user