File Coverage

blib/lib/Beam/Emitter.pm
Criterion Covered Total %
statement 71 71 100.0
branch 14 16 87.5
condition 11 13 84.6
subroutine 16 16 100.0
pod 7 7 100.0
total 119 123 96.7


line stmt bran cond sub pod time code
1             package Beam::Emitter;
2             our $VERSION = '1.005';
3             # ABSTRACT: Role for event emitting classes
4              
5 10     10   151116 use strict;
  10         23  
  10         393  
6 10     10   48 use warnings;
  10         21  
  10         370  
7              
8 10     10   5711 use Types::Standard qw(:all);
  10         594461  
  10         137  
9 10     10   377133 use Scalar::Util qw( weaken refaddr );
  10         23  
  10         996  
10 10     10   66 use Carp qw( croak );
  10         32  
  10         562  
11 10     10   5088 use Beam::Event;
  10         41  
  10         653  
12 10     10   112 use Module::Runtime qw( use_module );
  10         21  
  10         90  
13 10     10   2840 use Moo::Role; # Put this last to ensure proper, automatic cleanup
  10         30978  
  10         121  
14              
15              
16             # The event listeners on this object, a hashref of arrayrefs of
17             # EVENT_NAME => [ Beam::Listener object, ... ]
18              
19             has _listeners => (
20             is => 'ro',
21             isa => HashRef,
22             default => sub { {} },
23             );
24              
25             #pod =method subscribe ( event_name, subref, [ %args ] )
26             #pod
27             #pod Subscribe to an event from this object. C is the name of the event.
28             #pod C is a subroutine reference that will get either a L object
29             #pod (if using the L method) or something else (if using the L method).
30             #pod
31             #pod Returns a coderef that, when called, unsubscribes the new subscriber.
32             #pod
33             #pod my $unsubscribe = $emitter->subscribe( open_door => sub {
34             #pod warn "ding!";
35             #pod } );
36             #pod $emitter->emit( 'open_door' ); # ding!
37             #pod $unsubscribe->();
38             #pod $emitter->emit( 'open_door' ); # no ding
39             #pod
40             #pod This unsubscribe subref makes it easier to stop our subscription in a safe,
41             #pod non-leaking way:
42             #pod
43             #pod my $unsub;
44             #pod $unsub = $emitter->subscribe( open_door => sub {
45             #pod $unsub->(); # Only handle one event
46             #pod } );
47             #pod $emitter->emit( 'open_door' );
48             #pod
49             #pod The above code does not leak memory, but the following code does:
50             #pod
51             #pod # Create a memory cycle which must be broken manually
52             #pod my $cb;
53             #pod $cb = sub {
54             #pod my ( $event ) = @_;
55             #pod $event->emitter->unsubscribe( open_door => $cb ); # Only handle one event
56             #pod # Because the callback sub ($cb) closes over a reference to itself
57             #pod # ($cb), it can never be cleaned up unless something breaks the
58             #pod # cycle explicitly.
59             #pod };
60             #pod $emitter->subscribe( open_door => $cb );
61             #pod $emitter->emit( 'open_door' );
62             #pod
63             #pod The way to fix this second example is to explicitly C inside the callback
64             #pod sub. Forgetting to do that will result in a leak. The returned unsubscribe coderef
65             #pod does not have this issue.
66             #pod
67             #pod By default, the emitter only stores the subroutine reference in an
68             #pod object of class L. If more information should be
69             #pod stored, create a custom subclass of L and use C<%args>
70             #pod to specify the class name and any attributes to be passed to its
71             #pod constructor:
72             #pod
73             #pod {
74             #pod package MyListener;
75             #pod extends 'Beam::Listener';
76             #pod
77             #pod # add metadata with subscription time
78             #pod has sub_time => is ( 'ro',
79             #pod init_arg => undef,
80             #pod default => sub { time() },
81             #pod );
82             #pod }
83             #pod
84             #pod # My::Emitter consumes the Beam::Emitter role
85             #pod my $emitter = My::Emitter->new;
86             #pod $emitter->on( "foo",
87             #pod sub { print "Foo happened!\n"; },
88             #pod class => MyListener
89             #pod );
90             #pod
91             #pod The L method can be used to examine the subscribed listeners.
92             #pod
93             #pod
94             #pod =cut
95              
96             sub subscribe {
97 49     49 1 13047 my ( $self, $name, $sub, %args ) = @_;
98              
99 49   100     5133 my $class = delete $args{ class } || "Beam::Listener";
100 49 50       5148 croak( "listener object must descend from Beam::Listener" )
101             unless use_module($class)->isa( 'Beam::Listener' );
102              
103 49         150511 my $listener = $class->new( %args, callback => $sub );
104              
105 47         185722 push @{ $self->_listeners->{$name} }, $listener;
  47         9779  
106 47         5004 weaken $self;
107 47         4888 weaken $sub;
108             return sub {
109 16 100   16   10138 $self->unsubscribe($name => $sub)
110             if defined $self;
111 47         17199 };
112             }
113              
114             #pod =method on ( event_name, subref )
115             #pod
116             #pod An alias for L. B: Do not use this alias for method
117             #pod modifiers! If you want to override behavior, override C.
118             #pod
119             #pod =cut
120              
121 35     35 1 125965 sub on { shift->subscribe( @_ ) }
122              
123             #pod =method unsubscribe ( event_name [, subref ] )
124             #pod
125             #pod Unsubscribe from an event. C is the name of the event. C is
126             #pod the single listener subref to be removed. If no subref is given, will remove
127             #pod all listeners for this event.
128             #pod
129             #pod =cut
130              
131             sub unsubscribe {
132 24     24 1 4236 my ( $self, $name, $sub ) = @_;
133 24 100       3634 if ( !$sub ) {
134 1         4 delete $self->_listeners->{$name};
135             }
136             else {
137 23         3581 my $listeners = $self->_listeners->{$name};
138 23         3669 my $idx = 0;
139 23   100     3590 $idx++ until $idx > $#{$listeners} or refaddr $listeners->[$idx]->callback eq refaddr $sub;
  24         7264  
140 23 100       3641 if ( $idx > $#{$listeners} ) {
  23         7122  
141 2         269 croak "Could not find sub in listeners";
142             }
143 21         3624 splice @{$self->_listeners->{$name}}, $idx, 1;
  21         10492  
144             }
145 22         14127 return;
146             }
147              
148             #pod =method un ( event_name [, subref ] )
149             #pod
150             #pod An alias for L. B: Do not use this alias for method
151             #pod modifiers! If you want to override behavior, override C.
152             #pod
153             #pod =cut
154              
155 6     6 1 2866 sub un { shift->unsubscribe( @_ ) }
156              
157             #pod =method emit ( name, event_args )
158             #pod
159             #pod Emit a L with the given C. C is a list of name => value
160             #pod pairs to give to the C constructor.
161             #pod
162             #pod Use the C key in C to specify a different Event class.
163             #pod
164             #pod =cut
165              
166             sub emit {
167 34     34 1 15668 my ( $self, $name, %args ) = @_;
168              
169 34 100       3727 return unless exists $self->_listeners->{$name};
170              
171 33   100     3708 my $class = delete $args{ class } || "Beam::Event";
172 33   66     3705 $args{ emitter } ||= $self;
173 33   66     3751 $args{ name } ||= $name;
174 33         36620 my $event = $class->new( %args );
175              
176             # don't use $self->_listeners->{$name} directly, as callbacks may unsubscribe
177             # from $name, changing the array, and confusing the for loop
178 33         164265 my @listeners = @{ $self->_listeners->{$name} };
  33         7290  
179              
180 33         3699 for my $listener ( @listeners ) {
181 35         5017 $listener->callback->( $event );
182 35 100       50941 last if $event->is_stopped;
183             }
184 33         32541 return $event;
185             }
186              
187             #pod =method emit_args ( name, callback_args )
188             #pod
189             #pod Emit an event with the given C. C is a list that will be given
190             #pod directly to each subscribed callback.
191             #pod
192             #pod Use this if you want to avoid using L, though you miss out on the control
193             #pod features like L and L.
194             #pod
195             #pod =cut
196              
197             sub emit_args {
198 3     3 1 402 my ( $self, $name, @args ) = @_;
199              
200 3 100       24 return unless exists $self->_listeners->{$name};
201              
202             # don't use $self->_listeners->{$name} directly, as callbacks may unsubscribe
203             # from $name, changing the array, and confusing the for loop
204 2         5 my @listeners = @{ $self->_listeners->{$name} };
  2         8  
205              
206 2         5 for my $listener ( @listeners ) {
207 3         11 $listener->callback->( @args );
208             }
209 2         861 return;
210             }
211              
212             #pod =method listeners ( event_name )
213             #pod
214             #pod Returns a list containing the listeners which have subscribed to the
215             #pod specified event from this emitter. The list elements are either
216             #pod instances of L or of custom classes specified in calls
217             #pod to L.
218             #pod
219             #pod =cut
220              
221             sub listeners {
222              
223 4     4 1 4339 my ( $self, $name ) = @_;
224              
225 4 50       5 return @{ $self->_listeners->{$name} || [] };
  4         30  
226             }
227              
228             1;
229              
230             __END__