File Coverage

blib/lib/UAV/Pilot/EasyEvent.pm
Criterion Covered Total %
statement 59 59 100.0
branch 8 10 80.0
condition 2 2 100.0
subroutine 11 11 100.0
pod 2 4 50.0
total 82 86 95.3


line stmt bran cond sub pod time code
1             # Copyright (c) 2015 Timm Murray
2             # All rights reserved.
3             #
4             # Redistribution and use in source and binary forms, with or without
5             # modification, are permitted provided that the following conditions are met:
6             #
7             # * Redistributions of source code must retain the above copyright notice,
8             # this list of conditions and the following disclaimer.
9             # * Redistributions in binary form must reproduce the above copyright
10             # notice, this list of conditions and the following disclaimer in the
11             # documentation and/or other materials provided with the distribution.
12             #
13             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
17             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23             # POSSIBILITY OF SUCH DAMAGE.
24             package UAV::Pilot::EasyEvent;
25             $UAV::Pilot::EasyEvent::VERSION = '1.3';
26 2     2   22320 use v5.14;
  2         8  
27 2     2   818 use Moose;
  2         474543  
  2         16  
28 2     2   16249 use namespace::autoclean;
  2         9208  
  2         16  
29              
30             #with 'MooseX::Clone';
31              
32              
33             use constant {
34 2         1500 UNITS_MILLISECOND => 0,
35 2     2   312 };
  2         5  
36              
37             has 'condvar' => (
38             is => 'ro',
39             isa => 'AnyEvent::CondVar',
40             );
41             has '_timers' => (
42             traits => [ 'Array' ],
43             is => 'ro',
44             isa => 'ArrayRef[HashRef[Any]]',
45             default => sub { [] },
46             handles => {
47             _add_timer => 'push',
48             },
49             );
50             has '_events' => (
51             traits => [ 'Hash' ],
52             is => 'ro',
53             isa => 'HashRef[ArrayRef[HashRef[Item]]]',
54             default => sub { {} },
55             handles => {
56             '_set_event_callbacks' => 'set',
57             '_event_type_exists' => 'exists',
58             '_get_event_callbacks' => 'get',
59             },
60             );
61              
62              
63             sub add_timer
64             {
65 4     4 1 451 my ($self, $args) = @_;
66 4         6 my $duration = $$args{duration};
67 4         5 my $duration_units = $$args{duration_units};
68 4         7 my $callback = $$args{cb};
69              
70 4         8 my $true_time = $self->_convert_time_units( $duration, $duration_units );
71 4         136 my $new_self = ref($self)->new({
72             condvar => $self->condvar,
73             });
74              
75 4         180 $self->_add_timer({
76             time => $true_time,
77             cb => $callback,
78             child_events => $new_self,
79             });
80              
81 4         10 return $new_self;
82             }
83              
84             sub add_event
85             {
86 4     4 0 56 my ($self, $name, $callback, $is_oneoff) = @_;
87 4   100     14 $is_oneoff //= 0;
88              
89 4         5 my @callbacks;
90 4 100       177 if( $self->_event_type_exists( $name ) ) {
91 2         3 @callbacks = @{ $self->_get_event_callbacks( $name ) };
  2         86  
92             }
93             else {
94 2         5 @callbacks = ();
95             }
96              
97 4         12 push @callbacks, {
98             callback => $callback,
99             is_one_off => $is_oneoff,
100             };
101 4         170 $self->_set_event_callbacks( $name => \@callbacks );
102              
103 4         9 return 1;
104             }
105              
106             sub send_event
107             {
108 3     3 0 825227 my ($self, $name, @args) = @_;
109 3         263 my $callbacks = $self->_get_event_callbacks( $name );
110 3 50       14 return 1 unless defined $callbacks;
111 3         18 my @callbacks = (@$callbacks);
112 3         7 my $is_callbacks_changed = 0;
113              
114 3         13 foreach my $i (0 .. $#callbacks) {
115             # Always modify the *original* arrayref $callbacks here, not the
116             # copy @callbacks. If we splice out a one-off, @callbacks will be
117             # changed and the index will be off.
118 6         16 my $cb = $callbacks->[$i]{callback};
119 6         16 my $is_one_off = $callbacks->[$i]{is_one_off};
120 6         27 $cb->(@args);
121              
122 6 100       3366 if( $is_one_off ) {
123 1         6 splice @callbacks, $i, 1;
124 1         6 $is_callbacks_changed = 1;
125             }
126             }
127              
128 3 100       92 $self->_set_event_callbacks( $name => \@callbacks) if $is_callbacks_changed;
129 3         18 return 1;
130             }
131              
132             sub init_event_loop
133             {
134 5     5 1 19 my ($self) = @_;
135              
136 5         14 foreach my $timer_def (@{ $self->_timers }) {
  5         352  
137 4         29 my $timer; $timer = AnyEvent->timer(
138             after => $timer_def->{time},
139             cb => sub {
140 4     4   171259 $timer_def->{cb}->();
141 4         56 $timer_def->{child_events}->init_event_loop;
142 4         30 $timer;
143             },
144 4         51 );
145             }
146              
147 5         65 return 1;
148             }
149              
150              
151             sub _convert_time_units
152             {
153 4     4   7 my ($self, $time, $unit) = @_;
154              
155 4 50       13 if( $self->UNITS_MILLISECOND == $unit ) {
156 4         7 $time /= 1000;
157             }
158              
159 4         6 return $time;
160             }
161              
162              
163 2     2   13 no Moose;
  2         6  
  2         16  
164             __PACKAGE__->meta->make_immutable;
165             1;
166             __END__
167              
168              
169             =head1 NAME
170              
171             UAV::Pilot::EasyEvent
172              
173             =head1 SYNOPSIS
174              
175             my $cv = AnyEvent->condvar;
176             my $event = UAV::Pilot::EasyEvent->new({
177             condvar => $cv,
178             });
179            
180             my @events;
181             my $event2 = $event->add_timer({
182             duration => 100,
183             duration_units => $event->UNITS_MILLISECOND,
184             cb => sub {
185             push @events => 'First event',
186             },
187             })->add_timer({
188             duration => 10,
189             duration_units => $event->UNITS_MILLISECOND,
190             cb => sub {
191             push @events => 'Second event',
192             },
193             });
194            
195             $event2->add_timer({
196             duration => 50,
197             duration_units => $event->UNITS_MILLISECOND,
198             cb => sub {
199             push @events => 'Fourth event',
200             $cv->send;
201             },
202             });
203             $event2->add_timer({
204             duration => 10,
205             duration_units => $event->UNITS_MILLISECOND,
206             cb => sub {
207             push @events => 'Third event',
208             },
209             });
210            
211            
212             $event->init_event_loop;
213             $cv->recv;
214            
215             # After time passes, prints:
216             # First event
217             # Second event
218             # Third event
219             # Fourth event
220             #
221             say $_ for @events;
222              
223             =head1 DESCRIPTION
224              
225             C<AnyEvent> is the standard event framework used for C<UAV::Pilot>. However, its
226             interface isn't convenient for some of the typical things done for UAV piloting. For
227             instance, to put the code into plain English, we might want to say:
228              
229             Takeoff, wait 5 seconds, then pitch forward for 2 seconds, then pitch backwards
230             for 2 seconds, then land
231              
232             In the usual C<AnyEvent> interface, this requires building the timers inside the callbacks
233             of other timers, which leads to several levels of indentation. C<UAV::Pilot::EasyEvent>
234             simplifies the handling and syntax of this kind of event workflow.
235              
236             =head1 METHODS
237              
238             =head2 new
239              
240             new({
241             condvar => $cv,
242             })
243              
244             Constructor. The C<condvar> argument should be an C<AnyEvent::CondVar>.
245              
246             =head2 add_timer
247              
248             add_timer({
249             duration => 100,
250             duration_units => $event->UNITS_MILLISECOND,
251             cb => sub { ... },
252             })
253              
254             Add a timer to run in the event loop. It will run after C<duration> units of time, with
255             the units specified by C<duration_units>. The C<cb> parameter is a reference to a
256             subroutine to use as a callback.
257              
258             Returns a child C<EasyEvent> object. When the timer above has finished, any timers on
259             child objects will be setup for execution. This makes it easy to chain timers to run
260             after each other.
261              
262             =head2 init_event_loop
263              
264             This method must be called after running a series of C<add_timer()> calls. You only need
265             to call this on the root object, not the children.
266              
267             You must call C<recv> on the C<condvar> yourself.
268              
269             =head1 add_event
270              
271             add_event( 'foo', sub {...}, 0 )
272              
273             Add a subref that will be called when the named event is fired off. The
274             first parameter is the name of the event, and the second is the subref.
275              
276             The third is optional, and specifies if the call will be a "one-off" or not.
277             If it's a one-off, then after the first call to the sub, it will be removed
278             from list of callbacks. Defaults to false.
279              
280             The callback will receive the arguments that were passed to C<send_event()>
281             when the event is triggered.
282              
283             =head1 send_event
284              
285             send_event( 'foo', @args )
286              
287             Trigger an event with the given name. The first arg is the name of the event.
288             All subsequent args will be passed to the callbacks attached to that event
289             name.
290              
291             =cut