1pub use bern_protocol::Cancellation;
22use bern_protocol::{CommandError, Value};
23use std::collections::BTreeMap;
24use std::sync::{Arc, Mutex};
25
26#[derive(Clone, Debug, PartialEq)]
29pub enum Resource<T> {
30 Idle,
32 Loading,
34 Ready(T),
35 Refreshing(T),
37 Failed {
38 error: CommandError,
39 previous: Option<T>,
41 },
42}
43
44impl<T> Resource<T> {
45 pub fn data(&self) -> Option<&T> {
47 match self {
48 Resource::Ready(data) | Resource::Refreshing(data) => Some(data),
49 Resource::Failed { previous, .. } => previous.as_ref(),
50 Resource::Idle | Resource::Loading => None,
51 }
52 }
53}
54
55#[derive(Clone, Debug)]
57pub struct Ticket {
58 pub version: u64,
59 pub cancellation: Cancellation,
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum Resolution {
65 Applied,
66 Discarded,
68}
69
70#[derive(Debug)]
72pub struct Query<P, T> {
73 params: Option<P>,
74 version: u64,
75 in_flight: Option<Cancellation>,
76 state: Resource<T>,
77 ended: bool,
78 discarded: u64,
79}
80
81impl<P, T> Default for Query<P, T> {
82 fn default() -> Self {
83 Self {
84 params: None,
85 version: 0,
86 in_flight: None,
87 state: Resource::Idle,
88 ended: false,
89 discarded: 0,
90 }
91 }
92}
93
94impl<P: Clone, T: Clone> Query<P, T> {
95 pub fn new() -> Self {
96 Self::default()
97 }
98
99 pub fn state(&self) -> &Resource<T> {
100 &self.state
101 }
102
103 pub fn params(&self) -> Option<&P> {
104 self.params.as_ref()
105 }
106
107 pub fn version(&self) -> u64 {
109 self.version
110 }
111
112 pub fn discarded(&self) -> u64 {
114 self.discarded
115 }
116
117 pub fn request(&mut self, params: P) -> Ticket {
120 if let Some(previous) = self.in_flight.take() {
121 previous.cancel();
122 }
123 self.version += 1;
124 self.params = Some(params);
125 let previous = self.state.data().cloned();
126 self.state = match previous {
127 Some(data) => Resource::Refreshing(data),
128 None => Resource::Loading,
129 };
130 let cancellation = Cancellation::new();
131 self.in_flight = Some(cancellation.clone());
132 Ticket {
133 version: self.version,
134 cancellation,
135 }
136 }
137
138 pub fn resolve(&mut self, ticket: &Ticket, result: Result<T, CommandError>) -> Resolution {
140 if self.ended || ticket.version != self.version || ticket.cancellation.is_cancelled() {
141 self.discarded += 1;
142 return Resolution::Discarded;
143 }
144 self.in_flight = None;
145 self.state = match result {
146 Ok(data) => Resource::Ready(data),
147 Err(error) => Resource::Failed {
148 error,
149 previous: self.state.data().cloned(),
150 },
151 };
152 Resolution::Applied
153 }
154
155 pub fn end(&mut self) {
158 if let Some(previous) = self.in_flight.take() {
159 previous.cancel();
160 }
161 self.ended = true;
162 }
163}
164
165#[derive(Debug)]
167pub struct QuerySubscription {
168 pub window: String,
170 pub query: usize,
172 reads: Vec<usize>,
174 resource: Query<Vec<Value>, Value>,
175}
176
177impl QuerySubscription {
178 pub fn state(&self) -> &Resource<Value> {
179 self.resource.state()
180 }
181
182 pub fn version(&self) -> u64 {
183 self.resource.version()
184 }
185
186 pub fn params(&self) -> Option<&Vec<Value>> {
187 self.resource.params()
188 }
189
190 pub fn discarded(&self) -> u64 {
191 self.resource.discarded()
192 }
193}
194
195#[derive(Debug, Default)]
197pub struct QuerySubscriptions {
198 next: u64,
199 entries: BTreeMap<u64, QuerySubscription>,
200}
201
202impl QuerySubscriptions {
203 pub fn new() -> Self {
204 Self::default()
205 }
206
207 pub fn open(&mut self, window: impl Into<String>, query: usize, reads: Vec<usize>) -> u64 {
210 self.next += 1;
211 self.entries.insert(
212 self.next,
213 QuerySubscription {
214 window: window.into(),
215 query,
216 reads,
217 resource: Query::new(),
218 },
219 );
220 self.next
221 }
222
223 pub fn get(&self, id: u64) -> Option<&QuerySubscription> {
224 self.entries.get(&id)
225 }
226
227 pub fn len(&self) -> usize {
228 self.entries.len()
229 }
230
231 pub fn is_empty(&self) -> bool {
232 self.entries.is_empty()
233 }
234
235 pub fn request(&mut self, id: u64, params: Vec<Value>) -> Option<Ticket> {
238 self.entries
239 .get_mut(&id)
240 .map(|entry| entry.resource.request(params))
241 }
242
243 pub fn resolve(
245 &mut self,
246 id: u64,
247 ticket: &Ticket,
248 result: Result<Value, CommandError>,
249 ) -> Resolution {
250 match self.entries.get_mut(&id) {
251 Some(entry) => entry.resource.resolve(ticket, result),
252 None => Resolution::Discarded,
253 }
254 }
255
256 pub fn close(&mut self, id: u64) -> bool {
258 match self.entries.remove(&id) {
259 Some(mut entry) => {
260 entry.resource.end();
261 true
262 }
263 None => false,
264 }
265 }
266
267 pub fn close_window(&mut self, window: &str) -> Vec<u64> {
269 let ids: Vec<u64> = self
270 .entries
271 .iter()
272 .filter(|(_, entry)| entry.window == window)
273 .map(|(id, _)| *id)
274 .collect();
275 for id in &ids {
276 self.close(*id);
277 }
278 ids
279 }
280
281 pub fn refresh(&mut self, changed: &[usize]) -> Vec<(u64, Ticket, Vec<Value>)> {
285 let mut requests = Vec::new();
286 for (id, entry) in &mut self.entries {
287 if !entry.reads.iter().any(|field| changed.contains(field)) {
288 continue;
289 }
290 let Some(params) = entry.resource.params().cloned() else {
291 continue;
292 };
293 let ticket = entry.resource.request(params.clone());
294 requests.push((*id, ticket, params));
295 }
296 requests
297 }
298}
299
300type Timer = Box<dyn FnOnce() + Send>;
301
302pub trait Clock: Send + Sync + 'static {
305 fn now_ms(&self) -> u64;
307 fn after_ms(&self, delay_ms: u64, task: Timer);
310}
311
312#[derive(Clone, Default)]
316pub struct ManualClock {
317 inner: Arc<Mutex<ClockState>>,
318}
319
320#[derive(Default)]
321struct ClockState {
322 now: u64,
323 sequence: u64,
324 timers: Vec<(u64, u64, Timer)>,
325}
326
327impl ManualClock {
328 pub fn new() -> Self {
329 Self::default()
330 }
331
332 pub fn now(&self) -> u64 {
334 self.inner.lock().expect("clock lock").now
335 }
336
337 pub fn after(&self, delay_ms: u64, task: impl FnOnce() + Send + 'static) {
339 let mut state = self.inner.lock().expect("clock lock");
340 let due = state.now + delay_ms;
341 state.sequence += 1;
342 let sequence = state.sequence;
343 state.timers.push((due, sequence, Box::new(task)));
344 }
345
346 pub fn pending(&self) -> usize {
348 self.inner.lock().expect("clock lock").timers.len()
349 }
350
351 pub fn advance(&self, delta_ms: u64) -> usize {
354 let target = self.inner.lock().expect("clock lock").now + delta_ms;
355 let mut fired = 0;
356 loop {
357 let next = {
358 let mut state = self.inner.lock().expect("clock lock");
359 let due = state
360 .timers
361 .iter()
362 .enumerate()
363 .filter(|(_, (due, _, _))| *due <= target)
364 .min_by_key(|(_, (due, sequence, _))| (*due, *sequence))
365 .map(|(index, _)| index);
366 due.map(|index| {
367 let (due, _, task) = state.timers.remove(index);
368 state.now = due;
369 task
370 })
371 };
372 match next {
373 Some(task) => {
374 task();
375 fired += 1;
376 }
377 None => break,
378 }
379 }
380 self.inner.lock().expect("clock lock").now = target;
381 fired
382 }
383}
384
385impl Clock for ManualClock {
386 fn now_ms(&self) -> u64 {
387 self.now()
388 }
389
390 fn after_ms(&self, delay_ms: u64, task: Timer) {
391 self.after(delay_ms, task);
392 }
393}
394
395#[cfg(not(target_arch = "wasm32"))]
396pub use thread_clock::SystemClock;
397
398#[cfg(not(target_arch = "wasm32"))]
399mod thread_clock {
400 use super::{Clock, Timer};
401 use std::panic::{AssertUnwindSafe, catch_unwind};
402 use std::sync::{Arc, Condvar, Mutex};
403 use std::time::{Duration, Instant};
404
405 #[derive(Clone)]
408 pub struct SystemClock {
409 inner: Arc<SystemTimers>,
410 }
411
412 struct SystemTimers {
413 start: Instant,
414 queue: Mutex<TimerQueue>,
415 wake: Condvar,
416 }
417
418 #[derive(Default)]
419 struct TimerQueue {
420 sequence: u64,
421 timers: Vec<(Instant, u64, Timer)>,
422 running: bool,
423 }
424
425 impl Default for SystemClock {
426 fn default() -> Self {
427 Self::new()
428 }
429 }
430
431 impl SystemClock {
432 pub fn new() -> Self {
433 Self {
434 inner: Arc::new(SystemTimers {
435 start: Instant::now(),
436 queue: Mutex::new(TimerQueue::default()),
437 wake: Condvar::new(),
438 }),
439 }
440 }
441
442 fn run(timers: Arc<SystemTimers>) {
443 let mut queue = timers.queue.lock().expect("timer lock");
444 loop {
445 let now = Instant::now();
446 let next = queue
447 .timers
448 .iter()
449 .enumerate()
450 .min_by_key(|(_, (due, sequence, _))| (*due, *sequence))
451 .map(|(index, (due, _, _))| (index, *due));
452 match next {
453 None => {
454 queue.running = false;
455 return;
456 }
457 Some((index, due)) if due <= now => {
458 let (_, _, task) = queue.timers.remove(index);
459 drop(queue);
460 let _ = catch_unwind(AssertUnwindSafe(task));
462 queue = timers.queue.lock().expect("timer lock");
463 }
464 Some((_, due)) => {
465 queue = timers
466 .wake
467 .wait_timeout(queue, due - now)
468 .expect("timer lock")
469 .0;
470 }
471 }
472 }
473 }
474 }
475
476 impl Clock for SystemClock {
477 fn now_ms(&self) -> u64 {
478 self.inner.start.elapsed().as_millis() as u64
479 }
480
481 fn after_ms(&self, delay_ms: u64, task: Timer) {
482 let mut queue = self.inner.queue.lock().expect("timer lock");
483 queue.sequence += 1;
484 let sequence = queue.sequence;
485 queue.timers.push((
486 Instant::now() + Duration::from_millis(delay_ms),
487 sequence,
488 task,
489 ));
490 if !queue.running {
491 queue.running = true;
492 let timers = Arc::clone(&self.inner);
493 if std::thread::Builder::new()
494 .name("bern-timers".into())
495 .spawn(move || SystemClock::run(timers))
496 .is_err()
497 {
498 queue.running = false;
499 }
500 }
501 self.inner.wake.notify_one();
502 }
503 }
504}
505
506#[cfg(target_arch = "wasm32")]
507pub use page_clock::SystemClock;
508
509#[cfg(target_arch = "wasm32")]
510mod page_clock {
511 use super::{Clock, Timer};
512 use std::collections::BTreeMap;
513 use std::sync::Mutex;
514
515 #[link(wasm_import_module = "bern")]
516 unsafe extern "C" {
517 fn now_ms() -> f64;
519 fn set_timer(id: u32, delay_ms: f64);
521 }
522
523 static TIMERS: Mutex<(u32, BTreeMap<u32, Timer>)> = Mutex::new((0, BTreeMap::new()));
524
525 #[derive(Clone)]
528 pub struct SystemClock {
529 start: f64,
530 }
531
532 impl Default for SystemClock {
533 fn default() -> Self {
534 Self::new()
535 }
536 }
537
538 impl SystemClock {
539 pub fn new() -> Self {
540 Self {
542 start: unsafe { now_ms() },
543 }
544 }
545 }
546
547 impl Clock for SystemClock {
548 fn now_ms(&self) -> u64 {
549 (unsafe { now_ms() } - self.start).max(0.0) as u64
551 }
552
553 fn after_ms(&self, delay_ms: u64, task: Timer) {
554 let id = {
555 let mut timers = TIMERS.lock().expect("timer lock");
556 timers.0 = timers.0.wrapping_add(1);
557 let id = timers.0;
558 timers.1.insert(id, task);
559 id
560 };
561 unsafe { set_timer(id, delay_ms as f64) };
563 }
564 }
565
566 #[unsafe(no_mangle)]
568 pub extern "C" fn bern_timer_fired(id: u32) {
569 let task = TIMERS.lock().expect("timer lock").1.remove(&id);
570 if let Some(task) = task {
571 task();
572 }
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use std::sync::mpsc::channel;
580 use std::time::Duration;
581
582 struct Search {
584 clock: ManualClock,
585 query: Arc<Mutex<Query<String, Vec<String>>>>,
586 items: Vec<&'static str>,
587 cancelled: Arc<Mutex<Vec<String>>>,
588 }
589
590 impl Search {
591 fn new(clock: &ManualClock) -> Self {
592 Self {
593 clock: clock.clone(),
594 query: Arc::new(Mutex::new(Query::new())),
595 items: vec!["Documents", "Downloads", "Movies", "Music", "Photos"],
596 cancelled: Arc::default(),
597 }
598 }
599
600 fn type_text(&self, text: &str, latency_ms: u64) {
602 let ticket = self.query.lock().unwrap().request(text.to_string());
603 let query = Arc::clone(&self.query);
604 let cancelled = Arc::clone(&self.cancelled);
605 let text = text.to_lowercase();
606 let results: Vec<String> = self
607 .items
608 .iter()
609 .filter(|item| item.to_lowercase().contains(&text))
610 .map(|item| item.to_string())
611 .collect();
612 self.clock.after(latency_ms, move || {
613 if ticket.cancellation.is_cancelled() {
614 cancelled.lock().unwrap().push(text);
615 }
616 query.lock().unwrap().resolve(&ticket, Ok(results));
619 });
620 }
621
622 fn state(&self) -> Resource<Vec<String>> {
623 self.query.lock().unwrap().state().clone()
624 }
625 }
626
627 #[test]
628 fn a_slow_older_search_never_overwrites_a_newer_one() {
629 let clock = ManualClock::new();
630 let search = Search::new(&clock);
631 search.type_text("m", 300);
633 search.type_text("mo", 100);
634 assert_eq!(search.state(), Resource::Loading);
635 assert_eq!(clock.advance(100), 1);
636 assert_eq!(search.state(), Resource::Ready(vec!["Movies".to_string()]));
637 assert_eq!(clock.advance(200), 1);
638 assert_eq!(search.state(), Resource::Ready(vec!["Movies".to_string()]));
640 assert_eq!(search.query.lock().unwrap().discarded(), 1);
641 assert_eq!(*search.cancelled.lock().unwrap(), vec!["m".to_string()]);
642 assert_eq!(clock.now(), 300);
643 }
644
645 #[test]
646 fn in_order_answers_still_show_only_the_current_search() {
647 let clock = ManualClock::new();
648 let search = Search::new(&clock);
649 search.type_text("d", 100);
650 clock.advance(100);
651 assert_eq!(
652 search.state(),
653 Resource::Ready(vec!["Documents".to_string(), "Downloads".to_string()])
654 );
655 search.type_text("do", 200);
657 search.type_text("dow", 300);
658 assert_eq!(
659 search.state(),
660 Resource::Refreshing(vec!["Documents".to_string(), "Downloads".to_string()])
661 );
662 clock.advance(200);
663 assert!(matches!(search.state(), Resource::Refreshing(_)));
664 clock.advance(100);
665 assert_eq!(
666 search.state(),
667 Resource::Ready(vec!["Downloads".to_string()])
668 );
669 search.type_text("zzz", 50);
671 clock.advance(50);
672 assert_eq!(search.state(), Resource::Ready(vec![]));
673 }
674
675 #[test]
676 fn failures_keep_previous_data_and_ended_subscriptions_drop_results() {
677 let mut query: Query<String, Vec<i64>> = Query::new();
678 let first = query.request("a".into());
679 assert_eq!(query.resolve(&first, Ok(vec![1])), Resolution::Applied);
680 let second = query.request("b".into());
681 query.resolve(&second, Err(CommandError::new("offline", "no network")));
682 assert_eq!(query.state().data(), Some(&vec![1]));
683 assert!(matches!(query.state(), Resource::Failed { .. }));
684 let third = query.request("c".into());
686 query.end();
687 assert!(third.cancellation.is_cancelled());
688 assert_eq!(query.resolve(&third, Ok(vec![3])), Resolution::Discarded);
689 }
690
691 #[test]
692 fn subscriptions_end_with_their_window_and_refresh_when_read_state_changes() {
693 let mut subscriptions = QuerySubscriptions::new();
694 let search = subscriptions.open("main#1", 4, vec![2]);
695 let other = subscriptions.open("settings#1", 5, vec![]);
696 assert!(subscriptions.refresh(&[2]).is_empty());
698 let first = subscriptions
699 .request(search, vec![Value::Text("m".into())])
700 .unwrap();
701 assert_eq!(
702 subscriptions.resolve(search, &first, Ok(Value::Int(1))),
703 Resolution::Applied
704 );
705 subscriptions.request(other, vec![]).unwrap();
706 let refreshed = subscriptions.refresh(&[0, 2]);
709 assert_eq!(refreshed.len(), 1);
710 let (id, ticket, params) = &refreshed[0];
711 assert_eq!((*id, ticket.version), (search, 2));
712 assert_eq!(params, &vec![Value::Text("m".into())]);
713 assert_eq!(
714 subscriptions.get(search).unwrap().state(),
715 &Resource::Refreshing(Value::Int(1))
716 );
717 assert_eq!(
719 subscriptions.resolve(search, &first, Ok(Value::Int(9))),
720 Resolution::Discarded
721 );
722 assert_eq!(subscriptions.close_window("main#1"), vec![search]);
724 assert!(ticket.cancellation.is_cancelled());
725 assert_eq!(
726 subscriptions.resolve(search, ticket, Ok(Value::Int(2))),
727 Resolution::Discarded
728 );
729 assert_eq!(subscriptions.len(), 1);
730 assert!(subscriptions.close(other));
731 assert!(subscriptions.is_empty());
732 }
733
734 #[test]
735 fn the_system_clock_fires_timers_in_due_order() {
736 let clock = SystemClock::new();
737 let (sender, receiver) = channel();
738 for (delay, name) in [(40, "late"), (5, "early"), (20, "middle")] {
739 let sender = sender.clone();
740 clock.after_ms(delay, Box::new(move || sender.send(name).unwrap()));
741 }
742 let order: Vec<_> = (0..3)
743 .map(|_| receiver.recv_timeout(Duration::from_secs(5)).unwrap())
744 .collect();
745 assert_eq!(order, vec!["early", "middle", "late"]);
746 assert!(clock.now_ms() >= 40);
747 clock.after_ms(1, Box::new(move || sender.send("again").unwrap()));
749 assert_eq!(
750 receiver.recv_timeout(Duration::from_secs(5)).unwrap(),
751 "again"
752 );
753 }
754}