ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf.pm
Revision: 1.127
Committed: Wed Jan 3 00:05:26 2007 UTC (17 years, 4 months ago) by root
Branch: MAIN
Changes since 1.126: +22 -17 lines
Log Message:
- Create cf::async as safe alternative to Coro::async.
- used cf::async everywhere it makes sense
- fixed recursive call to find_map differently

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.6 use Storable;
9 root 1.23 use Opcode;
10     use Safe;
11     use Safe::Hole;
12 root 1.19
13 root 1.127 use Coro 3.3 ();
14 root 1.96 use Coro::Event;
15     use Coro::Timer;
16     use Coro::Signal;
17     use Coro::Semaphore;
18 root 1.105 use Coro::AIO;
19 root 1.96
20 root 1.108 use Digest::MD5;
21 root 1.105 use Fcntl;
22     use IO::AIO 2.31 ();
23 root 1.72 use YAML::Syck ();
24 root 1.32 use Time::HiRes;
25 root 1.96
26     use Event; $Event::Eval = 1; # no idea why this is required, but it is
27 root 1.1
28 root 1.72 # work around bug in YAML::Syck - bad news for perl6, will it be as broken wrt. unicode?
29     $YAML::Syck::ImplicitUnicode = 1;
30    
31 root 1.103 $Coro::main->prio (2); # run main coroutine ("the server") with very high priority
32 root 1.1
33 root 1.87 sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
34    
35 root 1.85 our %COMMAND = ();
36     our %COMMAND_TIME = ();
37     our %EXTCMD = ();
38    
39 root 1.1 our @EVENT;
40 root 1.88 our $LIBDIR = datadir . "/ext";
41 root 1.1
42 root 1.35 our $TICK = MAX_TIME * 1e-6;
43     our $TICK_WATCHER;
44     our $NEXT_TICK;
45 root 1.103 our $NOW;
46 root 1.35
47 root 1.70 our %CFG;
48    
49 root 1.84 our $UPTIME; $UPTIME ||= time;
50 root 1.103 our $RUNTIME;
51    
52     our %MAP; # all maps
53     our $LINK_MAP; # the special {link} map
54     our $FREEZE;
55 root 1.108 our $RANDOM_MAPS = cf::localdir . "/random";
56     our %EXT_CORO;
57 root 1.103
58     binmode STDOUT;
59     binmode STDERR;
60    
61     # read virtual server time, if available
62     unless ($RUNTIME || !-e cf::localdir . "/runtime") {
63     open my $fh, "<", cf::localdir . "/runtime"
64     or die "unable to read runtime file: $!";
65     $RUNTIME = <$fh> + 0.;
66     }
67    
68     mkdir cf::localdir;
69     mkdir cf::localdir . "/" . cf::playerdir;
70     mkdir cf::localdir . "/" . cf::tmpdir;
71     mkdir cf::localdir . "/" . cf::uniquedir;
72 root 1.108 mkdir $RANDOM_MAPS;
73 root 1.103
74 root 1.108 # a special map that is always available
75     our $LINK_MAP;
76 root 1.76
77 root 1.110 our $EMERGENCY_POSITION = $cf::CFG{emergency_position} || ["/world/world_105_115", 5, 37];
78    
79 root 1.70 #############################################################################
80    
81     =head2 GLOBAL VARIABLES
82    
83     =over 4
84    
85 root 1.83 =item $cf::UPTIME
86    
87     The timestamp of the server start (so not actually an uptime).
88    
89 root 1.103 =item $cf::RUNTIME
90    
91     The time this server has run, starts at 0 and is increased by $cf::TICK on
92     every server tick.
93    
94 root 1.70 =item $cf::LIBDIR
95    
96     The perl library directory, where extensions and cf-specific modules can
97     be found. It will be added to C<@INC> automatically.
98    
99 root 1.103 =item $cf::NOW
100    
101     The time of the last (current) server tick.
102    
103 root 1.70 =item $cf::TICK
104    
105     The interval between server ticks, in seconds.
106    
107     =item %cf::CFG
108    
109     Configuration for the server, loaded from C</etc/crossfire/config>, or
110     from wherever your confdir points to.
111    
112     =back
113    
114     =cut
115    
116 root 1.1 BEGIN {
117     *CORE::GLOBAL::warn = sub {
118     my $msg = join "", @_;
119 root 1.103 utf8::encode $msg;
120    
121 root 1.1 $msg .= "\n"
122     unless $msg =~ /\n$/;
123    
124     LOG llevError, "cfperl: $msg";
125     };
126     }
127    
128 root 1.93 @safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
129     @safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable';
130     @safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable';
131     @safe::cf::client::ISA = @cf::client::ISA = 'cf::attachable';
132     @safe::cf::map::ISA = @cf::map::ISA = 'cf::attachable';
133 root 1.41 @safe::cf::object::player::ISA = @cf::object::player::ISA = 'cf::object';
134 root 1.25
135 root 1.41 # we bless all objects into (empty) derived classes to force a method lookup
136 root 1.25 # within the Safe compartment.
137 root 1.86 for my $pkg (qw(
138 root 1.100 cf::global cf::attachable
139 root 1.86 cf::object cf::object::player
140 root 1.89 cf::client cf::player
141 root 1.86 cf::arch cf::living
142     cf::map cf::party cf::region
143     )) {
144 root 1.25 no strict 'refs';
145 root 1.41 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg;
146 root 1.25 }
147 root 1.1
148 root 1.18 $Event::DIED = sub {
149     warn "error in event callback: @_";
150     };
151    
152 root 1.5 my %ext_pkg;
153 root 1.1 my @exts;
154     my @hook;
155    
156 root 1.70 =head2 UTILITY FUNCTIONS
157    
158     =over 4
159    
160     =cut
161 root 1.44
162 root 1.45 use JSON::Syck (); # TODO# replace by JSON::PC once working
163 root 1.44
164 root 1.70 =item $ref = cf::from_json $json
165    
166     Converts a JSON string into the corresponding perl data structure.
167    
168     =cut
169    
170 root 1.45 sub from_json($) {
171     $JSON::Syck::ImplicitUnicode = 1; # work around JSON::Syck bugs
172     JSON::Syck::Load $_[0]
173 root 1.44 }
174    
175 root 1.70 =item $json = cf::to_json $ref
176    
177     Converts a perl data structure into its JSON representation.
178    
179     =cut
180    
181 root 1.45 sub to_json($) {
182     $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs
183     JSON::Syck::Dump $_[0]
184 root 1.44 }
185    
186 root 1.120 =item my $guard = cf::guard { BLOCK }
187    
188     Run the given callback when the guard object gets destroyed (useful for
189     coroutine cancellations).
190    
191     You can call C<< ->cancel >> on the guard object to stop the block from
192     being executed.
193    
194     =cut
195    
196     sub guard(&) {
197     bless \(my $cb = $_[0]), cf::guard::;
198     }
199    
200     sub cf::guard::cancel {
201     ${$_[0]} = sub { };
202     }
203    
204     sub cf::guard::DESTROY {
205     ${$_[0]}->();
206     }
207    
208     =item cf::lock_wait $string
209    
210     Wait until the given lock is available. See cf::lock_acquire.
211    
212     =item my $lock = cf::lock_acquire $string
213    
214     Wait until the given lock is available and then acquires it and returns
215     a guard object. If the guard object gets destroyed (goes out of scope,
216     for example when the coroutine gets canceled), the lock is automatically
217     returned.
218    
219     Lock names should begin with a unique identifier (for example, find_map
220     uses map_find and load_map uses map_load).
221    
222     =cut
223    
224     our %LOCK;
225    
226     sub lock_wait($) {
227     my ($key) = @_;
228    
229     # wait for lock, if any
230     while ($LOCK{$key}) {
231     push @{ $LOCK{$key} }, $Coro::current;
232     Coro::schedule;
233     }
234     }
235    
236     sub lock_acquire($) {
237     my ($key) = @_;
238    
239     # wait, to be sure we are not locked
240     lock_wait $key;
241    
242     $LOCK{$key} = [];
243    
244     cf::guard {
245     # wake up all waiters, to be on the safe side
246     $_->ready for @{ delete $LOCK{$key} };
247     }
248     }
249    
250 root 1.127 =item cf::async { BLOCK }
251    
252     Like C<Coro::async>, but runs the given BLOCK in an eval and only logs the
253     error instead of exiting the server in case of a problem.
254    
255     =cut
256    
257     sub async(&) {
258     my ($cb) = @_;
259    
260     Coro::async {
261     eval { $cb->() };
262     warn $@ if $@;
263     }
264     }
265    
266 root 1.106 =item cf::sync_job { BLOCK }
267    
268     The design of crossfire+ requires that the main coro ($Coro::main) is
269     always able to handle events or runnable, as crossfire+ is only partly
270     reentrant. Thus "blocking" it by e.g. waiting for I/O is not acceptable.
271    
272     If it must be done, put the blocking parts into C<sync_job>. This will run
273     the given BLOCK in another coroutine while waiting for the result. The
274     server will be frozen during this time, so the block should either finish
275     fast or be very important.
276    
277     =cut
278    
279 root 1.105 sub sync_job(&) {
280     my ($job) = @_;
281    
282     if ($Coro::current == $Coro::main) {
283 root 1.112 # this is the main coro, too bad, we have to block
284     # till the operation succeeds, freezing the server :/
285    
286 root 1.110 # TODO: use suspend/resume instead
287 root 1.112 # (but this is cancel-safe)
288 root 1.110 local $FREEZE = 1;
289 root 1.112
290     my $busy = 1;
291     my @res;
292    
293     (Coro::async {
294     @res = eval { $job->() };
295     warn $@ if $@;
296     undef $busy;
297     })->prio (Coro::PRIO_MAX);
298    
299 root 1.105 while ($busy) {
300     Coro::cede_notself;
301     Event::one_event unless Coro::nready;
302     }
303 root 1.112
304     wantarray ? @res : $res[0]
305 root 1.105 } else {
306 root 1.112 # we are in another coroutine, how wonderful, everything just works
307    
308     $job->()
309 root 1.105 }
310     }
311    
312 root 1.103 =item $coro = cf::coro { BLOCK }
313    
314     Creates and returns a new coro. This coro is automcatially being canceled
315     when the extension calling this is being unloaded.
316    
317     =cut
318    
319     sub coro(&) {
320     my $cb = shift;
321    
322 root 1.127 my $coro = &cf::async ($cb);
323 root 1.103
324     $coro->on_destroy (sub {
325     delete $EXT_CORO{$coro+0};
326     });
327     $EXT_CORO{$coro+0} = $coro;
328    
329     $coro
330     }
331    
332 root 1.108 sub write_runtime {
333     my $runtime = cf::localdir . "/runtime";
334    
335     my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644
336     or return;
337    
338 root 1.112 my $value = $cf::RUNTIME + 1 + 10; # 10 is the runtime save interval, for a monotonic clock
339 root 1.108 (aio_write $fh, 0, (length $value), $value, 0) <= 0
340     and return;
341    
342     aio_fsync $fh
343     and return;
344    
345     close $fh
346     or return;
347    
348     aio_rename "$runtime~", $runtime
349     and return;
350    
351     1
352     }
353    
354 root 1.70 =back
355    
356 root 1.71 =cut
357    
358 root 1.44 #############################################################################
359 root 1.39
360 root 1.108 package cf::path;
361    
362     sub new {
363     my ($class, $path, $base) = @_;
364    
365 root 1.110 $path = $path->as_string if ref $path;
366    
367 root 1.108 my $self = bless { }, $class;
368    
369 root 1.114 # {... are special paths that are not touched
370     # ?xxx/... are special absolute paths
371     # ?random/... random maps
372     # /! non-realised random map exit
373     # /... normal maps
374     # ~/... per-player maps without a specific player (DO NOT USE)
375     # ~user/... per-player map of a specific user
376    
377     if ($path =~ /^{/) {
378     # fine as it is
379     } elsif ($path =~ s{^\?random/}{}) {
380 root 1.109 Coro::AIO::aio_load "$cf::RANDOM_MAPS/$path.meta", my $data;
381     $self->{random} = cf::from_json $data;
382 root 1.108 } else {
383     if ($path =~ s{^~([^/]+)?}{}) {
384     $self->{user_rel} = 1;
385    
386     if (defined $1) {
387     $self->{user} = $1;
388     } elsif ($base =~ m{^~([^/]+)/}) {
389     $self->{user} = $1;
390     } else {
391     warn "cannot resolve user-relative path without user <$path,$base>\n";
392     }
393     } elsif ($path =~ /^\//) {
394     # already absolute
395     } else {
396     $base =~ s{[^/]+/?$}{};
397     return $class->new ("$base/$path");
398     }
399    
400     for ($path) {
401     redo if s{/\.?/}{/};
402     redo if s{/[^/]+/\.\./}{/};
403     }
404     }
405    
406     $self->{path} = $path;
407    
408     $self
409     }
410    
411     # the name / primary key / in-game path
412     sub as_string {
413     my ($self) = @_;
414    
415     $self->{user_rel} ? "~$self->{user}$self->{path}"
416     : $self->{random} ? "?random/$self->{path}"
417     : $self->{path}
418     }
419    
420     # the displayed name, this is a one way mapping
421     sub visible_name {
422     my ($self) = @_;
423    
424 root 1.109 # if (my $rmp = $self->{random}) {
425     # # todo: be more intelligent about this
426     # "?random/$rmp->{origin_map}+$rmp->{origin_x}+$rmp->{origin_y}/$rmp->{dungeon_level}"
427     # } else {
428     $self->as_string
429     # }
430 root 1.108 }
431    
432     # escape the /'s in the path
433     sub _escaped_path {
434     # ∕ is U+2215
435     (my $path = $_[0]{path}) =~ s/\//∕/g;
436     $path
437     }
438    
439     # the original (read-only) location
440     sub load_path {
441     my ($self) = @_;
442    
443     sprintf "%s/%s/%s", cf::datadir, cf::mapdir, $self->{path}
444     }
445    
446     # the temporary/swap location
447     sub save_path {
448     my ($self) = @_;
449    
450     $self->{user_rel} ? sprintf "%s/%s/%s/%s", cf::localdir, cf::playerdir, $self->{user}, $self->_escaped_path
451 root 1.109 : $self->{random} ? sprintf "%s/%s", $RANDOM_MAPS, $self->{path}
452 root 1.108 : sprintf "%s/%s/%s", cf::localdir, cf::tmpdir, $self->_escaped_path
453     }
454    
455     # the unique path, might be eq to save_path
456     sub uniq_path {
457     my ($self) = @_;
458    
459     $self->{user_rel} || $self->{random}
460     ? undef
461     : sprintf "%s/%s/%s", cf::localdir, cf::uniquedir, $self->_escaped_path
462     }
463    
464     # return random map parameters, or undef
465     sub random_map_params {
466     my ($self) = @_;
467    
468     $self->{random}
469     }
470    
471     # this is somewhat ugly, but style maps do need special treatment
472     sub is_style_map {
473     $_[0]{path} =~ m{^/styles/}
474     }
475    
476     package cf;
477    
478     #############################################################################
479    
480 root 1.93 =head2 ATTACHABLE OBJECTS
481    
482 root 1.94 Many objects in crossfire are so-called attachable objects. That means you can
483     attach callbacks/event handlers (a collection of which is called an "attachment")
484     to it. All such attachable objects support the following methods.
485    
486     In the following description, CLASS can be any of C<global>, C<object>
487     C<player>, C<client> or C<map> (i.e. the attachable objects in
488     crossfire+).
489 root 1.55
490     =over 4
491    
492 root 1.94 =item $attachable->attach ($attachment, key => $value...)
493    
494     =item $attachable->detach ($attachment)
495    
496     Attach/detach a pre-registered attachment to a specific object and give it
497     the specified key/value pairs as arguments.
498    
499     Example, attach a minesweeper attachment to the given object, making it a
500     10x10 minesweeper game:
501 root 1.46
502 root 1.94 $obj->attach (minesweeper => width => 10, height => 10);
503 root 1.53
504 root 1.93 =item $bool = $attachable->attached ($name)
505 root 1.46
506 root 1.93 Checks wether the named attachment is currently attached to the object.
507 root 1.46
508 root 1.94 =item cf::CLASS->attach ...
509 root 1.46
510 root 1.94 =item cf::CLASS->detach ...
511 root 1.92
512 root 1.94 Define an anonymous attachment and attach it to all objects of the given
513     CLASS. See the next function for an explanation of its arguments.
514 root 1.92
515 root 1.93 You can attach to global events by using the C<cf::global> class.
516 root 1.92
517 root 1.94 Example, log all player logins:
518    
519     cf::player->attach (
520     on_login => sub {
521     my ($pl) = @_;
522     ...
523     },
524     );
525    
526     Example, attach to the jeweler skill:
527    
528     cf::object->attach (
529     type => cf::SKILL,
530     subtype => cf::SK_JEWELER,
531     on_use_skill => sub {
532     my ($sk, $ob, $part, $dir, $msg) = @_;
533     ...
534     },
535     );
536    
537     =item cf::CLASS::attachment $name, ...
538    
539     Register an attachment by C<$name> through which attachable objects of the
540     given CLASS can refer to this attachment.
541    
542     Some classes such as crossfire maps and objects can specify attachments
543     that are attached at load/instantiate time, thus the need for a name.
544    
545     These calls expect any number of the following handler/hook descriptions:
546 root 1.46
547     =over 4
548    
549     =item prio => $number
550    
551     Set the priority for all following handlers/hooks (unless overwritten
552     by another C<prio> setting). Lower priority handlers get executed
553     earlier. The default priority is C<0>, and many built-in handlers are
554     registered at priority C<-1000>, so lower priorities should not be used
555     unless you know what you are doing.
556    
557 root 1.93 =item type => $type
558    
559     (Only for C<< cf::object->attach >> calls), limits the attachment to the
560     given type of objects only (the additional parameter C<subtype> can be
561     used to further limit to the given subtype).
562    
563 root 1.46 =item on_I<event> => \&cb
564    
565     Call the given code reference whenever the named event happens (event is
566     something like C<instantiate>, C<apply>, C<use_skill> and so on, and which
567     handlers are recognised generally depends on the type of object these
568     handlers attach to).
569    
570     See F<include/eventinc.h> for the full list of events supported, and their
571     class.
572    
573     =item package => package::
574    
575     Look for sub functions of the name C<< on_I<event> >> in the given
576     package and register them. Only handlers for eevents supported by the
577     object/class are recognised.
578    
579     =back
580    
581 root 1.94 Example, define an attachment called "sockpuppet" that calls the given
582     event handler when a monster attacks:
583    
584     cf::object::attachment sockpuppet =>
585     on_skill_attack => sub {
586     my ($self, $victim) = @_;
587     ...
588     }
589     }
590    
591 root 1.96 =item $attachable->valid
592    
593     Just because you have a perl object does not mean that the corresponding
594     C-level object still exists. If you try to access an object that has no
595     valid C counterpart anymore you get an exception at runtime. This method
596     can be used to test for existence of the C object part without causing an
597     exception.
598    
599 root 1.39 =cut
600    
601 root 1.40 # the following variables are defined in .xs and must not be re-created
602 root 1.100 our @CB_GLOBAL = (); # registry for all global events
603     our @CB_ATTACHABLE = (); # registry for all attachables
604     our @CB_OBJECT = (); # all objects (should not be used except in emergency)
605     our @CB_PLAYER = ();
606     our @CB_CLIENT = ();
607     our @CB_TYPE = (); # registry for type (cf-object class) based events
608     our @CB_MAP = ();
609 root 1.39
610 root 1.45 my %attachment;
611    
612 root 1.93 sub _attach_cb($$$$) {
613     my ($registry, $event, $prio, $cb) = @_;
614 root 1.39
615     use sort 'stable';
616    
617     $cb = [$prio, $cb];
618    
619     @{$registry->[$event]} = sort
620     { $a->[0] cmp $b->[0] }
621     @{$registry->[$event] || []}, $cb;
622     }
623    
624 root 1.100 # hack
625     my %attachable_klass = map +($_ => 1), KLASS_OBJECT, KLASS_CLIENT, KLASS_PLAYER, KLASS_MAP;
626    
627 root 1.39 # attach handles attaching event callbacks
628     # the only thing the caller has to do is pass the correct
629     # registry (== where the callback attaches to).
630 root 1.93 sub _attach {
631 root 1.45 my ($registry, $klass, @arg) = @_;
632 root 1.39
633 root 1.93 my $object_type;
634 root 1.39 my $prio = 0;
635     my %cb_id = map +("on_" . lc $EVENT[$_][0], $_) , grep $EVENT[$_][1] == $klass, 0 .. $#EVENT;
636    
637 root 1.100 #TODO: get rid of this hack
638     if ($attachable_klass{$klass}) {
639     %cb_id = (%cb_id, map +("on_" . lc $EVENT[$_][0], $_) , grep $EVENT[$_][1] == KLASS_ATTACHABLE, 0 .. $#EVENT);
640     }
641    
642 root 1.45 while (@arg) {
643     my $type = shift @arg;
644 root 1.39
645     if ($type eq "prio") {
646 root 1.45 $prio = shift @arg;
647 root 1.39
648 root 1.93 } elsif ($type eq "type") {
649     $object_type = shift @arg;
650     $registry = $CB_TYPE[$object_type] ||= [];
651    
652     } elsif ($type eq "subtype") {
653     defined $object_type or Carp::croak "subtype specified without type";
654     my $object_subtype = shift @arg;
655     $registry = $CB_TYPE[$object_type + $object_subtype * NUM_SUBTYPES] ||= [];
656    
657 root 1.39 } elsif ($type eq "package") {
658 root 1.45 my $pkg = shift @arg;
659 root 1.39
660     while (my ($name, $id) = each %cb_id) {
661     if (my $cb = $pkg->can ($name)) {
662 root 1.93 _attach_cb $registry, $id, $prio, $cb;
663 root 1.39 }
664     }
665    
666     } elsif (exists $cb_id{$type}) {
667 root 1.93 _attach_cb $registry, $cb_id{$type}, $prio, shift @arg;
668 root 1.39
669     } elsif (ref $type) {
670     warn "attaching objects not supported, ignoring.\n";
671    
672     } else {
673 root 1.45 shift @arg;
674 root 1.39 warn "attach argument '$type' not supported, ignoring.\n";
675     }
676     }
677     }
678    
679 root 1.93 sub _object_attach {
680 root 1.48 my ($obj, $name, %arg) = @_;
681 root 1.46
682 root 1.55 return if exists $obj->{_attachment}{$name};
683    
684 root 1.46 if (my $attach = $attachment{$name}) {
685     my $registry = $obj->registry;
686    
687 root 1.47 for (@$attach) {
688     my ($klass, @attach) = @$_;
689 root 1.93 _attach $registry, $klass, @attach;
690 root 1.47 }
691 root 1.46
692 root 1.48 $obj->{$name} = \%arg;
693 root 1.46 } else {
694     warn "object uses attachment '$name' that is not available, postponing.\n";
695     }
696    
697 root 1.50 $obj->{_attachment}{$name} = undef;
698 root 1.46 }
699    
700 root 1.93 sub cf::attachable::attach {
701     if (ref $_[0]) {
702     _object_attach @_;
703     } else {
704     _attach shift->_attach_registry, @_;
705     }
706 root 1.55 };
707 root 1.46
708 root 1.54 # all those should be optimised
709 root 1.93 sub cf::attachable::detach {
710 root 1.54 my ($obj, $name) = @_;
711 root 1.46
712 root 1.93 if (ref $obj) {
713     delete $obj->{_attachment}{$name};
714     reattach ($obj);
715     } else {
716     Carp::croak "cannot, currently, detach class attachments";
717     }
718 root 1.55 };
719    
720 root 1.93 sub cf::attachable::attached {
721 root 1.55 my ($obj, $name) = @_;
722    
723     exists $obj->{_attachment}{$name}
724 root 1.39 }
725    
726 root 1.100 for my $klass (qw(ATTACHABLE GLOBAL OBJECT PLAYER CLIENT MAP)) {
727 root 1.93 eval "#line " . __LINE__ . " 'cf.pm'
728     sub cf::\L$klass\E::_attach_registry {
729     (\\\@CB_$klass, KLASS_$klass)
730     }
731 root 1.45
732 root 1.93 sub cf::\L$klass\E::attachment {
733     my \$name = shift;
734 root 1.39
735 root 1.93 \$attachment{\$name} = [[KLASS_$klass, \@_]];
736     }
737     ";
738     die if $@;
739 root 1.52 }
740    
741 root 1.39 our $override;
742 root 1.45 our @invoke_results = (); # referenced from .xs code. TODO: play tricks with reify and mortals?
743 root 1.39
744 root 1.45 sub override {
745     $override = 1;
746     @invoke_results = ();
747 root 1.39 }
748    
749 root 1.45 sub do_invoke {
750 root 1.39 my $event = shift;
751 root 1.40 my $callbacks = shift;
752 root 1.39
753 root 1.45 @invoke_results = ();
754    
755 root 1.39 local $override;
756    
757 root 1.40 for (@$callbacks) {
758 root 1.39 eval { &{$_->[1]} };
759    
760     if ($@) {
761     warn "$@";
762 root 1.64 warn "... while processing $EVENT[$event][0](@_) event, skipping processing altogether.\n";
763 root 1.39 override;
764     }
765    
766     return 1 if $override;
767     }
768    
769     0
770     }
771    
772 root 1.96 =item $bool = cf::global::invoke (EVENT_CLASS_XXX, ...)
773 root 1.55
774 root 1.96 =item $bool = $attachable->invoke (EVENT_CLASS_XXX, ...)
775 root 1.55
776 root 1.96 Generate an object-specific event with the given arguments.
777 root 1.55
778 root 1.96 This API is preliminary (most likely, the EVENT_CLASS_xxx prefix will be
779 root 1.55 removed in future versions), and there is no public API to access override
780     results (if you must, access C<@cf::invoke_results> directly).
781    
782     =back
783    
784 root 1.71 =cut
785    
786 root 1.70 #############################################################################
787 root 1.45 # object support
788    
789 root 1.102 sub reattach {
790     # basically do the same as instantiate, without calling instantiate
791     my ($obj) = @_;
792    
793     my $registry = $obj->registry;
794    
795     @$registry = ();
796    
797     delete $obj->{_attachment} unless scalar keys %{ $obj->{_attachment} || {} };
798    
799     for my $name (keys %{ $obj->{_attachment} || {} }) {
800     if (my $attach = $attachment{$name}) {
801     for (@$attach) {
802     my ($klass, @attach) = @$_;
803     _attach $registry, $klass, @attach;
804     }
805     } else {
806     warn "object uses attachment '$name' that is not available, postponing.\n";
807     }
808     }
809     }
810    
811 root 1.100 cf::attachable->attach (
812     prio => -1000000,
813     on_instantiate => sub {
814     my ($obj, $data) = @_;
815 root 1.45
816 root 1.100 $data = from_json $data;
817 root 1.45
818 root 1.100 for (@$data) {
819     my ($name, $args) = @$_;
820 root 1.49
821 root 1.100 $obj->attach ($name, %{$args || {} });
822     }
823     },
824 root 1.102 on_reattach => \&reattach,
825 root 1.100 on_clone => sub {
826     my ($src, $dst) = @_;
827    
828     @{$dst->registry} = @{$src->registry};
829    
830     %$dst = %$src;
831    
832     %{$dst->{_attachment}} = %{$src->{_attachment}}
833     if exists $src->{_attachment};
834     },
835     );
836 root 1.45
837 root 1.46 sub object_freezer_save {
838 root 1.59 my ($filename, $rdata, $objs) = @_;
839 root 1.46
840 root 1.105 sync_job {
841     if (length $$rdata) {
842     warn sprintf "saving %s (%d,%d)\n",
843     $filename, length $$rdata, scalar @$objs;
844 root 1.60
845 root 1.105 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
846 root 1.60 chmod SAVE_MODE, $fh;
847 root 1.105 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
848     aio_fsync $fh;
849 root 1.60 close $fh;
850 root 1.105
851     if (@$objs) {
852     if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) {
853     chmod SAVE_MODE, $fh;
854     my $data = Storable::nfreeze { version => 1, objs => $objs };
855     aio_write $fh, 0, (length $data), $data, 0;
856     aio_fsync $fh;
857     close $fh;
858     aio_rename "$filename.pst~", "$filename.pst";
859     }
860     } else {
861     aio_unlink "$filename.pst";
862     }
863    
864     aio_rename "$filename~", $filename;
865 root 1.60 } else {
866 root 1.105 warn "FATAL: $filename~: $!\n";
867 root 1.60 }
868 root 1.59 } else {
869 root 1.105 aio_unlink $filename;
870     aio_unlink "$filename.pst";
871 root 1.59 }
872 root 1.45 }
873     }
874    
875 root 1.80 sub object_freezer_as_string {
876     my ($rdata, $objs) = @_;
877    
878     use Data::Dumper;
879    
880 root 1.81 $$rdata . Dumper $objs
881 root 1.80 }
882    
883 root 1.46 sub object_thawer_load {
884     my ($filename) = @_;
885    
886 root 1.105 my ($data, $av);
887 root 1.61
888 root 1.105 (aio_load $filename, $data) >= 0
889     or return;
890 root 1.61
891 root 1.105 unless (aio_stat "$filename.pst") {
892     (aio_load "$filename.pst", $av) >= 0
893     or return;
894 root 1.113 $av = eval { (Storable::thaw $av)->{objs} };
895 root 1.61 }
896 root 1.45
897 root 1.118 warn sprintf "loading %s (%d)\n",
898     $filename, length $data, scalar @{$av || []};#d#
899 root 1.105 return ($data, $av);
900 root 1.45 }
901    
902     #############################################################################
903 root 1.85 # command handling &c
904 root 1.39
905 root 1.85 =item cf::register_command $name => \&callback($ob,$args);
906 root 1.1
907 root 1.85 Register a callback for execution when the client sends the user command
908     $name.
909 root 1.5
910 root 1.85 =cut
911 root 1.5
912 root 1.85 sub register_command {
913     my ($name, $cb) = @_;
914 root 1.5
915 root 1.85 my $caller = caller;
916     #warn "registering command '$name/$time' to '$caller'";
917 root 1.1
918 root 1.85 push @{ $COMMAND{$name} }, [$caller, $cb];
919 root 1.1 }
920    
921 root 1.85 =item cf::register_extcmd $name => \&callback($pl,$packet);
922 root 1.1
923 root 1.85 Register a callbackf ro execution when the client sends an extcmd packet.
924 root 1.1
925 root 1.85 If the callback returns something, it is sent back as if reply was being
926     called.
927 root 1.1
928 root 1.85 =cut
929 root 1.1
930 root 1.16 sub register_extcmd {
931     my ($name, $cb) = @_;
932    
933     my $caller = caller;
934     #warn "registering extcmd '$name' to '$caller'";
935    
936 root 1.85 $EXTCMD{$name} = [$cb, $caller];
937 root 1.16 }
938    
939 root 1.93 cf::player->attach (
940 root 1.85 on_command => sub {
941     my ($pl, $name, $params) = @_;
942    
943     my $cb = $COMMAND{$name}
944     or return;
945    
946     for my $cmd (@$cb) {
947     $cmd->[1]->($pl->ob, $params);
948     }
949    
950     cf::override;
951     },
952     on_extcmd => sub {
953     my ($pl, $buf) = @_;
954    
955     my $msg = eval { from_json $buf };
956    
957     if (ref $msg) {
958     if (my $cb = $EXTCMD{$msg->{msgtype}}) {
959     if (my %reply = $cb->[0]->($pl, $msg)) {
960     $pl->ext_reply ($msg->{msgid}, %reply);
961     }
962     }
963     } else {
964     warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
965     }
966    
967     cf::override;
968     },
969 root 1.93 );
970 root 1.85
971 root 1.6 sub register {
972     my ($base, $pkg) = @_;
973    
974 root 1.45 #TODO
975 root 1.6 }
976    
977 root 1.1 sub load_extension {
978     my ($path) = @_;
979    
980     $path =~ /([^\/\\]+)\.ext$/ or die "$path";
981 root 1.5 my $base = $1;
982 root 1.1 my $pkg = $1;
983     $pkg =~ s/[^[:word:]]/_/g;
984 root 1.41 $pkg = "ext::$pkg";
985 root 1.1
986     warn "loading '$path' into '$pkg'\n";
987    
988     open my $fh, "<:utf8", $path
989     or die "$path: $!";
990    
991     my $source =
992     "package $pkg; use strict; use utf8;\n"
993     . "#line 1 \"$path\"\n{\n"
994     . (do { local $/; <$fh> })
995     . "\n};\n1";
996    
997     eval $source
998 root 1.82 or die $@ ? "$path: $@\n"
999     : "extension disabled.\n";
1000 root 1.1
1001     push @exts, $pkg;
1002 root 1.5 $ext_pkg{$base} = $pkg;
1003 root 1.1
1004 root 1.6 # no strict 'refs';
1005 root 1.23 # @{"$pkg\::ISA"} = ext::;
1006 root 1.1
1007 root 1.6 register $base, $pkg;
1008 root 1.1 }
1009    
1010     sub unload_extension {
1011     my ($pkg) = @_;
1012    
1013     warn "removing extension $pkg\n";
1014    
1015     # remove hooks
1016 root 1.45 #TODO
1017     # for my $idx (0 .. $#PLUGIN_EVENT) {
1018     # delete $hook[$idx]{$pkg};
1019     # }
1020 root 1.1
1021     # remove commands
1022 root 1.85 for my $name (keys %COMMAND) {
1023     my @cb = grep $_->[0] ne $pkg, @{ $COMMAND{$name} };
1024 root 1.1
1025     if (@cb) {
1026 root 1.85 $COMMAND{$name} = \@cb;
1027 root 1.1 } else {
1028 root 1.85 delete $COMMAND{$name};
1029 root 1.1 }
1030     }
1031    
1032 root 1.15 # remove extcmds
1033 root 1.85 for my $name (grep $EXTCMD{$_}[1] eq $pkg, keys %EXTCMD) {
1034     delete $EXTCMD{$name};
1035 root 1.15 }
1036    
1037 root 1.43 if (my $cb = $pkg->can ("unload")) {
1038 elmex 1.31 eval {
1039     $cb->($pkg);
1040     1
1041     } or warn "$pkg unloaded, but with errors: $@";
1042     }
1043    
1044 root 1.1 Symbol::delete_package $pkg;
1045     }
1046    
1047     sub load_extensions {
1048     for my $ext (<$LIBDIR/*.ext>) {
1049 root 1.3 next unless -r $ext;
1050 root 1.2 eval {
1051     load_extension $ext;
1052     1
1053     } or warn "$ext not loaded: $@";
1054 root 1.1 }
1055     }
1056    
1057 root 1.8 #############################################################################
1058     # load/save/clean perl data associated with a map
1059    
1060 root 1.39 *cf::mapsupport::on_clean = sub {
1061 root 1.13 my ($map) = @_;
1062 root 1.7
1063     my $path = $map->tmpname;
1064     defined $path or return;
1065    
1066 root 1.46 unlink "$path.pst";
1067 root 1.7 };
1068    
1069 root 1.93 cf::map->attach (prio => -10000, package => cf::mapsupport::);
1070 root 1.39
1071 root 1.8 #############################################################################
1072     # load/save perl data associated with player->ob objects
1073    
1074 root 1.33 sub all_objects(@) {
1075     @_, map all_objects ($_->inv), @_
1076     }
1077    
1078 root 1.60 # TODO: compatibility cruft, remove when no longer needed
1079 root 1.93 cf::player->attach (
1080 root 1.39 on_load => sub {
1081     my ($pl, $path) = @_;
1082    
1083     for my $o (all_objects $pl->ob) {
1084     if (my $value = $o->get_ob_key_value ("_perl_data")) {
1085     $o->set_ob_key_value ("_perl_data");
1086 root 1.8
1087 root 1.39 %$o = %{ Storable::thaw pack "H*", $value };
1088     }
1089 root 1.11 }
1090 root 1.39 },
1091 root 1.93 );
1092 root 1.6
1093 root 1.22 #############################################################################
1094 root 1.70
1095     =head2 CORE EXTENSIONS
1096    
1097     Functions and methods that extend core crossfire objects.
1098    
1099 root 1.95 =head3 cf::player
1100    
1101 root 1.70 =over 4
1102 root 1.22
1103 root 1.23 =item cf::player::exists $login
1104    
1105     Returns true when the given account exists.
1106    
1107     =cut
1108    
1109     sub cf::player::exists($) {
1110     cf::player::find $_[0]
1111     or -f sprintf "%s/%s/%s/%s.pl", cf::localdir, cf::playerdir, ($_[0]) x 2;
1112     }
1113    
1114 root 1.95 =item $player->ext_reply ($msgid, $msgtype, %msg)
1115    
1116     Sends an ext reply to the player.
1117    
1118     =cut
1119    
1120     sub cf::player::ext_reply($$$%) {
1121     my ($self, $id, %msg) = @_;
1122    
1123     $msg{msgid} = $id;
1124    
1125     $self->send ("ext " . to_json \%msg);
1126     }
1127    
1128     =back
1129    
1130 root 1.110
1131     =head3 cf::map
1132    
1133     =over 4
1134    
1135     =cut
1136    
1137     package cf::map;
1138    
1139     use Fcntl;
1140     use Coro::AIO;
1141    
1142     our $MAX_RESET = 7200;
1143     our $DEFAULT_RESET = 3600;
1144    
1145     sub generate_random_map {
1146     my ($path, $rmp) = @_;
1147    
1148     # mit "rum" bekleckern, nicht
1149     cf::map::_create_random_map
1150     $path,
1151     $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
1152     $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
1153     $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
1154     $rmp->{exit_on_final_map},
1155     $rmp->{xsize}, $rmp->{ysize},
1156     $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
1157     $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
1158     $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
1159     $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
1160     $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
1161     (cf::region::find $rmp->{region})
1162     }
1163    
1164     # and all this just because we cannot iterate over
1165     # all maps in C++...
1166     sub change_all_map_light {
1167     my ($change) = @_;
1168    
1169 root 1.122 $_->change_map_light ($change)
1170     for grep $_->outdoor, values %cf::MAP;
1171 root 1.110 }
1172    
1173     sub try_load_header($) {
1174     my ($path) = @_;
1175    
1176     utf8::encode $path;
1177     aio_open $path, O_RDONLY, 0
1178     or return;
1179    
1180     my $map = cf::map::new
1181     or return;
1182    
1183     $map->load_header ($path)
1184     or return;
1185    
1186     $map->{load_path} = $path;
1187    
1188     $map
1189     }
1190    
1191 root 1.126 sub find_map;
1192 root 1.110 sub find_map {
1193     my ($path, $origin) = @_;
1194    
1195     #warn "find_map<$path,$origin>\n";#d#
1196    
1197 root 1.112 $path = new cf::path $path, $origin && $origin->path;
1198 root 1.110 my $key = $path->as_string;
1199    
1200 root 1.120 cf::lock_wait "map_find:$key";
1201    
1202 root 1.110 $cf::MAP{$key} || do {
1203 root 1.120 my $guard = cf::lock_acquire "map_find:$key";
1204    
1205 root 1.110 # do it the slow way
1206     my $map = try_load_header $path->save_path;
1207    
1208     if ($map) {
1209     # safety
1210     $map->{instantiate_time} = $cf::RUNTIME
1211     if $map->{instantiate_time} > $cf::RUNTIME;
1212     } else {
1213     if (my $rmp = $path->random_map_params) {
1214     $map = generate_random_map $key, $rmp;
1215     } else {
1216     $map = try_load_header $path->load_path;
1217     }
1218    
1219     $map or return;
1220    
1221 root 1.111 $map->{load_original} = 1;
1222 root 1.110 $map->{instantiate_time} = $cf::RUNTIME;
1223     $map->instantiate;
1224    
1225     # per-player maps become, after loading, normal maps
1226     $map->per_player (0) if $path->{user_rel};
1227     }
1228    
1229     $map->path ($key);
1230     $map->{path} = $path;
1231 root 1.116 $map->{last_save} = $cf::RUNTIME;
1232 root 1.110 $map->last_access ($cf::RUNTIME);
1233    
1234 root 1.112 if ($map->should_reset) {
1235     $map->reset;
1236 root 1.123 undef $guard;
1237 root 1.126 $map = find_map $path
1238 root 1.124 or return;
1239 root 1.112 }
1240 root 1.110
1241     $cf::MAP{$key} = $map
1242     }
1243     }
1244    
1245     sub load {
1246     my ($self) = @_;
1247    
1248 root 1.120 my $path = $self->{path};
1249     my $guard = cf::lock_acquire "map_load:" . $path->as_string;
1250    
1251 root 1.110 return if $self->in_memory != cf::MAP_SWAPPED;
1252    
1253     $self->in_memory (cf::MAP_LOADING);
1254    
1255     $self->alloc;
1256     $self->load_objects ($self->{load_path}, 1)
1257     or return;
1258    
1259 root 1.112 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
1260     if delete $self->{load_original};
1261 root 1.111
1262 root 1.110 if (my $uniq = $path->uniq_path) {
1263     utf8::encode $uniq;
1264     if (aio_open $uniq, O_RDONLY, 0) {
1265     $self->clear_unique_items;
1266     $self->load_objects ($uniq, 0);
1267     }
1268     }
1269    
1270     # now do the right thing for maps
1271     $self->link_multipart_objects;
1272    
1273     if ($self->{path}->is_style_map) {
1274     $self->{deny_save} = 1;
1275     $self->{deny_reset} = 1;
1276     } else {
1277     $self->fix_auto_apply;
1278     $self->decay_objects;
1279     $self->update_buttons;
1280     $self->set_darkness_map;
1281     $self->difficulty ($self->estimate_difficulty)
1282     unless $self->difficulty;
1283     $self->activate;
1284     }
1285    
1286     $self->in_memory (cf::MAP_IN_MEMORY);
1287     }
1288    
1289     sub load_map_sync {
1290     my ($path, $origin) = @_;
1291    
1292     #warn "load_map_sync<$path, $origin>\n";#d#
1293    
1294     cf::sync_job {
1295     my $map = cf::map::find_map $path, $origin
1296     or return;
1297     $map->load;
1298     $map
1299     }
1300     }
1301    
1302     sub save {
1303     my ($self) = @_;
1304    
1305     $self->{last_save} = $cf::RUNTIME;
1306    
1307     return unless $self->dirty;
1308    
1309 root 1.117 my $save = $self->{path}->save_path; utf8::encode $save;
1310     my $uniq = $self->{path}->uniq_path; utf8::encode $uniq;
1311    
1312 root 1.110 $self->{load_path} = $save;
1313    
1314     return if $self->{deny_save};
1315    
1316     if ($uniq) {
1317     $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
1318     $self->save_objects ($uniq, cf::IO_UNIQUES);
1319     } else {
1320     $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
1321     }
1322     }
1323    
1324     sub swap_out {
1325     my ($self) = @_;
1326    
1327     return if $self->players;
1328     return if $self->in_memory != cf::MAP_IN_MEMORY;
1329     return if $self->{deny_save};
1330    
1331     $self->save;
1332     $self->clear;
1333     $self->in_memory (cf::MAP_SWAPPED);
1334     }
1335    
1336 root 1.112 sub reset_at {
1337     my ($self) = @_;
1338 root 1.110
1339     # TODO: safety, remove and allow resettable per-player maps
1340 root 1.114 return 1e99 if $self->{path}{user_rel};
1341     return 1e99 if $self->{deny_reset};
1342 root 1.110
1343 root 1.112 my $time = $self->fixed_resettime ? $self->{instantiate_time} : $self->last_access;
1344 root 1.114 my $to = List::Util::min $MAX_RESET, $self->reset_timeout || $DEFAULT_RESET;
1345 root 1.110
1346 root 1.112 $time + $to
1347     }
1348    
1349     sub should_reset {
1350     my ($self) = @_;
1351    
1352     $self->reset_at <= $cf::RUNTIME
1353 root 1.111 }
1354    
1355     sub unlink_save {
1356     my ($self) = @_;
1357    
1358     utf8::encode (my $save = $self->{path}->save_path);
1359     aioreq_pri 3; IO::AIO::aio_unlink $save;
1360     aioreq_pri 3; IO::AIO::aio_unlink "$save.pst";
1361 root 1.110 }
1362    
1363 root 1.113 sub rename {
1364     my ($self, $new_path) = @_;
1365    
1366     $self->unlink_save;
1367    
1368     delete $cf::MAP{$self->path};
1369     $self->{path} = new cf::path $new_path;
1370 root 1.114 $self->path ($self->{path}->as_string);
1371 root 1.113 $cf::MAP{$self->path} = $self;
1372    
1373     $self->save;
1374     }
1375    
1376 root 1.110 sub reset {
1377     my ($self) = @_;
1378    
1379     return if $self->players;
1380     return if $self->{path}{user_rel};#d#
1381    
1382     warn "resetting map ", $self->path;#d#
1383    
1384 root 1.111 delete $cf::MAP{$self->path};
1385 root 1.110
1386     $_->clear_links_to ($self) for values %cf::MAP;
1387    
1388 root 1.111 $self->unlink_save;
1389     $self->destroy;
1390 root 1.110 }
1391    
1392 root 1.114 my $nuke_counter = "aaaa";
1393    
1394     sub nuke {
1395     my ($self) = @_;
1396    
1397     $self->{deny_save} = 1;
1398     $self->reset_timeout (1);
1399     $self->rename ("{nuke}/" . ($nuke_counter++));
1400     $self->reset; # polite request, might not happen
1401     }
1402    
1403 root 1.110 sub customise_for {
1404     my ($map, $ob) = @_;
1405    
1406     if ($map->per_player) {
1407     return cf::map::find_map "~" . $ob->name . "/" . $map->{path}{path};
1408     }
1409    
1410     $map
1411     }
1412    
1413     sub emergency_save {
1414     local $cf::FREEZE = 1;
1415    
1416     warn "enter emergency map save\n";
1417    
1418     cf::sync_job {
1419     warn "begin emergency map save\n";
1420     $_->save for values %cf::MAP;
1421     };
1422    
1423     warn "end emergency map save\n";
1424     }
1425    
1426     package cf;
1427    
1428     =back
1429    
1430    
1431 root 1.95 =head3 cf::object::player
1432    
1433     =over 4
1434    
1435 root 1.79 =item $player_object->reply ($npc, $msg[, $flags])
1436 root 1.28
1437     Sends a message to the player, as if the npc C<$npc> replied. C<$npc>
1438     can be C<undef>. Does the right thing when the player is currently in a
1439     dialogue with the given NPC character.
1440    
1441     =cut
1442    
1443 root 1.22 # rough implementation of a future "reply" method that works
1444     # with dialog boxes.
1445 root 1.95 #TODO: the first argument must go, split into a $npc->reply_to ( method
1446 root 1.23 sub cf::object::player::reply($$$;$) {
1447     my ($self, $npc, $msg, $flags) = @_;
1448    
1449     $flags = cf::NDI_BROWN | cf::NDI_UNIQUE unless @_ >= 4;
1450 root 1.22
1451 root 1.24 if ($self->{record_replies}) {
1452     push @{ $self->{record_replies} }, [$npc, $msg, $flags];
1453     } else {
1454     $msg = $npc->name . " says: $msg" if $npc;
1455     $self->message ($msg, $flags);
1456     }
1457 root 1.22 }
1458    
1459 root 1.79 =item $player_object->may ("access")
1460    
1461     Returns wether the given player is authorized to access resource "access"
1462     (e.g. "command_wizcast").
1463    
1464     =cut
1465    
1466     sub cf::object::player::may {
1467     my ($self, $access) = @_;
1468    
1469     $self->flag (cf::FLAG_WIZ) ||
1470     (ref $cf::CFG{"may_$access"}
1471     ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}}
1472     : $cf::CFG{"may_$access"})
1473     }
1474 root 1.70
1475 root 1.115 =item $player_object->enter_link
1476    
1477     Freezes the player and moves him/her to a special map (C<{link}>).
1478    
1479     The player should be reaosnably safe there for short amounts of time. You
1480     I<MUST> call C<leave_link> as soon as possible, though.
1481    
1482     =item $player_object->leave_link ($map, $x, $y)
1483    
1484     Moves the player out of the specila link map onto the given map. If the
1485     map is not valid (or omitted), the player will be moved back to the
1486     location he/she was before the call to C<enter_link>, or, if that fails,
1487     to the emergency map position.
1488    
1489     Might block.
1490    
1491     =cut
1492    
1493 root 1.110 sub cf::object::player::enter_link {
1494     my ($self) = @_;
1495    
1496 root 1.120 $self->deactivate_recursive;
1497    
1498 root 1.110 return if $self->map == $LINK_MAP;
1499    
1500 root 1.120 $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y]
1501 root 1.110 if $self->map;
1502    
1503     $self->enter_map ($LINK_MAP, 20, 20);
1504     }
1505    
1506     sub cf::object::player::leave_link {
1507     my ($self, $map, $x, $y) = @_;
1508    
1509     my $link_pos = delete $self->{_link_pos};
1510    
1511     unless ($map) {
1512     # restore original map position
1513     ($map, $x, $y) = @{ $link_pos || [] };
1514     $map = cf::map::find_map $map;
1515    
1516     unless ($map) {
1517     ($map, $x, $y) = @$EMERGENCY_POSITION;
1518     $map = cf::map::find_map $map
1519     or die "FATAL: cannot load emergency map\n";
1520     }
1521     }
1522    
1523     ($x, $y) = (-1, -1)
1524     unless (defined $x) && (defined $y);
1525    
1526     # use -1 or undef as default coordinates, not 0, 0
1527     ($x, $y) = ($map->enter_x, $map->enter_y)
1528     if $x <=0 && $y <= 0;
1529    
1530     $map->load;
1531    
1532     $self->activate_recursive;
1533     $self->enter_map ($map, $x, $y);
1534     }
1535    
1536 root 1.120 cf::player->attach (
1537     on_logout => sub {
1538     my ($pl) = @_;
1539    
1540     # abort map switching before logout
1541     if ($pl->ob->{_link_pos}) {
1542     cf::sync_job {
1543     $pl->ob->leave_link
1544     };
1545     }
1546     },
1547     on_login => sub {
1548     my ($pl) = @_;
1549    
1550     # try to abort aborted map switching on player login :)
1551     # should happen only on crashes
1552     if ($pl->ob->{_link_pos}) {
1553     $pl->ob->enter_link;
1554 root 1.127 cf::async {
1555 root 1.120 # we need this sleep as the login has a concurrent enter_exit running
1556     # and this sleep increases chances of the player not ending up in scorn
1557     Coro::Timer::sleep 1;
1558     $pl->ob->leave_link;
1559     };
1560     }
1561     },
1562     );
1563    
1564 root 1.118 =item $player_object->goto_map ($path, $x, $y)
1565 root 1.110
1566     =cut
1567    
1568     sub cf::object::player::goto_map {
1569     my ($self, $path, $x, $y) = @_;
1570    
1571     $self->enter_link;
1572    
1573 root 1.127 (cf::async {
1574 root 1.110 $path = new cf::path $path;
1575    
1576     my $map = cf::map::find_map $path->as_string;
1577     $map = $map->customise_for ($self) if $map;
1578    
1579 root 1.119 # warn "entering ", $map->path, " at ($x, $y)\n"
1580     # if $map;
1581 root 1.110
1582 root 1.115 $map or $self->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED);
1583    
1584 root 1.110 $self->leave_link ($map, $x, $y);
1585     })->prio (1);
1586     }
1587    
1588     =item $player_object->enter_exit ($exit_object)
1589    
1590     =cut
1591    
1592     sub parse_random_map_params {
1593     my ($spec) = @_;
1594    
1595     my $rmp = { # defaults
1596     xsize => 10,
1597     ysize => 10,
1598     };
1599    
1600     for (split /\n/, $spec) {
1601     my ($k, $v) = split /\s+/, $_, 2;
1602    
1603     $rmp->{lc $k} = $v if (length $k) && (length $v);
1604     }
1605    
1606     $rmp
1607     }
1608    
1609     sub prepare_random_map {
1610     my ($exit) = @_;
1611    
1612     # all this does is basically replace the /! path by
1613     # a new random map path (?random/...) with a seed
1614     # that depends on the exit object
1615    
1616     my $rmp = parse_random_map_params $exit->msg;
1617    
1618     if ($exit->map) {
1619     $rmp->{region} = $exit->map->region_name;
1620     $rmp->{origin_map} = $exit->map->path;
1621     $rmp->{origin_x} = $exit->x;
1622     $rmp->{origin_y} = $exit->y;
1623     }
1624    
1625     $rmp->{random_seed} ||= $exit->random_seed;
1626    
1627     my $data = cf::to_json $rmp;
1628     my $md5 = Digest::MD5::md5_hex $data;
1629    
1630     if (my $fh = aio_open "$cf::RANDOM_MAPS/$md5.meta", O_WRONLY | O_CREAT, 0666) {
1631     aio_write $fh, 0, (length $data), $data, 0;
1632    
1633     $exit->slaying ("?random/$md5");
1634     $exit->msg (undef);
1635     }
1636     }
1637    
1638     sub cf::object::player::enter_exit {
1639     my ($self, $exit) = @_;
1640    
1641     return unless $self->type == cf::PLAYER;
1642    
1643     $self->enter_link;
1644    
1645 root 1.127 (cf::async {
1646 root 1.110 unless (eval {
1647     prepare_random_map $exit
1648     if $exit->slaying eq "/!";
1649    
1650     my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path;
1651     $self->goto_map ($path, $exit->stats->hp, $exit->stats->sp);
1652    
1653     1;
1654     }) {
1655     $self->message ("Something went wrong deep within the crossfire server. "
1656     . "I'll try to bring you back to the map you were before. "
1657     . "Please report this to the dungeon master",
1658     cf::NDI_UNIQUE | cf::NDI_RED);
1659    
1660     warn "ERROR in enter_exit: $@";
1661     $self->leave_link;
1662     }
1663     })->prio (1);
1664     }
1665    
1666 root 1.95 =head3 cf::client
1667    
1668     =over 4
1669    
1670     =item $client->send_drawinfo ($text, $flags)
1671    
1672     Sends a drawinfo packet to the client. Circumvents output buffering so
1673     should not be used under normal circumstances.
1674    
1675 root 1.70 =cut
1676    
1677 root 1.95 sub cf::client::send_drawinfo {
1678     my ($self, $text, $flags) = @_;
1679    
1680     utf8::encode $text;
1681     $self->send_packet (sprintf "drawinfo %d %s", $flags, $text);
1682     }
1683    
1684    
1685     =item $success = $client->query ($flags, "text", \&cb)
1686    
1687     Queues a query to the client, calling the given callback with
1688     the reply text on a reply. flags can be C<cf::CS_QUERY_YESNO>,
1689     C<cf::CS_QUERY_SINGLECHAR> or C<cf::CS_QUERY_HIDEINPUT> or C<0>.
1690    
1691     Queries can fail, so check the return code. Or don't, as queries will become
1692     reliable at some point in the future.
1693    
1694     =cut
1695    
1696     sub cf::client::query {
1697     my ($self, $flags, $text, $cb) = @_;
1698    
1699     return unless $self->state == ST_PLAYING
1700     || $self->state == ST_SETUP
1701     || $self->state == ST_CUSTOM;
1702    
1703     $self->state (ST_CUSTOM);
1704    
1705     utf8::encode $text;
1706     push @{ $self->{query_queue} }, [(sprintf "query %d %s", $flags, $text), $cb];
1707    
1708     $self->send_packet ($self->{query_queue}[0][0])
1709     if @{ $self->{query_queue} } == 1;
1710     }
1711    
1712     cf::client->attach (
1713     on_reply => sub {
1714     my ($ns, $msg) = @_;
1715    
1716     # this weird shuffling is so that direct followup queries
1717     # get handled first
1718     my $queue = delete $ns->{query_queue};
1719    
1720     (shift @$queue)->[1]->($msg);
1721    
1722     push @{ $ns->{query_queue} }, @$queue;
1723    
1724     if (@{ $ns->{query_queue} } == @$queue) {
1725     if (@$queue) {
1726     $ns->send_packet ($ns->{query_queue}[0][0]);
1727     } else {
1728 root 1.98 $ns->state (ST_PLAYING) if $ns->state == ST_CUSTOM;
1729 root 1.95 }
1730     }
1731     },
1732     );
1733    
1734 root 1.96 =item $client->coro (\&cb)
1735    
1736     Create a new coroutine, running the specified callback. The coroutine will
1737     be automatically cancelled when the client gets destroyed (e.g. on logout,
1738     or loss of connection).
1739    
1740     =cut
1741    
1742     sub cf::client::coro {
1743     my ($self, $cb) = @_;
1744    
1745 root 1.127 my $coro = &cf::async ($cb);
1746 root 1.103
1747     $coro->on_destroy (sub {
1748 root 1.96 delete $self->{_coro}{$coro+0};
1749 root 1.103 });
1750 root 1.96
1751     $self->{_coro}{$coro+0} = $coro;
1752 root 1.103
1753     $coro
1754 root 1.96 }
1755    
1756     cf::client->attach (
1757     on_destroy => sub {
1758     my ($ns) = @_;
1759    
1760 root 1.97 $_->cancel for values %{ (delete $ns->{_coro}) || {} };
1761 root 1.96 },
1762     );
1763    
1764 root 1.95 =back
1765    
1766 root 1.70
1767     =head2 SAFE SCRIPTING
1768    
1769     Functions that provide a safe environment to compile and execute
1770     snippets of perl code without them endangering the safety of the server
1771     itself. Looping constructs, I/O operators and other built-in functionality
1772     is not available in the safe scripting environment, and the number of
1773 root 1.79 functions and methods that can be called is greatly reduced.
1774 root 1.70
1775     =cut
1776 root 1.23
1777 root 1.42 our $safe = new Safe "safe";
1778 root 1.23 our $safe_hole = new Safe::Hole;
1779    
1780     $SIG{FPE} = 'IGNORE';
1781    
1782     $safe->permit_only (Opcode::opset qw(:base_core :base_mem :base_orig :base_math sort time));
1783    
1784 root 1.25 # here we export the classes and methods available to script code
1785    
1786 root 1.70 =pod
1787    
1788     The following fucntions and emthods are available within a safe environment:
1789    
1790 elmex 1.91 cf::object contr pay_amount pay_player map
1791 root 1.70 cf::object::player player
1792     cf::player peaceful
1793 elmex 1.91 cf::map trigger
1794 root 1.70
1795     =cut
1796    
1797 root 1.25 for (
1798 elmex 1.91 ["cf::object" => qw(contr pay_amount pay_player map)],
1799 root 1.25 ["cf::object::player" => qw(player)],
1800     ["cf::player" => qw(peaceful)],
1801 elmex 1.91 ["cf::map" => qw(trigger)],
1802 root 1.25 ) {
1803     no strict 'refs';
1804     my ($pkg, @funs) = @$_;
1805 root 1.41 *{"safe::$pkg\::$_"} = $safe_hole->wrap (\&{"$pkg\::$_"})
1806 root 1.25 for @funs;
1807     }
1808 root 1.23
1809 root 1.70 =over 4
1810    
1811     =item @retval = safe_eval $code, [var => value, ...]
1812    
1813     Compiled and executes the given perl code snippet. additional var/value
1814     pairs result in temporary local (my) scalar variables of the given name
1815     that are available in the code snippet. Example:
1816    
1817     my $five = safe_eval '$first + $second', first => 1, second => 4;
1818    
1819     =cut
1820    
1821 root 1.23 sub safe_eval($;@) {
1822     my ($code, %vars) = @_;
1823    
1824     my $qcode = $code;
1825     $qcode =~ s/"/‟/g; # not allowed in #line filenames
1826     $qcode =~ s/\n/\\n/g;
1827    
1828     local $_;
1829 root 1.41 local @safe::cf::_safe_eval_args = values %vars;
1830 root 1.23
1831 root 1.42 my $eval =
1832 root 1.23 "do {\n"
1833     . "my (" . (join ",", map "\$$_", keys %vars) . ") = \@cf::_safe_eval_args;\n"
1834     . "#line 0 \"{$qcode}\"\n"
1835     . $code
1836     . "\n}"
1837 root 1.25 ;
1838    
1839     sub_generation_inc;
1840 root 1.42 my @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval);
1841 root 1.25 sub_generation_inc;
1842    
1843 root 1.42 if ($@) {
1844     warn "$@";
1845     warn "while executing safe code '$code'\n";
1846     warn "with arguments " . (join " ", %vars) . "\n";
1847     }
1848    
1849 root 1.25 wantarray ? @res : $res[0]
1850 root 1.23 }
1851    
1852 root 1.69 =item cf::register_script_function $function => $cb
1853    
1854     Register a function that can be called from within map/npc scripts. The
1855     function should be reasonably secure and should be put into a package name
1856     like the extension.
1857    
1858     Example: register a function that gets called whenever a map script calls
1859     C<rent::overview>, as used by the C<rent> extension.
1860    
1861     cf::register_script_function "rent::overview" => sub {
1862     ...
1863     };
1864    
1865     =cut
1866    
1867 root 1.23 sub register_script_function {
1868     my ($fun, $cb) = @_;
1869    
1870     no strict 'refs';
1871 root 1.41 *{"safe::$fun"} = $safe_hole->wrap ($cb);
1872 root 1.23 }
1873    
1874 root 1.70 =back
1875    
1876 root 1.71 =cut
1877    
1878 root 1.23 #############################################################################
1879 root 1.65
1880     =head2 EXTENSION DATABASE SUPPORT
1881    
1882     Crossfire maintains a very simple database for extension use. It can
1883     currently store anything that can be serialised using Storable, which
1884     excludes objects.
1885    
1886     The parameter C<$family> should best start with the name of the extension
1887     using it, it should be unique.
1888    
1889     =over 4
1890    
1891     =item $hashref = cf::db_get $family
1892    
1893     Return a hashref for use by the extension C<$family>, which can be
1894     modified. After modifications, you have to call C<cf::db_dirty> or
1895     C<cf::db_sync>.
1896    
1897     =item $value = cf::db_get $family => $key
1898    
1899     Returns a single value from the database
1900    
1901     =item cf::db_put $family => $hashref
1902    
1903     Stores the given family hashref into the database. Updates are delayed, if
1904     you want the data to be synced to disk immediately, use C<cf::db_sync>.
1905    
1906     =item cf::db_put $family => $key => $value
1907    
1908     Stores the given C<$value> in the family hash. Updates are delayed, if you
1909     want the data to be synced to disk immediately, use C<cf::db_sync>.
1910    
1911     =item cf::db_dirty
1912    
1913     Marks the database as dirty, to be updated at a later time.
1914    
1915     =item cf::db_sync
1916    
1917     Immediately write the database to disk I<if it is dirty>.
1918    
1919     =cut
1920    
1921 root 1.78 our $DB;
1922    
1923 root 1.65 {
1924 root 1.66 my $path = cf::localdir . "/database.pst";
1925 root 1.65
1926     sub db_load() {
1927 root 1.78 $DB = stat $path ? Storable::retrieve $path : { };
1928 root 1.65 }
1929    
1930     my $pid;
1931    
1932     sub db_save() {
1933     waitpid $pid, 0 if $pid;
1934 root 1.67 if (0 == ($pid = fork)) {
1935 root 1.78 $DB->{_meta}{version} = 1;
1936     Storable::nstore $DB, "$path~";
1937 root 1.65 rename "$path~", $path;
1938     cf::_exit 0 if defined $pid;
1939     }
1940     }
1941    
1942     my $dirty;
1943    
1944     sub db_sync() {
1945     db_save if $dirty;
1946     undef $dirty;
1947     }
1948    
1949 root 1.87 my $idle = Event->idle (min => $TICK * 2.8, max => 10, repeat => 0, data => WF_AUTOCANCEL, cb => sub {
1950 root 1.65 db_sync;
1951     });
1952    
1953     sub db_dirty() {
1954     $dirty = 1;
1955     $idle->start;
1956     }
1957    
1958     sub db_get($;$) {
1959     @_ >= 2
1960 root 1.78 ? $DB->{$_[0]}{$_[1]}
1961     : ($DB->{$_[0]} ||= { })
1962 root 1.65 }
1963    
1964     sub db_put($$;$) {
1965     if (@_ >= 3) {
1966 root 1.78 $DB->{$_[0]}{$_[1]} = $_[2];
1967 root 1.65 } else {
1968 root 1.78 $DB->{$_[0]} = $_[1];
1969 root 1.65 }
1970     db_dirty;
1971     }
1972 root 1.67
1973 root 1.93 cf::global->attach (
1974     prio => 10000,
1975 root 1.67 on_cleanup => sub {
1976     db_sync;
1977     },
1978 root 1.93 );
1979 root 1.65 }
1980    
1981     #############################################################################
1982 root 1.34 # the server's main()
1983    
1984 root 1.73 sub cfg_load {
1985 root 1.72 open my $fh, "<:utf8", cf::confdir . "/config"
1986     or return;
1987    
1988     local $/;
1989     *CFG = YAML::Syck::Load <$fh>;
1990     }
1991    
1992 root 1.39 sub main {
1993 root 1.108 # we must not ever block the main coroutine
1994     local $Coro::idle = sub {
1995 root 1.115 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
1996 root 1.108 (Coro::unblock_sub {
1997     Event::one_event;
1998     })->();
1999     };
2000    
2001 root 1.73 cfg_load;
2002 root 1.65 db_load;
2003 root 1.61 load_extensions;
2004 root 1.34 Event::loop;
2005     }
2006    
2007     #############################################################################
2008 root 1.22 # initialisation
2009    
2010 root 1.111 sub reload() {
2011 root 1.106 # can/must only be called in main
2012     if ($Coro::current != $Coro::main) {
2013     warn "can only reload from main coroutine\n";
2014     return;
2015     }
2016    
2017 root 1.103 warn "reloading...";
2018    
2019 root 1.106 local $FREEZE = 1;
2020     cf::emergency_save;
2021    
2022 root 1.103 eval {
2023 root 1.106 # if anything goes wrong in here, we should simply crash as we already saved
2024 root 1.65
2025     # cancel all watchers
2026 root 1.87 for (Event::all_watchers) {
2027     $_->cancel if $_->data & WF_AUTOCANCEL;
2028     }
2029 root 1.65
2030 root 1.103 # cancel all extension coros
2031     $_->cancel for values %EXT_CORO;
2032     %EXT_CORO = ();
2033    
2034 root 1.65 # unload all extensions
2035     for (@exts) {
2036 root 1.103 warn "unloading <$_>";
2037 root 1.65 unload_extension $_;
2038     }
2039    
2040     # unload all modules loaded from $LIBDIR
2041     while (my ($k, $v) = each %INC) {
2042     next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
2043    
2044 root 1.103 warn "removing <$k>";
2045 root 1.65 delete $INC{$k};
2046    
2047     $k =~ s/\.pm$//;
2048     $k =~ s/\//::/g;
2049    
2050     if (my $cb = $k->can ("unload_module")) {
2051     $cb->();
2052     }
2053    
2054     Symbol::delete_package $k;
2055     }
2056    
2057     # sync database to disk
2058     cf::db_sync;
2059 root 1.103 IO::AIO::flush;
2060 root 1.65
2061     # get rid of safe::, as good as possible
2062     Symbol::delete_package "safe::$_"
2063 root 1.103 for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region);
2064 root 1.65
2065     # remove register_script_function callbacks
2066     # TODO
2067    
2068     # unload cf.pm "a bit"
2069     delete $INC{"cf.pm"};
2070    
2071     # don't, removes xs symbols, too,
2072     # and global variables created in xs
2073     #Symbol::delete_package __PACKAGE__;
2074    
2075     # reload cf.pm
2076 root 1.103 warn "reloading cf.pm";
2077 root 1.65 require cf;
2078 root 1.100 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
2079    
2080 root 1.73 # load config and database again
2081     cf::cfg_load;
2082 root 1.65 cf::db_load;
2083    
2084     # load extensions
2085 root 1.103 warn "load extensions";
2086 root 1.65 cf::load_extensions;
2087    
2088     # reattach attachments to objects
2089 root 1.103 warn "reattach";
2090 root 1.65 _global_reattach;
2091     };
2092    
2093 root 1.106 if ($@) {
2094     warn $@;
2095     warn "error while reloading, exiting.";
2096     exit 1;
2097     }
2098    
2099     warn "reloaded successfully";
2100 root 1.65 };
2101    
2102 root 1.108 #############################################################################
2103    
2104     unless ($LINK_MAP) {
2105     $LINK_MAP = cf::map::new;
2106    
2107     $LINK_MAP->width (41);
2108     $LINK_MAP->height (41);
2109     $LINK_MAP->alloc;
2110     $LINK_MAP->path ("{link}");
2111     $LINK_MAP->{path} = bless { path => "{link}" }, "cf::path";
2112     $LINK_MAP->in_memory (MAP_IN_MEMORY);
2113 root 1.110
2114     # dirty hack because... archetypes are not yet loaded
2115     Event->timer (
2116     after => 2,
2117     cb => sub {
2118     $_[0]->w->cancel;
2119    
2120     # provide some exits "home"
2121     my $exit = cf::object::new "exit";
2122    
2123     $exit->slaying ($EMERGENCY_POSITION->[0]);
2124     $exit->stats->hp ($EMERGENCY_POSITION->[1]);
2125     $exit->stats->sp ($EMERGENCY_POSITION->[2]);
2126    
2127     $LINK_MAP->insert ($exit->clone, 19, 19);
2128     $LINK_MAP->insert ($exit->clone, 19, 20);
2129     $LINK_MAP->insert ($exit->clone, 19, 21);
2130     $LINK_MAP->insert ($exit->clone, 20, 19);
2131     $LINK_MAP->insert ($exit->clone, 20, 21);
2132     $LINK_MAP->insert ($exit->clone, 21, 19);
2133     $LINK_MAP->insert ($exit->clone, 21, 20);
2134     $LINK_MAP->insert ($exit->clone, 21, 21);
2135    
2136     $exit->destroy;
2137     });
2138    
2139     $LINK_MAP->{deny_save} = 1;
2140     $LINK_MAP->{deny_reset} = 1;
2141    
2142     $cf::MAP{$LINK_MAP->path} = $LINK_MAP;
2143 root 1.108 }
2144    
2145 root 1.85 register "<global>", __PACKAGE__;
2146    
2147 root 1.111 register_command "reload" => sub {
2148 root 1.65 my ($who, $arg) = @_;
2149    
2150     if ($who->flag (FLAG_WIZ)) {
2151 root 1.107 $who->message ("start of reload.");
2152 root 1.111 reload;
2153 root 1.107 $who->message ("end of reload.");
2154 root 1.65 }
2155     };
2156    
2157 root 1.27 unshift @INC, $LIBDIR;
2158 root 1.17
2159 root 1.35 $TICK_WATCHER = Event->timer (
2160 root 1.104 reentrant => 0,
2161     prio => 0,
2162     at => $NEXT_TICK || $TICK,
2163     data => WF_AUTOCANCEL,
2164     cb => sub {
2165 root 1.103 unless ($FREEZE) {
2166     cf::server_tick; # one server iteration
2167     $RUNTIME += $TICK;
2168     }
2169 root 1.35
2170     $NEXT_TICK += $TICK;
2171    
2172 root 1.78 # if we are delayed by four ticks or more, skip them all
2173 root 1.103 $NEXT_TICK = Event::time if Event::time >= $NEXT_TICK + $TICK * 4;
2174 root 1.35
2175     $TICK_WATCHER->at ($NEXT_TICK);
2176     $TICK_WATCHER->start;
2177     },
2178     );
2179    
2180 root 1.80 IO::AIO::max_poll_time $TICK * 0.2;
2181 root 1.77
2182 root 1.108 Event->io (
2183     fd => IO::AIO::poll_fileno,
2184     poll => 'r',
2185     prio => 5,
2186     data => WF_AUTOCANCEL,
2187     cb => \&IO::AIO::poll_cb,
2188     );
2189    
2190     Event->timer (
2191     data => WF_AUTOCANCEL,
2192     after => 0,
2193     interval => 10,
2194     cb => sub {
2195     (Coro::unblock_sub {
2196     write_runtime
2197     or warn "ERROR: unable to write runtime file: $!";
2198     })->();
2199     },
2200     );
2201 root 1.103
2202 root 1.125 END { cf::emergency_save }
2203    
2204 root 1.1 1
2205