#PODNAME: Net::Object::Peer::Cookbook
#ABSTRACT: Recipes for Net::Object::Peer

# COPYRIGHT

#pod =head1 RECIPES
#pod
#pod =head2 Loop Detection
#pod
#pod In a situation where an event handler may itself emit an event it's
#pod possible to generate event loops.
#pod
#pod Here's a node class which accepts a single class of event. When a node
#pod receives an event, it also emits the same event.
#pod
#pod The event handler has a loop detector which will bail if the handler
#pod processes an event from an emitter more than once while the handler is
#pod itself emitting the event.
#pod
#pod  # examples/Loop.pm
#pod # EXAMPLE: examples/Loop.pm
#pod
#pod
#pod Now, let's set up a loop:
#pod
#pod  # examples/loop.pl
#pod # EXAMPLE: examples/loop.pl
#pod
#pod And the result:
#pod
#pod  % perl examples/loop.pl Loop
#pod # COMMAND: perl -Ilib examples/loop.pl Loop
#pod
#pod To protect all event handlers, we can modify
#pod L<build_sub|Net::Object::Peer/build_sub> so that it adds loop
#pod detection to every handler.  Here's a first attempt, which wraps
#pod the standard event handler in a subroutine:
#pod
#pod  # examples/LoopWrap.pm
#pod # EXAMPLE: examples/LoopWrap.pm
#pod
#pod And the result:
#pod
#pod  % perl examples/loop.pl LoopWrap
#pod # COMMAND: perl -Ilib examples/loop.pl LoopWrap
#pod
#pod Here's an alternative, taking advantage of the fact that
#pod L<build_sub|Net::Object::Peer/build_sub> returns a coderef generated
#pod by L<Sub::Quote/quote_sub>, so our loop detection code can be inlined
#pod directly into the event handler. Note that L<Sub::Quote> keeps
#pod I<copies> of the values referenced in C<%capture>, so handling of
#pod references to objects are slightly complicated.  C<$self> is weakened
#pod to avoid memory leaks, but copies of references turn into strong
#pod references, so instead a I<reference> to the weakened reference is
#pod passed.
#pod
#pod  # examples/LoopQuote.pm
#pod # EXAMPLE: examples/LoopQuote.pm
#pod
#pod And the result:
#pod
#pod  % perl examples/loop.pl LoopQuote
#pod # COMMAND: perl -Ilib examples/loop.pl LoopQuote
#pod

__END__

=pod

=head1 NAME

Net::Object::Peer::Cookbook - Recipes for Net::Object::Peer

=head1 VERSION

version 0.01

=head1 RECIPES

=head2 Loop Detection

In a situation where an event handler may itself emit an event it's
possible to generate event loops.

Here's a node class which accepts a single class of event. When a node
receives an event, it also emits the same event.

The event handler has a loop detector which will bail if the handler
processes an event from an emitter more than once while the handler is
itself emitting the event.

 # examples/Loop.pm
 package Loop;
 
 use Carp;
 our @CARP_NOT = qw( Beam::Emitter );
 
 use Try::Tiny;
 use Scalar::Util qw[ refaddr ];
 
 use Moo;
 use MooX::ClassAttribute;
 
 with 'Net::Object::Peer';
 
 has seen     => ( is => 'ro', default  => sub { {} } );
 has name     => ( is => 'ro', required => 1          );
 
 # Set high so can verify there is a loop
 class_has
     max_seen => ( is => 'rw', default =>  5          );
 
 sub label {
     my ( $self, $emitter, $name ) = @_;
     qq/@{[$self->name]} got '$name' from @{[$emitter->name]}/;
 }
 
 sub fail {
     my ( $self, $event ) = @_;
     croak( "loop detected: ",
 	   $self->label( $event->emitter, $event->name ) );
 }
 
 sub tag {
     my ( $self, $emitter, $name ) = @_;
     join $;, $name, refaddr $self, refaddr $emitter;
 }
 
 sub _cb_signal {
     my ( $self, $event ) = @_;
 
     my $tag = $self->tag( $event->emitter, $event->name );
 
     $self->fail( $event )
       if $self->seen->{$tag}++ == $self->max_seen;
 
     print $self->label( $event->emitter, $event->name ), "\n";
 
     try    { $self->emit( $event->name ) }
     catch  { die $_                      }
     finally{ delete $self->seen->{$tag}  };
 }
 
 1;

Now, let's set up a loop:

 # examples/loop.pl
 use Try::Tiny;
 
 use lib 'examples';
 use Module::Load;
 
 my ( $class ) = @ARGV;
 
 load $class;
 
 my $n1 = $class->new( name => 'n1' );
 my $n2 = $class->new( name => 'n2' );
 my $n3 = $class->new( name => 'n3' );
 
 $n3->subscribe( $n1, "signal" );
 $n3->subscribe( $n2, "signal" );
 $n2->subscribe( $n3, "signal" );
 
 # and start the ping-pong
 try {
     $n1->emit( 'signal' );
 }
 catch {
     print $_;
 };

And the result:

 % perl examples/loop.pl Loop
 n3 got 'signal' from n1
 n2 got 'signal' from n3
 n3 got 'signal' from n2
 n2 got 'signal' from n3
 n3 got 'signal' from n2
 n2 got 'signal' from n3
 n3 got 'signal' from n2
 n2 got 'signal' from n3
 n3 got 'signal' from n2
 n2 got 'signal' from n3
 n3 got 'signal' from n2
 loop detected: n2 got 'signal' from n3 at (eval 128) line 18.


To protect all event handlers, we can modify
L<build_sub|Net::Object::Peer/build_sub> so that it adds loop
detection to every handler.  Here's a first attempt, which wraps
the standard event handler in a subroutine:

 # examples/LoopWrap.pm
 package LoopWrap;
 
 use Try::Tiny;
 use Scalar::Util qw[ weaken ];
 use Moo;
 extends 'Loop';
 
 my %seen;
 
 around build_sub => sub {
 
     my $orig = shift;
     my ( $self, $emitter, $name ) = @_;
     my @args = @_;
 
     my $tag = $self->tag( $emitter, $name );
 
     my $sub = &$orig( @args );
 
     weaken $self;
 
     return sub {
         my ( $event ) = @_;
 
         $self->fail( $event )
            if $seen{$tag}++;
 
         my @args = @_;
 
         try     { $sub->( @args )    }
         catch   { die $_             }
         finally { delete $seen{$tag} };
     };
 };
 
 1;

And the result:

 % perl examples/loop.pl LoopWrap
 n3 got 'signal' from n1
 n2 got 'signal' from n3
 n3 got 'signal' from n2
 loop detected: n2 got 'signal' from n3 at examples/Loop.pm line 47.


Here's an alternative, taking advantage of the fact that
L<build_sub|Net::Object::Peer/build_sub> returns a coderef generated
by L<Sub::Quote/quote_sub>, so our loop detection code can be inlined
directly into the event handler. Note that L<Sub::Quote> keeps
I<copies> of the values referenced in C<%capture>, so handling of
references to objects are slightly complicated.  C<$self> is weakened
to avoid memory leaks, but copies of references turn into strong
references, so instead a I<reference> to the weakened reference is
passed.

 # examples/LoopQuote.pm
 package LoopQuote;
 
 use Scalar::Util qw[ weaken ];
 use Sub::QuoteX::Utils qw[ quote_subs ];
 use Moo;
 extends 'Loop';
 
 my %seen;
 
 around build_sub => sub {
 
     my $orig = shift;
     my ( $self, $emitter, $name ) = @_;
 
     my $tag = $self->tag( $emitter, $name );
 
     my $sub = &$orig;
 
     weaken $self;
 
     my %captures = (
         '$seen'   => \\%seen,
         '$r_self' => \\$self,
         '$name'   => \$name,
         '$tag'    => \$tag,
     );
 
     return quote_subs(
         \q[
             use Try::Tiny;
             my $event = $_[0];
             $$r_self->fail( $event ) if $seen->{$tag}++;
             my @args = @_;
             try {
            ],
         [ $sub, local => 1, args => q[@args] ],
         \q[
             }
             catch { die $_ } finally { delete $seen->{$tag} };
           ],
         { capture => \%captures },
     );
 };
 
 1;

And the result:

 % perl examples/loop.pl LoopQuote
 n3 got 'signal' from n1
 n2 got 'signal' from n3
 n3 got 'signal' from n2
 loop detected: n2 got 'signal' from n3 at examples/Loop.pm line 47.


=head1 AUTHOR

Diab Jerius <djerius@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2016 by Smithsonian Astrophysical Observatory.

This is free software, licensed under:

  The GNU General Public License, Version 3, June 2007

=cut
