while (1) {
my $timeout = 5 ;
while (@events) {
$timeout = $events[-1]->[0] - Time::HiRes::time() ;
last if ($timeout > 0.001) ;
my ($time, $rsub, @args) = @{pop @events} ;
$rsub->(@args) ;
$timeout = 5 ;
} ;
# use IO::Select recommended, but this is just for illustration...
select(my $rout = $rin, my $wout = $win, my $eout = $ein, $timeout) ;
# .....
} ;
# add_event($delay, $rsub, @args)
#
# schedule call of $rsub->(@args) in $delay (float) seconds in the future
sub add_event {
my ($delay, $rsub, @args) = @_ ;
my $when = $delay + Time::HiRes::time() ;
push @events, [$when, $rsub, @args] ;
if ((@events > 1) && ($events[-2]->[0] < $when)) {
@events = sort { $b->[0] <=> $a->[0] } @events ;
} ;
} ;
####
sub events {
while (@events) {
my $timeout = $events[-1]->[0] - Time::HiRes::time() ;
return $timeout if ($timeout > 0.001) ;
my ($time, $rsub, @args) = @{pop @events} ;
$rsub->(@args) ;
} ;
return 5 ;
} ;
####
use Time::HiRes qw(time ualarm) ;
use constant MS => 1_000_000 ;
# Event Handling
#
# When $in_event == 0, we may set the alarm.
# When $in_event == 0, $in_event_min == 0.
#
# So $in_event != 0 implies that the alarm is not running, and prevents
# add_event() from setting the alarm.
#
# When the signal handler is entered $in_event == $in_event_min == 0 (for
# only under these circumstances will the alarm be set. Before calling an
# event subroutine, $in_event = $in_event_min = 1 -- this prevents two
# things:
#
# (a) add_event() will not set the alarm -- which avoids the
# possibility of recursing through the event handler;
#
# (b) ei_events() will not reduce $in_event below 1, and hence will not
# set the alarm.
#
# To disable events, if $in_event == 0 we clear the alarm, then $in_event is
# incremented. (So can nest disables.)
#
# To re-enable events, $in_event is decremented (unless it is already equal
# to $in_event_min). If it is decremented to zero and there is at least one
# event in the queue, then the alarm is set. Note that:
#
# (a) if $in_event == $in_event_min == 0, it is a mistake to call
# ei_events(), but if there is an event outstanding the alarm will
# already be set, and we can simply do nothing.
#
# (b) if $in_event == $in_event_min == 1, it is also a mistake to call
# ei_events(), and we are inside the event handler, so we neither want
# to reduce $in_event nor do we want to set the alarm.
#
# On exit from the signal handler we set $in_event = $in_event_min = 0. So,
# any unbalanced di_events in an event subroutine are discarded.
my @events = () ; # Event queue
my $in_event = 0 ; # non-zero => events disabled
my $in_event_min = 0 ; # minimum allowed $in_event
# event_signal: Alarm Signal handler
local $SIG{ALRM} = \&event_signal ;
sub event_signal {
while (@events) {
my $timeout = $events[-1]->[0] - time() ;
if ($timeout > 0.001) {
ualarm($timeout * MS) ; # Restart the clock...
last ; # ... and we're done.
} ;
$in_event = $in_event_min = 1 ;
my ($time, $rsub, @args) = @{pop @events} ;
$rsub->(@args) ;
$in_event = $in_event_min = 0 ;
} ;
} ;
# add_event($delay, $rsub, @args)
#
# schedule call of $rsub->(@args) in $delay (float) seconds in the future
#
# can add events during event handling.
sub add_event {
my ($delay, $rsub, @args) = @_ ;
my $time = time() ;
my $when = $delay + $time ;
ualarm(0) unless $in_event ; # Stop the clock while we fiddle
# unless already stopped
push @events, [$when, $rsub, @args] ;
if ((@events > 1) && ($events[-2]->[0] < $when)) {
@events = sort { $b->[0] <=> $a->[0] } @events ;
$delay = $events[-1]->[0] - $time ;
} ;
$delay = 0.0001 if $delay < 0.0001 ;
ualarm($delay * MS) unless $in_event ; # Start the clock again
# unless disabled (or in event)
} ;
# di_events: disable event handling for the time being
#
# Note that can disable event handling 'n' times, and must enable events
# again 'n' times before events actually start again.
#
# It is only necessary to ualarm(0) if $in_event != 0. However, it is
# essential to do the ualarm(0) before incrementing $in_event, because if
# the alarm were to go off the signal handler will set $in_event to zero !
sub di_events {
ualarm(0) unless $in_event ;
$in_event++ ;
} ;
# ei_events: enable event handling again (reduce disable count)
#
# Note that $in_event_min is set to 1 while in the signal handler, and 0
# at other times. We use this to prevent excess calls to ei_events() from
# enabling events during event handling and from pushing the $in_event
# negative.
sub ei_events {
return if ($in_event == $in_event_min) || --$in_event || (@events == 0) ;
my $delay = $events[-1]->[0] - time() ;
$delay = 0.0001 if $delay < 0.0001 ;
ualarm($delay * MS) ;
} ;