ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf.pm
Revision: 1.354
Committed: Sun Sep 2 12:26:54 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.353: +8 -3 lines
Log Message:
optimisation

File Contents

# Content
1 package cf;
2
3 use utf8;
4 use strict;
5
6 use Symbol;
7 use List::Util;
8 use Socket;
9 use Storable;
10 use Event;
11 use Opcode;
12 use Safe;
13 use Safe::Hole;
14
15 use Coro 3.64 ();
16 use Coro::State;
17 use Coro::Handle;
18 use Coro::Event;
19 use Coro::Timer;
20 use Coro::Signal;
21 use Coro::Semaphore;
22 use Coro::AIO;
23 use Coro::Storable;
24 use Coro::Util ();
25
26 use JSON::XS ();
27 use BDB ();
28 use Data::Dumper;
29 use Digest::MD5;
30 use Fcntl;
31 use YAML::Syck ();
32 use IO::AIO 2.32 ();
33 use Time::HiRes;
34 use Compress::LZF;
35 use Digest::MD5 ();
36
37 # configure various modules to our taste
38 #
39 $Storable::canonical = 1; # reduce rsync transfers
40 Coro::State::cctx_stacksize 256000; # 1-2MB stack, for deep recursions in maze generator
41 Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later
42
43 $Event::Eval = 1; # no idea why this is required, but it is
44
45 # work around bug in YAML::Syck - bad news for perl6, will it be as broken wrt. unicode?
46 $YAML::Syck::ImplicitUnicode = 1;
47
48 $Coro::main->prio (Coro::PRIO_MAX); # run main coroutine ("the server") with very high priority
49
50 sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
51
52 our %COMMAND = ();
53 our %COMMAND_TIME = ();
54
55 our @EXTS = (); # list of extension package names
56 our %EXTCMD = ();
57 our %EXTICMD = ();
58 our %EXT_CORO = (); # coroutines bound to extensions
59 our %EXT_MAP = (); # pluggable maps
60
61 our $RELOAD; # number of reloads so far
62 our @EVENT;
63
64 our $CONFDIR = confdir;
65 our $DATADIR = datadir;
66 our $LIBDIR = "$DATADIR/ext";
67 our $PODDIR = "$DATADIR/pod";
68 our $MAPDIR = "$DATADIR/" . mapdir;
69 our $LOCALDIR = localdir;
70 our $TMPDIR = "$LOCALDIR/" . tmpdir;
71 our $UNIQUEDIR = "$LOCALDIR/" . uniquedir;
72 our $PLAYERDIR = "$LOCALDIR/" . playerdir;
73 our $RANDOMDIR = "$LOCALDIR/random";
74 our $BDBDIR = "$LOCALDIR/db";
75
76 our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!)
77 our $TICK_WATCHER;
78 our $AIO_POLL_WATCHER;
79 our $NEXT_RUNTIME_WRITE; # when should the runtime file be written
80 our $NEXT_TICK;
81 our $NOW;
82 our $USE_FSYNC = 1; # use fsync to write maps - default off
83
84 our $BDB_POLL_WATCHER;
85 our $DB_ENV;
86
87 our %CFG;
88
89 our $UPTIME; $UPTIME ||= time;
90 our $RUNTIME;
91
92 our %PLAYER; # all users
93 our %MAP; # all maps
94 our $LINK_MAP; # the special {link} map, which is always available
95
96 # used to convert map paths into valid unix filenames by replacing / by ∕
97 our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons
98
99 our $LOAD; # a number between 0 (idle) and 1 (too many objects)
100 our $LOADAVG; # same thing, but with alpha-smoothing
101 our $tick_start; # for load detecting purposes
102
103 binmode STDOUT;
104 binmode STDERR;
105
106 # read virtual server time, if available
107 unless ($RUNTIME || !-e "$LOCALDIR/runtime") {
108 open my $fh, "<", "$LOCALDIR/runtime"
109 or die "unable to read runtime file: $!";
110 $RUNTIME = <$fh> + 0.;
111 }
112
113 mkdir $_
114 for $LOCALDIR, $TMPDIR, $UNIQUEDIR, $PLAYERDIR, $RANDOMDIR, $BDBDIR;
115
116 our $EMERGENCY_POSITION;
117
118 sub cf::map::normalise;
119
120 #############################################################################
121
122 =head2 GLOBAL VARIABLES
123
124 =over 4
125
126 =item $cf::UPTIME
127
128 The timestamp of the server start (so not actually an uptime).
129
130 =item $cf::RUNTIME
131
132 The time this server has run, starts at 0 and is increased by $cf::TICK on
133 every server tick.
134
135 =item $cf::CONFDIR $cf::DATADIR $cf::LIBDIR $cf::PODDIR
136 $cf::MAPDIR $cf::LOCALDIR $cf::TMPDIR $cf::UNIQUEDIR
137 $cf::PLAYERDIR $cf::RANDOMDIR $cf::BDBDIR
138
139 Various directories - "/etc", read-only install directory, perl-library
140 directory, pod-directory, read-only maps directory, "/var", "/var/tmp",
141 unique-items directory, player file directory, random maps directory and
142 database environment.
143
144 =item $cf::NOW
145
146 The time of the last (current) server tick.
147
148 =item $cf::TICK
149
150 The interval between server ticks, in seconds.
151
152 =item $cf::LOADAVG
153
154 The current CPU load on the server (alpha-smoothed), as a value between 0
155 (none) and 1 (overloaded), indicating how much time is spent on processing
156 objects per tick. Healthy values are < 0.5.
157
158 =item $cf::LOAD
159
160 The raw value load value from the last tick.
161
162 =item %cf::CFG
163
164 Configuration for the server, loaded from C</etc/crossfire/config>, or
165 from wherever your confdir points to.
166
167 =item cf::wait_for_tick, cf::wait_for_tick_begin
168
169 These are functions that inhibit the current coroutine one tick. cf::wait_for_tick_begin only
170 returns directly I<after> the tick processing (and consequently, can only wake one process
171 per tick), while cf::wait_for_tick wakes up all waiters after tick processing.
172
173 =item @cf::INVOKE_RESULTS
174
175 This array contains the results of the last C<invoke ()> call. When
176 C<cf::override> is called C<@cf::INVOKE_RESULTS> is set to the parameters of
177 that call.
178
179 =back
180
181 =cut
182
183 BEGIN {
184 *CORE::GLOBAL::warn = sub {
185 my $msg = join "", @_;
186
187 $msg .= "\n"
188 unless $msg =~ /\n$/;
189
190 $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge;
191
192 utf8::encode $msg;
193 LOG llevError, $msg;
194 };
195 }
196
197 @safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
198 @safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable';
199 @safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable';
200 @safe::cf::client::ISA = @cf::client::ISA = 'cf::attachable';
201 @safe::cf::map::ISA = @cf::map::ISA = 'cf::attachable';
202 @safe::cf::arch::ISA = @cf::arch::ISA = 'cf::object';
203 @safe::cf::object::player::ISA = @cf::object::player::ISA = 'cf::object'; # not really true (yet)
204
205 # we bless all objects into (empty) derived classes to force a method lookup
206 # within the Safe compartment.
207 for my $pkg (qw(
208 cf::global cf::attachable
209 cf::object cf::object::player
210 cf::client cf::player
211 cf::arch cf::living
212 cf::map cf::party cf::region
213 )) {
214 no strict 'refs';
215 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg;
216 }
217
218 $Event::DIED = sub {
219 warn "error in event callback: @_";
220 };
221
222 #############################################################################
223
224 =head2 UTILITY FUNCTIONS
225
226 =over 4
227
228 =item dumpval $ref
229
230 =cut
231
232 sub dumpval {
233 eval {
234 local $SIG{__DIE__};
235 my $d;
236 if (1) {
237 $d = new Data::Dumper([$_[0]], ["*var"]);
238 $d->Terse(1);
239 $d->Indent(2);
240 $d->Quotekeys(0);
241 $d->Useqq(1);
242 #$d->Bless(...);
243 $d->Seen($_[1]) if @_ > 1;
244 $d = $d->Dump();
245 }
246 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge;
247 $d
248 } || "[unable to dump $_[0]: '$@']";
249 }
250
251 =item $ref = cf::from_json $json
252
253 Converts a JSON string into the corresponding perl data structure.
254
255 =item $json = cf::to_json $ref
256
257 Converts a perl data structure into its JSON representation.
258
259 =cut
260
261 our $json_coder = JSON::XS->new->utf8->max_size (1e6); # accept ~1mb max
262
263 sub to_json ($) { $json_coder->encode ($_[0]) }
264 sub from_json ($) { $json_coder->decode ($_[0]) }
265
266 =item cf::lock_wait $string
267
268 Wait until the given lock is available. See cf::lock_acquire.
269
270 =item my $lock = cf::lock_acquire $string
271
272 Wait until the given lock is available and then acquires it and returns
273 a Coro::guard object. If the guard object gets destroyed (goes out of scope,
274 for example when the coroutine gets canceled), the lock is automatically
275 returned.
276
277 Locks are *not* recursive, locking from the same coro twice results in a
278 deadlocked coro.
279
280 Lock names should begin with a unique identifier (for example, cf::map::find
281 uses map_find and cf::map::load uses map_load).
282
283 =item $locked = cf::lock_active $string
284
285 Return true if the lock is currently active, i.e. somebody has locked it.
286
287 =cut
288
289 our %LOCK;
290
291 sub lock_wait($) {
292 my ($key) = @_;
293
294 # wait for lock, if any
295 while ($LOCK{$key}) {
296 push @{ $LOCK{$key} }, $Coro::current;
297 Coro::schedule;
298 }
299 }
300
301 sub lock_acquire($) {
302 my ($key) = @_;
303
304 # wait, to be sure we are not locked
305 lock_wait $key;
306
307 $LOCK{$key} = [];
308
309 Coro::guard {
310 # wake up all waiters, to be on the safe side
311 $_->ready for @{ delete $LOCK{$key} };
312 }
313 }
314
315 sub lock_active($) {
316 my ($key) = @_;
317
318 ! ! $LOCK{$key}
319 }
320
321 sub freeze_mainloop {
322 return unless $TICK_WATCHER->is_active;
323
324 my $guard = Coro::guard {
325 $TICK_WATCHER->start;
326 };
327 $TICK_WATCHER->stop;
328 $guard
329 }
330
331 =item cf::get_slot $time[, $priority[, $name]]
332
333 Allocate $time seconds of blocking CPU time at priority C<$priority>:
334 This call blocks and returns only when you have at least C<$time> seconds
335 of cpu time till the next tick. The slot is only valid till the next cede.
336
337 The optional C<$name> can be used to identify the job to run. It might be
338 used for statistical purposes and should identify the same time-class.
339
340 Useful for short background jobs.
341
342 =cut
343
344 our @SLOT_QUEUE;
345 our $SLOT_QUEUE;
346
347 $SLOT_QUEUE->cancel if $SLOT_QUEUE;
348 $SLOT_QUEUE = Coro::async {
349 my $signal = new Coro::Signal;
350
351 while () {
352 next_job:
353 my $avail = cf::till_tick;
354 if ($avail > 0.01) {
355 for (0 .. $#SLOT_QUEUE) {
356 if ($SLOT_QUEUE[$_][0] < $avail) {
357 my $job = splice @SLOT_QUEUE, $_, 1, ();
358 $job->[2]->send;
359 Coro::cede;
360 goto next_job;
361 }
362 }
363 }
364
365 if (@SLOT_QUEUE) {
366 # we do not use wait_For_tick() as it returns immediately when tick is inactive
367 push @cf::WAIT_FOR_TICK, $signal;
368 $signal->wait;
369 } else {
370 Coro::schedule;
371 }
372 }
373 };
374
375 sub get_slot($;$$) {
376 my ($time, $pri, $name) = @_;
377
378 $time = $TICK * .6 if $time > $TICK * .6;
379 my $sig = new Coro::Signal;
380
381 push @SLOT_QUEUE, [$time, $pri, $sig, $name];
382 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE;
383 $SLOT_QUEUE->ready;
384 $sig->wait;
385 }
386
387 =item cf::async { BLOCK }
388
389 Currently the same as Coro::async_pool, meaning you cannot use
390 C<on_destroy>, C<join> or other gimmicks on these coroutines. The only
391 thing you are allowed to do is call C<prio> on it.
392
393 =cut
394
395 BEGIN { *async = \&Coro::async_pool }
396
397 =item cf::sync_job { BLOCK }
398
399 The design of Crossfire TRT requires that the main coroutine ($Coro::main)
400 is always able to handle events or runnable, as Crossfire TRT is only
401 partly reentrant. Thus "blocking" it by e.g. waiting for I/O is not
402 acceptable.
403
404 If it must be done, put the blocking parts into C<sync_job>. This will run
405 the given BLOCK in another coroutine while waiting for the result. The
406 server will be frozen during this time, so the block should either finish
407 fast or be very important.
408
409 =cut
410
411 sub sync_job(&) {
412 my ($job) = @_;
413
414 if ($Coro::current == $Coro::main) {
415 my $time = Event::time;
416
417 # this is the main coro, too bad, we have to block
418 # till the operation succeeds, freezing the server :/
419
420 # TODO: use suspend/resume instead
421 # (but this is cancel-safe)
422 my $freeze_guard = freeze_mainloop;
423
424 my $busy = 1;
425 my @res;
426
427 (async {
428 @res = eval { $job->() };
429 warn $@ if $@;
430 undef $busy;
431 })->prio (Coro::PRIO_MAX);
432
433 while ($busy) {
434 Coro::cede or Event::one_event;
435 }
436
437 $time = Event::time - $time;
438
439 LOG llevError | logBacktrace, Carp::longmess "long sync job"
440 if $time > $TICK * 0.5 && $TICK_WATCHER->is_active;
441
442 $tick_start += $time; # do not account sync jobs to server load
443
444 wantarray ? @res : $res[0]
445 } else {
446 # we are in another coroutine, how wonderful, everything just works
447
448 $job->()
449 }
450 }
451
452 =item $coro = cf::async_ext { BLOCK }
453
454 Like async, but this coro is automatically being canceled when the
455 extension calling this is being unloaded.
456
457 =cut
458
459 sub async_ext(&) {
460 my $cb = shift;
461
462 my $coro = &Coro::async ($cb);
463
464 $coro->on_destroy (sub {
465 delete $EXT_CORO{$coro+0};
466 });
467 $EXT_CORO{$coro+0} = $coro;
468
469 $coro
470 }
471
472 =item fork_call { }, $args
473
474 Executes the given code block with the given arguments in a seperate
475 process, returning the results. Everything must be serialisable with
476 Coro::Storable. May, of course, block. Note that the executed sub may
477 never block itself or use any form of Event handling.
478
479 =cut
480
481 sub fork_call(&@) {
482 my ($cb, @args) = @_;
483
484 # we seemingly have to make a local copy of the whole thing,
485 # otherwise perl prematurely frees the stuff :/
486 # TODO: investigate and fix (liekly this will be rather laborious)
487
488 my @res = Coro::Util::fork_eval {
489 reset_signals;
490 &$cb
491 }, @args;
492
493 wantarray ? @res : $res[-1]
494 }
495
496 =item $value = cf::db_get $family => $key
497
498 Returns a single value from the environment database.
499
500 =item cf::db_put $family => $key => $value
501
502 Stores the given C<$value> in the family. It can currently store binary
503 data only (use Compress::LZF::sfreeze_cr/sthaw to convert to/from binary).
504
505 =cut
506
507 our $DB;
508
509 sub db_init {
510 unless ($DB) {
511 $DB = BDB::db_create $DB_ENV;
512
513 cf::sync_job {
514 eval {
515 $DB->set_flags (BDB::CHKSUM);
516
517 BDB::db_open $DB, undef, "db", undef, BDB::BTREE,
518 BDB::CREATE | BDB::AUTO_COMMIT, 0666;
519 cf::cleanup "db_open(db): $!" if $!;
520 };
521 cf::cleanup "db_open(db): $@" if $@;
522 };
523 }
524 }
525
526 sub db_get($$) {
527 my $key = "$_[0]/$_[1]";
528
529 cf::sync_job {
530 BDB::db_get $DB, undef, $key, my $data;
531
532 $! ? ()
533 : $data
534 }
535 }
536
537 sub db_put($$$) {
538 BDB::dbreq_pri 4;
539 BDB::db_put $DB, undef, "$_[0]/$_[1]", $_[2], 0, sub { };
540 }
541
542 =item cf::cache $id => [$paths...], $processversion => $process
543
544 Generic caching function that returns the value of the resource $id,
545 caching and regenerating as required.
546
547 This function can block.
548
549 =cut
550
551 sub cache {
552 my ($id, $src, $processversion, $process) = @_;
553
554 my $meta =
555 join "\x00",
556 $processversion,
557 map {
558 aio_stat $_
559 and Carp::croak "$_: $!";
560
561 ($_, (stat _)[7,9])
562 } @$src;
563
564 my $dbmeta = db_get cache => "$id/meta";
565 if ($dbmeta ne $meta) {
566 # changed, we may need to process
567
568 my @data;
569 my $md5;
570
571 for (0 .. $#$src) {
572 0 <= aio_load $src->[$_], $data[$_]
573 or Carp::croak "$src->[$_]: $!";
574 }
575
576 # if processing is expensive, check
577 # checksum first
578 if (1) {
579 $md5 =
580 join "\x00",
581 $processversion,
582 map {
583 cf::cede_to_tick;
584 ($src->[$_], Digest::MD5::md5_hex $data[$_])
585 } 0.. $#$src;
586
587
588 my $dbmd5 = db_get cache => "$id/md5";
589 if ($dbmd5 eq $md5) {
590 db_put cache => "$id/meta", $meta;
591
592 return db_get cache => "$id/data";
593 }
594 }
595
596 my $t1 = Time::HiRes::time;
597 my $data = $process->(\@data);
598 my $t2 = Time::HiRes::time;
599
600 warn "cache: '$id' processed in ", $t2 - $t1, "s\n";
601
602 db_put cache => "$id/data", $data;
603 db_put cache => "$id/md5" , $md5;
604 db_put cache => "$id/meta", $meta;
605
606 return $data;
607 }
608
609 db_get cache => "$id/data"
610 }
611
612 =item cf::datalog type => key => value, ...
613
614 Log a datalog packet of the given type with the given key-value pairs.
615
616 =cut
617
618 sub datalog($@) {
619 my ($type, %kv) = @_;
620 warn "DATALOG ", JSON::XS->new->ascii->encode ({ %kv, type => $type });
621 }
622
623 =back
624
625 =cut
626
627 #############################################################################
628
629 =head2 ATTACHABLE OBJECTS
630
631 Many objects in crossfire are so-called attachable objects. That means you can
632 attach callbacks/event handlers (a collection of which is called an "attachment")
633 to it. All such attachable objects support the following methods.
634
635 In the following description, CLASS can be any of C<global>, C<object>
636 C<player>, C<client> or C<map> (i.e. the attachable objects in
637 Crossfire TRT).
638
639 =over 4
640
641 =item $attachable->attach ($attachment, key => $value...)
642
643 =item $attachable->detach ($attachment)
644
645 Attach/detach a pre-registered attachment to a specific object and give it
646 the specified key/value pairs as arguments.
647
648 Example, attach a minesweeper attachment to the given object, making it a
649 10x10 minesweeper game:
650
651 $obj->attach (minesweeper => width => 10, height => 10);
652
653 =item $bool = $attachable->attached ($name)
654
655 Checks wether the named attachment is currently attached to the object.
656
657 =item cf::CLASS->attach ...
658
659 =item cf::CLASS->detach ...
660
661 Define an anonymous attachment and attach it to all objects of the given
662 CLASS. See the next function for an explanation of its arguments.
663
664 You can attach to global events by using the C<cf::global> class.
665
666 Example, log all player logins:
667
668 cf::player->attach (
669 on_login => sub {
670 my ($pl) = @_;
671 ...
672 },
673 );
674
675 Example, attach to the jeweler skill:
676
677 cf::object->attach (
678 type => cf::SKILL,
679 subtype => cf::SK_JEWELER,
680 on_use_skill => sub {
681 my ($sk, $ob, $part, $dir, $msg) = @_;
682 ...
683 },
684 );
685
686 =item cf::CLASS::attachment $name, ...
687
688 Register an attachment by C<$name> through which attachable objects of the
689 given CLASS can refer to this attachment.
690
691 Some classes such as crossfire maps and objects can specify attachments
692 that are attached at load/instantiate time, thus the need for a name.
693
694 These calls expect any number of the following handler/hook descriptions:
695
696 =over 4
697
698 =item prio => $number
699
700 Set the priority for all following handlers/hooks (unless overwritten
701 by another C<prio> setting). Lower priority handlers get executed
702 earlier. The default priority is C<0>, and many built-in handlers are
703 registered at priority C<-1000>, so lower priorities should not be used
704 unless you know what you are doing.
705
706 =item type => $type
707
708 (Only for C<< cf::object->attach >> calls), limits the attachment to the
709 given type of objects only (the additional parameter C<subtype> can be
710 used to further limit to the given subtype).
711
712 =item on_I<event> => \&cb
713
714 Call the given code reference whenever the named event happens (event is
715 something like C<instantiate>, C<apply>, C<use_skill> and so on, and which
716 handlers are recognised generally depends on the type of object these
717 handlers attach to).
718
719 See F<include/eventinc.h> for the full list of events supported, and their
720 class.
721
722 =item package => package::
723
724 Look for sub functions of the name C<< on_I<event> >> in the given
725 package and register them. Only handlers for eevents supported by the
726 object/class are recognised.
727
728 =back
729
730 Example, define an attachment called "sockpuppet" that calls the given
731 event handler when a monster attacks:
732
733 cf::object::attachment sockpuppet =>
734 on_skill_attack => sub {
735 my ($self, $victim) = @_;
736 ...
737 }
738 }
739
740 =item $attachable->valid
741
742 Just because you have a perl object does not mean that the corresponding
743 C-level object still exists. If you try to access an object that has no
744 valid C counterpart anymore you get an exception at runtime. This method
745 can be used to test for existence of the C object part without causing an
746 exception.
747
748 =cut
749
750 # the following variables are defined in .xs and must not be re-created
751 our @CB_GLOBAL = (); # registry for all global events
752 our @CB_ATTACHABLE = (); # registry for all attachables
753 our @CB_OBJECT = (); # all objects (should not be used except in emergency)
754 our @CB_PLAYER = ();
755 our @CB_CLIENT = ();
756 our @CB_TYPE = (); # registry for type (cf-object class) based events
757 our @CB_MAP = ();
758
759 my %attachment;
760
761 sub cf::attachable::thawer_merge {
762 # simply override everything except _meta
763 local $_[0]{_meta};
764 %{$_[0]} = %{$_[1]};
765 }
766
767 sub _attach_cb($$$$) {
768 my ($registry, $event, $prio, $cb) = @_;
769
770 use sort 'stable';
771
772 $cb = [$prio, $cb];
773
774 @{$registry->[$event]} = sort
775 { $a->[0] cmp $b->[0] }
776 @{$registry->[$event] || []}, $cb;
777 }
778
779 # hack
780 my %attachable_klass = map +($_ => 1), KLASS_OBJECT, KLASS_CLIENT, KLASS_PLAYER, KLASS_MAP;
781
782 # attach handles attaching event callbacks
783 # the only thing the caller has to do is pass the correct
784 # registry (== where the callback attaches to).
785 sub _attach {
786 my ($registry, $klass, @arg) = @_;
787
788 my $object_type;
789 my $prio = 0;
790 my %cb_id = map +("on_" . lc $EVENT[$_][0], $_) , grep $EVENT[$_][1] == $klass, 0 .. $#EVENT;
791
792 #TODO: get rid of this hack
793 if ($attachable_klass{$klass}) {
794 %cb_id = (%cb_id, map +("on_" . lc $EVENT[$_][0], $_) , grep $EVENT[$_][1] == KLASS_ATTACHABLE, 0 .. $#EVENT);
795 }
796
797 while (@arg) {
798 my $type = shift @arg;
799
800 if ($type eq "prio") {
801 $prio = shift @arg;
802
803 } elsif ($type eq "type") {
804 $object_type = shift @arg;
805 $registry = $CB_TYPE[$object_type] ||= [];
806
807 } elsif ($type eq "subtype") {
808 defined $object_type or Carp::croak "subtype specified without type";
809 my $object_subtype = shift @arg;
810 $registry = $CB_TYPE[$object_type + $object_subtype * NUM_TYPES] ||= [];
811
812 } elsif ($type eq "package") {
813 my $pkg = shift @arg;
814
815 while (my ($name, $id) = each %cb_id) {
816 if (my $cb = $pkg->can ($name)) {
817 _attach_cb $registry, $id, $prio, $cb;
818 }
819 }
820
821 } elsif (exists $cb_id{$type}) {
822 _attach_cb $registry, $cb_id{$type}, $prio, shift @arg;
823
824 } elsif (ref $type) {
825 warn "attaching objects not supported, ignoring.\n";
826
827 } else {
828 shift @arg;
829 warn "attach argument '$type' not supported, ignoring.\n";
830 }
831 }
832 }
833
834 sub _object_attach {
835 my ($obj, $name, %arg) = @_;
836
837 return if exists $obj->{_attachment}{$name};
838
839 if (my $attach = $attachment{$name}) {
840 my $registry = $obj->registry;
841
842 for (@$attach) {
843 my ($klass, @attach) = @$_;
844 _attach $registry, $klass, @attach;
845 }
846
847 $obj->{$name} = \%arg;
848 } else {
849 warn "object uses attachment '$name' which is not available, postponing.\n";
850 }
851
852 $obj->{_attachment}{$name} = undef;
853 }
854
855 sub cf::attachable::attach {
856 if (ref $_[0]) {
857 _object_attach @_;
858 } else {
859 _attach shift->_attach_registry, @_;
860 }
861 _recalc_want;
862 };
863
864 # all those should be optimised
865 sub cf::attachable::detach {
866 my ($obj, $name) = @_;
867
868 if (ref $obj) {
869 delete $obj->{_attachment}{$name};
870 reattach ($obj);
871 } else {
872 Carp::croak "cannot, currently, detach class attachments";
873 }
874 _recalc_want;
875 };
876
877 sub cf::attachable::attached {
878 my ($obj, $name) = @_;
879
880 exists $obj->{_attachment}{$name}
881 }
882
883 for my $klass (qw(ATTACHABLE GLOBAL OBJECT PLAYER CLIENT MAP)) {
884 eval "#line " . __LINE__ . " 'cf.pm'
885 sub cf::\L$klass\E::_attach_registry {
886 (\\\@CB_$klass, KLASS_$klass)
887 }
888
889 sub cf::\L$klass\E::attachment {
890 my \$name = shift;
891
892 \$attachment{\$name} = [[KLASS_$klass, \@_]];
893 }
894 ";
895 die if $@;
896 }
897
898 our $override;
899 our @INVOKE_RESULTS = (); # referenced from .xs code. TODO: play tricks with reify and mortals?
900
901 sub override {
902 $override = 1;
903 @INVOKE_RESULTS = (@_);
904 }
905
906 sub do_invoke {
907 my $event = shift;
908 my $callbacks = shift;
909
910 @INVOKE_RESULTS = ();
911
912 local $override;
913
914 for (@$callbacks) {
915 eval { &{$_->[1]} };
916
917 if ($@) {
918 warn "$@";
919 warn "... while processing $EVENT[$event][0](@_) event, skipping processing altogether.\n";
920 override;
921 }
922
923 return 1 if $override;
924 }
925
926 0
927 }
928
929 =item $bool = cf::global::invoke (EVENT_CLASS_XXX, ...)
930
931 =item $bool = $attachable->invoke (EVENT_CLASS_XXX, ...)
932
933 Generate an object-specific event with the given arguments.
934
935 This API is preliminary (most likely, the EVENT_CLASS_xxx prefix will be
936 removed in future versions), and there is no public API to access override
937 results (if you must, access C<@cf::INVOKE_RESULTS> directly).
938
939 =back
940
941 =cut
942
943 #############################################################################
944 # object support
945 #
946
947 sub _can_merge {
948 my ($ob1, $ob2) = @_;
949
950 local $Storable::canonical = 1;
951 my $fob1 = Storable::freeze $ob1;
952 my $fob2 = Storable::freeze $ob2;
953
954 $fob1 eq $fob2
955 }
956
957 sub reattach {
958 # basically do the same as instantiate, without calling instantiate
959 my ($obj) = @_;
960
961 bless $obj, ref $obj; # re-bless in case extensions have been reloaded
962
963 my $registry = $obj->registry;
964
965 @$registry = ();
966
967 delete $obj->{_attachment} unless scalar keys %{ $obj->{_attachment} || {} };
968
969 for my $name (keys %{ $obj->{_attachment} || {} }) {
970 if (my $attach = $attachment{$name}) {
971 for (@$attach) {
972 my ($klass, @attach) = @$_;
973 _attach $registry, $klass, @attach;
974 }
975 } else {
976 warn "object uses attachment '$name' that is not available, postponing.\n";
977 }
978 }
979 }
980
981 cf::attachable->attach (
982 prio => -1000000,
983 on_instantiate => sub {
984 my ($obj, $data) = @_;
985
986 $data = from_json $data;
987
988 for (@$data) {
989 my ($name, $args) = @$_;
990
991 $obj->attach ($name, %{$args || {} });
992 }
993 },
994 on_reattach => \&reattach,
995 on_clone => sub {
996 my ($src, $dst) = @_;
997
998 @{$dst->registry} = @{$src->registry};
999
1000 %$dst = %$src;
1001
1002 %{$dst->{_attachment}} = %{$src->{_attachment}}
1003 if exists $src->{_attachment};
1004 },
1005 );
1006
1007 sub object_freezer_save {
1008 my ($filename, $rdata, $objs) = @_;
1009
1010 sync_job {
1011 if (length $$rdata) {
1012 warn sprintf "saving %s (%d,%d)\n",
1013 $filename, length $$rdata, scalar @$objs;
1014
1015 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
1016 chmod SAVE_MODE, $fh;
1017 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
1018 aio_fsync $fh if $cf::USE_FSYNC;
1019 close $fh;
1020
1021 if (@$objs) {
1022 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) {
1023 chmod SAVE_MODE, $fh;
1024 my $data = Storable::nfreeze { version => 1, objs => $objs };
1025 aio_write $fh, 0, (length $data), $data, 0;
1026 aio_fsync $fh if $cf::USE_FSYNC;
1027 close $fh;
1028 aio_rename "$filename.pst~", "$filename.pst";
1029 }
1030 } else {
1031 aio_unlink "$filename.pst";
1032 }
1033
1034 aio_rename "$filename~", $filename;
1035 } else {
1036 warn "FATAL: $filename~: $!\n";
1037 }
1038 } else {
1039 aio_unlink $filename;
1040 aio_unlink "$filename.pst";
1041 }
1042 }
1043 }
1044
1045 sub object_freezer_as_string {
1046 my ($rdata, $objs) = @_;
1047
1048 use Data::Dumper;
1049
1050 $$rdata . Dumper $objs
1051 }
1052
1053 sub object_thawer_load {
1054 my ($filename) = @_;
1055
1056 my ($data, $av);
1057
1058 (aio_load $filename, $data) >= 0
1059 or return;
1060
1061 unless (aio_stat "$filename.pst") {
1062 (aio_load "$filename.pst", $av) >= 0
1063 or return;
1064 $av = eval { (Storable::thaw $av)->{objs} };
1065 }
1066
1067 warn sprintf "loading %s (%d)\n",
1068 $filename, length $data, scalar @{$av || []};
1069 return ($data, $av);
1070 }
1071
1072 =head2 COMMAND CALLBACKS
1073
1074 =over 4
1075
1076 =cut
1077
1078 #############################################################################
1079 # command handling &c
1080
1081 =item cf::register_command $name => \&callback($ob,$args);
1082
1083 Register a callback for execution when the client sends the user command
1084 $name.
1085
1086 =cut
1087
1088 sub register_command {
1089 my ($name, $cb) = @_;
1090
1091 my $caller = caller;
1092 #warn "registering command '$name/$time' to '$caller'";
1093
1094 push @{ $COMMAND{$name} }, [$caller, $cb];
1095 }
1096
1097 =item cf::register_extcmd $name => \&callback($pl,$packet);
1098
1099 Register a callback for execution when the client sends an (synchronous)
1100 extcmd packet. Ext commands will be processed in the order they are
1101 received by the server, like other user commands. The first argument is
1102 the logged-in player. Ext commands can only be processed after a player
1103 has logged in successfully.
1104
1105 If the callback returns something, it is sent back as if reply was being
1106 called.
1107
1108 =item cf::register_exticmd $name => \&callback($ns,$packet);
1109
1110 Register a callback for execution when the client sends an (asynchronous)
1111 exticmd packet. Exti commands are processed by the server as soon as they
1112 are received, i.e. out of order w.r.t. other commands. The first argument
1113 is a client socket. Exti commands can be received anytime, even before
1114 log-in.
1115
1116 If the callback returns something, it is sent back as if reply was being
1117 called.
1118
1119 =cut
1120
1121 sub register_extcmd {
1122 my ($name, $cb) = @_;
1123
1124 $EXTCMD{$name} = $cb;
1125 }
1126
1127 sub register_exticmd {
1128 my ($name, $cb) = @_;
1129
1130 $EXTICMD{$name} = $cb;
1131 }
1132
1133 cf::player->attach (
1134 on_command => sub {
1135 my ($pl, $name, $params) = @_;
1136
1137 my $cb = $COMMAND{$name}
1138 or return;
1139
1140 for my $cmd (@$cb) {
1141 $cmd->[1]->($pl->ob, $params);
1142 }
1143
1144 cf::override;
1145 },
1146 on_extcmd => sub {
1147 my ($pl, $buf) = @_;
1148
1149 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) };
1150
1151 if (ref $msg) {
1152 my ($type, $reply, @payload) =
1153 "ARRAY" eq ref $msg
1154 ? @$msg
1155 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
1156
1157 my @reply;
1158
1159 if (my $cb = $EXTCMD{$type}) {
1160 @reply = $cb->($pl, @payload);
1161 }
1162
1163 $pl->ext_reply ($reply, @reply)
1164 if $reply;
1165
1166 } else {
1167 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
1168 }
1169
1170 cf::override;
1171 },
1172 );
1173
1174 sub load_extensions {
1175 cf::sync_job {
1176 my %todo;
1177
1178 for my $path (<$LIBDIR/*.ext>) {
1179 next unless -r $path;
1180
1181 $path =~ /([^\/\\]+)\.ext$/ or die "$path";
1182 my $base = $1;
1183 my $pkg = $1;
1184 $pkg =~ s/[^[:word:]]/_/g;
1185 $pkg = "ext::$pkg";
1186
1187 open my $fh, "<:utf8", $path
1188 or die "$path: $!";
1189
1190 my $source = do { local $/; <$fh> };
1191
1192 my %ext = (
1193 path => $path,
1194 base => $base,
1195 pkg => $pkg,
1196 );
1197
1198 $ext{meta} = { map { (split /=/, $_, 2)[0, 1] } split /\s+/, $1 }
1199 if $source =~ /\A#!.*?perl.*?#\s*(.*)$/m;
1200
1201 $ext{source} =
1202 "package $pkg; use strict; use utf8;\n"
1203 . "#line 1 \"$path\"\n{\n"
1204 . $source
1205 . "\n};\n1";
1206
1207 $todo{$base} = \%ext;
1208 }
1209
1210 my %done;
1211 while (%todo) {
1212 my $progress;
1213
1214 while (my ($k, $v) = each %todo) {
1215 for (split /,\s*/, $v->{meta}{depends}) {
1216 goto skip
1217 unless exists $done{$_};
1218 }
1219
1220 warn "... loading '$k' into '$v->{pkg}'\n";
1221
1222 unless (eval $v->{source}) {
1223 my $msg = $@ ? "$v->{path}: $@\n"
1224 : "$v->{base}: extension inactive.\n";
1225
1226 if (exists $v->{meta}{mandatory}) {
1227 warn $msg;
1228 warn "mandatory extension failed to load, exiting.\n";
1229 exit 1;
1230 }
1231
1232 warn $msg;
1233 }
1234
1235 $done{$k} = delete $todo{$k};
1236 push @EXTS, $v->{pkg};
1237 $progress = 1;
1238 }
1239
1240 skip:
1241 die "cannot load " . (join ", ", keys %todo) . ": unable to resolve dependencies\n"
1242 unless $progress;
1243 }
1244 };
1245 }
1246
1247 #############################################################################
1248
1249 =back
1250
1251 =head2 CORE EXTENSIONS
1252
1253 Functions and methods that extend core crossfire objects.
1254
1255 =cut
1256
1257 package cf::player;
1258
1259 use Coro::AIO;
1260
1261 =head3 cf::player
1262
1263 =over 4
1264
1265 =item cf::player::find $login
1266
1267 Returns the given player object, loading it if necessary (might block).
1268
1269 =cut
1270
1271 sub playerdir($) {
1272 "$PLAYERDIR/"
1273 . (ref $_[0] ? $_[0]->ob->name : $_[0])
1274 }
1275
1276 sub path($) {
1277 my $login = ref $_[0] ? $_[0]->ob->name : $_[0];
1278
1279 (playerdir $login) . "/playerdata"
1280 }
1281
1282 sub find_active($) {
1283 $cf::PLAYER{$_[0]}
1284 and $cf::PLAYER{$_[0]}->active
1285 and $cf::PLAYER{$_[0]}
1286 }
1287
1288 sub exists($) {
1289 my ($login) = @_;
1290
1291 $cf::PLAYER{$login}
1292 or cf::sync_job { !aio_stat path $login }
1293 }
1294
1295 sub find($) {
1296 return $cf::PLAYER{$_[0]} || do {
1297 my $login = $_[0];
1298
1299 my $guard = cf::lock_acquire "user_find:$login";
1300
1301 $cf::PLAYER{$_[0]} || do {
1302 # rename old playerfiles to new ones
1303 #TODO: remove when no longer required
1304 aio_link +(playerdir $login) . "/$login.pl.pst", (playerdir $login) . "/playerdata.pst";
1305 aio_link +(playerdir $login) . "/$login.pl" , (playerdir $login) . "/playerdata";
1306 aio_unlink +(playerdir $login) . "/$login.pl.pst";
1307 aio_unlink +(playerdir $login) . "/$login.pl";
1308
1309 my $pl = load_pl path $login
1310 or return;
1311 $cf::PLAYER{$login} = $pl
1312 }
1313 }
1314 }
1315
1316 sub save($) {
1317 my ($pl) = @_;
1318
1319 return if $pl->{deny_save};
1320
1321 my $path = path $pl;
1322 my $guard = cf::lock_acquire "user_save:$path";
1323
1324 return if $pl->{deny_save};
1325
1326 aio_mkdir playerdir $pl, 0770;
1327 $pl->{last_save} = $cf::RUNTIME;
1328
1329 $pl->save_pl ($path);
1330 cf::cede_to_tick;
1331 }
1332
1333 sub new($) {
1334 my ($login) = @_;
1335
1336 my $self = create;
1337
1338 $self->ob->name ($login);
1339 $self->{deny_save} = 1;
1340
1341 $cf::PLAYER{$login} = $self;
1342
1343 $self
1344 }
1345
1346 =item $player->send_msg ($channel, $msg, $color, [extra...])
1347
1348 =cut
1349
1350 sub send_msg {
1351 my $ns = shift->ns
1352 or return;
1353 $ns->send_msg (@_);
1354 }
1355
1356 =item $pl->quit_character
1357
1358 Nukes the player without looking back. If logged in, the connection will
1359 be destroyed. May block for a long time.
1360
1361 =cut
1362
1363 sub quit_character {
1364 my ($pl) = @_;
1365
1366 my $name = $pl->ob->name;
1367
1368 $pl->{deny_save} = 1;
1369 $pl->password ("*"); # this should lock out the player until we nuked the dir
1370
1371 $pl->invoke (cf::EVENT_PLAYER_LOGOUT, 1) if $pl->active;
1372 $pl->deactivate;
1373 $pl->invoke (cf::EVENT_PLAYER_QUIT);
1374 $pl->ns->destroy if $pl->ns;
1375
1376 my $path = playerdir $pl;
1377 my $temp = "$path~$cf::RUNTIME~deleting~";
1378 aio_rename $path, $temp;
1379 delete $cf::PLAYER{$pl->ob->name};
1380 $pl->destroy;
1381
1382 my $prefix = qr<^~\Q$name\E/>;
1383
1384 # nuke player maps
1385 $cf::MAP{$_}->nuke for grep /$prefix/, keys %cf::MAP;
1386
1387 IO::AIO::aio_rmtree $temp;
1388 }
1389
1390 =item $pl->kick
1391
1392 Kicks a player out of the game. This destroys the connection.
1393
1394 =cut
1395
1396 sub kick {
1397 my ($pl, $kicker) = @_;
1398
1399 $pl->invoke (cf::EVENT_PLAYER_KICK, $kicker);
1400 $pl->killer ("kicked");
1401 $pl->ns->destroy;
1402 }
1403
1404 =item cf::player::list_logins
1405
1406 Returns am arrayref of all valid playernames in the system, can take a
1407 while and may block, so not sync_job-capable, ever.
1408
1409 =cut
1410
1411 sub list_logins {
1412 my $dirs = aio_readdir $PLAYERDIR
1413 or return [];
1414
1415 my @logins;
1416
1417 for my $login (@$dirs) {
1418 my $path = path $login;
1419
1420 # a .pst is a dead give-away for a valid player
1421 unless (-e "$path.pst") {
1422 my $fh = aio_open $path, Fcntl::O_RDONLY, 0 or next;
1423 aio_read $fh, 0, 512, my $buf, 0 or next;
1424 $buf !~ /^password -------------$/m or next; # official not-valid tag
1425 }
1426
1427 utf8::decode $login;
1428 push @logins, $login;
1429 }
1430
1431 \@logins
1432 }
1433
1434 =item $player->maps
1435
1436 Returns an arrayref of map paths that are private for this
1437 player. May block.
1438
1439 =cut
1440
1441 sub maps($) {
1442 my ($pl) = @_;
1443
1444 $pl = ref $pl ? $pl->ob->name : $pl;
1445
1446 my $files = aio_readdir playerdir $pl
1447 or return;
1448
1449 my @paths;
1450
1451 for (@$files) {
1452 utf8::decode $_;
1453 next if /\.(?:pl|pst)$/;
1454 next unless /^$PATH_SEP/o;
1455
1456 push @paths, cf::map::normalise "~$pl/$_";
1457 }
1458
1459 \@paths
1460 }
1461
1462 =item $protocol_xml = $player->expand_cfpod ($crossfire_pod)
1463
1464 Expand crossfire pod fragments into protocol xml.
1465
1466 =cut
1467
1468 sub expand_cfpod {
1469 ((my $self), (local $_)) = @_;
1470
1471 # escape & and <
1472 s/&/&amp;/g;
1473 s/(?<![BIUGHT])</&lt;/g;
1474
1475 # this is buggy, it needs to properly take care of nested <'s
1476
1477 1 while
1478 # replace B<>, I<>, U<> etc.
1479 s/B<([^\>]*)>/<b>$1<\/b>/
1480 || s/I<([^\>]*)>/<i>$1<\/i>/
1481 || s/U<([^\>]*)>/<u>$1<\/u>/
1482 || s/T<([^\>]*)>/<big><b>$1<\/b><\/big>/
1483 # replace G<male|female> tags
1484 || s{G<([^>|]*)\|([^>]*)>}{
1485 $self->gender ? $2 : $1
1486 }ge
1487 # replace H<hint text>
1488 || s{H<([^\>]*)>}
1489 {
1490 ("<fg name=\"lightblue\">[$1 (Use hintmode to suppress hints)]</fg>",
1491 "<fg name=\"lightblue\">[Hint suppressed, see hintmode]</fg>",
1492 "")
1493 [$self->{hintmode}]
1494 }ge;
1495
1496 # create single paragraphs (very hackish)
1497 s/(?<=\S)\n(?=\w)/ /g;
1498
1499 # compress some whitespace
1500 s/\s+\n/\n/g; # ws line-ends
1501 s/\n\n+/\n/g; # double lines
1502 s/^\n+//; # beginning lines
1503 s/\n+$//; # ending lines
1504
1505 $_
1506 }
1507
1508 sub hintmode {
1509 $_[0]{hintmode} = $_[1] if @_ > 1;
1510 $_[0]{hintmode}
1511 }
1512
1513 =item $player->ext_reply ($msgid, @msg)
1514
1515 Sends an ext reply to the player.
1516
1517 =cut
1518
1519 sub ext_reply($$@) {
1520 my ($self, $id, @msg) = @_;
1521
1522 $self->ns->ext_reply ($id, @msg)
1523 }
1524
1525 =item $player->ext_msg ($type, @msg)
1526
1527 Sends an ext event to the client.
1528
1529 =cut
1530
1531 sub ext_msg($$@) {
1532 my ($self, $type, @msg) = @_;
1533
1534 $self->ns->ext_msg ($type, @msg);
1535 }
1536
1537 =head3 cf::region
1538
1539 =over 4
1540
1541 =cut
1542
1543 package cf::region;
1544
1545 =item cf::region::find_by_path $path
1546
1547 Tries to decuce the likely region for a map knowing only its path.
1548
1549 =cut
1550
1551 sub find_by_path($) {
1552 my ($path) = @_;
1553
1554 my ($match, $specificity);
1555
1556 for my $region (list) {
1557 if ($region->{match} && $path =~ $region->{match}) {
1558 ($match, $specificity) = ($region, $region->specificity)
1559 if $region->specificity > $specificity;
1560 }
1561 }
1562
1563 $match
1564 }
1565
1566 =back
1567
1568 =head3 cf::map
1569
1570 =over 4
1571
1572 =cut
1573
1574 package cf::map;
1575
1576 use Fcntl;
1577 use Coro::AIO;
1578
1579 use overload
1580 '""' => \&as_string,
1581 fallback => 1;
1582
1583 our $MAX_RESET = 3600;
1584 our $DEFAULT_RESET = 3000;
1585
1586 sub generate_random_map {
1587 my ($self, $rmp) = @_;
1588 # mit "rum" bekleckern, nicht
1589 $self->_create_random_map (
1590 $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
1591 $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
1592 $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
1593 $rmp->{exit_on_final_map},
1594 $rmp->{xsize}, $rmp->{ysize},
1595 $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
1596 $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
1597 $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
1598 $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
1599 $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
1600 (cf::region::find $rmp->{region}), $rmp->{custom}
1601 )
1602 }
1603
1604 =item cf::map->register ($regex, $prio)
1605
1606 Register a handler for the map path matching the given regex at the
1607 givne priority (higher is better, built-in handlers have priority 0, the
1608 default).
1609
1610 =cut
1611
1612 sub register {
1613 my (undef, $regex, $prio) = @_;
1614 my $pkg = caller;
1615
1616 no strict;
1617 push @{"$pkg\::ISA"}, __PACKAGE__;
1618
1619 $EXT_MAP{$pkg} = [$prio, qr<$regex>];
1620 }
1621
1622 # also paths starting with '/'
1623 $EXT_MAP{"cf::map"} = [0, qr{^(?=/)}];
1624
1625 sub thawer_merge {
1626 my ($self, $merge) = @_;
1627
1628 # we have to keep some variables in memory intact
1629 local $self->{path};
1630 local $self->{load_path};
1631
1632 $self->SUPER::thawer_merge ($merge);
1633 }
1634
1635 sub normalise {
1636 my ($path, $base) = @_;
1637
1638 $path = "$path"; # make sure its a string
1639
1640 $path =~ s/\.map$//;
1641
1642 # map plan:
1643 #
1644 # /! non-realised random map exit (special hack!)
1645 # {... are special paths that are not being touched
1646 # ?xxx/... are special absolute paths
1647 # ?random/... random maps
1648 # /... normal maps
1649 # ~user/... per-player map of a specific user
1650
1651 $path =~ s/$PATH_SEP/\//go;
1652
1653 # treat it as relative path if it starts with
1654 # something that looks reasonable
1655 if ($path =~ m{^(?:\./|\.\./|\w)}) {
1656 $base or Carp::carp "normalise called with relative path and no base: '$path'";
1657
1658 $base =~ s{[^/]+/?$}{};
1659 $path = "$base/$path";
1660 }
1661
1662 for ($path) {
1663 redo if s{//}{/};
1664 redo if s{/\.?/}{/};
1665 redo if s{/[^/]+/\.\./}{/};
1666 }
1667
1668 $path
1669 }
1670
1671 sub new_from_path {
1672 my (undef, $path, $base) = @_;
1673
1674 return $path if UNIVERSAL::isa $path, "cf::map"; # already a map object
1675
1676 $path = normalise $path, $base;
1677
1678 for my $pkg (sort { $EXT_MAP{$b}[0] <=> $EXT_MAP{$a}[0] } keys %EXT_MAP) {
1679 if ($path =~ $EXT_MAP{$pkg}[1]) {
1680 my $self = bless cf::map::new, $pkg;
1681 $self->{path} = $path; $self->path ($path);
1682 $self->init; # pass $1 etc.
1683 return $self;
1684 }
1685 }
1686
1687 Carp::cluck "unable to resolve path '$path' (base '$base').";
1688 ()
1689 }
1690
1691 sub init {
1692 my ($self) = @_;
1693
1694 $self
1695 }
1696
1697 sub as_string {
1698 my ($self) = @_;
1699
1700 "$self->{path}"
1701 }
1702
1703 # the displayed name, this is a one way mapping
1704 sub visible_name {
1705 &as_string
1706 }
1707
1708 # the original (read-only) location
1709 sub load_path {
1710 my ($self) = @_;
1711
1712 "$MAPDIR/$self->{path}.map"
1713 }
1714
1715 # the temporary/swap location
1716 sub save_path {
1717 my ($self) = @_;
1718
1719 (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/g;
1720 "$TMPDIR/$path.map"
1721 }
1722
1723 # the unique path, undef == no special unique path
1724 sub uniq_path {
1725 my ($self) = @_;
1726
1727 (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/g;
1728 "$UNIQUEDIR/$path"
1729 }
1730
1731 # and all this just because we cannot iterate over
1732 # all maps in C++...
1733 sub change_all_map_light {
1734 my ($change) = @_;
1735
1736 $_->change_map_light ($change)
1737 for grep $_->outdoor, values %cf::MAP;
1738 }
1739
1740 sub decay_objects {
1741 my ($self) = @_;
1742
1743 return if $self->{deny_reset};
1744
1745 $self->do_decay_objects;
1746 }
1747
1748 sub unlink_save {
1749 my ($self) = @_;
1750
1751 utf8::encode (my $save = $self->save_path);
1752 IO::AIO::aioreq_pri 4; Coro::AIO::aio_unlink $save;
1753 IO::AIO::aioreq_pri 4; Coro::AIO::aio_unlink "$save.pst";
1754 }
1755
1756 sub load_header_from($) {
1757 my ($self, $path) = @_;
1758
1759 utf8::encode $path;
1760 #aio_open $path, O_RDONLY, 0
1761 # or return;
1762
1763 $self->_load_header ($path)
1764 or return;
1765
1766 $self->{load_path} = $path;
1767
1768 1
1769 }
1770
1771 sub load_header_orig {
1772 my ($self) = @_;
1773
1774 $self->load_header_from ($self->load_path)
1775 }
1776
1777 sub load_header_temp {
1778 my ($self) = @_;
1779
1780 $self->load_header_from ($self->save_path)
1781 }
1782
1783 sub prepare_temp {
1784 my ($self) = @_;
1785
1786 $self->last_access ((delete $self->{last_access})
1787 || $cf::RUNTIME); #d#
1788 # safety
1789 $self->{instantiate_time} = $cf::RUNTIME
1790 if $self->{instantiate_time} > $cf::RUNTIME;
1791 }
1792
1793 sub prepare_orig {
1794 my ($self) = @_;
1795
1796 $self->{load_original} = 1;
1797 $self->{instantiate_time} = $cf::RUNTIME;
1798 $self->last_access ($cf::RUNTIME);
1799 $self->instantiate;
1800 }
1801
1802 sub load_header {
1803 my ($self) = @_;
1804
1805 if ($self->load_header_temp) {
1806 $self->prepare_temp;
1807 } else {
1808 $self->load_header_orig
1809 or return;
1810 $self->prepare_orig;
1811 }
1812
1813 $self->{deny_reset} = 1
1814 if $self->no_reset;
1815
1816 $self->default_region (cf::region::find_by_path $self->{path})
1817 unless $self->default_region;
1818
1819 1
1820 }
1821
1822 sub find;
1823 sub find {
1824 my ($path, $origin) = @_;
1825
1826 $path = normalise $path, $origin && $origin->path;
1827
1828 cf::lock_wait "map_find:$path";
1829
1830 $cf::MAP{$path} || do {
1831 my $guard = cf::lock_acquire "map_find:$path";
1832 my $map = new_from_path cf::map $path
1833 or return;
1834
1835 $map->{last_save} = $cf::RUNTIME;
1836
1837 $map->load_header
1838 or return;
1839
1840 if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?)
1841 # doing this can freeze the server in a sync job, obviously
1842 #$cf::WAIT_FOR_TICK->wait;
1843 $map->reset;
1844 undef $guard;
1845 return find $path;
1846 }
1847
1848 $cf::MAP{$path} = $map
1849 }
1850 }
1851
1852 sub pre_load { }
1853 sub post_load { }
1854
1855 sub load {
1856 my ($self) = @_;
1857
1858 local $self->{deny_reset} = 1; # loading can take a long time
1859
1860 my $path = $self->{path};
1861
1862 {
1863 my $guard1 = cf::lock_acquire "map_data:$path";
1864 my $guard2 = cf::lock_acquire "map_load:$path";
1865
1866 return if $self->in_memory != cf::MAP_SWAPPED;
1867
1868 $self->in_memory (cf::MAP_LOADING);
1869
1870 $self->alloc;
1871
1872 $self->pre_load;
1873 cf::cede_to_tick;
1874
1875 $self->_load_objects ($self->{load_path}, 1)
1876 or return;
1877
1878 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
1879 if delete $self->{load_original};
1880
1881 if (my $uniq = $self->uniq_path) {
1882 utf8::encode $uniq;
1883 if (aio_open $uniq, O_RDONLY, 0) {
1884 $self->clear_unique_items;
1885 $self->_load_objects ($uniq, 0);
1886 }
1887 }
1888
1889 cf::cede_to_tick;
1890 # now do the right thing for maps
1891 $self->link_multipart_objects;
1892 $self->difficulty ($self->estimate_difficulty)
1893 unless $self->difficulty;
1894 cf::cede_to_tick;
1895
1896 unless ($self->{deny_activate}) {
1897 $self->decay_objects;
1898 $self->fix_auto_apply;
1899 $self->update_buttons;
1900 cf::cede_to_tick;
1901 $self->set_darkness_map;
1902 cf::cede_to_tick;
1903 $self->activate;
1904 }
1905
1906 $self->{last_save} = $cf::RUNTIME;
1907 $self->last_access ($cf::RUNTIME);
1908
1909 $self->in_memory (cf::MAP_IN_MEMORY);
1910 }
1911
1912 $self->post_load;
1913 }
1914
1915 sub customise_for {
1916 my ($self, $ob) = @_;
1917
1918 return find "~" . $ob->name . "/" . $self->{path}
1919 if $self->per_player;
1920
1921 # return find "?party/" . $ob->name . "/" . $self->{path}
1922 # if $self->per_party;
1923
1924 $self
1925 }
1926
1927 # find and load all maps in the 3x3 area around a map
1928 sub load_neighbours {
1929 my ($map) = @_;
1930
1931 my @neigh; # diagonal neighbours
1932
1933 for (0 .. 3) {
1934 my $neigh = $map->tile_path ($_)
1935 or next;
1936 $neigh = find $neigh, $map
1937 or next;
1938 $neigh->load;
1939
1940 push @neigh,
1941 [$neigh->tile_path (($_ + 3) % 4), $neigh],
1942 [$neigh->tile_path (($_ + 1) % 4), $neigh];
1943 }
1944
1945 for (grep defined $_->[0], @neigh) {
1946 my ($path, $origin) = @$_;
1947 my $neigh = find $path, $origin
1948 or next;
1949 $neigh->load;
1950 }
1951 }
1952
1953 sub find_sync {
1954 my ($path, $origin) = @_;
1955
1956 cf::sync_job { find $path, $origin }
1957 }
1958
1959 sub do_load_sync {
1960 my ($map) = @_;
1961
1962 cf::LOG cf::llevDebug | cf::logBacktrace, "do_load_sync"
1963 if $Coro::current == $Coro::main;
1964
1965 cf::sync_job { $map->load };
1966 }
1967
1968 our %MAP_PREFETCH;
1969 our $MAP_PREFETCHER = undef;
1970
1971 sub find_async {
1972 my ($path, $origin, $load) = @_;
1973
1974 $path = normalise $path, $origin && $origin->{path};
1975
1976 if (my $map = $cf::MAP{$path}) {
1977 return $map if !$load || $map->in_memory == cf::MAP_IN_MEMORY;
1978 }
1979
1980 $MAP_PREFETCH{$path} |= $load;
1981
1982 $MAP_PREFETCHER ||= cf::async {
1983 while (%MAP_PREFETCH) {
1984 while (my ($k, $v) = each %MAP_PREFETCH) {
1985 if (my $map = find $k) {
1986 $map->load if $v;
1987 }
1988
1989 delete $MAP_PREFETCH{$k};
1990 }
1991 }
1992 undef $MAP_PREFETCHER;
1993 };
1994 $MAP_PREFETCHER->prio (6);
1995
1996 ()
1997 }
1998
1999 sub save {
2000 my ($self) = @_;
2001
2002 my $lock = cf::lock_acquire "map_data:$self->{path}";
2003
2004 $self->{last_save} = $cf::RUNTIME;
2005
2006 return unless $self->dirty;
2007
2008 my $save = $self->save_path; utf8::encode $save;
2009 my $uniq = $self->uniq_path; utf8::encode $uniq;
2010
2011 $self->{load_path} = $save;
2012
2013 return if $self->{deny_save};
2014
2015 local $self->{last_access} = $self->last_access;#d#
2016
2017 cf::async {
2018 $_->contr->save for $self->players;
2019 };
2020
2021 if ($uniq) {
2022 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
2023 $self->_save_objects ($uniq, cf::IO_UNIQUES);
2024 } else {
2025 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
2026 }
2027 }
2028
2029 sub swap_out {
2030 my ($self) = @_;
2031
2032 # save first because save cedes
2033 $self->save;
2034
2035 my $lock = cf::lock_acquire "map_data:$self->{path}";
2036
2037 return if $self->players;
2038 return if $self->in_memory != cf::MAP_IN_MEMORY;
2039 return if $self->{deny_save};
2040
2041 $self->clear;
2042 $self->in_memory (cf::MAP_SWAPPED);
2043 }
2044
2045 sub reset_at {
2046 my ($self) = @_;
2047
2048 # TODO: safety, remove and allow resettable per-player maps
2049 return 1e99 if $self->{deny_reset};
2050
2051 my $time = $self->fixed_resettime ? $self->{instantiate_time} : $self->last_access;
2052 my $to = List::Util::min $MAX_RESET, $self->reset_timeout || $DEFAULT_RESET;
2053
2054 $time + $to
2055 }
2056
2057 sub should_reset {
2058 my ($self) = @_;
2059
2060 $self->reset_at <= $cf::RUNTIME
2061 }
2062
2063 sub reset {
2064 my ($self) = @_;
2065
2066 my $lock = cf::lock_acquire "map_data:$self->{path}";
2067
2068 return if $self->players;
2069
2070 warn "resetting map ", $self->path;
2071
2072 $self->in_memory (cf::MAP_SWAPPED);
2073
2074 # need to save uniques path
2075 unless ($self->{deny_save}) {
2076 my $uniq = $self->uniq_path; utf8::encode $uniq;
2077
2078 $self->_save_objects ($uniq, cf::IO_UNIQUES)
2079 if $uniq;
2080 }
2081
2082 delete $cf::MAP{$self->path};
2083
2084 $self->clear;
2085
2086 $_->clear_links_to ($self) for values %cf::MAP;
2087
2088 $self->unlink_save;
2089 $self->destroy;
2090 }
2091
2092 my $nuke_counter = "aaaa";
2093
2094 sub nuke {
2095 my ($self) = @_;
2096
2097 {
2098 my $lock = cf::lock_acquire "map_data:$self->{path}";
2099
2100 delete $cf::MAP{$self->path};
2101
2102 $self->unlink_save;
2103
2104 bless $self, "cf::map";
2105 delete $self->{deny_reset};
2106 $self->{deny_save} = 1;
2107 $self->reset_timeout (1);
2108 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++));
2109
2110 $cf::MAP{$self->path} = $self;
2111 }
2112
2113 $self->reset; # polite request, might not happen
2114 }
2115
2116 =item $maps = cf::map::tmp_maps
2117
2118 Returns an arrayref with all map paths of currently instantiated and saved
2119 maps. May block.
2120
2121 =cut
2122
2123 sub tmp_maps() {
2124 [
2125 map {
2126 utf8::decode $_;
2127 /\.map$/
2128 ? normalise $_
2129 : ()
2130 } @{ aio_readdir $TMPDIR or [] }
2131 ]
2132 }
2133
2134 =item $maps = cf::map::random_maps
2135
2136 Returns an arrayref with all map paths of currently instantiated and saved
2137 random maps. May block.
2138
2139 =cut
2140
2141 sub random_maps() {
2142 [
2143 map {
2144 utf8::decode $_;
2145 /\.map$/
2146 ? normalise "?random/$_"
2147 : ()
2148 } @{ aio_readdir $RANDOMDIR or [] }
2149 ]
2150 }
2151
2152 =item cf::map::unique_maps
2153
2154 Returns an arrayref of paths of all shared maps that have
2155 instantiated unique items. May block.
2156
2157 =cut
2158
2159 sub unique_maps() {
2160 [
2161 map {
2162 utf8::decode $_;
2163 /\.map$/
2164 ? normalise $_
2165 : ()
2166 } @{ aio_readdir $UNIQUEDIR or [] }
2167 ]
2168 }
2169
2170 package cf;
2171
2172 =back
2173
2174 =head3 cf::object
2175
2176 =cut
2177
2178 package cf::object;
2179
2180 =over 4
2181
2182 =item $ob->inv_recursive
2183
2184 Returns the inventory of the object _and_ their inventories, recursively.
2185
2186 =cut
2187
2188 sub inv_recursive_;
2189 sub inv_recursive_ {
2190 map { $_, inv_recursive_ $_->inv } @_
2191 }
2192
2193 sub inv_recursive {
2194 inv_recursive_ inv $_[0]
2195 }
2196
2197 package cf;
2198
2199 =back
2200
2201 =head3 cf::object::player
2202
2203 =over 4
2204
2205 =item $player_object->reply ($npc, $msg[, $flags])
2206
2207 Sends a message to the player, as if the npc C<$npc> replied. C<$npc>
2208 can be C<undef>. Does the right thing when the player is currently in a
2209 dialogue with the given NPC character.
2210
2211 =cut
2212
2213 # rough implementation of a future "reply" method that works
2214 # with dialog boxes.
2215 #TODO: the first argument must go, split into a $npc->reply_to ( method
2216 sub cf::object::player::reply($$$;$) {
2217 my ($self, $npc, $msg, $flags) = @_;
2218
2219 $flags = cf::NDI_BROWN | cf::NDI_UNIQUE unless @_ >= 4;
2220
2221 if ($self->{record_replies}) {
2222 push @{ $self->{record_replies} }, [$npc, $msg, $flags];
2223
2224 } else {
2225 my $pl = $self->contr;
2226
2227 if ($pl->{npc_dialog} && $pl->{npc_dialog}->{id}) {
2228 my $dialog = $pl->{npc_dialog};
2229 $dialog->{pl}->ext_msg ($dialog->{id}, update => msg => $dialog->{pl}->expand_cfpod ($msg));
2230
2231 } else {
2232 $msg = $npc->name . " says: $msg" if $npc;
2233 $self->message ($msg, $flags);
2234 }
2235 }
2236 }
2237
2238 =item $object->send_msg ($channel, $msg, $color, [extra...])
2239
2240 =cut
2241
2242 sub cf::object::send_msg {
2243 my $pl = shift->contr
2244 or return;
2245 $pl->send_msg (@_);
2246 }
2247
2248 =item $player_object->may ("access")
2249
2250 Returns wether the given player is authorized to access resource "access"
2251 (e.g. "command_wizcast").
2252
2253 =cut
2254
2255 sub cf::object::player::may {
2256 my ($self, $access) = @_;
2257
2258 $self->flag (cf::FLAG_WIZ) ||
2259 (ref $cf::CFG{"may_$access"}
2260 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}}
2261 : $cf::CFG{"may_$access"})
2262 }
2263
2264 =item $player_object->enter_link
2265
2266 Freezes the player and moves him/her to a special map (C<{link}>).
2267
2268 The player should be reasonably safe there for short amounts of time. You
2269 I<MUST> call C<leave_link> as soon as possible, though.
2270
2271 Will never block.
2272
2273 =item $player_object->leave_link ($map, $x, $y)
2274
2275 Moves the player out of the special C<{link}> map onto the specified
2276 map. If the map is not valid (or omitted), the player will be moved back
2277 to the location he/she was before the call to C<enter_link>, or, if that
2278 fails, to the emergency map position.
2279
2280 Might block.
2281
2282 =cut
2283
2284 sub link_map {
2285 unless ($LINK_MAP) {
2286 $LINK_MAP = cf::map::find "{link}"
2287 or cf::cleanup "FATAL: unable to provide {link} map, exiting.";
2288 $LINK_MAP->load;
2289 }
2290
2291 $LINK_MAP
2292 }
2293
2294 sub cf::object::player::enter_link {
2295 my ($self) = @_;
2296
2297 $self->deactivate_recursive;
2298
2299 return if UNIVERSAL::isa $self->map, "ext::map_link";
2300
2301 $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y]
2302 if $self->map && $self->map->{path} ne "{link}";
2303
2304 $self->enter_map ($LINK_MAP || link_map, 10, 10);
2305 }
2306
2307 sub cf::object::player::leave_link {
2308 my ($self, $map, $x, $y) = @_;
2309
2310 return unless $self->contr->active;
2311
2312 my $link_pos = delete $self->{_link_pos};
2313
2314 unless ($map) {
2315 # restore original map position
2316 ($map, $x, $y) = @{ $link_pos || [] };
2317 $map = cf::map::find $map;
2318
2319 unless ($map) {
2320 ($map, $x, $y) = @$EMERGENCY_POSITION;
2321 $map = cf::map::find $map
2322 or die "FATAL: cannot load emergency map\n";
2323 }
2324 }
2325
2326 ($x, $y) = (-1, -1)
2327 unless (defined $x) && (defined $y);
2328
2329 # use -1 or undef as default coordinates, not 0, 0
2330 ($x, $y) = ($map->enter_x, $map->enter_y)
2331 if $x <=0 && $y <= 0;
2332
2333 $map->load;
2334 $map->load_neighbours;
2335
2336 return unless $self->contr->active;
2337 $self->activate_recursive;
2338
2339 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext
2340 $self->enter_map ($map, $x, $y);
2341 }
2342
2343 =item $player_object->goto ($path, $x, $y[, $check->($map)[, $done->()]])
2344
2345 Moves the player to the given map-path and coordinates by first freezing
2346 her, loading and preparing them map, calling the provided $check callback
2347 that has to return the map if sucecssful, and then unfreezes the player on
2348 the new (success) or old (failed) map position. In either case, $done will
2349 be called at the end of this process.
2350
2351 =cut
2352
2353 our $GOTOGEN;
2354
2355 sub cf::object::player::goto {
2356 my ($self, $path, $x, $y, $check, $done) = @_;
2357
2358 # do generation counting so two concurrent goto's will be executed in-order
2359 my $gen = $self->{_goto_generation} = ++$GOTOGEN;
2360
2361 $self->enter_link;
2362
2363 (async {
2364 my $map = eval {
2365 my $map = cf::map::find $path;
2366
2367 if ($map) {
2368 $map = $map->customise_for ($self);
2369 $map = $check->($map) if $check && $map;
2370 } else {
2371 $self->message ("The exit to '$path' is closed", cf::NDI_UNIQUE | cf::NDI_RED);
2372 }
2373
2374 $map
2375 };
2376
2377 if ($@) {
2378 $self->message ("Something went wrong within the server, please report this incident!", cf::NDI_UNIQUE | cf::NDI_RED);
2379 LOG llevError | logBacktrace, Carp::longmess $@;
2380 }
2381
2382 if ($gen == $self->{_goto_generation}) {
2383 delete $self->{_goto_generation};
2384 $self->leave_link ($map, $x, $y);
2385 }
2386
2387 $done->() if $done;
2388 })->prio (1);
2389 }
2390
2391 =item $player_object->enter_exit ($exit_object)
2392
2393 =cut
2394
2395 sub parse_random_map_params {
2396 my ($spec) = @_;
2397
2398 my $rmp = { # defaults
2399 xsize => (cf::rndm 15, 40),
2400 ysize => (cf::rndm 15, 40),
2401 symmetry => (cf::rndm 1, cf::SYMMETRY_XY),
2402 #layout => string,
2403 };
2404
2405 for (split /\n/, $spec) {
2406 my ($k, $v) = split /\s+/, $_, 2;
2407
2408 $rmp->{lc $k} = $v if (length $k) && (length $v);
2409 }
2410
2411 $rmp
2412 }
2413
2414 sub prepare_random_map {
2415 my ($exit) = @_;
2416
2417 my $guard = cf::lock_acquire "exit_prepare:$exit";
2418
2419 # all this does is basically replace the /! path by
2420 # a new random map path (?random/...) with a seed
2421 # that depends on the exit object
2422
2423 my $rmp = parse_random_map_params $exit->msg;
2424
2425 if ($exit->map) {
2426 $rmp->{region} = $exit->region->name;
2427 $rmp->{origin_map} = $exit->map->path;
2428 $rmp->{origin_x} = $exit->x;
2429 $rmp->{origin_y} = $exit->y;
2430 }
2431
2432 $rmp->{random_seed} ||= $exit->random_seed;
2433
2434 my $data = cf::to_json $rmp;
2435 my $md5 = Digest::MD5::md5_hex $data;
2436 my $meta = "$RANDOMDIR/$md5.meta";
2437
2438 if (my $fh = aio_open "$meta~", O_WRONLY | O_CREAT, 0666) {
2439 aio_write $fh, 0, (length $data), $data, 0;
2440 undef $fh;
2441 aio_rename "$meta~", $meta;
2442
2443 $exit->slaying ("?random/$md5");
2444 $exit->msg (undef);
2445 }
2446 }
2447
2448 sub cf::object::player::enter_exit {
2449 my ($self, $exit) = @_;
2450
2451 return unless $self->type == cf::PLAYER;
2452
2453 if ($exit->slaying eq "/!") {
2454 #TODO: this should de-fi-ni-te-ly not be a sync-job
2455 # the problem is that $exit might not survive long enough
2456 # so it needs to be done right now, right here
2457 cf::sync_job { prepare_random_map $exit };
2458 }
2459
2460 my $slaying = cf::map::normalise $exit->slaying, $exit->map && $exit->map->path;
2461 my $hp = $exit->stats->hp;
2462 my $sp = $exit->stats->sp;
2463
2464 $self->enter_link;
2465
2466 # if exit is damned, update players death & WoR home-position
2467 $self->contr->savebed ($slaying, $hp, $sp)
2468 if $exit->flag (FLAG_DAMNED);
2469
2470 (async {
2471 $self->deactivate_recursive; # just to be sure
2472 unless (eval {
2473 $self->goto ($slaying, $hp, $sp);
2474
2475 1;
2476 }) {
2477 $self->message ("Something went wrong deep within the crossfire server. "
2478 . "I'll try to bring you back to the map you were before. "
2479 . "Please report this to the dungeon master!",
2480 cf::NDI_UNIQUE | cf::NDI_RED);
2481
2482 warn "ERROR in enter_exit: $@";
2483 $self->leave_link;
2484 }
2485 })->prio (1);
2486 }
2487
2488 =head3 cf::client
2489
2490 =over 4
2491
2492 =item $client->send_drawinfo ($text, $flags)
2493
2494 Sends a drawinfo packet to the client. Circumvents output buffering so
2495 should not be used under normal circumstances.
2496
2497 =cut
2498
2499 sub cf::client::send_drawinfo {
2500 my ($self, $text, $flags) = @_;
2501
2502 utf8::encode $text;
2503 $self->send_packet (sprintf "drawinfo %d %s", $flags || cf::NDI_BLACK, $text);
2504 }
2505
2506 =item $client->send_msg ($channel, $msg, $color, [extra...])
2507
2508 Send a drawinfo or msg packet to the client, formatting the msg for the
2509 client if neccessary. C<$type> should be a string identifying the type of
2510 the message, with C<log> being the default. If C<$color> is negative, suppress
2511 the message unless the client supports the msg packet.
2512
2513 =cut
2514
2515 our %CHANNEL = (
2516 "c/identify" => {
2517 id => "identify",
2518 title => "Identify",
2519 reply => undef,
2520 tooltip => "Items recently identified",
2521 },
2522 "c/examine" => {
2523 id => "examine",
2524 title => "Examine",
2525 reply => undef,
2526 tooltip => "Signs and other items you examined",
2527 },
2528 );
2529
2530 sub cf::client::send_msg {
2531 my ($self, $channel, $msg, $color, @extra) = @_;
2532
2533 $msg = $self->pl->expand_cfpod ($msg);
2534
2535 $color &= cf::NDI_CLIENT_MASK; # just in case...
2536
2537 # check predefined channels, for the benefit of C
2538 $channel = $CHANNEL{$channel} if $CHANNEL{$channel};
2539
2540 if (ref $channel) {
2541 # send meta info to client, if not yet sent
2542 unless (exists $self->{channel}{$channel->{id}}) {
2543 $self->{channel}{$channel->{id}} = $channel;
2544 $self->ext_msg (channel_info => $channel)
2545 if $self->can_msg;
2546 }
2547
2548 $channel = $channel->{id};
2549 }
2550
2551 return unless @extra || length $msg;
2552
2553 if ($self->can_msg) {
2554 # default colour, mask it out
2555 $color &= ~(cf::NDI_COLOR_MASK | cf::NDI_DEF)
2556 if $color & cf::NDI_DEF;
2557
2558 $self->send_packet ("msg " . $self->{json_coder}->encode (
2559 [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra]));
2560 } else {
2561 if ($color >= 0) {
2562 # replace some tags by gcfclient-compatible ones
2563 for ($msg) {
2564 1 while
2565 s/<b>([^<]*)<\/b>/[b]${1}[\/b]/
2566 || s/<i>([^<]*)<\/i>/[i]${1}[\/i]/
2567 || s/<u>([^<]*)<\/u>/[ul]${1}[\/ul]/
2568 || s/<tt>([^<]*)<\/tt>/[fixed]${1}[\/fixed]/
2569 || s/<fg name=\"([^"]+)\">([^<]*)<\/fg>/[color=$1]${2}[\/color]/;
2570 }
2571
2572 $color &= cf::NDI_COLOR_MASK;
2573
2574 utf8::encode $msg;
2575
2576 if (0 && $msg =~ /\[/) {
2577 # COMMAND/INFO
2578 $self->send_packet ("drawextinfo $color 10 8 $msg")
2579 } else {
2580 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g;
2581 $self->send_packet ("drawinfo $color $msg")
2582 }
2583 }
2584 }
2585 }
2586
2587 =item $client->ext_msg ($type, @msg)
2588
2589 Sends an ext event to the client.
2590
2591 =cut
2592
2593 sub cf::client::ext_msg($$@) {
2594 my ($self, $type, @msg) = @_;
2595
2596 if ($self->extcmd == 2) {
2597 $self->send_packet ("ext " . $self->{json_coder}->encode ([$type, @msg]));
2598 } elsif ($self->extcmd == 1) { # TODO: remove
2599 push @msg, msgtype => "event_$type";
2600 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2601 }
2602 }
2603
2604 =item $client->ext_reply ($msgid, @msg)
2605
2606 Sends an ext reply to the client.
2607
2608 =cut
2609
2610 sub cf::client::ext_reply($$@) {
2611 my ($self, $id, @msg) = @_;
2612
2613 if ($self->extcmd == 2) {
2614 $self->send_packet ("ext " . $self->{json_coder}->encode (["reply-$id", @msg]));
2615 } elsif ($self->extcmd == 1) {
2616 #TODO: version 1, remove
2617 unshift @msg, msgtype => "reply", msgid => $id;
2618 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2619 }
2620 }
2621
2622 =item $success = $client->query ($flags, "text", \&cb)
2623
2624 Queues a query to the client, calling the given callback with
2625 the reply text on a reply. flags can be C<cf::CS_QUERY_YESNO>,
2626 C<cf::CS_QUERY_SINGLECHAR> or C<cf::CS_QUERY_HIDEINPUT> or C<0>.
2627
2628 Queries can fail, so check the return code. Or don't, as queries will
2629 become reliable at some point in the future.
2630
2631 =cut
2632
2633 sub cf::client::query {
2634 my ($self, $flags, $text, $cb) = @_;
2635
2636 return unless $self->state == ST_PLAYING
2637 || $self->state == ST_SETUP
2638 || $self->state == ST_CUSTOM;
2639
2640 $self->state (ST_CUSTOM);
2641
2642 utf8::encode $text;
2643 push @{ $self->{query_queue} }, [(sprintf "query %d %s", $flags, $text), $cb];
2644
2645 $self->send_packet ($self->{query_queue}[0][0])
2646 if @{ $self->{query_queue} } == 1;
2647
2648 1
2649 }
2650
2651 cf::client->attach (
2652 on_connect => sub {
2653 my ($ns) = @_;
2654
2655 $ns->{json_coder} = JSON::XS->new->utf8->max_size (1e6)->convert_blessed;
2656 },
2657 on_reply => sub {
2658 my ($ns, $msg) = @_;
2659
2660 # this weird shuffling is so that direct followup queries
2661 # get handled first
2662 my $queue = delete $ns->{query_queue}
2663 or return; # be conservative, not sure how that can happen, but we saw a crash here
2664
2665 (shift @$queue)->[1]->($msg);
2666 return unless $ns->valid; # temporary(?) workaround for callback destroying socket
2667
2668 push @{ $ns->{query_queue} }, @$queue;
2669
2670 if (@{ $ns->{query_queue} } == @$queue) {
2671 if (@$queue) {
2672 $ns->send_packet ($ns->{query_queue}[0][0]);
2673 } else {
2674 $ns->state (ST_PLAYING) if $ns->state == ST_CUSTOM;
2675 }
2676 }
2677 },
2678 on_exticmd => sub {
2679 my ($ns, $buf) = @_;
2680
2681 my $msg = eval { $ns->{json_coder}->decode ($buf) };
2682
2683 if (ref $msg) {
2684 my ($type, $reply, @payload) =
2685 "ARRAY" eq ref $msg
2686 ? @$msg
2687 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
2688
2689 my @reply;
2690
2691 if (my $cb = $EXTICMD{$type}) {
2692 @reply = $cb->($ns, @payload);
2693 }
2694
2695 $ns->ext_reply ($reply, @reply)
2696 if $reply;
2697
2698 } else {
2699 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n";
2700 }
2701
2702 cf::override;
2703 },
2704 );
2705
2706 =item $client->async (\&cb)
2707
2708 Create a new coroutine, running the specified callback. The coroutine will
2709 be automatically cancelled when the client gets destroyed (e.g. on logout,
2710 or loss of connection).
2711
2712 =cut
2713
2714 sub cf::client::async {
2715 my ($self, $cb) = @_;
2716
2717 my $coro = &Coro::async ($cb);
2718
2719 $coro->on_destroy (sub {
2720 delete $self->{_coro}{$coro+0};
2721 });
2722
2723 $self->{_coro}{$coro+0} = $coro;
2724
2725 $coro
2726 }
2727
2728 cf::client->attach (
2729 on_destroy => sub {
2730 my ($ns) = @_;
2731
2732 $_->cancel for values %{ (delete $ns->{_coro}) || {} };
2733 },
2734 );
2735
2736 =back
2737
2738
2739 =head2 SAFE SCRIPTING
2740
2741 Functions that provide a safe environment to compile and execute
2742 snippets of perl code without them endangering the safety of the server
2743 itself. Looping constructs, I/O operators and other built-in functionality
2744 is not available in the safe scripting environment, and the number of
2745 functions and methods that can be called is greatly reduced.
2746
2747 =cut
2748
2749 our $safe = new Safe "safe";
2750 our $safe_hole = new Safe::Hole;
2751
2752 $SIG{FPE} = 'IGNORE';
2753
2754 $safe->permit_only (Opcode::opset qw(
2755 :base_core :base_mem :base_orig :base_math
2756 grepstart grepwhile mapstart mapwhile
2757 sort time
2758 ));
2759
2760 # here we export the classes and methods available to script code
2761
2762 =pod
2763
2764 The following functions and methods are available within a safe environment:
2765
2766 cf::object
2767 contr pay_amount pay_player map x y force_find force_add
2768 insert remove name archname title slaying race decrease_ob_nr
2769
2770 cf::object::player
2771 player
2772
2773 cf::player
2774 peaceful
2775
2776 cf::map
2777 trigger
2778
2779 =cut
2780
2781 for (
2782 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y
2783 insert remove inv name archname title slaying race
2784 decrease_ob_nr)],
2785 ["cf::object::player" => qw(player)],
2786 ["cf::player" => qw(peaceful)],
2787 ["cf::map" => qw(trigger)],
2788 ) {
2789 no strict 'refs';
2790 my ($pkg, @funs) = @$_;
2791 *{"safe::$pkg\::$_"} = $safe_hole->wrap (\&{"$pkg\::$_"})
2792 for @funs;
2793 }
2794
2795 =over 4
2796
2797 =item @retval = safe_eval $code, [var => value, ...]
2798
2799 Compiled and executes the given perl code snippet. additional var/value
2800 pairs result in temporary local (my) scalar variables of the given name
2801 that are available in the code snippet. Example:
2802
2803 my $five = safe_eval '$first + $second', first => 1, second => 4;
2804
2805 =cut
2806
2807 sub safe_eval($;@) {
2808 my ($code, %vars) = @_;
2809
2810 my $qcode = $code;
2811 $qcode =~ s/"/‟/g; # not allowed in #line filenames
2812 $qcode =~ s/\n/\\n/g;
2813
2814 local $_;
2815 local @safe::cf::_safe_eval_args = values %vars;
2816
2817 my $eval =
2818 "do {\n"
2819 . "my (" . (join ",", map "\$$_", keys %vars) . ") = \@cf::_safe_eval_args;\n"
2820 . "#line 0 \"{$qcode}\"\n"
2821 . $code
2822 . "\n}"
2823 ;
2824
2825 sub_generation_inc;
2826 my @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval);
2827 sub_generation_inc;
2828
2829 if ($@) {
2830 warn "$@";
2831 warn "while executing safe code '$code'\n";
2832 warn "with arguments " . (join " ", %vars) . "\n";
2833 }
2834
2835 wantarray ? @res : $res[0]
2836 }
2837
2838 =item cf::register_script_function $function => $cb
2839
2840 Register a function that can be called from within map/npc scripts. The
2841 function should be reasonably secure and should be put into a package name
2842 like the extension.
2843
2844 Example: register a function that gets called whenever a map script calls
2845 C<rent::overview>, as used by the C<rent> extension.
2846
2847 cf::register_script_function "rent::overview" => sub {
2848 ...
2849 };
2850
2851 =cut
2852
2853 sub register_script_function {
2854 my ($fun, $cb) = @_;
2855
2856 no strict 'refs';
2857 *{"safe::$fun"} = $safe_hole->wrap ($cb);
2858 }
2859
2860 =back
2861
2862 =cut
2863
2864 #############################################################################
2865 # the server's init and main functions
2866
2867 sub load_facedata($) {
2868 my ($path) = @_;
2869
2870 # HACK to clear player env face cache, we need some signal framework
2871 # for this (global event?)
2872 %ext::player_env::MUSIC_FACE_CACHE = ();
2873
2874 my $enc = JSON::XS->new->utf8->canonical->relaxed;
2875
2876 warn "loading facedata from $path\n";
2877
2878 my $facedata;
2879 0 < aio_load $path, $facedata
2880 or die "$path: $!";
2881
2882 $facedata = Coro::Storable::thaw $facedata;
2883
2884 $facedata->{version} == 2
2885 or cf::cleanup "$path: version mismatch, cannot proceed.";
2886
2887 # patch in the exptable
2888 $facedata->{resource}{"res/exp_table"} = {
2889 type => FT_RSRC,
2890 data => $enc->encode ([map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]),
2891 };
2892 cf::cede_to_tick;
2893
2894 {
2895 my $faces = $facedata->{faceinfo};
2896
2897 while (my ($face, $info) = each %$faces) {
2898 my $idx = (cf::face::find $face) || cf::face::alloc $face;
2899 cf::face::set_visibility $idx, $info->{visibility};
2900 cf::face::set_magicmap $idx, $info->{magicmap};
2901 cf::face::set_data $idx, 0, $info->{data32}, Digest::MD5::md5 $info->{data32};
2902 cf::face::set_data $idx, 1, $info->{data64}, Digest::MD5::md5 $info->{data64};
2903
2904 cf::cede_to_tick;
2905 }
2906
2907 while (my ($face, $info) = each %$faces) {
2908 next unless $info->{smooth};
2909 my $idx = cf::face::find $face
2910 or next;
2911 if (my $smooth = cf::face::find $info->{smooth}) {
2912 cf::face::set_smooth $idx, $smooth;
2913 cf::face::set_smoothlevel $idx, $info->{smoothlevel};
2914 } else {
2915 warn "smooth face '$info->{smooth}' not found for face '$face'";
2916 }
2917
2918 cf::cede_to_tick;
2919 }
2920 }
2921
2922 {
2923 my $anims = $facedata->{animinfo};
2924
2925 while (my ($anim, $info) = each %$anims) {
2926 cf::anim::set $anim, $info->{frames}, $info->{facings};
2927 cf::cede_to_tick;
2928 }
2929
2930 cf::anim::invalidate_all; # d'oh
2931 }
2932
2933 {
2934 # TODO: for gcfclient pleasure, we should give resources
2935 # that gcfclient doesn't grok a >10000 face index.
2936 my $res = $facedata->{resource};
2937
2938 my $soundconf = delete $res->{"res/sound.conf"};
2939
2940 while (my ($name, $info) = each %$res) {
2941 my $idx = (cf::face::find $name) || cf::face::alloc $name;
2942 my $data;
2943
2944 if ($info->{type} & 1) {
2945 # prepend meta info
2946
2947 my $meta = $enc->encode ({
2948 name => $name,
2949 %{ $info->{meta} || {} },
2950 });
2951
2952 $data = pack "(w/a*)*", $meta, $info->{data};
2953 } else {
2954 $data = $info->{data};
2955 }
2956
2957 cf::face::set_data $idx, 0, $data, Digest::MD5::md5 $data;
2958 cf::face::set_type $idx, $info->{type};
2959
2960 cf::cede_to_tick;
2961 }
2962
2963 if ($soundconf) {
2964 $soundconf = $enc->decode (delete $soundconf->{data});
2965
2966 for (0 .. SOUND_CAST_SPELL_0 - 1) {
2967 my $sound = $soundconf->{compat}[$_]
2968 or next;
2969
2970 my $face = cf::face::find "sound/$sound->[1]";
2971 cf::sound::set $sound->[0] => $face;
2972 cf::sound::old_sound_index $_, $face; # gcfclient-compat
2973 }
2974
2975 while (my ($k, $v) = each %{$soundconf->{event}}) {
2976 my $face = cf::face::find "sound/$v";
2977 cf::sound::set $k => $face;
2978 }
2979 }
2980 }
2981
2982 1
2983 }
2984
2985 register_exticmd fx_want => sub {
2986 my ($ns, $want) = @_;
2987
2988 while (my ($k, $v) = each %$want) {
2989 $ns->fx_want ($k, $v);
2990 }
2991 };
2992
2993 sub reload_regions {
2994 # HACK to clear player env face cache, we need some signal framework
2995 # for this (global event?)
2996 %ext::player_env::MUSIC_FACE_CACHE = ();
2997
2998 load_resource_file "$MAPDIR/regions"
2999 or die "unable to load regions file\n";
3000
3001 for (cf::region::list) {
3002 $_->{match} = qr/$_->{match}/
3003 if exists $_->{match};
3004 }
3005 }
3006
3007 sub reload_facedata {
3008 load_facedata "$DATADIR/facedata"
3009 or die "unable to load facedata\n";
3010 }
3011
3012 sub reload_archetypes {
3013 load_resource_file "$DATADIR/archetypes"
3014 or die "unable to load archetypes\n";
3015 #d# NEED to laod twice to resolve forward references
3016 # this really needs to be done in an extra post-pass
3017 # (which needs to be synchronous, so solve it differently)
3018 load_resource_file "$DATADIR/archetypes"
3019 or die "unable to load archetypes\n";
3020 }
3021
3022 sub reload_treasures {
3023 load_resource_file "$DATADIR/treasures"
3024 or die "unable to load treasurelists\n";
3025 }
3026
3027 sub reload_resources {
3028 warn "reloading resource files...\n";
3029
3030 reload_regions;
3031 reload_facedata;
3032 #reload_archetypes;#d#
3033 reload_archetypes;
3034 reload_treasures;
3035
3036 warn "finished reloading resource files\n";
3037 }
3038
3039 sub init {
3040 reload_resources;
3041 }
3042
3043 sub reload_config {
3044 open my $fh, "<:utf8", "$CONFDIR/config"
3045 or return;
3046
3047 local $/;
3048 *CFG = YAML::Syck::Load <$fh>;
3049
3050 $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37];
3051
3052 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset};
3053 $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset};
3054
3055 if (exists $CFG{mlockall}) {
3056 eval {
3057 $CFG{mlockall} ? eval "mlockall()" : eval "munlockall()"
3058 and die "WARNING: m(un)lockall failed: $!\n";
3059 };
3060 warn $@ if $@;
3061 }
3062 }
3063
3064 sub main {
3065 # we must not ever block the main coroutine
3066 local $Coro::idle = sub {
3067 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
3068 (async {
3069 Event::one_event;
3070 })->prio (Coro::PRIO_MAX);
3071 };
3072
3073 reload_config;
3074 db_init;
3075 load_extensions;
3076
3077 $TICK_WATCHER->start;
3078 Event::loop;
3079 }
3080
3081 #############################################################################
3082 # initialisation and cleanup
3083
3084 # install some emergency cleanup handlers
3085 BEGIN {
3086 for my $signal (qw(INT HUP TERM)) {
3087 Event->signal (
3088 reentrant => 0,
3089 data => WF_AUTOCANCEL,
3090 signal => $signal,
3091 prio => 0,
3092 cb => sub {
3093 cf::cleanup "SIG$signal";
3094 },
3095 );
3096 }
3097 }
3098
3099 sub write_runtime {
3100 my $runtime = "$LOCALDIR/runtime";
3101
3102 # first touch the runtime file to show we are still running:
3103 # the fsync below can take a very very long time.
3104
3105 IO::AIO::aio_utime $runtime, undef, undef;
3106
3107 my $guard = cf::lock_acquire "write_runtime";
3108
3109 my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644
3110 or return;
3111
3112 my $value = $cf::RUNTIME + 90 + 10;
3113 # 10 is the runtime save interval, for a monotonic clock
3114 # 60 allows for the watchdog to kill the server.
3115
3116 (aio_write $fh, 0, (length $value), $value, 0) <= 0
3117 and return;
3118
3119 # always fsync - this file is important
3120 aio_fsync $fh
3121 and return;
3122
3123 # touch it again to show we are up-to-date
3124 aio_utime $fh, undef, undef;
3125
3126 close $fh
3127 or return;
3128
3129 aio_rename "$runtime~", $runtime
3130 and return;
3131
3132 warn "runtime file written.\n";
3133
3134 1
3135 }
3136
3137 sub emergency_save() {
3138 my $freeze_guard = cf::freeze_mainloop;
3139
3140 warn "enter emergency perl save\n";
3141
3142 cf::sync_job {
3143 # use a peculiar iteration method to avoid tripping on perl
3144 # refcount bugs in for. also avoids problems with players
3145 # and maps saved/destroyed asynchronously.
3146 warn "begin emergency player save\n";
3147 for my $login (keys %cf::PLAYER) {
3148 my $pl = $cf::PLAYER{$login} or next;
3149 $pl->valid or next;
3150 $pl->save;
3151 }
3152 warn "end emergency player save\n";
3153
3154 warn "begin emergency map save\n";
3155 for my $path (keys %cf::MAP) {
3156 my $map = $cf::MAP{$path} or next;
3157 $map->valid or next;
3158 $map->save;
3159 }
3160 warn "end emergency map save\n";
3161
3162 warn "begin emergency database checkpoint\n";
3163 BDB::db_env_txn_checkpoint $DB_ENV;
3164 warn "end emergency database checkpoint\n";
3165 };
3166
3167 warn "leave emergency perl save\n";
3168 }
3169
3170 sub post_cleanup {
3171 my ($make_core) = @_;
3172
3173 warn Carp::longmess "post_cleanup backtrace"
3174 if $make_core;
3175 }
3176
3177 sub do_reload_perl() {
3178 # can/must only be called in main
3179 if ($Coro::current != $Coro::main) {
3180 warn "can only reload from main coroutine";
3181 return;
3182 }
3183
3184 warn "reloading...";
3185
3186 warn "entering sync_job";
3187
3188 cf::sync_job {
3189 cf::write_runtime; # external watchdog should not bark
3190 cf::emergency_save;
3191 cf::write_runtime; # external watchdog should not bark
3192
3193 warn "syncing database to disk";
3194 BDB::db_env_txn_checkpoint $DB_ENV;
3195
3196 # if anything goes wrong in here, we should simply crash as we already saved
3197
3198 warn "cancelling all WF_AUTOCANCEL watchers";
3199 for (Event::all_watchers) {
3200 $_->cancel if $_->data & WF_AUTOCANCEL;
3201 }
3202
3203 warn "flushing outstanding aio requests";
3204 for (;;) {
3205 BDB::flush;
3206 IO::AIO::flush;
3207 Coro::cede;
3208 last unless IO::AIO::nreqs || BDB::nreqs;
3209 warn "iterate...";
3210 }
3211
3212 ++$RELOAD;
3213
3214 warn "cancelling all extension coros";
3215 $_->cancel for values %EXT_CORO;
3216 %EXT_CORO = ();
3217
3218 warn "removing commands";
3219 %COMMAND = ();
3220
3221 warn "removing ext/exti commands";
3222 %EXTCMD = ();
3223 %EXTICMD = ();
3224
3225 warn "unloading/nuking all extensions";
3226 for my $pkg (@EXTS) {
3227 warn "... unloading $pkg";
3228
3229 if (my $cb = $pkg->can ("unload")) {
3230 eval {
3231 $cb->($pkg);
3232 1
3233 } or warn "$pkg unloaded, but with errors: $@";
3234 }
3235
3236 warn "... nuking $pkg";
3237 Symbol::delete_package $pkg;
3238 }
3239
3240 warn "unloading all perl modules loaded from $LIBDIR";
3241 while (my ($k, $v) = each %INC) {
3242 next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
3243
3244 warn "... unloading $k";
3245 delete $INC{$k};
3246
3247 $k =~ s/\.pm$//;
3248 $k =~ s/\//::/g;
3249
3250 if (my $cb = $k->can ("unload_module")) {
3251 $cb->();
3252 }
3253
3254 Symbol::delete_package $k;
3255 }
3256
3257 warn "getting rid of safe::, as good as possible";
3258 Symbol::delete_package "safe::$_"
3259 for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region);
3260
3261 warn "unloading cf.pm \"a bit\"";
3262 delete $INC{"cf.pm"};
3263 delete $INC{"cf/pod.pm"};
3264
3265 # don't, removes xs symbols, too,
3266 # and global variables created in xs
3267 #Symbol::delete_package __PACKAGE__;
3268
3269 warn "unload completed, starting to reload now";
3270
3271 warn "reloading cf.pm";
3272 require cf;
3273 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
3274
3275 warn "loading config and database again";
3276 cf::reload_config;
3277
3278 warn "loading extensions";
3279 cf::load_extensions;
3280
3281 warn "reattaching attachments to objects/players";
3282 _global_reattach; # objects, sockets
3283 warn "reattaching attachments to maps";
3284 reattach $_ for values %MAP;
3285 warn "reattaching attachments to players";
3286 reattach $_ for values %PLAYER;
3287
3288 warn "leaving sync_job";
3289
3290 1
3291 } or do {
3292 warn $@;
3293 warn "error while reloading, exiting.";
3294 exit 1;
3295 };
3296
3297 warn "reloaded";
3298 };
3299
3300 our $RELOAD_WATCHER; # used only during reload
3301
3302 sub reload_perl() {
3303 # doing reload synchronously and two reloads happen back-to-back,
3304 # coro crashes during coro_state_free->destroy here.
3305
3306 $RELOAD_WATCHER ||= Event->timer (
3307 reentrant => 0,
3308 after => 0,
3309 data => WF_AUTOCANCEL,
3310 cb => sub {
3311 do_reload_perl;
3312 undef $RELOAD_WATCHER;
3313 },
3314 );
3315 }
3316
3317 register_command "reload" => sub {
3318 my ($who, $arg) = @_;
3319
3320 if ($who->flag (FLAG_WIZ)) {
3321 $who->message ("reloading server.");
3322 async { reload_perl };
3323 }
3324 };
3325
3326 unshift @INC, $LIBDIR;
3327
3328 my $bug_warning = 0;
3329
3330 our @WAIT_FOR_TICK;
3331 our @WAIT_FOR_TICK_BEGIN;
3332
3333 sub wait_for_tick {
3334 return unless $TICK_WATCHER->is_active;
3335 return if $Coro::current == $Coro::main;
3336
3337 my $signal = new Coro::Signal;
3338 push @WAIT_FOR_TICK, $signal;
3339 $signal->wait;
3340 }
3341
3342 sub wait_for_tick_begin {
3343 return unless $TICK_WATCHER->is_active;
3344 return if $Coro::current == $Coro::main;
3345
3346 my $signal = new Coro::Signal;
3347 push @WAIT_FOR_TICK_BEGIN, $signal;
3348 $signal->wait;
3349 }
3350
3351 my $min = 1e6;#d#
3352 my $avg = 10;
3353 $TICK_WATCHER = Event->timer (
3354 reentrant => 0,
3355 parked => 1,
3356 prio => 0,
3357 at => $NEXT_TICK || $TICK,
3358 data => WF_AUTOCANCEL,
3359 cb => sub {
3360 if ($Coro::current != $Coro::main) {
3361 Carp::cluck "major BUG: server tick called outside of main coro, skipping it"
3362 unless ++$bug_warning > 10;
3363 return;
3364 }
3365
3366 $NOW = $tick_start = Event::time;
3367
3368 cf::server_tick; # one server iteration
3369
3370 0 && sync_job {#d#
3371 for(1..10) {
3372 my $t = Event::time;
3373 my $map = my $map = new_from_path cf::map "/tmp/x.map"
3374 or die;
3375
3376 $map->width (50);
3377 $map->height (50);
3378 $map->alloc;
3379 $map->_load_objects ("/tmp/x.map", 1);
3380 my $t = Event::time - $t;
3381
3382 #next unless $t < 0.0013;#d#
3383 if ($t < $min) {
3384 $min = $t;
3385 }
3386 $avg = $avg * 0.99 + $t * 0.01;
3387 }
3388 warn "XXXXXXXXXXXXXXXXXX min $min avg $avg\n";#d#
3389 exit 0;
3390 # 2007-05-22 02:33:04.569 min 0.00112509727478027 avg 0.0012259249572477
3391 };
3392
3393 $RUNTIME += $TICK;
3394 $NEXT_TICK += $TICK;
3395
3396 if ($NOW >= $NEXT_RUNTIME_WRITE) {
3397 $NEXT_RUNTIME_WRITE = $NOW + 10;
3398 Coro::async_pool {
3399 write_runtime
3400 or warn "ERROR: unable to write runtime file: $!";
3401 };
3402 }
3403
3404 # my $AFTER = Event::time;
3405 # warn $AFTER - $NOW;#d#
3406
3407 if (my $sig = shift @WAIT_FOR_TICK_BEGIN) {
3408 $sig->send;
3409 }
3410 while (my $sig = shift @WAIT_FOR_TICK) {
3411 $sig->send;
3412 }
3413
3414 $NOW = Event::time;
3415
3416 # if we are delayed by four ticks or more, skip them all
3417 $NEXT_TICK = $NOW if $NOW >= $NEXT_TICK + $TICK * 4;
3418
3419 $TICK_WATCHER->at ($NEXT_TICK);
3420 $TICK_WATCHER->start;
3421
3422 $LOAD = ($NOW - $tick_start) / $TICK;
3423 $LOADAVG = $LOADAVG * 0.75 + $LOAD * 0.25;
3424
3425 _post_tick;
3426
3427
3428 },
3429 );
3430
3431 {
3432 BDB::max_poll_time $TICK * 0.1;
3433 $BDB_POLL_WATCHER = Event->io (
3434 reentrant => 0,
3435 fd => BDB::poll_fileno,
3436 poll => 'r',
3437 prio => 0,
3438 data => WF_AUTOCANCEL,
3439 cb => \&BDB::poll_cb,
3440 );
3441 BDB::min_parallel 8;
3442
3443 BDB::set_sync_prepare {
3444 my $status;
3445 my $current = $Coro::current;
3446 (
3447 sub {
3448 $status = $!;
3449 $current->ready; undef $current;
3450 },
3451 sub {
3452 Coro::schedule while defined $current;
3453 $! = $status;
3454 },
3455 )
3456 };
3457
3458 unless ($DB_ENV) {
3459 $DB_ENV = BDB::db_env_create;
3460
3461 cf::sync_job {
3462 eval {
3463 BDB::db_env_open
3464 $DB_ENV,
3465 $BDBDIR,
3466 BDB::INIT_LOCK | BDB::INIT_LOG | BDB::INIT_MPOOL | BDB::INIT_TXN
3467 | BDB::RECOVER | BDB::REGISTER | BDB::USE_ENVIRON | BDB::CREATE,
3468 0666;
3469
3470 cf::cleanup "db_env_open($BDBDIR): $!" if $!;
3471
3472 $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT | BDB::TXN_NOSYNC, 1);
3473 $DB_ENV->set_lk_detect;
3474 };
3475
3476 cf::cleanup "db_env_open(db): $@" if $@;
3477 };
3478 }
3479 }
3480
3481 {
3482 IO::AIO::min_parallel 8;
3483
3484 undef $Coro::AIO::WATCHER;
3485 IO::AIO::max_poll_time $TICK * 0.1;
3486 $AIO_POLL_WATCHER = Event->io (
3487 reentrant => 0,
3488 data => WF_AUTOCANCEL,
3489 fd => IO::AIO::poll_fileno,
3490 poll => 'r',
3491 prio => 6,
3492 cb => \&IO::AIO::poll_cb,
3493 );
3494 }
3495
3496 my $_log_backtrace;
3497
3498 sub _log_backtrace {
3499 my ($msg, @addr) = @_;
3500
3501 $msg =~ s/\n//;
3502
3503 # limit the # of concurrent backtraces
3504 if ($_log_backtrace < 2) {
3505 ++$_log_backtrace;
3506 async {
3507 my @bt = fork_call {
3508 @addr = map { sprintf "%x", $_ } @addr;
3509 my $self = (-f "/proc/$$/exe") ? "/proc/$$/exe" : $^X;
3510 open my $fh, "exec addr2line -C -f -i -e \Q$self\E @addr 2>&1 |"
3511 or die "addr2line: $!";
3512
3513 my @funcs;
3514 my @res = <$fh>;
3515 chomp for @res;
3516 while (@res) {
3517 my ($func, $line) = splice @res, 0, 2, ();
3518 push @funcs, "[$func] $line";
3519 }
3520
3521 @funcs
3522 };
3523
3524 LOG llevInfo, "[ABT] $msg\n";
3525 LOG llevInfo, "[ABT] $_\n" for @bt;
3526 --$_log_backtrace;
3527 };
3528 } else {
3529 LOG llevInfo, "[ABT] $msg\n";
3530 LOG llevInfo, "[ABT] [suppressed]\n";
3531 }
3532 }
3533
3534 # load additional modules
3535 use cf::pod;
3536
3537 END { cf::emergency_save }
3538
3539 1
3540