all_is_cubes_gpu/common/
reloadable.rs1use std::sync::{Arc, LazyLock, Mutex, PoisonError};
7use std::time::Duration;
8
9use resource::Resource;
10
11use all_is_cubes::listen;
12
13#[derive(Clone)]
14pub(crate) struct Reloadable(Arc<ReloadableInner>);
15
16pub(crate) struct ReloadableInner {
17 resource: Mutex<Resource<str>>,
18 cell: listen::Cell<Arc<str>>,
19}
20
21macro_rules! reloadable_str {
23 ($file_path:literal) => {
24 $crate::reloadable::Reloadable::new(::resource::resource_str!($file_path))
25 };
26}
27pub(crate) use reloadable_str;
28
29impl Reloadable {
30 pub fn new(resource: Resource<str>) -> Self {
31 let this = Reloadable(Arc::new(ReloadableInner {
32 cell: listen::Cell::new(res_arc_str(&resource)),
33 resource: Mutex::new(resource),
34 }));
35
36 if let Ok(mut reloadables) = POLLED_RELOADABLES.lock() {
38 reloadables.push(this.clone());
39 }
40
41 this
42 }
43
44 pub fn poll(&self) -> bool {
45 match self.0.resource.lock().as_deref_mut() {
46 Ok(resource) => {
47 let changed = resource.reload_if_changed();
48 if changed {
49 self.0.cell.set(res_arc_str(resource));
51 }
52 changed
53 }
54 Err(PoisonError { .. }) => false,
55 }
56 }
57
58 pub fn as_source(&self) -> listen::DynSource<Arc<str>> {
59 self.0.cell.as_source()
60 }
61}
62
63fn res_arc_str(r: &Resource<str>) -> Arc<str> {
64 Arc::from(<Resource<str> as AsRef<str>>::as_ref(r))
65}
66
67static POLLED_RELOADABLES: LazyLock<Mutex<Vec<Reloadable>>> = LazyLock::new(|| {
68 if cfg!(all(not(target_family = "wasm"), debug_assertions)) {
71 std::thread::spawn(poll_reloadables_loop);
72 }
73
74 Mutex::new(Vec::new())
75});
76
77fn poll_reloadables_loop() -> ! {
78 loop {
79 std::thread::sleep(Duration::from_secs(1));
80
81 let v = POLLED_RELOADABLES
83 .lock()
84 .expect("could not lock POLLED_RELOADABLES from polling thread");
85 let mut any = false;
86 for reloadable in v.iter() {
87 any |= reloadable.poll();
88 }
89 if any {
90 log::debug!("Reloadable polled and found changed");
91 }
92 }
93}