record-daemon: refactor to record raw league data

This commit is contained in:
2026-03-27 13:42:12 +01:00
parent b09f669e73
commit d67d52fa86
7 changed files with 191 additions and 951 deletions
+47 -171
View File
@@ -354,24 +354,16 @@ impl Daemon {
// Handle recording start/stop
match transition {
StateTransition::GameStarted => {
// Extract data from the GameStart event that was stored in the timeline
let (game_id, queue_type, queue_id, game_mode, map_name, session) =
if let GameEvent::GameStart(ref info) = event {
(
info.game_id,
info.queue_type.clone(),
info.queue_id,
info.game_mode.clone(),
info.map_name.clone(),
info.session.clone(),
)
} else {
(0, None, None, None, None, None)
};
// Extract game_id from the GameStart event
let game_id = if let GameEvent::GameStart(ref info) = event {
info.game_id
} else {
0
};
info!(
"[EVENT_HANDLER] GameStarted transition - game_id: {}, queue_type: {:?}, game_mode: {:?}",
game_id, queue_type, game_mode
"[EVENT_HANDLER] GameStarted transition - game_id: {}",
game_id
);
// If already recording, stop the current recording first
@@ -387,110 +379,34 @@ impl Daemon {
info!("[EVENT_HANDLER] Calling start_recording...");
// Fetch player game metadata (puuid, runes, summoner spells)
let player_metadata =
self.lqp_client.fetch_player_game_metadata().await.ok();
let (puuid, runes, summoner_spells, champion_id, team, summoner_name) =
player_metadata.map_or((None, None, None, None, None, None), |m| {
(
m.puuid,
m.runes,
m.summoner_spells,
m.champion_id,
m.team,
m.summoner_name,
)
});
// If we have puuid and session, extract player-specific data from session
let (champion, final_team, final_summoner_name, final_summoner_spells) =
if let Some(ref p) = puuid {
if let Some(ref sess) = session {
let champ_id = sess.get_champion_id(p);
let champ_name =
champ_id.and_then(record_daemon::lqp::champion_id_to_name);
let team_id = sess.get_team(p);
let summoner = sess.get_summoner_name(p).map(|s| s.to_string());
// Extract summoner spells from session's player_champion_selections
let spells_from_session =
sess.get_player_selection(p).map(|selection| {
record_daemon::lqp::SummonerSpells {
spell1_id: selection.spell1_id,
spell2_id: selection.spell2_id,
spell1_name: None,
spell2_name: None,
}
});
(
champ_name.or_else(|| {
champion_id
.and_then(record_daemon::lqp::champion_id_to_name)
}),
team_id.or(team),
summoner.or(summoner_name),
spells_from_session.or(summoner_spells),
)
} else {
(
champion_id
.and_then(record_daemon::lqp::champion_id_to_name),
team,
summoner_name,
summoner_spells,
)
}
} else {
(
champion_id.and_then(record_daemon::lqp::champion_id_to_name),
team,
summoner_name,
summoner_spells,
)
};
info!(
"[EVENT_HANDLER] Final values: puuid={:?}, runes={:?}, summoner_spells={:?}",
puuid, runes, final_summoner_spells
// Fetch raw API data in parallel
let (
raw_session,
raw_summoner,
raw_champion_select,
raw_rune_page,
raw_live_client_data,
) = tokio::join!(
self.lqp_client.fetch_raw_session(),
self.lqp_client.fetch_raw_summoner(),
self.lqp_client.fetch_raw_champion_select(),
self.lqp_client.fetch_raw_rune_page(),
self.lqp_client.fetch_raw_live_client_data()
);
// Fetch all players identities for puuid mapping
let all_players_identities =
self.lqp_client.fetch_all_players_identities().await.ok();
let all_players_info: Vec<record_daemon::timeline::PlayerIdentityInfo> =
all_players_identities
.unwrap_or_default()
.into_iter()
.map(|p| record_daemon::timeline::PlayerIdentityInfo {
puuid: p.puuid,
summoner_name: p.summoner_name,
champion_name: p.champion_name,
team: p.team,
})
.collect();
// Build game metadata for timeline
// Build game metadata for timeline with raw JSON
let metadata_update = record_daemon::timeline::MetadataUpdate {
queue_type,
queue_id,
game_mode,
map_name,
team: final_team,
summoner_name: final_summoner_name,
puuid,
runes,
summoner_spells: final_summoner_spells,
all_players: all_players_info,
..Default::default()
game_id: Some(game_id),
raw_session: raw_session.ok(),
raw_summoner: raw_summoner.ok(),
raw_champion_select: raw_champion_select.ok(),
raw_rune_page: raw_rune_page.ok(),
raw_live_client_data: raw_live_client_data.ok(),
raw_end_game_stats: None,
};
if let Err(e) = self
.start_recording_with_metadata(
game_id,
champion.as_deref(),
metadata_update,
)
.start_recording_with_metadata(game_id, metadata_update)
.await
{
error!("[EVENT_HANDLER] Failed to start recording: {}", e);
@@ -503,15 +419,17 @@ impl Daemon {
StateTransition::GameEnded => {
info!("[EVENT_HANDLER] GameEnded transition");
// Fetch end-of-game stats from API
let game_end_stats = self.lqp_client.fetch_game_end_stats().await.ok();
// Fetch raw end-of-game stats from API
let raw_end_game_stats =
self.lqp_client.fetch_raw_end_game_stats().await.ok();
info!(
"[EVENT_HANDLER] Game end stats from API: {:?}",
game_end_stats
raw_end_game_stats.is_some()
);
if let Err(e) = self.stop_recording_with_metadata(game_end_stats).await {
if let Err(e) = self.stop_recording_with_metadata(raw_end_game_stats).await
{
error!("[EVENT_HANDLER] Failed to stop recording: {}", e);
// Don't propagate error - keep daemon running
@@ -539,19 +457,18 @@ impl Daemon {
async fn start_recording_with_metadata(
&self,
game_id: u64,
champion: Option<&str>,
metadata_update: record_daemon::timeline::MetadataUpdate,
) -> Result<()> {
info!(
"Daemon::start_recording_with_metadata called - game {} ({:?})",
game_id, champion
"Daemon::start_recording_with_metadata called - game {}",
game_id
);
// Create a recording entry in the timeline store first
let recording_id = self
.timeline_store
.write()
.start_recording_entry(Some(game_id), champion.map(|s| s.to_string()));
.start_recording_entry(Some(game_id), None);
// Update metadata immediately with game start info
if let Err(e) = self
@@ -569,7 +486,6 @@ impl Daemon {
// Clone Arc references for use in spawn_blocking
let recording_engine = self.recording_engine.clone();
let event_mapper = self.event_mapper.clone();
let champion_owned = champion.map(|s| s.to_string());
// Use spawn_blocking to avoid blocking the async runtime
tokio::task::spawn_blocking(move || {
@@ -579,7 +495,7 @@ impl Daemon {
if let Some(ref mut engine) = *engine_guard {
info!("Calling engine.start_recording...");
engine.start_recording(Some(game_id), champion_owned.as_deref())?;
engine.start_recording(Some(game_id), None)?;
info!("engine.start_recording returned successfully");
event_mapper.write().start();
info!("Event mapper started");
@@ -604,10 +520,10 @@ impl Daemon {
self.stop_recording_with_metadata(None).await
}
/// Stop recording with optional game end stats.
/// Stop recording with optional raw game end stats JSON.
async fn stop_recording_with_metadata(
&self,
game_end_stats: Option<record_daemon::lqp::EndOfGameStatsResponse>,
raw_end_game_stats: Option<serde_json::Value>,
) -> Result<()> {
info!("Stopping recording");
@@ -618,7 +534,6 @@ impl Daemon {
let recording_engine = self.recording_engine.clone();
let event_mapper = self.event_mapper.clone();
let timeline_store = self.timeline_store.clone();
let pregame_data = self.pregame_data.read().clone();
// Use spawn_blocking to avoid blocking the async runtime
tokio::task::spawn_blocking(move || {
@@ -642,50 +557,11 @@ impl Daemon {
}
};
// Update metadata if we have it
let mut update = record_daemon::timeline::MetadataUpdate::default();
// Add pre-game data
if let Some(pregame) = pregame_data {
// Convert champion_id to name if available
if let Some(champion_id) = pregame.champion_id() {
update.champion =
record_daemon::lqp::champion_id_to_name(champion_id as u32);
}
update.summoner_name = pregame.summoner_name().map(|s| s.to_string());
update.queue_id = pregame.queue_id().map(|id| id as u32);
update.game_mode = pregame.game_mode().map(|s| s.to_string());
update.map_name = pregame.map_name().map(|s| s.to_string());
update.team = pregame.team().map(|id| id as u32);
}
// Add game end stats from API response
if let Some(stats) = game_end_stats {
update.victory = Some(stats.is_victory());
update.match_id = stats.match_id.map(|id| id.to_string());
// Store raw end-of-game stats as JSON
update.raw_end_game_stats = serde_json::to_value(&stats).ok();
// Get local player stats
if let Some(player) = stats.get_local_player() {
if let Some(player_stats) = &player.stats {
update.final_stats = Some(record_daemon::timeline::GameFinalStats {
kills: player_stats.champions_killed.unwrap_or(0) as u32,
deaths: player_stats.num_deaths.unwrap_or(0) as u32,
assists: player_stats.assists.unwrap_or(0) as u32,
creep_score: player_stats.minions_killed.unwrap_or(0) as u32,
gold_earned: player_stats.gold_earned.unwrap_or(0) as u32,
damage_dealt: player_stats
.total_damage_dealt_to_champions
.unwrap_or(0),
damage_taken: player_stats.total_damage_taken.unwrap_or(0),
vision_score: player_stats.vision_score.unwrap_or(0.0),
game_duration: stats.game_length.unwrap_or(0.0),
});
}
}
}
// Update metadata with raw end game stats
let update = record_daemon::timeline::MetadataUpdate {
raw_end_game_stats,
..Default::default()
};
// Apply the update
if let Err(e) = timeline_store.write().update_metadata(recording_id, update) {