File Coverage

blib/lib/UAV/Pilot/EasyEvent.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package UAV::Pilot::EasyEvent;
2 2     2   35266 use v5.14;
  2         8  
  2         80  
3 2     2   2746 use Moose;
  0            
  0            
4             use namespace::autoclean;
5              
6             #with 'MooseX::Clone';
7              
8              
9             use constant {
10             UNITS_MILLISECOND => 0,
11             };
12              
13             has 'condvar' => (
14             is => 'ro',
15             isa => 'AnyEvent::CondVar',
16             );
17             has '_timers' => (
18             traits => [ 'Array' ],
19             is => 'ro',
20             isa => 'ArrayRef[HashRef[Any]]',
21             default => sub { [] },
22             handles => {
23             _add_timer => 'push',
24             },
25             );
26             has '_events' => (
27             traits => [ 'Hash' ],
28             is => 'ro',
29             isa => 'HashRef[ArrayRef[HashRef[Item]]]',
30             default => sub { {} },
31             handles => {
32             '_set_event_callbacks' => 'set',
33             '_event_type_exists' => 'exists',
34             '_get_event_callbacks' => 'get',
35             },
36             );
37              
38              
39             sub add_timer
40             {
41             my ($self, $args) = @_;
42             my $duration = $$args{duration};
43             my $duration_units = $$args{duration_units};
44             my $callback = $$args{cb};
45              
46             my $true_time = $self->_convert_time_units( $duration, $duration_units );
47             my $new_self = ref($self)->new({
48             condvar => $self->condvar,
49             });
50              
51             $self->_add_timer({
52             time => $true_time,
53             cb => $callback,
54             child_events => $new_self,
55             });
56              
57             return $new_self;
58             }
59              
60             sub add_event
61             {
62             my ($self, $name, $callback, $is_oneoff) = @_;
63             $is_oneoff //= 0;
64              
65             my @callbacks;
66             if( $self->_event_type_exists( $name ) ) {
67             @callbacks = @{ $self->_get_event_callbacks( $name ) };
68             }
69             else {
70             @callbacks = ();
71             }
72              
73             push @callbacks, {
74             callback => $callback,
75             is_one_off => $is_oneoff,
76             };
77             $self->_set_event_callbacks( $name => \@callbacks );
78              
79             return 1;
80             }
81              
82             sub send_event
83             {
84             my ($self, $name, @args) = @_;
85             my $callbacks = $self->_get_event_callbacks( $name );
86             return 1 unless defined $callbacks;
87             my @callbacks = (@$callbacks);
88             my $is_callbacks_changed = 0;
89              
90             foreach my $i (0 .. $#callbacks) {
91             # Always modify the *original* arrayref $callbacks here, not the
92             # copy @callbacks. If we splice out a one-off, @callbacks will be
93             # changed and the index will be off.
94             my $cb = $callbacks->[$i]{callback};
95             my $is_one_off = $callbacks->[$i]{is_one_off};
96             $cb->(@args);
97              
98             if( $is_one_off ) {
99             splice @callbacks, $i, 1;
100             $is_callbacks_changed = 1;
101             }
102             }
103              
104             $self->_set_event_callbacks( $name => \@callbacks) if $is_callbacks_changed;
105             return 1;
106             }
107              
108             sub init_event_loop
109             {
110             my ($self) = @_;
111              
112             foreach my $timer_def (@{ $self->_timers }) {
113             my $timer; $timer = AnyEvent->timer(
114             after => $timer_def->{time},
115             cb => sub {
116             $timer_def->{cb}->();
117             $timer_def->{child_events}->init_event_loop;
118             $timer;
119             },
120             );
121             }
122              
123             return 1;
124             }
125              
126              
127             sub _convert_time_units
128             {
129             my ($self, $time, $unit) = @_;
130              
131             if( $self->UNITS_MILLISECOND == $unit ) {
132             $time /= 1000;
133             }
134              
135             return $time;
136             }
137              
138              
139             no Moose;
140             __PACKAGE__->meta->make_immutable;
141             1;
142             __END__
143              
144              
145             =head1 NAME
146              
147             UAV::Pilot::EasyEvent
148              
149             =head1 SYNOPSIS
150              
151             my $cv = AnyEvent->condvar;
152             my $event = UAV::Pilot::EasyEvent->new({
153             condvar => $cv,
154             });
155            
156             my @events;
157             my $event2 = $event->add_timer({
158             duration => 100,
159             duration_units => $event->UNITS_MILLISECOND,
160             cb => sub {
161             push @events => 'First event',
162             },
163             })->add_timer({
164             duration => 10,
165             duration_units => $event->UNITS_MILLISECOND,
166             cb => sub {
167             push @events => 'Second event',
168             },
169             });
170            
171             $event2->add_timer({
172             duration => 50,
173             duration_units => $event->UNITS_MILLISECOND,
174             cb => sub {
175             push @events => 'Fourth event',
176             $cv->send;
177             },
178             });
179             $event2->add_timer({
180             duration => 10,
181             duration_units => $event->UNITS_MILLISECOND,
182             cb => sub {
183             push @events => 'Third event',
184             },
185             });
186            
187            
188             $event->init_event_loop;
189             $cv->recv;
190            
191             # After time passes, prints:
192             # First event
193             # Second event
194             # Third event
195             # Fourth event
196             #
197             say $_ for @events;
198              
199             =head1 DESCRIPTION
200              
201             C<AnyEvent> is the standard event framework used for C<UAV::Pilot>. However, its
202             interface isn't convenient for some of the typical things done for UAV piloting. For
203             instance, to put the code into plain English, we might want to say:
204              
205             Takeoff, wait 5 seconds, then pitch forward for 2 seconds, then pitch backwards
206             for 2 seconds, then land
207              
208             In the usual C<AnyEvent> interface, this requires building the timers inside the callbacks
209             of other timers, which leads to several levels of indentation. C<UAV::Pilot::EasyEvent>
210             simplifies the handling and syntax of this kind of event workflow.
211              
212             =head1 METHODS
213              
214             =head2 new
215              
216             new({
217             condvar => $cv,
218             })
219              
220             Constructor. The C<condvar> argument should be an C<AnyEvent::CondVar>.
221              
222             =head2 add_timer
223              
224             add_timer({
225             duration => 100,
226             duration_units => $event->UNITS_MILLISECOND,
227             cb => sub { ... },
228             })
229              
230             Add a timer to run in the event loop. It will run after C<duration> units of time, with
231             the units specified by C<duration_units>. The C<cb> parameter is a reference to a
232             subroutine to use as a callback.
233              
234             Returns a child C<EasyEvent> object. When the timer above has finished, any timers on
235             child objects will be setup for execution. This makes it easy to chain timers to run
236             after each other.
237              
238             =head2 init_event_loop
239              
240             This method must be called after running a series of C<add_timer()> calls. You only need
241             to call this on the root object, not the children.
242              
243             You must call C<recv> on the C<condvar> yourself.
244              
245             =head1 add_event
246              
247             add_event( 'foo', sub {...}, 0 )
248              
249             Add a subref that will be called when the named event is fired off. The
250             first parameter is the name of the event, and the second is the subref.
251              
252             The third is optional, and specifies if the call will be a "one-off" or not.
253             If it's a one-off, then after the first call to the sub, it will be removed
254             from list of callbacks. Defaults to false.
255              
256             The callback will receive the arguments that were passed to C<send_event()>
257             when the event is triggered.
258              
259             =head1 send_event
260              
261             send_event( 'foo', @args )
262              
263             Trigger an event with the given name. The first arg is the name of the event.
264             All subsequent args will be passed to the callbacks attached to that event
265             name.
266              
267             =cut