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) ; } ;