solverforge_solver/
event.rs

1//! Event system for solver monitoring and extensibility.
2//!
3//! The event system provides hooks for monitoring solver progress and
4//! extending solver behavior. Event listeners can be registered to receive
5//! notifications about solver lifecycle events.
6//!
7//! # Event Types
8//!
9//! - **Solver Events**: Best solution changed, solving started/ended
10//! - **Phase Events**: Phase started, phase ended
11//! - **Step Events**: Step started, step ended
12//!
13//! # Usage
14//!
15//! ```
16//! use std::sync::Arc;
17//! use solverforge_solver::event::{SolverEventSupport, SolverEventListener};
18//! use solverforge_core::domain::PlanningSolution;
19//! use solverforge_core::score::SimpleScore;
20//!
21//! #[derive(Clone, Debug)]
22//! struct MySolution { score: Option<SimpleScore> }
23//! impl PlanningSolution for MySolution {
24//!     type Score = SimpleScore;
25//!     fn score(&self) -> Option<Self::Score> { self.score.clone() }
26//!     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
27//! }
28//!
29//! #[derive(Debug)]
30//! struct MyListener;
31//! impl SolverEventListener<MySolution> for MyListener {
32//!     fn on_best_solution_changed(&self, _solution: &MySolution, score: &SimpleScore) {
33//!         println!("New best: {:?}", score);
34//!     }
35//! }
36//!
37//! let mut support = SolverEventSupport::<MySolution>::new();
38//! support.add_solver_listener(Arc::new(MyListener));
39//! ```
40
41use std::fmt::Debug;
42use std::sync::Arc;
43
44use solverforge_core::domain::PlanningSolution;
45
46/// Listener for solver-level events.
47///
48/// Implement this trait to receive notifications about high-level
49/// solver events like best solution changes.
50pub trait SolverEventListener<S: PlanningSolution>: Send + Sync + Debug {
51    /// Called when a new best solution is found.
52    ///
53    /// # Arguments
54    ///
55    /// * `solution` - The new best solution
56    /// * `score` - The score of the new best solution
57    fn on_best_solution_changed(&self, solution: &S, score: &S::Score);
58
59    /// Called when solving starts.
60    fn on_solving_started(&self, _solution: &S) {}
61
62    /// Called when solving ends.
63    fn on_solving_ended(&self, _solution: &S, _is_terminated_early: bool) {}
64}
65
66/// Listener for phase lifecycle events.
67///
68/// Implement this trait to receive notifications about phase
69/// transitions during solving.
70pub trait PhaseLifecycleListener<S: PlanningSolution>: Send + Sync + Debug {
71    /// Called when a phase starts.
72    ///
73    /// # Arguments
74    ///
75    /// * `phase_index` - The index of the phase (0-based)
76    /// * `phase_type` - The type name of the phase
77    fn on_phase_started(&self, phase_index: usize, phase_type: &str);
78
79    /// Called when a phase ends.
80    ///
81    /// # Arguments
82    ///
83    /// * `phase_index` - The index of the phase (0-based)
84    /// * `phase_type` - The type name of the phase
85    fn on_phase_ended(&self, phase_index: usize, phase_type: &str);
86}
87
88/// Listener for step-level events within a phase.
89///
90/// Implement this trait to receive fine-grained notifications
91/// about individual solving steps.
92pub trait StepLifecycleListener<S: PlanningSolution>: Send + Sync + Debug {
93    /// Called when a step starts.
94    ///
95    /// # Arguments
96    ///
97    /// * `step_index` - The index of the step within the current phase
98    fn on_step_started(&self, step_index: u64);
99
100    /// Called when a step ends.
101    ///
102    /// # Arguments
103    ///
104    /// * `step_index` - The index of the step within the current phase
105    /// * `score` - The score after this step
106    fn on_step_ended(&self, step_index: u64, score: &S::Score);
107}
108
109/// Central event broadcaster for solver events.
110///
111/// Manages listener registration and event distribution.
112/// All listener methods are called synchronously in registration order.
113pub struct SolverEventSupport<S: PlanningSolution> {
114    /// Solver-level event listeners.
115    solver_listeners: Vec<Arc<dyn SolverEventListener<S>>>,
116
117    /// Phase lifecycle listeners.
118    phase_listeners: Vec<Arc<dyn PhaseLifecycleListener<S>>>,
119
120    /// Step lifecycle listeners.
121    step_listeners: Vec<Arc<dyn StepLifecycleListener<S>>>,
122}
123
124impl<S: PlanningSolution> SolverEventSupport<S> {
125    /// Creates a new event support instance.
126    pub fn new() -> Self {
127        Self {
128            solver_listeners: Vec::new(),
129            phase_listeners: Vec::new(),
130            step_listeners: Vec::new(),
131        }
132    }
133
134    // === Listener Registration ===
135
136    /// Adds a solver-level event listener.
137    pub fn add_solver_listener(&mut self, listener: Arc<dyn SolverEventListener<S>>) {
138        self.solver_listeners.push(listener);
139    }
140
141    /// Adds a phase lifecycle listener.
142    pub fn add_phase_listener(&mut self, listener: Arc<dyn PhaseLifecycleListener<S>>) {
143        self.phase_listeners.push(listener);
144    }
145
146    /// Adds a step lifecycle listener.
147    pub fn add_step_listener(&mut self, listener: Arc<dyn StepLifecycleListener<S>>) {
148        self.step_listeners.push(listener);
149    }
150
151    /// Removes all listeners.
152    pub fn clear_listeners(&mut self) {
153        self.solver_listeners.clear();
154        self.phase_listeners.clear();
155        self.step_listeners.clear();
156    }
157
158    // === Event Firing ===
159
160    /// Fires the best solution changed event.
161    pub fn fire_best_solution_changed(&self, solution: &S, score: &S::Score) {
162        for listener in &self.solver_listeners {
163            listener.on_best_solution_changed(solution, score);
164        }
165    }
166
167    /// Fires the solving started event.
168    pub fn fire_solving_started(&self, solution: &S) {
169        for listener in &self.solver_listeners {
170            listener.on_solving_started(solution);
171        }
172    }
173
174    /// Fires the solving ended event.
175    pub fn fire_solving_ended(&self, solution: &S, is_terminated_early: bool) {
176        for listener in &self.solver_listeners {
177            listener.on_solving_ended(solution, is_terminated_early);
178        }
179    }
180
181    /// Fires the phase started event.
182    pub fn fire_phase_started(&self, phase_index: usize, phase_type: &str) {
183        for listener in &self.phase_listeners {
184            listener.on_phase_started(phase_index, phase_type);
185        }
186    }
187
188    /// Fires the phase ended event.
189    pub fn fire_phase_ended(&self, phase_index: usize, phase_type: &str) {
190        for listener in &self.phase_listeners {
191            listener.on_phase_ended(phase_index, phase_type);
192        }
193    }
194
195    /// Fires the step started event.
196    pub fn fire_step_started(&self, step_index: u64) {
197        for listener in &self.step_listeners {
198            listener.on_step_started(step_index);
199        }
200    }
201
202    /// Fires the step ended event.
203    pub fn fire_step_ended(&self, step_index: u64, score: &S::Score) {
204        for listener in &self.step_listeners {
205            listener.on_step_ended(step_index, score);
206        }
207    }
208
209    // === Query Methods ===
210
211    /// Returns the number of solver listeners.
212    pub fn solver_listener_count(&self) -> usize {
213        self.solver_listeners.len()
214    }
215
216    /// Returns the number of phase listeners.
217    pub fn phase_listener_count(&self) -> usize {
218        self.phase_listeners.len()
219    }
220
221    /// Returns the number of step listeners.
222    pub fn step_listener_count(&self) -> usize {
223        self.step_listeners.len()
224    }
225
226    /// Returns true if there are any listeners registered.
227    pub fn has_listeners(&self) -> bool {
228        !self.solver_listeners.is_empty()
229            || !self.phase_listeners.is_empty()
230            || !self.step_listeners.is_empty()
231    }
232}
233
234impl<S: PlanningSolution> Default for SolverEventSupport<S> {
235    fn default() -> Self {
236        Self::new()
237    }
238}
239
240impl<S: PlanningSolution> Debug for SolverEventSupport<S> {
241    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242        f.debug_struct("SolverEventSupport")
243            .field("solver_listeners", &self.solver_listeners.len())
244            .field("phase_listeners", &self.phase_listeners.len())
245            .field("step_listeners", &self.step_listeners.len())
246            .finish()
247    }
248}
249
250/// A logging listener that prints events to stdout.
251///
252/// Useful for debugging and understanding solver behavior.
253#[derive(Debug, Clone, Default)]
254pub struct LoggingEventListener {
255    /// Prefix for log messages.
256    prefix: String,
257}
258
259impl LoggingEventListener {
260    /// Creates a new logging listener.
261    pub fn new() -> Self {
262        Self::default()
263    }
264
265    /// Creates a logging listener with a custom prefix.
266    pub fn with_prefix(prefix: impl Into<String>) -> Self {
267        Self {
268            prefix: prefix.into(),
269        }
270    }
271}
272
273impl<S: PlanningSolution> SolverEventListener<S> for LoggingEventListener {
274    fn on_best_solution_changed(&self, _solution: &S, score: &S::Score) {
275        println!(
276            "{}[Event] New best solution found with score: {:?}",
277            self.prefix, score
278        );
279    }
280
281    fn on_solving_started(&self, _solution: &S) {
282        println!("{}[Event] Solving started", self.prefix);
283    }
284
285    fn on_solving_ended(&self, _solution: &S, is_terminated_early: bool) {
286        if is_terminated_early {
287            println!("{}[Event] Solving ended (terminated early)", self.prefix);
288        } else {
289            println!("{}[Event] Solving ended", self.prefix);
290        }
291    }
292}
293
294impl<S: PlanningSolution> PhaseLifecycleListener<S> for LoggingEventListener {
295    fn on_phase_started(&self, phase_index: usize, phase_type: &str) {
296        println!(
297            "{}[Event] Phase {} ({}) started",
298            self.prefix, phase_index, phase_type
299        );
300    }
301
302    fn on_phase_ended(&self, phase_index: usize, phase_type: &str) {
303        println!(
304            "{}[Event] Phase {} ({}) ended",
305            self.prefix, phase_index, phase_type
306        );
307    }
308}
309
310impl<S: PlanningSolution> StepLifecycleListener<S> for LoggingEventListener {
311    fn on_step_started(&self, step_index: u64) {
312        println!("{}[Event] Step {} started", self.prefix, step_index);
313    }
314
315    fn on_step_ended(&self, step_index: u64, score: &S::Score) {
316        println!(
317            "{}[Event] Step {} ended with score: {:?}",
318            self.prefix, step_index, score
319        );
320    }
321}
322
323/// A counting listener that tracks event occurrences.
324///
325/// Useful for testing and statistics collection.
326#[derive(Debug, Default)]
327pub struct CountingEventListener {
328    best_solution_count: std::sync::atomic::AtomicUsize,
329    solving_started_count: std::sync::atomic::AtomicUsize,
330    solving_ended_count: std::sync::atomic::AtomicUsize,
331    phase_started_count: std::sync::atomic::AtomicUsize,
332    phase_ended_count: std::sync::atomic::AtomicUsize,
333    step_started_count: std::sync::atomic::AtomicUsize,
334    step_ended_count: std::sync::atomic::AtomicUsize,
335}
336
337impl CountingEventListener {
338    /// Creates a new counting listener.
339    pub fn new() -> Self {
340        Self::default()
341    }
342
343    /// Returns the number of best solution changed events.
344    pub fn best_solution_count(&self) -> usize {
345        self.best_solution_count
346            .load(std::sync::atomic::Ordering::SeqCst)
347    }
348
349    /// Returns the number of solving started events.
350    pub fn solving_started_count(&self) -> usize {
351        self.solving_started_count
352            .load(std::sync::atomic::Ordering::SeqCst)
353    }
354
355    /// Returns the number of solving ended events.
356    pub fn solving_ended_count(&self) -> usize {
357        self.solving_ended_count
358            .load(std::sync::atomic::Ordering::SeqCst)
359    }
360
361    /// Returns the number of phase started events.
362    pub fn phase_started_count(&self) -> usize {
363        self.phase_started_count
364            .load(std::sync::atomic::Ordering::SeqCst)
365    }
366
367    /// Returns the number of phase ended events.
368    pub fn phase_ended_count(&self) -> usize {
369        self.phase_ended_count
370            .load(std::sync::atomic::Ordering::SeqCst)
371    }
372
373    /// Returns the number of step started events.
374    pub fn step_started_count(&self) -> usize {
375        self.step_started_count
376            .load(std::sync::atomic::Ordering::SeqCst)
377    }
378
379    /// Returns the number of step ended events.
380    pub fn step_ended_count(&self) -> usize {
381        self.step_ended_count
382            .load(std::sync::atomic::Ordering::SeqCst)
383    }
384
385    /// Resets all counters to zero.
386    pub fn reset(&self) {
387        self.best_solution_count
388            .store(0, std::sync::atomic::Ordering::SeqCst);
389        self.solving_started_count
390            .store(0, std::sync::atomic::Ordering::SeqCst);
391        self.solving_ended_count
392            .store(0, std::sync::atomic::Ordering::SeqCst);
393        self.phase_started_count
394            .store(0, std::sync::atomic::Ordering::SeqCst);
395        self.phase_ended_count
396            .store(0, std::sync::atomic::Ordering::SeqCst);
397        self.step_started_count
398            .store(0, std::sync::atomic::Ordering::SeqCst);
399        self.step_ended_count
400            .store(0, std::sync::atomic::Ordering::SeqCst);
401    }
402}
403
404impl<S: PlanningSolution> SolverEventListener<S> for CountingEventListener {
405    fn on_best_solution_changed(&self, _solution: &S, _score: &S::Score) {
406        self.best_solution_count
407            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
408    }
409
410    fn on_solving_started(&self, _solution: &S) {
411        self.solving_started_count
412            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
413    }
414
415    fn on_solving_ended(&self, _solution: &S, _is_terminated_early: bool) {
416        self.solving_ended_count
417            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
418    }
419}
420
421impl<S: PlanningSolution> PhaseLifecycleListener<S> for CountingEventListener {
422    fn on_phase_started(&self, _phase_index: usize, _phase_type: &str) {
423        self.phase_started_count
424            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
425    }
426
427    fn on_phase_ended(&self, _phase_index: usize, _phase_type: &str) {
428        self.phase_ended_count
429            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
430    }
431}
432
433impl<S: PlanningSolution> StepLifecycleListener<S> for CountingEventListener {
434    fn on_step_started(&self, _step_index: u64) {
435        self.step_started_count
436            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
437    }
438
439    fn on_step_ended(&self, _step_index: u64, _score: &S::Score) {
440        self.step_ended_count
441            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
442    }
443}
444
445#[cfg(test)]
446#[path = "event_tests.rs"]
447mod tests;