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

Comparing cf.schmorp.de/server/lib/cf.pm (file contents):
Revision 1.287 by root, Mon Jun 25 05:43:45 2007 UTC vs.
Revision 1.411 by root, Fri Feb 1 15:54:07 2008 UTC

4use strict; 4use strict;
5 5
6use Symbol; 6use Symbol;
7use List::Util; 7use List::Util;
8use Socket; 8use Socket;
9use Storable; 9use EV 1.86;
10use Event;
11use Opcode; 10use Opcode;
12use Safe; 11use Safe;
13use Safe::Hole; 12use Safe::Hole;
13use Storable ();
14 14
15use Coro 3.61 (); 15use Coro 4.32 ();
16use Coro::State; 16use Coro::State;
17use Coro::Handle; 17use Coro::Handle;
18use Coro::Event; 18use Coro::EV;
19use Coro::Timer; 19use Coro::Timer;
20use Coro::Signal; 20use Coro::Signal;
21use Coro::Semaphore; 21use Coro::Semaphore;
22use Coro::AIO; 22use Coro::AIO;
23use Coro::BDB;
23use Coro::Storable; 24use Coro::Storable;
25use Coro::Util ();
24 26
27use JSON::XS 2.01 ();
25use BDB (); 28use BDB ();
26use Data::Dumper; 29use Data::Dumper;
27use Digest::MD5; 30use Digest::MD5;
28use Fcntl; 31use Fcntl;
29use YAML::Syck (); 32use YAML ();
30use IO::AIO 2.32 (); 33use IO::AIO 2.51 ();
31use Time::HiRes; 34use Time::HiRes;
32use Compress::LZF; 35use Compress::LZF;
36use Digest::MD5 ();
33 37
34# configure various modules to our taste 38# configure various modules to our taste
35# 39#
36$Storable::canonical = 1; # reduce rsync transfers 40$Storable::canonical = 1; # reduce rsync transfers
37Coro::State::cctx_stacksize 256000; # 1-2MB stack, for deep recursions in maze generator 41Coro::State::cctx_stacksize 256000; # 1-2MB stack, for deep recursions in maze generator
38Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later 42Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later
39
40$Event::Eval = 1; # no idea why this is required, but it is
41
42# work around bug in YAML::Syck - bad news for perl6, will it be as broken wrt. unicode?
43$YAML::Syck::ImplicitUnicode = 1;
44 43
45$Coro::main->prio (Coro::PRIO_MAX); # run main coroutine ("the server") with very high priority 44$Coro::main->prio (Coro::PRIO_MAX); # run main coroutine ("the server") with very high priority
46 45
47sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload 46sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
48 47
67our $TMPDIR = "$LOCALDIR/" . tmpdir; 66our $TMPDIR = "$LOCALDIR/" . tmpdir;
68our $UNIQUEDIR = "$LOCALDIR/" . uniquedir; 67our $UNIQUEDIR = "$LOCALDIR/" . uniquedir;
69our $PLAYERDIR = "$LOCALDIR/" . playerdir; 68our $PLAYERDIR = "$LOCALDIR/" . playerdir;
70our $RANDOMDIR = "$LOCALDIR/random"; 69our $RANDOMDIR = "$LOCALDIR/random";
71our $BDBDIR = "$LOCALDIR/db"; 70our $BDBDIR = "$LOCALDIR/db";
71our %RESOURCE;
72 72
73our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!) 73our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!)
74our $TICK_WATCHER; 74our $TICK_WATCHER;
75our $AIO_POLL_WATCHER; 75our $AIO_POLL_WATCHER;
76our $NEXT_RUNTIME_WRITE; # when should the runtime file be written 76our $NEXT_RUNTIME_WRITE; # when should the runtime file be written
77our $NEXT_TICK; 77our $NEXT_TICK;
78our $NOW;
79our $USE_FSYNC = 1; # use fsync to write maps - default off 78our $USE_FSYNC = 1; # use fsync to write maps - default off
80 79
81our $BDB_POLL_WATCHER; 80our $BDB_POLL_WATCHER;
81our $BDB_DEADLOCK_WATCHER;
82our $BDB_CHECKPOINT_WATCHER;
83our $BDB_TRICKLE_WATCHER;
82our $DB_ENV; 84our $DB_ENV;
83 85
84our %CFG; 86our %CFG;
85 87
86our $UPTIME; $UPTIME ||= time; 88our $UPTIME; $UPTIME ||= time;
87our $RUNTIME; 89our $RUNTIME;
90our $NOW;
88 91
89our %PLAYER; # all users 92our (%PLAYER, %PLAYER_LOADING); # all users
90our %MAP; # all maps 93our (%MAP, %MAP_LOADING ); # all maps
91our $LINK_MAP; # the special {link} map, which is always available 94our $LINK_MAP; # the special {link} map, which is always available
92 95
93# used to convert map paths into valid unix filenames by replacing / by ∕ 96# used to convert map paths into valid unix filenames by replacing / by ∕
94our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons 97our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons
95 98
156 159
157The raw value load value from the last tick. 160The raw value load value from the last tick.
158 161
159=item %cf::CFG 162=item %cf::CFG
160 163
161Configuration for the server, loaded from C</etc/crossfire/config>, or 164Configuration for the server, loaded from C</etc/deliantra-server/config>, or
162from wherever your confdir points to. 165from wherever your confdir points to.
163 166
164=item cf::wait_for_tick, cf::wait_for_tick_begin 167=item cf::wait_for_tick, cf::wait_for_tick_begin
165 168
166These are functions that inhibit the current coroutine one tick. cf::wait_for_tick_begin only 169These are functions that inhibit the current coroutine one tick. cf::wait_for_tick_begin only
167returns directly I<after> the tick processing (and consequently, can only wake one process 170returns directly I<after> the tick processing (and consequently, can only wake one process
168per tick), while cf::wait_for_tick wakes up all waiters after tick processing. 171per tick), while cf::wait_for_tick wakes up all waiters after tick processing.
169 172
173=item @cf::INVOKE_RESULTS
174
175This array contains the results of the last C<invoke ()> call. When
176C<cf::override> is called C<@cf::INVOKE_RESULTS> is set to the parameters of
177that call.
178
170=back 179=back
171 180
172=cut 181=cut
173 182
174BEGIN { 183BEGIN {
178 $msg .= "\n" 187 $msg .= "\n"
179 unless $msg =~ /\n$/; 188 unless $msg =~ /\n$/;
180 189
181 $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge; 190 $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge;
182 191
183 utf8::encode $msg;
184 LOG llevError, $msg; 192 LOG llevError, $msg;
185 }; 193 };
186} 194}
195
196$Coro::State::DIEHOOK = sub {
197 return unless $^S eq 0; # "eq", not "=="
198
199 if ($Coro::current == $Coro::main) {#d#
200 warn "DIEHOOK called in main context, Coro bug?\n";#d#
201 return;#d#
202 }#d#
203
204 # kill coroutine otherwise
205 warn Carp::longmess $_[0];
206 Coro::terminate
207};
208
209$SIG{__DIE__} = sub { }; #d#?
187 210
188@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable'; 211@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
189@safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable'; 212@safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable';
190@safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable'; 213@safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable';
191@safe::cf::client::ISA = @cf::client::ISA = 'cf::attachable'; 214@safe::cf::client::ISA = @cf::client::ISA = 'cf::attachable';
204)) { 227)) {
205 no strict 'refs'; 228 no strict 'refs';
206 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg; 229 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg;
207} 230}
208 231
209$Event::DIED = sub { 232$EV::DIED = sub {
210 warn "error in event callback: @_"; 233 warn "error in event callback: @_";
211}; 234};
212 235
213############################################################################# 236#############################################################################
214 237
237 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge; 260 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge;
238 $d 261 $d
239 } || "[unable to dump $_[0]: '$@']"; 262 } || "[unable to dump $_[0]: '$@']";
240} 263}
241 264
242use JSON::XS ();
243
244=item $ref = cf::from_json $json 265=item $ref = cf::decode_json $json
245 266
246Converts a JSON string into the corresponding perl data structure. 267Converts a JSON string into the corresponding perl data structure.
247 268
248=item $json = cf::to_json $ref 269=item $json = cf::encode_json $ref
249 270
250Converts a perl data structure into its JSON representation. 271Converts a perl data structure into its JSON representation.
251 272
252=cut 273=cut
253 274
254our $json_coder = JSON::XS->new->convert_blessed->utf8; 275our $json_coder = JSON::XS->new->utf8->max_size (1e6); # accept ~1mb max
255 276
256sub to_json ($) { $json_coder->encode ($_[0]) } 277sub encode_json($) { $json_coder->encode ($_[0]) }
257sub from_json ($) { $json_coder->decode ($_[0]) } 278sub decode_json($) { $json_coder->decode ($_[0]) }
258 279
259=item cf::lock_wait $string 280=item cf::lock_wait $string
260 281
261Wait until the given lock is available. See cf::lock_acquire. 282Wait until the given lock is available. See cf::lock_acquire.
262 283
265Wait until the given lock is available and then acquires it and returns 286Wait until the given lock is available and then acquires it and returns
266a Coro::guard object. If the guard object gets destroyed (goes out of scope, 287a Coro::guard object. If the guard object gets destroyed (goes out of scope,
267for example when the coroutine gets canceled), the lock is automatically 288for example when the coroutine gets canceled), the lock is automatically
268returned. 289returned.
269 290
291Locks are *not* recursive, locking from the same coro twice results in a
292deadlocked coro.
293
270Lock names should begin with a unique identifier (for example, cf::map::find 294Lock names should begin with a unique identifier (for example, cf::map::find
271uses map_find and cf::map::load uses map_load). 295uses map_find and cf::map::load uses map_load).
272 296
273=item $locked = cf::lock_active $string 297=item $locked = cf::lock_active $string
274 298
275Return true if the lock is currently active, i.e. somebody has locked it. 299Return true if the lock is currently active, i.e. somebody has locked it.
276 300
277=cut 301=cut
278 302
279our %LOCK; 303our %LOCK;
304our %LOCKER;#d#
280 305
281sub lock_wait($) { 306sub lock_wait($) {
282 my ($key) = @_; 307 my ($key) = @_;
308
309 if ($LOCKER{$key} == $Coro::current) {#d#
310 Carp::cluck "lock_wait($key) for already-acquired lock";#d#
311 return;#d#
312 }#d#
283 313
284 # wait for lock, if any 314 # wait for lock, if any
285 while ($LOCK{$key}) { 315 while ($LOCK{$key}) {
286 push @{ $LOCK{$key} }, $Coro::current; 316 push @{ $LOCK{$key} }, $Coro::current;
287 Coro::schedule; 317 Coro::schedule;
293 323
294 # wait, to be sure we are not locked 324 # wait, to be sure we are not locked
295 lock_wait $key; 325 lock_wait $key;
296 326
297 $LOCK{$key} = []; 327 $LOCK{$key} = [];
328 $LOCKER{$key} = $Coro::current;#d#
298 329
299 Coro::guard { 330 Coro::guard {
331 delete $LOCKER{$key};#d#
300 # wake up all waiters, to be on the safe side 332 # wake up all waiters, to be on the safe side
301 $_->ready for @{ delete $LOCK{$key} }; 333 $_->ready for @{ delete $LOCK{$key} };
302 } 334 }
303} 335}
304 336
316 }; 348 };
317 $TICK_WATCHER->stop; 349 $TICK_WATCHER->stop;
318 $guard 350 $guard
319} 351}
320 352
353=item cf::periodic $interval, $cb
354
355Like EV::periodic, but randomly selects a starting point so that the actions
356get spread over timer.
357
358=cut
359
360sub periodic($$) {
361 my ($interval, $cb) = @_;
362
363 my $start = rand List::Util::min 180, $interval;
364
365 EV::periodic $start, $interval, 0, $cb
366}
367
368=item cf::get_slot $time[, $priority[, $name]]
369
370Allocate $time seconds of blocking CPU time at priority C<$priority>:
371This call blocks and returns only when you have at least C<$time> seconds
372of cpu time till the next tick. The slot is only valid till the next cede.
373
374The optional C<$name> can be used to identify the job to run. It might be
375used for statistical purposes and should identify the same time-class.
376
377Useful for short background jobs.
378
379=cut
380
381our @SLOT_QUEUE;
382our $SLOT_QUEUE;
383
384$SLOT_QUEUE->cancel if $SLOT_QUEUE;
385$SLOT_QUEUE = Coro::async {
386 $Coro::current->desc ("timeslot manager");
387
388 my $signal = new Coro::Signal;
389
390 while () {
391 next_job:
392 my $avail = cf::till_tick;
393 if ($avail > 0.01) {
394 for (0 .. $#SLOT_QUEUE) {
395 if ($SLOT_QUEUE[$_][0] < $avail) {
396 my $job = splice @SLOT_QUEUE, $_, 1, ();
397 $job->[2]->send;
398 Coro::cede;
399 goto next_job;
400 }
401 }
402 }
403
404 if (@SLOT_QUEUE) {
405 # we do not use wait_for_tick() as it returns immediately when tick is inactive
406 push @cf::WAIT_FOR_TICK, $signal;
407 $signal->wait;
408 } else {
409 Coro::schedule;
410 }
411 }
412};
413
414sub get_slot($;$$) {
415 my ($time, $pri, $name) = @_;
416
417 $time = $TICK * .6 if $time > $TICK * .6;
418 my $sig = new Coro::Signal;
419
420 push @SLOT_QUEUE, [$time, $pri, $sig, $name];
421 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE;
422 $SLOT_QUEUE->ready;
423 $sig->wait;
424}
425
321=item cf::async { BLOCK } 426=item cf::async { BLOCK }
322 427
323Currently the same as Coro::async_pool, meaning you cannot use 428Currently the same as Coro::async_pool, meaning you cannot use
324C<on_destroy>, C<join> or other gimmicks on these coroutines. The only 429C<on_destroy>, C<join> or other gimmicks on these coroutines. The only
325thing you are allowed to do is call C<prio> on it. 430thing you are allowed to do is call C<prio> on it.
328 433
329BEGIN { *async = \&Coro::async_pool } 434BEGIN { *async = \&Coro::async_pool }
330 435
331=item cf::sync_job { BLOCK } 436=item cf::sync_job { BLOCK }
332 437
333The design of Crossfire TRT requires that the main coroutine ($Coro::main) 438The design of Deliantra requires that the main coroutine ($Coro::main)
334is always able to handle events or runnable, as Crossfire TRT is only 439is always able to handle events or runnable, as Deliantra is only
335partly reentrant. Thus "blocking" it by e.g. waiting for I/O is not 440partly reentrant. Thus "blocking" it by e.g. waiting for I/O is not
336acceptable. 441acceptable.
337 442
338If it must be done, put the blocking parts into C<sync_job>. This will run 443If it must be done, put the blocking parts into C<sync_job>. This will run
339the given BLOCK in another coroutine while waiting for the result. The 444the given BLOCK in another coroutine while waiting for the result. The
344 449
345sub sync_job(&) { 450sub sync_job(&) {
346 my ($job) = @_; 451 my ($job) = @_;
347 452
348 if ($Coro::current == $Coro::main) { 453 if ($Coro::current == $Coro::main) {
349 my $time = Event::time; 454 my $time = EV::time;
350 455
351 # this is the main coro, too bad, we have to block 456 # this is the main coro, too bad, we have to block
352 # till the operation succeeds, freezing the server :/ 457 # till the operation succeeds, freezing the server :/
353 458
459 LOG llevError, Carp::longmess "sync job";#d#
460
354 # TODO: use suspend/resume instead 461 # TODO: use suspend/resume instead
355 # (but this is cancel-safe) 462 # (but this is cancel-safe)
356 my $freeze_guard = freeze_mainloop; 463 my $freeze_guard = freeze_mainloop;
357 464
358 my $busy = 1; 465 my $busy = 1;
359 my @res; 466 my @res;
360 467
361 (async { 468 (async {
469 $Coro::current->desc ("sync job coro");
362 @res = eval { $job->() }; 470 @res = eval { $job->() };
363 warn $@ if $@; 471 warn $@ if $@;
364 undef $busy; 472 undef $busy;
365 })->prio (Coro::PRIO_MAX); 473 })->prio (Coro::PRIO_MAX);
366 474
367 while ($busy) { 475 while ($busy) {
368 Coro::cede or Event::one_event; 476 if (Coro::nready) {
477 Coro::cede_notself;
478 } else {
479 EV::loop EV::LOOP_ONESHOT;
369 } 480 }
481 }
370 482
371 $time = Event::time - $time; 483 $time = EV::time - $time;
372 484
373 LOG llevError | logBacktrace, Carp::longmess "long sync job" 485 LOG llevError | logBacktrace, Carp::longmess "long sync job"
374 if $time > $TICK * 0.5 && $TICK_WATCHER->is_active; 486 if $time > $TICK * 0.5 && $TICK_WATCHER->is_active;
375 487
376 $tick_start += $time; # do not account sync jobs to server load 488 $tick_start += $time; # do not account sync jobs to server load
406=item fork_call { }, $args 518=item fork_call { }, $args
407 519
408Executes the given code block with the given arguments in a seperate 520Executes the given code block with the given arguments in a seperate
409process, returning the results. Everything must be serialisable with 521process, returning the results. Everything must be serialisable with
410Coro::Storable. May, of course, block. Note that the executed sub may 522Coro::Storable. May, of course, block. Note that the executed sub may
411never block itself or use any form of Event handling. 523never block itself or use any form of event handling.
412 524
413=cut 525=cut
414 526
415sub fork_call(&@) { 527sub fork_call(&@) {
416 my ($cb, @args) = @_; 528 my ($cb, @args) = @_;
417 529
418# socketpair my $fh1, my $fh2, Socket::AF_UNIX, Socket::SOCK_STREAM, Socket::PF_UNSPEC 530 # we seemingly have to make a local copy of the whole thing,
419# or die "socketpair: $!"; 531 # otherwise perl prematurely frees the stuff :/
420 pipe my $fh1, my $fh2 532 # TODO: investigate and fix (likely this will be rather laborious)
421 or die "pipe: $!";
422 533
423 if (my $pid = fork) { 534 my @res = Coro::Util::fork_eval {
424 close $fh2;
425
426 my $res = (Coro::Handle::unblock $fh1)->readline (undef);
427 $res = Coro::Storable::thaw $res;
428
429 waitpid $pid, 0; # should not block anymore, we expect the child to simply behave
430
431 die $$res unless "ARRAY" eq ref $res;
432
433 return wantarray ? @$res : $res->[-1];
434 } else {
435 reset_signals; 535 reset_signals;
436 local $SIG{__WARN__}; 536 &$cb
437 local $SIG{__DIE__}; 537 }, @args;
438 eval {
439 close $fh1;
440 538
441 my @res = eval { $cb->(@args) }; 539 wantarray ? @res : $res[-1]
442 syswrite $fh2, Coro::Storable::freeze +($@ ? \"$@" : \@res);
443 };
444
445 warn $@ if $@;
446 _exit 0;
447 }
448} 540}
449 541
450=item $value = cf::db_get $family => $key 542=item $value = cf::db_get $family => $key
451 543
452Returns a single value from the environment database. 544Returns a single value from the environment database.
454=item cf::db_put $family => $key => $value 546=item cf::db_put $family => $key => $value
455 547
456Stores the given C<$value> in the family. It can currently store binary 548Stores the given C<$value> in the family. It can currently store binary
457data only (use Compress::LZF::sfreeze_cr/sthaw to convert to/from binary). 549data only (use Compress::LZF::sfreeze_cr/sthaw to convert to/from binary).
458 550
551=item $db = cf::db_table "name"
552
553Create and/or open a new database table. The string must not be "db" and must be unique
554within each server.
555
459=cut 556=cut
557
558sub db_table($) {
559 my ($name) = @_;
560 my $db = BDB::db_create $DB_ENV;
561
562 eval {
563 $db->set_flags (BDB::CHKSUM);
564
565 utf8::encode $name;
566 BDB::db_open $db, undef, $name, undef, BDB::BTREE,
567 BDB::CREATE | BDB::AUTO_COMMIT, 0666;
568 cf::cleanup "db_open(db): $!" if $!;
569 };
570 cf::cleanup "db_open(db): $@" if $@;
571
572 $db
573}
460 574
461our $DB; 575our $DB;
462 576
463sub db_init { 577sub db_init {
464 unless ($DB) {
465 $DB = BDB::db_create $DB_ENV;
466
467 cf::sync_job { 578 cf::sync_job {
468 eval { 579 $DB ||= db_table "db";
469 $DB->set_flags (BDB::CHKSUM);
470
471 BDB::db_open $DB, undef, "db", undef, BDB::BTREE,
472 BDB::CREATE | BDB::AUTO_COMMIT, 0666;
473 cf::cleanup "db_open(db): $!" if $!;
474 };
475 cf::cleanup "db_open(db): $@" if $@;
476 };
477 } 580 };
478} 581}
479 582
480sub db_get($$) { 583sub db_get($$) {
481 my $key = "$_[0]/$_[1]"; 584 my $key = "$_[0]/$_[1]";
482 585
532 if (1) { 635 if (1) {
533 $md5 = 636 $md5 =
534 join "\x00", 637 join "\x00",
535 $processversion, 638 $processversion,
536 map { 639 map {
537 Coro::cede; 640 cf::cede_to_tick;
538 ($src->[$_], Digest::MD5::md5_hex $data[$_]) 641 ($src->[$_], Digest::MD5::md5_hex $data[$_])
539 } 0.. $#$src; 642 } 0.. $#$src;
540 643
541 644
542 my $dbmd5 = db_get cache => "$id/md5"; 645 my $dbmd5 = db_get cache => "$id/md5";
586attach callbacks/event handlers (a collection of which is called an "attachment") 689attach callbacks/event handlers (a collection of which is called an "attachment")
587to it. All such attachable objects support the following methods. 690to it. All such attachable objects support the following methods.
588 691
589In the following description, CLASS can be any of C<global>, C<object> 692In the following description, CLASS can be any of C<global>, C<object>
590C<player>, C<client> or C<map> (i.e. the attachable objects in 693C<player>, C<client> or C<map> (i.e. the attachable objects in
591Crossfire TRT). 694Deliantra).
592 695
593=over 4 696=over 4
594 697
595=item $attachable->attach ($attachment, key => $value...) 698=item $attachable->attach ($attachment, key => $value...)
596 699
848 "; 951 ";
849 die if $@; 952 die if $@;
850} 953}
851 954
852our $override; 955our $override;
853our @invoke_results = (); # referenced from .xs code. TODO: play tricks with reify and mortals? 956our @INVOKE_RESULTS = (); # referenced from .xs code. TODO: play tricks with reify and mortals?
854 957
855sub override { 958sub override {
856 $override = 1; 959 $override = 1;
857 @invoke_results = (); 960 @INVOKE_RESULTS = (@_);
858} 961}
859 962
860sub do_invoke { 963sub do_invoke {
861 my $event = shift; 964 my $event = shift;
862 my $callbacks = shift; 965 my $callbacks = shift;
863 966
864 @invoke_results = (); 967 @INVOKE_RESULTS = ();
865 968
866 local $override; 969 local $override;
867 970
868 for (@$callbacks) { 971 for (@$callbacks) {
869 eval { &{$_->[1]} }; 972 eval { &{$_->[1]} };
878 } 981 }
879 982
880 0 983 0
881} 984}
882 985
883=item $bool = cf::global::invoke (EVENT_CLASS_XXX, ...) 986=item $bool = cf::global->invoke (EVENT_CLASS_XXX, ...)
884 987
885=item $bool = $attachable->invoke (EVENT_CLASS_XXX, ...) 988=item $bool = $attachable->invoke (EVENT_CLASS_XXX, ...)
886 989
887Generate an object-specific event with the given arguments. 990Generate an object-specific event with the given arguments.
888 991
889This API is preliminary (most likely, the EVENT_CLASS_xxx prefix will be 992This API is preliminary (most likely, the EVENT_CLASS_xxx prefix will be
890removed in future versions), and there is no public API to access override 993removed in future versions), and there is no public API to access override
891results (if you must, access C<@cf::invoke_results> directly). 994results (if you must, access C<@cf::INVOKE_RESULTS> directly).
892 995
893=back 996=back
894 997
895=cut 998=cut
896 999
897############################################################################# 1000#############################################################################
898# object support 1001# object support
1002
1003sub _object_equal($$);
1004sub _object_equal($$) {
1005 my ($a, $b) = @_;
1006
1007 return 0 unless (ref $a) eq (ref $b);
1008
1009 if ("HASH" eq ref $a) {
1010 my @ka = keys %$a;
1011 my @kb = keys %$b;
1012
1013 return 0 if @ka != @kb;
1014
1015 for (0 .. $#ka) {
1016 return 0 unless $ka[$_] eq $kb[$_];
1017 return 0 unless _object_equal $a->{$ka[$_]}, $b->{$kb[$_]};
1018 }
1019
1020 } elsif ("ARRAY" eq ref $a) {
1021
1022 return 0 if @$a != @$b;
1023
1024 for (0 .. $#$a) {
1025 return 0 unless _object_equal $a->[$_], $b->[$_];
1026 }
1027
1028 } elsif ($a ne $b) {
1029 return 0;
1030 }
1031
1032 1
1033}
1034
1035our $SLOW_MERGES;#d#
1036sub _can_merge {
1037 my ($ob1, $ob2) = @_;
1038
1039 ++$SLOW_MERGES;#d#
1040
1041 # we do the slow way here
1042 return _object_equal $ob1, $ob2
1043}
899 1044
900sub reattach { 1045sub reattach {
901 # basically do the same as instantiate, without calling instantiate 1046 # basically do the same as instantiate, without calling instantiate
902 my ($obj) = @_; 1047 my ($obj) = @_;
903 1048
924cf::attachable->attach ( 1069cf::attachable->attach (
925 prio => -1000000, 1070 prio => -1000000,
926 on_instantiate => sub { 1071 on_instantiate => sub {
927 my ($obj, $data) = @_; 1072 my ($obj, $data) = @_;
928 1073
929 $data = from_json $data; 1074 $data = decode_json $data;
930 1075
931 for (@$data) { 1076 for (@$data) {
932 my ($name, $args) = @$_; 1077 my ($name, $args) = @$_;
933 1078
934 $obj->attach ($name, %{$args || {} }); 1079 $obj->attach ($name, %{$args || {} });
950sub object_freezer_save { 1095sub object_freezer_save {
951 my ($filename, $rdata, $objs) = @_; 1096 my ($filename, $rdata, $objs) = @_;
952 1097
953 sync_job { 1098 sync_job {
954 if (length $$rdata) { 1099 if (length $$rdata) {
1100 utf8::decode (my $decname = $filename);
955 warn sprintf "saving %s (%d,%d)\n", 1101 warn sprintf "saving %s (%d,%d)\n",
956 $filename, length $$rdata, scalar @$objs; 1102 $decname, length $$rdata, scalar @$objs;
957 1103
958 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) { 1104 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
959 chmod SAVE_MODE, $fh; 1105 chmod SAVE_MODE, $fh;
960 aio_write $fh, 0, (length $$rdata), $$rdata, 0; 1106 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
961 aio_fsync $fh if $cf::USE_FSYNC; 1107 aio_fsync $fh if $cf::USE_FSYNC;
962 close $fh; 1108 close $fh;
963 1109
964 if (@$objs) { 1110 if (@$objs) {
965 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) { 1111 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) {
966 chmod SAVE_MODE, $fh; 1112 chmod SAVE_MODE, $fh;
967 my $data = Storable::nfreeze { version => 1, objs => $objs }; 1113 my $data = Coro::Storable::nfreeze { version => 1, objs => $objs };
968 aio_write $fh, 0, (length $data), $data, 0; 1114 aio_write $fh, 0, (length $data), $data, 0;
969 aio_fsync $fh if $cf::USE_FSYNC; 1115 aio_fsync $fh if $cf::USE_FSYNC;
970 close $fh; 1116 close $fh;
971 aio_rename "$filename.pst~", "$filename.pst"; 1117 aio_rename "$filename.pst~", "$filename.pst";
972 } 1118 }
980 } 1126 }
981 } else { 1127 } else {
982 aio_unlink $filename; 1128 aio_unlink $filename;
983 aio_unlink "$filename.pst"; 1129 aio_unlink "$filename.pst";
984 } 1130 }
985 } 1131 };
986} 1132}
987 1133
988sub object_freezer_as_string { 1134sub object_freezer_as_string {
989 my ($rdata, $objs) = @_; 1135 my ($rdata, $objs) = @_;
990 1136
1002 or return; 1148 or return;
1003 1149
1004 unless (aio_stat "$filename.pst") { 1150 unless (aio_stat "$filename.pst") {
1005 (aio_load "$filename.pst", $av) >= 0 1151 (aio_load "$filename.pst", $av) >= 0
1006 or return; 1152 or return;
1153
1007 $av = eval { (Storable::thaw $av)->{objs} }; 1154 my $st = eval { Coro::Storable::thaw $av };
1155 $av = $st->{objs};
1008 } 1156 }
1009 1157
1158 utf8::decode (my $decname = $filename);
1010 warn sprintf "loading %s (%d)\n", 1159 warn sprintf "loading %s (%d,%d)\n",
1011 $filename, length $data, scalar @{$av || []}; 1160 $decname, length $data, scalar @{$av || []};
1161
1012 return ($data, $av); 1162 ($data, $av)
1013} 1163}
1014 1164
1015=head2 COMMAND CALLBACKS 1165=head2 COMMAND CALLBACKS
1016 1166
1017=over 4 1167=over 4
1087 cf::override; 1237 cf::override;
1088 }, 1238 },
1089 on_extcmd => sub { 1239 on_extcmd => sub {
1090 my ($pl, $buf) = @_; 1240 my ($pl, $buf) = @_;
1091 1241
1092 my $msg = eval { from_json $buf }; 1242 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) };
1093 1243
1094 if (ref $msg) { 1244 if (ref $msg) {
1245 my ($type, $reply, @payload) =
1246 "ARRAY" eq ref $msg
1247 ? @$msg
1248 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
1249
1250 my @reply;
1251
1095 if (my $cb = $EXTCMD{$msg->{msgtype}}) { 1252 if (my $cb = $EXTCMD{$type}) {
1096 if (my %reply = $cb->($pl, $msg)) { 1253 @reply = $cb->($pl, @payload);
1097 $pl->ext_reply ($msg->{msgid}, %reply);
1098 }
1099 } 1254 }
1255
1256 $pl->ext_reply ($reply, @reply)
1257 if $reply;
1258
1100 } else { 1259 } else {
1101 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n"; 1260 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
1102 } 1261 }
1103 1262
1104 cf::override; 1263 cf::override;
1157 my $msg = $@ ? "$v->{path}: $@\n" 1316 my $msg = $@ ? "$v->{path}: $@\n"
1158 : "$v->{base}: extension inactive.\n"; 1317 : "$v->{base}: extension inactive.\n";
1159 1318
1160 if (exists $v->{meta}{mandatory}) { 1319 if (exists $v->{meta}{mandatory}) {
1161 warn $msg; 1320 warn $msg;
1162 warn "mandatory extension failed to load, exiting.\n"; 1321 cf::cleanup "mandatory extension failed to load, exiting.";
1163 exit 1;
1164 } 1322 }
1165 1323
1166 warn $msg; 1324 warn $msg;
1167 } 1325 }
1168 1326
1193use Coro::AIO; 1351use Coro::AIO;
1194 1352
1195=head3 cf::player 1353=head3 cf::player
1196 1354
1197=over 4 1355=over 4
1356
1357=item cf::player::num_playing
1358
1359Returns the official number of playing players, as per the Crossfire metaserver rules.
1360
1361=cut
1362
1363sub num_playing {
1364 scalar grep
1365 $_->ob->map
1366 && !$_->hidden
1367 && !$_->ob->flag (cf::FLAG_WIZ),
1368 cf::player::list
1369}
1198 1370
1199=item cf::player::find $login 1371=item cf::player::find $login
1200 1372
1201Returns the given player object, loading it if necessary (might block). 1373Returns the given player object, loading it if necessary (might block).
1202 1374
1238 aio_link +(playerdir $login) . "/$login.pl.pst", (playerdir $login) . "/playerdata.pst"; 1410 aio_link +(playerdir $login) . "/$login.pl.pst", (playerdir $login) . "/playerdata.pst";
1239 aio_link +(playerdir $login) . "/$login.pl" , (playerdir $login) . "/playerdata"; 1411 aio_link +(playerdir $login) . "/$login.pl" , (playerdir $login) . "/playerdata";
1240 aio_unlink +(playerdir $login) . "/$login.pl.pst"; 1412 aio_unlink +(playerdir $login) . "/$login.pl.pst";
1241 aio_unlink +(playerdir $login) . "/$login.pl"; 1413 aio_unlink +(playerdir $login) . "/$login.pl";
1242 1414
1243 my $pl = load_pl path $login 1415 my $f = new_from_file cf::object::thawer path $login
1244 or return; 1416 or return;
1417
1418 my $pl = cf::player::load_pl $f
1419 or return;
1420 local $cf::PLAYER_LOADING{$login} = $pl;
1421 $f->resolve_delayed_derefs;
1245 $cf::PLAYER{$login} = $pl 1422 $cf::PLAYER{$login} = $pl
1246 } 1423 }
1247 } 1424 }
1248} 1425}
1249 1426
1259 1436
1260 aio_mkdir playerdir $pl, 0770; 1437 aio_mkdir playerdir $pl, 0770;
1261 $pl->{last_save} = $cf::RUNTIME; 1438 $pl->{last_save} = $cf::RUNTIME;
1262 1439
1263 $pl->save_pl ($path); 1440 $pl->save_pl ($path);
1264 Coro::cede; 1441 cf::cede_to_tick;
1265} 1442}
1266 1443
1267sub new($) { 1444sub new($) {
1268 my ($login) = @_; 1445 my ($login) = @_;
1269 1446
1273 $self->{deny_save} = 1; 1450 $self->{deny_save} = 1;
1274 1451
1275 $cf::PLAYER{$login} = $self; 1452 $cf::PLAYER{$login} = $self;
1276 1453
1277 $self 1454 $self
1455}
1456
1457=item $player->send_msg ($channel, $msg, $color, [extra...])
1458
1459=cut
1460
1461sub send_msg {
1462 my $ns = shift->ns
1463 or return;
1464 $ns->send_msg (@_);
1278} 1465}
1279 1466
1280=item $pl->quit_character 1467=item $pl->quit_character
1281 1468
1282Nukes the player without looking back. If logged in, the connection will 1469Nukes the player without looking back. If logged in, the connection will
1337 or return []; 1524 or return [];
1338 1525
1339 my @logins; 1526 my @logins;
1340 1527
1341 for my $login (@$dirs) { 1528 for my $login (@$dirs) {
1529 my $path = path $login;
1530
1531 # a .pst is a dead give-away for a valid player
1532 unless (-e "$path.pst") {
1342 my $fh = aio_open path $login, Fcntl::O_RDONLY, 0 or next; 1533 my $fh = aio_open $path, Fcntl::O_RDONLY, 0 or next;
1343 aio_read $fh, 0, 512, my $buf, 0 or next; 1534 aio_read $fh, 0, 512, my $buf, 0 or next;
1344 $buf !~ /^password -------------$/m or next; # official not-valid tag 1535 $buf !~ /^password -------------$/m or next; # official not-valid tag
1536 }
1345 1537
1346 utf8::decode $login; 1538 utf8::decode $login;
1347 push @logins, $login; 1539 push @logins, $login;
1348 } 1540 }
1349 1541
1382 1574
1383Expand crossfire pod fragments into protocol xml. 1575Expand crossfire pod fragments into protocol xml.
1384 1576
1385=cut 1577=cut
1386 1578
1579use re 'eval';
1580
1581my $group;
1582my $interior; $interior = qr{
1583 # match a pod interior sequence sans C<< >>
1584 (?:
1585 \ (.*?)\ (?{ $group = $^N })
1586 | < (??{$interior}) >
1587 )
1588}x;
1589
1387sub expand_cfpod { 1590sub expand_cfpod {
1388 ((my $self), (local $_)) = @_; 1591 my ($self, $pod) = @_;
1389 1592
1390 # escape & and < 1593 my $xml;
1391 s/&/&amp;/g;
1392 s/(?<![BIUGH])</&lt;/g;
1393 1594
1394 # this is buggy, it needs to properly take care of nested <'s 1595 while () {
1596 if ($pod =~ /\G( (?: [^BCGHITU]+ | .(?!<) )+ )/xgcs) {
1597 $group = $1;
1395 1598
1396 1 while 1599 $group =~ s/&/&amp;/g;
1397 # replace B<>, I<>, U<> etc. 1600 $group =~ s/</&lt;/g;
1398 s/B<([^\>]*)>/<b>$1<\/b>/ 1601
1399 || s/I<([^\>]*)>/<i>$1<\/i>/ 1602 $xml .= $group;
1400 || s/U<([^\>]*)>/<u>$1<\/u>/ 1603 } elsif ($pod =~ m%\G
1401 # replace G<male|female> tags 1604 ([BCGHITU])
1402 || s{G<([^>|]*)\|([^>]*)>}{ 1605 <
1403 $self->gender ? $2 : $1 1606 (?:
1607 ([^<>]*) (?{ $group = $^N })
1608 | < $interior >
1609 )
1610 >
1611 %gcsx
1612 ) {
1613 my ($code, $data) = ($1, $group);
1614
1615 if ($code eq "B") {
1616 $xml .= "<b>" . expand_cfpod ($self, $data) . "</b>";
1617 } elsif ($code eq "I") {
1618 $xml .= "<i>" . expand_cfpod ($self, $data) . "</i>";
1619 } elsif ($code eq "U") {
1620 $xml .= "<u>" . expand_cfpod ($self, $data) . "</u>";
1621 } elsif ($code eq "C") {
1622 $xml .= "<tt>" . expand_cfpod ($self, $data) . "</tt>";
1623 } elsif ($code eq "T") {
1624 $xml .= "<big><b>" . expand_cfpod ($self, $data) . "</b></big>";
1625 } elsif ($code eq "G") {
1626 my ($male, $female) = split /\|/, $data;
1627 $data = $self->gender ? $female : $male;
1628 $xml .= expand_cfpod ($self, $data);
1629 } elsif ($code eq "H") {
1630 $xml .= ("<fg name=\"lightblue\">[" . expand_cfpod ($self, $data) . " (Use hintmode to suppress hints)]</fg>",
1631 "<fg name=\"lightblue\">[Hint suppressed, see hintmode]</fg>",
1632 "")
1633 [$self->{hintmode}];
1634 } else {
1635 $xml .= "error processing '$code($data)' directive";
1636 }
1637 } else {
1638 if ($pod =~ /\G(.+)/) {
1639 warn "parse error while expanding $pod (at $1)";
1640 }
1641 last;
1404 }ge 1642 }
1405 # replace H<hint text> 1643 }
1406 || s/H<([^\>]*)>/<fg name="lightblue">[$1]<\/fg>/g;
1407 1644
1645 for ($xml) {
1408 # create single paragraphs (very hackish) 1646 # create single paragraphs (very hackish)
1409 s/(?<=\S)\n(?=\w)/ /g; 1647 s/(?<=\S)\n(?=\w)/ /g;
1410 1648
1411 $_ 1649 # compress some whitespace
1412} 1650 s/\s+\n/\n/g; # ws line-ends
1651 s/\n\n+/\n/g; # double lines
1652 s/^\n+//; # beginning lines
1653 s/\n+$//; # ending lines
1654 }
1413 1655
1656 $xml
1657}
1658
1659no re 'eval';
1660
1661sub hintmode {
1662 $_[0]{hintmode} = $_[1] if @_ > 1;
1663 $_[0]{hintmode}
1664}
1665
1414=item $player->ext_reply ($msgid, %msg) 1666=item $player->ext_reply ($msgid, @msg)
1415 1667
1416Sends an ext reply to the player. 1668Sends an ext reply to the player.
1417 1669
1418=cut 1670=cut
1419 1671
1420sub ext_reply($$%) { 1672sub ext_reply($$@) {
1421 my ($self, $id, %msg) = @_; 1673 my ($self, $id, @msg) = @_;
1422 1674
1423 $msg{msgid} = $id; 1675 $self->ns->ext_reply ($id, @msg)
1424 $self->send ("ext " . cf::to_json \%msg);
1425} 1676}
1426 1677
1427=item $player->ext_event ($type, %msg) 1678=item $player->ext_msg ($type, @msg)
1428 1679
1429Sends an ext event to the client. 1680Sends an ext event to the client.
1430 1681
1431=cut 1682=cut
1432 1683
1433sub ext_event($$%) { 1684sub ext_msg($$@) {
1434 my ($self, $type, %msg) = @_; 1685 my ($self, $type, @msg) = @_;
1435 1686
1436 $self->ns->ext_event ($type, %msg); 1687 $self->ns->ext_msg ($type, @msg);
1437} 1688}
1438 1689
1439=head3 cf::region 1690=head3 cf::region
1440 1691
1441=over 4 1692=over 4
1454 my ($path) = @_; 1705 my ($path) = @_;
1455 1706
1456 my ($match, $specificity); 1707 my ($match, $specificity);
1457 1708
1458 for my $region (list) { 1709 for my $region (list) {
1459 if ($region->match && $path =~ $region->match) { 1710 if ($region->{match} && $path =~ $region->{match}) {
1460 ($match, $specificity) = ($region, $region->specificity) 1711 ($match, $specificity) = ($region, $region->specificity)
1461 if $region->specificity > $specificity; 1712 if $region->specificity > $specificity;
1462 } 1713 }
1463 } 1714 }
1464 1715
1584 $self->init; # pass $1 etc. 1835 $self->init; # pass $1 etc.
1585 return $self; 1836 return $self;
1586 } 1837 }
1587 } 1838 }
1588 1839
1589 Carp::carp "unable to resolve path '$path' (base '$base')."; 1840 Carp::cluck "unable to resolve path '$path' (base '$base').";
1590 () 1841 ()
1591} 1842}
1592 1843
1593sub init { 1844sub init {
1594 my ($self) = @_; 1845 my ($self) = @_;
1657 1908
1658sub load_header_from($) { 1909sub load_header_from($) {
1659 my ($self, $path) = @_; 1910 my ($self, $path) = @_;
1660 1911
1661 utf8::encode $path; 1912 utf8::encode $path;
1662 #aio_open $path, O_RDONLY, 0 1913 my $f = new_from_file cf::object::thawer $path
1663 # or return;
1664
1665 $self->_load_header ($path)
1666 or return; 1914 or return;
1915
1916 $self->_load_header ($f)
1917 or return;
1918
1919 local $MAP_LOADING{$self->{path}} = $self;
1920 $f->resolve_delayed_derefs;
1667 1921
1668 $self->{load_path} = $path; 1922 $self->{load_path} = $path;
1669 1923
1670 1 1924 1
1671} 1925}
1725sub find { 1979sub find {
1726 my ($path, $origin) = @_; 1980 my ($path, $origin) = @_;
1727 1981
1728 $path = normalise $path, $origin && $origin->path; 1982 $path = normalise $path, $origin && $origin->path;
1729 1983
1984 cf::lock_wait "map_data:$path";#d#remove
1730 cf::lock_wait "map_find:$path"; 1985 cf::lock_wait "map_find:$path";
1731 1986
1732 $cf::MAP{$path} || do { 1987 $cf::MAP{$path} || do {
1733 my $guard = cf::lock_acquire "map_find:$path"; 1988 my $guard1 = cf::lock_acquire "map_find:$path";
1989 my $guard2 = cf::lock_acquire "map_data:$path"; # just for the fun of it
1990
1734 my $map = new_from_path cf::map $path 1991 my $map = new_from_path cf::map $path
1735 or return; 1992 or return;
1736 1993
1737 $map->{last_save} = $cf::RUNTIME; 1994 $map->{last_save} = $cf::RUNTIME;
1738 1995
1740 or return; 1997 or return;
1741 1998
1742 if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?) 1999 if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?)
1743 # doing this can freeze the server in a sync job, obviously 2000 # doing this can freeze the server in a sync job, obviously
1744 #$cf::WAIT_FOR_TICK->wait; 2001 #$cf::WAIT_FOR_TICK->wait;
2002 undef $guard1;
2003 undef $guard2;
1745 $map->reset; 2004 $map->reset;
1746 undef $guard;
1747 return find $path; 2005 return find $path;
1748 } 2006 }
1749 2007
1750 $cf::MAP{$path} = $map 2008 $cf::MAP{$path} = $map
1751 } 2009 }
1760 local $self->{deny_reset} = 1; # loading can take a long time 2018 local $self->{deny_reset} = 1; # loading can take a long time
1761 2019
1762 my $path = $self->{path}; 2020 my $path = $self->{path};
1763 2021
1764 { 2022 {
1765 my $guard = cf::lock_acquire "map_load:$path"; 2023 my $guard = cf::lock_acquire "map_data:$path";
1766 2024
2025 return unless $self->valid;
1767 return if $self->in_memory != cf::MAP_SWAPPED; 2026 return unless $self->in_memory == cf::MAP_SWAPPED;
1768 2027
1769 $self->in_memory (cf::MAP_LOADING); 2028 $self->in_memory (cf::MAP_LOADING);
1770 2029
1771 $self->alloc; 2030 $self->alloc;
1772 2031
1773 $self->pre_load; 2032 $self->pre_load;
1774 Coro::cede; 2033 cf::cede_to_tick;
1775 2034
2035 my $f = new_from_file cf::object::thawer $self->{load_path};
2036 $f->skip_block;
1776 $self->_load_objects ($self->{load_path}, 1) 2037 $self->_load_objects ($f)
1777 or return; 2038 or return;
1778 2039
1779 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1) 2040 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
1780 if delete $self->{load_original}; 2041 if delete $self->{load_original};
1781 2042
1782 if (my $uniq = $self->uniq_path) { 2043 if (my $uniq = $self->uniq_path) {
1783 utf8::encode $uniq; 2044 utf8::encode $uniq;
1784 if (aio_open $uniq, O_RDONLY, 0) { 2045 unless (aio_stat $uniq) {
2046 if (my $f = new_from_file cf::object::thawer $uniq) {
1785 $self->clear_unique_items; 2047 $self->clear_unique_items;
1786 $self->_load_objects ($uniq, 0); 2048 $self->_load_objects ($f);
2049 $f->resolve_delayed_derefs;
2050 }
1787 } 2051 }
1788 } 2052 }
1789 2053
1790 Coro::cede; 2054 $f->resolve_delayed_derefs;
2055
2056 cf::cede_to_tick;
1791 # now do the right thing for maps 2057 # now do the right thing for maps
1792 $self->link_multipart_objects; 2058 $self->link_multipart_objects;
1793 $self->difficulty ($self->estimate_difficulty) 2059 $self->difficulty ($self->estimate_difficulty)
1794 unless $self->difficulty; 2060 unless $self->difficulty;
1795 Coro::cede; 2061 cf::cede_to_tick;
1796 2062
1797 unless ($self->{deny_activate}) { 2063 unless ($self->{deny_activate}) {
1798 $self->decay_objects; 2064 $self->decay_objects;
1799 $self->fix_auto_apply; 2065 $self->fix_auto_apply;
1800 $self->update_buttons; 2066 $self->update_buttons;
1801 Coro::cede; 2067 cf::cede_to_tick;
1802 $self->set_darkness_map; 2068 $self->set_darkness_map;
1803 Coro::cede; 2069 cf::cede_to_tick;
1804 $self->activate; 2070 $self->activate;
1805 } 2071 }
2072
2073 $self->{last_save} = $cf::RUNTIME;
2074 $self->last_access ($cf::RUNTIME);
1806 2075
1807 $self->in_memory (cf::MAP_IN_MEMORY); 2076 $self->in_memory (cf::MAP_IN_MEMORY);
1808 } 2077 }
1809 2078
1810 $self->post_load; 2079 $self->post_load;
1821 2090
1822 $self 2091 $self
1823} 2092}
1824 2093
1825# find and load all maps in the 3x3 area around a map 2094# find and load all maps in the 3x3 area around a map
1826sub load_diag { 2095sub load_neighbours {
1827 my ($map) = @_; 2096 my ($map) = @_;
1828 2097
1829 my @diag; # diagonal neighbours 2098 my @neigh; # diagonal neighbours
1830 2099
1831 for (0 .. 3) { 2100 for (0 .. 3) {
1832 my $neigh = $map->tile_path ($_) 2101 my $neigh = $map->tile_path ($_)
1833 or next; 2102 or next;
1834 $neigh = find $neigh, $map 2103 $neigh = find $neigh, $map
1835 or next; 2104 or next;
1836 $neigh->load; 2105 $neigh->load;
1837 2106
2107 push @neigh,
1838 push @diag, [$neigh->tile_path (($_ + 3) % 4), $neigh], 2108 [$neigh->tile_path (($_ + 3) % 4), $neigh],
1839 [$neigh->tile_path (($_ + 1) % 4), $neigh]; 2109 [$neigh->tile_path (($_ + 1) % 4), $neigh];
1840 } 2110 }
1841 2111
1842 for (@diag) { 2112 for (grep defined $_->[0], @neigh) {
2113 my ($path, $origin) = @$_;
1843 my $neigh = find @$_ 2114 my $neigh = find $path, $origin
1844 or next; 2115 or next;
1845 $neigh->load; 2116 $neigh->load;
1846 } 2117 }
1847} 2118}
1848 2119
1853} 2124}
1854 2125
1855sub do_load_sync { 2126sub do_load_sync {
1856 my ($map) = @_; 2127 my ($map) = @_;
1857 2128
2129 cf::LOG cf::llevDebug | cf::logBacktrace, "do_load_sync"
2130 if $Coro::current == $Coro::main;
2131
1858 cf::sync_job { $map->load }; 2132 cf::sync_job { $map->load };
1859} 2133}
1860 2134
1861our %MAP_PREFETCH; 2135our %MAP_PREFETCH;
1862our $MAP_PREFETCHER = undef; 2136our $MAP_PREFETCHER = undef;
1863 2137
1864sub find_async { 2138sub find_async {
1865 my ($path, $origin) = @_; 2139 my ($path, $origin, $load) = @_;
1866 2140
1867 $path = normalise $path, $origin && $origin->{path}; 2141 $path = normalise $path, $origin && $origin->{path};
1868 2142
1869 if (my $map = $cf::MAP{$path}) { 2143 if (my $map = $cf::MAP{$path}) {
1870 return $map if $map->in_memory == cf::MAP_IN_MEMORY; 2144 return $map if !$load || $map->in_memory == cf::MAP_IN_MEMORY;
1871 } 2145 }
1872 2146
1873 undef $MAP_PREFETCH{$path}; 2147 $MAP_PREFETCH{$path} |= $load;
2148
1874 $MAP_PREFETCHER ||= cf::async { 2149 $MAP_PREFETCHER ||= cf::async {
2150 $Coro::current->{desc} = "map prefetcher";
2151
1875 while (%MAP_PREFETCH) { 2152 while (%MAP_PREFETCH) {
1876 for my $path (keys %MAP_PREFETCH) { 2153 while (my ($k, $v) = each %MAP_PREFETCH) {
1877 my $map = find $path 2154 if (my $map = find $k) {
1878 or next;
1879 $map->load; 2155 $map->load if $v;
2156 }
1880 2157
1881 delete $MAP_PREFETCH{$path}; 2158 delete $MAP_PREFETCH{$k};
1882 } 2159 }
1883 } 2160 }
1884 undef $MAP_PREFETCHER; 2161 undef $MAP_PREFETCHER;
1885 }; 2162 };
1886 $MAP_PREFETCHER->prio (6); 2163 $MAP_PREFETCHER->prio (6);
1889} 2166}
1890 2167
1891sub save { 2168sub save {
1892 my ($self) = @_; 2169 my ($self) = @_;
1893 2170
1894 my $lock = cf::lock_acquire "map_data:" . $self->path; 2171 my $lock = cf::lock_acquire "map_data:$self->{path}";
1895 2172
1896 $self->{last_save} = $cf::RUNTIME; 2173 $self->{last_save} = $cf::RUNTIME;
1897 2174
1898 return unless $self->dirty; 2175 return unless $self->dirty;
1899 2176
1905 return if $self->{deny_save}; 2182 return if $self->{deny_save};
1906 2183
1907 local $self->{last_access} = $self->last_access;#d# 2184 local $self->{last_access} = $self->last_access;#d#
1908 2185
1909 cf::async { 2186 cf::async {
2187 $Coro::current->{desc} = "map player save";
1910 $_->contr->save for $self->players; 2188 $_->contr->save for $self->players;
1911 }; 2189 };
1912 2190
1913 if ($uniq) { 2191 if ($uniq) {
1914 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS); 2192 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
1922 my ($self) = @_; 2200 my ($self) = @_;
1923 2201
1924 # save first because save cedes 2202 # save first because save cedes
1925 $self->save; 2203 $self->save;
1926 2204
1927 my $lock = cf::lock_acquire "map_data:" . $self->path; 2205 my $lock = cf::lock_acquire "map_data:$self->{path}";
1928 2206
1929 return if $self->players; 2207 return if $self->players;
1930 return if $self->in_memory != cf::MAP_IN_MEMORY; 2208 return if $self->in_memory != cf::MAP_IN_MEMORY;
1931 return if $self->{deny_save}; 2209 return if $self->{deny_save};
1932 2210
2211 $self->in_memory (cf::MAP_SWAPPED);
2212
2213 $self->deactivate;
2214 $_->clear_links_to ($self) for values %cf::MAP;
1933 $self->clear; 2215 $self->clear;
1934 $self->in_memory (cf::MAP_SWAPPED);
1935} 2216}
1936 2217
1937sub reset_at { 2218sub reset_at {
1938 my ($self) = @_; 2219 my ($self) = @_;
1939 2220
1971 if $uniq; 2252 if $uniq;
1972 } 2253 }
1973 2254
1974 delete $cf::MAP{$self->path}; 2255 delete $cf::MAP{$self->path};
1975 2256
2257 $self->deactivate;
2258 $_->clear_links_to ($self) for values %cf::MAP;
1976 $self->clear; 2259 $self->clear;
1977
1978 $_->clear_links_to ($self) for values %cf::MAP;
1979 2260
1980 $self->unlink_save; 2261 $self->unlink_save;
1981 $self->destroy; 2262 $self->destroy;
1982} 2263}
1983 2264
1984my $nuke_counter = "aaaa"; 2265my $nuke_counter = "aaaa";
1985 2266
1986sub nuke { 2267sub nuke {
1987 my ($self) = @_; 2268 my ($self) = @_;
1988 2269
2270 {
2271 my $lock = cf::lock_acquire "map_data:$self->{path}";
2272
1989 delete $cf::MAP{$self->path}; 2273 delete $cf::MAP{$self->path};
1990 2274
1991 $self->unlink_save; 2275 $self->unlink_save;
1992 2276
1993 bless $self, "cf::map"; 2277 bless $self, "cf::map";
1994 delete $self->{deny_reset}; 2278 delete $self->{deny_reset};
1995 $self->{deny_save} = 1; 2279 $self->{deny_save} = 1;
1996 $self->reset_timeout (1); 2280 $self->reset_timeout (1);
1997 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++)); 2281 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++));
1998 2282
1999 $cf::MAP{$self->path} = $self; 2283 $cf::MAP{$self->path} = $self;
2284 }
2000 2285
2001 $self->reset; # polite request, might not happen 2286 $self->reset; # polite request, might not happen
2002} 2287}
2003 2288
2004=item $maps = cf::map::tmp_maps 2289=item $maps = cf::map::tmp_maps
2080 2365
2081sub inv_recursive { 2366sub inv_recursive {
2082 inv_recursive_ inv $_[0] 2367 inv_recursive_ inv $_[0]
2083} 2368}
2084 2369
2370=item $ref = $ob->ref
2371
2372creates and returns a persistent reference to an objetc that can be stored as a string.
2373
2374=item $ob = cf::object::deref ($refstring)
2375
2376returns the objetc referenced by refstring. may return undef when it cnanot find the object,
2377even if the object actually exists. May block.
2378
2379=cut
2380
2381sub deref {
2382 my ($ref) = @_;
2383
2384 if ($ref =~ m{^player\/(<1\.[0-9a-f]+>)/(.*)$}) {
2385 my ($uuid, $name) = ($1, $2);
2386 my $pl = $cf::PLAYER_LOADING{$name} || cf::player::find $name
2387 or return;
2388 $pl->ob->uuid eq $uuid
2389 or return;
2390
2391 $pl->ob
2392 } else {
2393 warn "$ref: cannot resolve object reference\n";
2394 undef
2395 }
2396}
2397
2085package cf; 2398package cf;
2086 2399
2087=back 2400=back
2088 2401
2089=head3 cf::object::player 2402=head3 cf::object::player
2111 2424
2112 } else { 2425 } else {
2113 my $pl = $self->contr; 2426 my $pl = $self->contr;
2114 2427
2115 if ($pl->{npc_dialog} && $pl->{npc_dialog}->{id}) { 2428 if ($pl->{npc_dialog} && $pl->{npc_dialog}->{id}) {
2116 my $diag = $pl->{npc_dialog}; 2429 my $dialog = $pl->{npc_dialog};
2117 $diag->{pl}->ext_reply ( 2430 $dialog->{pl}->ext_msg ($dialog->{id}, update => msg => $dialog->{pl}->expand_cfpod ($msg));
2118 $diag->{id},
2119 msgtype => "reply",
2120 msg => $diag->{pl}->expand_cfpod ($msg),
2121 add_topics => []
2122 );
2123 2431
2124 } else { 2432 } else {
2125 $msg = $npc->name . " says: $msg" if $npc; 2433 $msg = $npc->name . " says: $msg" if $npc;
2126 $self->message ($msg, $flags); 2434 $self->message ($msg, $flags);
2127 } 2435 }
2128 } 2436 }
2437}
2438
2439=item $object->send_msg ($channel, $msg, $color, [extra...])
2440
2441=cut
2442
2443sub cf::object::send_msg {
2444 my $pl = shift->contr
2445 or return;
2446 $pl->send_msg (@_);
2129} 2447}
2130 2448
2131=item $player_object->may ("access") 2449=item $player_object->may ("access")
2132 2450
2133Returns wether the given player is authorized to access resource "access" 2451Returns wether the given player is authorized to access resource "access"
2212 # use -1 or undef as default coordinates, not 0, 0 2530 # use -1 or undef as default coordinates, not 0, 0
2213 ($x, $y) = ($map->enter_x, $map->enter_y) 2531 ($x, $y) = ($map->enter_x, $map->enter_y)
2214 if $x <=0 && $y <= 0; 2532 if $x <=0 && $y <= 0;
2215 2533
2216 $map->load; 2534 $map->load;
2217 $map->load_diag; 2535 $map->load_neighbours;
2218 2536
2219 return unless $self->contr->active; 2537 return unless $self->contr->active;
2220 $self->activate_recursive; 2538 $self->activate_recursive;
2221 2539
2222 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext 2540 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext
2223 $self->enter_map ($map, $x, $y); 2541 $self->enter_map ($map, $x, $y);
2224} 2542}
2225 2543
2226=item $player_object->goto ($path, $x, $y[, $check->($map)]) 2544=item $player_object->goto ($path, $x, $y[, $check->($map)[, $done->()]])
2227 2545
2228Moves the player to the given map-path and coordinates by first freezing 2546Moves the player to the given map-path and coordinates by first freezing
2229her, loading and preparing them map, calling the provided $check callback 2547her, loading and preparing them map, calling the provided $check callback
2230that has to return the map if sucecssful, and then unfreezes the player on 2548that has to return the map if sucecssful, and then unfreezes the player on
2231the new (success) or old (failed) map position. 2549the new (success) or old (failed) map position. In either case, $done will
2550be called at the end of this process.
2232 2551
2233=cut 2552=cut
2234 2553
2235our $GOTOGEN; 2554our $GOTOGEN;
2236 2555
2237sub cf::object::player::goto { 2556sub cf::object::player::goto {
2238 my ($self, $path, $x, $y, $check) = @_; 2557 my ($self, $path, $x, $y, $check, $done) = @_;
2239 2558
2240 # do generation counting so two concurrent goto's will be executed in-order 2559 # do generation counting so two concurrent goto's will be executed in-order
2241 my $gen = $self->{_goto_generation} = ++$GOTOGEN; 2560 my $gen = $self->{_goto_generation} = ++$GOTOGEN;
2242 2561
2243 $self->enter_link; 2562 $self->enter_link;
2244 2563
2245 (async { 2564 (async {
2565 $Coro::current->{desc} = "player::goto $path $x $y";
2566
2567 # *tag paths override both path and x|y
2568 if ($path =~ /^\*(.*)$/) {
2569 if (my @obs = grep $_->map, ext::map_tags::find $1) {
2570 my $ob = $obs[rand @obs];
2571
2572 # see if we actually can go there
2573 if (@obs = grep !$self->blocked ($_->map, $_->x, $_->y), $ob, $ob->tail) {
2574 $ob = $obs[rand @obs];
2575 } else {
2576 $self->message ("Wow, it's pretty crowded in there.", cf::NDI_UNIQUE | cf::NDI_RED);
2577 }
2578 # else put us there anyways for now #d#
2579
2580 ($path, $x, $y) = ($ob->map, $ob->x, $ob->y);
2581 } else {
2582 ($path, $x, $y) = (undef, undef, undef);
2583 }
2584 }
2585
2246 my $map = eval { 2586 my $map = eval {
2247 my $map = cf::map::find $path; 2587 my $map = defined $path ? cf::map::find $path : undef;
2248 2588
2249 if ($map) { 2589 if ($map) {
2250 $map = $map->customise_for ($self); 2590 $map = $map->customise_for ($self);
2251 $map = $check->($map) if $check && $map; 2591 $map = $check->($map) if $check && $map;
2252 } else { 2592 } else {
2253 $self->message ("The exit to '$path' is closed", cf::NDI_UNIQUE | cf::NDI_RED); 2593 $self->message ("The exit to '$path' is closed.", cf::NDI_UNIQUE | cf::NDI_RED);
2254 } 2594 }
2255 2595
2256 $map 2596 $map
2257 }; 2597 };
2258 2598
2263 2603
2264 if ($gen == $self->{_goto_generation}) { 2604 if ($gen == $self->{_goto_generation}) {
2265 delete $self->{_goto_generation}; 2605 delete $self->{_goto_generation};
2266 $self->leave_link ($map, $x, $y); 2606 $self->leave_link ($map, $x, $y);
2267 } 2607 }
2608
2609 $done->() if $done;
2268 })->prio (1); 2610 })->prio (1);
2269} 2611}
2270 2612
2271=item $player_object->enter_exit ($exit_object) 2613=item $player_object->enter_exit ($exit_object)
2272 2614
2309 $rmp->{origin_y} = $exit->y; 2651 $rmp->{origin_y} = $exit->y;
2310 } 2652 }
2311 2653
2312 $rmp->{random_seed} ||= $exit->random_seed; 2654 $rmp->{random_seed} ||= $exit->random_seed;
2313 2655
2314 my $data = cf::to_json $rmp; 2656 my $data = cf::encode_json $rmp;
2315 my $md5 = Digest::MD5::md5_hex $data; 2657 my $md5 = Digest::MD5::md5_hex $data;
2316 my $meta = "$RANDOMDIR/$md5.meta"; 2658 my $meta = "$RANDOMDIR/$md5.meta";
2317 2659
2318 if (my $fh = aio_open "$meta~", O_WRONLY | O_CREAT, 0666) { 2660 if (my $fh = aio_open "$meta~", O_WRONLY | O_CREAT, 0666) {
2319 aio_write $fh, 0, (length $data), $data, 0; 2661 aio_write $fh, 0, (length $data), $data, 0;
2341 my $hp = $exit->stats->hp; 2683 my $hp = $exit->stats->hp;
2342 my $sp = $exit->stats->sp; 2684 my $sp = $exit->stats->sp;
2343 2685
2344 $self->enter_link; 2686 $self->enter_link;
2345 2687
2688 # if exit is damned, update players death & WoR home-position
2689 $self->contr->savebed ($slaying, $hp, $sp)
2690 if $exit->flag (FLAG_DAMNED);
2691
2346 (async { 2692 (async {
2693 $Coro::current->{desc} = "enter_exit $slaying $hp $sp";
2694
2347 $self->deactivate_recursive; # just to be sure 2695 $self->deactivate_recursive; # just to be sure
2348 unless (eval { 2696 unless (eval {
2349 $self->goto ($slaying, $hp, $sp); 2697 $self->goto ($slaying, $hp, $sp);
2350 2698
2351 1; 2699 1;
2377 2725
2378 utf8::encode $text; 2726 utf8::encode $text;
2379 $self->send_packet (sprintf "drawinfo %d %s", $flags || cf::NDI_BLACK, $text); 2727 $self->send_packet (sprintf "drawinfo %d %s", $flags || cf::NDI_BLACK, $text);
2380} 2728}
2381 2729
2382=item $client->send_msg ($color, $type, $msg, [extra...]) 2730=item $client->send_msg ($channel, $msg, $color, [extra...])
2383 2731
2384Send a drawinfo or msg packet to the client, formatting the msg for the 2732Send a drawinfo or msg packet to the client, formatting the msg for the
2385client if neccessary. C<$type> should be a string identifying the type of 2733client if neccessary. C<$type> should be a string identifying the type of
2386the message, with C<log> being the default. If C<$color> is negative, suppress 2734the message, with C<log> being the default. If C<$color> is negative, suppress
2387the message unless the client supports the msg packet. 2735the message unless the client supports the msg packet.
2388 2736
2389=cut 2737=cut
2390 2738
2739# non-persistent channels (usually the info channel)
2740our %CHANNEL = (
2741 "c/identify" => {
2742 id => "infobox",
2743 title => "Identify",
2744 reply => undef,
2745 tooltip => "Items recently identified",
2746 },
2747 "c/examine" => {
2748 id => "infobox",
2749 title => "Examine",
2750 reply => undef,
2751 tooltip => "Signs and other items you examined",
2752 },
2753 "c/book" => {
2754 id => "infobox",
2755 title => "Book",
2756 reply => undef,
2757 tooltip => "The contents of a note or book",
2758 },
2759 "c/lookat" => {
2760 id => "infobox",
2761 title => "Look",
2762 reply => undef,
2763 tooltip => "What you saw there",
2764 },
2765 "c/who" => {
2766 id => "infobox",
2767 title => "Players",
2768 reply => undef,
2769 tooltip => "Shows players who are currently online",
2770 },
2771 "c/body" => {
2772 id => "infobox",
2773 title => "Body Parts",
2774 reply => undef,
2775 tooltip => "Shows which body parts you posess and are available",
2776 },
2777 "c/uptime" => {
2778 id => "infobox",
2779 title => "Uptime",
2780 reply => undef,
2781 tooltip => "How long the server has been running since last restart",
2782 },
2783 "c/mapinfo" => {
2784 id => "infobox",
2785 title => "Map Info",
2786 reply => undef,
2787 tooltip => "Information related to the maps",
2788 },
2789);
2790
2391sub cf::client::send_msg { 2791sub cf::client::send_msg {
2392 my ($self, $color, $type, $msg, @extra) = @_; 2792 my ($self, $channel, $msg, $color, @extra) = @_;
2393 2793
2394 $msg = $self->pl->expand_cfpod ($msg); 2794 $msg = $self->pl->expand_cfpod ($msg);
2395 2795
2796 $color &= cf::NDI_CLIENT_MASK; # just in case...
2797
2798 # check predefined channels, for the benefit of C
2799 if ($CHANNEL{$channel}) {
2800 $channel = $CHANNEL{$channel};
2801
2802 $self->ext_msg (channel_info => $channel)
2803 if $self->can_msg;
2804
2805 $channel = $channel->{id};
2806
2807 } elsif (ref $channel) {
2808 # send meta info to client, if not yet sent
2809 unless (exists $self->{channel}{$channel->{id}}) {
2810 $self->{channel}{$channel->{id}} = $channel;
2811 $self->ext_msg (channel_info => $channel)
2812 if $self->can_msg;
2813 }
2814
2815 $channel = $channel->{id};
2816 }
2817
2818 return unless @extra || length $msg;
2819
2396 if ($self->can_msg) { 2820 if ($self->can_msg) {
2397 $self->send_packet ("msg " . cf::to_json [$color, $type, $msg, @extra]); 2821 # default colour, mask it out
2822 $color &= ~(cf::NDI_COLOR_MASK | cf::NDI_DEF)
2823 if $color & cf::NDI_DEF;
2824
2825 $self->send_packet ("msg " . $self->{json_coder}->encode (
2826 [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra]));
2398 } else { 2827 } else {
2399 # replace some tags by gcfclient-compatible ones
2400 for ($msg) {
2401 1 while
2402 s/<b>([^<]*)<\/b>/[b]${1}[\/b]/
2403 || s/<i>([^<]*)<\/i>/[i]${1}[\/i]/
2404 || s/<u>([^<]*)<\/u>/[ul]${1}[\/ul]/
2405 || s/<tt>([^<]*)<\/tt>/[fixed]${1}[\/fixed]/
2406 || s/<fg name=\"([^"]+)\">([^<]*)<\/fg>/[color=$1]${2}[\/color]/;
2407 }
2408
2409 if ($color >= 0) { 2828 if ($color >= 0) {
2829 # replace some tags by gcfclient-compatible ones
2830 for ($msg) {
2831 1 while
2832 s/<b>([^<]*)<\/b>/[b]${1}[\/b]/
2833 || s/<i>([^<]*)<\/i>/[i]${1}[\/i]/
2834 || s/<u>([^<]*)<\/u>/[ul]${1}[\/ul]/
2835 || s/<tt>([^<]*)<\/tt>/[fixed]${1}[\/fixed]/
2836 || s/<fg name=\"([^"]+)\">([^<]*)<\/fg>/[color=$1]${2}[\/color]/;
2837 }
2838
2839 $color &= cf::NDI_COLOR_MASK;
2840
2841 utf8::encode $msg;
2842
2410 if (0 && $msg =~ /\[/) { 2843 if (0 && $msg =~ /\[/) {
2844 # COMMAND/INFO
2411 $self->send_packet ("drawextinfo $color 4 0 $msg") 2845 $self->send_packet ("drawextinfo $color 10 8 $msg")
2412 } else { 2846 } else {
2413 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g; 2847 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g;
2414 $self->send_packet ("drawinfo $color $msg") 2848 $self->send_packet ("drawinfo $color $msg")
2415 } 2849 }
2416 } 2850 }
2417 } 2851 }
2418} 2852}
2419 2853
2420=item $client->ext_event ($type, %msg) 2854=item $client->ext_msg ($type, @msg)
2421 2855
2422Sends an ext event to the client. 2856Sends an ext event to the client.
2423 2857
2424=cut 2858=cut
2425 2859
2426sub cf::client::ext_event($$%) { 2860sub cf::client::ext_msg($$@) {
2427 my ($self, $type, %msg) = @_; 2861 my ($self, $type, @msg) = @_;
2428 2862
2863 if ($self->extcmd == 2) {
2864 $self->send_packet ("ext " . $self->{json_coder}->encode ([$type, @msg]));
2865 } elsif ($self->extcmd == 1) { # TODO: remove
2429 $msg{msgtype} = "event_$type"; 2866 push @msg, msgtype => "event_$type";
2430 $self->send_packet ("ext " . cf::to_json \%msg); 2867 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2868 }
2869}
2870
2871=item $client->ext_reply ($msgid, @msg)
2872
2873Sends an ext reply to the client.
2874
2875=cut
2876
2877sub cf::client::ext_reply($$@) {
2878 my ($self, $id, @msg) = @_;
2879
2880 if ($self->extcmd == 2) {
2881 $self->send_packet ("ext " . $self->{json_coder}->encode (["reply-$id", @msg]));
2882 } elsif ($self->extcmd == 1) {
2883 #TODO: version 1, remove
2884 unshift @msg, msgtype => "reply", msgid => $id;
2885 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2886 }
2431} 2887}
2432 2888
2433=item $success = $client->query ($flags, "text", \&cb) 2889=item $success = $client->query ($flags, "text", \&cb)
2434 2890
2435Queues a query to the client, calling the given callback with 2891Queues a query to the client, calling the given callback with
2458 2914
2459 1 2915 1
2460} 2916}
2461 2917
2462cf::client->attach ( 2918cf::client->attach (
2919 on_connect => sub {
2920 my ($ns) = @_;
2921
2922 $ns->{json_coder} = JSON::XS->new->utf8->max_size (1e6)->convert_blessed;
2923 },
2463 on_reply => sub { 2924 on_reply => sub {
2464 my ($ns, $msg) = @_; 2925 my ($ns, $msg) = @_;
2465 2926
2466 # this weird shuffling is so that direct followup queries 2927 # this weird shuffling is so that direct followup queries
2467 # get handled first 2928 # get handled first
2482 } 2943 }
2483 }, 2944 },
2484 on_exticmd => sub { 2945 on_exticmd => sub {
2485 my ($ns, $buf) = @_; 2946 my ($ns, $buf) = @_;
2486 2947
2487 my $msg = eval { from_json $buf }; 2948 my $msg = eval { $ns->{json_coder}->decode ($buf) };
2488 2949
2489 if (ref $msg) { 2950 if (ref $msg) {
2951 my ($type, $reply, @payload) =
2952 "ARRAY" eq ref $msg
2953 ? @$msg
2954 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
2955
2956 my @reply;
2957
2490 if (my $cb = $EXTICMD{$msg->{msgtype}}) { 2958 if (my $cb = $EXTICMD{$type}) {
2491 if (my %reply = $cb->($ns, $msg)) { 2959 @reply = $cb->($ns, @payload);
2492 $reply{msgid} = $msg->{msgid};
2493 $ns->send ("ext " . cf::to_json \%reply);
2494 }
2495 } 2960 }
2961
2962 $ns->ext_reply ($reply, @reply)
2963 if $reply;
2964
2496 } else { 2965 } else {
2497 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n"; 2966 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n";
2498 } 2967 }
2499 2968
2500 cf::override; 2969 cf::override;
2547our $safe = new Safe "safe"; 3016our $safe = new Safe "safe";
2548our $safe_hole = new Safe::Hole; 3017our $safe_hole = new Safe::Hole;
2549 3018
2550$SIG{FPE} = 'IGNORE'; 3019$SIG{FPE} = 'IGNORE';
2551 3020
2552$safe->permit_only (Opcode::opset qw(:base_core :base_mem :base_orig :base_math sort time)); 3021$safe->permit_only (Opcode::opset qw(
3022 :base_core :base_mem :base_orig :base_math
3023 grepstart grepwhile mapstart mapwhile
3024 sort time
3025));
2553 3026
2554# here we export the classes and methods available to script code 3027# here we export the classes and methods available to script code
2555 3028
2556=pod 3029=pod
2557 3030
2558The following functions and methods are available within a safe environment: 3031The following functions and methods are available within a safe environment:
2559 3032
2560 cf::object contr pay_amount pay_player map 3033 cf::object
3034 contr pay_amount pay_player map x y force_find force_add destroy
3035 insert remove name archname title slaying race decrease_ob_nr
3036
2561 cf::object::player player 3037 cf::object::player
2562 cf::player peaceful 3038 player
2563 cf::map trigger 3039
3040 cf::player
3041 peaceful
3042
3043 cf::map
3044 trigger
2564 3045
2565=cut 3046=cut
2566 3047
2567for ( 3048for (
2568 ["cf::object" => qw(contr pay_amount pay_player map)], 3049 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y
3050 insert remove inv name archname title slaying race
3051 decrease_ob_nr destroy)],
2569 ["cf::object::player" => qw(player)], 3052 ["cf::object::player" => qw(player)],
2570 ["cf::player" => qw(peaceful)], 3053 ["cf::player" => qw(peaceful)],
2571 ["cf::map" => qw(trigger)], 3054 ["cf::map" => qw(trigger)],
2572) { 3055) {
2573 no strict 'refs'; 3056 no strict 'refs';
2649# the server's init and main functions 3132# the server's init and main functions
2650 3133
2651sub load_facedata($) { 3134sub load_facedata($) {
2652 my ($path) = @_; 3135 my ($path) = @_;
2653 3136
3137 # HACK to clear player env face cache, we need some signal framework
3138 # for this (global event?)
3139 %ext::player_env::MUSIC_FACE_CACHE = ();
3140
3141 my $enc = JSON::XS->new->utf8->canonical->relaxed;
3142
2654 warn "loading facedata from $path\n"; 3143 warn "loading facedata from $path\n";
2655 3144
2656 my $facedata; 3145 my $facedata;
2657 0 < aio_load $path, $facedata 3146 0 < aio_load $path, $facedata
2658 or die "$path: $!"; 3147 or die "$path: $!";
2660 $facedata = Coro::Storable::thaw $facedata; 3149 $facedata = Coro::Storable::thaw $facedata;
2661 3150
2662 $facedata->{version} == 2 3151 $facedata->{version} == 2
2663 or cf::cleanup "$path: version mismatch, cannot proceed."; 3152 or cf::cleanup "$path: version mismatch, cannot proceed.";
2664 3153
3154 # patch in the exptable
3155 $facedata->{resource}{"res/exp_table"} = {
3156 type => FT_RSRC,
3157 data => $enc->encode ([map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]),
3158 };
3159 cf::cede_to_tick;
3160
2665 { 3161 {
2666 my $faces = $facedata->{faceinfo}; 3162 my $faces = $facedata->{faceinfo};
2667 3163
2668 while (my ($face, $info) = each %$faces) { 3164 while (my ($face, $info) = each %$faces) {
2669 my $idx = (cf::face::find $face) || cf::face::alloc $face; 3165 my $idx = (cf::face::find $face) || cf::face::alloc $face;
3166
2670 cf::face::set $idx, $info->{visibility}, $info->{magicmap}; 3167 cf::face::set_visibility $idx, $info->{visibility};
3168 cf::face::set_magicmap $idx, $info->{magicmap};
2671 cf::face::set_data $idx, 0, $info->{data32}, $info->{chksum32}; 3169 cf::face::set_data $idx, 0, $info->{data32}, Digest::MD5::md5 $info->{data32};
2672 cf::face::set_data $idx, 1, $info->{data64}, $info->{chksum64}; 3170 cf::face::set_data $idx, 1, $info->{data64}, Digest::MD5::md5 $info->{data64};
2673 Coro::cede; 3171
3172 cf::cede_to_tick;
2674 } 3173 }
2675 3174
2676 while (my ($face, $info) = each %$faces) { 3175 while (my ($face, $info) = each %$faces) {
2677 next unless $info->{smooth}; 3176 next unless $info->{smooth};
3177
2678 my $idx = cf::face::find $face 3178 my $idx = cf::face::find $face
2679 or next; 3179 or next;
3180
2680 if (my $smooth = cf::face::find $info->{smooth}) { 3181 if (my $smooth = cf::face::find $info->{smooth}) {
3182 cf::face::set_smooth $idx, $smooth;
2681 cf::face::set_smooth $idx, $smooth, $info->{smoothlevel}; 3183 cf::face::set_smoothlevel $idx, $info->{smoothlevel};
2682 } else { 3184 } else {
2683 warn "smooth face '$info->{smooth}' not found for face '$face'"; 3185 warn "smooth face '$info->{smooth}' not found for face '$face'";
2684 } 3186 }
2685 Coro::cede; 3187
3188 cf::cede_to_tick;
2686 } 3189 }
2687 } 3190 }
2688 3191
2689 { 3192 {
2690 my $anims = $facedata->{animinfo}; 3193 my $anims = $facedata->{animinfo};
2691 3194
2692 while (my ($anim, $info) = each %$anims) { 3195 while (my ($anim, $info) = each %$anims) {
2693 cf::anim::set $anim, $info->{frames}, $info->{facings}; 3196 cf::anim::set $anim, $info->{frames}, $info->{facings};
2694 Coro::cede; 3197 cf::cede_to_tick;
2695 } 3198 }
2696 3199
2697 cf::anim::invalidate_all; # d'oh 3200 cf::anim::invalidate_all; # d'oh
2698 } 3201 }
2699 3202
3203 {
3204 # TODO: for gcfclient pleasure, we should give resources
3205 # that gcfclient doesn't grok a >10000 face index.
3206 my $res = $facedata->{resource};
3207
3208 while (my ($name, $info) = each %$res) {
3209 if (defined $info->{type}) {
3210 my $idx = (cf::face::find $name) || cf::face::alloc $name;
3211 my $data;
3212
3213 if ($info->{type} & 1) {
3214 # prepend meta info
3215
3216 my $meta = $enc->encode ({
3217 name => $name,
3218 %{ $info->{meta} || {} },
3219 });
3220
3221 $data = pack "(w/a*)*", $meta, $info->{data};
3222 } else {
3223 $data = $info->{data};
3224 }
3225
3226 cf::face::set_data $idx, 0, $data, Digest::MD5::md5 $data;
3227 cf::face::set_type $idx, $info->{type};
3228 } else {
3229 $RESOURCE{$name} = $info;
3230 }
3231
3232 cf::cede_to_tick;
3233 }
3234 }
3235
3236 cf::global->invoke (EVENT_GLOBAL_RESOURCE_UPDATE);
3237
2700 1 3238 1
2701} 3239}
2702 3240
3241cf::global->attach (on_resource_update => sub {
3242 if (my $soundconf = $RESOURCE{"res/sound.conf"}) {
3243 $soundconf = JSON::XS->new->utf8->relaxed->decode ($soundconf->{data});
3244
3245 for (0 .. SOUND_CAST_SPELL_0 - 1) {
3246 my $sound = $soundconf->{compat}[$_]
3247 or next;
3248
3249 my $face = cf::face::find "sound/$sound->[1]";
3250 cf::sound::set $sound->[0] => $face;
3251 cf::sound::old_sound_index $_, $face; # gcfclient-compat
3252 }
3253
3254 while (my ($k, $v) = each %{$soundconf->{event}}) {
3255 my $face = cf::face::find "sound/$v";
3256 cf::sound::set $k => $face;
3257 }
3258 }
3259});
3260
3261register_exticmd fx_want => sub {
3262 my ($ns, $want) = @_;
3263
3264 while (my ($k, $v) = each %$want) {
3265 $ns->fx_want ($k, $v);
3266 }
3267};
3268
2703sub reload_regions { 3269sub reload_regions {
3270 # HACK to clear player env face cache, we need some signal framework
3271 # for this (global event?)
3272 %ext::player_env::MUSIC_FACE_CACHE = ();
3273
2704 load_resource_file "$MAPDIR/regions" 3274 load_resource_file "$MAPDIR/regions"
2705 or die "unable to load regions file\n"; 3275 or die "unable to load regions file\n";
3276
3277 for (cf::region::list) {
3278 $_->{match} = qr/$_->{match}/
3279 if exists $_->{match};
3280 }
2706} 3281}
2707 3282
2708sub reload_facedata { 3283sub reload_facedata {
2709 load_facedata "$DATADIR/facedata" 3284 load_facedata "$DATADIR/facedata"
2710 or die "unable to load facedata\n"; 3285 or die "unable to load facedata\n";
2711} 3286}
2712 3287
2713sub reload_archetypes { 3288sub reload_archetypes {
3289 load_resource_file "$DATADIR/archetypes"
3290 or die "unable to load archetypes\n";
3291 #d# NEED to laod twice to resolve forward references
3292 # this really needs to be done in an extra post-pass
3293 # (which needs to be synchronous, so solve it differently)
2714 load_resource_file "$DATADIR/archetypes" 3294 load_resource_file "$DATADIR/archetypes"
2715 or die "unable to load archetypes\n"; 3295 or die "unable to load archetypes\n";
2716} 3296}
2717 3297
2718sub reload_treasures { 3298sub reload_treasures {
2734 3314
2735sub init { 3315sub init {
2736 reload_resources; 3316 reload_resources;
2737} 3317}
2738 3318
2739sub cfg_load { 3319sub reload_config {
2740 open my $fh, "<:utf8", "$CONFDIR/config" 3320 open my $fh, "<:utf8", "$CONFDIR/config"
2741 or return; 3321 or return;
2742 3322
2743 local $/; 3323 local $/;
2744 *CFG = YAML::Syck::Load <$fh>; 3324 *CFG = YAML::Load <$fh>;
2745 3325
2746 $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37]; 3326 $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37];
2747 3327
2748 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset}; 3328 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset};
2749 $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset}; 3329 $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset};
2760sub main { 3340sub main {
2761 # we must not ever block the main coroutine 3341 # we must not ever block the main coroutine
2762 local $Coro::idle = sub { 3342 local $Coro::idle = sub {
2763 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d# 3343 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
2764 (async { 3344 (async {
2765 Event::one_event; 3345 $Coro::current->{desc} = "IDLE BUG HANDLER";
3346 EV::loop EV::LOOP_ONESHOT;
2766 })->prio (Coro::PRIO_MAX); 3347 })->prio (Coro::PRIO_MAX);
2767 }; 3348 };
2768 3349
2769 cfg_load; 3350 reload_config;
2770 db_init; 3351 db_init;
2771 load_extensions; 3352 load_extensions;
2772 3353
2773 $TICK_WATCHER->start; 3354 $TICK_WATCHER->start;
3355 $Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority
2774 Event::loop; 3356 EV::loop;
2775} 3357}
2776 3358
2777############################################################################# 3359#############################################################################
2778# initialisation and cleanup 3360# initialisation and cleanup
2779 3361
2780# install some emergency cleanup handlers 3362# install some emergency cleanup handlers
2781BEGIN { 3363BEGIN {
3364 our %SIGWATCHER = ();
2782 for my $signal (qw(INT HUP TERM)) { 3365 for my $signal (qw(INT HUP TERM)) {
2783 Event->signal ( 3366 $SIGWATCHER{$signal} = EV::signal $signal, sub {
2784 reentrant => 0,
2785 data => WF_AUTOCANCEL,
2786 signal => $signal,
2787 prio => 0,
2788 cb => sub {
2789 cf::cleanup "SIG$signal"; 3367 cf::cleanup "SIG$signal";
2790 },
2791 ); 3368 };
2792 } 3369 }
2793} 3370}
2794 3371
2795sub write_runtime { 3372sub write_runtime {
2796 my $runtime = "$LOCALDIR/runtime"; 3373 my $runtime = "$LOCALDIR/runtime";
2841 # and maps saved/destroyed asynchronously. 3418 # and maps saved/destroyed asynchronously.
2842 warn "begin emergency player save\n"; 3419 warn "begin emergency player save\n";
2843 for my $login (keys %cf::PLAYER) { 3420 for my $login (keys %cf::PLAYER) {
2844 my $pl = $cf::PLAYER{$login} or next; 3421 my $pl = $cf::PLAYER{$login} or next;
2845 $pl->valid or next; 3422 $pl->valid or next;
3423 delete $pl->{unclean_save}; # not strictly necessary, but cannot hurt
2846 $pl->save; 3424 $pl->save;
2847 } 3425 }
2848 warn "end emergency player save\n"; 3426 warn "end emergency player save\n";
2849 3427
2850 warn "begin emergency map save\n"; 3428 warn "begin emergency map save\n";
2889 warn "syncing database to disk"; 3467 warn "syncing database to disk";
2890 BDB::db_env_txn_checkpoint $DB_ENV; 3468 BDB::db_env_txn_checkpoint $DB_ENV;
2891 3469
2892 # if anything goes wrong in here, we should simply crash as we already saved 3470 # if anything goes wrong in here, we should simply crash as we already saved
2893 3471
2894 warn "cancelling all WF_AUTOCANCEL watchers";
2895 for (Event::all_watchers) {
2896 $_->cancel if $_->data & WF_AUTOCANCEL;
2897 }
2898
2899 warn "flushing outstanding aio requests"; 3472 warn "flushing outstanding aio requests";
2900 for (;;) { 3473 for (;;) {
2901 BDB::flush; 3474 BDB::flush;
2902 IO::AIO::flush; 3475 IO::AIO::flush;
2903 Coro::cede; 3476 Coro::cede_notself;
2904 last unless IO::AIO::nreqs || BDB::nreqs; 3477 last unless IO::AIO::nreqs || BDB::nreqs;
2905 warn "iterate..."; 3478 warn "iterate...";
2906 } 3479 }
2907 3480
2908 ++$RELOAD; 3481 ++$RELOAD;
2967 warn "reloading cf.pm"; 3540 warn "reloading cf.pm";
2968 require cf; 3541 require cf;
2969 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt 3542 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
2970 3543
2971 warn "loading config and database again"; 3544 warn "loading config and database again";
2972 cf::cfg_load; 3545 cf::reload_config;
2973 3546
2974 warn "loading extensions"; 3547 warn "loading extensions";
2975 cf::load_extensions; 3548 cf::load_extensions;
2976 3549
2977 warn "reattaching attachments to objects/players"; 3550 warn "reattaching attachments to objects/players";
2984 warn "leaving sync_job"; 3557 warn "leaving sync_job";
2985 3558
2986 1 3559 1
2987 } or do { 3560 } or do {
2988 warn $@; 3561 warn $@;
2989 warn "error while reloading, exiting."; 3562 cf::cleanup "error while reloading, exiting.";
2990 exit 1;
2991 }; 3563 };
2992 3564
2993 warn "reloaded"; 3565 warn "reloaded";
2994}; 3566};
2995 3567
2997 3569
2998sub reload_perl() { 3570sub reload_perl() {
2999 # doing reload synchronously and two reloads happen back-to-back, 3571 # doing reload synchronously and two reloads happen back-to-back,
3000 # coro crashes during coro_state_free->destroy here. 3572 # coro crashes during coro_state_free->destroy here.
3001 3573
3002 $RELOAD_WATCHER ||= Event->timer ( 3574 $RELOAD_WATCHER ||= EV::timer 0, 0, sub {
3003 reentrant => 0,
3004 after => 0,
3005 data => WF_AUTOCANCEL,
3006 cb => sub {
3007 do_reload_perl; 3575 do_reload_perl;
3008 undef $RELOAD_WATCHER; 3576 undef $RELOAD_WATCHER;
3009 },
3010 ); 3577 };
3011} 3578}
3012 3579
3013register_command "reload" => sub { 3580register_command "reload" => sub {
3014 my ($who, $arg) = @_; 3581 my ($who, $arg) = @_;
3015 3582
3016 if ($who->flag (FLAG_WIZ)) { 3583 if ($who->flag (FLAG_WIZ)) {
3017 $who->message ("reloading server."); 3584 $who->message ("reloading server.");
3585 async {
3586 $Coro::current->{desc} = "perl_reload";
3018 async { reload_perl }; 3587 reload_perl;
3588 };
3019 } 3589 }
3020}; 3590};
3021 3591
3022unshift @INC, $LIBDIR; 3592unshift @INC, $LIBDIR;
3023 3593
3042 my $signal = new Coro::Signal; 3612 my $signal = new Coro::Signal;
3043 push @WAIT_FOR_TICK_BEGIN, $signal; 3613 push @WAIT_FOR_TICK_BEGIN, $signal;
3044 $signal->wait; 3614 $signal->wait;
3045} 3615}
3046 3616
3047 my $min = 1e6;#d# 3617$TICK_WATCHER = EV::periodic_ns 0, $TICK, 0, sub {
3048 my $avg = 10;
3049$TICK_WATCHER = Event->timer (
3050 reentrant => 0,
3051 parked => 1,
3052 prio => 0,
3053 at => $NEXT_TICK || $TICK,
3054 data => WF_AUTOCANCEL,
3055 cb => sub {
3056 if ($Coro::current != $Coro::main) { 3618 if ($Coro::current != $Coro::main) {
3057 Carp::cluck "major BUG: server tick called outside of main coro, skipping it" 3619 Carp::cluck "major BUG: server tick called outside of main coro, skipping it"
3058 unless ++$bug_warning > 10; 3620 unless ++$bug_warning > 10;
3059 return; 3621 return;
3060 } 3622 }
3061 3623
3062 $NOW = $tick_start = Event::time; 3624 $NOW = $tick_start = EV::now;
3063 3625
3064 cf::server_tick; # one server iteration 3626 cf::server_tick; # one server iteration
3065 3627
3066 0 && sync_job {#d# 3628 $RUNTIME += $TICK;
3067 for(1..10) { 3629 $NEXT_TICK = $_[0]->at;
3068 my $t = Event::time;
3069 my $map = my $map = new_from_path cf::map "/tmp/x.map"
3070 or die;
3071 3630
3072 $map->width (50); 3631 if ($NOW >= $NEXT_RUNTIME_WRITE) {
3073 $map->height (50); 3632 $NEXT_RUNTIME_WRITE = List::Util::max $NEXT_RUNTIME_WRITE + 10, $NOW + 5.;
3074 $map->alloc; 3633 Coro::async_pool {
3075 $map->_load_objects ("/tmp/x.map", 1); 3634 $Coro::current->{desc} = "runtime saver";
3076 my $t = Event::time - $t; 3635 write_runtime
3077 3636 or warn "ERROR: unable to write runtime file: $!";
3078 #next unless $t < 0.0013;#d#
3079 if ($t < $min) {
3080 $min = $t;
3081 }
3082 $avg = $avg * 0.99 + $t * 0.01;
3083 }
3084 warn "XXXXXXXXXXXXXXXXXX min $min avg $avg\n";#d#
3085 exit 0;
3086 # 2007-05-22 02:33:04.569 min 0.00112509727478027 avg 0.0012259249572477
3087 }; 3637 };
3638 }
3088 3639
3089 $RUNTIME += $TICK;
3090 $NEXT_TICK += $TICK;
3091
3092 if ($NOW >= $NEXT_RUNTIME_WRITE) {
3093 $NEXT_RUNTIME_WRITE = $NOW + 10;
3094 Coro::async_pool {
3095 write_runtime
3096 or warn "ERROR: unable to write runtime file: $!";
3097 };
3098 }
3099
3100# my $AFTER = Event::time;
3101# warn $AFTER - $NOW;#d#
3102
3103 if (my $sig = shift @WAIT_FOR_TICK_BEGIN) { 3640 if (my $sig = shift @WAIT_FOR_TICK_BEGIN) {
3104 $sig->send; 3641 $sig->send;
3105 } 3642 }
3106 while (my $sig = shift @WAIT_FOR_TICK) { 3643 while (my $sig = shift @WAIT_FOR_TICK) {
3107 $sig->send; 3644 $sig->send;
3108 } 3645 }
3109 3646
3110 $NOW = Event::time;
3111
3112 # if we are delayed by four ticks or more, skip them all
3113 $NEXT_TICK = $NOW if $NOW >= $NEXT_TICK + $TICK * 4;
3114
3115 $TICK_WATCHER->at ($NEXT_TICK);
3116 $TICK_WATCHER->start;
3117
3118 $LOAD = ($NOW - $tick_start) / $TICK; 3647 $LOAD = ($NOW - $tick_start) / $TICK;
3119 $LOADAVG = $LOADAVG * 0.75 + $LOAD * 0.25; 3648 $LOADAVG = $LOADAVG * 0.75 + $LOAD * 0.25;
3120 3649
3121 _post_tick; 3650 _post_tick;
3122 3651};
3123 3652$TICK_WATCHER->priority (EV::MAXPRI);
3124 },
3125);
3126 3653
3127{ 3654{
3128 BDB::max_poll_time $TICK * 0.1; 3655 # configure BDB
3129 $BDB_POLL_WATCHER = Event->io ( 3656
3130 reentrant => 0,
3131 fd => BDB::poll_fileno,
3132 poll => 'r',
3133 prio => 0,
3134 data => WF_AUTOCANCEL,
3135 cb => \&BDB::poll_cb,
3136 );
3137 BDB::min_parallel 8; 3657 BDB::min_parallel 8;
3138 3658 BDB::max_poll_reqs $TICK * 0.1;
3139 BDB::set_sync_prepare { 3659 $Coro::BDB::WATCHER->priority (1);
3140 my $status;
3141 my $current = $Coro::current;
3142 (
3143 sub {
3144 $status = $!;
3145 $current->ready; undef $current;
3146 },
3147 sub {
3148 Coro::schedule while defined $current;
3149 $! = $status;
3150 },
3151 )
3152 };
3153 3660
3154 unless ($DB_ENV) { 3661 unless ($DB_ENV) {
3155 $DB_ENV = BDB::db_env_create; 3662 $DB_ENV = BDB::db_env_create;
3663 $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT | BDB::TXN_NOSYNC
3664 | BDB::LOG_AUTOREMOVE, 1);
3665 $DB_ENV->set_timeout (30, BDB::SET_TXN_TIMEOUT);
3666 $DB_ENV->set_timeout (30, BDB::SET_LOCK_TIMEOUT);
3156 3667
3157 cf::sync_job { 3668 cf::sync_job {
3158 eval { 3669 eval {
3159 BDB::db_env_open 3670 BDB::db_env_open
3160 $DB_ENV, 3671 $DB_ENV,
3162 BDB::INIT_LOCK | BDB::INIT_LOG | BDB::INIT_MPOOL | BDB::INIT_TXN 3673 BDB::INIT_LOCK | BDB::INIT_LOG | BDB::INIT_MPOOL | BDB::INIT_TXN
3163 | BDB::RECOVER | BDB::REGISTER | BDB::USE_ENVIRON | BDB::CREATE, 3674 | BDB::RECOVER | BDB::REGISTER | BDB::USE_ENVIRON | BDB::CREATE,
3164 0666; 3675 0666;
3165 3676
3166 cf::cleanup "db_env_open($BDBDIR): $!" if $!; 3677 cf::cleanup "db_env_open($BDBDIR): $!" if $!;
3167
3168 $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT | BDB::TXN_NOSYNC, 1);
3169 $DB_ENV->set_lk_detect;
3170 }; 3678 };
3171 3679
3172 cf::cleanup "db_env_open(db): $@" if $@; 3680 cf::cleanup "db_env_open(db): $@" if $@;
3173 }; 3681 };
3174 } 3682 }
3683
3684 $BDB_DEADLOCK_WATCHER = EV::periodic 0, 3, 0, sub {
3685 BDB::db_env_lock_detect $DB_ENV, 0, BDB::LOCK_DEFAULT, 0, sub { };
3686 };
3687 $BDB_CHECKPOINT_WATCHER = EV::periodic 0, 60, 0, sub {
3688 BDB::db_env_txn_checkpoint $DB_ENV, 0, 0, 0, sub { };
3689 };
3690 $BDB_TRICKLE_WATCHER = EV::periodic 0, 10, 0, sub {
3691 BDB::db_env_memp_trickle $DB_ENV, 20, 0, sub { };
3692 };
3175} 3693}
3176 3694
3177{ 3695{
3696 # configure IO::AIO
3697
3178 IO::AIO::min_parallel 8; 3698 IO::AIO::min_parallel 8;
3179
3180 undef $Coro::AIO::WATCHER;
3181 IO::AIO::max_poll_time $TICK * 0.1; 3699 IO::AIO::max_poll_time $TICK * 0.1;
3182 $AIO_POLL_WATCHER = Event->io ( 3700 $Coro::AIO::WATCHER->priority (1);
3183 reentrant => 0,
3184 data => WF_AUTOCANCEL,
3185 fd => IO::AIO::poll_fileno,
3186 poll => 'r',
3187 prio => 6,
3188 cb => \&IO::AIO::poll_cb,
3189 );
3190} 3701}
3191 3702
3192my $_log_backtrace; 3703my $_log_backtrace;
3193 3704
3194sub _log_backtrace { 3705sub _log_backtrace {
3198 3709
3199 # limit the # of concurrent backtraces 3710 # limit the # of concurrent backtraces
3200 if ($_log_backtrace < 2) { 3711 if ($_log_backtrace < 2) {
3201 ++$_log_backtrace; 3712 ++$_log_backtrace;
3202 async { 3713 async {
3714 $Coro::current->{desc} = "abt $msg";
3715
3203 my @bt = fork_call { 3716 my @bt = fork_call {
3204 @addr = map { sprintf "%x", $_ } @addr; 3717 @addr = map { sprintf "%x", $_ } @addr;
3205 my $self = (-f "/proc/$$/exe") ? "/proc/$$/exe" : $^X; 3718 my $self = (-f "/proc/$$/exe") ? "/proc/$$/exe" : $^X;
3206 open my $fh, "exec addr2line -C -f -i -e \Q$self\E @addr 2>&1 |" 3719 open my $fh, "exec addr2line -C -f -i -e \Q$self\E @addr 2>&1 |"
3207 or die "addr2line: $!"; 3720 or die "addr2line: $!";

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines