ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf.pm
Revision: 1.349
Committed: Fri Aug 31 04:10:43 2007 UTC (16 years, 8 months ago) by root
Branch: MAIN
Changes since 1.348: +12 -8 lines
Log Message:
- slap in some more locking, due to the problem Cid encountered
  (couldn't enter nimbus because it was "loading").

2007-08-31 05:29:46.8853 loading /var/cfserver/players/Cid/âÂÂnimbus.map (443960)
2007-08-31 05:29:46.8888 resetting map {nuke}/aaad
2007-08-31 05:29:46.9895 move_object: monster has been removed - will not process further
2007-08-31 05:29:46.9896 move_object: monster has been removed - will not process further
2007-08-31 05:29:46.9897 move_object: monster has been removed - will not process further
2007-08-31 05:29:46.9907 BUG: process_events(): removed object is on active list: {cnt:597029,uuid:<1.2346e1c5f>,name:"Cid",flags:[0,2,7,11,15,56,57,71,80,90,98],type:1}(on {link}@10+10)
2007-08-31 05:29:47.6096 runtime file written.

seems the problem was that the map was nuked and reset at the same time he entered. infact,
his nimbus map was in memory for a very long time before.

the change will not help if we try to load when we are nuking, but it
will help in case we want to nuke a map currently being loaded, which is
probably what happened here, although it is not at all clear to me why it
would nuke at that moment.

File Contents

# User Rev Content
1 root 1.1 package cf;
2    
3 root 1.96 use utf8;
4     use strict;
5    
6 root 1.1 use Symbol;
7     use List::Util;
8 root 1.250 use Socket;
9 root 1.6 use Storable;
10 root 1.224 use Event;
11 root 1.23 use Opcode;
12     use Safe;
13     use Safe::Hole;
14 root 1.19
15 root 1.332 use Coro 3.64 ();
16 root 1.224 use Coro::State;
17 root 1.250 use Coro::Handle;
18 root 1.96 use Coro::Event;
19     use Coro::Timer;
20     use Coro::Signal;
21     use Coro::Semaphore;
22 root 1.105 use Coro::AIO;
23 root 1.237 use Coro::Storable;
24 root 1.332 use Coro::Util ();
25 root 1.96
26 root 1.344 use JSON::XS ();
27 root 1.206 use BDB ();
28 root 1.154 use Data::Dumper;
29 root 1.108 use Digest::MD5;
30 root 1.105 use Fcntl;
31 root 1.227 use YAML::Syck ();
32 root 1.145 use IO::AIO 2.32 ();
33 root 1.32 use Time::HiRes;
34 root 1.208 use Compress::LZF;
35 root 1.302 use Digest::MD5 ();
36 root 1.208
37 root 1.227 # configure various modules to our taste
38     #
39 root 1.237 $Storable::canonical = 1; # reduce rsync transfers
40 root 1.224 Coro::State::cctx_stacksize 256000; # 1-2MB stack, for deep recursions in maze generator
41 root 1.208 Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later
42 root 1.227
43 root 1.224 $Event::Eval = 1; # no idea why this is required, but it is
44 root 1.1
45 root 1.72 # work around bug in YAML::Syck - bad news for perl6, will it be as broken wrt. unicode?
46     $YAML::Syck::ImplicitUnicode = 1;
47    
48 root 1.139 $Coro::main->prio (Coro::PRIO_MAX); # run main coroutine ("the server") with very high priority
49 root 1.1
50 root 1.227 sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
51    
52 root 1.85 our %COMMAND = ();
53     our %COMMAND_TIME = ();
54 root 1.159
55     our @EXTS = (); # list of extension package names
56 root 1.85 our %EXTCMD = ();
57 root 1.287 our %EXTICMD = ();
58 root 1.159 our %EXT_CORO = (); # coroutines bound to extensions
59 root 1.161 our %EXT_MAP = (); # pluggable maps
60 root 1.85
61 root 1.223 our $RELOAD; # number of reloads so far
62 root 1.1 our @EVENT;
63 root 1.253
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 root 1.1
76 root 1.245 our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!)
77 root 1.35 our $TICK_WATCHER;
78 root 1.168 our $AIO_POLL_WATCHER;
79 root 1.214 our $NEXT_RUNTIME_WRITE; # when should the runtime file be written
80 root 1.35 our $NEXT_TICK;
81 root 1.103 our $NOW;
82 root 1.205 our $USE_FSYNC = 1; # use fsync to write maps - default off
83 root 1.35
84 root 1.206 our $BDB_POLL_WATCHER;
85     our $DB_ENV;
86    
87 root 1.70 our %CFG;
88    
89 root 1.84 our $UPTIME; $UPTIME ||= time;
90 root 1.103 our $RUNTIME;
91    
92 root 1.143 our %PLAYER; # all users
93     our %MAP; # all maps
94 root 1.166 our $LINK_MAP; # the special {link} map, which is always available
95 root 1.103
96 root 1.166 # 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 root 1.265 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 root 1.103 binmode STDOUT;
104     binmode STDERR;
105    
106     # read virtual server time, if available
107 root 1.253 unless ($RUNTIME || !-e "$LOCALDIR/runtime") {
108     open my $fh, "<", "$LOCALDIR/runtime"
109 root 1.103 or die "unable to read runtime file: $!";
110     $RUNTIME = <$fh> + 0.;
111     }
112    
113 root 1.253 mkdir $_
114     for $LOCALDIR, $TMPDIR, $UNIQUEDIR, $PLAYERDIR, $RANDOMDIR, $BDBDIR;
115 root 1.103
116 root 1.131 our $EMERGENCY_POSITION;
117 root 1.110
118 root 1.199 sub cf::map::normalise;
119    
120 root 1.70 #############################################################################
121    
122     =head2 GLOBAL VARIABLES
123    
124     =over 4
125    
126 root 1.83 =item $cf::UPTIME
127    
128     The timestamp of the server start (so not actually an uptime).
129    
130 root 1.103 =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 root 1.253 =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 root 1.70
144 root 1.103 =item $cf::NOW
145    
146     The time of the last (current) server tick.
147    
148 root 1.70 =item $cf::TICK
149    
150     The interval between server ticks, in seconds.
151    
152 root 1.265 =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 root 1.70 =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 root 1.239 =item cf::wait_for_tick, cf::wait_for_tick_begin
168 root 1.155
169 root 1.239 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 root 1.155
173 elmex 1.310 =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 root 1.70 =back
180    
181     =cut
182    
183 root 1.1 BEGIN {
184     *CORE::GLOBAL::warn = sub {
185     my $msg = join "", @_;
186 root 1.103
187 root 1.1 $msg .= "\n"
188     unless $msg =~ /\n$/;
189    
190 root 1.255 $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge;
191 root 1.250
192     utf8::encode $msg;
193 root 1.146 LOG llevError, $msg;
194 root 1.1 };
195     }
196    
197 root 1.93 @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 root 1.273 @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 root 1.25
205 root 1.41 # we bless all objects into (empty) derived classes to force a method lookup
206 root 1.25 # within the Safe compartment.
207 root 1.86 for my $pkg (qw(
208 root 1.100 cf::global cf::attachable
209 root 1.86 cf::object cf::object::player
210 root 1.89 cf::client cf::player
211 root 1.86 cf::arch cf::living
212     cf::map cf::party cf::region
213     )) {
214 root 1.25 no strict 'refs';
215 root 1.41 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg;
216 root 1.25 }
217 root 1.1
218 root 1.18 $Event::DIED = sub {
219     warn "error in event callback: @_";
220     };
221    
222 root 1.281 #############################################################################
223    
224 root 1.70 =head2 UTILITY FUNCTIONS
225    
226     =over 4
227    
228 root 1.154 =item dumpval $ref
229    
230 root 1.70 =cut
231 root 1.44
232 root 1.154 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 root 1.70 =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 root 1.287 =cut
260    
261 root 1.290 our $json_coder = JSON::XS->new->utf8->max_size (1e6); # accept ~1mb max
262 root 1.287
263     sub to_json ($) { $json_coder->encode ($_[0]) }
264     sub from_json ($) { $json_coder->decode ($_[0]) }
265    
266 root 1.120 =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 root 1.135 a Coro::guard object. If the guard object gets destroyed (goes out of scope,
274 root 1.120 for example when the coroutine gets canceled), the lock is automatically
275     returned.
276    
277 root 1.347 Locks are *not* recursive, locking from the same coro twice results in a
278     deadlocked coro.
279    
280 root 1.133 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 root 1.120
283 root 1.253 =item $locked = cf::lock_active $string
284    
285     Return true if the lock is currently active, i.e. somebody has locked it.
286    
287 root 1.120 =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 root 1.135 Coro::guard {
310 root 1.120 # wake up all waiters, to be on the safe side
311     $_->ready for @{ delete $LOCK{$key} };
312     }
313     }
314    
315 root 1.253 sub lock_active($) {
316     my ($key) = @_;
317    
318     ! ! $LOCK{$key}
319     }
320    
321 root 1.133 sub freeze_mainloop {
322     return unless $TICK_WATCHER->is_active;
323    
324 root 1.168 my $guard = Coro::guard {
325     $TICK_WATCHER->start;
326     };
327 root 1.133 $TICK_WATCHER->stop;
328     $guard
329     }
330    
331 root 1.315 =item cf::get_slot $time[, $priority[, $name]]
332 root 1.314
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 root 1.315 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 root 1.314 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 root 1.315 sub get_slot($;$$) {
376     my ($time, $pri, $name) = @_;
377    
378     $time = $TICK * .6 if $time > $TICK * .6;
379     my $sig = new Coro::Signal;
380 root 1.314
381 root 1.315 push @SLOT_QUEUE, [$time, $pri, $sig, $name];
382 root 1.314 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE;
383     $SLOT_QUEUE->ready;
384     $sig->wait;
385     }
386    
387 root 1.140 =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 root 1.106 =item cf::sync_job { BLOCK }
398    
399 root 1.281 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 root 1.106
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 root 1.105 sub sync_job(&) {
412     my ($job) = @_;
413    
414     if ($Coro::current == $Coro::main) {
415 root 1.265 my $time = Event::time;
416    
417 root 1.112 # this is the main coro, too bad, we have to block
418     # till the operation succeeds, freezing the server :/
419    
420 root 1.110 # TODO: use suspend/resume instead
421 root 1.112 # (but this is cancel-safe)
422 root 1.133 my $freeze_guard = freeze_mainloop;
423 root 1.112
424     my $busy = 1;
425     my @res;
426    
427 root 1.140 (async {
428 root 1.112 @res = eval { $job->() };
429     warn $@ if $@;
430     undef $busy;
431     })->prio (Coro::PRIO_MAX);
432    
433 root 1.105 while ($busy) {
434 root 1.141 Coro::cede or Event::one_event;
435 root 1.105 }
436 root 1.112
437 root 1.265 $time = Event::time - $time;
438    
439 root 1.266 LOG llevError | logBacktrace, Carp::longmess "long sync job"
440     if $time > $TICK * 0.5 && $TICK_WATCHER->is_active;
441 root 1.265
442     $tick_start += $time; # do not account sync jobs to server load
443    
444 root 1.112 wantarray ? @res : $res[0]
445 root 1.105 } else {
446 root 1.112 # we are in another coroutine, how wonderful, everything just works
447    
448     $job->()
449 root 1.105 }
450     }
451    
452 root 1.140 =item $coro = cf::async_ext { BLOCK }
453 root 1.103
454 root 1.159 Like async, but this coro is automatically being canceled when the
455 root 1.140 extension calling this is being unloaded.
456 root 1.103
457     =cut
458    
459 root 1.140 sub async_ext(&) {
460 root 1.103 my $cb = shift;
461    
462 root 1.140 my $coro = &Coro::async ($cb);
463 root 1.103
464     $coro->on_destroy (sub {
465     delete $EXT_CORO{$coro+0};
466     });
467     $EXT_CORO{$coro+0} = $coro;
468    
469     $coro
470     }
471    
472 root 1.281 =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 root 1.332 # 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 root 1.281
488 root 1.332 my @res = Coro::Util::fork_eval {
489 root 1.281 reset_signals;
490 root 1.332 &$cb
491     }, @args;
492 root 1.298
493 root 1.332 wantarray ? @res : $res[-1]
494 root 1.281 }
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 root 1.264
561 root 1.281 ($_, (stat _)[7,9])
562     } @$src;
563 root 1.264
564 root 1.281 my $dbmeta = db_get cache => "$id/meta";
565     if ($dbmeta ne $meta) {
566     # changed, we may need to process
567 root 1.264
568 root 1.281 my @data;
569     my $md5;
570 root 1.219
571 root 1.281 for (0 .. $#$src) {
572     0 <= aio_load $src->[$_], $data[$_]
573     or Carp::croak "$src->[$_]: $!";
574     }
575 root 1.108
576 root 1.281 # if processing is expensive, check
577     # checksum first
578     if (1) {
579     $md5 =
580     join "\x00",
581     $processversion,
582     map {
583 root 1.346 cf::cede_to_tick;
584 root 1.281 ($src->[$_], Digest::MD5::md5_hex $data[$_])
585     } 0.. $#$src;
586    
587 root 1.186
588 root 1.281 my $dbmd5 = db_get cache => "$id/md5";
589     if ($dbmd5 eq $md5) {
590     db_put cache => "$id/meta", $meta;
591 root 1.108
592 root 1.281 return db_get cache => "$id/data";
593     }
594     }
595 root 1.108
596 root 1.281 my $t1 = Time::HiRes::time;
597     my $data = $process->(\@data);
598     my $t2 = Time::HiRes::time;
599 root 1.264
600 root 1.281 warn "cache: '$id' processed in ", $t2 - $t1, "s\n";
601 root 1.108
602 root 1.281 db_put cache => "$id/data", $data;
603     db_put cache => "$id/md5" , $md5;
604     db_put cache => "$id/meta", $meta;
605 root 1.108
606 root 1.281 return $data;
607     }
608 root 1.263
609 root 1.281 db_get cache => "$id/data"
610 root 1.108 }
611    
612 root 1.230 =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 root 1.70 =back
624    
625 root 1.71 =cut
626    
627 root 1.44 #############################################################################
628 root 1.39
629 root 1.93 =head2 ATTACHABLE OBJECTS
630    
631 root 1.94 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 root 1.281 Crossfire TRT).
638 root 1.55
639     =over 4
640    
641 root 1.94 =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 root 1.46
651 root 1.94 $obj->attach (minesweeper => width => 10, height => 10);
652 root 1.53
653 root 1.93 =item $bool = $attachable->attached ($name)
654 root 1.46
655 root 1.93 Checks wether the named attachment is currently attached to the object.
656 root 1.46
657 root 1.94 =item cf::CLASS->attach ...
658 root 1.46
659 root 1.94 =item cf::CLASS->detach ...
660 root 1.92
661 root 1.94 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 root 1.92
664 root 1.93 You can attach to global events by using the C<cf::global> class.
665 root 1.92
666 root 1.94 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 root 1.46
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 root 1.93 =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 root 1.46 =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 root 1.94 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 root 1.96 =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 root 1.39 =cut
749    
750 root 1.40 # the following variables are defined in .xs and must not be re-created
751 root 1.100 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 root 1.39
759 root 1.45 my %attachment;
760    
761 root 1.170 sub cf::attachable::thawer_merge {
762     # simply override everything except _meta
763     local $_[0]{_meta};
764     %{$_[0]} = %{$_[1]};
765     }
766    
767 root 1.93 sub _attach_cb($$$$) {
768     my ($registry, $event, $prio, $cb) = @_;
769 root 1.39
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 root 1.100 # hack
780     my %attachable_klass = map +($_ => 1), KLASS_OBJECT, KLASS_CLIENT, KLASS_PLAYER, KLASS_MAP;
781    
782 root 1.39 # 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 root 1.93 sub _attach {
786 root 1.45 my ($registry, $klass, @arg) = @_;
787 root 1.39
788 root 1.93 my $object_type;
789 root 1.39 my $prio = 0;
790     my %cb_id = map +("on_" . lc $EVENT[$_][0], $_) , grep $EVENT[$_][1] == $klass, 0 .. $#EVENT;
791    
792 root 1.100 #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 root 1.45 while (@arg) {
798     my $type = shift @arg;
799 root 1.39
800     if ($type eq "prio") {
801 root 1.45 $prio = shift @arg;
802 root 1.39
803 root 1.93 } 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 root 1.267 $registry = $CB_TYPE[$object_type + $object_subtype * NUM_TYPES] ||= [];
811 root 1.93
812 root 1.39 } elsif ($type eq "package") {
813 root 1.45 my $pkg = shift @arg;
814 root 1.39
815     while (my ($name, $id) = each %cb_id) {
816     if (my $cb = $pkg->can ($name)) {
817 root 1.93 _attach_cb $registry, $id, $prio, $cb;
818 root 1.39 }
819     }
820    
821     } elsif (exists $cb_id{$type}) {
822 root 1.93 _attach_cb $registry, $cb_id{$type}, $prio, shift @arg;
823 root 1.39
824     } elsif (ref $type) {
825     warn "attaching objects not supported, ignoring.\n";
826    
827     } else {
828 root 1.45 shift @arg;
829 root 1.39 warn "attach argument '$type' not supported, ignoring.\n";
830     }
831     }
832     }
833    
834 root 1.93 sub _object_attach {
835 root 1.48 my ($obj, $name, %arg) = @_;
836 root 1.46
837 root 1.55 return if exists $obj->{_attachment}{$name};
838    
839 root 1.46 if (my $attach = $attachment{$name}) {
840     my $registry = $obj->registry;
841    
842 root 1.47 for (@$attach) {
843     my ($klass, @attach) = @$_;
844 root 1.93 _attach $registry, $klass, @attach;
845 root 1.47 }
846 root 1.46
847 root 1.48 $obj->{$name} = \%arg;
848 root 1.46 } else {
849 root 1.280 warn "object uses attachment '$name' which is not available, postponing.\n";
850 root 1.46 }
851    
852 root 1.50 $obj->{_attachment}{$name} = undef;
853 root 1.46 }
854    
855 root 1.93 sub cf::attachable::attach {
856     if (ref $_[0]) {
857     _object_attach @_;
858     } else {
859     _attach shift->_attach_registry, @_;
860     }
861 root 1.267 _recalc_want;
862 root 1.55 };
863 root 1.46
864 root 1.54 # all those should be optimised
865 root 1.93 sub cf::attachable::detach {
866 root 1.54 my ($obj, $name) = @_;
867 root 1.46
868 root 1.93 if (ref $obj) {
869     delete $obj->{_attachment}{$name};
870     reattach ($obj);
871     } else {
872     Carp::croak "cannot, currently, detach class attachments";
873     }
874 root 1.267 _recalc_want;
875 root 1.55 };
876    
877 root 1.93 sub cf::attachable::attached {
878 root 1.55 my ($obj, $name) = @_;
879    
880     exists $obj->{_attachment}{$name}
881 root 1.39 }
882    
883 root 1.100 for my $klass (qw(ATTACHABLE GLOBAL OBJECT PLAYER CLIENT MAP)) {
884 root 1.93 eval "#line " . __LINE__ . " 'cf.pm'
885     sub cf::\L$klass\E::_attach_registry {
886     (\\\@CB_$klass, KLASS_$klass)
887     }
888 root 1.45
889 root 1.93 sub cf::\L$klass\E::attachment {
890     my \$name = shift;
891 root 1.39
892 root 1.93 \$attachment{\$name} = [[KLASS_$klass, \@_]];
893     }
894     ";
895     die if $@;
896 root 1.52 }
897    
898 root 1.39 our $override;
899 elmex 1.310 our @INVOKE_RESULTS = (); # referenced from .xs code. TODO: play tricks with reify and mortals?
900 root 1.39
901 root 1.45 sub override {
902     $override = 1;
903 elmex 1.310 @INVOKE_RESULTS = (@_);
904 root 1.39 }
905    
906 root 1.45 sub do_invoke {
907 root 1.39 my $event = shift;
908 root 1.40 my $callbacks = shift;
909 root 1.39
910 elmex 1.310 @INVOKE_RESULTS = ();
911 root 1.45
912 root 1.39 local $override;
913    
914 root 1.40 for (@$callbacks) {
915 root 1.39 eval { &{$_->[1]} };
916    
917     if ($@) {
918     warn "$@";
919 root 1.64 warn "... while processing $EVENT[$event][0](@_) event, skipping processing altogether.\n";
920 root 1.39 override;
921     }
922    
923     return 1 if $override;
924     }
925    
926     0
927     }
928    
929 root 1.96 =item $bool = cf::global::invoke (EVENT_CLASS_XXX, ...)
930 root 1.55
931 root 1.96 =item $bool = $attachable->invoke (EVENT_CLASS_XXX, ...)
932 root 1.55
933 root 1.96 Generate an object-specific event with the given arguments.
934 root 1.55
935 root 1.96 This API is preliminary (most likely, the EVENT_CLASS_xxx prefix will be
936 root 1.55 removed in future versions), and there is no public API to access override
937 elmex 1.310 results (if you must, access C<@cf::INVOKE_RESULTS> directly).
938 root 1.55
939     =back
940    
941 root 1.71 =cut
942    
943 root 1.70 #############################################################################
944 root 1.45 # object support
945 root 1.312 #
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 root 1.45
957 root 1.102 sub reattach {
958     # basically do the same as instantiate, without calling instantiate
959     my ($obj) = @_;
960    
961 root 1.169 bless $obj, ref $obj; # re-bless in case extensions have been reloaded
962    
963 root 1.102 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 root 1.100 cf::attachable->attach (
982     prio => -1000000,
983     on_instantiate => sub {
984     my ($obj, $data) = @_;
985 root 1.45
986 root 1.100 $data = from_json $data;
987 root 1.45
988 root 1.100 for (@$data) {
989     my ($name, $args) = @$_;
990 root 1.49
991 root 1.100 $obj->attach ($name, %{$args || {} });
992     }
993     },
994 root 1.102 on_reattach => \&reattach,
995 root 1.100 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 root 1.45
1007 root 1.46 sub object_freezer_save {
1008 root 1.59 my ($filename, $rdata, $objs) = @_;
1009 root 1.46
1010 root 1.105 sync_job {
1011     if (length $$rdata) {
1012     warn sprintf "saving %s (%d,%d)\n",
1013     $filename, length $$rdata, scalar @$objs;
1014 root 1.60
1015 root 1.105 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
1016 root 1.60 chmod SAVE_MODE, $fh;
1017 root 1.105 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
1018 root 1.204 aio_fsync $fh if $cf::USE_FSYNC;
1019 root 1.60 close $fh;
1020 root 1.105
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 root 1.204 aio_fsync $fh if $cf::USE_FSYNC;
1027 root 1.105 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 root 1.60 } else {
1036 root 1.105 warn "FATAL: $filename~: $!\n";
1037 root 1.60 }
1038 root 1.59 } else {
1039 root 1.105 aio_unlink $filename;
1040     aio_unlink "$filename.pst";
1041 root 1.59 }
1042 root 1.45 }
1043     }
1044    
1045 root 1.80 sub object_freezer_as_string {
1046     my ($rdata, $objs) = @_;
1047    
1048     use Data::Dumper;
1049    
1050 root 1.81 $$rdata . Dumper $objs
1051 root 1.80 }
1052    
1053 root 1.46 sub object_thawer_load {
1054     my ($filename) = @_;
1055    
1056 root 1.105 my ($data, $av);
1057 root 1.61
1058 root 1.105 (aio_load $filename, $data) >= 0
1059     or return;
1060 root 1.61
1061 root 1.105 unless (aio_stat "$filename.pst") {
1062     (aio_load "$filename.pst", $av) >= 0
1063     or return;
1064 root 1.113 $av = eval { (Storable::thaw $av)->{objs} };
1065 root 1.61 }
1066 root 1.45
1067 root 1.118 warn sprintf "loading %s (%d)\n",
1068 root 1.274 $filename, length $data, scalar @{$av || []};
1069 root 1.105 return ($data, $av);
1070 root 1.45 }
1071    
1072 root 1.281 =head2 COMMAND CALLBACKS
1073    
1074     =over 4
1075    
1076     =cut
1077    
1078 root 1.45 #############################################################################
1079 root 1.85 # command handling &c
1080 root 1.39
1081 root 1.85 =item cf::register_command $name => \&callback($ob,$args);
1082 root 1.1
1083 root 1.85 Register a callback for execution when the client sends the user command
1084     $name.
1085 root 1.5
1086 root 1.85 =cut
1087 root 1.5
1088 root 1.85 sub register_command {
1089     my ($name, $cb) = @_;
1090 root 1.5
1091 root 1.85 my $caller = caller;
1092     #warn "registering command '$name/$time' to '$caller'";
1093 root 1.1
1094 root 1.85 push @{ $COMMAND{$name} }, [$caller, $cb];
1095 root 1.1 }
1096    
1097 root 1.85 =item cf::register_extcmd $name => \&callback($pl,$packet);
1098 root 1.1
1099 root 1.287 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 root 1.1
1116 root 1.85 If the callback returns something, it is sent back as if reply was being
1117     called.
1118 root 1.1
1119 root 1.85 =cut
1120 root 1.1
1121 root 1.16 sub register_extcmd {
1122     my ($name, $cb) = @_;
1123    
1124 root 1.159 $EXTCMD{$name} = $cb;
1125 root 1.16 }
1126    
1127 root 1.287 sub register_exticmd {
1128     my ($name, $cb) = @_;
1129    
1130     $EXTICMD{$name} = $cb;
1131     }
1132    
1133 root 1.93 cf::player->attach (
1134 root 1.85 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 root 1.290 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) };
1150 root 1.85
1151     if (ref $msg) {
1152 root 1.316 my ($type, $reply, @payload) =
1153     "ARRAY" eq ref $msg
1154     ? @$msg
1155     : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
1156    
1157 root 1.338 my @reply;
1158    
1159 root 1.316 if (my $cb = $EXTCMD{$type}) {
1160 root 1.338 @reply = $cb->($pl, @payload);
1161     }
1162    
1163     $pl->ext_reply ($reply, @reply)
1164     if $reply;
1165 root 1.316
1166 root 1.85 } else {
1167     warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
1168     }
1169    
1170     cf::override;
1171     },
1172 root 1.93 );
1173 root 1.85
1174 root 1.278 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 root 1.1
1192 root 1.278 my %ext = (
1193     path => $path,
1194     base => $base,
1195     pkg => $pkg,
1196     );
1197 root 1.1
1198 root 1.279 $ext{meta} = { map { (split /=/, $_, 2)[0, 1] } split /\s+/, $1 }
1199     if $source =~ /\A#!.*?perl.*?#\s*(.*)$/m;
1200 root 1.1
1201 root 1.278 $ext{source} =
1202     "package $pkg; use strict; use utf8;\n"
1203     . "#line 1 \"$path\"\n{\n"
1204     . $source
1205     . "\n};\n1";
1206 root 1.1
1207 root 1.278 $todo{$base} = \%ext;
1208 root 1.166 }
1209 root 1.1
1210 root 1.278 my %done;
1211     while (%todo) {
1212     my $progress;
1213    
1214     while (my ($k, $v) = each %todo) {
1215 root 1.279 for (split /,\s*/, $v->{meta}{depends}) {
1216 root 1.278 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 root 1.279 : "$v->{base}: extension inactive.\n";
1225 root 1.278
1226     if (exists $v->{meta}{mandatory}) {
1227     warn $msg;
1228     warn "mandatory extension failed to load, exiting.\n";
1229     exit 1;
1230     }
1231    
1232 root 1.279 warn $msg;
1233 root 1.278 }
1234    
1235     $done{$k} = delete $todo{$k};
1236     push @EXTS, $v->{pkg};
1237 root 1.279 $progress = 1;
1238 root 1.278 }
1239 root 1.1
1240 root 1.278 skip:
1241     die "cannot load " . (join ", ", keys %todo) . ": unable to resolve dependencies\n"
1242     unless $progress;
1243     }
1244     };
1245 root 1.1 }
1246    
1247 root 1.8 #############################################################################
1248 root 1.70
1249 root 1.281 =back
1250    
1251 root 1.70 =head2 CORE EXTENSIONS
1252    
1253     Functions and methods that extend core crossfire objects.
1254    
1255 root 1.143 =cut
1256    
1257     package cf::player;
1258    
1259 root 1.154 use Coro::AIO;
1260    
1261 root 1.95 =head3 cf::player
1262    
1263 root 1.70 =over 4
1264 root 1.22
1265 root 1.143 =item cf::player::find $login
1266 root 1.23
1267 root 1.143 Returns the given player object, loading it if necessary (might block).
1268 root 1.23
1269     =cut
1270    
1271 root 1.145 sub playerdir($) {
1272 root 1.253 "$PLAYERDIR/"
1273 root 1.145 . (ref $_[0] ? $_[0]->ob->name : $_[0])
1274     }
1275    
1276 root 1.143 sub path($) {
1277 root 1.145 my $login = ref $_[0] ? $_[0]->ob->name : $_[0];
1278    
1279 root 1.234 (playerdir $login) . "/playerdata"
1280 root 1.143 }
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 root 1.180 or cf::sync_job { !aio_stat path $login }
1293 root 1.143 }
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 root 1.151 $cf::PLAYER{$_[0]} || do {
1302 root 1.234 # 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 root 1.151 my $pl = load_pl path $login
1310     or return;
1311     $cf::PLAYER{$login} = $pl
1312     }
1313     }
1314 root 1.143 }
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 root 1.146
1326 root 1.154 aio_mkdir playerdir $pl, 0770;
1327 root 1.143 $pl->{last_save} = $cf::RUNTIME;
1328    
1329     $pl->save_pl ($path);
1330 root 1.346 cf::cede_to_tick;
1331 root 1.143 }
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 root 1.23 }
1345    
1346 root 1.329 =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 root 1.154 =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 root 1.145 sub quit_character {
1364     my ($pl) = @_;
1365    
1366 root 1.220 my $name = $pl->ob->name;
1367    
1368 root 1.145 $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 root 1.154 aio_rename $path, $temp;
1379 root 1.150 delete $cf::PLAYER{$pl->ob->name};
1380     $pl->destroy;
1381 root 1.220
1382     my $prefix = qr<^~\Q$name\E/>;
1383    
1384     # nuke player maps
1385     $cf::MAP{$_}->nuke for grep /$prefix/, keys %cf::MAP;
1386    
1387 root 1.150 IO::AIO::aio_rmtree $temp;
1388 root 1.145 }
1389    
1390 pippijn 1.221 =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 root 1.154 =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 root 1.253 my $dirs = aio_readdir $PLAYERDIR
1413 root 1.154 or return [];
1414    
1415     my @logins;
1416    
1417     for my $login (@$dirs) {
1418     my $fh = aio_open path $login, Fcntl::O_RDONLY, 0 or next;
1419     aio_read $fh, 0, 512, my $buf, 0 or next;
1420 root 1.155 $buf !~ /^password -------------$/m or next; # official not-valid tag
1421 root 1.154
1422     utf8::decode $login;
1423     push @logins, $login;
1424     }
1425    
1426     \@logins
1427     }
1428    
1429     =item $player->maps
1430    
1431 root 1.166 Returns an arrayref of map paths that are private for this
1432 root 1.154 player. May block.
1433    
1434     =cut
1435    
1436     sub maps($) {
1437     my ($pl) = @_;
1438    
1439 root 1.201 $pl = ref $pl ? $pl->ob->name : $pl;
1440    
1441 root 1.154 my $files = aio_readdir playerdir $pl
1442     or return;
1443    
1444     my @paths;
1445    
1446     for (@$files) {
1447     utf8::decode $_;
1448     next if /\.(?:pl|pst)$/;
1449 root 1.158 next unless /^$PATH_SEP/o;
1450 root 1.154
1451 root 1.201 push @paths, cf::map::normalise "~$pl/$_";
1452 root 1.154 }
1453    
1454     \@paths
1455     }
1456    
1457 root 1.283 =item $protocol_xml = $player->expand_cfpod ($crossfire_pod)
1458    
1459     Expand crossfire pod fragments into protocol xml.
1460    
1461     =cut
1462    
1463     sub expand_cfpod {
1464     ((my $self), (local $_)) = @_;
1465    
1466     # escape & and <
1467     s/&/&amp;/g;
1468     s/(?<![BIUGH])</&lt;/g;
1469    
1470     # this is buggy, it needs to properly take care of nested <'s
1471    
1472     1 while
1473     # replace B<>, I<>, U<> etc.
1474     s/B<([^\>]*)>/<b>$1<\/b>/
1475     || s/I<([^\>]*)>/<i>$1<\/i>/
1476     || s/U<([^\>]*)>/<u>$1<\/u>/
1477     # replace G<male|female> tags
1478     || s{G<([^>|]*)\|([^>]*)>}{
1479     $self->gender ? $2 : $1
1480     }ge
1481     # replace H<hint text>
1482 root 1.291 || s{H<([^\>]*)>}
1483     {
1484     ("<fg name=\"lightblue\">[$1 (Use hintmode to suppress hints)]</fg>",
1485     "<fg name=\"lightblue\">[Hint suppressed, see hintmode]</fg>",
1486     "")
1487     [$self->{hintmode}]
1488     }ge;
1489 root 1.283
1490     # create single paragraphs (very hackish)
1491     s/(?<=\S)\n(?=\w)/ /g;
1492    
1493 root 1.291 # compress some whitespace
1494 root 1.295 s/\s+\n/\n/g; # ws line-ends
1495     s/\n\n+/\n/g; # double lines
1496     s/^\n+//; # beginning lines
1497     s/\n+$//; # ending lines
1498 root 1.293
1499 root 1.283 $_
1500     }
1501    
1502 root 1.291 sub hintmode {
1503     $_[0]{hintmode} = $_[1] if @_ > 1;
1504     $_[0]{hintmode}
1505     }
1506    
1507 root 1.316 =item $player->ext_reply ($msgid, @msg)
1508 root 1.95
1509     Sends an ext reply to the player.
1510    
1511     =cut
1512    
1513 root 1.316 sub ext_reply($$@) {
1514     my ($self, $id, @msg) = @_;
1515 root 1.95
1516 root 1.336 $self->ns->ext_reply ($id, @msg)
1517 root 1.95 }
1518    
1519 root 1.316 =item $player->ext_msg ($type, @msg)
1520 root 1.231
1521     Sends an ext event to the client.
1522    
1523     =cut
1524    
1525 root 1.316 sub ext_msg($$@) {
1526     my ($self, $type, @msg) = @_;
1527 root 1.231
1528 root 1.316 $self->ns->ext_msg ($type, @msg);
1529 root 1.231 }
1530    
1531 root 1.238 =head3 cf::region
1532    
1533     =over 4
1534    
1535     =cut
1536    
1537     package cf::region;
1538    
1539     =item cf::region::find_by_path $path
1540    
1541 root 1.281 Tries to decuce the likely region for a map knowing only its path.
1542 root 1.238
1543     =cut
1544    
1545     sub find_by_path($) {
1546     my ($path) = @_;
1547    
1548     my ($match, $specificity);
1549    
1550     for my $region (list) {
1551 root 1.304 if ($region->{match} && $path =~ $region->{match}) {
1552 root 1.238 ($match, $specificity) = ($region, $region->specificity)
1553     if $region->specificity > $specificity;
1554     }
1555     }
1556    
1557     $match
1558     }
1559 root 1.143
1560 root 1.95 =back
1561    
1562 root 1.110 =head3 cf::map
1563    
1564     =over 4
1565    
1566     =cut
1567    
1568     package cf::map;
1569    
1570     use Fcntl;
1571     use Coro::AIO;
1572    
1573 root 1.166 use overload
1574 root 1.173 '""' => \&as_string,
1575     fallback => 1;
1576 root 1.166
1577 root 1.133 our $MAX_RESET = 3600;
1578     our $DEFAULT_RESET = 3000;
1579 root 1.110
1580     sub generate_random_map {
1581 root 1.166 my ($self, $rmp) = @_;
1582 root 1.110 # mit "rum" bekleckern, nicht
1583 root 1.166 $self->_create_random_map (
1584 root 1.110 $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
1585     $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
1586     $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
1587     $rmp->{exit_on_final_map},
1588     $rmp->{xsize}, $rmp->{ysize},
1589     $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
1590     $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
1591     $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
1592     $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
1593     $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
1594 root 1.162 (cf::region::find $rmp->{region}), $rmp->{custom}
1595     )
1596 root 1.110 }
1597    
1598 root 1.187 =item cf::map->register ($regex, $prio)
1599    
1600     Register a handler for the map path matching the given regex at the
1601     givne priority (higher is better, built-in handlers have priority 0, the
1602     default).
1603    
1604     =cut
1605    
1606 root 1.166 sub register {
1607 root 1.187 my (undef, $regex, $prio) = @_;
1608 root 1.166 my $pkg = caller;
1609    
1610     no strict;
1611     push @{"$pkg\::ISA"}, __PACKAGE__;
1612    
1613 root 1.187 $EXT_MAP{$pkg} = [$prio, qr<$regex>];
1614 root 1.166 }
1615    
1616     # also paths starting with '/'
1617 root 1.187 $EXT_MAP{"cf::map"} = [0, qr{^(?=/)}];
1618 root 1.166
1619 root 1.170 sub thawer_merge {
1620 root 1.172 my ($self, $merge) = @_;
1621    
1622 root 1.170 # we have to keep some variables in memory intact
1623 root 1.172 local $self->{path};
1624     local $self->{load_path};
1625 root 1.170
1626 root 1.172 $self->SUPER::thawer_merge ($merge);
1627 root 1.170 }
1628    
1629 root 1.166 sub normalise {
1630     my ($path, $base) = @_;
1631    
1632 root 1.192 $path = "$path"; # make sure its a string
1633    
1634 root 1.199 $path =~ s/\.map$//;
1635    
1636 root 1.166 # map plan:
1637     #
1638     # /! non-realised random map exit (special hack!)
1639     # {... are special paths that are not being touched
1640     # ?xxx/... are special absolute paths
1641     # ?random/... random maps
1642     # /... normal maps
1643     # ~user/... per-player map of a specific user
1644    
1645     $path =~ s/$PATH_SEP/\//go;
1646    
1647     # treat it as relative path if it starts with
1648     # something that looks reasonable
1649     if ($path =~ m{^(?:\./|\.\./|\w)}) {
1650     $base or Carp::carp "normalise called with relative path and no base: '$path'";
1651    
1652     $base =~ s{[^/]+/?$}{};
1653     $path = "$base/$path";
1654     }
1655    
1656     for ($path) {
1657     redo if s{//}{/};
1658     redo if s{/\.?/}{/};
1659     redo if s{/[^/]+/\.\./}{/};
1660     }
1661    
1662     $path
1663     }
1664    
1665     sub new_from_path {
1666     my (undef, $path, $base) = @_;
1667    
1668     return $path if UNIVERSAL::isa $path, "cf::map"; # already a map object
1669    
1670     $path = normalise $path, $base;
1671    
1672 root 1.187 for my $pkg (sort { $EXT_MAP{$b}[0] <=> $EXT_MAP{$a}[0] } keys %EXT_MAP) {
1673     if ($path =~ $EXT_MAP{$pkg}[1]) {
1674 root 1.166 my $self = bless cf::map::new, $pkg;
1675     $self->{path} = $path; $self->path ($path);
1676     $self->init; # pass $1 etc.
1677     return $self;
1678     }
1679     }
1680    
1681 root 1.308 Carp::cluck "unable to resolve path '$path' (base '$base').";
1682 root 1.166 ()
1683     }
1684    
1685     sub init {
1686     my ($self) = @_;
1687    
1688     $self
1689     }
1690    
1691     sub as_string {
1692     my ($self) = @_;
1693    
1694     "$self->{path}"
1695     }
1696    
1697     # the displayed name, this is a one way mapping
1698     sub visible_name {
1699     &as_string
1700     }
1701    
1702     # the original (read-only) location
1703     sub load_path {
1704     my ($self) = @_;
1705    
1706 root 1.254 "$MAPDIR/$self->{path}.map"
1707 root 1.166 }
1708    
1709     # the temporary/swap location
1710     sub save_path {
1711     my ($self) = @_;
1712    
1713 root 1.167 (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/g;
1714 root 1.254 "$TMPDIR/$path.map"
1715 root 1.166 }
1716    
1717     # the unique path, undef == no special unique path
1718     sub uniq_path {
1719     my ($self) = @_;
1720    
1721 root 1.167 (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/g;
1722 root 1.253 "$UNIQUEDIR/$path"
1723 root 1.166 }
1724    
1725 root 1.110 # and all this just because we cannot iterate over
1726     # all maps in C++...
1727     sub change_all_map_light {
1728     my ($change) = @_;
1729    
1730 root 1.122 $_->change_map_light ($change)
1731     for grep $_->outdoor, values %cf::MAP;
1732 root 1.110 }
1733    
1734 root 1.275 sub decay_objects {
1735     my ($self) = @_;
1736    
1737     return if $self->{deny_reset};
1738    
1739     $self->do_decay_objects;
1740     }
1741    
1742 root 1.166 sub unlink_save {
1743     my ($self) = @_;
1744    
1745     utf8::encode (my $save = $self->save_path);
1746 root 1.170 IO::AIO::aioreq_pri 4; Coro::AIO::aio_unlink $save;
1747     IO::AIO::aioreq_pri 4; Coro::AIO::aio_unlink "$save.pst";
1748 root 1.166 }
1749    
1750     sub load_header_from($) {
1751     my ($self, $path) = @_;
1752 root 1.110
1753     utf8::encode $path;
1754 root 1.200 #aio_open $path, O_RDONLY, 0
1755     # or return;
1756 root 1.110
1757 root 1.166 $self->_load_header ($path)
1758 root 1.110 or return;
1759    
1760 root 1.166 $self->{load_path} = $path;
1761 root 1.135
1762 root 1.166 1
1763     }
1764 root 1.110
1765 root 1.188 sub load_header_orig {
1766 root 1.166 my ($self) = @_;
1767 root 1.110
1768 root 1.166 $self->load_header_from ($self->load_path)
1769 root 1.110 }
1770    
1771 root 1.188 sub load_header_temp {
1772 root 1.166 my ($self) = @_;
1773 root 1.110
1774 root 1.166 $self->load_header_from ($self->save_path)
1775     }
1776 root 1.110
1777 root 1.188 sub prepare_temp {
1778     my ($self) = @_;
1779    
1780     $self->last_access ((delete $self->{last_access})
1781     || $cf::RUNTIME); #d#
1782     # safety
1783     $self->{instantiate_time} = $cf::RUNTIME
1784     if $self->{instantiate_time} > $cf::RUNTIME;
1785     }
1786    
1787     sub prepare_orig {
1788     my ($self) = @_;
1789    
1790     $self->{load_original} = 1;
1791     $self->{instantiate_time} = $cf::RUNTIME;
1792     $self->last_access ($cf::RUNTIME);
1793     $self->instantiate;
1794     }
1795    
1796 root 1.166 sub load_header {
1797     my ($self) = @_;
1798 root 1.110
1799 root 1.188 if ($self->load_header_temp) {
1800     $self->prepare_temp;
1801 root 1.166 } else {
1802 root 1.188 $self->load_header_orig
1803 root 1.166 or return;
1804 root 1.188 $self->prepare_orig;
1805 root 1.166 }
1806 root 1.120
1807 root 1.275 $self->{deny_reset} = 1
1808     if $self->no_reset;
1809    
1810 root 1.238 $self->default_region (cf::region::find_by_path $self->{path})
1811     unless $self->default_region;
1812    
1813 root 1.166 1
1814     }
1815 root 1.110
1816 root 1.166 sub find;
1817     sub find {
1818     my ($path, $origin) = @_;
1819 root 1.134
1820 root 1.166 $path = normalise $path, $origin && $origin->path;
1821 root 1.110
1822 root 1.166 cf::lock_wait "map_find:$path";
1823 root 1.110
1824 root 1.166 $cf::MAP{$path} || do {
1825     my $guard = cf::lock_acquire "map_find:$path";
1826     my $map = new_from_path cf::map $path
1827     or return;
1828 root 1.110
1829 root 1.116 $map->{last_save} = $cf::RUNTIME;
1830 root 1.110
1831 root 1.166 $map->load_header
1832     or return;
1833 root 1.134
1834 root 1.275 if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?)
1835 root 1.185 # doing this can freeze the server in a sync job, obviously
1836     #$cf::WAIT_FOR_TICK->wait;
1837 root 1.112 $map->reset;
1838 root 1.123 undef $guard;
1839 root 1.192 return find $path;
1840 root 1.112 }
1841 root 1.110
1842 root 1.166 $cf::MAP{$path} = $map
1843 root 1.110 }
1844     }
1845    
1846 root 1.188 sub pre_load { }
1847     sub post_load { }
1848    
1849 root 1.110 sub load {
1850     my ($self) = @_;
1851    
1852 root 1.196 local $self->{deny_reset} = 1; # loading can take a long time
1853    
1854 root 1.120 my $path = $self->{path};
1855    
1856 root 1.256 {
1857 root 1.345 my $guard1 = cf::lock_acquire "map_data:$path";
1858     my $guard2 = cf::lock_acquire "map_load:$path";
1859 root 1.256
1860     return if $self->in_memory != cf::MAP_SWAPPED;
1861 root 1.110
1862 root 1.256 $self->in_memory (cf::MAP_LOADING);
1863 root 1.110
1864 root 1.256 $self->alloc;
1865 root 1.188
1866 root 1.256 $self->pre_load;
1867 root 1.346 cf::cede_to_tick;
1868 root 1.188
1869 root 1.256 $self->_load_objects ($self->{load_path}, 1)
1870     or return;
1871 root 1.110
1872 root 1.256 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
1873     if delete $self->{load_original};
1874 root 1.111
1875 root 1.256 if (my $uniq = $self->uniq_path) {
1876     utf8::encode $uniq;
1877     if (aio_open $uniq, O_RDONLY, 0) {
1878     $self->clear_unique_items;
1879     $self->_load_objects ($uniq, 0);
1880     }
1881 root 1.110 }
1882    
1883 root 1.346 cf::cede_to_tick;
1884 root 1.256 # now do the right thing for maps
1885     $self->link_multipart_objects;
1886 root 1.110 $self->difficulty ($self->estimate_difficulty)
1887     unless $self->difficulty;
1888 root 1.346 cf::cede_to_tick;
1889 root 1.256
1890     unless ($self->{deny_activate}) {
1891     $self->decay_objects;
1892     $self->fix_auto_apply;
1893     $self->update_buttons;
1894 root 1.346 cf::cede_to_tick;
1895 root 1.256 $self->set_darkness_map;
1896 root 1.346 cf::cede_to_tick;
1897 root 1.256 $self->activate;
1898     }
1899    
1900 root 1.325 $self->{last_save} = $cf::RUNTIME;
1901     $self->last_access ($cf::RUNTIME);
1902 root 1.324
1903 root 1.256 $self->in_memory (cf::MAP_IN_MEMORY);
1904 root 1.110 }
1905    
1906 root 1.188 $self->post_load;
1907 root 1.166 }
1908    
1909     sub customise_for {
1910     my ($self, $ob) = @_;
1911    
1912     return find "~" . $ob->name . "/" . $self->{path}
1913     if $self->per_player;
1914 root 1.134
1915 root 1.275 # return find "?party/" . $ob->name . "/" . $self->{path}
1916     # if $self->per_party;
1917    
1918 root 1.166 $self
1919 root 1.110 }
1920    
1921 root 1.157 # find and load all maps in the 3x3 area around a map
1922 root 1.333 sub load_neighbours {
1923 root 1.157 my ($map) = @_;
1924    
1925 root 1.333 my @neigh; # diagonal neighbours
1926 root 1.157
1927     for (0 .. 3) {
1928     my $neigh = $map->tile_path ($_)
1929     or next;
1930     $neigh = find $neigh, $map
1931     or next;
1932     $neigh->load;
1933    
1934 root 1.333 push @neigh,
1935     [$neigh->tile_path (($_ + 3) % 4), $neigh],
1936     [$neigh->tile_path (($_ + 1) % 4), $neigh];
1937 root 1.157 }
1938    
1939 root 1.333 for (grep defined $_->[0], @neigh) {
1940     my ($path, $origin) = @$_;
1941     my $neigh = find $path, $origin
1942 root 1.157 or next;
1943     $neigh->load;
1944     }
1945     }
1946    
1947 root 1.133 sub find_sync {
1948 root 1.110 my ($path, $origin) = @_;
1949    
1950 root 1.157 cf::sync_job { find $path, $origin }
1951 root 1.133 }
1952    
1953     sub do_load_sync {
1954     my ($map) = @_;
1955 root 1.110
1956 root 1.339 cf::LOG cf::llevDebug | cf::logBacktrace, "do_load_sync"
1957 root 1.342 if $Coro::current == $Coro::main;
1958 root 1.339
1959 root 1.133 cf::sync_job { $map->load };
1960 root 1.110 }
1961    
1962 root 1.157 our %MAP_PREFETCH;
1963 root 1.183 our $MAP_PREFETCHER = undef;
1964 root 1.157
1965     sub find_async {
1966 root 1.339 my ($path, $origin, $load) = @_;
1967 root 1.157
1968 root 1.166 $path = normalise $path, $origin && $origin->{path};
1969 root 1.157
1970 root 1.166 if (my $map = $cf::MAP{$path}) {
1971 root 1.340 return $map if !$load || $map->in_memory == cf::MAP_IN_MEMORY;
1972 root 1.157 }
1973    
1974 root 1.339 $MAP_PREFETCH{$path} |= $load;
1975    
1976 root 1.183 $MAP_PREFETCHER ||= cf::async {
1977     while (%MAP_PREFETCH) {
1978 root 1.339 while (my ($k, $v) = each %MAP_PREFETCH) {
1979     if (my $map = find $k) {
1980     $map->load if $v;
1981 root 1.308 }
1982 root 1.183
1983 root 1.339 delete $MAP_PREFETCH{$k};
1984 root 1.183 }
1985     }
1986     undef $MAP_PREFETCHER;
1987     };
1988 root 1.189 $MAP_PREFETCHER->prio (6);
1989 root 1.157
1990     ()
1991     }
1992    
1993 root 1.110 sub save {
1994     my ($self) = @_;
1995    
1996 root 1.345 my $lock = cf::lock_acquire "map_data:$self->{path}";
1997 root 1.137
1998 root 1.110 $self->{last_save} = $cf::RUNTIME;
1999    
2000     return unless $self->dirty;
2001    
2002 root 1.166 my $save = $self->save_path; utf8::encode $save;
2003     my $uniq = $self->uniq_path; utf8::encode $uniq;
2004 root 1.117
2005 root 1.110 $self->{load_path} = $save;
2006    
2007     return if $self->{deny_save};
2008    
2009 root 1.132 local $self->{last_access} = $self->last_access;#d#
2010    
2011 root 1.143 cf::async {
2012     $_->contr->save for $self->players;
2013     };
2014    
2015 root 1.110 if ($uniq) {
2016 root 1.166 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
2017     $self->_save_objects ($uniq, cf::IO_UNIQUES);
2018 root 1.110 } else {
2019 root 1.166 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
2020 root 1.110 }
2021     }
2022    
2023     sub swap_out {
2024     my ($self) = @_;
2025    
2026 root 1.130 # save first because save cedes
2027     $self->save;
2028    
2029 root 1.345 my $lock = cf::lock_acquire "map_data:$self->{path}";
2030 root 1.137
2031 root 1.110 return if $self->players;
2032     return if $self->in_memory != cf::MAP_IN_MEMORY;
2033     return if $self->{deny_save};
2034    
2035     $self->clear;
2036     $self->in_memory (cf::MAP_SWAPPED);
2037     }
2038    
2039 root 1.112 sub reset_at {
2040     my ($self) = @_;
2041 root 1.110
2042     # TODO: safety, remove and allow resettable per-player maps
2043 root 1.114 return 1e99 if $self->{deny_reset};
2044 root 1.110
2045 root 1.112 my $time = $self->fixed_resettime ? $self->{instantiate_time} : $self->last_access;
2046 root 1.114 my $to = List::Util::min $MAX_RESET, $self->reset_timeout || $DEFAULT_RESET;
2047 root 1.110
2048 root 1.112 $time + $to
2049     }
2050    
2051     sub should_reset {
2052     my ($self) = @_;
2053    
2054     $self->reset_at <= $cf::RUNTIME
2055 root 1.111 }
2056    
2057 root 1.110 sub reset {
2058     my ($self) = @_;
2059    
2060 root 1.167 my $lock = cf::lock_acquire "map_data:$self->{path}";
2061 root 1.137
2062 root 1.110 return if $self->players;
2063    
2064 root 1.274 warn "resetting map ", $self->path;
2065 root 1.110
2066 root 1.210 $self->in_memory (cf::MAP_SWAPPED);
2067    
2068     # need to save uniques path
2069     unless ($self->{deny_save}) {
2070     my $uniq = $self->uniq_path; utf8::encode $uniq;
2071    
2072     $self->_save_objects ($uniq, cf::IO_UNIQUES)
2073     if $uniq;
2074     }
2075    
2076 root 1.111 delete $cf::MAP{$self->path};
2077 root 1.110
2078 root 1.167 $self->clear;
2079    
2080 root 1.110 $_->clear_links_to ($self) for values %cf::MAP;
2081    
2082 root 1.166 $self->unlink_save;
2083 root 1.111 $self->destroy;
2084 root 1.110 }
2085    
2086 root 1.114 my $nuke_counter = "aaaa";
2087    
2088     sub nuke {
2089     my ($self) = @_;
2090    
2091 root 1.349 {
2092     my $lock = cf::lock_acquire "map_data:$self->{path}";
2093    
2094     delete $cf::MAP{$self->path};
2095 root 1.174
2096 root 1.349 bless $self, "cf::map";
2097     delete $self->{deny_reset};
2098     $self->{deny_save} = 1;
2099     $self->reset_timeout (1);
2100     $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++));
2101 root 1.174
2102 root 1.349 $self->unlink_save;
2103 root 1.174
2104 root 1.349 $cf::MAP{$self->path} = $self;
2105     }
2106 root 1.174
2107 root 1.114 $self->reset; # polite request, might not happen
2108     }
2109    
2110 root 1.276 =item $maps = cf::map::tmp_maps
2111    
2112     Returns an arrayref with all map paths of currently instantiated and saved
2113 root 1.277 maps. May block.
2114 root 1.276
2115     =cut
2116    
2117     sub tmp_maps() {
2118     [
2119     map {
2120     utf8::decode $_;
2121 root 1.277 /\.map$/
2122 root 1.276 ? normalise $_
2123     : ()
2124     } @{ aio_readdir $TMPDIR or [] }
2125     ]
2126     }
2127    
2128 root 1.277 =item $maps = cf::map::random_maps
2129    
2130     Returns an arrayref with all map paths of currently instantiated and saved
2131     random maps. May block.
2132    
2133     =cut
2134    
2135     sub random_maps() {
2136     [
2137     map {
2138     utf8::decode $_;
2139     /\.map$/
2140     ? normalise "?random/$_"
2141     : ()
2142     } @{ aio_readdir $RANDOMDIR or [] }
2143     ]
2144     }
2145    
2146 root 1.158 =item cf::map::unique_maps
2147    
2148 root 1.166 Returns an arrayref of paths of all shared maps that have
2149 root 1.158 instantiated unique items. May block.
2150    
2151     =cut
2152    
2153     sub unique_maps() {
2154 root 1.276 [
2155     map {
2156     utf8::decode $_;
2157 root 1.277 /\.map$/
2158 root 1.276 ? normalise $_
2159     : ()
2160     } @{ aio_readdir $UNIQUEDIR or [] }
2161     ]
2162 root 1.158 }
2163    
2164 root 1.155 package cf;
2165    
2166     =back
2167    
2168     =head3 cf::object
2169    
2170     =cut
2171    
2172     package cf::object;
2173    
2174     =over 4
2175    
2176     =item $ob->inv_recursive
2177 root 1.110
2178 root 1.155 Returns the inventory of the object _and_ their inventories, recursively.
2179 root 1.110
2180 root 1.155 =cut
2181 root 1.144
2182 root 1.155 sub inv_recursive_;
2183     sub inv_recursive_ {
2184     map { $_, inv_recursive_ $_->inv } @_
2185     }
2186 root 1.110
2187 root 1.155 sub inv_recursive {
2188     inv_recursive_ inv $_[0]
2189 root 1.110 }
2190    
2191     package cf;
2192    
2193     =back
2194    
2195 root 1.95 =head3 cf::object::player
2196    
2197     =over 4
2198    
2199 root 1.79 =item $player_object->reply ($npc, $msg[, $flags])
2200 root 1.28
2201     Sends a message to the player, as if the npc C<$npc> replied. C<$npc>
2202     can be C<undef>. Does the right thing when the player is currently in a
2203     dialogue with the given NPC character.
2204    
2205     =cut
2206    
2207 root 1.22 # rough implementation of a future "reply" method that works
2208     # with dialog boxes.
2209 root 1.95 #TODO: the first argument must go, split into a $npc->reply_to ( method
2210 root 1.23 sub cf::object::player::reply($$$;$) {
2211     my ($self, $npc, $msg, $flags) = @_;
2212    
2213     $flags = cf::NDI_BROWN | cf::NDI_UNIQUE unless @_ >= 4;
2214 root 1.22
2215 root 1.24 if ($self->{record_replies}) {
2216     push @{ $self->{record_replies} }, [$npc, $msg, $flags];
2217 elmex 1.282
2218 root 1.24 } else {
2219 elmex 1.282 my $pl = $self->contr;
2220    
2221     if ($pl->{npc_dialog} && $pl->{npc_dialog}->{id}) {
2222 root 1.316 my $dialog = $pl->{npc_dialog};
2223     $dialog->{pl}->ext_msg ($dialog->{id}, update => msg => $dialog->{pl}->expand_cfpod ($msg));
2224 elmex 1.282
2225     } else {
2226     $msg = $npc->name . " says: $msg" if $npc;
2227     $self->message ($msg, $flags);
2228     }
2229 root 1.24 }
2230 root 1.22 }
2231    
2232 root 1.329 =item $object->send_msg ($channel, $msg, $color, [extra...])
2233    
2234     =cut
2235    
2236     sub cf::object::send_msg {
2237     my $pl = shift->contr
2238     or return;
2239     $pl->send_msg (@_);
2240     }
2241    
2242 root 1.79 =item $player_object->may ("access")
2243    
2244     Returns wether the given player is authorized to access resource "access"
2245     (e.g. "command_wizcast").
2246    
2247     =cut
2248    
2249     sub cf::object::player::may {
2250     my ($self, $access) = @_;
2251    
2252     $self->flag (cf::FLAG_WIZ) ||
2253     (ref $cf::CFG{"may_$access"}
2254     ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}}
2255     : $cf::CFG{"may_$access"})
2256     }
2257 root 1.70
2258 root 1.115 =item $player_object->enter_link
2259    
2260     Freezes the player and moves him/her to a special map (C<{link}>).
2261    
2262 root 1.166 The player should be reasonably safe there for short amounts of time. You
2263 root 1.115 I<MUST> call C<leave_link> as soon as possible, though.
2264    
2265 root 1.166 Will never block.
2266    
2267 root 1.115 =item $player_object->leave_link ($map, $x, $y)
2268    
2269 root 1.166 Moves the player out of the special C<{link}> map onto the specified
2270     map. If the map is not valid (or omitted), the player will be moved back
2271     to the location he/she was before the call to C<enter_link>, or, if that
2272     fails, to the emergency map position.
2273 root 1.115
2274     Might block.
2275    
2276     =cut
2277    
2278 root 1.166 sub link_map {
2279     unless ($LINK_MAP) {
2280     $LINK_MAP = cf::map::find "{link}"
2281 root 1.196 or cf::cleanup "FATAL: unable to provide {link} map, exiting.";
2282 root 1.166 $LINK_MAP->load;
2283     }
2284    
2285     $LINK_MAP
2286     }
2287    
2288 root 1.110 sub cf::object::player::enter_link {
2289     my ($self) = @_;
2290    
2291 root 1.259 $self->deactivate_recursive;
2292 root 1.258
2293 root 1.166 return if UNIVERSAL::isa $self->map, "ext::map_link";
2294 root 1.110
2295 root 1.120 $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y]
2296 root 1.270 if $self->map && $self->map->{path} ne "{link}";
2297 root 1.110
2298 root 1.194 $self->enter_map ($LINK_MAP || link_map, 10, 10);
2299 root 1.110 }
2300    
2301     sub cf::object::player::leave_link {
2302     my ($self, $map, $x, $y) = @_;
2303    
2304 root 1.270 return unless $self->contr->active;
2305    
2306 root 1.110 my $link_pos = delete $self->{_link_pos};
2307    
2308     unless ($map) {
2309     # restore original map position
2310     ($map, $x, $y) = @{ $link_pos || [] };
2311 root 1.133 $map = cf::map::find $map;
2312 root 1.110
2313     unless ($map) {
2314     ($map, $x, $y) = @$EMERGENCY_POSITION;
2315 root 1.133 $map = cf::map::find $map
2316 root 1.110 or die "FATAL: cannot load emergency map\n";
2317     }
2318     }
2319    
2320     ($x, $y) = (-1, -1)
2321     unless (defined $x) && (defined $y);
2322    
2323     # use -1 or undef as default coordinates, not 0, 0
2324     ($x, $y) = ($map->enter_x, $map->enter_y)
2325     if $x <=0 && $y <= 0;
2326    
2327     $map->load;
2328 root 1.333 $map->load_neighbours;
2329 root 1.110
2330 root 1.143 return unless $self->contr->active;
2331 root 1.110 $self->activate_recursive;
2332 root 1.215
2333     local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext
2334 root 1.110 $self->enter_map ($map, $x, $y);
2335     }
2336    
2337 root 1.306 =item $player_object->goto ($path, $x, $y[, $check->($map)[, $done->()]])
2338 root 1.268
2339     Moves the player to the given map-path and coordinates by first freezing
2340     her, loading and preparing them map, calling the provided $check callback
2341     that has to return the map if sucecssful, and then unfreezes the player on
2342 root 1.306 the new (success) or old (failed) map position. In either case, $done will
2343     be called at the end of this process.
2344 root 1.110
2345     =cut
2346    
2347 root 1.270 our $GOTOGEN;
2348    
2349 root 1.136 sub cf::object::player::goto {
2350 root 1.306 my ($self, $path, $x, $y, $check, $done) = @_;
2351 root 1.268
2352 root 1.270 # do generation counting so two concurrent goto's will be executed in-order
2353     my $gen = $self->{_goto_generation} = ++$GOTOGEN;
2354    
2355 root 1.110 $self->enter_link;
2356    
2357 root 1.140 (async {
2358 root 1.197 my $map = eval {
2359     my $map = cf::map::find $path;
2360 root 1.268
2361     if ($map) {
2362     $map = $map->customise_for ($self);
2363     $map = $check->($map) if $check && $map;
2364     } else {
2365     $self->message ("The exit to '$path' is closed", cf::NDI_UNIQUE | cf::NDI_RED);
2366     }
2367    
2368 root 1.197 $map
2369 root 1.268 };
2370    
2371     if ($@) {
2372     $self->message ("Something went wrong within the server, please report this incident!", cf::NDI_UNIQUE | cf::NDI_RED);
2373     LOG llevError | logBacktrace, Carp::longmess $@;
2374     }
2375 root 1.115
2376 root 1.270 if ($gen == $self->{_goto_generation}) {
2377     delete $self->{_goto_generation};
2378     $self->leave_link ($map, $x, $y);
2379     }
2380 root 1.306
2381     $done->() if $done;
2382 root 1.110 })->prio (1);
2383     }
2384    
2385     =item $player_object->enter_exit ($exit_object)
2386    
2387     =cut
2388    
2389     sub parse_random_map_params {
2390     my ($spec) = @_;
2391    
2392     my $rmp = { # defaults
2393 root 1.181 xsize => (cf::rndm 15, 40),
2394     ysize => (cf::rndm 15, 40),
2395     symmetry => (cf::rndm 1, cf::SYMMETRY_XY),
2396 root 1.182 #layout => string,
2397 root 1.110 };
2398    
2399     for (split /\n/, $spec) {
2400     my ($k, $v) = split /\s+/, $_, 2;
2401    
2402     $rmp->{lc $k} = $v if (length $k) && (length $v);
2403     }
2404    
2405     $rmp
2406     }
2407    
2408     sub prepare_random_map {
2409     my ($exit) = @_;
2410    
2411 root 1.179 my $guard = cf::lock_acquire "exit_prepare:$exit";
2412    
2413 root 1.110 # all this does is basically replace the /! path by
2414     # a new random map path (?random/...) with a seed
2415     # that depends on the exit object
2416    
2417     my $rmp = parse_random_map_params $exit->msg;
2418    
2419     if ($exit->map) {
2420 root 1.198 $rmp->{region} = $exit->region->name;
2421 root 1.110 $rmp->{origin_map} = $exit->map->path;
2422     $rmp->{origin_x} = $exit->x;
2423     $rmp->{origin_y} = $exit->y;
2424     }
2425    
2426     $rmp->{random_seed} ||= $exit->random_seed;
2427    
2428     my $data = cf::to_json $rmp;
2429     my $md5 = Digest::MD5::md5_hex $data;
2430 root 1.253 my $meta = "$RANDOMDIR/$md5.meta";
2431 root 1.110
2432 root 1.177 if (my $fh = aio_open "$meta~", O_WRONLY | O_CREAT, 0666) {
2433 root 1.110 aio_write $fh, 0, (length $data), $data, 0;
2434 root 1.177 undef $fh;
2435     aio_rename "$meta~", $meta;
2436 root 1.110
2437     $exit->slaying ("?random/$md5");
2438     $exit->msg (undef);
2439     }
2440     }
2441    
2442     sub cf::object::player::enter_exit {
2443     my ($self, $exit) = @_;
2444    
2445     return unless $self->type == cf::PLAYER;
2446    
2447 root 1.195 if ($exit->slaying eq "/!") {
2448     #TODO: this should de-fi-ni-te-ly not be a sync-job
2449 root 1.233 # the problem is that $exit might not survive long enough
2450     # so it needs to be done right now, right here
2451 root 1.195 cf::sync_job { prepare_random_map $exit };
2452     }
2453    
2454     my $slaying = cf::map::normalise $exit->slaying, $exit->map && $exit->map->path;
2455     my $hp = $exit->stats->hp;
2456     my $sp = $exit->stats->sp;
2457    
2458 root 1.110 $self->enter_link;
2459    
2460 root 1.296 # if exit is damned, update players death & WoR home-position
2461     $self->contr->savebed ($slaying, $hp, $sp)
2462     if $exit->flag (FLAG_DAMNED);
2463    
2464 root 1.140 (async {
2465 root 1.133 $self->deactivate_recursive; # just to be sure
2466 root 1.110 unless (eval {
2467 root 1.195 $self->goto ($slaying, $hp, $sp);
2468 root 1.110
2469     1;
2470     }) {
2471     $self->message ("Something went wrong deep within the crossfire server. "
2472 root 1.233 . "I'll try to bring you back to the map you were before. "
2473     . "Please report this to the dungeon master!",
2474     cf::NDI_UNIQUE | cf::NDI_RED);
2475 root 1.110
2476     warn "ERROR in enter_exit: $@";
2477     $self->leave_link;
2478     }
2479     })->prio (1);
2480     }
2481    
2482 root 1.95 =head3 cf::client
2483    
2484     =over 4
2485    
2486     =item $client->send_drawinfo ($text, $flags)
2487    
2488     Sends a drawinfo packet to the client. Circumvents output buffering so
2489     should not be used under normal circumstances.
2490    
2491 root 1.70 =cut
2492    
2493 root 1.95 sub cf::client::send_drawinfo {
2494     my ($self, $text, $flags) = @_;
2495    
2496     utf8::encode $text;
2497 root 1.225 $self->send_packet (sprintf "drawinfo %d %s", $flags || cf::NDI_BLACK, $text);
2498 root 1.95 }
2499    
2500 root 1.311 =item $client->send_msg ($channel, $msg, $color, [extra...])
2501 root 1.283
2502     Send a drawinfo or msg packet to the client, formatting the msg for the
2503     client if neccessary. C<$type> should be a string identifying the type of
2504     the message, with C<log> being the default. If C<$color> is negative, suppress
2505     the message unless the client supports the msg packet.
2506    
2507     =cut
2508    
2509     sub cf::client::send_msg {
2510 root 1.311 my ($self, $channel, $msg, $color, @extra) = @_;
2511 root 1.283
2512     $msg = $self->pl->expand_cfpod ($msg);
2513    
2514 root 1.323 $color &= cf::NDI_CLIENT_MASK; # just in case...
2515 root 1.311
2516     if (ref $channel) {
2517     # send meta info to client, if not yet sent
2518     unless (exists $self->{channel}{$channel->{id}}) {
2519     $self->{channel}{$channel->{id}} = $channel;
2520 root 1.319 $self->ext_msg (channel_info => $channel);
2521 root 1.311 }
2522    
2523     $channel = $channel->{id};
2524     }
2525    
2526 root 1.313 return unless @extra || length $msg;
2527    
2528 root 1.283 if ($self->can_msg) {
2529 root 1.323 # default colour, mask it out
2530     $color &= ~(cf::NDI_COLOR_MASK | cf::NDI_DEF)
2531     if $color & cf::NDI_DEF;
2532    
2533     $self->send_packet ("msg " . $self->{json_coder}->encode (
2534     [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra]));
2535 root 1.283 } else {
2536 root 1.323 if ($color >= 0) {
2537     # replace some tags by gcfclient-compatible ones
2538     for ($msg) {
2539     1 while
2540     s/<b>([^<]*)<\/b>/[b]${1}[\/b]/
2541     || s/<i>([^<]*)<\/i>/[i]${1}[\/i]/
2542     || s/<u>([^<]*)<\/u>/[ul]${1}[\/ul]/
2543     || s/<tt>([^<]*)<\/tt>/[fixed]${1}[\/fixed]/
2544     || s/<fg name=\"([^"]+)\">([^<]*)<\/fg>/[color=$1]${2}[\/color]/;
2545     }
2546    
2547     $color &= cf::NDI_COLOR_MASK;
2548 root 1.283
2549 root 1.327 utf8::encode $msg;
2550    
2551 root 1.284 if (0 && $msg =~ /\[/) {
2552 root 1.331 # COMMAND/INFO
2553     $self->send_packet ("drawextinfo $color 10 8 $msg")
2554 root 1.283 } else {
2555 root 1.286 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g;
2556 root 1.283 $self->send_packet ("drawinfo $color $msg")
2557     }
2558     }
2559     }
2560     }
2561    
2562 root 1.316 =item $client->ext_msg ($type, @msg)
2563 root 1.232
2564 root 1.287 Sends an ext event to the client.
2565 root 1.232
2566     =cut
2567    
2568 root 1.316 sub cf::client::ext_msg($$@) {
2569     my ($self, $type, @msg) = @_;
2570 root 1.232
2571 root 1.343 if ($self->extcmd == 2) {
2572 root 1.316 $self->send_packet ("ext " . $self->{json_coder}->encode ([$type, @msg]));
2573 root 1.343 } elsif ($self->extcmd == 1) { # TODO: remove
2574 root 1.316 push @msg, msgtype => "event_$type";
2575     $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2576     }
2577 root 1.232 }
2578 root 1.95
2579 root 1.336 =item $client->ext_reply ($msgid, @msg)
2580    
2581     Sends an ext reply to the client.
2582    
2583     =cut
2584    
2585     sub cf::client::ext_reply($$@) {
2586     my ($self, $id, @msg) = @_;
2587    
2588     if ($self->extcmd == 2) {
2589     $self->send_packet ("ext " . $self->{json_coder}->encode (["reply-$id", @msg]));
2590 root 1.343 } elsif ($self->extcmd == 1) {
2591 root 1.336 #TODO: version 1, remove
2592     unshift @msg, msgtype => "reply", msgid => $id;
2593     $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2594     }
2595     }
2596    
2597 root 1.95 =item $success = $client->query ($flags, "text", \&cb)
2598    
2599     Queues a query to the client, calling the given callback with
2600     the reply text on a reply. flags can be C<cf::CS_QUERY_YESNO>,
2601     C<cf::CS_QUERY_SINGLECHAR> or C<cf::CS_QUERY_HIDEINPUT> or C<0>.
2602    
2603 root 1.287 Queries can fail, so check the return code. Or don't, as queries will
2604     become reliable at some point in the future.
2605 root 1.95
2606     =cut
2607    
2608     sub cf::client::query {
2609     my ($self, $flags, $text, $cb) = @_;
2610    
2611     return unless $self->state == ST_PLAYING
2612     || $self->state == ST_SETUP
2613     || $self->state == ST_CUSTOM;
2614    
2615     $self->state (ST_CUSTOM);
2616    
2617     utf8::encode $text;
2618     push @{ $self->{query_queue} }, [(sprintf "query %d %s", $flags, $text), $cb];
2619    
2620     $self->send_packet ($self->{query_queue}[0][0])
2621     if @{ $self->{query_queue} } == 1;
2622 root 1.287
2623     1
2624 root 1.95 }
2625    
2626     cf::client->attach (
2627 root 1.290 on_connect => sub {
2628     my ($ns) = @_;
2629    
2630     $ns->{json_coder} = JSON::XS->new->utf8->max_size (1e6)->convert_blessed;
2631     },
2632 root 1.95 on_reply => sub {
2633     my ($ns, $msg) = @_;
2634    
2635     # this weird shuffling is so that direct followup queries
2636     # get handled first
2637 root 1.128 my $queue = delete $ns->{query_queue}
2638 root 1.129 or return; # be conservative, not sure how that can happen, but we saw a crash here
2639 root 1.95
2640     (shift @$queue)->[1]->($msg);
2641 root 1.202 return unless $ns->valid; # temporary(?) workaround for callback destroying socket
2642 root 1.95
2643     push @{ $ns->{query_queue} }, @$queue;
2644    
2645     if (@{ $ns->{query_queue} } == @$queue) {
2646     if (@$queue) {
2647     $ns->send_packet ($ns->{query_queue}[0][0]);
2648     } else {
2649 root 1.98 $ns->state (ST_PLAYING) if $ns->state == ST_CUSTOM;
2650 root 1.95 }
2651     }
2652     },
2653 root 1.287 on_exticmd => sub {
2654     my ($ns, $buf) = @_;
2655    
2656 root 1.290 my $msg = eval { $ns->{json_coder}->decode ($buf) };
2657 root 1.287
2658     if (ref $msg) {
2659 root 1.316 my ($type, $reply, @payload) =
2660     "ARRAY" eq ref $msg
2661     ? @$msg
2662     : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
2663    
2664 root 1.338 my @reply;
2665    
2666 root 1.316 if (my $cb = $EXTICMD{$type}) {
2667 root 1.338 @reply = $cb->($ns, @payload);
2668     }
2669    
2670     $ns->ext_reply ($reply, @reply)
2671     if $reply;
2672 root 1.316
2673 root 1.287 } else {
2674     warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n";
2675     }
2676    
2677     cf::override;
2678     },
2679 root 1.95 );
2680    
2681 root 1.140 =item $client->async (\&cb)
2682 root 1.96
2683     Create a new coroutine, running the specified callback. The coroutine will
2684     be automatically cancelled when the client gets destroyed (e.g. on logout,
2685     or loss of connection).
2686    
2687     =cut
2688    
2689 root 1.140 sub cf::client::async {
2690 root 1.96 my ($self, $cb) = @_;
2691    
2692 root 1.140 my $coro = &Coro::async ($cb);
2693 root 1.103
2694     $coro->on_destroy (sub {
2695 root 1.96 delete $self->{_coro}{$coro+0};
2696 root 1.103 });
2697 root 1.96
2698     $self->{_coro}{$coro+0} = $coro;
2699 root 1.103
2700     $coro
2701 root 1.96 }
2702    
2703     cf::client->attach (
2704     on_destroy => sub {
2705     my ($ns) = @_;
2706    
2707 root 1.97 $_->cancel for values %{ (delete $ns->{_coro}) || {} };
2708 root 1.96 },
2709     );
2710    
2711 root 1.95 =back
2712    
2713 root 1.70
2714     =head2 SAFE SCRIPTING
2715    
2716     Functions that provide a safe environment to compile and execute
2717     snippets of perl code without them endangering the safety of the server
2718     itself. Looping constructs, I/O operators and other built-in functionality
2719     is not available in the safe scripting environment, and the number of
2720 root 1.79 functions and methods that can be called is greatly reduced.
2721 root 1.70
2722     =cut
2723 root 1.23
2724 root 1.42 our $safe = new Safe "safe";
2725 root 1.23 our $safe_hole = new Safe::Hole;
2726    
2727     $SIG{FPE} = 'IGNORE';
2728    
2729 root 1.328 $safe->permit_only (Opcode::opset qw(
2730     :base_core :base_mem :base_orig :base_math
2731     grepstart grepwhile mapstart mapwhile
2732     sort time
2733     ));
2734 root 1.23
2735 root 1.25 # here we export the classes and methods available to script code
2736    
2737 root 1.70 =pod
2738    
2739 root 1.228 The following functions and methods are available within a safe environment:
2740 root 1.70
2741 root 1.297 cf::object
2742     contr pay_amount pay_player map x y force_find force_add
2743 elmex 1.341 insert remove name archname title slaying race decrease_ob_nr
2744 root 1.297
2745     cf::object::player
2746     player
2747    
2748     cf::player
2749     peaceful
2750    
2751     cf::map
2752     trigger
2753 root 1.70
2754     =cut
2755    
2756 root 1.25 for (
2757 root 1.297 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y
2758 elmex 1.341 insert remove inv name archname title slaying race
2759     decrease_ob_nr)],
2760 root 1.25 ["cf::object::player" => qw(player)],
2761     ["cf::player" => qw(peaceful)],
2762 elmex 1.91 ["cf::map" => qw(trigger)],
2763 root 1.25 ) {
2764     no strict 'refs';
2765     my ($pkg, @funs) = @$_;
2766 root 1.41 *{"safe::$pkg\::$_"} = $safe_hole->wrap (\&{"$pkg\::$_"})
2767 root 1.25 for @funs;
2768     }
2769 root 1.23
2770 root 1.70 =over 4
2771    
2772     =item @retval = safe_eval $code, [var => value, ...]
2773    
2774     Compiled and executes the given perl code snippet. additional var/value
2775     pairs result in temporary local (my) scalar variables of the given name
2776     that are available in the code snippet. Example:
2777    
2778     my $five = safe_eval '$first + $second', first => 1, second => 4;
2779    
2780     =cut
2781    
2782 root 1.23 sub safe_eval($;@) {
2783     my ($code, %vars) = @_;
2784    
2785     my $qcode = $code;
2786     $qcode =~ s/"/‟/g; # not allowed in #line filenames
2787     $qcode =~ s/\n/\\n/g;
2788    
2789     local $_;
2790 root 1.41 local @safe::cf::_safe_eval_args = values %vars;
2791 root 1.23
2792 root 1.42 my $eval =
2793 root 1.23 "do {\n"
2794     . "my (" . (join ",", map "\$$_", keys %vars) . ") = \@cf::_safe_eval_args;\n"
2795     . "#line 0 \"{$qcode}\"\n"
2796     . $code
2797     . "\n}"
2798 root 1.25 ;
2799    
2800     sub_generation_inc;
2801 root 1.42 my @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval);
2802 root 1.25 sub_generation_inc;
2803    
2804 root 1.42 if ($@) {
2805     warn "$@";
2806     warn "while executing safe code '$code'\n";
2807     warn "with arguments " . (join " ", %vars) . "\n";
2808     }
2809    
2810 root 1.25 wantarray ? @res : $res[0]
2811 root 1.23 }
2812    
2813 root 1.69 =item cf::register_script_function $function => $cb
2814    
2815     Register a function that can be called from within map/npc scripts. The
2816     function should be reasonably secure and should be put into a package name
2817     like the extension.
2818    
2819     Example: register a function that gets called whenever a map script calls
2820     C<rent::overview>, as used by the C<rent> extension.
2821    
2822     cf::register_script_function "rent::overview" => sub {
2823     ...
2824     };
2825    
2826     =cut
2827    
2828 root 1.23 sub register_script_function {
2829     my ($fun, $cb) = @_;
2830    
2831     no strict 'refs';
2832 root 1.41 *{"safe::$fun"} = $safe_hole->wrap ($cb);
2833 root 1.23 }
2834    
2835 root 1.70 =back
2836    
2837 root 1.71 =cut
2838    
2839 root 1.23 #############################################################################
2840 root 1.203 # the server's init and main functions
2841    
2842 root 1.246 sub load_facedata($) {
2843     my ($path) = @_;
2844 root 1.223
2845 root 1.348 # HACK to clear player env face cache, we need some signal framework
2846     # for this (global event?)
2847     %ext::player_env::MUSIC_FACE_CACHE = ();
2848    
2849 root 1.344 my $enc = JSON::XS->new->utf8->canonical->relaxed;
2850 root 1.334
2851 root 1.229 warn "loading facedata from $path\n";
2852 root 1.223
2853 root 1.236 my $facedata;
2854     0 < aio_load $path, $facedata
2855 root 1.223 or die "$path: $!";
2856    
2857 root 1.237 $facedata = Coro::Storable::thaw $facedata;
2858 root 1.223
2859 root 1.236 $facedata->{version} == 2
2860 root 1.226 or cf::cleanup "$path: version mismatch, cannot proceed.";
2861    
2862 root 1.334 # patch in the exptable
2863     $facedata->{resource}{"res/exp_table"} = {
2864     type => FT_RSRC,
2865 root 1.337 data => $enc->encode ([map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]),
2866 root 1.334 };
2867     cf::cede_to_tick;
2868    
2869 root 1.236 {
2870     my $faces = $facedata->{faceinfo};
2871    
2872     while (my ($face, $info) = each %$faces) {
2873     my $idx = (cf::face::find $face) || cf::face::alloc $face;
2874 root 1.302 cf::face::set_visibility $idx, $info->{visibility};
2875     cf::face::set_magicmap $idx, $info->{magicmap};
2876 root 1.334 cf::face::set_data $idx, 0, $info->{data32}, Digest::MD5::md5 $info->{data32};
2877     cf::face::set_data $idx, 1, $info->{data64}, Digest::MD5::md5 $info->{data64};
2878 root 1.302
2879     cf::cede_to_tick;
2880 root 1.236 }
2881    
2882     while (my ($face, $info) = each %$faces) {
2883     next unless $info->{smooth};
2884     my $idx = cf::face::find $face
2885     or next;
2886     if (my $smooth = cf::face::find $info->{smooth}) {
2887 root 1.302 cf::face::set_smooth $idx, $smooth;
2888     cf::face::set_smoothlevel $idx, $info->{smoothlevel};
2889 root 1.236 } else {
2890     warn "smooth face '$info->{smooth}' not found for face '$face'";
2891     }
2892 root 1.302
2893     cf::cede_to_tick;
2894 root 1.236 }
2895 root 1.223 }
2896    
2897 root 1.236 {
2898     my $anims = $facedata->{animinfo};
2899    
2900     while (my ($anim, $info) = each %$anims) {
2901     cf::anim::set $anim, $info->{frames}, $info->{facings};
2902 root 1.302 cf::cede_to_tick;
2903 root 1.225 }
2904 root 1.236
2905     cf::anim::invalidate_all; # d'oh
2906 root 1.225 }
2907    
2908 root 1.302 {
2909     # TODO: for gcfclient pleasure, we should give resources
2910     # that gcfclient doesn't grok a >10000 face index.
2911     my $res = $facedata->{resource};
2912    
2913 root 1.321 my $soundconf = delete $res->{"res/sound.conf"};
2914 root 1.320
2915 root 1.302 while (my ($name, $info) = each %$res) {
2916     my $idx = (cf::face::find $name) || cf::face::alloc $name;
2917 root 1.334 my $data;
2918 root 1.307
2919 root 1.318 if ($info->{type} & 1) {
2920     # prepend meta info
2921    
2922 root 1.334 my $meta = $enc->encode ({
2923     name => $name,
2924     %{ $info->{meta} || {} },
2925     });
2926 root 1.307
2927 root 1.334 $data = pack "(w/a*)*", $meta, $info->{data};
2928 root 1.337 } else {
2929     $data = $info->{data};
2930 root 1.307 }
2931 root 1.302
2932 root 1.334 cf::face::set_data $idx, 0, $data, Digest::MD5::md5 $data;
2933 root 1.318 cf::face::set_type $idx, $info->{type};
2934    
2935 root 1.302 cf::cede_to_tick;
2936     }
2937 root 1.321
2938     if ($soundconf) {
2939     $soundconf = $enc->decode (delete $soundconf->{data});
2940    
2941     for (0 .. SOUND_CAST_SPELL_0 - 1) {
2942     my $sound = $soundconf->{compat}[$_]
2943     or next;
2944    
2945     my $face = cf::face::find "sound/$sound->[1]";
2946     cf::sound::set $sound->[0] => $face;
2947     cf::sound::old_sound_index $_, $face; # gcfclient-compat
2948     }
2949    
2950 root 1.326 while (my ($k, $v) = each %{$soundconf->{event}}) {
2951     my $face = cf::face::find "sound/$v";
2952     cf::sound::set $k => $face;
2953     }
2954 root 1.321 }
2955 root 1.302 }
2956    
2957 root 1.223 1
2958     }
2959    
2960 root 1.318 register_exticmd fx_want => sub {
2961     my ($ns, $want) = @_;
2962    
2963     while (my ($k, $v) = each %$want) {
2964     $ns->fx_want ($k, $v);
2965     }
2966     };
2967    
2968 root 1.253 sub reload_regions {
2969 root 1.348 # HACK to clear player env face cache, we need some signal framework
2970     # for this (global event?)
2971     %ext::player_env::MUSIC_FACE_CACHE = ();
2972    
2973 root 1.253 load_resource_file "$MAPDIR/regions"
2974     or die "unable to load regions file\n";
2975 root 1.304
2976     for (cf::region::list) {
2977     $_->{match} = qr/$_->{match}/
2978     if exists $_->{match};
2979     }
2980 root 1.253 }
2981    
2982 root 1.246 sub reload_facedata {
2983 root 1.253 load_facedata "$DATADIR/facedata"
2984 root 1.246 or die "unable to load facedata\n";
2985     }
2986    
2987     sub reload_archetypes {
2988 root 1.253 load_resource_file "$DATADIR/archetypes"
2989 root 1.246 or die "unable to load archetypes\n";
2990 root 1.289 #d# NEED to laod twice to resolve forward references
2991     # this really needs to be done in an extra post-pass
2992     # (which needs to be synchronous, so solve it differently)
2993     load_resource_file "$DATADIR/archetypes"
2994     or die "unable to load archetypes\n";
2995 root 1.241 }
2996    
2997 root 1.246 sub reload_treasures {
2998 root 1.253 load_resource_file "$DATADIR/treasures"
2999 root 1.246 or die "unable to load treasurelists\n";
3000 root 1.241 }
3001    
3002 root 1.223 sub reload_resources {
3003 root 1.245 warn "reloading resource files...\n";
3004    
3005 root 1.246 reload_regions;
3006     reload_facedata;
3007 root 1.274 #reload_archetypes;#d#
3008 root 1.246 reload_archetypes;
3009     reload_treasures;
3010 root 1.245
3011     warn "finished reloading resource files\n";
3012 root 1.223 }
3013    
3014     sub init {
3015     reload_resources;
3016 root 1.203 }
3017 root 1.34
3018 root 1.345 sub reload_config {
3019 root 1.253 open my $fh, "<:utf8", "$CONFDIR/config"
3020 root 1.72 or return;
3021    
3022     local $/;
3023     *CFG = YAML::Syck::Load <$fh>;
3024 root 1.131
3025     $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37];
3026    
3027 root 1.139 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset};
3028     $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset};
3029    
3030 root 1.131 if (exists $CFG{mlockall}) {
3031     eval {
3032 root 1.147 $CFG{mlockall} ? eval "mlockall()" : eval "munlockall()"
3033 root 1.131 and die "WARNING: m(un)lockall failed: $!\n";
3034     };
3035     warn $@ if $@;
3036     }
3037 root 1.72 }
3038    
3039 root 1.39 sub main {
3040 root 1.108 # we must not ever block the main coroutine
3041     local $Coro::idle = sub {
3042 root 1.115 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
3043 root 1.175 (async {
3044     Event::one_event;
3045     })->prio (Coro::PRIO_MAX);
3046 root 1.108 };
3047    
3048 root 1.345 reload_config;
3049 root 1.210 db_init;
3050 root 1.61 load_extensions;
3051 root 1.183
3052     $TICK_WATCHER->start;
3053 root 1.34 Event::loop;
3054     }
3055    
3056     #############################################################################
3057 root 1.155 # initialisation and cleanup
3058    
3059     # install some emergency cleanup handlers
3060     BEGIN {
3061     for my $signal (qw(INT HUP TERM)) {
3062     Event->signal (
3063 root 1.189 reentrant => 0,
3064     data => WF_AUTOCANCEL,
3065     signal => $signal,
3066 root 1.191 prio => 0,
3067 root 1.189 cb => sub {
3068 root 1.155 cf::cleanup "SIG$signal";
3069     },
3070     );
3071     }
3072     }
3073    
3074 root 1.281 sub write_runtime {
3075     my $runtime = "$LOCALDIR/runtime";
3076    
3077     # first touch the runtime file to show we are still running:
3078     # the fsync below can take a very very long time.
3079    
3080     IO::AIO::aio_utime $runtime, undef, undef;
3081    
3082     my $guard = cf::lock_acquire "write_runtime";
3083    
3084     my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644
3085     or return;
3086    
3087     my $value = $cf::RUNTIME + 90 + 10;
3088     # 10 is the runtime save interval, for a monotonic clock
3089     # 60 allows for the watchdog to kill the server.
3090    
3091     (aio_write $fh, 0, (length $value), $value, 0) <= 0
3092     and return;
3093    
3094     # always fsync - this file is important
3095     aio_fsync $fh
3096     and return;
3097    
3098     # touch it again to show we are up-to-date
3099     aio_utime $fh, undef, undef;
3100    
3101     close $fh
3102     or return;
3103    
3104     aio_rename "$runtime~", $runtime
3105     and return;
3106    
3107     warn "runtime file written.\n";
3108    
3109     1
3110     }
3111    
3112 root 1.156 sub emergency_save() {
3113 root 1.155 my $freeze_guard = cf::freeze_mainloop;
3114    
3115     warn "enter emergency perl save\n";
3116    
3117     cf::sync_job {
3118     # use a peculiar iteration method to avoid tripping on perl
3119     # refcount bugs in for. also avoids problems with players
3120 root 1.167 # and maps saved/destroyed asynchronously.
3121 root 1.155 warn "begin emergency player save\n";
3122     for my $login (keys %cf::PLAYER) {
3123     my $pl = $cf::PLAYER{$login} or next;
3124     $pl->valid or next;
3125     $pl->save;
3126     }
3127     warn "end emergency player save\n";
3128    
3129     warn "begin emergency map save\n";
3130     for my $path (keys %cf::MAP) {
3131     my $map = $cf::MAP{$path} or next;
3132     $map->valid or next;
3133     $map->save;
3134     }
3135     warn "end emergency map save\n";
3136 root 1.208
3137     warn "begin emergency database checkpoint\n";
3138     BDB::db_env_txn_checkpoint $DB_ENV;
3139     warn "end emergency database checkpoint\n";
3140 root 1.155 };
3141    
3142     warn "leave emergency perl save\n";
3143     }
3144 root 1.22
3145 root 1.211 sub post_cleanup {
3146     my ($make_core) = @_;
3147    
3148     warn Carp::longmess "post_cleanup backtrace"
3149     if $make_core;
3150     }
3151    
3152 root 1.246 sub do_reload_perl() {
3153 root 1.106 # can/must only be called in main
3154     if ($Coro::current != $Coro::main) {
3155 root 1.183 warn "can only reload from main coroutine";
3156 root 1.106 return;
3157     }
3158    
3159 root 1.103 warn "reloading...";
3160    
3161 root 1.212 warn "entering sync_job";
3162    
3163 root 1.213 cf::sync_job {
3164 root 1.214 cf::write_runtime; # external watchdog should not bark
3165 root 1.212 cf::emergency_save;
3166 root 1.214 cf::write_runtime; # external watchdog should not bark
3167 root 1.183
3168 root 1.212 warn "syncing database to disk";
3169     BDB::db_env_txn_checkpoint $DB_ENV;
3170 root 1.106
3171     # if anything goes wrong in here, we should simply crash as we already saved
3172 root 1.65
3173 root 1.183 warn "cancelling all WF_AUTOCANCEL watchers";
3174 root 1.87 for (Event::all_watchers) {
3175     $_->cancel if $_->data & WF_AUTOCANCEL;
3176     }
3177 root 1.65
3178 root 1.183 warn "flushing outstanding aio requests";
3179     for (;;) {
3180 root 1.208 BDB::flush;
3181 root 1.183 IO::AIO::flush;
3182     Coro::cede;
3183 root 1.208 last unless IO::AIO::nreqs || BDB::nreqs;
3184 root 1.183 warn "iterate...";
3185     }
3186    
3187 root 1.223 ++$RELOAD;
3188    
3189 root 1.183 warn "cancelling all extension coros";
3190 root 1.103 $_->cancel for values %EXT_CORO;
3191     %EXT_CORO = ();
3192    
3193 root 1.183 warn "removing commands";
3194 root 1.159 %COMMAND = ();
3195    
3196 root 1.287 warn "removing ext/exti commands";
3197     %EXTCMD = ();
3198     %EXTICMD = ();
3199 root 1.159
3200 root 1.183 warn "unloading/nuking all extensions";
3201 root 1.159 for my $pkg (@EXTS) {
3202 root 1.160 warn "... unloading $pkg";
3203 root 1.159
3204     if (my $cb = $pkg->can ("unload")) {
3205     eval {
3206     $cb->($pkg);
3207     1
3208     } or warn "$pkg unloaded, but with errors: $@";
3209     }
3210    
3211 root 1.160 warn "... nuking $pkg";
3212 root 1.159 Symbol::delete_package $pkg;
3213 root 1.65 }
3214    
3215 root 1.183 warn "unloading all perl modules loaded from $LIBDIR";
3216 root 1.65 while (my ($k, $v) = each %INC) {
3217     next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
3218    
3219 root 1.183 warn "... unloading $k";
3220 root 1.65 delete $INC{$k};
3221    
3222     $k =~ s/\.pm$//;
3223     $k =~ s/\//::/g;
3224    
3225     if (my $cb = $k->can ("unload_module")) {
3226     $cb->();
3227     }
3228    
3229     Symbol::delete_package $k;
3230     }
3231    
3232 root 1.183 warn "getting rid of safe::, as good as possible";
3233 root 1.65 Symbol::delete_package "safe::$_"
3234 root 1.103 for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region);
3235 root 1.65
3236 root 1.183 warn "unloading cf.pm \"a bit\"";
3237 root 1.65 delete $INC{"cf.pm"};
3238 root 1.252 delete $INC{"cf/pod.pm"};
3239 root 1.65
3240     # don't, removes xs symbols, too,
3241     # and global variables created in xs
3242     #Symbol::delete_package __PACKAGE__;
3243    
3244 root 1.183 warn "unload completed, starting to reload now";
3245    
3246 root 1.103 warn "reloading cf.pm";
3247 root 1.65 require cf;
3248 root 1.100 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
3249    
3250 root 1.183 warn "loading config and database again";
3251 root 1.345 cf::reload_config;
3252 root 1.65
3253 root 1.183 warn "loading extensions";
3254 root 1.65 cf::load_extensions;
3255    
3256 root 1.183 warn "reattaching attachments to objects/players";
3257 root 1.222 _global_reattach; # objects, sockets
3258 root 1.183 warn "reattaching attachments to maps";
3259 root 1.144 reattach $_ for values %MAP;
3260 root 1.222 warn "reattaching attachments to players";
3261     reattach $_ for values %PLAYER;
3262 root 1.183
3263 root 1.212 warn "leaving sync_job";
3264 root 1.183
3265 root 1.212 1
3266     } or do {
3267 root 1.106 warn $@;
3268     warn "error while reloading, exiting.";
3269     exit 1;
3270 root 1.212 };
3271 root 1.106
3272 root 1.159 warn "reloaded";
3273 root 1.65 };
3274    
3275 root 1.175 our $RELOAD_WATCHER; # used only during reload
3276    
3277 root 1.246 sub reload_perl() {
3278     # doing reload synchronously and two reloads happen back-to-back,
3279     # coro crashes during coro_state_free->destroy here.
3280    
3281     $RELOAD_WATCHER ||= Event->timer (
3282     reentrant => 0,
3283     after => 0,
3284     data => WF_AUTOCANCEL,
3285     cb => sub {
3286     do_reload_perl;
3287     undef $RELOAD_WATCHER;
3288     },
3289     );
3290     }
3291    
3292 root 1.111 register_command "reload" => sub {
3293 root 1.65 my ($who, $arg) = @_;
3294    
3295     if ($who->flag (FLAG_WIZ)) {
3296 root 1.175 $who->message ("reloading server.");
3297 root 1.246 async { reload_perl };
3298 root 1.65 }
3299     };
3300    
3301 root 1.27 unshift @INC, $LIBDIR;
3302 root 1.17
3303 root 1.183 my $bug_warning = 0;
3304    
3305 root 1.239 our @WAIT_FOR_TICK;
3306     our @WAIT_FOR_TICK_BEGIN;
3307    
3308     sub wait_for_tick {
3309 root 1.240 return unless $TICK_WATCHER->is_active;
3310 root 1.241 return if $Coro::current == $Coro::main;
3311    
3312 root 1.239 my $signal = new Coro::Signal;
3313     push @WAIT_FOR_TICK, $signal;
3314     $signal->wait;
3315     }
3316    
3317     sub wait_for_tick_begin {
3318 root 1.240 return unless $TICK_WATCHER->is_active;
3319 root 1.241 return if $Coro::current == $Coro::main;
3320    
3321 root 1.239 my $signal = new Coro::Signal;
3322     push @WAIT_FOR_TICK_BEGIN, $signal;
3323     $signal->wait;
3324     }
3325    
3326 root 1.268 my $min = 1e6;#d#
3327     my $avg = 10;
3328 root 1.35 $TICK_WATCHER = Event->timer (
3329 root 1.104 reentrant => 0,
3330 root 1.183 parked => 1,
3331 root 1.191 prio => 0,
3332 root 1.104 at => $NEXT_TICK || $TICK,
3333     data => WF_AUTOCANCEL,
3334     cb => sub {
3335 root 1.183 if ($Coro::current != $Coro::main) {
3336     Carp::cluck "major BUG: server tick called outside of main coro, skipping it"
3337     unless ++$bug_warning > 10;
3338     return;
3339     }
3340    
3341 root 1.265 $NOW = $tick_start = Event::time;
3342 root 1.163
3343 root 1.133 cf::server_tick; # one server iteration
3344 root 1.245
3345 root 1.268 0 && sync_job {#d#
3346     for(1..10) {
3347     my $t = Event::time;
3348     my $map = my $map = new_from_path cf::map "/tmp/x.map"
3349     or die;
3350    
3351     $map->width (50);
3352     $map->height (50);
3353     $map->alloc;
3354     $map->_load_objects ("/tmp/x.map", 1);
3355     my $t = Event::time - $t;
3356    
3357     #next unless $t < 0.0013;#d#
3358     if ($t < $min) {
3359     $min = $t;
3360     }
3361     $avg = $avg * 0.99 + $t * 0.01;
3362     }
3363     warn "XXXXXXXXXXXXXXXXXX min $min avg $avg\n";#d#
3364     exit 0;
3365     # 2007-05-22 02:33:04.569 min 0.00112509727478027 avg 0.0012259249572477
3366     };
3367    
3368 root 1.133 $RUNTIME += $TICK;
3369 root 1.35 $NEXT_TICK += $TICK;
3370    
3371 root 1.214 if ($NOW >= $NEXT_RUNTIME_WRITE) {
3372     $NEXT_RUNTIME_WRITE = $NOW + 10;
3373     Coro::async_pool {
3374     write_runtime
3375     or warn "ERROR: unable to write runtime file: $!";
3376     };
3377     }
3378    
3379 root 1.191 # my $AFTER = Event::time;
3380     # warn $AFTER - $NOW;#d#
3381 root 1.190
3382 root 1.245 if (my $sig = shift @WAIT_FOR_TICK_BEGIN) {
3383     $sig->send;
3384     }
3385     while (my $sig = shift @WAIT_FOR_TICK) {
3386     $sig->send;
3387     }
3388    
3389 root 1.265 $NOW = Event::time;
3390    
3391     # if we are delayed by four ticks or more, skip them all
3392     $NEXT_TICK = $NOW if $NOW >= $NEXT_TICK + $TICK * 4;
3393    
3394     $TICK_WATCHER->at ($NEXT_TICK);
3395     $TICK_WATCHER->start;
3396    
3397     $LOAD = ($NOW - $tick_start) / $TICK;
3398     $LOADAVG = $LOADAVG * 0.75 + $LOAD * 0.25;
3399    
3400 root 1.245 _post_tick;
3401 root 1.265
3402    
3403 root 1.35 },
3404     );
3405    
3406 root 1.206 {
3407     BDB::max_poll_time $TICK * 0.1;
3408     $BDB_POLL_WATCHER = Event->io (
3409     reentrant => 0,
3410     fd => BDB::poll_fileno,
3411     poll => 'r',
3412     prio => 0,
3413     data => WF_AUTOCANCEL,
3414     cb => \&BDB::poll_cb,
3415     );
3416     BDB::min_parallel 8;
3417    
3418     BDB::set_sync_prepare {
3419     my $status;
3420     my $current = $Coro::current;
3421     (
3422     sub {
3423     $status = $!;
3424     $current->ready; undef $current;
3425     },
3426     sub {
3427     Coro::schedule while defined $current;
3428     $! = $status;
3429     },
3430     )
3431     };
3432 root 1.77
3433 root 1.206 unless ($DB_ENV) {
3434     $DB_ENV = BDB::db_env_create;
3435    
3436     cf::sync_job {
3437 root 1.208 eval {
3438     BDB::db_env_open
3439     $DB_ENV,
3440 root 1.253 $BDBDIR,
3441 root 1.208 BDB::INIT_LOCK | BDB::INIT_LOG | BDB::INIT_MPOOL | BDB::INIT_TXN
3442     | BDB::RECOVER | BDB::REGISTER | BDB::USE_ENVIRON | BDB::CREATE,
3443     0666;
3444    
3445 root 1.253 cf::cleanup "db_env_open($BDBDIR): $!" if $!;
3446 root 1.208
3447     $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT | BDB::TXN_NOSYNC, 1);
3448     $DB_ENV->set_lk_detect;
3449     };
3450    
3451     cf::cleanup "db_env_open(db): $@" if $@;
3452 root 1.206 };
3453     }
3454     }
3455    
3456     {
3457     IO::AIO::min_parallel 8;
3458    
3459     undef $Coro::AIO::WATCHER;
3460     IO::AIO::max_poll_time $TICK * 0.1;
3461     $AIO_POLL_WATCHER = Event->io (
3462     reentrant => 0,
3463 root 1.214 data => WF_AUTOCANCEL,
3464 root 1.206 fd => IO::AIO::poll_fileno,
3465     poll => 'r',
3466     prio => 6,
3467     cb => \&IO::AIO::poll_cb,
3468     );
3469     }
3470 root 1.108
3471 root 1.262 my $_log_backtrace;
3472    
3473 root 1.260 sub _log_backtrace {
3474     my ($msg, @addr) = @_;
3475    
3476 root 1.262 $msg =~ s/\n//;
3477 root 1.260
3478 root 1.262 # limit the # of concurrent backtraces
3479     if ($_log_backtrace < 2) {
3480     ++$_log_backtrace;
3481     async {
3482     my @bt = fork_call {
3483     @addr = map { sprintf "%x", $_ } @addr;
3484     my $self = (-f "/proc/$$/exe") ? "/proc/$$/exe" : $^X;
3485     open my $fh, "exec addr2line -C -f -i -e \Q$self\E @addr 2>&1 |"
3486     or die "addr2line: $!";
3487    
3488     my @funcs;
3489     my @res = <$fh>;
3490     chomp for @res;
3491     while (@res) {
3492     my ($func, $line) = splice @res, 0, 2, ();
3493     push @funcs, "[$func] $line";
3494     }
3495 root 1.260
3496 root 1.262 @funcs
3497     };
3498 root 1.260
3499 root 1.262 LOG llevInfo, "[ABT] $msg\n";
3500     LOG llevInfo, "[ABT] $_\n" for @bt;
3501     --$_log_backtrace;
3502     };
3503     } else {
3504 root 1.260 LOG llevInfo, "[ABT] $msg\n";
3505 root 1.262 LOG llevInfo, "[ABT] [suppressed]\n";
3506     }
3507 root 1.260 }
3508    
3509 root 1.249 # load additional modules
3510     use cf::pod;
3511    
3512 root 1.125 END { cf::emergency_save }
3513    
3514 root 1.1 1
3515