Skip to main content

manabrew_agent_interface/
game_log_event.rs

1use manabrew_engine::agent::{GameLogEvent, GameLogKind};
2use manabrew_engine::ids::{CardId, PlayerId};
3use serde::{Deserialize, Serialize};
4use std::time::{SystemTime, UNIX_EPOCH};
5
6/// Structured log entry sent from the engine thread to the frontend.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub struct GameLogEntryDto {
10    pub entry_type: GameLogEntryTypeDto,
11    pub message: String,
12    pub timestamp_ms: u64,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub player_id: Option<String>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub card_id: Option<String>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub source_card_id: Option<String>,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub target_card_id: Option<String>,
21}
22
23#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub enum GameLogEntryTypeDto {
26    Info,
27    Action,
28    Stack,
29    Priority,
30    Rule,
31    Warning,
32}
33
34impl GameLogEntryDto {
35    pub fn from_message(message: &str) -> Self {
36        Self {
37            entry_type: GameLogEntryTypeDto::Info,
38            message: message.to_string(),
39            timestamp_ms: now_timestamp_ms(),
40            player_id: None,
41            card_id: None,
42            source_card_id: None,
43            target_card_id: None,
44        }
45    }
46
47    pub fn from_event(event: GameLogEvent) -> Self {
48        Self {
49            entry_type: map_kind(event.kind),
50            message: event.message,
51            timestamp_ms: now_timestamp_ms(),
52            player_id: event.player.map(player_id_str),
53            card_id: event
54                .card
55                .or(event.source_card)
56                .or(event.target_card)
57                .map(card_id_str),
58            source_card_id: event.source_card.map(card_id_str),
59            target_card_id: event.target_card.map(card_id_str),
60        }
61    }
62}
63
64fn now_timestamp_ms() -> u64 {
65    #[cfg(not(target_arch = "wasm32"))]
66    {
67        SystemTime::now()
68            .duration_since(UNIX_EPOCH)
69            .unwrap_or_default()
70            .as_millis() as u64
71    }
72    #[cfg(target_arch = "wasm32")]
73    {
74        0 // Timestamps are display-only; WASM doesn't need real time here
75    }
76}
77
78fn map_kind(kind: GameLogKind) -> GameLogEntryTypeDto {
79    match kind {
80        GameLogKind::Info => GameLogEntryTypeDto::Info,
81        GameLogKind::Action => GameLogEntryTypeDto::Action,
82        GameLogKind::Stack => GameLogEntryTypeDto::Stack,
83        GameLogKind::Priority => GameLogEntryTypeDto::Priority,
84        GameLogKind::Rule => GameLogEntryTypeDto::Rule,
85        GameLogKind::Warning => GameLogEntryTypeDto::Warning,
86    }
87}
88
89fn player_id_str(id: PlayerId) -> String {
90    format!("player-{}", id.0)
91}
92
93fn card_id_str(id: CardId) -> String {
94    format!("card-{}", id.0)
95}