1use std::any::{Any, TypeId};
99use std::cell::{Cell, OnceCell, RefCell};
100use std::collections::HashMap;
101use std::hash::{Hash, Hasher};
102use std::ops::Deref;
103use std::rc::Rc;
104
105use frus_core::{Rect, Scene};
106use frus_layout::Style;
107
108use crate::callback::Callback;
109use crate::interaction::{Status, WidgetId};
110use crate::runtime::Runtime;
111use crate::theme::Theme;
112use crate::widget::Widget;
113
114thread_local! {
119 static REBUILD: Cell<bool> = const { Cell::new(false) };
120 static DEPTH: Cell<u32> = const { Cell::new(0) };
121 static INTERVALS: RefCell<Vec<Interval>> = const { RefCell::new(Vec::new()) };
122 static BACK_BLOCKS: Cell<u32> = const { Cell::new(0) };
123}
124
125#[inline(never)]
132fn depth() -> u32 {
133 DEPTH.with(|d| d.get())
134}
135
136#[inline(never)]
138fn push_interval(interval: Interval) {
139 INTERVALS.with(|intervals| intervals.borrow_mut().push(interval));
140}
141
142#[inline(never)]
146pub fn back_blocked() -> bool {
147 BACK_BLOCKS.with(|blocks| blocks.get() > 0)
148}
149
150#[derive(Clone)]
152pub struct Interval {
153 pub id: u64,
156 pub period: std::time::Duration,
158 pub callback: Callback,
160}
161
162#[inline(never)]
165pub fn intervals() -> Vec<Interval> {
166 INTERVALS.with(|intervals| intervals.borrow().clone())
167}
168
169#[inline(never)]
173pub fn request_rebuild() {
174 REBUILD.with(|flag| flag.set(true));
175}
176
177#[inline(never)]
179pub fn rebuild_requested() -> bool {
180 REBUILD.with(|flag| flag.get())
181}
182
183#[inline(never)]
186pub fn take_rebuild_request() -> bool {
187 REBUILD.with(|flag| flag.replace(false))
188}
189
190struct Deeper(u32);
194
195impl Deeper {
196 #[inline(never)]
197 fn enter() -> Self {
198 let now = DEPTH.with(|d| d.get());
199 DEPTH.with(|d| d.set(now + 1));
200 Deeper(now)
201 }
202}
203
204impl Drop for Deeper {
205 #[inline(never)]
206 fn drop(&mut self) {
207 DEPTH.with(|d| d.set(self.0));
208 }
209}
210
211type StateKey = (WidgetId, u32);
218
219#[derive(Default)]
222pub struct StateStore {
223 entries: RefCell<HashMap<StateKey, Entry>>,
224 epoch: Cell<u64>,
226 building: Cell<bool>,
228 effects: RefCell<Vec<PendingEffect>>,
229 services: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
230 restoring: RefCell<HashMap<(StateKey, String), String>>,
232}
233
234struct Entry {
235 kind: TypeId,
236 epoch: u64,
237 state: Option<Rc<dyn Any>>,
238 hooks: Rc<Hooks>,
239 dispose: Option<Box<dyn FnOnce()>>,
240 save: Option<Saver>,
242}
243
244struct Saver {
246 name: &'static str,
247 save: Box<dyn Fn() -> Option<String>>,
248}
249
250impl Entry {
251 fn dispose_all(self) {
252 if let Some(dispose) = self.dispose {
253 dispose();
254 }
255 self.hooks.dispose();
256 }
257}
258
259type Cleanup = Box<dyn FnOnce()>;
261
262struct PendingEffect {
263 hooks: Rc<Hooks>,
264 index: usize,
265 run: Box<dyn FnOnce() -> Option<Cleanup>>,
266}
267
268impl StateStore {
269 #[inline(never)]
271 pub fn begin_build(&self) {
272 self.epoch.set(self.epoch.get() + 1);
273 self.building.set(true);
274 INTERVALS.with(|intervals| intervals.borrow_mut().clear());
277 BACK_BLOCKS.with(|blocks| blocks.set(0));
278 }
279
280 pub fn end_frame(&self) {
287 if self.building.replace(false) {
288 self.sweep();
289 }
290 self.flush_effects();
291 }
292
293 pub fn snapshot(&self) -> String {
300 let mut lines: Vec<String> = self
301 .entries
302 .borrow()
303 .iter()
304 .filter_map(|((id, depth), entry)| {
305 let saver = entry.save.as_ref()?;
306 let saved = (saver.save)()?;
307 Some(format!(
308 "{:x}\t{depth}\t{}\t{}",
309 id.as_u64(),
310 saver.name,
311 escape(&saved)
312 ))
313 })
314 .collect();
315 lines.sort();
316 lines.join("\n")
317 }
318
319 pub fn restore_from(&self, snapshot: &str) {
322 let mut restoring = self.restoring.borrow_mut();
323 for line in snapshot.lines() {
324 let mut fields = line.splitn(4, '\t');
325 let (Some(id), Some(depth), Some(name), Some(saved)) =
326 (fields.next(), fields.next(), fields.next(), fields.next())
327 else {
328 continue;
329 };
330 let (Ok(id), Ok(depth)) = (u64::from_str_radix(id, 16), depth.parse::<u32>()) else {
331 continue;
332 };
333 restoring.insert(
334 ((WidgetId::from_u64(id), depth), name.to_string()),
335 unescape(saved),
336 );
337 }
338 }
339
340 pub fn provide<T: 'static>(&self, value: Rc<T>) {
343 self.services.borrow_mut().insert(TypeId::of::<T>(), value);
344 }
345
346 pub fn service<T: 'static>(&self) -> Option<Rc<T>> {
348 self.services
349 .borrow()
350 .get(&TypeId::of::<T>())
351 .and_then(|s| s.clone().downcast::<T>().ok())
352 }
353
354 pub fn len(&self) -> usize {
356 self.entries.borrow().len()
357 }
358
359 pub fn is_empty(&self) -> bool {
361 self.entries.borrow().is_empty()
362 }
363
364 fn sweep(&self) {
365 let epoch = self.epoch.get();
366 let stale: Vec<Entry> = {
367 let mut entries = self.entries.borrow_mut();
368 let keys: Vec<StateKey> = entries
369 .iter()
370 .filter(|(_, entry)| entry.epoch < epoch)
371 .map(|(key, _)| *key)
372 .collect();
373 keys.into_iter()
374 .filter_map(|key| entries.remove(&key))
375 .collect()
376 };
377 for entry in stale {
378 entry.dispose_all();
379 }
380 }
381
382 fn flush_effects(&self) {
383 let pending: Vec<PendingEffect> = std::mem::take(&mut *self.effects.borrow_mut());
384 for effect in pending {
385 if effect.hooks.disposed.get() {
386 continue;
387 }
388 let previous = effect.hooks.take_cleanup(effect.index);
389 if let Some(cleanup) = previous {
390 cleanup();
391 }
392 let cleanup = (effect.run)();
393 effect.hooks.set_cleanup(effect.index, cleanup);
394 }
395 }
396
397 fn hooks_for(&self, key: StateKey, kind: TypeId) -> Rc<Hooks> {
400 let stale = {
401 let mut entries = self.entries.borrow_mut();
402 match entries.get_mut(&key) {
403 Some(entry) if entry.kind == kind => {
404 entry.epoch = self.epoch.get();
405 return entry.hooks.clone();
406 }
407 Some(_) => entries.remove(&key),
408 None => None,
409 }
410 };
411 if let Some(stale) = stale {
412 stale.dispose_all();
413 }
414 let hooks = Rc::new(Hooks::default());
415 self.entries.borrow_mut().insert(
416 key,
417 Entry {
418 kind,
419 epoch: self.epoch.get(),
420 state: None,
421 hooks: hooks.clone(),
422 dispose: None,
423 save: None,
424 },
425 );
426 hooks
427 }
428
429 fn mount<W: StatefulWidget>(&self, key: StateKey, widget: Rc<W>) -> Mounted<W> {
432 let kind = TypeId::of::<W>();
433 let found = {
434 let mut entries = self.entries.borrow_mut();
435 entries
436 .get_mut(&key)
437 .filter(|entry| entry.kind == kind)
438 .map(|entry| {
439 entry.epoch = self.epoch.get();
440 (entry.state.clone(), entry.hooks.clone())
441 })
442 };
443 if let Some((Some(state), hooks)) = found {
444 let cell = state
445 .downcast::<StateCell<W::State>>()
446 .unwrap_or_else(|_| unreachable!("an entry's kind says what its state is"));
447 let previous = std::mem::replace(&mut *cell.widget.borrow_mut(), widget);
448 return Mounted {
449 cell,
450 hooks,
451 previous: Some(previous),
452 };
453 }
454 let stale = self.entries.borrow_mut().remove(&key);
455 if let Some(stale) = stale {
456 stale.dispose_all();
457 }
458 let cell = Rc::new(StateCell {
459 state: RefCell::new(widget.create_state()),
460 widget: RefCell::new(widget),
461 mounted: Cell::new(true),
462 });
463 let name = std::any::type_name::<W::State>();
465 let saved = self.restoring.borrow_mut().remove(&(key, name.to_string()));
466 if let Some(saved) = saved {
467 cell.state.borrow_mut().restore(&saved);
468 }
469 let hooks = Rc::new(Hooks::default());
470 let leaving = cell.clone();
471 let saving = cell.clone();
472 self.entries.borrow_mut().insert(
473 key,
474 Entry {
475 kind,
476 epoch: self.epoch.get(),
477 state: Some(cell.clone() as Rc<dyn Any>),
478 hooks: hooks.clone(),
479 dispose: Some(Box::new(move || {
480 leaving.mounted.set(false);
481 if let Ok(mut state) = leaving.state.try_borrow_mut() {
482 state.dispose();
483 }
484 })),
485 save: Some(Saver {
486 name,
487 save: Box::new(move || saving.state.try_borrow().ok()?.save()),
488 }),
489 },
490 );
491 Mounted {
492 cell,
493 hooks,
494 previous: None,
495 }
496 }
497}
498
499struct Mounted<W: StatefulWidget> {
500 cell: Rc<StateCell<W::State>>,
501 hooks: Rc<Hooks>,
502 previous: Option<Rc<W>>,
503}
504
505fn escape(text: &str) -> String {
507 text.replace('\\', "\\\\")
508 .replace('\t', "\\t")
509 .replace('\n', "\\n")
510 .replace('\r', "\\r")
511}
512
513fn unescape(text: &str) -> String {
514 let mut out = String::with_capacity(text.len());
515 let mut chars = text.chars();
516 while let Some(c) = chars.next() {
517 if c != '\\' {
518 out.push(c);
519 continue;
520 }
521 match chars.next() {
522 Some('t') => out.push('\t'),
523 Some('n') => out.push('\n'),
524 Some('r') => out.push('\r'),
525 Some(other) => out.push(other),
526 None => out.push('\\'),
527 }
528 }
529 out
530}
531
532const ORDER: &str = "hooks were called in a different order than on the last build: call every \
537 hook unconditionally, in the same order, never inside an `if`, a loop or a \
538 closure";
539
540#[derive(Default)]
541struct Hooks {
542 slots: RefCell<Vec<Slot>>,
543 disposed: Cell<bool>,
544}
545
546enum Slot {
547 Value(Rc<dyn Any>),
548 Effect {
549 deps: Box<dyn Any>,
550 cleanup: Option<Cleanup>,
551 },
552}
553
554impl Hooks {
555 fn value<T: 'static>(&self, index: usize, init: impl FnOnce() -> T) -> Rc<T> {
557 let existing = self.slots.borrow().get(index).map(|slot| match slot {
558 Slot::Value(value) => Some(value.clone()),
559 Slot::Effect { .. } => None,
560 });
561 match existing {
562 Some(Some(value)) => value.downcast::<T>().unwrap_or_else(|_| panic!("{ORDER}")),
563 Some(None) => panic!("{ORDER}"),
564 None => {
565 let value = Rc::new(init());
566 let mut slots = self.slots.borrow_mut();
567 assert_eq!(slots.len(), index, "{ORDER}");
568 slots.push(Slot::Value(value.clone() as Rc<dyn Any>));
569 value
570 }
571 }
572 }
573
574 fn take_cleanup(&self, index: usize) -> Option<Cleanup> {
575 match self.slots.borrow_mut().get_mut(index) {
576 Some(Slot::Effect { cleanup, .. }) => cleanup.take(),
577 _ => None,
578 }
579 }
580
581 fn set_cleanup(&self, index: usize, cleanup: Option<Cleanup>) {
582 if let Some(Slot::Effect { cleanup: slot, .. }) = self.slots.borrow_mut().get_mut(index) {
583 *slot = cleanup;
584 }
585 }
586
587 fn dispose(&self) {
588 self.disposed.set(true);
589 let cleanups: Vec<Cleanup> = self
590 .slots
591 .borrow_mut()
592 .iter_mut()
593 .filter_map(|slot| match slot {
594 Slot::Effect { cleanup, .. } => cleanup.take(),
595 Slot::Value(_) => None,
596 })
597 .collect();
598 for cleanup in cleanups {
599 cleanup();
600 }
601 }
602}
603
604pub trait IntoCleanup {
606 fn into_cleanup(self) -> Option<Box<dyn FnOnce()>>;
608}
609
610impl IntoCleanup for () {
611 fn into_cleanup(self) -> Option<Cleanup> {
612 None
613 }
614}
615
616impl<F: FnOnce() + 'static> IntoCleanup for F {
617 fn into_cleanup(self) -> Option<Cleanup> {
618 Some(Box::new(self))
619 }
620}
621
622pub struct UseState<T> {
628 cell: Rc<RefCell<T>>,
629}
630
631impl<T> Clone for UseState<T> {
632 fn clone(&self) -> Self {
633 Self {
634 cell: self.cell.clone(),
635 }
636 }
637}
638
639impl<T: 'static> UseState<T> {
640 pub fn get(&self) -> T
642 where
643 T: Clone,
644 {
645 self.cell.borrow().clone()
646 }
647
648 pub fn with<R>(&self, read: impl FnOnce(&T) -> R) -> R {
650 read(&self.cell.borrow())
651 }
652
653 pub fn set(&self, value: T) {
655 *self.cell.borrow_mut() = value;
656 request_rebuild();
657 }
658
659 pub fn update(&self, change: impl FnOnce(&mut T)) {
661 change(&mut self.cell.borrow_mut());
662 request_rebuild();
663 }
664}
665
666impl<T: std::fmt::Debug> std::fmt::Debug for UseState<T> {
667 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
668 f.debug_tuple("UseState")
669 .field(&*self.cell.borrow())
670 .finish()
671 }
672}
673
674pub struct UseRef<T> {
677 cell: Rc<RefCell<T>>,
678}
679
680impl<T> Clone for UseRef<T> {
681 fn clone(&self) -> Self {
682 Self {
683 cell: self.cell.clone(),
684 }
685 }
686}
687
688impl<T: 'static> UseRef<T> {
689 pub fn get(&self) -> T
691 where
692 T: Clone,
693 {
694 self.cell.borrow().clone()
695 }
696
697 pub fn with<R>(&self, read: impl FnOnce(&T) -> R) -> R {
699 read(&self.cell.borrow())
700 }
701
702 pub fn set(&self, value: T) {
704 *self.cell.borrow_mut() = value;
705 }
706
707 pub fn update(&self, change: impl FnOnce(&mut T)) {
709 change(&mut self.cell.borrow_mut());
710 }
711}
712
713pub struct BuildContext<'a> {
720 key: StateKey,
721 kind: TypeId,
722 runtime: &'a Runtime,
723 theme: &'a Theme,
724 hooks: OnceCell<Rc<Hooks>>,
725 cursor: Cell<usize>,
726 intervals: Cell<usize>,
727}
728
729impl<'a> BuildContext<'a> {
730 fn new(key: StateKey, kind: TypeId, runtime: &'a Runtime, theme: &'a Theme) -> Self {
731 Self {
732 key,
733 kind,
734 runtime,
735 theme,
736 hooks: OnceCell::new(),
737 cursor: Cell::new(0),
738 intervals: Cell::new(0),
739 }
740 }
741
742 #[cfg(test)]
744 pub(crate) fn for_test(runtime: &'a Runtime, theme: &'a Theme) -> Self {
745 Self::new((WidgetId::ROOT, 0), TypeId::of::<()>(), runtime, theme)
746 }
747
748 pub fn theme(&self) -> &Theme {
750 self.theme
751 }
752
753 pub fn id(&self) -> WidgetId {
755 self.key.0
756 }
757
758 pub fn runtime(&self) -> &Runtime {
760 self.runtime
761 }
762
763 pub fn service<T: 'static>(&self) -> Option<Rc<T>> {
765 self.runtime.states.service::<T>()
766 }
767
768 fn hooks(&self) -> Rc<Hooks> {
769 self.hooks
770 .get_or_init(|| self.runtime.states.hooks_for(self.key, self.kind))
771 .clone()
772 }
773
774 fn next_slot(&self) -> usize {
775 let slot = self.cursor.get();
776 self.cursor.set(slot + 1);
777 slot
778 }
779
780 pub fn use_state<T: 'static>(&self, init: impl FnOnce() -> T) -> UseState<T> {
792 let cell = self
793 .hooks()
794 .value(self.next_slot(), || RefCell::new(init()));
795 UseState { cell }
796 }
797
798 pub fn use_ref<T: 'static>(&self, init: impl FnOnce() -> T) -> UseRef<T> {
801 let cell = self
802 .hooks()
803 .value(self.next_slot(), || RefCell::new(init()));
804 UseRef { cell }
805 }
806
807 pub fn use_memo<D: PartialEq + 'static, T: 'static>(
810 &self,
811 deps: D,
812 compute: impl FnOnce() -> T,
813 ) -> Rc<T> {
814 let memo = self
815 .hooks()
816 .value(self.next_slot(), || RefCell::new(None::<(D, Rc<T>)>));
817 let mut memo = memo.borrow_mut();
818 match memo.as_ref() {
819 Some((previous, value)) if *previous == deps => value.clone(),
820 _ => {
821 let value = Rc::new(compute());
822 *memo = Some((deps, value.clone()));
823 value
824 }
825 }
826 }
827
828 pub fn use_interval(&self, period: std::time::Duration, on_tick: impl Into<Callback>) {
849 let index = self.intervals.get();
850 self.intervals.set(index + 1);
851 let id = {
852 let mut hasher = std::collections::hash_map::DefaultHasher::new();
853 (self.key.0.as_u64(), self.key.1, index, period.as_millis()).hash(&mut hasher);
854 hasher.finish()
855 };
856 push_interval(Interval {
857 id,
858 period,
859 callback: on_tick.into(),
860 });
861 }
862
863 #[inline(never)]
870 pub fn block_back(&self, blocked: bool) {
871 if blocked {
872 BACK_BLOCKS.with(|blocks| blocks.set(blocks.get() + 1));
873 }
874 }
875
876 pub fn use_effect<D, C>(&self, deps: D, effect: impl FnOnce() -> C + 'static)
893 where
894 D: PartialEq + 'static,
895 C: IntoCleanup,
896 {
897 let hooks = self.hooks();
898 let index = self.next_slot();
899 let changed = {
900 let mut slots = hooks.slots.borrow_mut();
901 match slots.get_mut(index) {
902 None => {
903 assert_eq!(slots.len(), index, "{ORDER}");
904 slots.push(Slot::Effect {
905 deps: Box::new(deps),
906 cleanup: None,
907 });
908 true
909 }
910 Some(Slot::Effect { deps: old, .. }) => {
911 if old.downcast_ref::<D>().is_some_and(|old| *old == deps) {
912 false
913 } else {
914 *old = Box::new(deps);
915 true
916 }
917 }
918 Some(Slot::Value(_)) => panic!("{ORDER}"),
919 }
920 };
921 if changed {
922 self.runtime
923 .states
924 .effects
925 .borrow_mut()
926 .push(PendingEffect {
927 hooks,
928 index,
929 run: Box::new(move || effect().into_cleanup()),
930 });
931 }
932 }
933}
934
935pub trait StatelessWidget: 'static {
945 fn build(&self, cx: &BuildContext) -> Box<dyn Widget>;
947
948 fn into_widget(self) -> Component
951 where
952 Self: Sized,
953 {
954 Component::stateless(self)
955 }
956}
957
958impl<F> StatelessWidget for F
959where
960 F: Fn(&BuildContext) -> Box<dyn Widget> + 'static,
961{
962 fn build(&self, cx: &BuildContext) -> Box<dyn Widget> {
963 self(cx)
964 }
965}
966
967pub trait StatefulWidget: 'static {
974 type State: State<Widget = Self>;
976
977 fn create_state(&self) -> Self::State;
979
980 fn into_widget(self) -> Component
982 where
983 Self: Sized,
984 {
985 Component::stateful(self)
986 }
987}
988
989pub trait State: Sized + 'static {
991 type Widget: StatefulWidget<State = Self>;
993
994 fn init_state(&mut self, _cx: &StateContext<Self>) {}
998
999 fn did_update_widget(&mut self, _old: &Self::Widget, _cx: &StateContext<Self>) {}
1002
1003 fn dispose(&mut self) {}
1005
1006 fn save(&self) -> Option<String> {
1023 None
1024 }
1025
1026 fn restore(&mut self, _saved: &str) {}
1031
1032 fn build(&self, cx: &StateContext<Self>) -> Box<dyn Widget>;
1039}
1040
1041pub(crate) struct StateCell<S: State> {
1042 state: RefCell<S>,
1043 widget: RefCell<Rc<S::Widget>>,
1044 mounted: Cell<bool>,
1045}
1046
1047pub struct StateHandle<S: State> {
1050 cell: Rc<StateCell<S>>,
1051}
1052
1053impl<S: State> Clone for StateHandle<S> {
1054 fn clone(&self) -> Self {
1055 Self {
1056 cell: self.cell.clone(),
1057 }
1058 }
1059}
1060
1061impl<S: State> StateHandle<S> {
1062 pub fn set_state(&self, change: impl FnOnce(&mut S)) {
1067 if !self.cell.mounted.get() {
1068 return;
1069 }
1070 match self.cell.state.try_borrow_mut() {
1071 Ok(mut state) => change(&mut state),
1072 Err(_) => panic!(
1073 "set_state was called while the state was being read — from inside `build`, or \
1074 from inside another `set_state`. Change state from a handler, not while building"
1075 ),
1076 }
1077 request_rebuild();
1078 }
1079
1080 pub fn read<R>(&self, read: impl FnOnce(&S) -> R) -> R {
1082 read(&self.cell.state.borrow())
1083 }
1084
1085 pub fn widget(&self) -> Rc<S::Widget> {
1087 self.cell.widget.borrow().clone()
1088 }
1089
1090 pub fn is_mounted(&self) -> bool {
1092 self.cell.mounted.get()
1093 }
1094
1095 pub fn callback(&self, change: impl Fn(&mut S) + 'static) -> Callback {
1097 let handle = self.clone();
1098 Callback::new(move || handle.set_state(|state| change(state)))
1099 }
1100
1101 pub fn handler<T: Clone + 'static>(
1108 &self,
1109 change: impl Fn(&mut S, T) + 'static,
1110 ) -> impl Fn(T) -> Callback + 'static {
1111 let handle = self.clone();
1112 let change = Rc::new(change);
1113 move |value| {
1114 let (handle, change) = (handle.clone(), change.clone());
1115 Callback::new(move || handle.set_state(|state| change(state, value.clone())))
1116 }
1117 }
1118}
1119
1120pub struct StateContext<'c, 'a, S: State> {
1123 cx: &'c BuildContext<'a>,
1124 cell: &'c Rc<StateCell<S>>,
1125}
1126
1127impl<'a, S: State> Deref for StateContext<'_, 'a, S> {
1128 type Target = BuildContext<'a>;
1129
1130 fn deref(&self) -> &BuildContext<'a> {
1131 self.cx
1132 }
1133}
1134
1135impl<S: State> StateContext<'_, '_, S> {
1136 pub fn widget(&self) -> Rc<S::Widget> {
1138 self.cell.widget.borrow().clone()
1139 }
1140
1141 pub fn handle(&self) -> StateHandle<S> {
1143 StateHandle {
1144 cell: self.cell.clone(),
1145 }
1146 }
1147
1148 pub fn callback(&self, change: impl Fn(&mut S) + 'static) -> Callback {
1150 self.handle().callback(change)
1151 }
1152
1153 pub fn handler<T: Clone + 'static>(
1156 &self,
1157 change: impl Fn(&mut S, T) + 'static,
1158 ) -> impl Fn(T) -> Callback + 'static {
1159 self.handle().handler(change)
1160 }
1161}
1162
1163type Builder<Msg> = Box<dyn FnOnce(&BuildContext) -> Box<dyn Widget<Msg>>>;
1168
1169pub struct Component<Msg = Callback> {
1176 inner: LazyChild<Msg>,
1177 key: Option<u64>,
1178}
1179
1180#[inline]
1184fn build_stateless<W: StatelessWidget>(widget: &W, cx: &BuildContext) -> Box<dyn Widget> {
1185 #[cfg(feature = "hot-reload")]
1186 {
1187 subsecond::HotFn::current(<W as StatelessWidget>::build).call((widget, cx))
1188 }
1189 #[cfg(not(feature = "hot-reload"))]
1190 {
1191 widget.build(cx)
1192 }
1193}
1194
1195#[inline]
1197fn build_state<S: State>(state: &S, cx: &StateContext<S>) -> Box<dyn Widget> {
1198 #[cfg(feature = "hot-reload")]
1199 {
1200 subsecond::HotFn::current(<S as State>::build).call((state, cx))
1201 }
1202 #[cfg(not(feature = "hot-reload"))]
1203 {
1204 state.build(cx)
1205 }
1206}
1207
1208impl Component<Callback> {
1209 pub fn stateless<W: StatelessWidget>(widget: W) -> Self {
1211 Self::from_builder(
1212 TypeId::of::<W>(),
1213 Box::new(move |cx: &BuildContext| build_stateless(&widget, cx)),
1214 )
1215 }
1216
1217 pub fn stateful<W: StatefulWidget>(widget: W) -> Self {
1219 Self::from_builder(
1220 TypeId::of::<W>(),
1221 Box::new(move |cx: &BuildContext| {
1222 let Mounted {
1223 cell,
1224 hooks,
1225 previous,
1226 } = cx.runtime.states.mount(cx.key, Rc::new(widget));
1227 let _ = cx.hooks.set(hooks);
1228 let state_cx = StateContext { cx, cell: &cell };
1229 match previous {
1230 None => cell.state.borrow_mut().init_state(&state_cx),
1231 Some(old) => cell.state.borrow_mut().did_update_widget(&old, &state_cx),
1232 }
1233 let built = build_state(&*cell.state.borrow(), &state_cx);
1234 built
1235 }),
1236 )
1237 }
1238}
1239
1240impl<Msg> Component<Msg> {
1241 fn from_builder(kind: TypeId, build: Builder<Msg>) -> Self {
1242 Self {
1243 inner: LazyChild {
1244 kind,
1245 build: RefCell::new(Some(build)),
1246 built: OnceCell::new(),
1247 unbuilt: Unbuilt,
1248 },
1249 key: None,
1250 }
1251 }
1252
1253 pub fn with_key(mut self, key: impl Hash) -> Self {
1256 let mut hasher = std::collections::hash_map::DefaultHasher::new();
1257 key.hash(&mut hasher);
1258 self.key = Some(hasher.finish());
1259 self
1260 }
1261
1262 fn restyle(&self, base: Style) -> Style {
1264 base
1265 }
1266}
1267
1268struct LazyChild<Msg> {
1270 kind: TypeId,
1271 build: RefCell<Option<Builder<Msg>>>,
1272 built: OnceCell<Box<dyn Widget<Msg>>>,
1273 unbuilt: Unbuilt,
1274}
1275
1276impl<Msg> LazyChild<Msg> {
1277 fn expand(&self, id: WidgetId, runtime: &Runtime, theme: &Theme) {
1280 if self.built.get().is_none() {
1281 let build = self.build.borrow_mut().take();
1282 if let Some(build) = build {
1283 let cx = BuildContext::new((id, depth()), self.kind, runtime, theme);
1284 let child = build(&cx);
1285 let _ = self.built.set(child);
1286 }
1287 }
1288 if let Some(child) = self.built.get() {
1289 let _deeper = Deeper::enter();
1290 child.expand(id, runtime, theme);
1291 }
1292 }
1293
1294 fn build_in(&self, id: WidgetId, runtime: &Runtime, theme: &Theme) {
1295 if let Some(child) = self.built.get() {
1296 child.build_in(id, runtime, theme);
1297 }
1298 }
1299
1300 fn build_themed(&self, theme: &Theme) {
1301 if let Some(child) = self.built.get() {
1302 child.build_themed(theme);
1303 }
1304 }
1305
1306 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
1307 match self.built.get() {
1308 Some(child) => child.children(),
1309 None => {
1310 #[cfg(debug_assertions)]
1311 if self.build.borrow().is_some() {
1312 panic!(
1313 "a component's children were read before it was built: a traversal \
1314 reached this tree before the walk that builds components did. Call \
1315 `build_deferred(tree, &theme, &runtime)` on it first"
1316 );
1317 }
1318 &[]
1319 }
1320 }
1321 }
1322}
1323
1324impl<Msg> Deref for LazyChild<Msg> {
1325 type Target = dyn Widget<Msg>;
1326
1327 fn deref(&self) -> &(dyn Widget<Msg> + 'static) {
1328 match self.built.get() {
1329 Some(child) => child.as_ref(),
1330 None => &self.unbuilt,
1331 }
1332 }
1333}
1334
1335struct Unbuilt;
1337
1338impl<Msg> Widget<Msg> for Unbuilt {
1339 fn style(&self) -> Style {
1340 Style::default()
1341 }
1342
1343 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
1344 &[]
1345 }
1346
1347 fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
1348
1349 fn on_click(&self) -> Option<Msg> {
1350 None
1351 }
1352
1353 fn debug_name(&self) -> &'static str {
1354 "Component"
1355 }
1356}
1357
1358crate::transparent::forward_transparent!(Component {
1359 fn key(&self) -> Option<u64> {
1363 self.key
1364 }
1365
1366 fn positioned(&self) -> Option<crate::positioned::Positioning> {
1367 self.inner.positioned()
1368 }
1369
1370 fn theme_override(
1371 &self,
1372 inherited: &crate::theme::Theme,
1373 ) -> Option<Box<crate::theme::Theme>> {
1374 self.inner.theme_override(inherited)
1375 }
1376
1377 fn media_override(&self, inherited: crate::MediaQuery) -> Option<crate::MediaQuery> {
1378 self.inner.media_override(inherited)
1379 }
1380
1381 fn scaffold_override(&self) -> Option<crate::ScaffoldInfo> {
1382 self.inner.scaffold_override()
1383 }
1384 fn scroll_behavior_override(&self) -> Option<crate::ScrollBehavior> {
1385 self.inner.scroll_behavior_override()
1386 }
1387
1388 fn autofill_group(&self) -> bool {
1389 self.inner.autofill_group()
1390 }
1391 fn area_toolbar(
1393 &self,
1394 context: crate::ToolbarContext,
1395 ) -> Option<Option<&dyn crate::widget::Widget<Msg>>> {
1396 self.inner.area_toolbar(context)
1397 }
1398});
1399
1400pub fn component(build: impl Fn(&BuildContext) -> Box<dyn Widget> + 'static) -> Component {
1413 Component::stateless(build)
1414}
1415
1416#[cfg(test)]
1417mod tests {
1418 use super::*;
1419 use crate::{build_deferred, keyed, Container, Flex};
1420 use frus_layout::Dimension;
1421
1422 type Builder = Rc<dyn Fn(&BuildContext) -> Box<dyn Widget>>;
1423 type Log = Rc<RefCell<Vec<String>>>;
1424 type Handle = Rc<RefCell<Option<StateHandle<LifeState>>>>;
1425
1426 fn log() -> Log {
1427 Rc::new(RefCell::new(Vec::new()))
1428 }
1429
1430 fn taken(log: &Log) -> Vec<String> {
1431 std::mem::take(&mut *log.borrow_mut())
1432 }
1433
1434 fn frame(runtime: &Runtime, root: &dyn Widget) {
1437 runtime.states.begin_build();
1438 build_deferred(root, &Theme::default(), runtime);
1439 runtime.states.end_frame();
1440 }
1441
1442 struct Life {
1444 tag: u32,
1445 log: Log,
1446 out: Handle,
1447 }
1448
1449 struct LifeState {
1450 log: Log,
1451 out: Handle,
1452 count: i32,
1453 }
1454
1455 impl StatefulWidget for Life {
1456 type State = LifeState;
1457 fn create_state(&self) -> LifeState {
1458 LifeState {
1459 log: self.log.clone(),
1460 out: self.out.clone(),
1461 count: 0,
1462 }
1463 }
1464 }
1465
1466 impl State for LifeState {
1467 type Widget = Life;
1468 fn init_state(&mut self, cx: &StateContext<Self>) {
1469 self.log
1470 .borrow_mut()
1471 .push(format!("init {}", cx.widget().tag));
1472 *self.out.borrow_mut() = Some(cx.handle());
1473 }
1474 fn did_update_widget(&mut self, old: &Life, cx: &StateContext<Self>) {
1475 self.log
1476 .borrow_mut()
1477 .push(format!("update {}->{}", old.tag, cx.widget().tag));
1478 }
1479 fn dispose(&mut self) {
1480 self.log.borrow_mut().push("dispose".to_string());
1481 }
1482 fn build(&self, _cx: &StateContext<Self>) -> Box<dyn Widget> {
1483 Box::new(Container::new().width(10.0 + self.count as f32).height(4.0))
1484 }
1485 }
1486
1487 fn life(tag: u32, log: &Log, out: &Handle) -> Component {
1488 Life {
1489 tag,
1490 log: log.clone(),
1491 out: out.clone(),
1492 }
1493 .into_widget()
1494 }
1495
1496 fn handle() -> Handle {
1497 Rc::new(RefCell::new(None))
1498 }
1499
1500 fn width(widget: &dyn Widget) -> Dimension {
1501 widget.style().width
1502 }
1503
1504 struct Kept;
1506
1507 struct KeptState {
1508 count: i32,
1509 seen_at_init: Option<i32>,
1510 }
1511
1512 impl StatefulWidget for Kept {
1513 type State = KeptState;
1514 fn create_state(&self) -> KeptState {
1515 KeptState {
1516 count: 0,
1517 seen_at_init: None,
1518 }
1519 }
1520 }
1521
1522 impl State for KeptState {
1523 type Widget = Kept;
1524 fn init_state(&mut self, _cx: &StateContext<Self>) {
1525 self.seen_at_init = Some(self.count);
1526 }
1527 fn save(&self) -> Option<String> {
1528 Some(format!("{}\tline two\n\\", self.count))
1529 }
1530 fn restore(&mut self, saved: &str) {
1531 assert!(saved.ends_with("\tline two\n\\"), "{saved:?}");
1532 self.count = saved.split('\t').next().unwrap().parse().unwrap();
1533 }
1534 fn build(&self, _cx: &StateContext<Self>) -> Box<dyn Widget> {
1535 Box::new(Container::new().width(10.0 + self.count as f32).height(4.0))
1536 }
1537 }
1538
1539 #[test]
1542 fn kept_states_cross_a_restart() {
1543 let before = Runtime::default();
1544 let (log, out) = (log(), handle());
1545 let tree = || -> Box<dyn Widget> {
1546 Box::new(crate::column![Kept.into_widget(), life(1, &log, &out)])
1547 };
1548 let root = tree();
1549 frame(&before, root.as_ref());
1550 let kept = before
1551 .states
1552 .entries
1553 .borrow()
1554 .values()
1555 .find_map(|entry| entry.state.clone()?.downcast::<StateCell<KeptState>>().ok())
1556 .unwrap();
1557 kept.state.borrow_mut().count = 7;
1558 let snapshot = before.states.snapshot();
1559 assert_eq!(
1560 snapshot.lines().count(),
1561 1,
1562 "only the state that saves: {snapshot}"
1563 );
1564
1565 let after = Runtime::default();
1566 after.states.restore_from(&snapshot);
1567 let root = tree();
1568 frame(&after, root.as_ref());
1569 let restored = after
1570 .states
1571 .entries
1572 .borrow()
1573 .values()
1574 .find_map(|entry| entry.state.clone()?.downcast::<StateCell<KeptState>>().ok())
1575 .unwrap();
1576 let state = restored.state.borrow();
1577 assert_eq!(state.count, 7);
1578 assert_eq!(state.seen_at_init, Some(7), "restored before init_state");
1579 assert!(after.states.restoring.borrow().is_empty(), "taken once");
1580 }
1581
1582 #[test]
1583 fn saved_text_survives_escaping() {
1584 for text in [
1585 "plain",
1586 "tab\there",
1587 "two\nlines",
1588 "back\\slash",
1589 "\\n literally",
1590 "",
1591 ] {
1592 assert_eq!(unescape(&escape(text)), text);
1593 assert!(!escape(text).contains(['\n', '\t']));
1594 }
1595 }
1596
1597 #[test]
1598 fn a_stateless_component_is_the_widget_it_builds() {
1599 let runtime = Runtime::default();
1600 let root = component(|_| Box::new(Container::new().width(120.0).height(30.0)));
1601 frame(&runtime, &root);
1602 assert_eq!(width(&root), Dimension::Length(120.0));
1603 assert_eq!(root.style().height, Dimension::Length(30.0));
1604 }
1605
1606 #[test]
1607 fn a_state_is_made_once_and_survives_rebuilds() {
1608 let (runtime, log, out) = (Runtime::default(), log(), handle());
1609 let first = life(1, &log, &out);
1610 frame(&runtime, &first);
1611 assert_eq!(taken(&log), ["init 1"]);
1612 assert_eq!(width(&first), Dimension::Length(10.0));
1613
1614 out.borrow().as_ref().unwrap().set_state(|s| s.count = 5);
1616 assert!(take_rebuild_request(), "set_state asks for a rebuild");
1617
1618 let second = life(2, &log, &out);
1619 frame(&runtime, &second);
1620 assert_eq!(
1621 taken(&log),
1622 ["update 1->2"],
1623 "the same place, the same kind: the state is kept and told what changed"
1624 );
1625 assert_eq!(
1626 width(&second),
1627 Dimension::Length(15.0),
1628 "and it is the kept one"
1629 );
1630 }
1631
1632 #[test]
1633 fn a_handler_carries_the_value_a_widget_reports_into_the_state() {
1634 let (runtime, log, out) = (Runtime::default(), log(), handle());
1635 frame(&runtime, &life(1, &log, &out));
1636 let handle = out.borrow().clone().unwrap();
1637 let on_value = handle.handler(|s, by: i32| s.count += by);
1638 let (three, four) = (on_value(3), on_value(4));
1639 assert_eq!(
1640 handle.read(|s| s.count),
1641 0,
1642 "nothing changes until it is delivered"
1643 );
1644 three.call();
1645 four.call();
1646 four.call();
1647 assert_eq!(
1648 handle.read(|s| s.count),
1649 11,
1650 "each delivery makes the change"
1651 );
1652 assert!(take_rebuild_request(), "and it asks for the rebuild");
1653 }
1654
1655 #[test]
1656 fn a_component_that_blocks_back_does_so_for_one_build_only() {
1657 let runtime = Runtime::default();
1658 let open = Rc::new(Cell::new(true));
1659 let asked = open.clone();
1660 let root = component(move |cx| {
1661 cx.block_back(asked.get());
1662 Box::new(Container::new().width(4.0).height(4.0))
1663 });
1664 frame(&runtime, &root);
1665 assert!(back_blocked(), "an open menu is in the way");
1666 open.set(false);
1667 frame(&runtime, &root);
1668 assert!(!back_blocked(), "each build says it afresh");
1669 }
1670
1671 #[test]
1672 fn a_state_is_disposed_when_a_rebuild_no_longer_reaches_it() {
1673 let (runtime, log, out) = (Runtime::default(), log(), handle());
1674 frame(&runtime, &Flex::column().child(life(1, &log, &out)));
1675 taken(&log);
1676 assert_eq!(runtime.states.len(), 1);
1677
1678 frame(&runtime, &Flex::column().child(Container::new()));
1679 assert_eq!(taken(&log), ["dispose"]);
1680 assert!(runtime.states.is_empty());
1681 let handle = out.borrow().clone().unwrap();
1682 assert!(!handle.is_mounted());
1683 take_rebuild_request();
1684 handle.set_state(|s| s.count = 9);
1685 assert!(
1686 !take_rebuild_request(),
1687 "a state that has left cannot ask for a rebuild"
1688 );
1689 }
1690
1691 #[test]
1692 fn a_different_kind_of_widget_in_the_same_place_starts_over() {
1693 let (runtime, log, out) = (Runtime::default(), log(), handle());
1694 frame(&runtime, &Flex::column().child(life(1, &log, &out)));
1695 taken(&log);
1696 let other = component(|cx| {
1698 cx.use_state(|| 0u8);
1699 Box::new(Container::new())
1700 });
1701 frame(&runtime, &Flex::column().child(other));
1702 assert_eq!(
1703 taken(&log),
1704 ["dispose"],
1705 "the old kind is disposed, not reused"
1706 );
1707 }
1708
1709 #[test]
1710 fn a_key_carries_the_state_wherever_the_siblings_go() {
1711 let (runtime, log) = (Runtime::default(), log());
1712 let (a, b) = (handle(), handle());
1713 let column = |order: [u32; 2]| {
1714 let mut column = Flex::column();
1715 for tag in order {
1716 let out = if tag == 1 { &a } else { &b };
1717 column = column.child(keyed(tag, life(tag, &log, out)));
1718 }
1719 column
1720 };
1721 frame(&runtime, &column([1, 2]));
1722 a.borrow().as_ref().unwrap().set_state(|s| s.count = 1);
1723 b.borrow().as_ref().unwrap().set_state(|s| s.count = 2);
1724 taken(&log);
1725
1726 frame(&runtime, &column([2, 1]));
1727 let counts = |h: &Handle| h.borrow().as_ref().unwrap().read(|s| s.count);
1728 assert_eq!(
1729 (counts(&a), counts(&b)),
1730 (1, 2),
1731 "each state stayed with its key"
1732 );
1733 assert_eq!(runtime.states.len(), 2);
1734 assert!(
1735 !taken(&log)
1736 .iter()
1737 .any(|line| line == "dispose" || line.starts_with("init")),
1738 "and neither was made again or let go"
1739 );
1740 }
1741
1742 #[test]
1743 fn a_component_that_builds_a_component_keeps_both_states() {
1744 let (runtime, log, out) = (Runtime::default(), log(), handle());
1745 let (inner_log, inner_out) = (log.clone(), out.clone());
1746 let outer = component(move |cx| {
1747 cx.use_state(|| 0);
1748 Box::new(life(7, &inner_log, &inner_out))
1750 });
1751 frame(&runtime, &outer);
1752 assert_eq!(
1753 runtime.states.len(),
1754 2,
1755 "one for the hooks, one for the state"
1756 );
1757 assert_eq!(taken(&log), ["init 7"]);
1758 }
1759
1760 #[test]
1761 fn hooks_keep_their_values_between_builds() {
1762 let runtime = Runtime::default();
1763 let seen = Rc::new(RefCell::new(Vec::new()));
1764 let build = {
1766 let seen = seen.clone();
1767 Rc::new(move |cx: &BuildContext| -> Box<dyn Widget> {
1768 let taps = cx.use_state(|| 10);
1769 let scratch = cx.use_ref(|| 0);
1770 scratch.update(|n| *n += 1);
1771 taps.update(|n| *n += 1);
1772 seen.borrow_mut().push((taps.get(), scratch.get()));
1773 Box::new(Container::new())
1774 })
1775 };
1776 let make = |build: &Builder| {
1777 let build = build.clone();
1778 Component::stateless(move |cx: &BuildContext| build(cx))
1779 };
1780 let build: Builder = build;
1781 frame(&runtime, &make(&build));
1782 assert!(
1783 take_rebuild_request(),
1784 "changing a state hook asks for a rebuild"
1785 );
1786 frame(&runtime, &make(&build));
1787 frame(&runtime, &make(&build));
1788 assert_eq!(*seen.borrow(), [(11, 1), (12, 2), (13, 3)]);
1789 }
1790
1791 #[test]
1792 fn use_memo_recomputes_only_when_its_dependencies_change() {
1793 let runtime = Runtime::default();
1794 let computed = Rc::new(Cell::new(0));
1795 let dep = Rc::new(Cell::new(1));
1796 let build: Builder = {
1797 let (computed, dep) = (computed.clone(), dep.clone());
1798 Rc::new(move |cx| {
1799 let d = dep.get();
1800 let value = cx.use_memo(d, || {
1801 computed.set(computed.get() + 1);
1802 d * 100
1803 });
1804 Box::new(Container::new().width(*value as f32))
1805 })
1806 };
1807 let make = || {
1808 let build = build.clone();
1809 Component::stateless(move |cx: &BuildContext| build(cx))
1810 };
1811 let root = make();
1812 frame(&runtime, &root);
1813 assert_eq!(
1814 (computed.get(), width(&root)),
1815 (1, Dimension::Length(100.0))
1816 );
1817 let root = make();
1818 frame(&runtime, &root);
1819 assert_eq!(computed.get(), 1, "the same dependency: not computed again");
1820 dep.set(2);
1821 let root = make();
1822 frame(&runtime, &root);
1823 assert_eq!(
1824 (computed.get(), width(&root)),
1825 (2, Dimension::Length(200.0))
1826 );
1827 }
1828
1829 #[test]
1830 fn effects_run_after_the_build_and_clean_up_before_the_next_and_at_the_end() {
1831 let (runtime, log) = (Runtime::default(), log());
1832 let dep = Rc::new(Cell::new(1));
1833 let build: Builder = {
1834 let (log, dep) = (log.clone(), dep.clone());
1835 Rc::new(move |cx| {
1836 let d = dep.get();
1837 let log = log.clone();
1838 log.borrow_mut().push(format!("build {d}"));
1839 cx.use_effect(d, move || {
1840 log.borrow_mut().push(format!("effect {d}"));
1841 move || log.borrow_mut().push(format!("cleanup {d}"))
1842 });
1843 Box::new(Container::new())
1844 })
1845 };
1846 let make = || {
1847 let build = build.clone();
1848 Component::stateless(move |cx: &BuildContext| build(cx))
1849 };
1850 frame(&runtime, &make());
1851 assert_eq!(
1852 taken(&log),
1853 ["build 1", "effect 1"],
1854 "after the build, not during it"
1855 );
1856
1857 frame(&runtime, &make());
1858 assert_eq!(
1859 taken(&log),
1860 ["build 1"],
1861 "the same dependency: nothing runs"
1862 );
1863
1864 dep.set(2);
1865 frame(&runtime, &make());
1866 assert_eq!(
1867 taken(&log),
1868 ["build 2", "cleanup 1", "effect 2"],
1869 "the old one is undone before the new one starts"
1870 );
1871
1872 frame(&runtime, &Flex::column().child(Container::new()));
1874 assert_eq!(taken(&log), ["cleanup 2"]);
1875 }
1876
1877 #[test]
1878 #[should_panic(expected = "different order")]
1879 fn hooks_called_in_a_different_order_are_caught() {
1880 let runtime = Runtime::default();
1881 let first = Rc::new(Cell::new(true));
1882 let build: Builder = {
1883 let first = first.clone();
1884 Rc::new(move |cx| {
1885 if first.get() {
1886 cx.use_state(|| 1i32);
1887 }
1888 cx.use_ref(String::new);
1889 Box::new(Container::new())
1890 })
1891 };
1892 let make = || {
1893 let build = build.clone();
1894 Component::stateless(move |cx: &BuildContext| build(cx))
1895 };
1896 frame(&runtime, &make());
1897 first.set(false);
1898 frame(&runtime, &make());
1899 }
1900
1901 #[test]
1902 #[should_panic(expected = "set_state was called while")]
1903 fn changing_state_from_inside_build_is_refused() {
1904 struct Greedy;
1905 struct GreedyState;
1906 impl StatefulWidget for Greedy {
1907 type State = GreedyState;
1908 fn create_state(&self) -> GreedyState {
1909 GreedyState
1910 }
1911 }
1912 impl State for GreedyState {
1913 type Widget = Greedy;
1914 fn build(&self, cx: &StateContext<Self>) -> Box<dyn Widget> {
1915 cx.handle().set_state(|_| {});
1916 Box::new(Container::new())
1917 }
1918 }
1919 frame(&Runtime::default(), &Greedy.into_widget());
1920 }
1921
1922 #[test]
1923 fn an_interval_is_asked_for_by_each_build_that_wants_it() {
1924 let runtime = Runtime::default();
1925 let on = Rc::new(Cell::new(true));
1926 let build: Builder = {
1927 let on = on.clone();
1928 Rc::new(move |cx| {
1929 if on.get() {
1930 cx.use_interval(std::time::Duration::from_secs(1), || {});
1931 cx.use_interval(std::time::Duration::from_secs(5), || {});
1932 }
1933 Box::new(Container::new())
1934 })
1935 };
1936 let make = || {
1937 let build = build.clone();
1938 Component::stateless(move |cx: &BuildContext| build(cx))
1939 };
1940 frame(&runtime, &make());
1941 let first = intervals();
1942 assert_eq!(first.len(), 2);
1943 assert_ne!(first[0].id, first[1].id, "two timers are told apart");
1944
1945 frame(&runtime, &make());
1946 let second = intervals();
1947 assert_eq!(
1948 first.iter().map(|i| i.id).collect::<Vec<_>>(),
1949 second.iter().map(|i| i.id).collect::<Vec<_>>(),
1950 "and each is the same timer from one build to the next"
1951 );
1952
1953 on.set(false);
1954 frame(&runtime, &make());
1955 assert!(
1956 intervals().is_empty(),
1957 "a build that stops asking stops the timer"
1958 );
1959 }
1960
1961 #[test]
1962 fn a_service_provided_to_the_runtime_is_reachable_from_a_build() {
1963 let runtime = Runtime::default();
1964 runtime.states.provide(Rc::new(41u32));
1965 let root = component(|cx| {
1966 let n = cx.service::<u32>().expect("provided");
1967 Box::new(Container::new().width(*n as f32 + 1.0))
1968 });
1969 frame(&runtime, &root);
1970 assert_eq!(width(&root), Dimension::Length(42.0));
1971 assert!(runtime.states.service::<String>().is_none());
1972 }
1973}