Selkie.git | lib/X/Selkie/ | WidgetCycle.rakumod
=begin pod
=head1 NAME
X::Selkie::WidgetCycle - thrown when a widget's parent chain loops back on itself
=head1 SYNOPSIS
=begin code :lang<raku>
use X::Selkie::WidgetCycle;
CATCH {
when X::Selkie::WidgetCycle {
note "widget tree is cyclic: {.chain}";
}
}
=end code
=head1 DESCRIPTION
A Selkie widget tree is a tree by convention, not by construction:
C<parent> is a writable attribute that layout containers set when they
adopt a child. One mis-ordered reparent — a container adopting a widget
that is already one of its own ancestors — makes the "tree" cyclic, and
every walk up a parent chain then runs forever.
Selkie's parent-chain walks all hop through
C<Selkie::Tree>'s C<next-ancestor>, which throws this exception once a
single walk exceeds C<PARENT-CHAIN-LIMIT> hops. Catching it is rarely
useful: it is always an upstream bug in whatever set C<parent>, and the
whole point of throwing is that the alternative — a render thread
spinning on an endless chain, with a frozen screen and no keyboard — is
undiagnosable from the outside.
C<chain> holds a bounded, human-readable rendering of the widgets
involved (class name and C<widget-id>, joined by arrows), which is
normally enough to identify the offending container directly. C<hops>
is the budget that was exceeded.
=head1 SEE ALSO
=item L<Selkie::Tree> — C<next-ancestor>, C<PARENT-CHAIN-LIMIT>, and the walk contract
=item L<Selkie::Widget> — where C<parent> lives
=end pod
#| Thrown when a parent-chain walk exceeds Selkie::Tree's
#| C<PARENT-CHAIN-LIMIT>, i.e. when the widget tree contains a cycle.
unit class X::Selkie::WidgetCycle is Exception;
#| Bounded description of the widgets on the offending chain, as
#| C<Class#id -> Class#id -> …>.
has Str $.chain is required;
#| The hop budget that was exceeded.
has Int $.hops is required;
method message(--> Str) {
"Selkie: parent-chain walk exceeded $!hops hops — the widget tree "
~ "contains a cycle (a widget is reachable from itself through "
~ "`parent`). Chain from the walk's current node: $!chain. This is "
~ "always a reparenting bug in the code that set `parent`: a "
~ "container adopted a widget that is already one of its own "
~ "ancestors.";
}