ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf.pm
(Generate patch)

Comparing deliantra/server/lib/cf.pm (file contents):
Revision 1.144 by root, Sun Jan 7 18:01:10 2007 UTC vs.
Revision 1.158 by root, Wed Jan 10 19:52:43 2007 UTC

15use Coro::Timer; 15use Coro::Timer;
16use Coro::Signal; 16use Coro::Signal;
17use Coro::Semaphore; 17use Coro::Semaphore;
18use Coro::AIO; 18use Coro::AIO;
19 19
20use Data::Dumper;
20use Digest::MD5; 21use Digest::MD5;
21use Fcntl; 22use Fcntl;
22use IO::AIO 2.31 (); 23use IO::AIO 2.32 ();
23use YAML::Syck (); 24use YAML::Syck ();
24use Time::HiRes; 25use Time::HiRes;
25 26
26use Event; $Event::Eval = 1; # no idea why this is required, but it is 27use Event; $Event::Eval = 1; # no idea why this is required, but it is
27 28
29sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
30
28# work around bug in YAML::Syck - bad news for perl6, will it be as broken wrt. unicode? 31# work around bug in YAML::Syck - bad news for perl6, will it be as broken wrt. unicode?
29$YAML::Syck::ImplicitUnicode = 1; 32$YAML::Syck::ImplicitUnicode = 1;
30 33
31$Coro::main->prio (Coro::PRIO_MAX); # run main coroutine ("the server") with very high priority 34$Coro::main->prio (Coro::PRIO_MAX); # run main coroutine ("the server") with very high priority
32
33sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
34 35
35our %COMMAND = (); 36our %COMMAND = ();
36our %COMMAND_TIME = (); 37our %COMMAND_TIME = ();
37our %EXTCMD = (); 38our %EXTCMD = ();
38 39
53our %MAP; # all maps 54our %MAP; # all maps
54our $LINK_MAP; # the special {link} map 55our $LINK_MAP; # the special {link} map
55our $RANDOM_MAPS = cf::localdir . "/random"; 56our $RANDOM_MAPS = cf::localdir . "/random";
56our %EXT_CORO; # coroutines bound to extensions 57our %EXT_CORO; # coroutines bound to extensions
57 58
59our $WAIT_FOR_TICK; $WAIT_FOR_TICK ||= new Coro::Signal;
60our $WAIT_FOR_TICK_ONE; $WAIT_FOR_TICK_ONE ||= new Coro::Signal;
61
58binmode STDOUT; 62binmode STDOUT;
59binmode STDERR; 63binmode STDERR;
60 64
61# read virtual server time, if available 65# read virtual server time, if available
62unless ($RUNTIME || !-e cf::localdir . "/runtime") { 66unless ($RUNTIME || !-e cf::localdir . "/runtime") {
106=item %cf::CFG 110=item %cf::CFG
107 111
108Configuration for the server, loaded from C</etc/crossfire/config>, or 112Configuration for the server, loaded from C</etc/crossfire/config>, or
109from wherever your confdir points to. 113from wherever your confdir points to.
110 114
115=item $cf::WAIT_FOR_TICK, $cf::WAIT_FOR_TICK_ONE
116
117These are Coro::Signal objects that are C<< ->broadcast >> (WAIT_FOR_TICK)
118or C<< ->send >> (WAIT_FOR_TICK_ONE) on after normal server tick
119processing has been done. Call C<< ->wait >> on them to maximise the
120window of cpu time available, or simply to synchronise to the server tick.
121
111=back 122=back
112 123
113=cut 124=cut
114 125
115BEGIN { 126BEGIN {
118 utf8::encode $msg; 129 utf8::encode $msg;
119 130
120 $msg .= "\n" 131 $msg .= "\n"
121 unless $msg =~ /\n$/; 132 unless $msg =~ /\n$/;
122 133
123 LOG llevError, "cfperl: $msg"; 134 LOG llevError, $msg;
124 }; 135 };
125} 136}
126 137
127@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable'; 138@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
128@safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable'; 139@safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable';
154 165
155=head2 UTILITY FUNCTIONS 166=head2 UTILITY FUNCTIONS
156 167
157=over 4 168=over 4
158 169
170=item dumpval $ref
171
159=cut 172=cut
173
174sub dumpval {
175 eval {
176 local $SIG{__DIE__};
177 my $d;
178 if (1) {
179 $d = new Data::Dumper([$_[0]], ["*var"]);
180 $d->Terse(1);
181 $d->Indent(2);
182 $d->Quotekeys(0);
183 $d->Useqq(1);
184 #$d->Bless(...);
185 $d->Seen($_[1]) if @_ > 1;
186 $d = $d->Dump();
187 }
188 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge;
189 $d
190 } || "[unable to dump $_[0]: '$@']";
191}
160 192
161use JSON::Syck (); # TODO# replace by JSON::PC once working 193use JSON::Syck (); # TODO# replace by JSON::PC once working
162 194
163=item $ref = cf::from_json $json 195=item $ref = cf::from_json $json
164 196
335 367
336############################################################################# 368#############################################################################
337 369
338package cf::path; 370package cf::path;
339 371
372use overload
373 '""' => \&as_string;
374
375# used to convert map paths into valid unix filenames by repalcing / by ∕
376our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons
377
340sub new { 378sub new {
341 my ($class, $path, $base) = @_; 379 my ($class, $path, $base) = @_;
342 380
343 $path = $path->as_string if ref $path; 381 $path = $path->as_string if ref $path;
344 382
349 # ?random/... random maps 387 # ?random/... random maps
350 # /! non-realised random map exit 388 # /! non-realised random map exit
351 # /... normal maps 389 # /... normal maps
352 # ~/... per-player maps without a specific player (DO NOT USE) 390 # ~/... per-player maps without a specific player (DO NOT USE)
353 # ~user/... per-player map of a specific user 391 # ~user/... per-player map of a specific user
392
393 $path =~ s/$PATH_SEP/\//go;
354 394
355 if ($path =~ /^{/) { 395 if ($path =~ /^{/) {
356 # fine as it is 396 # fine as it is
357 } elsif ($path =~ s{^\?random/}{}) { 397 } elsif ($path =~ s{^\?random/}{}) {
358 Coro::AIO::aio_load "$cf::RANDOM_MAPS/$path.meta", my $data; 398 Coro::AIO::aio_load "$cf::RANDOM_MAPS/$path.meta", my $data;
407# } 447# }
408} 448}
409 449
410# escape the /'s in the path 450# escape the /'s in the path
411sub _escaped_path { 451sub _escaped_path {
412 # ∕ is U+2215
413 (my $path = $_[0]{path}) =~ s/\///g; 452 (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/g;
414 $path 453 $path
415} 454}
416 455
417# the original (read-only) location 456# the original (read-only) location
418sub load_path { 457sub load_path {
1045}; 1084};
1046 1085
1047cf::map->attach (prio => -10000, package => cf::mapsupport::); 1086cf::map->attach (prio => -10000, package => cf::mapsupport::);
1048 1087
1049############################################################################# 1088#############################################################################
1050# load/save perl data associated with player->ob objects
1051
1052sub all_objects(@) {
1053 @_, map all_objects ($_->inv), @_
1054}
1055
1056# TODO: compatibility cruft, remove when no longer needed
1057cf::player->attach (
1058 on_load => sub {
1059 my ($pl, $path) = @_;
1060
1061 for my $o (all_objects $pl->ob) {
1062 if (my $value = $o->get_ob_key_value ("_perl_data")) {
1063 $o->set_ob_key_value ("_perl_data");
1064
1065 %$o = %{ Storable::thaw pack "H*", $value };
1066 }
1067 }
1068 },
1069);
1070
1071#############################################################################
1072 1089
1073=head2 CORE EXTENSIONS 1090=head2 CORE EXTENSIONS
1074 1091
1075Functions and methods that extend core crossfire objects. 1092Functions and methods that extend core crossfire objects.
1076 1093
1077=cut 1094=cut
1078 1095
1079package cf::player; 1096package cf::player;
1080 1097
1098use Coro::AIO;
1099
1081=head3 cf::player 1100=head3 cf::player
1082 1101
1083=over 4 1102=over 4
1084 1103
1085=item cf::player::find $login 1104=item cf::player::find $login
1086 1105
1087Returns the given player object, loading it if necessary (might block). 1106Returns the given player object, loading it if necessary (might block).
1088 1107
1089=cut 1108=cut
1090 1109
1110sub playerdir($) {
1111 cf::localdir
1112 . "/"
1113 . cf::playerdir
1114 . "/"
1115 . (ref $_[0] ? $_[0]->ob->name : $_[0])
1116}
1117
1091sub path($) { 1118sub path($) {
1092 sprintf "%s/%s/%s/%s.pl", 1119 my $login = ref $_[0] ? $_[0]->ob->name : $_[0];
1093 cf::localdir, cf::playerdir, 1120
1094 (ref $_[0] ? $_[0]->ob->name : $_[0]) x 2 1121 (playerdir $login) . "/$login.pl"
1095} 1122}
1096 1123
1097sub find_active($) { 1124sub find_active($) {
1098 $cf::PLAYER{$_[0]} 1125 $cf::PLAYER{$_[0]}
1099 and $cf::PLAYER{$_[0]}->active 1126 and $cf::PLAYER{$_[0]}->active
1111 return $cf::PLAYER{$_[0]} || do { 1138 return $cf::PLAYER{$_[0]} || do {
1112 my $login = $_[0]; 1139 my $login = $_[0];
1113 1140
1114 my $guard = cf::lock_acquire "user_find:$login"; 1141 my $guard = cf::lock_acquire "user_find:$login";
1115 1142
1116 $cf::PLAYER{$login} ||= (load_pl path $login or return); 1143 $cf::PLAYER{$_[0]} || do {
1144 my $pl = load_pl path $login
1145 or return;
1146 $cf::PLAYER{$login} = $pl
1147 }
1117 }; 1148 }
1118} 1149}
1119 1150
1120sub save($) { 1151sub save($) {
1121 my ($pl) = @_; 1152 my ($pl) = @_;
1122 1153
1124 1155
1125 my $path = path $pl; 1156 my $path = path $pl;
1126 my $guard = cf::lock_acquire "user_save:$path"; 1157 my $guard = cf::lock_acquire "user_save:$path";
1127 1158
1128 return if $pl->{deny_save}; 1159 return if $pl->{deny_save};
1160
1161 aio_mkdir playerdir $pl, 0770;
1129 $pl->{last_save} = $cf::RUNTIME; 1162 $pl->{last_save} = $cf::RUNTIME;
1130 1163
1131 Coro::cede;
1132 $pl->save_pl ($path); 1164 $pl->save_pl ($path);
1133 Coro::cede; 1165 Coro::cede;
1134} 1166}
1135 1167
1136sub new($) { 1168sub new($) {
1142 $self->{deny_save} = 1; 1174 $self->{deny_save} = 1;
1143 1175
1144 $cf::PLAYER{$login} = $self; 1176 $cf::PLAYER{$login} = $self;
1145 1177
1146 $self 1178 $self
1179}
1180
1181=item $pl->quit_character
1182
1183Nukes the player without looking back. If logged in, the connection will
1184be destroyed. May block for a long time.
1185
1186=cut
1187
1188sub quit_character {
1189 my ($pl) = @_;
1190
1191 $pl->{deny_save} = 1;
1192 $pl->password ("*"); # this should lock out the player until we nuked the dir
1193
1194 $pl->invoke (cf::EVENT_PLAYER_LOGOUT, 1) if $pl->active;
1195 $pl->deactivate;
1196 $pl->invoke (cf::EVENT_PLAYER_QUIT);
1197 $pl->ns->destroy if $pl->ns;
1198
1199 my $path = playerdir $pl;
1200 my $temp = "$path~$cf::RUNTIME~deleting~";
1201 aio_rename $path, $temp;
1202 delete $cf::PLAYER{$pl->ob->name};
1203 $pl->destroy;
1204 IO::AIO::aio_rmtree $temp;
1205}
1206
1207=item cf::player::list_logins
1208
1209Returns am arrayref of all valid playernames in the system, can take a
1210while and may block, so not sync_job-capable, ever.
1211
1212=cut
1213
1214sub list_logins {
1215 my $dirs = aio_readdir cf::localdir . "/" . cf::playerdir
1216 or return [];
1217
1218 my @logins;
1219
1220 for my $login (@$dirs) {
1221 my $fh = aio_open path $login, Fcntl::O_RDONLY, 0 or next;
1222 aio_read $fh, 0, 512, my $buf, 0 or next;
1223 $buf !~ /^password -------------$/m or next; # official not-valid tag
1224
1225 utf8::decode $login;
1226 push @logins, $login;
1227 }
1228
1229 \@logins
1230}
1231
1232=item $player->maps
1233
1234Returns an arrayref of cf::path's of all maps that are private for this
1235player. May block.
1236
1237=cut
1238
1239sub maps($) {
1240 my ($pl) = @_;
1241
1242 my $files = aio_readdir playerdir $pl
1243 or return;
1244
1245 my @paths;
1246
1247 for (@$files) {
1248 utf8::decode $_;
1249 next if /\.(?:pl|pst)$/;
1250 next unless /^$PATH_SEP/o;
1251
1252 push @paths, new cf::path "~" . $pl->ob->name . "/" . $_;
1253 }
1254
1255 \@paths
1147} 1256}
1148 1257
1149=item $player->ext_reply ($msgid, $msgtype, %msg) 1258=item $player->ext_reply ($msgid, $msgtype, %msg)
1150 1259
1151Sends an ext reply to the player. 1260Sends an ext reply to the player.
1334 Coro::cede; 1443 Coro::cede;
1335 1444
1336 $self->in_memory (cf::MAP_IN_MEMORY); 1445 $self->in_memory (cf::MAP_IN_MEMORY);
1337} 1446}
1338 1447
1448# find and load all maps in the 3x3 area around a map
1449sub load_diag {
1450 my ($map) = @_;
1451
1452 my @diag; # diagonal neighbours
1453
1454 for (0 .. 3) {
1455 my $neigh = $map->tile_path ($_)
1456 or next;
1457 $neigh = find $neigh, $map
1458 or next;
1459 $neigh->load;
1460
1461 push @diag, [$neigh->tile_path (($_ + 3) % 4), $neigh],
1462 [$neigh->tile_path (($_ + 1) % 4), $neigh];
1463 }
1464
1465 for (@diag) {
1466 my $neigh = find @$_
1467 or next;
1468 $neigh->load;
1469 }
1470}
1471
1339sub find_sync { 1472sub find_sync {
1340 my ($path, $origin) = @_; 1473 my ($path, $origin) = @_;
1341 1474
1342 cf::sync_job { cf::map::find $path, $origin } 1475 cf::sync_job { find $path, $origin }
1343} 1476}
1344 1477
1345sub do_load_sync { 1478sub do_load_sync {
1346 my ($map) = @_; 1479 my ($map) = @_;
1347 1480
1348 cf::sync_job { $map->load }; 1481 cf::sync_job { $map->load };
1482}
1483
1484our %MAP_PREFETCH;
1485our $MAP_PREFETCHER = Coro::async {
1486 while () {
1487 while (%MAP_PREFETCH) {
1488 my $key = each %MAP_PREFETCH
1489 or next;
1490 my $path = delete $MAP_PREFETCH{$key};
1491
1492 my $map = find $path
1493 or next;
1494 $map->load;
1495 }
1496 Coro::schedule;
1497 }
1498};
1499
1500sub find_async {
1501 my ($path, $origin) = @_;
1502
1503 $path = new cf::path $path, $origin && $origin->path;
1504 my $key = $path->as_string;
1505
1506 if (my $map = $cf::MAP{$key}) {
1507 return $map if $map->in_memory == cf::MAP_IN_MEMORY;
1508 }
1509
1510 $MAP_PREFETCH{$key} = $path;
1511 $MAP_PREFETCHER->ready;
1512
1513 ()
1349} 1514}
1350 1515
1351sub save { 1516sub save {
1352 my ($self) = @_; 1517 my ($self) = @_;
1353 1518
1471 } 1636 }
1472 1637
1473 $map 1638 $map
1474} 1639}
1475 1640
1476sub emergency_save { 1641=item cf::map::unique_maps
1477 my $freeze_guard = cf::freeze_mainloop;
1478 1642
1479 warn "enter emergency perl save\n"; 1643Returns an arrayref of cf::path's of all shared maps that have
1644instantiated unique items. May block.
1480 1645
1481 cf::sync_job { 1646=cut
1482 warn "begin emergency player save\n";
1483 $_->save for values %cf::PLAYER;
1484 warn "end emergency player save\n";
1485 1647
1486 warn "begin emergency map save\n"; 1648sub unique_maps() {
1487 $_->save for values %cf::MAP; 1649 my $files = aio_readdir cf::localdir . "/" . cf::uniquedir
1488 warn "end emergency map save\n"; 1650 or return;
1651
1652 my @paths;
1653
1654 for (@$files) {
1655 utf8::decode $_;
1656 next if /\.pst$/;
1657 next unless /^$PATH_SEP/o;
1658
1659 push @paths, new cf::path $_;
1489 }; 1660 }
1490 1661
1491 warn "leave emergency perl save\n"; 1662 \@paths
1492} 1663}
1493 1664
1494package cf; 1665package cf;
1495 1666
1496=back 1667=back
1497 1668
1669=head3 cf::object
1670
1671=cut
1672
1673package cf::object;
1674
1675=over 4
1676
1677=item $ob->inv_recursive
1678
1679Returns the inventory of the object _and_ their inventories, recursively.
1680
1681=cut
1682
1683sub inv_recursive_;
1684sub inv_recursive_ {
1685 map { $_, inv_recursive_ $_->inv } @_
1686}
1687
1688sub inv_recursive {
1689 inv_recursive_ inv $_[0]
1690}
1691
1692package cf;
1693
1694=back
1498 1695
1499=head3 cf::object::player 1696=head3 cf::object::player
1500 1697
1501=over 4 1698=over 4
1502 1699
1594 # use -1 or undef as default coordinates, not 0, 0 1791 # use -1 or undef as default coordinates, not 0, 0
1595 ($x, $y) = ($map->enter_x, $map->enter_y) 1792 ($x, $y) = ($map->enter_x, $map->enter_y)
1596 if $x <=0 && $y <= 0; 1793 if $x <=0 && $y <= 0;
1597 1794
1598 $map->load; 1795 $map->load;
1796 $map->load_diag;
1599 1797
1600 return unless $self->contr->active; 1798 return unless $self->contr->active;
1601 $self->activate_recursive; 1799 $self->activate_recursive;
1602 $self->enter_map ($map, $x, $y); 1800 $self->enter_map ($map, $x, $y);
1603} 1801}
1639=cut 1837=cut
1640 1838
1641sub cf::object::player::goto { 1839sub cf::object::player::goto {
1642 my ($self, $path, $x, $y) = @_; 1840 my ($self, $path, $x, $y) = @_;
1643 1841
1842 $path = new cf::path $path;
1843
1644 $self->enter_link; 1844 $self->enter_link;
1645 1845
1646 (async { 1846 (async {
1647 $path = new cf::path $path;
1648
1649 my $map = cf::map::find $path->as_string; 1847 my $map = cf::map::find $path->as_string;
1650 $map = $map->customise_for ($self) if $map; 1848 $map = $map->customise_for ($self) if $map;
1651 1849
1652# warn "entering ", $map->path, " at ($x, $y)\n" 1850# warn "entering ", $map->path, " at ($x, $y)\n"
1653# if $map; 1851# if $map;
1654 1852
1655 $map or $self->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED); 1853 $map or $self->message ("The exit to '" . ($path->visible_name) . "' is closed", cf::NDI_UNIQUE | cf::NDI_RED);
1656 1854
1657 $self->leave_link ($map, $x, $y); 1855 $self->leave_link ($map, $x, $y);
1658 })->prio (1); 1856 })->prio (1);
1659} 1857}
1660 1858
1726 1924
1727 1; 1925 1;
1728 }) { 1926 }) {
1729 $self->message ("Something went wrong deep within the crossfire server. " 1927 $self->message ("Something went wrong deep within the crossfire server. "
1730 . "I'll try to bring you back to the map you were before. " 1928 . "I'll try to bring you back to the map you were before. "
1731 . "Please report this to the dungeon master", 1929 . "Please report this to the dungeon master!",
1732 cf::NDI_UNIQUE | cf::NDI_RED); 1930 cf::NDI_UNIQUE | cf::NDI_RED);
1733 1931
1734 warn "ERROR in enter_exit: $@"; 1932 warn "ERROR in enter_exit: $@";
1735 $self->leave_link; 1933 $self->leave_link;
1736 } 1934 }
2068 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset}; 2266 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset};
2069 $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset}; 2267 $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset};
2070 2268
2071 if (exists $CFG{mlockall}) { 2269 if (exists $CFG{mlockall}) {
2072 eval { 2270 eval {
2073 $CFG{mlockall} ? &mlockall : &munlockall 2271 $CFG{mlockall} ? eval "mlockall()" : eval "munlockall()"
2074 and die "WARNING: m(un)lockall failed: $!\n"; 2272 and die "WARNING: m(un)lockall failed: $!\n";
2075 }; 2273 };
2076 warn $@ if $@; 2274 warn $@ if $@;
2077 } 2275 }
2078} 2276}
2089 load_extensions; 2287 load_extensions;
2090 Event::loop; 2288 Event::loop;
2091} 2289}
2092 2290
2093############################################################################# 2291#############################################################################
2094# initialisation 2292# initialisation and cleanup
2293
2294# install some emergency cleanup handlers
2295BEGIN {
2296 for my $signal (qw(INT HUP TERM)) {
2297 Event->signal (
2298 data => WF_AUTOCANCEL,
2299 signal => $signal,
2300 cb => sub {
2301 cf::cleanup "SIG$signal";
2302 },
2303 );
2304 }
2305}
2306
2307sub emergency_save() {
2308 my $freeze_guard = cf::freeze_mainloop;
2309
2310 warn "enter emergency perl save\n";
2311
2312 cf::sync_job {
2313 # use a peculiar iteration method to avoid tripping on perl
2314 # refcount bugs in for. also avoids problems with players
2315 # and maps saved/Destroyed asynchronously.
2316 warn "begin emergency player save\n";
2317 for my $login (keys %cf::PLAYER) {
2318 my $pl = $cf::PLAYER{$login} or next;
2319 $pl->valid or next;
2320 $pl->save;
2321 }
2322 warn "end emergency player save\n";
2323
2324 warn "begin emergency map save\n";
2325 for my $path (keys %cf::MAP) {
2326 my $map = $cf::MAP{$path} or next;
2327 $map->valid or next;
2328 $map->save;
2329 }
2330 warn "end emergency map save\n";
2331 };
2332
2333 warn "leave emergency perl save\n";
2334}
2095 2335
2096sub reload() { 2336sub reload() {
2097 # can/must only be called in main 2337 # can/must only be called in main
2098 if ($Coro::current != $Coro::main) { 2338 if ($Coro::current != $Coro::main) {
2099 warn "can only reload from main coroutine\n"; 2339 warn "can only reload from main coroutine\n";
2250 data => WF_AUTOCANCEL, 2490 data => WF_AUTOCANCEL,
2251 cb => sub { 2491 cb => sub {
2252 cf::server_tick; # one server iteration 2492 cf::server_tick; # one server iteration
2253 $RUNTIME += $TICK; 2493 $RUNTIME += $TICK;
2254 $NEXT_TICK += $TICK; 2494 $NEXT_TICK += $TICK;
2495
2496 $WAIT_FOR_TICK->broadcast;
2497 $WAIT_FOR_TICK_ONE->send if $WAIT_FOR_TICK_ONE->awaited;
2255 2498
2256 # if we are delayed by four ticks or more, skip them all 2499 # if we are delayed by four ticks or more, skip them all
2257 $NEXT_TICK = Event::time if Event::time >= $NEXT_TICK + $TICK * 4; 2500 $NEXT_TICK = Event::time if Event::time >= $NEXT_TICK + $TICK * 4;
2258 2501
2259 $TICK_WATCHER->at ($NEXT_TICK); 2502 $TICK_WATCHER->at ($NEXT_TICK);

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines