Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions third-party/nsh/crates/nsh-core/src/element/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,19 @@ impl Region {
let error = failed.get();
// The view is under the boundary, the fallback is not
// (what fails in it goes to the boundary above).
if error.is_none() {
super::reactive::register_boundary(failed);
}
// The run's owner is under the binding, where the
// boundary is: `boundary_of` finds it from there.
let boundary = error
.is_none()
.then(|| super::reactive::register_boundary(failed));
let owner = run_owner();
let elements = untracked(|| {
owner.scope(|| match error {
Some(error) => fallback(error, Reset(failed)),
None => view(),
owner.scope(|| {
if let Some(boundary) = boundary {
super::reactive::provide_context(boundary);
}
match error {
Some(error) => fallback(error, Reset(failed)),
None => view(),
}
})
})
.into_iter()
Expand Down
30 changes: 22 additions & 8 deletions third-party/nsh/crates/nsh-core/src/element/reactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,24 +66,38 @@ pub(crate) struct RustData {
boundaries: rustc_hash::FxHashMap<Owner, Signal<Option<super::ComponentError>>>,
}

/// Makes the current owner an error boundary: what fails under it sets
/// `failed`, for as long as it lives.
pub(crate) fn register_boundary(failed: Signal<Option<super::ComponentError>>) {
/// The binding that owns a boundary, inherited by its content.
#[derive(Clone, Copy)]
pub(super) struct BoundaryScope(Owner);

/// Makes the current binding catch its own failures and those of its content.
pub(super) fn register_boundary(failed: Signal<Option<super::ComponentError>>) -> BoundaryScope {
let owner = Owner::current();
with_data(|d| d.boundaries.insert(owner, failed));
cleanup(move || {
with_data(|d| d.boundaries.remove(&owner));
});
BoundaryScope(owner)
}

/// The nearest error boundary at or above `owner`.
pub(crate) fn boundary_of(owner: Owner) -> Option<Signal<Option<super::ComponentError>>> {
let mut at = Some(owner);
while let Some(owner) = at.filter(Owner::is_live) {
if let Some(signal) = with_data(|d| d.boundaries.get(&owner).copied()) {
return Some(signal);
let handle = owner.0.filter(|_| owner.is_live())?;
let graph = graph();
let boundaries = &graph.data.boundaries;
if let Some(&failed) = boundaries.get(&owner) {
return Some(failed);
}
// Placed children can outlive one run of the boundary's content.
// Their context still names the binding that catches their failures.
let mut frame = graph.context_of(handle.id);
while let Some(context) = frame {
if let Some(scope) = context.data.1.downcast_ref::<BoundaryScope>()
&& let Some(&failed) = boundaries.get(&scope.0)
{
return Some(failed);
}
at = owner.parent();
frame = context.parent.clone();
}
None
}
Expand Down
Loading