Skip to content

Commit 396799a

Browse files
authored
Merge pull request #128 from togglebyte/feature/better-errors
BUGFIX: terminating from error display now works
2 parents f97b559 + aae9cbe commit 396799a

35 files changed

Lines changed: 553 additions & 320 deletions

File tree

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
* Messages can be emitted to both widget ids and component ids
44
* Feature flag: `serde` is now a feature flag that adds `Serialize` / `Deserialize` to `WidgetId`
55
* There is now a distinction between `global`s and `local`s
6+
* BUGFIX: ctrl+c works with the error display
7+
* Trying to use a component twice will now include the component name in the
8+
error
69
* 0.2.7
710
* BUGFIX: use correct truthiness check in control flow update
811
* 0.2.6

anathema-runtime/src/builder.rs

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,15 @@ impl<G: GlobalEventHandler> Builder<G> {
183183
let (blueprint, globals) = loop {
184184
match self.document.compile() {
185185
Ok(val) => break val,
186-
Err(error) => {
187-
show_error(error, backend, &mut self.document)?;
188-
}
186+
// This can only show template errors.
187+
// Widget errors doesn't become available until after the first tick.
188+
Err(error) => match show_error(error.into(), backend, &self.document) {
189+
Ok(()) => return Err(Error::Stop),
190+
Err(Error::Reload) if self.hot_reload => {
191+
_ = self.document.reload_templates();
192+
}
193+
err => err?,
194+
},
189195
}
190196
};
191197

@@ -204,42 +210,10 @@ impl<G: GlobalEventHandler> Builder<G> {
204210
self.fps,
205211
self.global_event_handler,
206212
self.function_table,
213+
self.hot_reload,
207214
);
208215

209-
// NOTE:
210-
// this enables hot reload,
211-
// however with this enabled the `with_frame` function
212-
// on the runtime will repeat
213-
loop {
214-
match f(&mut inst, backend) {
215-
Ok(()) => (),
216-
e => match e {
217-
Ok(_) => continue,
218-
Err(Error::Stop) => break Ok(()),
219-
Err(Error::Template(error)) => match show_error(error, backend, &mut inst.document) {
220-
Ok(_) => continue,
221-
Err(err) => panic!("error console failed: {err}"),
222-
},
223-
Err(Error::Widget(err)) => panic!("this should not panic in the future: {err}"),
224-
Err(e) => break Err(e),
225-
},
226-
}
227-
228-
if !self.hot_reload {
229-
break Ok(());
230-
}
231-
232-
match inst.reload() {
233-
Ok(()) => continue,
234-
Err(Error::Stop) => todo!(),
235-
Err(Error::Template(error)) => match show_error(error, backend, &mut inst.document) {
236-
Ok(_) => continue,
237-
Err(err) => panic!("error console failed: {err}"),
238-
},
239-
Err(Error::Widget(_error)) => todo!(),
240-
Err(e) => break Err(e),
241-
}
242-
}
216+
f(&mut inst, backend)
243217
}
244218

245219
fn set_watcher(&mut self, hot_reload: bool) -> Result<Option<RecommendedWatcher>> {

anathema-runtime/src/error.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,24 @@ pub type Result<T> = std::result::Result<T, Error>;
88
#[derive(Debug)]
99
pub enum Error {
1010
Template(TemplateError),
11-
Notify(notify::Error),
1211
Widget(anathema_widgets::error::Error),
12+
Notify(notify::Error),
1313
Stop,
14+
Reload,
1415
InvalidComponentName,
1516
Resolver(anathema_value_resolver::Error),
1617
}
1718

1819
impl Display for Error {
1920
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2021
match self {
21-
Error::Template(template) => write!(f, "{template}"),
22-
Error::Stop => write!(f, "stopping"),
23-
Error::Notify(err) => write!(f, "{err}"),
24-
Error::Widget(err) => write!(f, "{err}"),
25-
Error::Resolver(err) => write!(f, "{err}"),
26-
Error::InvalidComponentName => write!(f, "no such component"),
22+
Self::Template(template) => write!(f, "{template}"),
23+
Self::Stop => write!(f, "stopping"),
24+
Self::Reload => write!(f, "reloading"),
25+
Self::Notify(err) => write!(f, "{err}"),
26+
Self::Widget(err) => write!(f, "{err}"),
27+
Self::Resolver(err) => write!(f, "{err}"),
28+
Self::InvalidComponentName => write!(f, "no such component"),
2729
}
2830
}
2931
}

anathema-runtime/src/runtime/error.rs

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
22

33
use anathema_backend::Backend;
44
use anathema_templates::Document;
5-
use anathema_templates::error::Error as TemplateError;
65
use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher, recommended_watcher};
76

87
use super::Runtime;
@@ -13,75 +12,87 @@ static STOP_ERROR_RT: AtomicBool = AtomicBool::new(false);
1312
// TODO:
1413
// this should be cleaned up.
1514
// The file watcher is almost identitcal to the one in the runtime.
16-
pub(crate) fn show_error<B: Backend>(error: TemplateError, backend: &mut B, document: &mut Document) -> Result<()> {
15+
pub(crate) fn show_error<B: Backend>(error: Error, backend: &mut B, document: &Document) -> Result<()> {
1716
if STOP_ERROR_RT.load(Ordering::Relaxed) {
1817
panic!("recursive error displays.");
1918
}
2019

20+
let template_path = match &error {
21+
Error::Template(error) => format!(": {}", error.path()),
22+
Error::Widget(error) => match error.path.as_ref() {
23+
Some(path) => format!(": {}", path.display()),
24+
None => String::new(),
25+
},
26+
_ => String::new(),
27+
};
28+
2129
let tpl = format!(
2230
"
2331
align [alignment: 'centre']
2432
border [background: 'red', foreground: 'black']
2533
vstack
26-
text [bold: true] 'Template error:'
34+
text [bold: true] 'Template error{template_path}'
2735
text '{error}'
2836
"
2937
);
3038

3139
let doc = Document::new(tpl);
3240

3341
// File watchers here
34-
let _watcher = set_watcher(document)?;
42+
let _watcher = set_watcher(document);
3543

3644
let mut builder = Runtime::builder(doc, backend);
3745
builder.hot_reload(false);
38-
builder.finish(backend, |runtime, backend| {
46+
let res = builder.finish(backend, |runtime, backend| {
3947
runtime.with_frame(backend, |backend, mut frame| {
4048
loop {
4149
if STOP_ERROR_RT.load(Ordering::Relaxed) {
42-
break;
50+
break Err(Error::Reload);
4351
}
4452

4553
frame.tick(backend)?;
4654
frame.present(backend);
4755
frame.cleanup();
48-
}
56+
}?;
4957
backend.clear();
5058
Err(Error::Stop)
5159
})
52-
})?;
53-
document.reload_templates()?;
60+
});
5461

5562
// Reset
5663
STOP_ERROR_RT.store(false, Ordering::Relaxed);
5764

58-
Ok(())
65+
res
5966
}
6067

6168
fn set_watcher(document: &Document) -> Result<RecommendedWatcher> {
62-
let paths = document
69+
let _paths = document
6370
.template_paths()
6471
.filter_map(|p| p.canonicalize().ok())
6572
.collect::<Vec<_>>();
6673

6774
let mut watcher = recommended_watcher(move |event: std::result::Result<Event, _>| match event {
6875
Ok(event) => match event.kind {
6976
notify::EventKind::Create(_) | notify::EventKind::Remove(_) | notify::EventKind::Modify(_) => {
70-
if paths.iter().any(|p| event.paths.contains(p)) {
71-
STOP_ERROR_RT.store(true, Ordering::Relaxed);
72-
}
77+
// NOTE: we'll let any changes to any files stop the error runtime,
78+
// and let the main runtime attempt to restart again.
79+
//
80+
// This might change in the future but it works for now
81+
82+
// if paths.iter().any(|p| event.paths.contains(p)) {
83+
STOP_ERROR_RT.store(true, Ordering::Relaxed);
84+
// }
7385
}
7486
notify::EventKind::Any | notify::EventKind::Access(_) | notify::EventKind::Other => (),
7587
},
7688
Err(_err) => (),
7789
})?;
7890

7991
for path in document.template_paths() {
80-
let path = path.canonicalize().unwrap();
92+
let Some(path) = path.parent() else { continue };
93+
let Ok(path) = path.canonicalize() else { continue };
8194

82-
if let Some(parent) = path.parent() {
83-
watcher.watch(parent, RecursiveMode::NonRecursive)?;
84-
}
95+
watcher.watch(&path, RecursiveMode::NonRecursive)?;
8596
}
8697

8798
Ok(watcher)

anathema-runtime/src/runtime/mod.rs

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub struct Runtime<G> {
5656
pub(super) deferred_components: DeferredComponents,
5757
pub(super) global_event_handler: G,
5858
pub(super) function_table: FunctionTable,
59+
pub(super) hot_reload: bool,
5960
}
6061

6162
impl Runtime<()> {
@@ -89,6 +90,7 @@ impl<G: GlobalEventHandler> Runtime<G> {
8990
fps: u32,
9091
global_event_handler: G,
9192
function_table: FunctionTable,
93+
hot_reload: bool,
9294
) -> Self {
9395
let sleep_micros: u64 = ((1.0 / fps as f64) * 1000.0 * 1000.0) as u64;
9496

@@ -113,6 +115,7 @@ impl<G: GlobalEventHandler> Runtime<G> {
113115
sleep_micros,
114116
global_event_handler,
115117
function_table,
118+
hot_reload,
116119
}
117120
}
118121

@@ -133,39 +136,81 @@ impl<G: GlobalEventHandler> Runtime<G> {
133136

134137
pub fn run<B: Backend>(&mut self, backend: &mut B) -> Result<()> {
135138
let sleep_micros = self.sleep_micros;
136-
self.with_frame(backend, |backend, mut frame| {
137-
// Perform the initial tick so tab index has a tree to work with.
138-
// This means we can not react to any events in this tick as the tree does not
139-
// yet have any widgets or components.
140-
frame.tick(backend)?;
141139

142-
let mut tabindex = TabIndex::new(&mut frame.tabindex, frame.tree.view());
143-
tabindex.next();
140+
loop {
141+
let res = self.with_frame(backend, |backend, mut frame| {
142+
// Perform the initial tick so tab index has a tree to work with.
143+
// This means we can not react to any events in this tick as the tree does not
144+
// yet have any widgets or components.
145+
_ = frame.tick(backend);
146+
TabIndex::new(&mut frame.tabindex, frame.tree.view()).next();
147+
148+
if let Some(current) = frame.tabindex.as_ref() {
149+
frame.with_component(current.widget_id, current.state_id, |comp, children, ctx| {
150+
comp.dyn_component.any_focus(children, ctx)
151+
});
152+
}
144153

145-
if let Some(current) = frame.tabindex.as_ref() {
146-
frame.with_component(current.widget_id, current.state_id, |comp, children, ctx| {
147-
comp.dyn_component.any_focus(children, ctx)
148-
});
149-
}
154+
loop {
155+
if REBUILD.swap(false, Ordering::Relaxed) {
156+
frame.force_unmount_return();
157+
backend.clear();
158+
break Err(Error::Reload);
159+
}
150160

151-
loop {
152-
frame.tick(backend)?;
153-
if frame.layout_ctx.stop_runtime {
154-
return Err(Error::Stop);
155-
}
161+
match frame.tick(backend) {
162+
Ok(_duration) => (),
163+
Err(err) => match err {
164+
err @ (Error::Template(_) | Error::Widget(_)) => {
165+
match show_error(err, backend, frame.document) {
166+
Err(Error::Stop) => return Err(Error::Stop),
167+
// NOTE: we continue here as this should
168+
// cause the REBUILD to trigger
169+
Err(Error::Reload) => continue,
170+
_ => unreachable!("show_error only return stop or rebuild"),
171+
}
172+
}
173+
err => return Err(err),
174+
},
175+
}
156176

157-
frame.present(backend);
158-
frame.cleanup();
159-
std::thread::sleep(Duration::from_micros(sleep_micros));
177+
if frame.layout_ctx.stop_runtime {
178+
return Err(Error::Stop);
179+
}
160180

161-
if REBUILD.swap(false, Ordering::Relaxed) {
162-
frame.force_rebuild()?;
163-
backend.clear();
164-
break Ok(());
181+
frame.present(backend);
182+
frame.cleanup();
183+
std::thread::sleep(Duration::from_micros(sleep_micros));
165184
}
185+
});
186+
187+
match res {
188+
Ok(()) => panic!(),
189+
Err(e) => match e {
190+
Error::Template(_) | Error::Widget(_) => {
191+
unreachable!("these error variants are handled inside the tick loop")
192+
}
193+
Error::Stop => return Err(Error::Stop),
194+
195+
Error::Reload => loop {
196+
// Reload can fail if the template fails to parse.
197+
match self.reload() {
198+
Ok(()) => break,
199+
Err(e) => {
200+
if let Err(Error::Stop) = show_error(e, backend, &self.document) {
201+
return Err(Error::Stop);
202+
}
203+
}
204+
}
205+
},
206+
e => return Err(e),
207+
},
166208
}
167-
})?;
168209

210+
if !self.hot_reload {
211+
break;
212+
}
213+
}
169214
Ok(())
170215
}
171216

@@ -250,7 +295,8 @@ pub struct Frame<'rt, 'bp, G> {
250295
}
251296

252297
impl<'rt, 'bp, G: GlobalEventHandler> Frame<'rt, 'bp, G> {
253-
pub fn force_rebuild(mut self) -> Result<()> {
298+
/// Unmount all components and return them to storage
299+
pub fn force_unmount_return(mut self) {
254300
// call unmount on all components
255301
for i in 0..self.layout_ctx.components.len() {
256302
let Some((widget_id, state_id)) = self.layout_ctx.components.get_ticking(i) else { continue };
@@ -259,7 +305,6 @@ impl<'rt, 'bp, G: GlobalEventHandler> Frame<'rt, 'bp, G> {
259305
}
260306

261307
self.return_state_and_component();
262-
Ok(())
263308
}
264309

265310
pub fn handle_global_event(&mut self, event: Event) -> Option<Event> {
@@ -714,11 +759,6 @@ impl<'rt, 'bp, G: GlobalEventHandler> Frame<'rt, 'bp, G> {
714759
}
715760
}
716761

717-
// fn display_error(&mut self, backend: &mut impl Backend) {
718-
// let _tpl = "text 'you goofed up'";
719-
// backend.render(self.layout_ctx.glyph_map);
720-
// }
721-
722762
fn post_cycle_events(&mut self) {
723763
while let Some(event) = self.post_cycle_events.pop_front() {
724764
self.event(event);

0 commit comments

Comments
 (0)