record-daemon: remove data in state machine
All checks were successful
record-daemon / Build, check and test (push) Successful in 2m11s
All checks were successful
record-daemon / Build, check and test (push) Successful in 2m11s
This commit is contained in:
@@ -353,33 +353,32 @@ impl Daemon {
|
||||
if let Some(_new_state) = self.state_machine.transition(transition.clone()) {
|
||||
// Handle recording start/stop
|
||||
match transition {
|
||||
StateTransition::GameStarted {
|
||||
game_id,
|
||||
champion,
|
||||
queue_type,
|
||||
queue_id,
|
||||
game_mode,
|
||||
map_name,
|
||||
team,
|
||||
summoner_name,
|
||||
puuid: transition_puuid,
|
||||
runes: transition_runes,
|
||||
summoner_spells: transition_summoner_spells,
|
||||
} => {
|
||||
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)
|
||||
};
|
||||
|
||||
info!(
|
||||
"[EVENT_HANDLER] GameStarted transition - game_id: {}, champion: {:?}, queue_type: {:?}, game_mode: {:?}",
|
||||
game_id, champion, queue_type, game_mode
|
||||
);
|
||||
info!(
|
||||
"[EVENT_HANDLER] Transition provided: puuid={:?}, runes={:?}, summoner_spells={:?}",
|
||||
transition_puuid, transition_runes, transition_summoner_spells
|
||||
);
|
||||
"[EVENT_HANDLER] GameStarted transition - game_id: {}, queue_type: {:?}, game_mode: {:?}",
|
||||
game_id, queue_type, game_mode
|
||||
);
|
||||
|
||||
// If already recording, stop the current recording first
|
||||
if self.state_machine.is_recording() {
|
||||
info!(
|
||||
"[EVENT_HANDLER] Stopping previous recording before starting new one"
|
||||
);
|
||||
"[EVENT_HANDLER] Stopping previous recording before starting new one"
|
||||
);
|
||||
|
||||
if let Err(e) = self.stop_recording().await {
|
||||
warn!("[EVENT_HANDLER] Failed to stop previous recording: {}", e);
|
||||
@@ -388,51 +387,56 @@ impl Daemon {
|
||||
|
||||
info!("[EVENT_HANDLER] Calling start_recording...");
|
||||
|
||||
// Wrap the start_recording call to catch any panics
|
||||
let start_result =
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
// We need to use a blocking approach here since we're in catch_unwind
|
||||
// The actual async call happens outside
|
||||
}));
|
||||
|
||||
if let Err(panic_info) = start_result {
|
||||
error!(
|
||||
"[EVENT_HANDLER] PANIC before start_recording: {:?}",
|
||||
panic_info
|
||||
);
|
||||
|
||||
eprintln!(
|
||||
"[EVENT_HANDLER] PANIC before start_recording: {:?}",
|
||||
panic_info
|
||||
);
|
||||
}
|
||||
|
||||
// Get pre-game metadata to pass to recording
|
||||
let pregame = self.pregame_data.read().clone();
|
||||
let champion_name = champion.or_else(|| {
|
||||
pregame.as_ref().and({
|
||||
// Try to get champion name from metadata if not provided
|
||||
None // We only have champion_id, not name
|
||||
})
|
||||
});
|
||||
|
||||
// Fetch player game metadata (runes, summoner spells, puuid) as fallback
|
||||
// Fetch player game metadata (puuid, runes, summoner spells)
|
||||
let player_metadata =
|
||||
self.lqp_client.fetch_player_game_metadata().await.ok();
|
||||
let (fetched_puuid, fetched_runes, fetched_summoner_spells) =
|
||||
player_metadata.map_or((None, None, None), |m| {
|
||||
(m.puuid, m.runes, m.summoner_spells)
|
||||
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,
|
||||
)
|
||||
});
|
||||
|
||||
// Use transition values first, then fall back to fetched values
|
||||
let final_puuid = transition_puuid.or(fetched_puuid);
|
||||
let final_runes = transition_runes.or(fetched_runes);
|
||||
let final_summoner_spells =
|
||||
transition_summoner_spells.or(fetched_summoner_spells);
|
||||
// If we have puuid and session, extract player-specific data from session
|
||||
let (champion, final_team, final_summoner_name) = 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());
|
||||
(
|
||||
champ_name.or_else(|| {
|
||||
champion_id
|
||||
.and_then(record_daemon::lqp::champion_id_to_name)
|
||||
}),
|
||||
team_id.or(team),
|
||||
summoner.or(summoner_name),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
champion_id.and_then(record_daemon::lqp::champion_id_to_name),
|
||||
team,
|
||||
summoner_name,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(
|
||||
champion_id.and_then(record_daemon::lqp::champion_id_to_name),
|
||||
team,
|
||||
summoner_name,
|
||||
)
|
||||
};
|
||||
|
||||
info!(
|
||||
"[EVENT_HANDLER] Final values: puuid={:?}, runes={:?}, summoner_spells={:?}",
|
||||
final_puuid, final_runes, final_summoner_spells
|
||||
puuid, runes, summoner_spells
|
||||
);
|
||||
|
||||
// Fetch all players identities for puuid mapping
|
||||
@@ -452,15 +456,15 @@ impl Daemon {
|
||||
|
||||
// Build game metadata for timeline
|
||||
let metadata_update = record_daemon::timeline::MetadataUpdate {
|
||||
queue_type: queue_type.clone(),
|
||||
queue_type,
|
||||
queue_id,
|
||||
game_mode: game_mode.clone(),
|
||||
map_name: map_name.clone(),
|
||||
team,
|
||||
summoner_name: summoner_name.clone(),
|
||||
puuid: final_puuid,
|
||||
runes: final_runes,
|
||||
summoner_spells: final_summoner_spells,
|
||||
game_mode,
|
||||
map_name,
|
||||
team: final_team,
|
||||
summoner_name: final_summoner_name,
|
||||
puuid,
|
||||
runes,
|
||||
summoner_spells,
|
||||
all_players: all_players_info,
|
||||
..Default::default()
|
||||
};
|
||||
@@ -468,7 +472,7 @@ impl Daemon {
|
||||
if let Err(e) = self
|
||||
.start_recording_with_metadata(
|
||||
game_id,
|
||||
champion_name.as_deref(),
|
||||
champion.as_deref(),
|
||||
metadata_update,
|
||||
)
|
||||
.await
|
||||
@@ -480,14 +484,8 @@ impl Daemon {
|
||||
info!("[EVENT_HANDLER] start_recording completed successfully");
|
||||
}
|
||||
}
|
||||
StateTransition::GameEnded {
|
||||
game_end_info,
|
||||
final_items: _,
|
||||
} => {
|
||||
info!(
|
||||
"[EVENT_HANDLER] GameEnded transition with info: {:?}",
|
||||
game_end_info
|
||||
);
|
||||
StateTransition::GameEnded => {
|
||||
info!("[EVENT_HANDLER] GameEnded transition");
|
||||
|
||||
// Fetch final items before stopping
|
||||
let fetched_final_items =
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
//! Daemon state machine implementation.
|
||||
//!
|
||||
//! Only tracks state
|
||||
//! All game data is stored in events and processed at game end.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -6,7 +9,7 @@ use parking_lot::RwLock;
|
||||
use tracing::{info, trace, warn};
|
||||
|
||||
use super::DaemonStatus;
|
||||
use crate::lqp::{GameEndInfo, GameEvent, GameflowPhase, ItemBuild, RunePage, SummonerSpells};
|
||||
use crate::lqp::{GameEvent, GameflowPhase};
|
||||
|
||||
/// Internal daemon state.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -36,36 +39,19 @@ impl From<DaemonState> for DaemonStatus {
|
||||
}
|
||||
|
||||
/// State transition event.
|
||||
///
|
||||
/// Simplified: Only tracks state changes, no data passing.
|
||||
/// Data is stored in events and processed at game end.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StateTransition {
|
||||
/// League Client started.
|
||||
ClientStarted,
|
||||
/// League Client stopped.
|
||||
ClientStopped,
|
||||
/// Game started.
|
||||
GameStarted {
|
||||
game_id: u64,
|
||||
champion: Option<String>,
|
||||
queue_type: Option<String>,
|
||||
queue_id: Option<u32>,
|
||||
game_mode: Option<String>,
|
||||
map_name: Option<String>,
|
||||
team: Option<u32>,
|
||||
summoner_name: Option<String>,
|
||||
/// Player's PUUID
|
||||
puuid: Option<String>,
|
||||
/// Rune page at game start
|
||||
runes: Option<RunePage>,
|
||||
/// Summoner spells
|
||||
summoner_spells: Option<SummonerSpells>,
|
||||
},
|
||||
/// Game ended.
|
||||
GameEnded {
|
||||
/// Game end info with stats from WebSocket.
|
||||
game_end_info: Option<GameEndInfo>,
|
||||
/// Final items
|
||||
final_items: Option<ItemBuild>,
|
||||
},
|
||||
/// Game started
|
||||
GameStarted,
|
||||
/// Game ended
|
||||
GameEnded,
|
||||
/// Error occurred.
|
||||
Error(String),
|
||||
/// Error recovered.
|
||||
@@ -153,13 +139,11 @@ impl DaemonStateMachine {
|
||||
|
||||
// Update related state
|
||||
match &transition {
|
||||
StateTransition::GameStarted {
|
||||
game_id, champion, ..
|
||||
} => {
|
||||
*self.current_game_id.write() = Some(*game_id);
|
||||
*self.current_champion.write() = champion.clone();
|
||||
StateTransition::GameStarted => {
|
||||
// Game ID and champion are now tracked in events, not state
|
||||
}
|
||||
StateTransition::GameEnded { .. } => {
|
||||
StateTransition::GameEnded => {
|
||||
// Clear state tracking
|
||||
*self.current_game_id.write() = None;
|
||||
*self.current_champion.write() = None;
|
||||
}
|
||||
@@ -189,20 +173,14 @@ impl DaemonStateMachine {
|
||||
|
||||
// From Monitoring
|
||||
(DaemonState::Monitoring, StateTransition::ClientStopped) => Some(DaemonState::Idle),
|
||||
(DaemonState::Monitoring, StateTransition::GameStarted { .. }) => {
|
||||
Some(DaemonState::Recording)
|
||||
}
|
||||
(DaemonState::Monitoring, StateTransition::GameStarted) => Some(DaemonState::Recording),
|
||||
(DaemonState::Monitoring, StateTransition::Error(_)) => Some(DaemonState::Error),
|
||||
(DaemonState::Monitoring, StateTransition::Shutdown) => Some(DaemonState::ShuttingDown),
|
||||
|
||||
// From Recording
|
||||
(DaemonState::Recording, StateTransition::GameEnded { .. }) => {
|
||||
Some(DaemonState::Monitoring)
|
||||
}
|
||||
(DaemonState::Recording, StateTransition::GameEnded) => Some(DaemonState::Monitoring),
|
||||
// Allow GameStarted from Recording (handles case where GameEnded wasn't received)
|
||||
(DaemonState::Recording, StateTransition::GameStarted { .. }) => {
|
||||
Some(DaemonState::Recording)
|
||||
}
|
||||
(DaemonState::Recording, StateTransition::GameStarted) => Some(DaemonState::Recording),
|
||||
(DaemonState::Recording, StateTransition::ClientStopped) => Some(DaemonState::Idle),
|
||||
(DaemonState::Recording, StateTransition::Error(_)) => Some(DaemonState::Error),
|
||||
(DaemonState::Recording, StateTransition::Shutdown) => Some(DaemonState::ShuttingDown),
|
||||
@@ -224,6 +202,9 @@ impl DaemonStateMachine {
|
||||
}
|
||||
|
||||
/// Process a game event and potentially trigger a transition.
|
||||
///
|
||||
/// Only returns state transitions
|
||||
/// All data is stored in events in the timeline and processed at game end.
|
||||
pub fn process_event(&self, event: &GameEvent) -> Option<StateTransition> {
|
||||
trace!(
|
||||
"Processing event in state {:?}: {:?}",
|
||||
@@ -232,33 +213,13 @@ impl DaemonStateMachine {
|
||||
);
|
||||
|
||||
match event {
|
||||
GameEvent::GameStart(info) => {
|
||||
Some(StateTransition::GameStarted {
|
||||
game_id: info.game_id,
|
||||
champion: info.champion.clone(),
|
||||
queue_type: info.queue_type.clone(),
|
||||
queue_id: info.queue_id,
|
||||
game_mode: info.game_mode.clone(),
|
||||
map_name: info.map_name.clone(),
|
||||
team: info.team,
|
||||
summoner_name: info.summoner_name.clone(),
|
||||
puuid: None, // Will be populated from client.fetch_player_game_metadata()
|
||||
runes: None, // Will be populated from client.fetch_player_game_metadata()
|
||||
summoner_spells: None, // Will be populated from client.fetch_player_game_metadata()
|
||||
})
|
||||
}
|
||||
GameEvent::GameEnd(info) => Some(StateTransition::GameEnded {
|
||||
game_end_info: Some(info.clone()),
|
||||
final_items: None, // Will be populated from client.fetch_final_items()
|
||||
}),
|
||||
GameEvent::GameStart(_) => Some(StateTransition::GameStarted),
|
||||
GameEvent::GameEnd(_) => Some(StateTransition::GameEnded),
|
||||
GameEvent::PhaseChange(info) => {
|
||||
// Only trigger GameEnded on EndOfGame phase (stats are available by then)
|
||||
// The actual GameEnd event with stats comes from /lol-end-of-game/v1/eog-stats-block
|
||||
if info.phase == "EndOfGame" && self.is_recording() {
|
||||
Some(StateTransition::GameEnded {
|
||||
game_end_info: None,
|
||||
final_items: None,
|
||||
})
|
||||
Some(StateTransition::GameEnded)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -303,23 +264,9 @@ mod tests {
|
||||
let machine = DaemonStateMachine::new();
|
||||
|
||||
machine.transition(StateTransition::ClientStarted);
|
||||
let new_state = machine.transition(StateTransition::GameStarted {
|
||||
game_id: 12345,
|
||||
champion: Some("Ahri".to_string()),
|
||||
queue_type: None,
|
||||
queue_id: None,
|
||||
game_mode: None,
|
||||
map_name: None,
|
||||
team: None,
|
||||
summoner_name: None,
|
||||
puuid: None,
|
||||
runes: None,
|
||||
summoner_spells: None,
|
||||
});
|
||||
let new_state = machine.transition(StateTransition::GameStarted);
|
||||
|
||||
assert_eq!(new_state, Some(DaemonState::Recording));
|
||||
assert_eq!(machine.current_game_id(), Some(12345));
|
||||
assert_eq!(machine.current_champion(), Some("Ahri".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -327,19 +274,7 @@ mod tests {
|
||||
let machine = DaemonStateMachine::new();
|
||||
|
||||
// Can't start recording from Idle
|
||||
let result = machine.transition(StateTransition::GameStarted {
|
||||
game_id: 12345,
|
||||
champion: None,
|
||||
queue_type: None,
|
||||
queue_id: None,
|
||||
game_mode: None,
|
||||
map_name: None,
|
||||
team: None,
|
||||
summoner_name: None,
|
||||
puuid: None,
|
||||
runes: None,
|
||||
summoner_spells: None,
|
||||
});
|
||||
let result = machine.transition(StateTransition::GameStarted);
|
||||
|
||||
assert_eq!(result, None);
|
||||
assert_eq!(machine.current_state(), DaemonState::Idle);
|
||||
|
||||
Reference in New Issue
Block a user