1# You may distribute under the terms of either the GNU General Public License 2# or the Artistic License (the same terms as Perl itself) 3# 4# (C) Paul Evans, 2009-2015 -- leonerd@leonerd.org.uk 5 6package IO::Async::Timer::Periodic; 7 8use strict; 9use warnings; 10use base qw( IO::Async::Timer ); 11 12our $VERSION = '0.800'; 13 14use Carp; 15 16=head1 NAME 17 18C<IO::Async::Timer::Periodic> - event callback at regular intervals 19 20=head1 SYNOPSIS 21 22 use IO::Async::Timer::Periodic; 23 24 use IO::Async::Loop; 25 my $loop = IO::Async::Loop->new; 26 27 my $timer = IO::Async::Timer::Periodic->new( 28 interval => 60, 29 30 on_tick => sub { 31 print "You've had a minute\n"; 32 }, 33 ); 34 35 $timer->start; 36 37 $loop->add( $timer ); 38 39 $loop->run; 40 41=head1 DESCRIPTION 42 43This subclass of L<IO::Async::Timer> implements repeating events at regular 44clock intervals. The timing may or may not be subject to how long it takes the 45callback to execute. Iterations may be rescheduled runs at fixed regular 46intervals beginning at the time the timer was started, or by a fixed delay 47after the previous code has finished executing. 48 49For a C<Timer> object that only runs a callback once, after a given delay, see 50instead L<IO::Async::Timer::Countdown>. A Countdown timer can also be used to 51create repeating events that fire at a fixed delay after the previous event 52has finished processing. See als the examples in 53C<IO::Async::Timer::Countdown>. 54 55=cut 56 57=head1 EVENTS 58 59The following events are invoked, either using subclass methods or CODE 60references in parameters: 61 62=head2 on_tick 63 64Invoked on each interval of the timer. 65 66=cut 67 68=head1 PARAMETERS 69 70The following named parameters may be passed to C<new> or C<configure>: 71 72=head2 on_tick => CODE 73 74CODE reference for the C<on_tick> event. 75 76=head2 interval => NUM 77 78The interval in seconds between invocations of the callback or method. Cannot 79be changed if the timer is running. 80 81=head2 first_interval => NUM 82 83Optional. If defined, the interval in seconds after calling the C<start> 84method before the first invocation of the callback or method. Thereafter, the 85regular C<interval> will be used. If not supplied, the first interval will be 86the same as the others. 87 88Even if this value is zero, the first invocation will be made asynchronously, 89by the containing C<Loop> object, and not synchronously by the C<start> method 90itself. 91 92=head2 reschedule => STRING 93 94Optional. Must be one of C<hard>, C<skip> or C<drift>. Defines the algorithm 95used to reschedule the next invocation. 96 97C<hard> schedules each iteration at the fixed interval from the previous 98iteration's schedule time, ensuring a regular repeating event. 99 100C<skip> schedules similarly to C<hard>, but skips over times that have already 101passed. This matters if the duration is particularly short and there's a 102possibility that times may be missed, or if the entire process is stopped and 103resumed by C<SIGSTOP> or similar. 104 105C<drift> schedules each iteration at the fixed interval from the time that the 106previous iteration's event handler returns. This allows it to slowly drift over 107time and become desynchronised with other events of the same interval or 108multiples/fractions of it. 109 110Once constructed, the timer object will need to be added to the C<Loop> before 111it will work. It will also need to be started by the C<start> method. 112 113=cut 114 115sub _init 116{ 117 my $self = shift; 118 $self->SUPER::_init( @_ ); 119 120 $self->{reschedule} = "hard"; 121} 122 123sub configure 124{ 125 my $self = shift; 126 my %params = @_; 127 128 if( exists $params{on_tick} ) { 129 my $on_tick = delete $params{on_tick}; 130 ref $on_tick or croak "Expected 'on_tick' as a reference"; 131 132 $self->{on_tick} = $on_tick; 133 undef $self->{cb}; # Will be lazily constructed when needed 134 } 135 136 if( exists $params{interval} ) { 137 $self->is_running and croak "Cannot configure 'interval' of a running timer\n"; 138 139 my $interval = delete $params{interval}; 140 $interval > 0 or croak "Expected a 'interval' as a positive number"; 141 142 $self->{interval} = $interval; 143 } 144 145 if( exists $params{first_interval} ) { 146 $self->is_running and croak "Cannot configure 'first_interval' of a running timer\n"; 147 148 my $first_interval = delete $params{first_interval}; 149 $first_interval >= 0 or croak "Expected a 'first_interval' as a non-negative number"; 150 151 $self->{first_interval} = $first_interval; 152 } 153 154 if( exists $params{reschedule} ) { 155 my $resched = delete $params{reschedule} || "hard"; 156 grep { $_ eq $resched } qw( hard skip drift ) or 157 croak "Expected 'reschedule' to be one of hard, skip, drift"; 158 159 $self->{reschedule} = $resched; 160 } 161 162 unless( $self->can_event( 'on_tick' ) ) { 163 croak 'Expected either a on_tick callback or an ->on_tick method'; 164 } 165 166 $self->SUPER::configure( %params ); 167} 168 169sub _reschedule 170{ 171 my $self = shift; 172 173 my $now = $self->loop->time; 174 my $resched = $self->{reschedule}; 175 176 my $next_interval = $self->{is_first} && defined $self->{first_interval} 177 ? $self->{first_interval} : $self->{interval}; 178 delete $self->{is_first}; 179 180 if( !defined $self->{next_time} ) { 181 $self->{next_time} = $now + $next_interval; 182 } 183 elsif( $resched eq "hard" ) { 184 $self->{next_time} += $next_interval; 185 } 186 elsif( $resched eq "skip" ) { 187 # How many ticks are needed? 188 my $ticks = POSIX::ceil( ( $now - $self->{next_time} ) / $next_interval ); 189 # $self->{last_ticks} = $ticks; 190 $self->{next_time} += $next_interval * $ticks; 191 } 192 elsif( $resched eq "drift" ) { 193 $self->{next_time} = $now + $next_interval; 194 } 195 196 $self->SUPER::start; 197} 198 199sub start 200{ 201 my $self = shift; 202 203 $self->{is_first} = 1; 204 205 # Only actually define a time if we've got a loop; otherwise it'll just 206 # become start-pending. We'll calculate it properly when it gets added to 207 # the Loop 208 if( $self->loop ) { 209 $self->_reschedule; 210 } 211 else { 212 $self->SUPER::start; 213 } 214} 215 216sub stop 217{ 218 my $self = shift; 219 $self->SUPER::stop; 220 221 undef $self->{next_time}; 222} 223 224sub _make_cb 225{ 226 my $self = shift; 227 228 return $self->_capture_weakself( sub { 229 my $self = shift or return; 230 231 undef $self->{id}; 232 233 my $ok = eval { $self->invoke_event( on_tick => ); 1 } or 234 my $e = $@; 235 236 # detect ->stop 237 $self->_reschedule if defined $self->{next_time}; 238 239 die $e if !$ok; 240 } ); 241} 242 243sub _make_enqueueargs 244{ 245 my $self = shift; 246 247 return at => $self->{next_time}; 248} 249 250=head1 AUTHOR 251 252Paul Evans <leonerd@leonerd.org.uk> 253 254=cut 255 2560x55AA; 257