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.314 by root, Mon Jul 23 16:53:15 2007 UTC vs.
Revision 1.408 by root, Mon Jan 14 10:12:12 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
25use JSON::XS 1.4 (); 27use JSON::XS 2.01 ();
26use BDB (); 28use BDB ();
27use Data::Dumper; 29use Data::Dumper;
28use Digest::MD5; 30use Digest::MD5;
29use Fcntl; 31use Fcntl;
30use YAML::Syck (); 32use YAML ();
31use IO::AIO 2.32 (); 33use IO::AIO 2.51 ();
32use Time::HiRes; 34use Time::HiRes;
33use Compress::LZF; 35use Compress::LZF;
34use Digest::MD5 (); 36use Digest::MD5 ();
35 37
36# configure various modules to our taste 38# configure various modules to our taste
37# 39#
38$Storable::canonical = 1; # reduce rsync transfers 40$Storable::canonical = 1; # reduce rsync transfers
39Coro::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
40Compress::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
41
42$Event::Eval = 1; # no idea why this is required, but it is
43
44# work around bug in YAML::Syck - bad news for perl6, will it be as broken wrt. unicode?
45$YAML::Syck::ImplicitUnicode = 1;
46 43
47$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
48 45
49sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload 46sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
50 47
69our $TMPDIR = "$LOCALDIR/" . tmpdir; 66our $TMPDIR = "$LOCALDIR/" . tmpdir;
70our $UNIQUEDIR = "$LOCALDIR/" . uniquedir; 67our $UNIQUEDIR = "$LOCALDIR/" . uniquedir;
71our $PLAYERDIR = "$LOCALDIR/" . playerdir; 68our $PLAYERDIR = "$LOCALDIR/" . playerdir;
72our $RANDOMDIR = "$LOCALDIR/random"; 69our $RANDOMDIR = "$LOCALDIR/random";
73our $BDBDIR = "$LOCALDIR/db"; 70our $BDBDIR = "$LOCALDIR/db";
71our %RESOURCE;
74 72
75our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!) 73our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!)
76our $TICK_WATCHER; 74our $TICK_WATCHER;
77our $AIO_POLL_WATCHER; 75our $AIO_POLL_WATCHER;
78our $NEXT_RUNTIME_WRITE; # when should the runtime file be written 76our $NEXT_RUNTIME_WRITE; # when should the runtime file be written
79our $NEXT_TICK; 77our $NEXT_TICK;
80our $NOW;
81our $USE_FSYNC = 1; # use fsync to write maps - default off 78our $USE_FSYNC = 1; # use fsync to write maps - default off
82 79
83our $BDB_POLL_WATCHER; 80our $BDB_POLL_WATCHER;
81our $BDB_DEADLOCK_WATCHER;
82our $BDB_CHECKPOINT_WATCHER;
83our $BDB_TRICKLE_WATCHER;
84our $DB_ENV; 84our $DB_ENV;
85 85
86our %CFG; 86our %CFG;
87 87
88our $UPTIME; $UPTIME ||= time; 88our $UPTIME; $UPTIME ||= time;
89our $RUNTIME; 89our $RUNTIME;
90our $NOW;
90 91
91our %PLAYER; # all users 92our (%PLAYER, %PLAYER_LOADING); # all users
92our %MAP; # all maps 93our (%MAP, %MAP_LOADING ); # all maps
93our $LINK_MAP; # the special {link} map, which is always available 94our $LINK_MAP; # the special {link} map, which is always available
94 95
95# 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 ∕
96our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons 97our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons
97 98
158 159
159The raw value load value from the last tick. 160The raw value load value from the last tick.
160 161
161=item %cf::CFG 162=item %cf::CFG
162 163
163Configuration for the server, loaded from C</etc/crossfire/config>, or 164Configuration for the server, loaded from C</etc/deliantra-server/config>, or
164from wherever your confdir points to. 165from wherever your confdir points to.
165 166
166=item cf::wait_for_tick, cf::wait_for_tick_begin 167=item cf::wait_for_tick, cf::wait_for_tick_begin
167 168
168These 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
186 $msg .= "\n" 187 $msg .= "\n"
187 unless $msg =~ /\n$/; 188 unless $msg =~ /\n$/;
188 189
189 $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;
190 191
191 utf8::encode $msg;
192 LOG llevError, $msg; 192 LOG llevError, $msg;
193 }; 193 };
194} 194}
195
196$Coro::State::DIEHOOK = sub {
197 warn Carp::longmess $_[0];
198 Coro::terminate;
199};
195 200
196@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable'; 201@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
197@safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable'; 202@safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable';
198@safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable'; 203@safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable';
199@safe::cf::client::ISA = @cf::client::ISA = 'cf::attachable'; 204@safe::cf::client::ISA = @cf::client::ISA = 'cf::attachable';
212)) { 217)) {
213 no strict 'refs'; 218 no strict 'refs';
214 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg; 219 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg;
215} 220}
216 221
217$Event::DIED = sub { 222$EV::DIED = sub {
218 warn "error in event callback: @_"; 223 warn "error in event callback: @_";
219}; 224};
220 225
221############################################################################# 226#############################################################################
222 227
245 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge; 250 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge;
246 $d 251 $d
247 } || "[unable to dump $_[0]: '$@']"; 252 } || "[unable to dump $_[0]: '$@']";
248} 253}
249 254
250=item $ref = cf::from_json $json 255=item $ref = cf::decode_json $json
251 256
252Converts a JSON string into the corresponding perl data structure. 257Converts a JSON string into the corresponding perl data structure.
253 258
254=item $json = cf::to_json $ref 259=item $json = cf::encode_json $ref
255 260
256Converts a perl data structure into its JSON representation. 261Converts a perl data structure into its JSON representation.
257 262
258=cut 263=cut
259 264
260our $json_coder = JSON::XS->new->utf8->max_size (1e6); # accept ~1mb max 265our $json_coder = JSON::XS->new->utf8->max_size (1e6); # accept ~1mb max
261 266
262sub to_json ($) { $json_coder->encode ($_[0]) } 267sub encode_json($) { $json_coder->encode ($_[0]) }
263sub from_json ($) { $json_coder->decode ($_[0]) } 268sub decode_json($) { $json_coder->decode ($_[0]) }
264 269
265=item cf::lock_wait $string 270=item cf::lock_wait $string
266 271
267Wait until the given lock is available. See cf::lock_acquire. 272Wait until the given lock is available. See cf::lock_acquire.
268 273
271Wait until the given lock is available and then acquires it and returns 276Wait until the given lock is available and then acquires it and returns
272a Coro::guard object. If the guard object gets destroyed (goes out of scope, 277a Coro::guard object. If the guard object gets destroyed (goes out of scope,
273for example when the coroutine gets canceled), the lock is automatically 278for example when the coroutine gets canceled), the lock is automatically
274returned. 279returned.
275 280
281Locks are *not* recursive, locking from the same coro twice results in a
282deadlocked coro.
283
276Lock names should begin with a unique identifier (for example, cf::map::find 284Lock names should begin with a unique identifier (for example, cf::map::find
277uses map_find and cf::map::load uses map_load). 285uses map_find and cf::map::load uses map_load).
278 286
279=item $locked = cf::lock_active $string 287=item $locked = cf::lock_active $string
280 288
281Return true if the lock is currently active, i.e. somebody has locked it. 289Return true if the lock is currently active, i.e. somebody has locked it.
282 290
283=cut 291=cut
284 292
285our %LOCK; 293our %LOCK;
294our %LOCKER;#d#
286 295
287sub lock_wait($) { 296sub lock_wait($) {
288 my ($key) = @_; 297 my ($key) = @_;
298
299 if ($LOCKER{$key} == $Coro::current) {#d#
300 Carp::cluck "lock_wait($key) for already-acquired lock";#d#
301 return;#d#
302 }#d#
289 303
290 # wait for lock, if any 304 # wait for lock, if any
291 while ($LOCK{$key}) { 305 while ($LOCK{$key}) {
292 push @{ $LOCK{$key} }, $Coro::current; 306 push @{ $LOCK{$key} }, $Coro::current;
293 Coro::schedule; 307 Coro::schedule;
299 313
300 # wait, to be sure we are not locked 314 # wait, to be sure we are not locked
301 lock_wait $key; 315 lock_wait $key;
302 316
303 $LOCK{$key} = []; 317 $LOCK{$key} = [];
318 $LOCKER{$key} = $Coro::current;#d#
304 319
305 Coro::guard { 320 Coro::guard {
321 delete $LOCKER{$key};#d#
306 # wake up all waiters, to be on the safe side 322 # wake up all waiters, to be on the safe side
307 $_->ready for @{ delete $LOCK{$key} }; 323 $_->ready for @{ delete $LOCK{$key} };
308 } 324 }
309} 325}
310 326
322 }; 338 };
323 $TICK_WATCHER->stop; 339 $TICK_WATCHER->stop;
324 $guard 340 $guard
325} 341}
326 342
343=item cf::periodic $interval, $cb
344
345Like EV::periodic, but randomly selects a starting point so that the actions
346get spread over timer.
347
348=cut
349
350sub periodic($$) {
351 my ($interval, $cb) = @_;
352
353 my $start = rand List::Util::min 180, $interval;
354
355 EV::periodic $start, $interval, 0, $cb
356}
357
327=item cf::get_slot $time[, $priority] 358=item cf::get_slot $time[, $priority[, $name]]
328 359
329Allocate $time seconds of blocking CPU time at priority C<$priority>: 360Allocate $time seconds of blocking CPU time at priority C<$priority>:
330This call blocks and returns only when you have at least C<$time> seconds 361This call blocks and returns only when you have at least C<$time> seconds
331of cpu time till the next tick. The slot is only valid till the next cede. 362of cpu time till the next tick. The slot is only valid till the next cede.
332 363
364The optional C<$name> can be used to identify the job to run. It might be
365used for statistical purposes and should identify the same time-class.
366
333Useful for short background jobs. 367Useful for short background jobs.
334 368
335=cut 369=cut
336 370
337our @SLOT_QUEUE; 371our @SLOT_QUEUE;
338our $SLOT_QUEUE; 372our $SLOT_QUEUE;
339 373
340$SLOT_QUEUE->cancel if $SLOT_QUEUE; 374$SLOT_QUEUE->cancel if $SLOT_QUEUE;
341$SLOT_QUEUE = Coro::async { 375$SLOT_QUEUE = Coro::async {
376 $Coro::current->desc ("timeslot manager");
377
342 my $signal = new Coro::Signal; 378 my $signal = new Coro::Signal;
343 379
344 while () { 380 while () {
345 next_job: 381 next_job:
346 my $avail = cf::till_tick; 382 my $avail = cf::till_tick;
354 } 390 }
355 } 391 }
356 } 392 }
357 393
358 if (@SLOT_QUEUE) { 394 if (@SLOT_QUEUE) {
359 # we do not use wait_For_tick() as it returns immediately when tick is inactive 395 # we do not use wait_for_tick() as it returns immediately when tick is inactive
360 push @cf::WAIT_FOR_TICK, $signal; 396 push @cf::WAIT_FOR_TICK, $signal;
361 $signal->wait; 397 $signal->wait;
362 } else { 398 } else {
363 Coro::schedule; 399 Coro::schedule;
364 } 400 }
365 } 401 }
366}; 402};
367 403
368sub get_slot($;$) { 404sub get_slot($;$$) {
369 my ($time, $pri) = @_; 405 my ($time, $pri, $name) = @_;
370 406
407 $time = $TICK * .6 if $time > $TICK * .6;
408 my $sig = new Coro::Signal;
409
371 push @SLOT_QUEUE, [$time, $pri, my $sig = new Coro::Signal]; 410 push @SLOT_QUEUE, [$time, $pri, $sig, $name];
372 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE; 411 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE;
373 $SLOT_QUEUE->ready; 412 $SLOT_QUEUE->ready;
374 $sig->wait; 413 $sig->wait;
375} 414}
376 415
384 423
385BEGIN { *async = \&Coro::async_pool } 424BEGIN { *async = \&Coro::async_pool }
386 425
387=item cf::sync_job { BLOCK } 426=item cf::sync_job { BLOCK }
388 427
389The design of Crossfire TRT requires that the main coroutine ($Coro::main) 428The design of Deliantra requires that the main coroutine ($Coro::main)
390is always able to handle events or runnable, as Crossfire TRT is only 429is always able to handle events or runnable, as Deliantra is only
391partly reentrant. Thus "blocking" it by e.g. waiting for I/O is not 430partly reentrant. Thus "blocking" it by e.g. waiting for I/O is not
392acceptable. 431acceptable.
393 432
394If it must be done, put the blocking parts into C<sync_job>. This will run 433If it must be done, put the blocking parts into C<sync_job>. This will run
395the given BLOCK in another coroutine while waiting for the result. The 434the given BLOCK in another coroutine while waiting for the result. The
400 439
401sub sync_job(&) { 440sub sync_job(&) {
402 my ($job) = @_; 441 my ($job) = @_;
403 442
404 if ($Coro::current == $Coro::main) { 443 if ($Coro::current == $Coro::main) {
405 my $time = Event::time; 444 my $time = EV::time;
406 445
407 # this is the main coro, too bad, we have to block 446 # this is the main coro, too bad, we have to block
408 # till the operation succeeds, freezing the server :/ 447 # till the operation succeeds, freezing the server :/
409 448
449 LOG llevError, Carp::longmess "sync job";#d#
450
410 # TODO: use suspend/resume instead 451 # TODO: use suspend/resume instead
411 # (but this is cancel-safe) 452 # (but this is cancel-safe)
412 my $freeze_guard = freeze_mainloop; 453 my $freeze_guard = freeze_mainloop;
413 454
414 my $busy = 1; 455 my $busy = 1;
415 my @res; 456 my @res;
416 457
417 (async { 458 (async {
459 $Coro::current->desc ("sync job coro");
418 @res = eval { $job->() }; 460 @res = eval { $job->() };
419 warn $@ if $@; 461 warn $@ if $@;
420 undef $busy; 462 undef $busy;
421 })->prio (Coro::PRIO_MAX); 463 })->prio (Coro::PRIO_MAX);
422 464
423 while ($busy) { 465 while ($busy) {
424 Coro::cede or Event::one_event; 466 if (Coro::nready) {
467 Coro::cede_notself;
468 } else {
469 EV::loop EV::LOOP_ONESHOT;
425 } 470 }
471 }
426 472
427 $time = Event::time - $time; 473 $time = EV::time - $time;
428 474
429 LOG llevError | logBacktrace, Carp::longmess "long sync job" 475 LOG llevError | logBacktrace, Carp::longmess "long sync job"
430 if $time > $TICK * 0.5 && $TICK_WATCHER->is_active; 476 if $time > $TICK * 0.5 && $TICK_WATCHER->is_active;
431 477
432 $tick_start += $time; # do not account sync jobs to server load 478 $tick_start += $time; # do not account sync jobs to server load
462=item fork_call { }, $args 508=item fork_call { }, $args
463 509
464Executes the given code block with the given arguments in a seperate 510Executes the given code block with the given arguments in a seperate
465process, returning the results. Everything must be serialisable with 511process, returning the results. Everything must be serialisable with
466Coro::Storable. May, of course, block. Note that the executed sub may 512Coro::Storable. May, of course, block. Note that the executed sub may
467never block itself or use any form of Event handling. 513never block itself or use any form of event handling.
468 514
469=cut 515=cut
470
471sub _store_scalar {
472 open my $fh, ">", \my $buf
473 or die "fork_call: cannot open fh-to-buf in child : $!";
474 Storable::store_fd $_[0], $fh;
475 close $fh;
476
477 $buf
478}
479 516
480sub fork_call(&@) { 517sub fork_call(&@) {
481 my ($cb, @args) = @_; 518 my ($cb, @args) = @_;
482 519
483# socketpair my $fh1, my $fh2, Socket::AF_UNIX, Socket::SOCK_STREAM, Socket::PF_UNSPEC 520 # we seemingly have to make a local copy of the whole thing,
484# or die "socketpair: $!"; 521 # otherwise perl prematurely frees the stuff :/
485 pipe my $fh1, my $fh2 522 # TODO: investigate and fix (likely this will be rather laborious)
486 or die "pipe: $!";
487 523
488 if (my $pid = fork) { 524 my @res = Coro::Util::fork_eval {
489 close $fh2;
490
491 my $res = (Coro::Handle::unblock $fh1)->readline (undef);
492 warn "pst<$res>" unless $res =~ /^pst/;
493 $res = Coro::Storable::thaw $res;
494
495 waitpid $pid, 0; # should not block anymore, we expect the child to simply behave
496
497 Carp::confess $$res unless "ARRAY" eq ref $res;
498
499 return wantarray ? @$res : $res->[-1];
500 } else {
501 reset_signals; 525 reset_signals;
502 local $SIG{__WARN__}; 526 &$cb
503 local $SIG{__DIE__}; 527 }, @args;
504 # just in case, this hack effectively disables event
505 # in the child. cleaner and slower would be canceling all watchers,
506 # but this works for the time being.
507 local $Coro::idle;
508 $Coro::current->prio (Coro::PRIO_MAX);
509 528
510 eval { 529 wantarray ? @res : $res[-1]
511 close $fh1;
512
513 my @res = eval { $cb->(@args) };
514
515 syswrite $fh2, _store_scalar $@ ? \"$@" : \@res;
516 close $fh2;
517 };
518
519 warn $@ if $@;
520 _exit 0;
521 }
522} 530}
523 531
524=item $value = cf::db_get $family => $key 532=item $value = cf::db_get $family => $key
525 533
526Returns a single value from the environment database. 534Returns a single value from the environment database.
528=item cf::db_put $family => $key => $value 536=item cf::db_put $family => $key => $value
529 537
530Stores the given C<$value> in the family. It can currently store binary 538Stores the given C<$value> in the family. It can currently store binary
531data only (use Compress::LZF::sfreeze_cr/sthaw to convert to/from binary). 539data only (use Compress::LZF::sfreeze_cr/sthaw to convert to/from binary).
532 540
541=item $db = cf::db_table "name"
542
543Create and/or open a new database table. The string must not be "db" and must be unique
544within each server.
545
533=cut 546=cut
547
548sub db_table($) {
549 my ($name) = @_;
550 my $db = BDB::db_create $DB_ENV;
551
552 eval {
553 $db->set_flags (BDB::CHKSUM);
554
555 utf8::encode $name;
556 BDB::db_open $db, undef, $name, undef, BDB::BTREE,
557 BDB::CREATE | BDB::AUTO_COMMIT, 0666;
558 cf::cleanup "db_open(db): $!" if $!;
559 };
560 cf::cleanup "db_open(db): $@" if $@;
561
562 $db
563}
534 564
535our $DB; 565our $DB;
536 566
537sub db_init { 567sub db_init {
538 unless ($DB) {
539 $DB = BDB::db_create $DB_ENV;
540
541 cf::sync_job { 568 cf::sync_job {
542 eval { 569 $DB ||= db_table "db";
543 $DB->set_flags (BDB::CHKSUM);
544
545 BDB::db_open $DB, undef, "db", undef, BDB::BTREE,
546 BDB::CREATE | BDB::AUTO_COMMIT, 0666;
547 cf::cleanup "db_open(db): $!" if $!;
548 };
549 cf::cleanup "db_open(db): $@" if $@;
550 };
551 } 570 };
552} 571}
553 572
554sub db_get($$) { 573sub db_get($$) {
555 my $key = "$_[0]/$_[1]"; 574 my $key = "$_[0]/$_[1]";
556 575
606 if (1) { 625 if (1) {
607 $md5 = 626 $md5 =
608 join "\x00", 627 join "\x00",
609 $processversion, 628 $processversion,
610 map { 629 map {
611 Coro::cede; 630 cf::cede_to_tick;
612 ($src->[$_], Digest::MD5::md5_hex $data[$_]) 631 ($src->[$_], Digest::MD5::md5_hex $data[$_])
613 } 0.. $#$src; 632 } 0.. $#$src;
614 633
615 634
616 my $dbmd5 = db_get cache => "$id/md5"; 635 my $dbmd5 = db_get cache => "$id/md5";
660attach callbacks/event handlers (a collection of which is called an "attachment") 679attach callbacks/event handlers (a collection of which is called an "attachment")
661to it. All such attachable objects support the following methods. 680to it. All such attachable objects support the following methods.
662 681
663In the following description, CLASS can be any of C<global>, C<object> 682In the following description, CLASS can be any of C<global>, C<object>
664C<player>, C<client> or C<map> (i.e. the attachable objects in 683C<player>, C<client> or C<map> (i.e. the attachable objects in
665Crossfire TRT). 684Deliantra).
666 685
667=over 4 686=over 4
668 687
669=item $attachable->attach ($attachment, key => $value...) 688=item $attachable->attach ($attachment, key => $value...)
670 689
952 } 971 }
953 972
954 0 973 0
955} 974}
956 975
957=item $bool = cf::global::invoke (EVENT_CLASS_XXX, ...) 976=item $bool = cf::global->invoke (EVENT_CLASS_XXX, ...)
958 977
959=item $bool = $attachable->invoke (EVENT_CLASS_XXX, ...) 978=item $bool = $attachable->invoke (EVENT_CLASS_XXX, ...)
960 979
961Generate an object-specific event with the given arguments. 980Generate an object-specific event with the given arguments.
962 981
968 987
969=cut 988=cut
970 989
971############################################################################# 990#############################################################################
972# object support 991# object support
973#
974 992
993sub _object_equal($$);
994sub _object_equal($$) {
995 my ($a, $b) = @_;
996
997 return 0 unless (ref $a) eq (ref $b);
998
999 if ("HASH" eq ref $a) {
1000 my @ka = keys %$a;
1001 my @kb = keys %$b;
1002
1003 return 0 if @ka != @kb;
1004
1005 for (0 .. $#ka) {
1006 return 0 unless $ka[$_] eq $kb[$_];
1007 return 0 unless _object_equal $a->{$ka[$_]}, $b->{$kb[$_]};
1008 }
1009
1010 } elsif ("ARRAY" eq ref $a) {
1011
1012 return 0 if @$a != @$b;
1013
1014 for (0 .. $#$a) {
1015 return 0 unless _object_equal $a->[$_], $b->[$_];
1016 }
1017
1018 } elsif ($a ne $b) {
1019 return 0;
1020 }
1021
1022 1
1023}
1024
1025our $SLOW_MERGES;#d#
975sub _can_merge { 1026sub _can_merge {
976 my ($ob1, $ob2) = @_; 1027 my ($ob1, $ob2) = @_;
977 1028
978 local $Storable::canonical = 1; 1029 ++$SLOW_MERGES;#d#
979 my $fob1 = Storable::freeze $ob1;
980 my $fob2 = Storable::freeze $ob2;
981 1030
982 $fob1 eq $fob2 1031 # we do the slow way here
1032 return _object_equal $ob1, $ob2
983} 1033}
984 1034
985sub reattach { 1035sub reattach {
986 # basically do the same as instantiate, without calling instantiate 1036 # basically do the same as instantiate, without calling instantiate
987 my ($obj) = @_; 1037 my ($obj) = @_;
1009cf::attachable->attach ( 1059cf::attachable->attach (
1010 prio => -1000000, 1060 prio => -1000000,
1011 on_instantiate => sub { 1061 on_instantiate => sub {
1012 my ($obj, $data) = @_; 1062 my ($obj, $data) = @_;
1013 1063
1014 $data = from_json $data; 1064 $data = decode_json $data;
1015 1065
1016 for (@$data) { 1066 for (@$data) {
1017 my ($name, $args) = @$_; 1067 my ($name, $args) = @$_;
1018 1068
1019 $obj->attach ($name, %{$args || {} }); 1069 $obj->attach ($name, %{$args || {} });
1035sub object_freezer_save { 1085sub object_freezer_save {
1036 my ($filename, $rdata, $objs) = @_; 1086 my ($filename, $rdata, $objs) = @_;
1037 1087
1038 sync_job { 1088 sync_job {
1039 if (length $$rdata) { 1089 if (length $$rdata) {
1090 utf8::decode (my $decname = $filename);
1040 warn sprintf "saving %s (%d,%d)\n", 1091 warn sprintf "saving %s (%d,%d)\n",
1041 $filename, length $$rdata, scalar @$objs; 1092 $decname, length $$rdata, scalar @$objs;
1042 1093
1043 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) { 1094 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
1044 chmod SAVE_MODE, $fh; 1095 chmod SAVE_MODE, $fh;
1045 aio_write $fh, 0, (length $$rdata), $$rdata, 0; 1096 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
1046 aio_fsync $fh if $cf::USE_FSYNC; 1097 aio_fsync $fh if $cf::USE_FSYNC;
1047 close $fh; 1098 close $fh;
1048 1099
1049 if (@$objs) { 1100 if (@$objs) {
1050 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) { 1101 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) {
1051 chmod SAVE_MODE, $fh; 1102 chmod SAVE_MODE, $fh;
1052 my $data = Storable::nfreeze { version => 1, objs => $objs }; 1103 my $data = Coro::Storable::nfreeze { version => 1, objs => $objs };
1053 aio_write $fh, 0, (length $data), $data, 0; 1104 aio_write $fh, 0, (length $data), $data, 0;
1054 aio_fsync $fh if $cf::USE_FSYNC; 1105 aio_fsync $fh if $cf::USE_FSYNC;
1055 close $fh; 1106 close $fh;
1056 aio_rename "$filename.pst~", "$filename.pst"; 1107 aio_rename "$filename.pst~", "$filename.pst";
1057 } 1108 }
1065 } 1116 }
1066 } else { 1117 } else {
1067 aio_unlink $filename; 1118 aio_unlink $filename;
1068 aio_unlink "$filename.pst"; 1119 aio_unlink "$filename.pst";
1069 } 1120 }
1070 } 1121 };
1071} 1122}
1072 1123
1073sub object_freezer_as_string { 1124sub object_freezer_as_string {
1074 my ($rdata, $objs) = @_; 1125 my ($rdata, $objs) = @_;
1075 1126
1087 or return; 1138 or return;
1088 1139
1089 unless (aio_stat "$filename.pst") { 1140 unless (aio_stat "$filename.pst") {
1090 (aio_load "$filename.pst", $av) >= 0 1141 (aio_load "$filename.pst", $av) >= 0
1091 or return; 1142 or return;
1143
1092 $av = eval { (Storable::thaw $av)->{objs} }; 1144 my $st = eval { Coro::Storable::thaw $av };
1145 $av = $st->{objs};
1093 } 1146 }
1094 1147
1148 utf8::decode (my $decname = $filename);
1095 warn sprintf "loading %s (%d)\n", 1149 warn sprintf "loading %s (%d,%d)\n",
1096 $filename, length $data, scalar @{$av || []}; 1150 $decname, length $data, scalar @{$av || []};
1151
1097 return ($data, $av); 1152 ($data, $av)
1098} 1153}
1099 1154
1100=head2 COMMAND CALLBACKS 1155=head2 COMMAND CALLBACKS
1101 1156
1102=over 4 1157=over 4
1175 my ($pl, $buf) = @_; 1230 my ($pl, $buf) = @_;
1176 1231
1177 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) }; 1232 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) };
1178 1233
1179 if (ref $msg) { 1234 if (ref $msg) {
1235 my ($type, $reply, @payload) =
1236 "ARRAY" eq ref $msg
1237 ? @$msg
1238 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
1239
1240 my @reply;
1241
1180 if (my $cb = $EXTCMD{$msg->{msgtype}}) { 1242 if (my $cb = $EXTCMD{$type}) {
1181 if (my %reply = $cb->($pl, $msg)) { 1243 @reply = $cb->($pl, @payload);
1182 $pl->ext_reply ($msg->{msgid}, %reply);
1183 }
1184 } 1244 }
1245
1246 $pl->ext_reply ($reply, @reply)
1247 if $reply;
1248
1185 } else { 1249 } else {
1186 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n"; 1250 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
1187 } 1251 }
1188 1252
1189 cf::override; 1253 cf::override;
1279 1343
1280=head3 cf::player 1344=head3 cf::player
1281 1345
1282=over 4 1346=over 4
1283 1347
1348=item cf::player::num_playing
1349
1350Returns the official number of playing players, as per the Crossfire metaserver rules.
1351
1352=cut
1353
1354sub num_playing {
1355 scalar grep
1356 $_->ob->map
1357 && !$_->hidden
1358 && !$_->ob->flag (cf::FLAG_WIZ),
1359 cf::player::list
1360}
1361
1284=item cf::player::find $login 1362=item cf::player::find $login
1285 1363
1286Returns the given player object, loading it if necessary (might block). 1364Returns the given player object, loading it if necessary (might block).
1287 1365
1288=cut 1366=cut
1323 aio_link +(playerdir $login) . "/$login.pl.pst", (playerdir $login) . "/playerdata.pst"; 1401 aio_link +(playerdir $login) . "/$login.pl.pst", (playerdir $login) . "/playerdata.pst";
1324 aio_link +(playerdir $login) . "/$login.pl" , (playerdir $login) . "/playerdata"; 1402 aio_link +(playerdir $login) . "/$login.pl" , (playerdir $login) . "/playerdata";
1325 aio_unlink +(playerdir $login) . "/$login.pl.pst"; 1403 aio_unlink +(playerdir $login) . "/$login.pl.pst";
1326 aio_unlink +(playerdir $login) . "/$login.pl"; 1404 aio_unlink +(playerdir $login) . "/$login.pl";
1327 1405
1328 my $pl = load_pl path $login 1406 my $f = new_from_file cf::object::thawer path $login
1329 or return; 1407 or return;
1408
1409 my $pl = cf::player::load_pl $f
1410 or return;
1411 local $cf::PLAYER_LOADING{$login} = $pl;
1412 $f->resolve_delayed_derefs;
1330 $cf::PLAYER{$login} = $pl 1413 $cf::PLAYER{$login} = $pl
1331 } 1414 }
1332 } 1415 }
1333} 1416}
1334 1417
1344 1427
1345 aio_mkdir playerdir $pl, 0770; 1428 aio_mkdir playerdir $pl, 0770;
1346 $pl->{last_save} = $cf::RUNTIME; 1429 $pl->{last_save} = $cf::RUNTIME;
1347 1430
1348 $pl->save_pl ($path); 1431 $pl->save_pl ($path);
1349 Coro::cede; 1432 cf::cede_to_tick;
1350} 1433}
1351 1434
1352sub new($) { 1435sub new($) {
1353 my ($login) = @_; 1436 my ($login) = @_;
1354 1437
1358 $self->{deny_save} = 1; 1441 $self->{deny_save} = 1;
1359 1442
1360 $cf::PLAYER{$login} = $self; 1443 $cf::PLAYER{$login} = $self;
1361 1444
1362 $self 1445 $self
1446}
1447
1448=item $player->send_msg ($channel, $msg, $color, [extra...])
1449
1450=cut
1451
1452sub send_msg {
1453 my $ns = shift->ns
1454 or return;
1455 $ns->send_msg (@_);
1363} 1456}
1364 1457
1365=item $pl->quit_character 1458=item $pl->quit_character
1366 1459
1367Nukes the player without looking back. If logged in, the connection will 1460Nukes the player without looking back. If logged in, the connection will
1422 or return []; 1515 or return [];
1423 1516
1424 my @logins; 1517 my @logins;
1425 1518
1426 for my $login (@$dirs) { 1519 for my $login (@$dirs) {
1520 my $path = path $login;
1521
1522 # a .pst is a dead give-away for a valid player
1523 unless (-e "$path.pst") {
1427 my $fh = aio_open path $login, Fcntl::O_RDONLY, 0 or next; 1524 my $fh = aio_open $path, Fcntl::O_RDONLY, 0 or next;
1428 aio_read $fh, 0, 512, my $buf, 0 or next; 1525 aio_read $fh, 0, 512, my $buf, 0 or next;
1429 $buf !~ /^password -------------$/m or next; # official not-valid tag 1526 $buf !~ /^password -------------$/m or next; # official not-valid tag
1527 }
1430 1528
1431 utf8::decode $login; 1529 utf8::decode $login;
1432 push @logins, $login; 1530 push @logins, $login;
1433 } 1531 }
1434 1532
1467 1565
1468Expand crossfire pod fragments into protocol xml. 1566Expand crossfire pod fragments into protocol xml.
1469 1567
1470=cut 1568=cut
1471 1569
1570use re 'eval';
1571
1572my $group;
1573my $interior; $interior = qr{
1574 # match a pod interior sequence sans C<< >>
1575 (?:
1576 \ (.*?)\ (?{ $group = $^N })
1577 | < (??{$interior}) >
1578 )
1579}x;
1580
1472sub expand_cfpod { 1581sub expand_cfpod {
1473 ((my $self), (local $_)) = @_; 1582 my ($self, $pod) = @_;
1474 1583
1475 # escape & and < 1584 my $xml;
1476 s/&/&amp;/g;
1477 s/(?<![BIUGH])</&lt;/g;
1478 1585
1479 # this is buggy, it needs to properly take care of nested <'s 1586 while () {
1587 if ($pod =~ /\G( (?: [^BCGHITU]+ | .(?!<) )+ )/xgcs) {
1588 $group = $1;
1480 1589
1481 1 while 1590 $group =~ s/&/&amp;/g;
1482 # replace B<>, I<>, U<> etc. 1591 $group =~ s/</&lt;/g;
1483 s/B<([^\>]*)>/<b>$1<\/b>/ 1592
1484 || s/I<([^\>]*)>/<i>$1<\/i>/ 1593 $xml .= $group;
1485 || s/U<([^\>]*)>/<u>$1<\/u>/ 1594 } elsif ($pod =~ m%\G
1486 # replace G<male|female> tags 1595 ([BCGHITU])
1487 || s{G<([^>|]*)\|([^>]*)>}{ 1596 <
1488 $self->gender ? $2 : $1 1597 (?:
1489 }ge 1598 ([^<>]*) (?{ $group = $^N })
1490 # replace H<hint text> 1599 | < $interior >
1491 || s{H<([^\>]*)>} 1600 )
1601 >
1602 %gcsx
1492 { 1603 ) {
1604 my ($code, $data) = ($1, $group);
1605
1606 if ($code eq "B") {
1607 $xml .= "<b>" . expand_cfpod ($self, $data) . "</b>";
1608 } elsif ($code eq "I") {
1609 $xml .= "<i>" . expand_cfpod ($self, $data) . "</i>";
1610 } elsif ($code eq "U") {
1611 $xml .= "<u>" . expand_cfpod ($self, $data) . "</u>";
1612 } elsif ($code eq "C") {
1613 $xml .= "<tt>" . expand_cfpod ($self, $data) . "</tt>";
1614 } elsif ($code eq "T") {
1615 $xml .= "<big><b>" . expand_cfpod ($self, $data) . "</b></big>";
1616 } elsif ($code eq "G") {
1617 my ($male, $female) = split /\|/, $data;
1618 $data = $self->gender ? $female : $male;
1619 $xml .= expand_cfpod ($self, $data);
1620 } elsif ($code eq "H") {
1493 ("<fg name=\"lightblue\">[$1 (Use hintmode to suppress hints)]</fg>", 1621 $xml .= ("<fg name=\"lightblue\">[" . expand_cfpod ($self, $data) . " (Use hintmode to suppress hints)]</fg>",
1494 "<fg name=\"lightblue\">[Hint suppressed, see hintmode]</fg>", 1622 "<fg name=\"lightblue\">[Hint suppressed, see hintmode]</fg>",
1495 "") 1623 "")
1496 [$self->{hintmode}] 1624 [$self->{hintmode}];
1625 } else {
1626 $xml .= "error processing '$code($data)' directive";
1497 }ge; 1627 }
1628 } else {
1629 if ($pod =~ /\G(.+)/) {
1630 warn "parse error while expanding $pod (at $1)";
1631 }
1632 last;
1633 }
1634 }
1498 1635
1636 for ($xml) {
1499 # create single paragraphs (very hackish) 1637 # create single paragraphs (very hackish)
1500 s/(?<=\S)\n(?=\w)/ /g; 1638 s/(?<=\S)\n(?=\w)/ /g;
1501 1639
1502 # compress some whitespace 1640 # compress some whitespace
1503 s/\s+\n/\n/g; # ws line-ends 1641 s/\s+\n/\n/g; # ws line-ends
1504 s/\n\n+/\n/g; # double lines 1642 s/\n\n+/\n/g; # double lines
1505 s/^\n+//; # beginning lines 1643 s/^\n+//; # beginning lines
1506 s/\n+$//; # ending lines 1644 s/\n+$//; # ending lines
1645 }
1507 1646
1508 $_ 1647 $xml
1509} 1648}
1649
1650no re 'eval';
1510 1651
1511sub hintmode { 1652sub hintmode {
1512 $_[0]{hintmode} = $_[1] if @_ > 1; 1653 $_[0]{hintmode} = $_[1] if @_ > 1;
1513 $_[0]{hintmode} 1654 $_[0]{hintmode}
1514} 1655}
1515 1656
1516=item $player->ext_reply ($msgid, %msg) 1657=item $player->ext_reply ($msgid, @msg)
1517 1658
1518Sends an ext reply to the player. 1659Sends an ext reply to the player.
1519 1660
1520=cut 1661=cut
1521 1662
1522sub ext_reply($$%) { 1663sub ext_reply($$@) {
1523 my ($self, $id, %msg) = @_; 1664 my ($self, $id, @msg) = @_;
1524 1665
1525 $msg{msgid} = $id; 1666 $self->ns->ext_reply ($id, @msg)
1526 $self->send ("ext " . $self->ns->{json_coder}->encode (\%msg));
1527} 1667}
1528 1668
1529=item $player->ext_event ($type, %msg) 1669=item $player->ext_msg ($type, @msg)
1530 1670
1531Sends an ext event to the client. 1671Sends an ext event to the client.
1532 1672
1533=cut 1673=cut
1534 1674
1535sub ext_event($$%) { 1675sub ext_msg($$@) {
1536 my ($self, $type, %msg) = @_; 1676 my ($self, $type, @msg) = @_;
1537 1677
1538 $self->ns->ext_event ($type, %msg); 1678 $self->ns->ext_msg ($type, @msg);
1539} 1679}
1540 1680
1541=head3 cf::region 1681=head3 cf::region
1542 1682
1543=over 4 1683=over 4
1759 1899
1760sub load_header_from($) { 1900sub load_header_from($) {
1761 my ($self, $path) = @_; 1901 my ($self, $path) = @_;
1762 1902
1763 utf8::encode $path; 1903 utf8::encode $path;
1764 #aio_open $path, O_RDONLY, 0 1904 my $f = new_from_file cf::object::thawer $path
1765 # or return;
1766
1767 $self->_load_header ($path)
1768 or return; 1905 or return;
1906
1907 $self->_load_header ($f)
1908 or return;
1909
1910 local $MAP_LOADING{$self->{path}} = $self;
1911 $f->resolve_delayed_derefs;
1769 1912
1770 $self->{load_path} = $path; 1913 $self->{load_path} = $path;
1771 1914
1772 1 1915 1
1773} 1916}
1827sub find { 1970sub find {
1828 my ($path, $origin) = @_; 1971 my ($path, $origin) = @_;
1829 1972
1830 $path = normalise $path, $origin && $origin->path; 1973 $path = normalise $path, $origin && $origin->path;
1831 1974
1975 cf::lock_wait "map_data:$path";#d#remove
1832 cf::lock_wait "map_find:$path"; 1976 cf::lock_wait "map_find:$path";
1833 1977
1834 $cf::MAP{$path} || do { 1978 $cf::MAP{$path} || do {
1835 my $guard = cf::lock_acquire "map_find:$path"; 1979 my $guard1 = cf::lock_acquire "map_find:$path";
1980 my $guard2 = cf::lock_acquire "map_data:$path"; # just for the fun of it
1981
1836 my $map = new_from_path cf::map $path 1982 my $map = new_from_path cf::map $path
1837 or return; 1983 or return;
1838 1984
1839 $map->{last_save} = $cf::RUNTIME; 1985 $map->{last_save} = $cf::RUNTIME;
1840 1986
1842 or return; 1988 or return;
1843 1989
1844 if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?) 1990 if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?)
1845 # doing this can freeze the server in a sync job, obviously 1991 # doing this can freeze the server in a sync job, obviously
1846 #$cf::WAIT_FOR_TICK->wait; 1992 #$cf::WAIT_FOR_TICK->wait;
1993 undef $guard1;
1994 undef $guard2;
1847 $map->reset; 1995 $map->reset;
1848 undef $guard;
1849 return find $path; 1996 return find $path;
1850 } 1997 }
1851 1998
1852 $cf::MAP{$path} = $map 1999 $cf::MAP{$path} = $map
1853 } 2000 }
1862 local $self->{deny_reset} = 1; # loading can take a long time 2009 local $self->{deny_reset} = 1; # loading can take a long time
1863 2010
1864 my $path = $self->{path}; 2011 my $path = $self->{path};
1865 2012
1866 { 2013 {
1867 my $guard = cf::lock_acquire "map_load:$path"; 2014 my $guard = cf::lock_acquire "map_data:$path";
1868 2015
2016 return unless $self->valid;
1869 return if $self->in_memory != cf::MAP_SWAPPED; 2017 return unless $self->in_memory == cf::MAP_SWAPPED;
1870 2018
1871 $self->in_memory (cf::MAP_LOADING); 2019 $self->in_memory (cf::MAP_LOADING);
1872 2020
1873 $self->alloc; 2021 $self->alloc;
1874 2022
1875 $self->pre_load; 2023 $self->pre_load;
1876 Coro::cede; 2024 cf::cede_to_tick;
1877 2025
2026 my $f = new_from_file cf::object::thawer $self->{load_path};
2027 $f->skip_block;
1878 $self->_load_objects ($self->{load_path}, 1) 2028 $self->_load_objects ($f)
1879 or return; 2029 or return;
1880 2030
1881 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1) 2031 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
1882 if delete $self->{load_original}; 2032 if delete $self->{load_original};
1883 2033
1884 if (my $uniq = $self->uniq_path) { 2034 if (my $uniq = $self->uniq_path) {
1885 utf8::encode $uniq; 2035 utf8::encode $uniq;
1886 if (aio_open $uniq, O_RDONLY, 0) { 2036 unless (aio_stat $uniq) {
2037 if (my $f = new_from_file cf::object::thawer $uniq) {
1887 $self->clear_unique_items; 2038 $self->clear_unique_items;
1888 $self->_load_objects ($uniq, 0); 2039 $self->_load_objects ($f);
2040 $f->resolve_delayed_derefs;
2041 }
1889 } 2042 }
1890 } 2043 }
1891 2044
1892 Coro::cede; 2045 $f->resolve_delayed_derefs;
2046
2047 cf::cede_to_tick;
1893 # now do the right thing for maps 2048 # now do the right thing for maps
1894 $self->link_multipart_objects; 2049 $self->link_multipart_objects;
1895 $self->difficulty ($self->estimate_difficulty) 2050 $self->difficulty ($self->estimate_difficulty)
1896 unless $self->difficulty; 2051 unless $self->difficulty;
1897 Coro::cede; 2052 cf::cede_to_tick;
1898 2053
1899 unless ($self->{deny_activate}) { 2054 unless ($self->{deny_activate}) {
1900 $self->decay_objects; 2055 $self->decay_objects;
1901 $self->fix_auto_apply; 2056 $self->fix_auto_apply;
1902 $self->update_buttons; 2057 $self->update_buttons;
1903 Coro::cede; 2058 cf::cede_to_tick;
1904 $self->set_darkness_map; 2059 $self->set_darkness_map;
1905 Coro::cede; 2060 cf::cede_to_tick;
1906 $self->activate; 2061 $self->activate;
1907 } 2062 }
2063
2064 $self->{last_save} = $cf::RUNTIME;
2065 $self->last_access ($cf::RUNTIME);
1908 2066
1909 $self->in_memory (cf::MAP_IN_MEMORY); 2067 $self->in_memory (cf::MAP_IN_MEMORY);
1910 } 2068 }
1911 2069
1912 $self->post_load; 2070 $self->post_load;
1923 2081
1924 $self 2082 $self
1925} 2083}
1926 2084
1927# find and load all maps in the 3x3 area around a map 2085# find and load all maps in the 3x3 area around a map
1928sub load_diag { 2086sub load_neighbours {
1929 my ($map) = @_; 2087 my ($map) = @_;
1930 2088
1931 my @diag; # diagonal neighbours 2089 my @neigh; # diagonal neighbours
1932 2090
1933 for (0 .. 3) { 2091 for (0 .. 3) {
1934 my $neigh = $map->tile_path ($_) 2092 my $neigh = $map->tile_path ($_)
1935 or next; 2093 or next;
1936 $neigh = find $neigh, $map 2094 $neigh = find $neigh, $map
1937 or next; 2095 or next;
1938 $neigh->load; 2096 $neigh->load;
1939 2097
2098 push @neigh,
1940 push @diag, [$neigh->tile_path (($_ + 3) % 4), $neigh], 2099 [$neigh->tile_path (($_ + 3) % 4), $neigh],
1941 [$neigh->tile_path (($_ + 1) % 4), $neigh]; 2100 [$neigh->tile_path (($_ + 1) % 4), $neigh];
1942 } 2101 }
1943 2102
1944 for (@diag) { 2103 for (grep defined $_->[0], @neigh) {
2104 my ($path, $origin) = @$_;
1945 my $neigh = find @$_ 2105 my $neigh = find $path, $origin
1946 or next; 2106 or next;
1947 $neigh->load; 2107 $neigh->load;
1948 } 2108 }
1949} 2109}
1950 2110
1955} 2115}
1956 2116
1957sub do_load_sync { 2117sub do_load_sync {
1958 my ($map) = @_; 2118 my ($map) = @_;
1959 2119
2120 cf::LOG cf::llevDebug | cf::logBacktrace, "do_load_sync"
2121 if $Coro::current == $Coro::main;
2122
1960 cf::sync_job { $map->load }; 2123 cf::sync_job { $map->load };
1961} 2124}
1962 2125
1963our %MAP_PREFETCH; 2126our %MAP_PREFETCH;
1964our $MAP_PREFETCHER = undef; 2127our $MAP_PREFETCHER = undef;
1965 2128
1966sub find_async { 2129sub find_async {
1967 my ($path, $origin) = @_; 2130 my ($path, $origin, $load) = @_;
1968 2131
1969 $path = normalise $path, $origin && $origin->{path}; 2132 $path = normalise $path, $origin && $origin->{path};
1970 2133
1971 if (my $map = $cf::MAP{$path}) { 2134 if (my $map = $cf::MAP{$path}) {
1972 return $map if $map->in_memory == cf::MAP_IN_MEMORY; 2135 return $map if !$load || $map->in_memory == cf::MAP_IN_MEMORY;
1973 } 2136 }
1974 2137
1975 undef $MAP_PREFETCH{$path}; 2138 $MAP_PREFETCH{$path} |= $load;
2139
1976 $MAP_PREFETCHER ||= cf::async { 2140 $MAP_PREFETCHER ||= cf::async {
2141 $Coro::current->{desc} = "map prefetcher";
2142
1977 while (%MAP_PREFETCH) { 2143 while (%MAP_PREFETCH) {
1978 for my $path (keys %MAP_PREFETCH) { 2144 while (my ($k, $v) = each %MAP_PREFETCH) {
1979 if (my $map = find $path) { 2145 if (my $map = find $k) {
1980 $map->load; 2146 $map->load if $v;
1981 } 2147 }
1982 2148
1983 delete $MAP_PREFETCH{$path}; 2149 delete $MAP_PREFETCH{$k};
1984 } 2150 }
1985 } 2151 }
1986 undef $MAP_PREFETCHER; 2152 undef $MAP_PREFETCHER;
1987 }; 2153 };
1988 $MAP_PREFETCHER->prio (6); 2154 $MAP_PREFETCHER->prio (6);
1991} 2157}
1992 2158
1993sub save { 2159sub save {
1994 my ($self) = @_; 2160 my ($self) = @_;
1995 2161
1996 my $lock = cf::lock_acquire "map_data:" . $self->path; 2162 my $lock = cf::lock_acquire "map_data:$self->{path}";
1997 2163
1998 $self->{last_save} = $cf::RUNTIME; 2164 $self->{last_save} = $cf::RUNTIME;
1999 2165
2000 return unless $self->dirty; 2166 return unless $self->dirty;
2001 2167
2007 return if $self->{deny_save}; 2173 return if $self->{deny_save};
2008 2174
2009 local $self->{last_access} = $self->last_access;#d# 2175 local $self->{last_access} = $self->last_access;#d#
2010 2176
2011 cf::async { 2177 cf::async {
2178 $Coro::current->{desc} = "map player save";
2012 $_->contr->save for $self->players; 2179 $_->contr->save for $self->players;
2013 }; 2180 };
2014 2181
2015 if ($uniq) { 2182 if ($uniq) {
2016 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS); 2183 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
2024 my ($self) = @_; 2191 my ($self) = @_;
2025 2192
2026 # save first because save cedes 2193 # save first because save cedes
2027 $self->save; 2194 $self->save;
2028 2195
2029 my $lock = cf::lock_acquire "map_data:" . $self->path; 2196 my $lock = cf::lock_acquire "map_data:$self->{path}";
2030 2197
2031 return if $self->players; 2198 return if $self->players;
2032 return if $self->in_memory != cf::MAP_IN_MEMORY; 2199 return if $self->in_memory != cf::MAP_IN_MEMORY;
2033 return if $self->{deny_save}; 2200 return if $self->{deny_save};
2034 2201
2202 $self->in_memory (cf::MAP_SWAPPED);
2203
2204 $self->deactivate;
2205 $_->clear_links_to ($self) for values %cf::MAP;
2035 $self->clear; 2206 $self->clear;
2036 $self->in_memory (cf::MAP_SWAPPED);
2037} 2207}
2038 2208
2039sub reset_at { 2209sub reset_at {
2040 my ($self) = @_; 2210 my ($self) = @_;
2041 2211
2073 if $uniq; 2243 if $uniq;
2074 } 2244 }
2075 2245
2076 delete $cf::MAP{$self->path}; 2246 delete $cf::MAP{$self->path};
2077 2247
2248 $self->deactivate;
2249 $_->clear_links_to ($self) for values %cf::MAP;
2078 $self->clear; 2250 $self->clear;
2079
2080 $_->clear_links_to ($self) for values %cf::MAP;
2081 2251
2082 $self->unlink_save; 2252 $self->unlink_save;
2083 $self->destroy; 2253 $self->destroy;
2084} 2254}
2085 2255
2086my $nuke_counter = "aaaa"; 2256my $nuke_counter = "aaaa";
2087 2257
2088sub nuke { 2258sub nuke {
2089 my ($self) = @_; 2259 my ($self) = @_;
2090 2260
2261 {
2262 my $lock = cf::lock_acquire "map_data:$self->{path}";
2263
2091 delete $cf::MAP{$self->path}; 2264 delete $cf::MAP{$self->path};
2092 2265
2093 $self->unlink_save; 2266 $self->unlink_save;
2094 2267
2095 bless $self, "cf::map"; 2268 bless $self, "cf::map";
2096 delete $self->{deny_reset}; 2269 delete $self->{deny_reset};
2097 $self->{deny_save} = 1; 2270 $self->{deny_save} = 1;
2098 $self->reset_timeout (1); 2271 $self->reset_timeout (1);
2099 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++)); 2272 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++));
2100 2273
2101 $cf::MAP{$self->path} = $self; 2274 $cf::MAP{$self->path} = $self;
2275 }
2102 2276
2103 $self->reset; # polite request, might not happen 2277 $self->reset; # polite request, might not happen
2104} 2278}
2105 2279
2106=item $maps = cf::map::tmp_maps 2280=item $maps = cf::map::tmp_maps
2182 2356
2183sub inv_recursive { 2357sub inv_recursive {
2184 inv_recursive_ inv $_[0] 2358 inv_recursive_ inv $_[0]
2185} 2359}
2186 2360
2361=item $ref = $ob->ref
2362
2363creates and returns a persistent reference to an objetc that can be stored as a string.
2364
2365=item $ob = cf::object::deref ($refstring)
2366
2367returns the objetc referenced by refstring. may return undef when it cnanot find the object,
2368even if the object actually exists. May block.
2369
2370=cut
2371
2372sub deref {
2373 my ($ref) = @_;
2374
2375 if ($ref =~ m{^player\/(<1\.[0-9a-f]+>)/(.*)$}) {
2376 my ($uuid, $name) = ($1, $2);
2377 my $pl = $cf::PLAYER_LOADING{$name} || cf::player::find $name
2378 or return;
2379 $pl->ob->uuid eq $uuid
2380 or return;
2381
2382 $pl->ob
2383 } else {
2384 warn "$ref: cannot resolve object reference\n";
2385 undef
2386 }
2387}
2388
2187package cf; 2389package cf;
2188 2390
2189=back 2391=back
2190 2392
2191=head3 cf::object::player 2393=head3 cf::object::player
2213 2415
2214 } else { 2416 } else {
2215 my $pl = $self->contr; 2417 my $pl = $self->contr;
2216 2418
2217 if ($pl->{npc_dialog} && $pl->{npc_dialog}->{id}) { 2419 if ($pl->{npc_dialog} && $pl->{npc_dialog}->{id}) {
2218 my $diag = $pl->{npc_dialog}; 2420 my $dialog = $pl->{npc_dialog};
2219 $diag->{pl}->ext_reply ( 2421 $dialog->{pl}->ext_msg ($dialog->{id}, update => msg => $dialog->{pl}->expand_cfpod ($msg));
2220 $diag->{id},
2221 msgtype => "reply",
2222 msg => $diag->{pl}->expand_cfpod ($msg),
2223 add_topics => []
2224 );
2225 2422
2226 } else { 2423 } else {
2227 $msg = $npc->name . " says: $msg" if $npc; 2424 $msg = $npc->name . " says: $msg" if $npc;
2228 $self->message ($msg, $flags); 2425 $self->message ($msg, $flags);
2229 } 2426 }
2230 } 2427 }
2428}
2429
2430=item $object->send_msg ($channel, $msg, $color, [extra...])
2431
2432=cut
2433
2434sub cf::object::send_msg {
2435 my $pl = shift->contr
2436 or return;
2437 $pl->send_msg (@_);
2231} 2438}
2232 2439
2233=item $player_object->may ("access") 2440=item $player_object->may ("access")
2234 2441
2235Returns wether the given player is authorized to access resource "access" 2442Returns wether the given player is authorized to access resource "access"
2314 # use -1 or undef as default coordinates, not 0, 0 2521 # use -1 or undef as default coordinates, not 0, 0
2315 ($x, $y) = ($map->enter_x, $map->enter_y) 2522 ($x, $y) = ($map->enter_x, $map->enter_y)
2316 if $x <=0 && $y <= 0; 2523 if $x <=0 && $y <= 0;
2317 2524
2318 $map->load; 2525 $map->load;
2319 $map->load_diag; 2526 $map->load_neighbours;
2320 2527
2321 return unless $self->contr->active; 2528 return unless $self->contr->active;
2322 $self->activate_recursive; 2529 $self->activate_recursive;
2323 2530
2324 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext 2531 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext
2344 my $gen = $self->{_goto_generation} = ++$GOTOGEN; 2551 my $gen = $self->{_goto_generation} = ++$GOTOGEN;
2345 2552
2346 $self->enter_link; 2553 $self->enter_link;
2347 2554
2348 (async { 2555 (async {
2556 $Coro::current->{desc} = "player::goto $path $x $y";
2557
2558 # *tag paths override both path and x|y
2559 if ($path =~ /^\*(.*)$/) {
2560 if (my @obs = grep $_->map, ext::map_tags::find $1) {
2561 my $ob = $obs[rand @obs];
2562
2563 # see if we actually can go there
2564 if (@obs = grep !$self->blocked ($_->map, $_->x, $_->y), $ob, $ob->tail) {
2565 $ob = $obs[rand @obs];
2566 } else {
2567 $self->message ("Wow, it's pretty crowded in there.", cf::NDI_UNIQUE | cf::NDI_RED);
2568 }
2569 # else put us there anyways for now #d#
2570
2571 ($path, $x, $y) = ($ob->map, $ob->x, $ob->y);
2572 } else {
2573 ($path, $x, $y) = (undef, undef, undef);
2574 }
2575 }
2576
2349 my $map = eval { 2577 my $map = eval {
2350 my $map = cf::map::find $path; 2578 my $map = defined $path ? cf::map::find $path : undef;
2351 2579
2352 if ($map) { 2580 if ($map) {
2353 $map = $map->customise_for ($self); 2581 $map = $map->customise_for ($self);
2354 $map = $check->($map) if $check && $map; 2582 $map = $check->($map) if $check && $map;
2355 } else { 2583 } else {
2356 $self->message ("The exit to '$path' is closed", cf::NDI_UNIQUE | cf::NDI_RED); 2584 $self->message ("The exit to '$path' is closed.", cf::NDI_UNIQUE | cf::NDI_RED);
2357 } 2585 }
2358 2586
2359 $map 2587 $map
2360 }; 2588 };
2361 2589
2414 $rmp->{origin_y} = $exit->y; 2642 $rmp->{origin_y} = $exit->y;
2415 } 2643 }
2416 2644
2417 $rmp->{random_seed} ||= $exit->random_seed; 2645 $rmp->{random_seed} ||= $exit->random_seed;
2418 2646
2419 my $data = cf::to_json $rmp; 2647 my $data = cf::encode_json $rmp;
2420 my $md5 = Digest::MD5::md5_hex $data; 2648 my $md5 = Digest::MD5::md5_hex $data;
2421 my $meta = "$RANDOMDIR/$md5.meta"; 2649 my $meta = "$RANDOMDIR/$md5.meta";
2422 2650
2423 if (my $fh = aio_open "$meta~", O_WRONLY | O_CREAT, 0666) { 2651 if (my $fh = aio_open "$meta~", O_WRONLY | O_CREAT, 0666) {
2424 aio_write $fh, 0, (length $data), $data, 0; 2652 aio_write $fh, 0, (length $data), $data, 0;
2451 # if exit is damned, update players death & WoR home-position 2679 # if exit is damned, update players death & WoR home-position
2452 $self->contr->savebed ($slaying, $hp, $sp) 2680 $self->contr->savebed ($slaying, $hp, $sp)
2453 if $exit->flag (FLAG_DAMNED); 2681 if $exit->flag (FLAG_DAMNED);
2454 2682
2455 (async { 2683 (async {
2684 $Coro::current->{desc} = "enter_exit $slaying $hp $sp";
2685
2456 $self->deactivate_recursive; # just to be sure 2686 $self->deactivate_recursive; # just to be sure
2457 unless (eval { 2687 unless (eval {
2458 $self->goto ($slaying, $hp, $sp); 2688 $self->goto ($slaying, $hp, $sp);
2459 2689
2460 1; 2690 1;
2495the message, with C<log> being the default. If C<$color> is negative, suppress 2725the message, with C<log> being the default. If C<$color> is negative, suppress
2496the message unless the client supports the msg packet. 2726the message unless the client supports the msg packet.
2497 2727
2498=cut 2728=cut
2499 2729
2730# non-persistent channels (usually the info channel)
2731our %CHANNEL = (
2732 "c/identify" => {
2733 id => "infobox",
2734 title => "Identify",
2735 reply => undef,
2736 tooltip => "Items recently identified",
2737 },
2738 "c/examine" => {
2739 id => "infobox",
2740 title => "Examine",
2741 reply => undef,
2742 tooltip => "Signs and other items you examined",
2743 },
2744 "c/book" => {
2745 id => "infobox",
2746 title => "Book",
2747 reply => undef,
2748 tooltip => "The contents of a note or book",
2749 },
2750 "c/lookat" => {
2751 id => "infobox",
2752 title => "Look",
2753 reply => undef,
2754 tooltip => "What you saw there",
2755 },
2756 "c/who" => {
2757 id => "infobox",
2758 title => "Players",
2759 reply => undef,
2760 tooltip => "Shows players who are currently online",
2761 },
2762 "c/body" => {
2763 id => "infobox",
2764 title => "Body Parts",
2765 reply => undef,
2766 tooltip => "Shows which body parts you posess and are available",
2767 },
2768 "c/uptime" => {
2769 id => "infobox",
2770 title => "Uptime",
2771 reply => undef,
2772 tooltip => "How long the server has been running since last restart",
2773 },
2774 "c/mapinfo" => {
2775 id => "infobox",
2776 title => "Map Info",
2777 reply => undef,
2778 tooltip => "Information related to the maps",
2779 },
2780);
2781
2500sub cf::client::send_msg { 2782sub cf::client::send_msg {
2501 my ($self, $channel, $msg, $color, @extra) = @_; 2783 my ($self, $channel, $msg, $color, @extra) = @_;
2502 2784
2503 $msg = $self->pl->expand_cfpod ($msg); 2785 $msg = $self->pl->expand_cfpod ($msg);
2504 2786
2505 $color &= ~cf::NDI_UNIQUE; # just in case... 2787 $color &= cf::NDI_CLIENT_MASK; # just in case...
2506 2788
2789 # check predefined channels, for the benefit of C
2790 if ($CHANNEL{$channel}) {
2791 $channel = $CHANNEL{$channel};
2792
2793 $self->ext_msg (channel_info => $channel)
2794 if $self->can_msg;
2795
2796 $channel = $channel->{id};
2797
2507 if (ref $channel) { 2798 } elsif (ref $channel) {
2508 # send meta info to client, if not yet sent 2799 # send meta info to client, if not yet sent
2509 unless (exists $self->{channel}{$channel->{id}}) { 2800 unless (exists $self->{channel}{$channel->{id}}) {
2510 $self->{channel}{$channel->{id}} = $channel; 2801 $self->{channel}{$channel->{id}} = $channel;
2511 $self->ext_event (channel_info => %$channel); 2802 $self->ext_msg (channel_info => $channel)
2803 if $self->can_msg;
2512 } 2804 }
2513 2805
2514 $channel = $channel->{id}; 2806 $channel = $channel->{id};
2515 } 2807 }
2516 2808
2517 return unless @extra || length $msg; 2809 return unless @extra || length $msg;
2518 2810
2519 if ($self->can_msg) { 2811 if ($self->can_msg) {
2812 # default colour, mask it out
2813 $color &= ~(cf::NDI_COLOR_MASK | cf::NDI_DEF)
2814 if $color & cf::NDI_DEF;
2815
2520 $self->send_packet ("msg " . $self->{json_coder}->encode ([$color, $channel, $msg, @extra])); 2816 $self->send_packet ("msg " . $self->{json_coder}->encode (
2817 [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra]));
2521 } else { 2818 } else {
2522 # replace some tags by gcfclient-compatible ones
2523 for ($msg) {
2524 1 while
2525 s/<b>([^<]*)<\/b>/[b]${1}[\/b]/
2526 || s/<i>([^<]*)<\/i>/[i]${1}[\/i]/
2527 || s/<u>([^<]*)<\/u>/[ul]${1}[\/ul]/
2528 || s/<tt>([^<]*)<\/tt>/[fixed]${1}[\/fixed]/
2529 || s/<fg name=\"([^"]+)\">([^<]*)<\/fg>/[color=$1]${2}[\/color]/;
2530 }
2531
2532 if ($color >= 0) { 2819 if ($color >= 0) {
2820 # replace some tags by gcfclient-compatible ones
2821 for ($msg) {
2822 1 while
2823 s/<b>([^<]*)<\/b>/[b]${1}[\/b]/
2824 || s/<i>([^<]*)<\/i>/[i]${1}[\/i]/
2825 || s/<u>([^<]*)<\/u>/[ul]${1}[\/ul]/
2826 || s/<tt>([^<]*)<\/tt>/[fixed]${1}[\/fixed]/
2827 || s/<fg name=\"([^"]+)\">([^<]*)<\/fg>/[color=$1]${2}[\/color]/;
2828 }
2829
2830 $color &= cf::NDI_COLOR_MASK;
2831
2832 utf8::encode $msg;
2833
2533 if (0 && $msg =~ /\[/) { 2834 if (0 && $msg =~ /\[/) {
2835 # COMMAND/INFO
2534 $self->send_packet ("drawextinfo $color 4 0 $msg") 2836 $self->send_packet ("drawextinfo $color 10 8 $msg")
2535 } else { 2837 } else {
2536 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g; 2838 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g;
2537 $self->send_packet ("drawinfo $color $msg") 2839 $self->send_packet ("drawinfo $color $msg")
2538 } 2840 }
2539 } 2841 }
2540 } 2842 }
2541} 2843}
2542 2844
2543=item $client->ext_event ($type, %msg) 2845=item $client->ext_msg ($type, @msg)
2544 2846
2545Sends an ext event to the client. 2847Sends an ext event to the client.
2546 2848
2547=cut 2849=cut
2548 2850
2549sub cf::client::ext_event($$%) { 2851sub cf::client::ext_msg($$@) {
2550 my ($self, $type, %msg) = @_; 2852 my ($self, $type, @msg) = @_;
2551 2853
2552 return unless $self->extcmd; 2854 if ($self->extcmd == 2) {
2553 2855 $self->send_packet ("ext " . $self->{json_coder}->encode ([$type, @msg]));
2856 } elsif ($self->extcmd == 1) { # TODO: remove
2554 $msg{msgtype} = "event_$type"; 2857 push @msg, msgtype => "event_$type";
2555 $self->send_packet ("ext " . $self->{json_coder}->encode (\%msg)); 2858 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2859 }
2860}
2861
2862=item $client->ext_reply ($msgid, @msg)
2863
2864Sends an ext reply to the client.
2865
2866=cut
2867
2868sub cf::client::ext_reply($$@) {
2869 my ($self, $id, @msg) = @_;
2870
2871 if ($self->extcmd == 2) {
2872 $self->send_packet ("ext " . $self->{json_coder}->encode (["reply-$id", @msg]));
2873 } elsif ($self->extcmd == 1) {
2874 #TODO: version 1, remove
2875 unshift @msg, msgtype => "reply", msgid => $id;
2876 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2877 }
2556} 2878}
2557 2879
2558=item $success = $client->query ($flags, "text", \&cb) 2880=item $success = $client->query ($flags, "text", \&cb)
2559 2881
2560Queues a query to the client, calling the given callback with 2882Queues a query to the client, calling the given callback with
2615 my ($ns, $buf) = @_; 2937 my ($ns, $buf) = @_;
2616 2938
2617 my $msg = eval { $ns->{json_coder}->decode ($buf) }; 2939 my $msg = eval { $ns->{json_coder}->decode ($buf) };
2618 2940
2619 if (ref $msg) { 2941 if (ref $msg) {
2942 my ($type, $reply, @payload) =
2943 "ARRAY" eq ref $msg
2944 ? @$msg
2945 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
2946
2947 my @reply;
2948
2620 if (my $cb = $EXTICMD{$msg->{msgtype}}) { 2949 if (my $cb = $EXTICMD{$type}) {
2621 if (my %reply = $cb->($ns, $msg)) { 2950 @reply = $cb->($ns, @payload);
2622 $reply{msgid} = $msg->{msgid};
2623 $ns->send ("ext " . $ns->{json_coder}->encode (\%reply));
2624 }
2625 } 2951 }
2952
2953 $ns->ext_reply ($reply, @reply)
2954 if $reply;
2955
2626 } else { 2956 } else {
2627 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n"; 2957 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n";
2628 } 2958 }
2629 2959
2630 cf::override; 2960 cf::override;
2677our $safe = new Safe "safe"; 3007our $safe = new Safe "safe";
2678our $safe_hole = new Safe::Hole; 3008our $safe_hole = new Safe::Hole;
2679 3009
2680$SIG{FPE} = 'IGNORE'; 3010$SIG{FPE} = 'IGNORE';
2681 3011
2682$safe->permit_only (Opcode::opset qw(:base_core :base_mem :base_orig :base_math sort time)); 3012$safe->permit_only (Opcode::opset qw(
3013 :base_core :base_mem :base_orig :base_math
3014 grepstart grepwhile mapstart mapwhile
3015 sort time
3016));
2683 3017
2684# here we export the classes and methods available to script code 3018# here we export the classes and methods available to script code
2685 3019
2686=pod 3020=pod
2687 3021
2688The following functions and methods are available within a safe environment: 3022The following functions and methods are available within a safe environment:
2689 3023
2690 cf::object 3024 cf::object
2691 contr pay_amount pay_player map x y force_find force_add 3025 contr pay_amount pay_player map x y force_find force_add destroy
2692 insert remove 3026 insert remove name archname title slaying race decrease_ob_nr
2693 3027
2694 cf::object::player 3028 cf::object::player
2695 player 3029 player
2696 3030
2697 cf::player 3031 cf::player
2702 3036
2703=cut 3037=cut
2704 3038
2705for ( 3039for (
2706 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y 3040 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y
3041 insert remove inv name archname title slaying race
2707 insert remove)], 3042 decrease_ob_nr destroy)],
2708 ["cf::object::player" => qw(player)], 3043 ["cf::object::player" => qw(player)],
2709 ["cf::player" => qw(peaceful)], 3044 ["cf::player" => qw(peaceful)],
2710 ["cf::map" => qw(trigger)], 3045 ["cf::map" => qw(trigger)],
2711) { 3046) {
2712 no strict 'refs'; 3047 no strict 'refs';
2788# the server's init and main functions 3123# the server's init and main functions
2789 3124
2790sub load_facedata($) { 3125sub load_facedata($) {
2791 my ($path) = @_; 3126 my ($path) = @_;
2792 3127
3128 # HACK to clear player env face cache, we need some signal framework
3129 # for this (global event?)
3130 %ext::player_env::MUSIC_FACE_CACHE = ();
3131
3132 my $enc = JSON::XS->new->utf8->canonical->relaxed;
3133
2793 warn "loading facedata from $path\n"; 3134 warn "loading facedata from $path\n";
2794 3135
2795 my $facedata; 3136 my $facedata;
2796 0 < aio_load $path, $facedata 3137 0 < aio_load $path, $facedata
2797 or die "$path: $!"; 3138 or die "$path: $!";
2799 $facedata = Coro::Storable::thaw $facedata; 3140 $facedata = Coro::Storable::thaw $facedata;
2800 3141
2801 $facedata->{version} == 2 3142 $facedata->{version} == 2
2802 or cf::cleanup "$path: version mismatch, cannot proceed."; 3143 or cf::cleanup "$path: version mismatch, cannot proceed.";
2803 3144
3145 # patch in the exptable
3146 $facedata->{resource}{"res/exp_table"} = {
3147 type => FT_RSRC,
3148 data => $enc->encode ([map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]),
3149 };
3150 cf::cede_to_tick;
3151
2804 { 3152 {
2805 my $faces = $facedata->{faceinfo}; 3153 my $faces = $facedata->{faceinfo};
2806 3154
2807 while (my ($face, $info) = each %$faces) { 3155 while (my ($face, $info) = each %$faces) {
2808 my $idx = (cf::face::find $face) || cf::face::alloc $face; 3156 my $idx = (cf::face::find $face) || cf::face::alloc $face;
3157
2809 cf::face::set_visibility $idx, $info->{visibility}; 3158 cf::face::set_visibility $idx, $info->{visibility};
2810 cf::face::set_magicmap $idx, $info->{magicmap}; 3159 cf::face::set_magicmap $idx, $info->{magicmap};
2811 cf::face::set_data $idx, 0, $info->{data32}, $info->{chksum32}; 3160 cf::face::set_data $idx, 0, $info->{data32}, Digest::MD5::md5 $info->{data32};
2812 cf::face::set_data $idx, 1, $info->{data64}, $info->{chksum64}; 3161 cf::face::set_data $idx, 1, $info->{data64}, Digest::MD5::md5 $info->{data64};
2813 3162
2814 cf::cede_to_tick; 3163 cf::cede_to_tick;
2815 } 3164 }
2816 3165
2817 while (my ($face, $info) = each %$faces) { 3166 while (my ($face, $info) = each %$faces) {
2818 next unless $info->{smooth}; 3167 next unless $info->{smooth};
3168
2819 my $idx = cf::face::find $face 3169 my $idx = cf::face::find $face
2820 or next; 3170 or next;
3171
2821 if (my $smooth = cf::face::find $info->{smooth}) { 3172 if (my $smooth = cf::face::find $info->{smooth}) {
2822 cf::face::set_smooth $idx, $smooth; 3173 cf::face::set_smooth $idx, $smooth;
2823 cf::face::set_smoothlevel $idx, $info->{smoothlevel}; 3174 cf::face::set_smoothlevel $idx, $info->{smoothlevel};
2824 } else { 3175 } else {
2825 warn "smooth face '$info->{smooth}' not found for face '$face'"; 3176 warn "smooth face '$info->{smooth}' not found for face '$face'";
2842 3193
2843 { 3194 {
2844 # TODO: for gcfclient pleasure, we should give resources 3195 # TODO: for gcfclient pleasure, we should give resources
2845 # that gcfclient doesn't grok a >10000 face index. 3196 # that gcfclient doesn't grok a >10000 face index.
2846 my $res = $facedata->{resource}; 3197 my $res = $facedata->{resource};
2847 my $enc = JSON::XS->new->utf8->canonical;
2848 3198
2849 while (my ($name, $info) = each %$res) { 3199 while (my ($name, $info) = each %$res) {
2850 my $meta = $enc->encode ({ 3200 if (defined $info->{type}) {
2851 name => $name,
2852 type => $info->{type},
2853 copyright => $info->{copyright}, #TODO#
2854 });
2855
2856 my $idx = (cf::face::find $name) || cf::face::alloc $name; 3201 my $idx = (cf::face::find $name) || cf::face::alloc $name;
3202 my $data;
2857 3203
2858 if ($name =~ /\.jpg$/) { 3204 if ($info->{type} & 1) {
2859 cf::face::set_data $idx, 0, $info->{data}, $info->{chksum};#d# temp hack 3205 # prepend meta info
2860 cf::face::set_data $idx, 1, $info->{data}, $info->{chksum};#d# temp hack 3206
3207 my $meta = $enc->encode ({
3208 name => $name,
3209 %{ $info->{meta} || {} },
3210 });
3211
3212 $data = pack "(w/a*)*", $meta, $info->{data};
3213 } else {
3214 $data = $info->{data};
3215 }
3216
3217 cf::face::set_data $idx, 0, $data, Digest::MD5::md5 $data;
3218 cf::face::set_type $idx, $info->{type};
2861 } else { 3219 } else {
2862 my $data = pack "(w/a*)*", $meta, $info->{data}; 3220 $RESOURCE{$name} = $info;
2863 my $chk = Digest::MD5::md5 "$info->{chksum},$meta"; # mangle data checksum and metadata
2864
2865 cf::face::set_type $idx, 1;
2866 cf::face::set_data $idx, 0, $data, $chk;
2867 } 3221 }
2868 3222
2869 cf::cede_to_tick; 3223 cf::cede_to_tick;
2870 } 3224 }
2871 } 3225 }
2872 3226
3227 cf::global->invoke (EVENT_GLOBAL_RESOURCE_UPDATE);
3228
2873 1 3229 1
2874} 3230}
2875 3231
3232cf::global->attach (on_resource_update => sub {
3233 if (my $soundconf = $RESOURCE{"res/sound.conf"}) {
3234 $soundconf = JSON::XS->new->utf8->relaxed->decode ($soundconf->{data});
3235
3236 for (0 .. SOUND_CAST_SPELL_0 - 1) {
3237 my $sound = $soundconf->{compat}[$_]
3238 or next;
3239
3240 my $face = cf::face::find "sound/$sound->[1]";
3241 cf::sound::set $sound->[0] => $face;
3242 cf::sound::old_sound_index $_, $face; # gcfclient-compat
3243 }
3244
3245 while (my ($k, $v) = each %{$soundconf->{event}}) {
3246 my $face = cf::face::find "sound/$v";
3247 cf::sound::set $k => $face;
3248 }
3249 }
3250});
3251
3252register_exticmd fx_want => sub {
3253 my ($ns, $want) = @_;
3254
3255 while (my ($k, $v) = each %$want) {
3256 $ns->fx_want ($k, $v);
3257 }
3258};
3259
2876sub reload_regions { 3260sub reload_regions {
3261 # HACK to clear player env face cache, we need some signal framework
3262 # for this (global event?)
3263 %ext::player_env::MUSIC_FACE_CACHE = ();
3264
2877 load_resource_file "$MAPDIR/regions" 3265 load_resource_file "$MAPDIR/regions"
2878 or die "unable to load regions file\n"; 3266 or die "unable to load regions file\n";
2879 3267
2880 for (cf::region::list) { 3268 for (cf::region::list) {
2881 $_->{match} = qr/$_->{match}/ 3269 $_->{match} = qr/$_->{match}/
2917 3305
2918sub init { 3306sub init {
2919 reload_resources; 3307 reload_resources;
2920} 3308}
2921 3309
2922sub cfg_load { 3310sub reload_config {
2923 open my $fh, "<:utf8", "$CONFDIR/config" 3311 open my $fh, "<:utf8", "$CONFDIR/config"
2924 or return; 3312 or return;
2925 3313
2926 local $/; 3314 local $/;
2927 *CFG = YAML::Syck::Load <$fh>; 3315 *CFG = YAML::Load <$fh>;
2928 3316
2929 $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37]; 3317 $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37];
2930 3318
2931 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset}; 3319 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset};
2932 $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset}; 3320 $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset};
2943sub main { 3331sub main {
2944 # we must not ever block the main coroutine 3332 # we must not ever block the main coroutine
2945 local $Coro::idle = sub { 3333 local $Coro::idle = sub {
2946 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d# 3334 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
2947 (async { 3335 (async {
2948 Event::one_event; 3336 $Coro::current->{desc} = "IDLE BUG HANDLER";
3337 EV::loop EV::LOOP_ONESHOT;
2949 })->prio (Coro::PRIO_MAX); 3338 })->prio (Coro::PRIO_MAX);
2950 }; 3339 };
2951 3340
2952 cfg_load; 3341 reload_config;
2953 db_init; 3342 db_init;
2954 load_extensions; 3343 load_extensions;
2955 3344
2956 $TICK_WATCHER->start; 3345 $TICK_WATCHER->start;
3346 $Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority
2957 Event::loop; 3347 EV::loop;
2958} 3348}
2959 3349
2960############################################################################# 3350#############################################################################
2961# initialisation and cleanup 3351# initialisation and cleanup
2962 3352
2963# install some emergency cleanup handlers 3353# install some emergency cleanup handlers
2964BEGIN { 3354BEGIN {
3355 our %SIGWATCHER = ();
2965 for my $signal (qw(INT HUP TERM)) { 3356 for my $signal (qw(INT HUP TERM)) {
2966 Event->signal ( 3357 $SIGWATCHER{$signal} = EV::signal $signal, sub {
2967 reentrant => 0,
2968 data => WF_AUTOCANCEL,
2969 signal => $signal,
2970 prio => 0,
2971 cb => sub {
2972 cf::cleanup "SIG$signal"; 3358 cf::cleanup "SIG$signal";
2973 },
2974 ); 3359 };
2975 } 3360 }
2976} 3361}
2977 3362
2978sub write_runtime { 3363sub write_runtime {
2979 my $runtime = "$LOCALDIR/runtime"; 3364 my $runtime = "$LOCALDIR/runtime";
3024 # and maps saved/destroyed asynchronously. 3409 # and maps saved/destroyed asynchronously.
3025 warn "begin emergency player save\n"; 3410 warn "begin emergency player save\n";
3026 for my $login (keys %cf::PLAYER) { 3411 for my $login (keys %cf::PLAYER) {
3027 my $pl = $cf::PLAYER{$login} or next; 3412 my $pl = $cf::PLAYER{$login} or next;
3028 $pl->valid or next; 3413 $pl->valid or next;
3414 delete $pl->{unclean_save}; # not strictly necessary, but cannot hurt
3029 $pl->save; 3415 $pl->save;
3030 } 3416 }
3031 warn "end emergency player save\n"; 3417 warn "end emergency player save\n";
3032 3418
3033 warn "begin emergency map save\n"; 3419 warn "begin emergency map save\n";
3072 warn "syncing database to disk"; 3458 warn "syncing database to disk";
3073 BDB::db_env_txn_checkpoint $DB_ENV; 3459 BDB::db_env_txn_checkpoint $DB_ENV;
3074 3460
3075 # if anything goes wrong in here, we should simply crash as we already saved 3461 # if anything goes wrong in here, we should simply crash as we already saved
3076 3462
3077 warn "cancelling all WF_AUTOCANCEL watchers";
3078 for (Event::all_watchers) {
3079 $_->cancel if $_->data & WF_AUTOCANCEL;
3080 }
3081
3082 warn "flushing outstanding aio requests"; 3463 warn "flushing outstanding aio requests";
3083 for (;;) { 3464 for (;;) {
3084 BDB::flush; 3465 BDB::flush;
3085 IO::AIO::flush; 3466 IO::AIO::flush;
3086 Coro::cede; 3467 Coro::cede_notself;
3087 last unless IO::AIO::nreqs || BDB::nreqs; 3468 last unless IO::AIO::nreqs || BDB::nreqs;
3088 warn "iterate..."; 3469 warn "iterate...";
3089 } 3470 }
3090 3471
3091 ++$RELOAD; 3472 ++$RELOAD;
3150 warn "reloading cf.pm"; 3531 warn "reloading cf.pm";
3151 require cf; 3532 require cf;
3152 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt 3533 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
3153 3534
3154 warn "loading config and database again"; 3535 warn "loading config and database again";
3155 cf::cfg_load; 3536 cf::reload_config;
3156 3537
3157 warn "loading extensions"; 3538 warn "loading extensions";
3158 cf::load_extensions; 3539 cf::load_extensions;
3159 3540
3160 warn "reattaching attachments to objects/players"; 3541 warn "reattaching attachments to objects/players";
3180 3561
3181sub reload_perl() { 3562sub reload_perl() {
3182 # doing reload synchronously and two reloads happen back-to-back, 3563 # doing reload synchronously and two reloads happen back-to-back,
3183 # coro crashes during coro_state_free->destroy here. 3564 # coro crashes during coro_state_free->destroy here.
3184 3565
3185 $RELOAD_WATCHER ||= Event->timer ( 3566 $RELOAD_WATCHER ||= EV::timer 0, 0, sub {
3186 reentrant => 0,
3187 after => 0,
3188 data => WF_AUTOCANCEL,
3189 cb => sub {
3190 do_reload_perl;
3191 undef $RELOAD_WATCHER; 3567 undef $RELOAD_WATCHER;
3192 }, 3568 do_reload_perl;
3193 ); 3569 };
3194} 3570}
3195 3571
3196register_command "reload" => sub { 3572register_command "reload" => sub {
3197 my ($who, $arg) = @_; 3573 my ($who, $arg) = @_;
3198 3574
3199 if ($who->flag (FLAG_WIZ)) { 3575 if ($who->flag (FLAG_WIZ)) {
3200 $who->message ("reloading server."); 3576 $who->message ("reloading server.");
3577 async {
3578 $Coro::current->{desc} = "perl_reload";
3201 async { reload_perl }; 3579 reload_perl;
3580 };
3202 } 3581 }
3203}; 3582};
3204 3583
3205unshift @INC, $LIBDIR; 3584unshift @INC, $LIBDIR;
3206 3585
3225 my $signal = new Coro::Signal; 3604 my $signal = new Coro::Signal;
3226 push @WAIT_FOR_TICK_BEGIN, $signal; 3605 push @WAIT_FOR_TICK_BEGIN, $signal;
3227 $signal->wait; 3606 $signal->wait;
3228} 3607}
3229 3608
3230 my $min = 1e6;#d# 3609$TICK_WATCHER = EV::periodic_ns 0, $TICK, 0, sub {
3231 my $avg = 10;
3232$TICK_WATCHER = Event->timer (
3233 reentrant => 0,
3234 parked => 1,
3235 prio => 0,
3236 at => $NEXT_TICK || $TICK,
3237 data => WF_AUTOCANCEL,
3238 cb => sub {
3239 if ($Coro::current != $Coro::main) { 3610 if ($Coro::current != $Coro::main) {
3240 Carp::cluck "major BUG: server tick called outside of main coro, skipping it" 3611 Carp::cluck "major BUG: server tick called outside of main coro, skipping it"
3241 unless ++$bug_warning > 10; 3612 unless ++$bug_warning > 10;
3242 return; 3613 return;
3243 } 3614 }
3244 3615
3245 $NOW = $tick_start = Event::time; 3616 $NOW = $tick_start = EV::now;
3246 3617
3247 cf::server_tick; # one server iteration 3618 cf::server_tick; # one server iteration
3248 3619
3249 0 && sync_job {#d# 3620 $RUNTIME += $TICK;
3250 for(1..10) { 3621 $NEXT_TICK = $_[0]->at;
3251 my $t = Event::time;
3252 my $map = my $map = new_from_path cf::map "/tmp/x.map"
3253 or die;
3254 3622
3255 $map->width (50); 3623 if ($NOW >= $NEXT_RUNTIME_WRITE) {
3256 $map->height (50); 3624 $NEXT_RUNTIME_WRITE = List::Util::max $NEXT_RUNTIME_WRITE + 10, $NOW + 5.;
3257 $map->alloc; 3625 Coro::async_pool {
3258 $map->_load_objects ("/tmp/x.map", 1); 3626 $Coro::current->{desc} = "runtime saver";
3259 my $t = Event::time - $t; 3627 write_runtime
3260 3628 or warn "ERROR: unable to write runtime file: $!";
3261 #next unless $t < 0.0013;#d#
3262 if ($t < $min) {
3263 $min = $t;
3264 }
3265 $avg = $avg * 0.99 + $t * 0.01;
3266 }
3267 warn "XXXXXXXXXXXXXXXXXX min $min avg $avg\n";#d#
3268 exit 0;
3269 # 2007-05-22 02:33:04.569 min 0.00112509727478027 avg 0.0012259249572477
3270 }; 3629 };
3630 }
3271 3631
3272 $RUNTIME += $TICK;
3273 $NEXT_TICK += $TICK;
3274
3275 if ($NOW >= $NEXT_RUNTIME_WRITE) {
3276 $NEXT_RUNTIME_WRITE = $NOW + 10;
3277 Coro::async_pool {
3278 write_runtime
3279 or warn "ERROR: unable to write runtime file: $!";
3280 };
3281 }
3282
3283# my $AFTER = Event::time;
3284# warn $AFTER - $NOW;#d#
3285
3286 if (my $sig = shift @WAIT_FOR_TICK_BEGIN) { 3632 if (my $sig = shift @WAIT_FOR_TICK_BEGIN) {
3287 $sig->send; 3633 $sig->send;
3288 } 3634 }
3289 while (my $sig = shift @WAIT_FOR_TICK) { 3635 while (my $sig = shift @WAIT_FOR_TICK) {
3290 $sig->send; 3636 $sig->send;
3291 } 3637 }
3292 3638
3293 $NOW = Event::time;
3294
3295 # if we are delayed by four ticks or more, skip them all
3296 $NEXT_TICK = $NOW if $NOW >= $NEXT_TICK + $TICK * 4;
3297
3298 $TICK_WATCHER->at ($NEXT_TICK);
3299 $TICK_WATCHER->start;
3300
3301 $LOAD = ($NOW - $tick_start) / $TICK; 3639 $LOAD = ($NOW - $tick_start) / $TICK;
3302 $LOADAVG = $LOADAVG * 0.75 + $LOAD * 0.25; 3640 $LOADAVG = $LOADAVG * 0.75 + $LOAD * 0.25;
3303 3641
3304 _post_tick; 3642 _post_tick;
3305 3643};
3306 3644$TICK_WATCHER->priority (EV::MAXPRI);
3307 },
3308);
3309 3645
3310{ 3646{
3311 BDB::max_poll_time $TICK * 0.1; 3647 # configure BDB
3312 $BDB_POLL_WATCHER = Event->io ( 3648
3313 reentrant => 0,
3314 fd => BDB::poll_fileno,
3315 poll => 'r',
3316 prio => 0,
3317 data => WF_AUTOCANCEL,
3318 cb => \&BDB::poll_cb,
3319 );
3320 BDB::min_parallel 8; 3649 BDB::min_parallel 8;
3321 3650 BDB::max_poll_reqs $TICK * 0.1;
3322 BDB::set_sync_prepare { 3651 $Coro::BDB::WATCHER->priority (1);
3323 my $status;
3324 my $current = $Coro::current;
3325 (
3326 sub {
3327 $status = $!;
3328 $current->ready; undef $current;
3329 },
3330 sub {
3331 Coro::schedule while defined $current;
3332 $! = $status;
3333 },
3334 )
3335 };
3336 3652
3337 unless ($DB_ENV) { 3653 unless ($DB_ENV) {
3338 $DB_ENV = BDB::db_env_create; 3654 $DB_ENV = BDB::db_env_create;
3655 $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT | BDB::TXN_NOSYNC
3656 | BDB::LOG_AUTOREMOVE, 1);
3657 $DB_ENV->set_timeout (30, BDB::SET_TXN_TIMEOUT);
3658 $DB_ENV->set_timeout (30, BDB::SET_LOCK_TIMEOUT);
3339 3659
3340 cf::sync_job { 3660 cf::sync_job {
3341 eval { 3661 eval {
3342 BDB::db_env_open 3662 BDB::db_env_open
3343 $DB_ENV, 3663 $DB_ENV,
3345 BDB::INIT_LOCK | BDB::INIT_LOG | BDB::INIT_MPOOL | BDB::INIT_TXN 3665 BDB::INIT_LOCK | BDB::INIT_LOG | BDB::INIT_MPOOL | BDB::INIT_TXN
3346 | BDB::RECOVER | BDB::REGISTER | BDB::USE_ENVIRON | BDB::CREATE, 3666 | BDB::RECOVER | BDB::REGISTER | BDB::USE_ENVIRON | BDB::CREATE,
3347 0666; 3667 0666;
3348 3668
3349 cf::cleanup "db_env_open($BDBDIR): $!" if $!; 3669 cf::cleanup "db_env_open($BDBDIR): $!" if $!;
3350
3351 $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT | BDB::TXN_NOSYNC, 1);
3352 $DB_ENV->set_lk_detect;
3353 }; 3670 };
3354 3671
3355 cf::cleanup "db_env_open(db): $@" if $@; 3672 cf::cleanup "db_env_open(db): $@" if $@;
3356 }; 3673 };
3357 } 3674 }
3675
3676 $BDB_DEADLOCK_WATCHER = EV::periodic 0, 3, 0, sub {
3677 BDB::db_env_lock_detect $DB_ENV, 0, BDB::LOCK_DEFAULT, 0, sub { };
3678 };
3679 $BDB_CHECKPOINT_WATCHER = EV::periodic 0, 60, 0, sub {
3680 BDB::db_env_txn_checkpoint $DB_ENV, 0, 0, 0, sub { };
3681 };
3682 $BDB_TRICKLE_WATCHER = EV::periodic 0, 10, 0, sub {
3683 BDB::db_env_memp_trickle $DB_ENV, 20, 0, sub { };
3684 };
3358} 3685}
3359 3686
3360{ 3687{
3688 # configure IO::AIO
3689
3361 IO::AIO::min_parallel 8; 3690 IO::AIO::min_parallel 8;
3362
3363 undef $Coro::AIO::WATCHER;
3364 IO::AIO::max_poll_time $TICK * 0.1; 3691 IO::AIO::max_poll_time $TICK * 0.1;
3365 $AIO_POLL_WATCHER = Event->io ( 3692 $Coro::AIO::WATCHER->priority (1);
3366 reentrant => 0,
3367 data => WF_AUTOCANCEL,
3368 fd => IO::AIO::poll_fileno,
3369 poll => 'r',
3370 prio => 6,
3371 cb => \&IO::AIO::poll_cb,
3372 );
3373} 3693}
3374 3694
3375my $_log_backtrace; 3695my $_log_backtrace;
3376 3696
3377sub _log_backtrace { 3697sub _log_backtrace {
3381 3701
3382 # limit the # of concurrent backtraces 3702 # limit the # of concurrent backtraces
3383 if ($_log_backtrace < 2) { 3703 if ($_log_backtrace < 2) {
3384 ++$_log_backtrace; 3704 ++$_log_backtrace;
3385 async { 3705 async {
3706 $Coro::current->{desc} = "abt $msg";
3707
3386 my @bt = fork_call { 3708 my @bt = fork_call {
3387 @addr = map { sprintf "%x", $_ } @addr; 3709 @addr = map { sprintf "%x", $_ } @addr;
3388 my $self = (-f "/proc/$$/exe") ? "/proc/$$/exe" : $^X; 3710 my $self = (-f "/proc/$$/exe") ? "/proc/$$/exe" : $^X;
3389 open my $fh, "exec addr2line -C -f -i -e \Q$self\E @addr 2>&1 |" 3711 open my $fh, "exec addr2line -C -f -i -e \Q$self\E @addr 2>&1 |"
3390 or die "addr2line: $!"; 3712 or die "addr2line: $!";

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines