File Coverage

blib/lib/Role/EventEmitter.pm
Criterion Covered Total %
statement 49 68 72.0
branch 12 24 50.0
condition 4 7 57.1
subroutine 12 15 80.0
pod 8 8 100.0
total 85 122 69.6


line stmt bran cond sub pod time code
1             package Role::EventEmitter;
2              
3 1     1   613 use Carp 'croak';
  1         2  
  1         63  
4 1     1   7 use Scalar::Util qw(blessed refaddr weaken);
  1         2  
  1         54  
5 1   50 1   5 use constant DEBUG => $ENV{ROLE_EVENTEMITTER_DEBUG} || 0;
  1         2  
  1         89  
6              
7 1     1   7 use Role::Tiny;
  1         1  
  1         6  
8              
9             our $VERSION = '0.003';
10              
11 1 50   1 1 3 sub catch { $_[0]->on(error => $_[1]) and return $_[0] }
12              
13             sub emit {
14 31     31 1 2883 my $self = shift;
15 31         73 my $name = shift;
16 31 100       116 if (my $s = $self->{_role_ee_events}{$name}) {
17 29         48 warn "-- Emit $name in @{[blessed $self]} (@{[scalar @$s]})\n" if DEBUG;
18 29         78 for my $cb (@$s) { $self->$cb(@_) }
  32         138  
19             } else {
20 2         5 warn "-- Emit $name in @{[blessed $self]} (0)\n" if DEBUG;
21 2 100       8 die "@{[blessed $self]}: $_[0]" if $name eq 'error';
  1         11  
22             }
23 28         175 return $self;
24             }
25              
26 8     8 1 589 sub has_subscribers { !!$_[0]->{_role_ee_events}{$_[1]} }
27              
28 19 50   19 1 1868 sub on { push @{$_[0]{_role_ee_events}{$_[1]}}, $_[2] and return $_[2] }
  19         125  
29              
30             sub once {
31 11     11 1 805 my ($self, $name, $cb) = @_;
32              
33 11         78 weaken $self;
34 11         48 my $wrapper;
35             $wrapper = sub {
36 10     10   56 $self->unsubscribe($name => $wrapper);
37 10         45 $cb->(@_);
38 11         84 };
39 11         69 $self->on($name => $wrapper);
40 11         49 weaken $wrapper;
41              
42 11         42 return $wrapper;
43             }
44              
45             my $has_future;
46             sub once_f {
47 0     0 1 0 my ($self, $name) = @_;
48              
49 0 0       0 unless (defined $has_future) {
50 0         0 local $@;
51 0 0       0 eval { require Future; $has_future = 1 } or $has_future = 0;
  0         0  
  0         0  
52             }
53 0 0       0 croak "Future is required for once_f method" unless $has_future;
54              
55 0         0 my $f = Future->new;
56 0     0   0 my $wrapper = sub { $f->done(@_) };
  0         0  
57 0         0 $self->on($name => $wrapper);
58 0         0 $self->{_role_ee_futures}{$name}{refaddr $wrapper} = $f;
59            
60 0         0 weaken $self;
61 0         0 weaken $wrapper;
62 0     0   0 return $f->on_ready(sub { $self->unsubscribe($name => $wrapper) });
  0         0  
63             }
64              
65 18   100 18 1 1785 sub subscribers { $_[0]->{_role_ee_events}{$_[1]} ||= [] }
66              
67             sub unsubscribe {
68 15     15 1 55 my ($self, $name, $cb) = @_;
69 15 100       47 if ($cb) { # One
70 14         65 my $addr = refaddr $cb;
71 14         30 $self->{_role_ee_events}{$name} = [grep { $addr != refaddr $_ } @{$self->{_role_ee_events}{$name}}];
  22         129  
  14         67  
72 14 100       42 delete $self->{_role_ee_events}{$name} unless @{$self->{_role_ee_events}{$name}};
  14         75  
73 14 50 33     82 if ($self->{_role_ee_futures}{$name} and my $f = delete $self->{_role_ee_futures}{$name}{$addr}) {
74 0         0 $f->cancel;
75 0 0       0 delete $self->{_role_ee_futures}{$name} unless keys %{$self->{_role_ee_futures}{$name}};
  0         0  
76             }
77             } else { # All
78 1         12 delete $self->{_role_ee_events}{$name};
79 1 50       6 $_->cancel for values %{delete $self->{_role_ee_futures}{$name} || {}};
  1         14  
80             }
81 15         52 return $self;
82             }
83              
84             1;
85              
86             =head1 NAME
87              
88             Role::EventEmitter - Event emitter role
89              
90             =head1 SYNOPSIS
91              
92             package Channel;
93             use Moo;
94             with 'Role::EventEmitter';
95              
96             # Emit events
97             sub send_message {
98             my $self = shift;
99             $self->emit(message => @_);
100             }
101              
102             package main;
103              
104             # Subscribe to events
105             my $channel_a = Channel->new;
106             $channel_a->on(message => sub {
107             my ($channel, $text) = @_;
108             say "Received message: $text";
109             });
110             $channel_a->send_message('All is well');
111              
112             =head1 DESCRIPTION
113              
114             L is a simple L role for event emitting objects
115             based on L. This role can be applied to any hash-based
116             object class such as those created with L, L, or L.
117              
118             =head1 EVENTS
119              
120             L can emit the following events.
121              
122             =head2 error
123              
124             $e->on(error => sub {
125             my ($e, $err) = @_;
126             ...
127             });
128              
129             This is a special event for errors, it will not be emitted directly by this
130             role but is fatal if unhandled.
131              
132             $e->on(error => sub {
133             my ($e, $err) = @_;
134             say "This looks bad: $err";
135             });
136              
137             =head1 METHODS
138              
139             L composes the following methods.
140              
141             =head2 catch
142              
143             $e = $e->catch(sub {...});
144              
145             Subscribe to L event.
146              
147             # Longer version
148             $e->on(error => sub {...});
149              
150             =head2 emit
151              
152             $e = $e->emit('foo');
153             $e = $e->emit('foo', 123);
154              
155             Emit event.
156              
157             =head2 has_subscribers
158              
159             my $bool = $e->has_subscribers('foo');
160              
161             Check if event has subscribers.
162              
163             =head2 on
164              
165             my $cb = $e->on(foo => sub {...});
166              
167             Subscribe to event.
168              
169             $e->on(foo => sub {
170             my ($e, @args) = @_;
171             ...
172             });
173              
174             =head2 once
175              
176             my $cb = $e->once(foo => sub {...});
177              
178             Subscribe to event and unsubscribe again after it has been emitted once.
179              
180             $e->once(foo => sub {
181             my ($e, @args) = @_;
182             ...
183             });
184              
185             =head2 once_f
186              
187             my $f = $e->once_f('foo');
188              
189             Subscribe to event as in L, returning a L that will be marked
190             complete after it has been emitted once. Requires L to be installed.
191              
192             my $f = $e->once_f('foo')->on_done(sub {
193             my ($e, @args) = @_;
194             ...
195             });
196              
197             To unsubscribe the returned L early, cancel it.
198              
199             $f->cancel;
200              
201             =head2 subscribers
202              
203             my $subscribers = $e->subscribers('foo');
204              
205             All subscribers for event.
206              
207             # Unsubscribe last subscriber
208             $e->unsubscribe(foo => $e->subscribers('foo')->[-1]);
209              
210             # Change order of subscribers
211             @{$e->subscribers('foo')} = reverse @{$e->subscribers('foo')};
212              
213             =head2 unsubscribe
214              
215             $e = $e->unsubscribe('foo');
216             $e = $e->unsubscribe(foo => $cb);
217              
218             Unsubscribe from event. Related Futures will also be cancelled.
219              
220             =head1 DEBUGGING
221              
222             You can set the C environment variable to get some
223             advanced diagnostics information printed to C.
224              
225             ROLE_EVENTEMITTER_DEBUG=1
226              
227             =head1 BUGS
228              
229             Report any issues on the public bugtracker.
230              
231             =head1 AUTHOR
232              
233             Dan Book
234              
235             Code and tests adapted from L, an event emitter base class
236             by the L team.
237              
238             =head1 COPYRIGHT AND LICENSE
239              
240             Copyright (c) 2008-2015 Sebastian Riedel.
241              
242             Copyright (c) 2015 Dan Book for adaptation to a role and further changes.
243              
244             This is free software, licensed under:
245              
246             The Artistic License 2.0 (GPL Compatible)
247              
248             =head1 SEE ALSO
249              
250             L, L, L,
251             L