use vj_abi::Region;
use crate::sched::SchedTime;
use crate::state::{FourStateValue, StateCellId};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RegionCallbackRegionError {
NotDrained(Region),
}
impl std::fmt::Display for RegionCallbackRegionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RegionCallbackRegionError::NotDrained(region) => {
write!(
f,
"{region:?} is never drained by Engine::drain_region_callbacks (only \
Region::Reactive and Region::Postponed are); a callback registered for it \
would never fire"
)
}
}
}
}
impl std::error::Error for RegionCallbackRegionError {}
pub trait SchedulerCtx {
fn now(&self) -> SchedTime;
fn read(&self, cell: StateCellId) -> FourStateValue;
fn rearm(&self, region: vj_abi::Region, cb: Box<dyn RegionCallback>);
fn take_monitor_changed(&self) -> bool;
}
pub trait RegionCallback: Send {
fn invoke(&mut self, ctx: &dyn SchedulerCtx);
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
struct RecordingCallback {
seen: Arc<Mutex<Vec<FourStateValue>>>,
cell: StateCellId,
}
impl RegionCallback for RecordingCallback {
fn invoke(&mut self, ctx: &dyn SchedulerCtx) {
self.seen.lock().unwrap().push(ctx.read(self.cell));
}
}
struct FixedCtx {
now: SchedTime,
cell: StateCellId,
value: FourStateValue,
}
impl SchedulerCtx for FixedCtx {
fn now(&self) -> SchedTime {
self.now
}
fn read(&self, cell: StateCellId) -> FourStateValue {
assert_eq!(
cell, self.cell,
"this fixed test ctx only ever answers for the one cell it was built with"
);
self.value.clone()
}
fn rearm(&self, _region: vj_abi::Region, _cb: Box<dyn RegionCallback>) {
panic!("this fixed test ctx is never asked to re-arm a callback");
}
fn take_monitor_changed(&self) -> bool {
panic!("this fixed test ctx has no $monitor");
}
}
#[test]
fn region_callback_invoke_receives_the_ctx_and_can_read_the_named_cell() {
let cell = StateCellId(7);
let seen = Arc::new(Mutex::new(Vec::new()));
let mut cb = RecordingCallback {
seen: seen.clone(),
cell,
};
let ctx = FixedCtx {
now: SchedTime { ticks: 3 },
cell,
value: FourStateValue::Narrow(42),
};
cb.invoke(&ctx);
let recorded = seen.lock().unwrap();
assert_eq!(recorded.len(), 1, "invoke must call through exactly once");
assert!(matches!(recorded[0], FourStateValue::Narrow(42)));
}
}