feat(api): add Observer trait and NullObserver default
Observer replaces verbose: bool with structured progress callbacks: on_iteration_end, on_batch_processed, on_converged — all no-op default impls so users override only what they need. NullObserver is a ZST default. Send + Sync bounds deferred to T3 (Rayon support). Fully additive — wired into History::converge in Task 12. Part of T2 of docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md.
This commit is contained in:
@@ -19,6 +19,7 @@ pub mod gaussian;
|
|||||||
mod history;
|
mod history;
|
||||||
mod key_table;
|
mod key_table;
|
||||||
mod matrix;
|
mod matrix;
|
||||||
|
mod observer;
|
||||||
mod outcome;
|
mod outcome;
|
||||||
mod rating;
|
mod rating;
|
||||||
pub(crate) mod schedule;
|
pub(crate) mod schedule;
|
||||||
@@ -33,6 +34,7 @@ pub use gaussian::Gaussian;
|
|||||||
pub use history::History;
|
pub use history::History;
|
||||||
pub use key_table::KeyTable;
|
pub use key_table::KeyTable;
|
||||||
use matrix::Matrix;
|
use matrix::Matrix;
|
||||||
|
pub use observer::{NullObserver, Observer};
|
||||||
pub use outcome::Outcome;
|
pub use outcome::Outcome;
|
||||||
pub use rating::Rating;
|
pub use rating::Rating;
|
||||||
pub use schedule::ScheduleReport;
|
pub use schedule::ScheduleReport;
|
||||||
|
|||||||
48
src/observer.rs
Normal file
48
src/observer.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
//! Observer trait for progress reporting during convergence.
|
||||||
|
//!
|
||||||
|
//! Replaces the old `verbose: bool` + `println!` path. Callers wire in any
|
||||||
|
//! observer that implements the trait; default methods are no-ops so users
|
||||||
|
//! override only what they need.
|
||||||
|
|
||||||
|
use crate::time::Time;
|
||||||
|
|
||||||
|
/// Receives progress callbacks during `History::converge`.
|
||||||
|
///
|
||||||
|
/// All methods have default no-op implementations; implement only what's
|
||||||
|
/// interesting. Send/Sync is NOT required in T2 (added in T3 along with
|
||||||
|
/// Rayon support).
|
||||||
|
pub trait Observer<T: Time> {
|
||||||
|
/// Called after each convergence iteration across the whole history.
|
||||||
|
fn on_iteration_end(&self, _iter: usize, _max_step: (f64, f64)) {}
|
||||||
|
|
||||||
|
/// Called after each time slice is processed within an iteration.
|
||||||
|
fn on_batch_processed(&self, _time: &T, _slice_idx: usize, _n_events: usize) {}
|
||||||
|
|
||||||
|
/// Called once when convergence completes (or max iters is reached).
|
||||||
|
fn on_converged(&self, _iters: usize, _final_step: (f64, f64), _converged: bool) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ZST no-op observer; the default when none is configured.
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub struct NullObserver;
|
||||||
|
|
||||||
|
impl<T: Time> Observer<T> for NullObserver {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_observer_compiles_for_i64() {
|
||||||
|
let o = NullObserver;
|
||||||
|
<NullObserver as Observer<i64>>::on_iteration_end(&o, 1, (0.0, 0.0));
|
||||||
|
<NullObserver as Observer<i64>>::on_converged(&o, 5, (1e-6, 1e-6), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn null_observer_compiles_for_untimed() {
|
||||||
|
use crate::Untimed;
|
||||||
|
let o = NullObserver;
|
||||||
|
<NullObserver as Observer<Untimed>>::on_iteration_end(&o, 1, (0.0, 0.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user