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,26 +353,25 @@ impl Daemon {
|
|||||||
if let Some(_new_state) = self.state_machine.transition(transition.clone()) {
|
if let Some(_new_state) = self.state_machine.transition(transition.clone()) {
|
||||||
// Handle recording start/stop
|
// Handle recording start/stop
|
||||||
match transition {
|
match transition {
|
||||||
StateTransition::GameStarted {
|
StateTransition::GameStarted => {
|
||||||
game_id,
|
// Extract data from the GameStart event that was stored in the timeline
|
||||||
champion,
|
let (game_id, queue_type, queue_id, game_mode, map_name, session) =
|
||||||
queue_type,
|
if let GameEvent::GameStart(ref info) = event {
|
||||||
queue_id,
|
(
|
||||||
game_mode,
|
info.game_id,
|
||||||
map_name,
|
info.queue_type.clone(),
|
||||||
team,
|
info.queue_id,
|
||||||
summoner_name,
|
info.game_mode.clone(),
|
||||||
puuid: transition_puuid,
|
info.map_name.clone(),
|
||||||
runes: transition_runes,
|
info.session.clone(),
|
||||||
summoner_spells: transition_summoner_spells,
|
)
|
||||||
} => {
|
} else {
|
||||||
|
(0, None, None, None, None, None)
|
||||||
|
};
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"[EVENT_HANDLER] GameStarted transition - game_id: {}, champion: {:?}, queue_type: {:?}, game_mode: {:?}",
|
"[EVENT_HANDLER] GameStarted transition - game_id: {}, queue_type: {:?}, game_mode: {:?}",
|
||||||
game_id, champion, queue_type, game_mode
|
game_id, queue_type, game_mode
|
||||||
);
|
|
||||||
info!(
|
|
||||||
"[EVENT_HANDLER] Transition provided: puuid={:?}, runes={:?}, summoner_spells={:?}",
|
|
||||||
transition_puuid, transition_runes, transition_summoner_spells
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// If already recording, stop the current recording first
|
// If already recording, stop the current recording first
|
||||||
@@ -388,51 +387,56 @@ impl Daemon {
|
|||||||
|
|
||||||
info!("[EVENT_HANDLER] Calling start_recording...");
|
info!("[EVENT_HANDLER] Calling start_recording...");
|
||||||
|
|
||||||
// Wrap the start_recording call to catch any panics
|
// Fetch player game metadata (puuid, runes, summoner spells)
|
||||||
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
|
|
||||||
let player_metadata =
|
let player_metadata =
|
||||||
self.lqp_client.fetch_player_game_metadata().await.ok();
|
self.lqp_client.fetch_player_game_metadata().await.ok();
|
||||||
let (fetched_puuid, fetched_runes, fetched_summoner_spells) =
|
let (puuid, runes, summoner_spells, champion_id, team, summoner_name) =
|
||||||
player_metadata.map_or((None, None, None), |m| {
|
player_metadata.map_or((None, None, None, None, None, None), |m| {
|
||||||
(m.puuid, m.runes, m.summoner_spells)
|
(
|
||||||
|
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
|
// If we have puuid and session, extract player-specific data from session
|
||||||
let final_puuid = transition_puuid.or(fetched_puuid);
|
let (champion, final_team, final_summoner_name) = if let Some(ref p) = puuid
|
||||||
let final_runes = transition_runes.or(fetched_runes);
|
{
|
||||||
let final_summoner_spells =
|
if let Some(ref sess) = session {
|
||||||
transition_summoner_spells.or(fetched_summoner_spells);
|
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!(
|
info!(
|
||||||
"[EVENT_HANDLER] Final values: puuid={:?}, runes={:?}, summoner_spells={:?}",
|
"[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
|
// Fetch all players identities for puuid mapping
|
||||||
@@ -452,15 +456,15 @@ impl Daemon {
|
|||||||
|
|
||||||
// Build game metadata for timeline
|
// Build game metadata for timeline
|
||||||
let metadata_update = record_daemon::timeline::MetadataUpdate {
|
let metadata_update = record_daemon::timeline::MetadataUpdate {
|
||||||
queue_type: queue_type.clone(),
|
queue_type,
|
||||||
queue_id,
|
queue_id,
|
||||||
game_mode: game_mode.clone(),
|
game_mode,
|
||||||
map_name: map_name.clone(),
|
map_name,
|
||||||
team,
|
team: final_team,
|
||||||
summoner_name: summoner_name.clone(),
|
summoner_name: final_summoner_name,
|
||||||
puuid: final_puuid,
|
puuid,
|
||||||
runes: final_runes,
|
runes,
|
||||||
summoner_spells: final_summoner_spells,
|
summoner_spells,
|
||||||
all_players: all_players_info,
|
all_players: all_players_info,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
@@ -468,7 +472,7 @@ impl Daemon {
|
|||||||
if let Err(e) = self
|
if let Err(e) = self
|
||||||
.start_recording_with_metadata(
|
.start_recording_with_metadata(
|
||||||
game_id,
|
game_id,
|
||||||
champion_name.as_deref(),
|
champion.as_deref(),
|
||||||
metadata_update,
|
metadata_update,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -480,14 +484,8 @@ impl Daemon {
|
|||||||
info!("[EVENT_HANDLER] start_recording completed successfully");
|
info!("[EVENT_HANDLER] start_recording completed successfully");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StateTransition::GameEnded {
|
StateTransition::GameEnded => {
|
||||||
game_end_info,
|
info!("[EVENT_HANDLER] GameEnded transition");
|
||||||
final_items: _,
|
|
||||||
} => {
|
|
||||||
info!(
|
|
||||||
"[EVENT_HANDLER] GameEnded transition with info: {:?}",
|
|
||||||
game_end_info
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch final items before stopping
|
// Fetch final items before stopping
|
||||||
let fetched_final_items =
|
let fetched_final_items =
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
//! Daemon state machine implementation.
|
//! Daemon state machine implementation.
|
||||||
|
//!
|
||||||
|
//! Only tracks state
|
||||||
|
//! All game data is stored in events and processed at game end.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -6,7 +9,7 @@ use parking_lot::RwLock;
|
|||||||
use tracing::{info, trace, warn};
|
use tracing::{info, trace, warn};
|
||||||
|
|
||||||
use super::DaemonStatus;
|
use super::DaemonStatus;
|
||||||
use crate::lqp::{GameEndInfo, GameEvent, GameflowPhase, ItemBuild, RunePage, SummonerSpells};
|
use crate::lqp::{GameEvent, GameflowPhase};
|
||||||
|
|
||||||
/// Internal daemon state.
|
/// Internal daemon state.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -36,36 +39,19 @@ impl From<DaemonState> for DaemonStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// State transition event.
|
/// State transition event.
|
||||||
|
///
|
||||||
|
/// Simplified: Only tracks state changes, no data passing.
|
||||||
|
/// Data is stored in events and processed at game end.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum StateTransition {
|
pub enum StateTransition {
|
||||||
/// League Client started.
|
/// League Client started.
|
||||||
ClientStarted,
|
ClientStarted,
|
||||||
/// League Client stopped.
|
/// League Client stopped.
|
||||||
ClientStopped,
|
ClientStopped,
|
||||||
/// Game started.
|
/// Game started
|
||||||
GameStarted {
|
GameStarted,
|
||||||
game_id: u64,
|
/// Game ended
|
||||||
champion: Option<String>,
|
GameEnded,
|
||||||
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>,
|
|
||||||
},
|
|
||||||
/// Error occurred.
|
/// Error occurred.
|
||||||
Error(String),
|
Error(String),
|
||||||
/// Error recovered.
|
/// Error recovered.
|
||||||
@@ -153,13 +139,11 @@ impl DaemonStateMachine {
|
|||||||
|
|
||||||
// Update related state
|
// Update related state
|
||||||
match &transition {
|
match &transition {
|
||||||
StateTransition::GameStarted {
|
StateTransition::GameStarted => {
|
||||||
game_id, champion, ..
|
// Game ID and champion are now tracked in events, not state
|
||||||
} => {
|
|
||||||
*self.current_game_id.write() = Some(*game_id);
|
|
||||||
*self.current_champion.write() = champion.clone();
|
|
||||||
}
|
}
|
||||||
StateTransition::GameEnded { .. } => {
|
StateTransition::GameEnded => {
|
||||||
|
// Clear state tracking
|
||||||
*self.current_game_id.write() = None;
|
*self.current_game_id.write() = None;
|
||||||
*self.current_champion.write() = None;
|
*self.current_champion.write() = None;
|
||||||
}
|
}
|
||||||
@@ -189,20 +173,14 @@ impl DaemonStateMachine {
|
|||||||
|
|
||||||
// From Monitoring
|
// From Monitoring
|
||||||
(DaemonState::Monitoring, StateTransition::ClientStopped) => Some(DaemonState::Idle),
|
(DaemonState::Monitoring, StateTransition::ClientStopped) => Some(DaemonState::Idle),
|
||||||
(DaemonState::Monitoring, StateTransition::GameStarted { .. }) => {
|
(DaemonState::Monitoring, StateTransition::GameStarted) => Some(DaemonState::Recording),
|
||||||
Some(DaemonState::Recording)
|
|
||||||
}
|
|
||||||
(DaemonState::Monitoring, StateTransition::Error(_)) => Some(DaemonState::Error),
|
(DaemonState::Monitoring, StateTransition::Error(_)) => Some(DaemonState::Error),
|
||||||
(DaemonState::Monitoring, StateTransition::Shutdown) => Some(DaemonState::ShuttingDown),
|
(DaemonState::Monitoring, StateTransition::Shutdown) => Some(DaemonState::ShuttingDown),
|
||||||
|
|
||||||
// From Recording
|
// From Recording
|
||||||
(DaemonState::Recording, StateTransition::GameEnded { .. }) => {
|
(DaemonState::Recording, StateTransition::GameEnded) => Some(DaemonState::Monitoring),
|
||||||
Some(DaemonState::Monitoring)
|
|
||||||
}
|
|
||||||
// Allow GameStarted from Recording (handles case where GameEnded wasn't received)
|
// Allow GameStarted from Recording (handles case where GameEnded wasn't received)
|
||||||
(DaemonState::Recording, StateTransition::GameStarted { .. }) => {
|
(DaemonState::Recording, StateTransition::GameStarted) => Some(DaemonState::Recording),
|
||||||
Some(DaemonState::Recording)
|
|
||||||
}
|
|
||||||
(DaemonState::Recording, StateTransition::ClientStopped) => Some(DaemonState::Idle),
|
(DaemonState::Recording, StateTransition::ClientStopped) => Some(DaemonState::Idle),
|
||||||
(DaemonState::Recording, StateTransition::Error(_)) => Some(DaemonState::Error),
|
(DaemonState::Recording, StateTransition::Error(_)) => Some(DaemonState::Error),
|
||||||
(DaemonState::Recording, StateTransition::Shutdown) => Some(DaemonState::ShuttingDown),
|
(DaemonState::Recording, StateTransition::Shutdown) => Some(DaemonState::ShuttingDown),
|
||||||
@@ -224,6 +202,9 @@ impl DaemonStateMachine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Process a game event and potentially trigger a transition.
|
/// 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> {
|
pub fn process_event(&self, event: &GameEvent) -> Option<StateTransition> {
|
||||||
trace!(
|
trace!(
|
||||||
"Processing event in state {:?}: {:?}",
|
"Processing event in state {:?}: {:?}",
|
||||||
@@ -232,33 +213,13 @@ impl DaemonStateMachine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
GameEvent::GameStart(info) => {
|
GameEvent::GameStart(_) => Some(StateTransition::GameStarted),
|
||||||
Some(StateTransition::GameStarted {
|
GameEvent::GameEnd(_) => Some(StateTransition::GameEnded),
|
||||||
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::PhaseChange(info) => {
|
GameEvent::PhaseChange(info) => {
|
||||||
// Only trigger GameEnded on EndOfGame phase (stats are available by then)
|
// 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
|
// The actual GameEnd event with stats comes from /lol-end-of-game/v1/eog-stats-block
|
||||||
if info.phase == "EndOfGame" && self.is_recording() {
|
if info.phase == "EndOfGame" && self.is_recording() {
|
||||||
Some(StateTransition::GameEnded {
|
Some(StateTransition::GameEnded)
|
||||||
game_end_info: None,
|
|
||||||
final_items: None,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -303,23 +264,9 @@ mod tests {
|
|||||||
let machine = DaemonStateMachine::new();
|
let machine = DaemonStateMachine::new();
|
||||||
|
|
||||||
machine.transition(StateTransition::ClientStarted);
|
machine.transition(StateTransition::ClientStarted);
|
||||||
let new_state = machine.transition(StateTransition::GameStarted {
|
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(new_state, Some(DaemonState::Recording));
|
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]
|
#[test]
|
||||||
@@ -327,19 +274,7 @@ mod tests {
|
|||||||
let machine = DaemonStateMachine::new();
|
let machine = DaemonStateMachine::new();
|
||||||
|
|
||||||
// Can't start recording from Idle
|
// Can't start recording from Idle
|
||||||
let result = machine.transition(StateTransition::GameStarted {
|
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(result, None);
|
assert_eq!(result, None);
|
||||||
assert_eq!(machine.current_state(), DaemonState::Idle);
|
assert_eq!(machine.current_state(), DaemonState::Idle);
|
||||||
|
|||||||
Reference in New Issue
Block a user