1use std::cell::RefCell;
18
19use crate::Context;
20use crate::cell::Source;
21
22pub trait TimelineSource {
28 fn tick(&mut self, now: u64) -> bool;
31
32 fn next_fire(&self) -> Option<u64>;
34}
35
36#[derive(Debug, Clone, Copy, Default)]
39pub struct ManualClock {
40 now: u64,
41}
42
43impl ManualClock {
44 pub fn new() -> Self {
45 Self { now: 0 }
46 }
47 pub fn now(&self) -> u64 {
48 self.now
49 }
50 pub fn advance(&mut self, now: u64) -> u64 {
53 self.now = self.now.max(now);
54 self.now
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct TimerCore {
66 fire_at: u64,
67 fired: bool,
68}
69
70impl TimerCore {
71 pub fn new(fire_at: u64) -> Self {
72 Self {
73 fire_at,
74 fired: false,
75 }
76 }
77 pub fn fired(&self) -> bool {
78 self.fired
79 }
80}
81
82impl TimelineSource for TimerCore {
83 fn tick(&mut self, now: u64) -> bool {
84 if self.fired || now < self.fire_at {
85 return false;
86 }
87 self.fired = true;
88 true
89 }
90 fn next_fire(&self) -> Option<u64> {
91 if self.fired { None } else { Some(self.fire_at) }
92 }
93}
94
95pub struct TimerCell {
98 core: RefCell<TimerCore>,
99 fired: Source<bool>,
100}
101
102impl TimerCell {
103 pub fn new(ctx: &Context, fire_at: u64) -> Self {
104 Self {
105 core: RefCell::new(TimerCore::new(fire_at)),
106 fired: ctx.source(false),
107 }
108 }
109
110 pub fn tick(&self, ctx: &Context, now: u64) -> bool {
114 let edge = self.core.borrow_mut().tick(now);
115 if edge {
116 self.fired.set(ctx, true);
117 }
118 edge
119 }
120
121 pub fn has_fired(&self, ctx: &Context) -> bool {
123 self.fired.get(ctx)
124 }
125
126 pub fn value(&self, ctx: &Context) -> Option<()> {
128 if self.fired.get(ctx) { Some(()) } else { None }
129 }
130
131 pub fn fired_cell(&self) -> Source<bool> {
133 self.fired
134 }
135
136 pub fn next_fire(&self) -> Option<u64> {
137 self.core.borrow().next_fire()
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct IntervalCore {
150 period: u64,
151 next: u64,
152 count: u64,
153}
154
155impl IntervalCore {
156 pub fn new(period: u64) -> Self {
157 let period = period.max(1);
158 Self {
159 period,
160 next: period,
161 count: 0,
162 }
163 }
164 pub fn count(&self) -> u64 {
165 self.count
166 }
167
168 fn fires_this_tick(&self, now: u64) -> u64 {
170 if now < self.next {
171 0
172 } else {
173 (now - self.next) / self.period + 1
174 }
175 }
176}
177
178impl TimelineSource for IntervalCore {
179 fn tick(&mut self, now: u64) -> bool {
180 let fires = self.fires_this_tick(now);
181 if fires == 0 {
182 return false;
183 }
184 self.count += fires;
185 self.next += fires * self.period;
186 true
187 }
188 fn next_fire(&self) -> Option<u64> {
189 Some(self.next)
190 }
191}
192
193pub struct IntervalCell {
196 core: RefCell<IntervalCore>,
197 count: Source<u64>,
198}
199
200impl IntervalCell {
201 pub fn new(ctx: &Context, period: u64) -> Self {
202 Self {
203 core: RefCell::new(IntervalCore::new(period)),
204 count: ctx.source(0u64),
205 }
206 }
207
208 pub fn tick(&self, ctx: &Context, now: u64) -> bool {
211 let edge = self.core.borrow_mut().tick(now);
212 if edge {
213 let c = self.core.borrow().count();
214 self.count.set(ctx, c);
215 }
216 edge
217 }
218
219 pub fn count(&self, ctx: &Context) -> u64 {
221 self.count.get(ctx)
222 }
223
224 pub fn count_cell(&self) -> Source<u64> {
225 self.count
226 }
227
228 pub fn next_fire(&self) -> Option<u64> {
229 self.core.borrow().next_fire()
230 }
231}
232
233fn count_upto(n: u64, o: u64, cycle: u64) -> u64 {
239 if o == 0 {
240 n / cycle
241 } else if o <= n {
242 (n - o) / cycle + 1
243 } else {
244 0
245 }
246}
247
248#[derive(Debug, Clone, PartialEq, Eq)]
253pub struct CronCore {
254 cycle: u64,
255 offsets: Vec<u64>,
256 cursor: u64,
257 count: u64,
258}
259
260impl CronCore {
261 pub fn new(cycle: u64, offsets: impl IntoIterator<Item = u64>) -> Self {
264 let cycle = cycle.max(1);
265 let mut offsets: Vec<u64> = offsets.into_iter().map(|o| o % cycle).collect();
266 offsets.sort_unstable();
267 offsets.dedup();
268 Self {
269 cycle,
270 offsets,
271 cursor: 0,
272 count: 0,
273 }
274 }
275 pub fn count(&self) -> u64 {
276 self.count
277 }
278
279 fn matches_in(&self, lo: u64, hi: u64) -> u64 {
280 self.offsets
281 .iter()
282 .map(|&o| count_upto(hi, o, self.cycle) - count_upto(lo, o, self.cycle))
283 .sum()
284 }
285}
286
287impl TimelineSource for CronCore {
288 fn tick(&mut self, now: u64) -> bool {
289 if now <= self.cursor {
290 self.cursor = self.cursor.max(now);
291 return false;
292 }
293 let fires = self.matches_in(self.cursor, now);
294 self.cursor = now;
295 if fires == 0 {
296 return false;
297 }
298 self.count += fires;
299 true
300 }
301 fn next_fire(&self) -> Option<u64> {
302 if self.offsets.is_empty() {
303 return None;
304 }
305 let start = self.cursor + 1;
307 let base = start / self.cycle * self.cycle;
308 for cyc in 0..2u64 {
309 let block = base + cyc * self.cycle;
310 for &o in &self.offsets {
311 let cand = block + o;
312 if cand >= start {
313 return Some(cand);
314 }
315 }
316 }
317 None
318 }
319}
320
321pub struct CronCell {
323 core: RefCell<CronCore>,
324 count: Source<u64>,
325}
326
327impl CronCell {
328 pub fn new(ctx: &Context, cycle: u64, offsets: impl IntoIterator<Item = u64>) -> Self {
329 Self {
330 core: RefCell::new(CronCore::new(cycle, offsets)),
331 count: ctx.source(0u64),
332 }
333 }
334
335 pub fn tick(&self, ctx: &Context, now: u64) -> bool {
336 let edge = self.core.borrow_mut().tick(now);
337 if edge {
338 let c = self.core.borrow().count();
339 self.count.set(ctx, c);
340 }
341 edge
342 }
343
344 pub fn count(&self, ctx: &Context) -> u64 {
345 self.count.get(ctx)
346 }
347
348 pub fn count_cell(&self) -> Source<u64> {
349 self.count
350 }
351
352 pub fn next_fire(&self) -> Option<u64> {
353 self.core.borrow().next_fire()
354 }
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq)]
364pub enum Deadlined<T> {
365 Live(T),
366 Expired(T),
367}
368
369impl<T> Deadlined<T> {
370 pub fn is_expired(&self) -> bool {
371 matches!(self, Deadlined::Expired(_))
372 }
373 pub fn value(&self) -> &T {
374 match self {
375 Deadlined::Live(v) | Deadlined::Expired(v) => v,
376 }
377 }
378 pub fn into_value(self) -> T {
379 match self {
380 Deadlined::Live(v) | Deadlined::Expired(v) => v,
381 }
382 }
383}
384
385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
388pub struct DeadlineCore {
389 timer: TimerCore,
390}
391
392impl DeadlineCore {
393 pub fn new(deadline: u64) -> Self {
394 Self {
395 timer: TimerCore::new(deadline),
396 }
397 }
398 pub fn is_expired(&self) -> bool {
399 self.timer.fired()
400 }
401}
402
403impl TimelineSource for DeadlineCore {
404 fn tick(&mut self, now: u64) -> bool {
405 self.timer.tick(now)
406 }
407 fn next_fire(&self) -> Option<u64> {
408 self.timer.next_fire()
409 }
410}
411
412pub struct DeadlineCell<T> {
415 core: RefCell<DeadlineCore>,
416 value: T,
417 expired: Source<bool>,
418}
419
420impl<T> DeadlineCell<T>
421where
422 T: Clone + 'static,
423{
424 pub fn new(ctx: &Context, value: T, deadline: u64) -> Self {
425 Self {
426 core: RefCell::new(DeadlineCore::new(deadline)),
427 value,
428 expired: ctx.source(false),
429 }
430 }
431
432 pub fn tick(&self, ctx: &Context, now: u64) -> bool {
434 let edge = self.core.borrow_mut().tick(now);
435 if edge {
436 self.expired.set(ctx, true);
437 }
438 edge
439 }
440
441 pub fn state(&self, ctx: &Context) -> Deadlined<T> {
443 if self.expired.get(ctx) {
444 Deadlined::Expired(self.value.clone())
445 } else {
446 Deadlined::Live(self.value.clone())
447 }
448 }
449
450 pub fn is_expired(&self, ctx: &Context) -> bool {
451 self.expired.get(ctx)
452 }
453
454 pub fn expired_cell(&self) -> Source<bool> {
455 self.expired
456 }
457
458 pub fn next_fire(&self) -> Option<u64> {
459 self.core.borrow().next_fire()
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
468 fn timer_fires_once() {
469 let mut t = TimerCore::new(3);
470 assert!(!t.tick(1));
471 assert_eq!(t.next_fire(), Some(3));
472 assert!(t.tick(3));
473 assert!(!t.tick(5)); assert_eq!(t.next_fire(), None);
475 assert!(t.fired());
476 }
477
478 #[test]
479 fn timer_cell_edge_only_invalidation() {
480 let ctx = Context::new();
481 let timer = TimerCell::new(&ctx, 3);
482 let observed = {
483 let timer_fired = timer.fired_cell();
484 ctx.computed(move |c| timer_fired.get(c))
485 };
486 assert!(!observed.get(&ctx));
487 assert!(!timer.tick(&ctx, 1));
488 assert_eq!(timer.value(&ctx), None);
489 assert!(timer.tick(&ctx, 3));
490 assert_eq!(timer.value(&ctx), Some(()));
491 assert!(timer.has_fired(&ctx));
492 assert!(!timer.tick(&ctx, 9));
494 assert!(observed.get(&ctx));
495 }
496
497 #[test]
498 fn interval_counts_boundaries_including_jumps() {
499 let mut iv = IntervalCore::new(2);
500 assert!(!iv.tick(1));
501 assert_eq!(iv.count(), 0);
502 assert!(iv.tick(2));
503 assert_eq!(iv.count(), 1);
504 assert!(iv.tick(4));
505 assert_eq!(iv.count(), 2);
506 assert!(!iv.tick(5));
507 assert_eq!(iv.count(), 2);
508 assert!(iv.tick(8)); assert_eq!(iv.count(), 4);
510 assert_eq!(iv.next_fire(), Some(10));
511 }
512
513 #[test]
514 fn cron_fires_on_pattern() {
515 let mut c = CronCore::new(5, [0, 3]);
516 assert!(!c.tick(2));
517 assert_eq!(c.count(), 0);
518 assert_eq!(c.next_fire(), Some(3));
519 assert!(c.tick(3));
520 assert_eq!(c.count(), 1);
521 assert_eq!(c.next_fire(), Some(5));
522 assert!(c.tick(5));
523 assert_eq!(c.count(), 2);
524 assert!(c.tick(8));
525 assert_eq!(c.count(), 3);
526 assert!(c.tick(10));
527 assert_eq!(c.count(), 4);
528 assert_eq!(c.next_fire(), Some(13));
529 }
530
531 #[test]
532 fn deadline_expires_preserving_value() {
533 let ctx = Context::new();
534 let d = DeadlineCell::new(&ctx, "payload".to_string(), 4);
535 assert!(matches!(d.state(&ctx), Deadlined::Live(_)));
536 assert!(!d.tick(&ctx, 2));
537 assert!(d.tick(&ctx, 4));
538 match d.state(&ctx) {
539 Deadlined::Expired(v) => assert_eq!(v, "payload"),
540 _ => panic!("expected Expired"),
541 }
542 assert!(!d.tick(&ctx, 9)); assert!(d.is_expired(&ctx));
544 }
545}