| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # $Id: Poll.pm,v 1.7 2009/11/30 13:25:06 dk Exp $ | 
| 2 |  |  |  |  |  |  | package IO::Lambda::Poll; | 
| 3 | 2 |  |  |  |  | 266 | use vars qw( | 
| 4 |  |  |  |  |  |  | @ISA @EXPORT_OK %EXPORT_TAGS | 
| 5 |  |  |  |  |  |  | $DEBUG @RECORDS @TIMER $TIMER_ACTIVE $MASTER | 
| 6 | 2 |  |  | 2 |  | 1109 | ); | 
|  | 2 |  |  |  |  | 3 |  | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | $DEBUG = $IO::Lambda::DEBUG{poll} || 0; | 
| 9 |  |  |  |  |  |  | @ISA = qw(Exporter); | 
| 10 |  |  |  |  |  |  | @EXPORT_OK  = qw(poll_event poll_cancel poller); | 
| 11 |  |  |  |  |  |  | %EXPORT_TAGS = ( all => \@EXPORT_OK); | 
| 12 |  |  |  |  |  |  |  | 
| 13 | 2 |  |  | 2 |  | 11 | use strict; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 54 |  | 
| 14 | 2 |  |  | 2 |  | 8 | use warnings; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 77 |  | 
| 15 | 2 |  |  | 2 |  | 9 | use Time::HiRes qw(time); | 
|  | 2 |  |  |  |  | 24 |  | 
|  | 2 |  |  |  |  | 15 |  | 
| 16 | 2 |  |  | 2 |  | 223 | use IO::Lambda qw(:all :dev set_frame get_frame); | 
|  | 2 |  |  |  |  | 872 |  | 
|  | 2 |  |  |  |  | 2786 |  | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | $MASTER = bless {}, __PACKAGE__; | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | # register yield handler | 
| 21 |  |  |  |  |  |  | IO::Lambda::add_loop($MASTER); | 
| 22 |  |  |  |  |  |  | END { | 
| 23 | 2 |  |  | 2 |  | 782 | @RECORDS = (); | 
| 24 | 2 |  |  |  |  | 7 | IO::Lambda::remove_loop($MASTER); | 
| 25 |  |  |  |  |  |  | }; | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | # There'll also be a single timer as we need timeouts | 
| 28 |  |  |  |  |  |  | $TIMER[WATCH_OBJ] = bless {}, "IO::Lambda::Poll::Timer"; | 
| 29 |  |  |  |  |  |  | sub IO::Lambda::Poll::Timer::io_handler | 
| 30 |  |  |  |  |  |  | { | 
| 31 | 2 | 50 |  | 2 |  | 6 | warn "poll.timer < expired\n" if $DEBUG; | 
| 32 | 2 |  |  |  |  | 10 | $TIMER_ACTIVE = 0; | 
| 33 |  |  |  |  |  |  | } | 
| 34 |  |  |  |  |  |  |  | 
| 35 | 18 |  |  | 18 | 0 | 69 | sub empty { 0 == @RECORDS } | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | sub remove | 
| 38 |  |  |  |  |  |  | { | 
| 39 | 3 |  |  | 3 | 0 | 4 | my $lambda = $_[1]; | 
| 40 | 3 |  |  |  |  | 4 | my $n = @RECORDS; | 
| 41 | 3 |  |  |  |  | 6 | @RECORDS = grep { $_-> {this} ne $lambda } @RECORDS; | 
|  | 2 |  |  |  |  | 12 |  | 
| 42 | 3 | 100 |  |  |  | 11 | return if $n == @RECORDS; | 
| 43 | 1 | 50 |  |  |  | 5 | warn "poll.remove $lambda\n" if $DEBUG; | 
| 44 | 1 |  |  |  |  | 3 | reset_timer(); | 
| 45 |  |  |  |  |  |  | } | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | sub yield | 
| 48 |  |  |  |  |  |  | { | 
| 49 | 16 | 50 |  | 16 | 0 | 42 | warn "poll.yield\n" if $DEBUG > 1; | 
| 50 | 16 |  |  |  |  | 46 | my $time = time; | 
| 51 |  |  |  |  |  |  |  | 
| 52 | 16 |  |  |  |  | 20 | my @new; | 
| 53 | 16 |  |  |  |  | 48 | my @frame = get_frame; | 
| 54 | 16 |  |  |  |  | 35 | for my $rec ( @RECORDS) { | 
| 55 |  |  |  |  |  |  | my ( $ok, @result) = $rec-> {poller}-> ( | 
| 56 |  |  |  |  |  |  | defined($rec->{deadline}) && $rec->{deadline} <= $time, | 
| 57 | 16 |  | 100 |  |  | 88 | @{ $rec-> {param}} | 
|  | 16 |  |  |  |  | 64 |  | 
| 58 |  |  |  |  |  |  | ); | 
| 59 | 16 | 100 |  |  |  | 43 | unless ($ok) { | 
| 60 | 10 |  |  |  |  | 18 | push @new, $rec; | 
| 61 | 10 |  |  |  |  | 24 | next; | 
| 62 |  |  |  |  |  |  | } | 
| 63 | 6 | 50 |  |  |  | 23 | warn "poll.resolve($rec)\n" if $DEBUG; | 
| 64 | 6 |  |  |  |  | 14 | my $this = $rec-> {this}; | 
| 65 | 6 |  |  |  |  | 17 | $this-> set_frame($rec-> {method}, $rec->{callback}, @{ $rec->{context} }); | 
|  | 6 |  |  |  |  | 29 |  | 
| 66 | 6 |  |  |  |  | 28 | $this-> callout( $rec-> {callback}, @result); | 
| 67 | 6 |  |  |  |  | 26 | $this-> resolve( $rec-> {bind}); | 
| 68 |  |  |  |  |  |  | } | 
| 69 | 16 |  |  |  |  | 51 | set_frame(@frame); | 
| 70 | 16 | 100 |  |  |  | 54 | return if @RECORDS == @new; | 
| 71 |  |  |  |  |  |  |  | 
| 72 | 6 |  |  |  |  | 46 | @RECORDS = @new; | 
| 73 | 6 |  |  |  |  | 18 | reset_timer(); | 
| 74 |  |  |  |  |  |  | } | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | sub reset_timer | 
| 77 |  |  |  |  |  |  | { | 
| 78 | 14 |  |  | 14 | 0 | 21 | my ( $expires, $frequency); | 
| 79 | 14 |  |  |  |  | 30 | for my $rec (@RECORDS) { | 
| 80 | 7 |  |  |  |  | 12 | my ($f,$d) = @{$rec}{qw(frequency deadline)}; | 
|  | 7 |  |  |  |  | 19 |  | 
| 81 | 7 | 50 | 0 |  |  | 34 | $frequency = $f if not defined($frequency) or (defined($f) and $frequency > $f); | 
|  |  |  | 33 |  |  |  |  | 
| 82 | 7 | 50 | 0 |  |  | 38 | $expires   = $d if not defined($expires)   or (defined($d) and $expires   > $d); | 
|  |  |  | 33 |  |  |  |  | 
| 83 |  |  |  |  |  |  | } | 
| 84 |  |  |  |  |  |  |  | 
| 85 | 14 | 100 |  |  |  | 36 | if ( defined $frequency) { | 
| 86 | 4 |  |  |  |  | 14 | $frequency += time; | 
| 87 | 4 | 50 |  |  |  | 10 | if ( defined $expires) { | 
|  |  | 0 |  |  |  |  |  | 
| 88 | 4 | 100 |  |  |  | 14 | $expires = $frequency if $expires > $frequency; | 
| 89 |  |  |  |  |  |  | } elsif ( @RECORDS) { | 
| 90 | 0 |  |  |  |  | 0 | $expires = $frequency; | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  | } | 
| 93 |  |  |  |  |  |  |  | 
| 94 | 14 | 100 |  |  |  | 54 | if ( defined $expires) { | 
|  |  | 100 |  |  |  |  |  | 
| 95 | 4 | 50 |  |  |  | 14 | if ( $TIMER_ACTIVE) { | 
| 96 | 0 | 0 |  |  |  | 0 | if ( abs( $expires - $TIMER[WATCH_DEADLINE]) > 0.001) { | 
| 97 |  |  |  |  |  |  | # restart the active timer | 
| 98 | 0 | 0 |  |  |  | 0 | warn "poll.timer > restart $expires/$TIMER[WATCH_DEADLINE]\n" | 
| 99 |  |  |  |  |  |  | if $DEBUG; | 
| 100 | 0 |  |  |  |  | 0 | $IO::Lambda::LOOP-> remove_event( \@TIMER); | 
| 101 | 0 |  |  |  |  | 0 | $TIMER[WATCH_DEADLINE] = $expires; | 
| 102 | 0 |  |  |  |  | 0 | $IO::Lambda::LOOP-> after( \@TIMER); | 
| 103 |  |  |  |  |  |  | } | 
| 104 |  |  |  |  |  |  | # else, same timeout, on already active timer - do nothing | 
| 105 |  |  |  |  |  |  | } else { | 
| 106 |  |  |  |  |  |  | # resubmit | 
| 107 | 4 | 50 |  |  |  | 16 | warn "poll.timer > submit $expires\n" if $DEBUG; | 
| 108 | 4 |  |  |  |  | 7 | $TIMER[WATCH_DEADLINE] = $expires; | 
| 109 | 4 |  |  |  |  | 19 | $IO::Lambda::LOOP-> after( \@TIMER); | 
| 110 | 4 |  |  |  |  | 7 | $TIMER_ACTIVE = 1; | 
| 111 |  |  |  |  |  |  | } | 
| 112 |  |  |  |  |  |  | } elsif ( $TIMER_ACTIVE) { | 
| 113 | 2 | 50 |  |  |  | 8 | warn "poll.timer > stop\n" if $DEBUG; | 
| 114 |  |  |  |  |  |  | # stop timer | 
| 115 | 2 |  |  |  |  | 14 | $IO::Lambda::LOOP-> remove_event( \@TIMER); | 
| 116 | 2 |  |  |  |  | 7 | $TIMER_ACTIVE = 0; | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  | } | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | sub poll_event | 
| 121 |  |  |  |  |  |  | { | 
| 122 | 7 |  |  | 7 | 1 | 28 | my ( $cb, $method, $poller, $deadline, $frequency, @param ) = @_; | 
| 123 |  |  |  |  |  |  |  | 
| 124 | 7 | 100 | 66 |  |  | 47 | $deadline += time if defined($deadline) and $deadline < 1_000_000_000; | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 7 |  |  |  |  | 25 | push @RECORDS, { | 
| 127 |  |  |  |  |  |  | this      => this, | 
| 128 |  |  |  |  |  |  | bind      => this-> bind, | 
| 129 |  |  |  |  |  |  | method    => $method, | 
| 130 |  |  |  |  |  |  | callback  => $cb, | 
| 131 |  |  |  |  |  |  | context   => [ context ], | 
| 132 |  |  |  |  |  |  | poller    => $poller, | 
| 133 |  |  |  |  |  |  | deadline  => $deadline, | 
| 134 |  |  |  |  |  |  | param     => \@param, | 
| 135 |  |  |  |  |  |  | frequency => $frequency, | 
| 136 |  |  |  |  |  |  | }; | 
| 137 |  |  |  |  |  |  |  | 
| 138 | 7 |  |  |  |  | 21 | reset_timer; | 
| 139 | 7 | 50 |  |  |  | 18 | warn "poll.new($RECORDS[-1]) on ", this, "\n" if $DEBUG; | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 7 |  |  |  |  | 44 | return $RECORDS[-1]; | 
| 142 |  |  |  |  |  |  | } | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | # don't call this, use lambda-> cancel_event( $record->{bind} ) | 
| 145 |  |  |  |  |  |  | sub poll_cancel | 
| 146 |  |  |  |  |  |  | { | 
| 147 | 0 |  |  | 0 | 1 | 0 | my $rec = shift; | 
| 148 | 0 |  |  |  |  | 0 | my $n = @RECORDS; | 
| 149 | 0 |  |  |  |  | 0 | @RECORDS = grep { $rec != $_ } @RECORDS; | 
|  | 0 |  |  |  |  | 0 |  | 
| 150 | 0 | 0 |  |  |  | 0 | return if $n == @RECORDS; | 
| 151 | 0 | 0 |  |  |  | 0 | warn "poll.cancel($rec)\n" if $DEBUG; | 
| 152 | 0 |  |  |  |  | 0 | reset_timer; | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | sub poll_handler | 
| 156 |  |  |  |  |  |  | { | 
| 157 | 9 |  |  | 9 | 0 | 18 | my ( $expired, $cb, @opt) = @_; | 
| 158 | 9 |  |  |  |  | 27 | my @res = $cb->(@opt); | 
| 159 | 9 | 100 |  |  |  | 39 | return 1,@res if $res[0]; | 
| 160 | 6 | 100 |  |  |  | 14 | return 1,0 if $expired; | 
| 161 | 5 |  |  |  |  | 13 | return 0; | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | sub poller(&) | 
| 165 |  |  |  |  |  |  | { | 
| 166 | 2 |  |  | 2 | 1 | 245 | my $cb = _subname poller => shift; | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | lambda { | 
| 169 | 5 |  |  | 5 |  | 15 | my %opt = @_; | 
| 170 |  |  |  |  |  |  | poll_event( | 
| 171 |  |  |  |  |  |  | undef, undef, \&poll_handler, | 
| 172 |  |  |  |  |  |  | exists($opt{timeout}) ? $opt{timeout} : $opt{deadline}, | 
| 173 |  |  |  |  |  |  | $opt{frequency}, | 
| 174 | 5 | 100 |  |  |  | 74 | $cb, %opt | 
| 175 |  |  |  |  |  |  | ); | 
| 176 |  |  |  |  |  |  | } | 
| 177 | 2 |  |  |  |  | 15 | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | 1; | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | __DATA__ |