Skip to main content

plato_engine_block/
alarm.rs

1//! Alarm — condition-based rules with cooldowns.
2
3/// An alarm condition callback: receives latest tick data, returns true to fire.
4pub type AlarmCondition = Box<dyn Fn(&[(String, f64)]) -> bool + Send + Sync>;
5
6/// State of an alarm.
7#[derive(Debug, Clone, PartialEq)]
8pub enum AlarmState {
9    /// Alarm is idle (not triggered, or cooldown expired).
10    Idle,
11    /// Alarm is active (currently triggered).
12    Active { since_tick: u64 },
13    /// Alarm is in cooldown (recently fired, waiting).
14    Cooldown { until_tick: u64 },
15}
16
17/// An alarm rule with condition and cooldown.
18pub struct AlarmRule {
19    pub name: String,
20    pub condition: AlarmCondition,
21    /// Minimum ticks between alarm fires.
22    pub cooldown_ticks: u64,
23    /// Current state.
24    pub state: AlarmState,
25    /// Tick when the alarm last fired (for cooldown tracking).
26    pub last_fired_tick: u64,
27}
28
29impl AlarmRule {
30    pub fn new(
31        name: impl Into<String>,
32        condition: AlarmCondition,
33        cooldown_ticks: u64,
34    ) -> Self {
35        AlarmRule {
36            name: name.into(),
37            condition,
38            cooldown_ticks,
39            state: AlarmState::Idle,
40            last_fired_tick: 0,
41        }
42    }
43
44    /// Evaluate the alarm condition against tick data.
45    /// Returns true if the alarm fires (transitioned to Active).
46    pub fn evaluate(&mut self, data: &[(String, f64)], current_tick: u64) -> bool {
47        let triggered = (self.condition)(data);
48
49        match &self.state {
50            AlarmState::Idle => {
51                if triggered {
52                    self.state = AlarmState::Active { since_tick: current_tick };
53                    self.last_fired_tick = current_tick;
54                    return true;
55                }
56            }
57            AlarmState::Active { since_tick: _ } => {
58                if !triggered {
59                    self.state = AlarmState::Cooldown {
60                        until_tick: current_tick + self.cooldown_ticks,
61                    };
62                }
63            }
64            AlarmState::Cooldown { until_tick } => {
65                if current_tick >= *until_tick {
66                    if triggered {
67                        self.state = AlarmState::Active { since_tick: current_tick };
68                        self.last_fired_tick = current_tick;
69                        return true;
70                    } else {
71                        self.state = AlarmState::Idle;
72                    }
73                }
74            }
75        }
76        false
77    }
78
79    /// Reset alarm to idle.
80    pub fn reset(&mut self) {
81        self.state = AlarmState::Idle;
82    }
83}
84
85impl core::fmt::Debug for AlarmRule {
86    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
87        f.debug_struct("AlarmRule")
88            .field("name", &self.name)
89            .field("cooldown_ticks", &self.cooldown_ticks)
90            .field("state", &self.state)
91            .finish()
92    }
93}