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

Comparing deliantra/server/lib/cf.pm (file contents):
Revision 1.154 by root, Tue Jan 9 15:36:19 2007 UTC vs.
Revision 1.539 by root, Tue May 4 22:49:21 2010 UTC

1#
2# This file is part of Deliantra, the Roguelike Realtime MMORPG.
3#
4# Copyright (©) 2006,2007,2008,2009,2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
5#
6# Deliantra is free software: you can redistribute it and/or modify it under
7# the terms of the Affero GNU General Public License as published by the
8# Free Software Foundation, either version 3 of the License, or (at your
9# option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the Affero GNU General Public License
17# and the GNU General Public License along with this program. If not, see
18# <http://www.gnu.org/licenses/>.
19#
20# The authors can be reached via e-mail to <support@deliantra.net>
21#
22
1package cf; 23package cf;
2 24
3use utf8; 25use common::sense;
4use strict;
5 26
6use Symbol; 27use Symbol;
7use List::Util; 28use List::Util;
8use Storable; 29use Socket;
30use EV;
9use Opcode; 31use Opcode;
10use Safe; 32use Safe;
11use Safe::Hole; 33use Safe::Hole;
34use Storable ();
35use Carp ();
12 36
37use Guard ();
13use Coro 3.3 (); 38use Coro ();
39use Coro::State;
40use Coro::Handle;
41use Coro::EV;
14use Coro::Event; 42use Coro::AnyEvent;
15use Coro::Timer; 43use Coro::Timer;
16use Coro::Signal; 44use Coro::Signal;
17use Coro::Semaphore; 45use Coro::Semaphore;
46use Coro::SemaphoreSet;
47use Coro::AnyEvent;
18use Coro::AIO; 48use Coro::AIO;
49use Coro::BDB 1.6;
50use Coro::Storable;
51use Coro::Util ();
19 52
53use JSON::XS 2.01 ();
54use BDB ();
20use Data::Dumper; 55use Data::Dumper;
21use Digest::MD5;
22use Fcntl; 56use Fcntl;
23use IO::AIO 2.32 ();
24use YAML::Syck (); 57use YAML::XS ();
58use IO::AIO ();
25use Time::HiRes; 59use Time::HiRes;
60use Compress::LZF;
61use Digest::MD5 ();
26 62
27use Event; $Event::Eval = 1; # no idea why this is required, but it is 63AnyEvent::detect;
28 64
29# work around bug in YAML::Syck - bad news for perl6, will it be as broken wrt. unicode? 65# configure various modules to our taste
30$YAML::Syck::ImplicitUnicode = 1; 66#
67$Storable::canonical = 1; # reduce rsync transfers
68Coro::State::cctx_stacksize 256000; # 1-2MB stack, for deep recursions in maze generator
31 69
32$Coro::main->prio (Coro::PRIO_MAX); # run main coroutine ("the server") with very high priority 70$Coro::main->prio (Coro::PRIO_MAX); # run main coroutine ("the server") with very high priority
71
72# make sure c-lzf reinitialises itself
73Compress::LZF::set_serializer "Storable", "Storable::net_mstore", "Storable::mretrieve";
74Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later
75
76# strictly for debugging
77$SIG{QUIT} = sub { Carp::cluck "SIGQUIT" };
33 78
34sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload 79sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
35 80
36our %COMMAND = (); 81our %COMMAND = ();
37our %COMMAND_TIME = (); 82our %COMMAND_TIME = ();
83
84our @EXTS = (); # list of extension package names
38our %EXTCMD = (); 85our %EXTCMD = ();
86our %EXTICMD = ();
87our %EXT_CORO = (); # coroutines bound to extensions
88our %EXT_MAP = (); # pluggable maps
39 89
90our $RELOAD; # number of reloads so far, non-zero while in reload
40our @EVENT; 91our @EVENT;
41our $LIBDIR = datadir . "/ext"; 92our @REFLECT; # set by XS
93our %REFLECT; # set by us
42 94
43our $TICK = MAX_TIME * 1e-6; 95our $CONFDIR = confdir;
44our $TICK_WATCHER; 96our $DATADIR = datadir;
97our $LIBDIR = "$DATADIR/ext";
98our $PODDIR = "$DATADIR/pod";
99our $MAPDIR = "$DATADIR/" . mapdir;
100our $LOCALDIR = localdir;
101our $TMPDIR = "$LOCALDIR/" . tmpdir;
102our $UNIQUEDIR = "$LOCALDIR/" . uniquedir;
103our $PLAYERDIR = "$LOCALDIR/" . playerdir;
104our $RANDOMDIR = "$LOCALDIR/random";
105our $BDBDIR = "$LOCALDIR/db";
106our $PIDFILE = "$LOCALDIR/pid";
107our $RUNTIMEFILE = "$LOCALDIR/runtime";
108
109our %RESOURCE; # unused
110
111our $OUTPUT_RATE_MIN = 3000;
112our $OUTPUT_RATE_MAX = 1000000;
113
114our $MAX_LINKS = 32; # how many chained exits to follow
115our $VERBOSE_IO = 1;
116
117our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!)
118our $NEXT_RUNTIME_WRITE; # when should the runtime file be written
45our $NEXT_TICK; 119our $NEXT_TICK;
46our $NOW; 120our $USE_FSYNC = 1; # use fsync to write maps - default on
121
122our $BDB_DEADLOCK_WATCHER;
123our $BDB_CHECKPOINT_WATCHER;
124our $BDB_TRICKLE_WATCHER;
125our $DB_ENV;
126
127our @EXTRA_MODULES = qw(pod match mapscript);
47 128
48our %CFG; 129our %CFG;
49 130
50our $UPTIME; $UPTIME ||= time; 131our $UPTIME; $UPTIME ||= time;
51our $RUNTIME; 132our $RUNTIME;
133our $NOW;
52 134
53our %PLAYER; # all users 135our (%PLAYER, %PLAYER_LOADING); # all users
54our %MAP; # all maps 136our (%MAP, %MAP_LOADING ); # all maps
55our $LINK_MAP; # the special {link} map 137our $LINK_MAP; # the special {link} map, which is always available
56our $RANDOM_MAPS = cf::localdir . "/random"; 138
57our %EXT_CORO; # coroutines bound to extensions 139# used to convert map paths into valid unix filenames by replacing / by ∕
140our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons
141
142our $LOAD; # a number between 0 (idle) and 1 (too many objects)
143our $LOADAVG; # same thing, but with alpha-smoothing
144our $JITTER; # average jitter
145our $TICK_START; # for load detecting purposes
146
147our @POST_INIT;
148
149our $REATTACH_ON_RELOAD; # set to true to force object reattach on reload (slow)
150our $REALLY_UNLOOP; # never set to true, please :)
58 151
59binmode STDOUT; 152binmode STDOUT;
60binmode STDERR; 153binmode STDERR;
61 154
62# read virtual server time, if available 155# read virtual server time, if available
63unless ($RUNTIME || !-e cf::localdir . "/runtime") { 156unless ($RUNTIME || !-e $RUNTIMEFILE) {
64 open my $fh, "<", cf::localdir . "/runtime" 157 open my $fh, "<", $RUNTIMEFILE
65 or die "unable to read runtime file: $!"; 158 or die "unable to read $RUNTIMEFILE file: $!";
66 $RUNTIME = <$fh> + 0.; 159 $RUNTIME = <$fh> + 0.;
67} 160}
68 161
69mkdir cf::localdir; 162eval "sub TICK() { $TICK } 1" or die;
70mkdir cf::localdir . "/" . cf::playerdir;
71mkdir cf::localdir . "/" . cf::tmpdir;
72mkdir cf::localdir . "/" . cf::uniquedir;
73mkdir $RANDOM_MAPS;
74 163
75# a special map that is always available 164mkdir $_
76our $LINK_MAP; 165 for $LOCALDIR, $TMPDIR, $UNIQUEDIR, $PLAYERDIR, $RANDOMDIR, $BDBDIR;
166
77our $EMERGENCY_POSITION; 167our $EMERGENCY_POSITION;
168
169sub cf::map::normalise;
170
171sub in_main() {
172 $Coro::current == $Coro::main
173}
174
175#############################################################################
176
177%REFLECT = ();
178for (@REFLECT) {
179 my $reflect = JSON::XS::decode_json $_;
180 $REFLECT{$reflect->{class}} = $reflect;
181}
182
183# this is decidedly evil
184$REFLECT{object}{flags} = { map +($_ => undef), grep $_, map /^FLAG_([A-Z0-9_]+)$/ && lc $1, keys %{"cf::"} };
78 185
79############################################################################# 186#############################################################################
80 187
81=head2 GLOBAL VARIABLES 188=head2 GLOBAL VARIABLES
82 189
89=item $cf::RUNTIME 196=item $cf::RUNTIME
90 197
91The time this server has run, starts at 0 and is increased by $cf::TICK on 198The time this server has run, starts at 0 and is increased by $cf::TICK on
92every server tick. 199every server tick.
93 200
94=item $cf::LIBDIR 201=item $cf::CONFDIR $cf::DATADIR $cf::LIBDIR $cf::PODDIR
202$cf::MAPDIR $cf::LOCALDIR $cf::TMPDIR $cf::UNIQUEDIR
203$cf::PLAYERDIR $cf::RANDOMDIR $cf::BDBDIR
95 204
96The perl library directory, where extensions and cf-specific modules can 205Various directories - "/etc", read-only install directory, perl-library
97be found. It will be added to C<@INC> automatically. 206directory, pod-directory, read-only maps directory, "/var", "/var/tmp",
207unique-items directory, player file directory, random maps directory and
208database environment.
98 209
99=item $cf::NOW 210=item $cf::NOW
100 211
101The time of the last (current) server tick. 212The time of the last (current) server tick.
102 213
103=item $cf::TICK 214=item $cf::TICK
104 215
105The interval between server ticks, in seconds. 216The interval between server ticks, in seconds.
106 217
218=item $cf::LOADAVG
219
220The current CPU load on the server (alpha-smoothed), as a value between 0
221(none) and 1 (overloaded), indicating how much time is spent on processing
222objects per tick. Healthy values are < 0.5.
223
224=item $cf::LOAD
225
226The raw value load value from the last tick.
227
107=item %cf::CFG 228=item %cf::CFG
108 229
109Configuration for the server, loaded from C</etc/crossfire/config>, or 230Configuration for the server, loaded from C</etc/deliantra-server/config>, or
110from wherever your confdir points to. 231from wherever your confdir points to.
111 232
233=item cf::wait_for_tick, cf::wait_for_tick_begin
234
235These are functions that inhibit the current coroutine one tick. cf::wait_for_tick_begin only
236returns directly I<after> the tick processing (and consequently, can only wake one process
237per tick), while cf::wait_for_tick wakes up all waiters after tick processing.
238
239=item @cf::INVOKE_RESULTS
240
241This array contains the results of the last C<invoke ()> call. When
242C<cf::override> is called C<@cf::INVOKE_RESULTS> is set to the parameters of
243that call.
244
245=item %cf::REFLECT
246
247Contains, for each (C++) class name, a hash reference with information
248about object members (methods, scalars, arrays and flags) and other
249metadata, which is useful for introspection.
250
112=back 251=back
113 252
114=cut 253=cut
115 254
116BEGIN { 255sub error(@) { LOG llevError, join "", @_ }
117 *CORE::GLOBAL::warn = sub { 256sub warn (@) { LOG llevWarn , join "", @_ }
257sub info (@) { LOG llevInfo , join "", @_ }
258sub debug(@) { LOG llevDebug, join "", @_ }
259sub trace(@) { LOG llevTrace, join "", @_ }
260
261$Coro::State::WARNHOOK = sub {
118 my $msg = join "", @_; 262 my $msg = join "", @_;
119 utf8::encode $msg;
120 263
121 $msg .= "\n" 264 $msg .= "\n"
122 unless $msg =~ /\n$/; 265 unless $msg =~ /\n$/;
123 266
267 $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge;
268
124 LOG llevError, $msg; 269 LOG llevWarn, $msg;
125 }; 270};
126} 271
272$Coro::State::DIEHOOK = sub {
273 return unless $^S eq 0; # "eq", not "=="
274
275 error Carp::longmess $_[0];
276
277 if (in_main) {#d#
278 error "DIEHOOK called in main context, Coro bug?\n";#d#
279 return;#d#
280 }#d#
281
282 # kill coroutine otherwise
283 Coro::terminate
284};
127 285
128@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable'; 286@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
129@safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable'; 287@safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable';
130@safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable'; 288@safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable';
131@safe::cf::client::ISA = @cf::client::ISA = 'cf::attachable'; 289@safe::cf::client::ISA = @cf::client::ISA = 'cf::attachable';
132@safe::cf::map::ISA = @cf::map::ISA = 'cf::attachable'; 290@safe::cf::map::ISA = @cf::map::ISA = 'cf::attachable';
291@safe::cf::arch::ISA = @cf::arch::ISA = 'cf::object';
133@safe::cf::object::player::ISA = @cf::object::player::ISA = 'cf::object'; 292@safe::cf::object::player::ISA = @cf::object::player::ISA = 'cf::object'; # not really true (yet)
134 293
135# we bless all objects into (empty) derived classes to force a method lookup 294# we bless all objects into (empty) derived classes to force a method lookup
136# within the Safe compartment. 295# within the Safe compartment.
137for my $pkg (qw( 296for my $pkg (qw(
138 cf::global cf::attachable 297 cf::global cf::attachable
139 cf::object cf::object::player 298 cf::object cf::object::player
140 cf::client cf::player 299 cf::client cf::player
141 cf::arch cf::living 300 cf::arch cf::living
301 cf::map cf::mapspace
142 cf::map cf::party cf::region 302 cf::party cf::region
143)) { 303)) {
144 no strict 'refs';
145 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg; 304 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg;
146} 305}
147 306
148$Event::DIED = sub { 307$EV::DIED = sub {
149 warn "error in event callback: @_"; 308 Carp::cluck "error in event callback: @_";
150}; 309};
151 310
152my %ext_pkg; 311#############################################################################
153my @exts;
154my @hook;
155 312
156=head2 UTILITY FUNCTIONS 313=head2 UTILITY FUNCTIONS
157 314
158=over 4 315=over 4
159 316
178 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge; 335 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge;
179 $d 336 $d
180 } || "[unable to dump $_[0]: '$@']"; 337 } || "[unable to dump $_[0]: '$@']";
181} 338}
182 339
183use JSON::Syck (); # TODO# replace by JSON::PC once working
184
185=item $ref = cf::from_json $json 340=item $ref = cf::decode_json $json
186 341
187Converts a JSON string into the corresponding perl data structure. 342Converts a JSON string into the corresponding perl data structure.
188 343
189=cut
190
191sub from_json($) {
192 $JSON::Syck::ImplicitUnicode = 1; # work around JSON::Syck bugs
193 JSON::Syck::Load $_[0]
194}
195
196=item $json = cf::to_json $ref 344=item $json = cf::encode_json $ref
197 345
198Converts a perl data structure into its JSON representation. 346Converts a perl data structure into its JSON representation.
199 347
200=cut 348=cut
201 349
202sub to_json($) { 350our $json_coder = JSON::XS->new->utf8->max_size (1e6); # accept ~1mb max
203 $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs 351
204 JSON::Syck::Dump $_[0] 352sub encode_json($) { $json_coder->encode ($_[0]) }
353sub decode_json($) { $json_coder->decode ($_[0]) }
354
355=item cf::post_init { BLOCK }
356
357Execute the given codeblock, I<after> all extensions have been (re-)loaded,
358but I<before> the server starts ticking again.
359
360The cdoeblock will have a single boolean argument to indicate whether this
361is a reload or not.
362
363=cut
364
365sub post_init(&) {
366 push @POST_INIT, shift;
205} 367}
206 368
207=item cf::lock_wait $string 369=item cf::lock_wait $string
208 370
209Wait until the given lock is available. See cf::lock_acquire. 371Wait until the given lock is available. See cf::lock_acquire.
210 372
211=item my $lock = cf::lock_acquire $string 373=item my $lock = cf::lock_acquire $string
212 374
213Wait until the given lock is available and then acquires it and returns 375Wait until the given lock is available and then acquires it and returns
214a Coro::guard object. If the guard object gets destroyed (goes out of scope, 376a L<Guard> object. If the guard object gets destroyed (goes out of scope,
215for example when the coroutine gets canceled), the lock is automatically 377for example when the coroutine gets canceled), the lock is automatically
216returned. 378returned.
217 379
380Locks are *not* recursive, locking from the same coro twice results in a
381deadlocked coro.
382
218Lock names should begin with a unique identifier (for example, cf::map::find 383Lock names should begin with a unique identifier (for example, cf::map::find
219uses map_find and cf::map::load uses map_load). 384uses map_find and cf::map::load uses map_load).
220 385
221=cut 386=item $locked = cf::lock_active $string
222 387
223our %LOCK; 388Return true if the lock is currently active, i.e. somebody has locked it.
389
390=cut
391
392our $LOCKS = new Coro::SemaphoreSet;
224 393
225sub lock_wait($) { 394sub lock_wait($) {
226 my ($key) = @_; 395 $LOCKS->wait ($_[0]);
227
228 # wait for lock, if any
229 while ($LOCK{$key}) {
230 push @{ $LOCK{$key} }, $Coro::current;
231 Coro::schedule;
232 }
233} 396}
234 397
235sub lock_acquire($) { 398sub lock_acquire($) {
236 my ($key) = @_; 399 $LOCKS->guard ($_[0])
400}
237 401
238 # wait, to be sure we are not locked 402sub lock_active($) {
239 lock_wait $key; 403 $LOCKS->count ($_[0]) < 1
240
241 $LOCK{$key} = [];
242
243 Coro::guard {
244 # wake up all waiters, to be on the safe side
245 $_->ready for @{ delete $LOCK{$key} };
246 }
247} 404}
248 405
249sub freeze_mainloop { 406sub freeze_mainloop {
250 return unless $TICK_WATCHER->is_active; 407 tick_inhibit_inc;
251 408
252 my $guard = Coro::guard { $TICK_WATCHER->start }; 409 &Guard::guard (\&tick_inhibit_dec);
253 $TICK_WATCHER->stop; 410}
254 $guard 411
412=item cf::periodic $interval, $cb
413
414Like EV::periodic, but randomly selects a starting point so that the actions
415get spread over time.
416
417=cut
418
419sub periodic($$) {
420 my ($interval, $cb) = @_;
421
422 my $start = rand List::Util::min 180, $interval;
423
424 EV::periodic $start, $interval, 0, $cb
425}
426
427=item cf::get_slot $time[, $priority[, $name]]
428
429Allocate $time seconds of blocking CPU time at priority C<$priority>:
430This call blocks and returns only when you have at least C<$time> seconds
431of cpu time till the next tick. The slot is only valid till the next cede.
432
433The optional C<$name> can be used to identify the job to run. It might be
434used for statistical purposes and should identify the same time-class.
435
436Useful for short background jobs.
437
438=cut
439
440our @SLOT_QUEUE;
441our $SLOT_QUEUE;
442our $SLOT_DECAY = 0.9;
443
444$SLOT_QUEUE->cancel if $SLOT_QUEUE;
445$SLOT_QUEUE = Coro::async {
446 $Coro::current->desc ("timeslot manager");
447
448 my $signal = new Coro::Signal;
449 my $busy;
450
451 while () {
452 next_job:
453
454 my $avail = cf::till_tick;
455
456 for (0 .. $#SLOT_QUEUE) {
457 if ($SLOT_QUEUE[$_][0] <= $avail) {
458 $busy = 0;
459 my $job = splice @SLOT_QUEUE, $_, 1, ();
460 $job->[2]->send;
461 Coro::cede;
462 goto next_job;
463 } else {
464 $SLOT_QUEUE[$_][0] *= $SLOT_DECAY;
465 }
466 }
467
468 if (@SLOT_QUEUE) {
469 # we do not use wait_for_tick() as it returns immediately when tick is inactive
470 push @cf::WAIT_FOR_TICK, $signal;
471 $signal->wait;
472 } else {
473 $busy = 0;
474 Coro::schedule;
475 }
476 }
477};
478
479sub get_slot($;$$) {
480 return if tick_inhibit || $Coro::current == $Coro::main;
481
482 my ($time, $pri, $name) = @_;
483
484 $time = clamp $time, 0.01, $TICK * .6;
485
486 my $sig = new Coro::Signal;
487
488 push @SLOT_QUEUE, [$time, $pri, $sig, $name];
489 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE;
490 $SLOT_QUEUE->ready;
491 $sig->wait;
255} 492}
256 493
257=item cf::async { BLOCK } 494=item cf::async { BLOCK }
258 495
259Currently the same as Coro::async_pool, meaning you cannot use 496Currently the same as Coro::async_pool, meaning you cannot use
264 501
265BEGIN { *async = \&Coro::async_pool } 502BEGIN { *async = \&Coro::async_pool }
266 503
267=item cf::sync_job { BLOCK } 504=item cf::sync_job { BLOCK }
268 505
269The design of crossfire+ requires that the main coro ($Coro::main) is 506The design of Deliantra requires that the main coroutine ($Coro::main)
270always able to handle events or runnable, as crossfire+ is only partly 507is always able to handle events or runnable, as Deliantra is only
271reentrant. Thus "blocking" it by e.g. waiting for I/O is not acceptable. 508partly reentrant. Thus "blocking" it by e.g. waiting for I/O is not
509acceptable.
272 510
273If it must be done, put the blocking parts into C<sync_job>. This will run 511If it must be done, put the blocking parts into C<sync_job>. This will run
274the given BLOCK in another coroutine while waiting for the result. The 512the given BLOCK in another coroutine while waiting for the result. The
275server will be frozen during this time, so the block should either finish 513server will be frozen during this time, so the block should either finish
276fast or be very important. 514fast or be very important.
278=cut 516=cut
279 517
280sub sync_job(&) { 518sub sync_job(&) {
281 my ($job) = @_; 519 my ($job) = @_;
282 520
283 if ($Coro::current == $Coro::main) { 521 if (in_main) {
522 my $time = AE::time;
523
284 # this is the main coro, too bad, we have to block 524 # this is the main coro, too bad, we have to block
285 # till the operation succeeds, freezing the server :/ 525 # till the operation succeeds, freezing the server :/
286 526
287 # TODO: use suspend/resume instead 527 #LOG llevError, Carp::longmess "sync job";#d#
288 # (but this is cancel-safe) 528
289 my $freeze_guard = freeze_mainloop; 529 my $freeze_guard = freeze_mainloop;
290 530
291 my $busy = 1; 531 my $busy = 1;
292 my @res; 532 my @res;
293 533
294 (async { 534 (async {
535 $Coro::current->desc ("sync job coro");
295 @res = eval { $job->() }; 536 @res = eval { $job->() };
296 warn $@ if $@; 537 error $@ if $@;
297 undef $busy; 538 undef $busy;
298 })->prio (Coro::PRIO_MAX); 539 })->prio (Coro::PRIO_MAX);
299 540
300 while ($busy) { 541 while ($busy) {
301 Coro::cede or Event::one_event; 542 if (Coro::nready) {
543 Coro::cede_notself;
544 } else {
545 EV::loop EV::LOOP_ONESHOT;
302 } 546 }
547 }
548
549 my $time = AE::time - $time;
550
551 $TICK_START += $time; # do not account sync jobs to server load
303 552
304 wantarray ? @res : $res[0] 553 wantarray ? @res : $res[0]
305 } else { 554 } else {
306 # we are in another coroutine, how wonderful, everything just works 555 # we are in another coroutine, how wonderful, everything just works
307 556
309 } 558 }
310} 559}
311 560
312=item $coro = cf::async_ext { BLOCK } 561=item $coro = cf::async_ext { BLOCK }
313 562
314Like async, but this coro is automcatially being canceled when the 563Like async, but this coro is automatically being canceled when the
315extension calling this is being unloaded. 564extension calling this is being unloaded.
316 565
317=cut 566=cut
318 567
319sub async_ext(&) { 568sub async_ext(&) {
327 $EXT_CORO{$coro+0} = $coro; 576 $EXT_CORO{$coro+0} = $coro;
328 577
329 $coro 578 $coro
330} 579}
331 580
332sub write_runtime { 581=item fork_call { }, $args
333 my $runtime = cf::localdir . "/runtime";
334 582
335 my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644 583Executes the given code block with the given arguments in a seperate
336 or return; 584process, returning the results. Everything must be serialisable with
585Coro::Storable. May, of course, block. Note that the executed sub may
586never block itself or use any form of event handling.
337 587
338 my $value = $cf::RUNTIME + 1 + 10; # 10 is the runtime save interval, for a monotonic clock 588=cut
339 (aio_write $fh, 0, (length $value), $value, 0) <= 0
340 and return;
341 589
342 aio_fsync $fh 590sub fork_call(&@) {
343 and return; 591 my ($cb, @args) = @_;
344 592
345 close $fh 593 # we seemingly have to make a local copy of the whole thing,
346 or return; 594 # otherwise perl prematurely frees the stuff :/
595 # TODO: investigate and fix (likely this will be rather laborious)
347 596
348 aio_rename "$runtime~", $runtime 597 my @res = Coro::Util::fork_eval {
349 and return; 598 reset_signals;
599 &$cb
600 }, @args;
350 601
602 wantarray ? @res : $res[-1]
603}
604
605sub objinfo {
351 1 606 (
607 "counter value" => cf::object::object_count,
608 "objects created" => cf::object::create_count,
609 "objects destroyed" => cf::object::destroy_count,
610 "freelist size" => cf::object::free_count,
611 "allocated objects" => cf::object::objects_size,
612 "active objects" => cf::object::actives_size,
613 )
614}
615
616=item $coin = coin_from_name $name
617
618=cut
619
620our %coin_alias = (
621 "silver" => "silvercoin",
622 "silvercoin" => "silvercoin",
623 "silvercoins" => "silvercoin",
624 "gold" => "goldcoin",
625 "goldcoin" => "goldcoin",
626 "goldcoins" => "goldcoin",
627 "platinum" => "platinacoin",
628 "platinumcoin" => "platinacoin",
629 "platinumcoins" => "platinacoin",
630 "platina" => "platinacoin",
631 "platinacoin" => "platinacoin",
632 "platinacoins" => "platinacoin",
633 "royalty" => "royalty",
634 "royalties" => "royalty",
635);
636
637sub coin_from_name($) {
638 $coin_alias{$_[0]}
639 ? cf::arch::find $coin_alias{$_[0]}
640 : undef
641}
642
643=item $value = cf::db_get $family => $key
644
645Returns a single value from the environment database.
646
647=item cf::db_put $family => $key => $value
648
649Stores the given C<$value> in the family. It can currently store binary
650data only (use Compress::LZF::sfreeze_cr/sthaw to convert to/from binary).
651
652=item $db = cf::db_table "name"
653
654Create and/or open a new database table. The string must not be "db" and must be unique
655within each server.
656
657=cut
658
659sub db_table($) {
660 cf::error "db_get called from main context"
661 if $Coro::current == $Coro::main;
662
663 my ($name) = @_;
664 my $db = BDB::db_create $DB_ENV;
665
666 eval {
667 $db->set_flags (BDB::CHKSUM);
668
669 utf8::encode $name;
670 BDB::db_open $db, undef, $name, undef, BDB::BTREE,
671 BDB::CREATE | BDB::AUTO_COMMIT, 0666;
672 cf::cleanup "db_open(db): $!" if $!;
673 };
674 cf::cleanup "db_open(db): $@" if $@;
675
676 $db
677}
678
679our $DB;
680
681sub db_init {
682 $DB ||= db_table "db";
683}
684
685sub db_get($$) {
686 my $key = "$_[0]/$_[1]";
687
688 cf::error "db_get called from main context"
689 if $Coro::current == $Coro::main;
690
691 BDB::db_get $DB, undef, $key, my $data;
692
693 $! ? ()
694 : $data
695}
696
697sub db_put($$$) {
698 BDB::dbreq_pri 4;
699 BDB::db_put $DB, undef, "$_[0]/$_[1]", $_[2], 0, sub { };
700}
701
702=item cf::cache $id => [$paths...], $processversion => $process
703
704Generic caching function that returns the value of the resource $id,
705caching and regenerating as required.
706
707This function can block.
708
709=cut
710
711sub cache {
712 my ($id, $src, $processversion, $process) = @_;
713
714 my $meta =
715 join "\x00",
716 $processversion,
717 map {
718 aio_stat $_
719 and Carp::croak "$_: $!";
720
721 ($_, (stat _)[7,9])
722 } @$src;
723
724 my $dbmeta = db_get cache => "$id/meta";
725 if ($dbmeta ne $meta) {
726 # changed, we may need to process
727
728 my @data;
729 my $md5;
730
731 for (0 .. $#$src) {
732 0 <= aio_load $src->[$_], $data[$_]
733 or Carp::croak "$src->[$_]: $!";
734 }
735
736 # if processing is expensive, check
737 # checksum first
738 if (1) {
739 $md5 =
740 join "\x00",
741 $processversion,
742 map {
743 cf::cede_to_tick;
744 ($src->[$_], Digest::MD5::md5_hex $data[$_])
745 } 0.. $#$src;
746
747
748 my $dbmd5 = db_get cache => "$id/md5";
749 if ($dbmd5 eq $md5) {
750 db_put cache => "$id/meta", $meta;
751
752 return db_get cache => "$id/data";
753 }
754 }
755
756 my $t1 = Time::HiRes::time;
757 my $data = $process->(\@data);
758 my $t2 = Time::HiRes::time;
759
760 info "cache: '$id' processed in ", $t2 - $t1, "s\n";
761
762 db_put cache => "$id/data", $data;
763 db_put cache => "$id/md5" , $md5;
764 db_put cache => "$id/meta", $meta;
765
766 return $data;
767 }
768
769 db_get cache => "$id/data"
770}
771
772=item cf::datalog type => key => value, ...
773
774Log a datalog packet of the given type with the given key-value pairs.
775
776=cut
777
778sub datalog($@) {
779 my ($type, %kv) = @_;
780 info "DATALOG ", JSON::XS->new->ascii->encode ({ %kv, type => $type });
352} 781}
353 782
354=back 783=back
355 784
356=cut 785=cut
357 786
358############################################################################# 787#############################################################################
359 788
360package cf::path;
361
362# used to convert map paths into valid unix filenames by repalcing / by ∕
363our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons
364
365sub new {
366 my ($class, $path, $base) = @_;
367
368 $path = $path->as_string if ref $path;
369
370 my $self = bless { }, $class;
371
372 # {... are special paths that are not touched
373 # ?xxx/... are special absolute paths
374 # ?random/... random maps
375 # /! non-realised random map exit
376 # /... normal maps
377 # ~/... per-player maps without a specific player (DO NOT USE)
378 # ~user/... per-player map of a specific user
379
380 if ($path =~ /^{/) {
381 # fine as it is
382 } elsif ($path =~ s{^\?random/}{}) {
383 Coro::AIO::aio_load "$cf::RANDOM_MAPS/$path.meta", my $data;
384 $self->{random} = cf::from_json $data;
385 } else {
386 if ($path =~ s{^~([^/]+)?}{}) {
387 $self->{user_rel} = 1;
388
389 if (defined $1) {
390 $self->{user} = $1;
391 } elsif ($base =~ m{^~([^/]+)/}) {
392 $self->{user} = $1;
393 } else {
394 warn "cannot resolve user-relative path without user <$path,$base>\n";
395 }
396 } elsif ($path =~ /^\//) {
397 # already absolute
398 } else {
399 $base =~ s{[^/]+/?$}{};
400 return $class->new ("$base/$path");
401 }
402
403 for ($path) {
404 redo if s{/\.?/}{/};
405 redo if s{/[^/]+/\.\./}{/};
406 }
407 }
408
409 $self->{path} = $path;
410
411 $self
412}
413
414# the name / primary key / in-game path
415sub as_string {
416 my ($self) = @_;
417
418 $self->{user_rel} ? "~$self->{user}$self->{path}"
419 : $self->{random} ? "?random/$self->{path}"
420 : $self->{path}
421}
422
423# the displayed name, this is a one way mapping
424sub visible_name {
425 my ($self) = @_;
426
427# if (my $rmp = $self->{random}) {
428# # todo: be more intelligent about this
429# "?random/$rmp->{origin_map}+$rmp->{origin_x}+$rmp->{origin_y}/$rmp->{dungeon_level}"
430# } else {
431 $self->as_string
432# }
433}
434
435# escape the /'s in the path
436sub _escaped_path {
437 (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/g;
438 $path
439}
440
441# the original (read-only) location
442sub load_path {
443 my ($self) = @_;
444
445 sprintf "%s/%s/%s", cf::datadir, cf::mapdir, $self->{path}
446}
447
448# the temporary/swap location
449sub save_path {
450 my ($self) = @_;
451
452 $self->{user_rel} ? sprintf "%s/%s/%s/%s", cf::localdir, cf::playerdir, $self->{user}, $self->_escaped_path
453 : $self->{random} ? sprintf "%s/%s", $RANDOM_MAPS, $self->{path}
454 : sprintf "%s/%s/%s", cf::localdir, cf::tmpdir, $self->_escaped_path
455}
456
457# the unique path, might be eq to save_path
458sub uniq_path {
459 my ($self) = @_;
460
461 $self->{user_rel} || $self->{random}
462 ? undef
463 : sprintf "%s/%s/%s", cf::localdir, cf::uniquedir, $self->_escaped_path
464}
465
466# return random map parameters, or undef
467sub random_map_params {
468 my ($self) = @_;
469
470 $self->{random}
471}
472
473# this is somewhat ugly, but style maps do need special treatment
474sub is_style_map {
475 $_[0]{path} =~ m{^/styles/}
476}
477
478package cf;
479
480#############################################################################
481
482=head2 ATTACHABLE OBJECTS 789=head2 ATTACHABLE OBJECTS
483 790
484Many objects in crossfire are so-called attachable objects. That means you can 791Many objects in deliantra are so-called attachable objects. That means you can
485attach callbacks/event handlers (a collection of which is called an "attachment") 792attach callbacks/event handlers (a collection of which is called an "attachment")
486to it. All such attachable objects support the following methods. 793to it. All such attachable objects support the following methods.
487 794
488In the following description, CLASS can be any of C<global>, C<object> 795In the following description, CLASS can be any of C<global>, C<object>
489C<player>, C<client> or C<map> (i.e. the attachable objects in 796C<player>, C<client> or C<map> (i.e. the attachable objects in
490crossfire+). 797Deliantra).
491 798
492=over 4 799=over 4
493 800
494=item $attachable->attach ($attachment, key => $value...) 801=item $attachable->attach ($attachment, key => $value...)
495 802
539=item cf::CLASS::attachment $name, ... 846=item cf::CLASS::attachment $name, ...
540 847
541Register an attachment by C<$name> through which attachable objects of the 848Register an attachment by C<$name> through which attachable objects of the
542given CLASS can refer to this attachment. 849given CLASS can refer to this attachment.
543 850
544Some classes such as crossfire maps and objects can specify attachments 851Some classes such as deliantra maps and objects can specify attachments
545that are attached at load/instantiate time, thus the need for a name. 852that are attached at load/instantiate time, thus the need for a name.
546 853
547These calls expect any number of the following handler/hook descriptions: 854These calls expect any number of the following handler/hook descriptions:
548 855
549=over 4 856=over 4
609our @CB_TYPE = (); # registry for type (cf-object class) based events 916our @CB_TYPE = (); # registry for type (cf-object class) based events
610our @CB_MAP = (); 917our @CB_MAP = ();
611 918
612my %attachment; 919my %attachment;
613 920
921sub cf::attachable::thawer_merge {
922 # simply override everything except _meta
923 local $_[0]{_meta};
924 %{$_[0]} = %{$_[1]};
925}
926
614sub _attach_cb($$$$) { 927sub _attach_cb($$$$) {
615 my ($registry, $event, $prio, $cb) = @_; 928 my ($registry, $event, $prio, $cb) = @_;
616 929
617 use sort 'stable'; 930 use sort 'stable';
618 931
652 $registry = $CB_TYPE[$object_type] ||= []; 965 $registry = $CB_TYPE[$object_type] ||= [];
653 966
654 } elsif ($type eq "subtype") { 967 } elsif ($type eq "subtype") {
655 defined $object_type or Carp::croak "subtype specified without type"; 968 defined $object_type or Carp::croak "subtype specified without type";
656 my $object_subtype = shift @arg; 969 my $object_subtype = shift @arg;
657 $registry = $CB_TYPE[$object_type + $object_subtype * NUM_SUBTYPES] ||= []; 970 $registry = $CB_TYPE[$object_type + $object_subtype * NUM_TYPES] ||= [];
658 971
659 } elsif ($type eq "package") { 972 } elsif ($type eq "package") {
660 my $pkg = shift @arg; 973 my $pkg = shift @arg;
661 974
662 while (my ($name, $id) = each %cb_id) { 975 while (my ($name, $id) = each %cb_id) {
667 980
668 } elsif (exists $cb_id{$type}) { 981 } elsif (exists $cb_id{$type}) {
669 _attach_cb $registry, $cb_id{$type}, $prio, shift @arg; 982 _attach_cb $registry, $cb_id{$type}, $prio, shift @arg;
670 983
671 } elsif (ref $type) { 984 } elsif (ref $type) {
672 warn "attaching objects not supported, ignoring.\n"; 985 error "attaching objects not supported, ignoring.\n";
673 986
674 } else { 987 } else {
675 shift @arg; 988 shift @arg;
676 warn "attach argument '$type' not supported, ignoring.\n"; 989 error "attach argument '$type' not supported, ignoring.\n";
677 } 990 }
678 } 991 }
679} 992}
680 993
681sub _object_attach { 994sub _object_attach {
691 _attach $registry, $klass, @attach; 1004 _attach $registry, $klass, @attach;
692 } 1005 }
693 1006
694 $obj->{$name} = \%arg; 1007 $obj->{$name} = \%arg;
695 } else { 1008 } else {
696 warn "object uses attachment '$name' that is not available, postponing.\n"; 1009 info "object uses attachment '$name' which is not available, postponing.\n";
697 } 1010 }
698 1011
699 $obj->{_attachment}{$name} = undef; 1012 $obj->{_attachment}{$name} = undef;
700} 1013}
701 1014
703 if (ref $_[0]) { 1016 if (ref $_[0]) {
704 _object_attach @_; 1017 _object_attach @_;
705 } else { 1018 } else {
706 _attach shift->_attach_registry, @_; 1019 _attach shift->_attach_registry, @_;
707 } 1020 }
1021 _recalc_want;
708}; 1022};
709 1023
710# all those should be optimised 1024# all those should be optimised
711sub cf::attachable::detach { 1025sub cf::attachable::detach {
712 my ($obj, $name) = @_; 1026 my ($obj, $name) = @_;
715 delete $obj->{_attachment}{$name}; 1029 delete $obj->{_attachment}{$name};
716 reattach ($obj); 1030 reattach ($obj);
717 } else { 1031 } else {
718 Carp::croak "cannot, currently, detach class attachments"; 1032 Carp::croak "cannot, currently, detach class attachments";
719 } 1033 }
1034 _recalc_want;
720}; 1035};
721 1036
722sub cf::attachable::attached { 1037sub cf::attachable::attached {
723 my ($obj, $name) = @_; 1038 my ($obj, $name) = @_;
724 1039
739 "; 1054 ";
740 die if $@; 1055 die if $@;
741} 1056}
742 1057
743our $override; 1058our $override;
744our @invoke_results = (); # referenced from .xs code. TODO: play tricks with reify and mortals? 1059our @INVOKE_RESULTS = (); # referenced from .xs code. TODO: play tricks with reify and mortals?
745 1060
746sub override { 1061sub override {
747 $override = 1; 1062 $override = 1;
748 @invoke_results = (); 1063 @INVOKE_RESULTS = (@_);
749} 1064}
750 1065
751sub do_invoke { 1066sub do_invoke {
752 my $event = shift; 1067 my $event = shift;
753 my $callbacks = shift; 1068 my $callbacks = shift;
754 1069
755 @invoke_results = (); 1070 @INVOKE_RESULTS = ();
756 1071
757 local $override; 1072 local $override;
758 1073
759 for (@$callbacks) { 1074 for (@$callbacks) {
760 eval { &{$_->[1]} }; 1075 eval { &{$_->[1]} };
761 1076
762 if ($@) { 1077 if ($@) {
763 warn "$@";
764 warn "... while processing $EVENT[$event][0](@_) event, skipping processing altogether.\n"; 1078 error "$@", "... while processing $EVENT[$event][0](@_) event, skipping processing altogether.\n";
765 override; 1079 override;
766 } 1080 }
767 1081
768 return 1 if $override; 1082 return 1 if $override;
769 } 1083 }
770 1084
771 0 1085 0
772} 1086}
773 1087
774=item $bool = cf::global::invoke (EVENT_CLASS_XXX, ...) 1088=item $bool = cf::global->invoke (EVENT_CLASS_XXX, ...)
775 1089
776=item $bool = $attachable->invoke (EVENT_CLASS_XXX, ...) 1090=item $bool = $attachable->invoke (EVENT_CLASS_XXX, ...)
777 1091
778Generate an object-specific event with the given arguments. 1092Generate an object-specific event with the given arguments.
779 1093
780This API is preliminary (most likely, the EVENT_CLASS_xxx prefix will be 1094This API is preliminary (most likely, the EVENT_CLASS_xxx prefix will be
781removed in future versions), and there is no public API to access override 1095removed in future versions), and there is no public API to access override
782results (if you must, access C<@cf::invoke_results> directly). 1096results (if you must, access C<@cf::INVOKE_RESULTS> directly).
783 1097
784=back 1098=back
785 1099
786=cut 1100=cut
787 1101
788############################################################################# 1102#############################################################################
789# object support 1103# object support
1104
1105sub _object_equal($$);
1106sub _object_equal($$) {
1107 my ($a, $b) = @_;
1108
1109 return 0 unless (ref $a) eq (ref $b);
1110
1111 if ("HASH" eq ref $a) {
1112 my @ka = keys %$a;
1113 my @kb = keys %$b;
1114
1115 return 0 if @ka != @kb;
1116
1117 for (0 .. $#ka) {
1118 return 0 unless $ka[$_] eq $kb[$_];
1119 return 0 unless _object_equal $a->{$ka[$_]}, $b->{$kb[$_]};
1120 }
1121
1122 } elsif ("ARRAY" eq ref $a) {
1123
1124 return 0 if @$a != @$b;
1125
1126 for (0 .. $#$a) {
1127 return 0 unless _object_equal $a->[$_], $b->[$_];
1128 }
1129
1130 } elsif ($a ne $b) {
1131 return 0;
1132 }
1133
1134 1
1135}
1136
1137our $SLOW_MERGES;#d#
1138sub _can_merge {
1139 my ($ob1, $ob2) = @_;
1140
1141 ++$SLOW_MERGES;#d#
1142
1143 # we do the slow way here
1144 return _object_equal $ob1, $ob2
1145}
790 1146
791sub reattach { 1147sub reattach {
792 # basically do the same as instantiate, without calling instantiate 1148 # basically do the same as instantiate, without calling instantiate
793 my ($obj) = @_; 1149 my ($obj) = @_;
1150
1151 # no longer needed after getting rid of delete_package?
1152 #bless $obj, ref $obj; # re-bless in case extensions have been reloaded
794 1153
795 my $registry = $obj->registry; 1154 my $registry = $obj->registry;
796 1155
797 @$registry = (); 1156 @$registry = ();
798 1157
803 for (@$attach) { 1162 for (@$attach) {
804 my ($klass, @attach) = @$_; 1163 my ($klass, @attach) = @$_;
805 _attach $registry, $klass, @attach; 1164 _attach $registry, $klass, @attach;
806 } 1165 }
807 } else { 1166 } else {
808 warn "object uses attachment '$name' that is not available, postponing.\n"; 1167 info "object uses attachment '$name' that is not available, postponing.\n";
809 } 1168 }
810 } 1169 }
811} 1170}
812 1171
813cf::attachable->attach ( 1172cf::attachable->attach (
814 prio => -1000000, 1173 prio => -1000000,
815 on_instantiate => sub { 1174 on_instantiate => sub {
816 my ($obj, $data) = @_; 1175 my ($obj, $data) = @_;
817 1176
818 $data = from_json $data; 1177 $data = decode_json $data;
819 1178
820 for (@$data) { 1179 for (@$data) {
821 my ($name, $args) = @$_; 1180 my ($name, $args) = @$_;
822 1181
823 $obj->attach ($name, %{$args || {} }); 1182 $obj->attach ($name, %{$args || {} });
839sub object_freezer_save { 1198sub object_freezer_save {
840 my ($filename, $rdata, $objs) = @_; 1199 my ($filename, $rdata, $objs) = @_;
841 1200
842 sync_job { 1201 sync_job {
843 if (length $$rdata) { 1202 if (length $$rdata) {
1203 utf8::decode (my $decname = $filename);
844 warn sprintf "saving %s (%d,%d)\n", 1204 trace sprintf "saving %s (%d,%d)\n",
845 $filename, length $$rdata, scalar @$objs; 1205 $decname, length $$rdata, scalar @$objs
1206 if $VERBOSE_IO;
846 1207
847 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) { 1208 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
848 chmod SAVE_MODE, $fh; 1209 aio_chmod $fh, SAVE_MODE;
849 aio_write $fh, 0, (length $$rdata), $$rdata, 0; 1210 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
1211 if ($cf::USE_FSYNC) {
1212 aio_sync_file_range $fh, 0, 0, IO::AIO::SYNC_FILE_RANGE_WAIT_BEFORE | IO::AIO::SYNC_FILE_RANGE_WRITE | IO::AIO::SYNC_FILE_RANGE_WAIT_AFTER;
850 aio_fsync $fh; 1213 aio_fsync $fh;
1214 }
851 close $fh; 1215 aio_close $fh;
852 1216
853 if (@$objs) { 1217 if (@$objs) {
854 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) { 1218 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) {
855 chmod SAVE_MODE, $fh; 1219 aio_chmod $fh, SAVE_MODE;
856 my $data = Storable::nfreeze { version => 1, objs => $objs }; 1220 my $data = Coro::Storable::nfreeze { version => 1, objs => $objs };
857 aio_write $fh, 0, (length $data), $data, 0; 1221 aio_write $fh, 0, (length $data), $data, 0;
1222 if ($cf::USE_FSYNC) {
1223 aio_sync_file_range $fh, 0, 0, IO::AIO::SYNC_FILE_RANGE_WAIT_BEFORE | IO::AIO::SYNC_FILE_RANGE_WRITE | IO::AIO::SYNC_FILE_RANGE_WAIT_AFTER;
858 aio_fsync $fh; 1224 aio_fsync $fh;
1225 }
859 close $fh; 1226 aio_close $fh;
860 aio_rename "$filename.pst~", "$filename.pst"; 1227 aio_rename "$filename.pst~", "$filename.pst";
861 } 1228 }
862 } else { 1229 } else {
863 aio_unlink "$filename.pst"; 1230 aio_unlink "$filename.pst";
864 } 1231 }
865 1232
866 aio_rename "$filename~", $filename; 1233 aio_rename "$filename~", $filename;
1234
1235 $filename =~ s%/[^/]+$%%;
1236 aio_pathsync $filename if $cf::USE_FSYNC;
867 } else { 1237 } else {
868 warn "FATAL: $filename~: $!\n"; 1238 error "unable to save objects: $filename~: $!\n";
869 } 1239 }
870 } else { 1240 } else {
871 aio_unlink $filename; 1241 aio_unlink $filename;
872 aio_unlink "$filename.pst"; 1242 aio_unlink "$filename.pst";
873 } 1243 }
874 } 1244 };
875} 1245}
876 1246
877sub object_freezer_as_string { 1247sub object_freezer_as_string {
878 my ($rdata, $objs) = @_; 1248 my ($rdata, $objs) = @_;
879 1249
891 or return; 1261 or return;
892 1262
893 unless (aio_stat "$filename.pst") { 1263 unless (aio_stat "$filename.pst") {
894 (aio_load "$filename.pst", $av) >= 0 1264 (aio_load "$filename.pst", $av) >= 0
895 or return; 1265 or return;
1266
896 $av = eval { (Storable::thaw $av)->{objs} }; 1267 my $st = eval { Coro::Storable::thaw $av };
1268 $av = $st->{objs};
897 } 1269 }
898 1270
1271 utf8::decode (my $decname = $filename);
899 warn sprintf "loading %s (%d)\n", 1272 trace sprintf "loading %s (%d,%d)\n",
900 $filename, length $data, scalar @{$av || []};#d# 1273 $decname, length $data, scalar @{$av || []}
1274 if $VERBOSE_IO;
1275
901 return ($data, $av); 1276 ($data, $av)
902} 1277}
1278
1279=head2 COMMAND CALLBACKS
1280
1281=over 4
1282
1283=cut
903 1284
904############################################################################# 1285#############################################################################
905# command handling &c 1286# command handling &c
906 1287
907=item cf::register_command $name => \&callback($ob,$args); 1288=item cf::register_command $name => \&callback($ob,$args);
920 push @{ $COMMAND{$name} }, [$caller, $cb]; 1301 push @{ $COMMAND{$name} }, [$caller, $cb];
921} 1302}
922 1303
923=item cf::register_extcmd $name => \&callback($pl,$packet); 1304=item cf::register_extcmd $name => \&callback($pl,$packet);
924 1305
925Register a callbackf ro execution when the client sends an extcmd packet. 1306Register a callback for execution when the client sends an (synchronous)
1307extcmd packet. Ext commands will be processed in the order they are
1308received by the server, like other user commands. The first argument is
1309the logged-in player. Ext commands can only be processed after a player
1310has logged in successfully.
926 1311
927If the callback returns something, it is sent back as if reply was being 1312If the callback returns something, it is sent back as if reply was being
928called. 1313called.
929 1314
1315=item cf::register_exticmd $name => \&callback($ns,$packet);
1316
1317Register a callback for execution when the client sends an (asynchronous)
1318exticmd packet. Exti commands are processed by the server as soon as they
1319are received, i.e. out of order w.r.t. other commands. The first argument
1320is a client socket. Exti commands can be received anytime, even before
1321log-in.
1322
1323If the callback returns something, it is sent back as if reply was being
1324called.
1325
930=cut 1326=cut
931 1327
932sub register_extcmd { 1328sub register_extcmd {
933 my ($name, $cb) = @_; 1329 my ($name, $cb) = @_;
934 1330
935 my $caller = caller;
936 #warn "registering extcmd '$name' to '$caller'";
937
938 $EXTCMD{$name} = [$cb, $caller]; 1331 $EXTCMD{$name} = $cb;
939} 1332}
1333
1334sub register_exticmd {
1335 my ($name, $cb) = @_;
1336
1337 $EXTICMD{$name} = $cb;
1338}
1339
1340use File::Glob ();
940 1341
941cf::player->attach ( 1342cf::player->attach (
942 on_command => sub { 1343 on_unknown_command => sub {
943 my ($pl, $name, $params) = @_; 1344 my ($pl, $name, $params) = @_;
944 1345
945 my $cb = $COMMAND{$name} 1346 my $cb = $COMMAND{$name}
946 or return; 1347 or return;
947 1348
952 cf::override; 1353 cf::override;
953 }, 1354 },
954 on_extcmd => sub { 1355 on_extcmd => sub {
955 my ($pl, $buf) = @_; 1356 my ($pl, $buf) = @_;
956 1357
957 my $msg = eval { from_json $buf }; 1358 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) };
958 1359
959 if (ref $msg) { 1360 if (ref $msg) {
1361 my ($type, $reply, @payload) =
1362 "ARRAY" eq ref $msg
1363 ? @$msg
1364 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
1365
1366 my @reply;
1367
960 if (my $cb = $EXTCMD{$msg->{msgtype}}) { 1368 if (my $cb = $EXTCMD{$type}) {
961 if (my %reply = $cb->[0]->($pl, $msg)) { 1369 @reply = $cb->($pl, @payload);
962 $pl->ext_reply ($msg->{msgid}, %reply);
963 }
964 } 1370 }
1371
1372 $pl->ext_reply ($reply, @reply)
1373 if $reply;
1374
965 } else { 1375 } else {
966 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n"; 1376 error "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
967 } 1377 }
968 1378
969 cf::override; 1379 cf::override;
970 }, 1380 },
971); 1381);
972 1382
973sub register { 1383# "readahead" all extensions
974 my ($base, $pkg) = @_;
975
976 #TODO
977}
978
979sub load_extension { 1384sub cache_extensions {
980 my ($path) = @_; 1385 my $grp = IO::AIO::aio_group;
981 1386
982 $path =~ /([^\/\\]+)\.ext$/ or die "$path"; 1387 add $grp IO::AIO::aio_readdirx $LIBDIR, IO::AIO::READDIR_STAT_ORDER, sub {
983 my $base = $1; 1388 for (grep /\.ext$/, @{$_[0]}) {
984 my $pkg = $1; 1389 add $grp IO::AIO::aio_load "$LIBDIR/$_", my $data;
985 $pkg =~ s/[^[:word:]]/_/g;
986 $pkg = "ext::$pkg";
987
988 warn "loading '$path' into '$pkg'\n";
989
990 open my $fh, "<:utf8", $path
991 or die "$path: $!";
992
993 my $source =
994 "package $pkg; use strict; use utf8;\n"
995 . "#line 1 \"$path\"\n{\n"
996 . (do { local $/; <$fh> })
997 . "\n};\n1";
998
999 eval $source
1000 or die $@ ? "$path: $@\n"
1001 : "extension disabled.\n";
1002
1003 push @exts, $pkg;
1004 $ext_pkg{$base} = $pkg;
1005
1006# no strict 'refs';
1007# @{"$pkg\::ISA"} = ext::;
1008
1009 register $base, $pkg;
1010}
1011
1012sub unload_extension {
1013 my ($pkg) = @_;
1014
1015 warn "removing extension $pkg\n";
1016
1017 # remove hooks
1018 #TODO
1019# for my $idx (0 .. $#PLUGIN_EVENT) {
1020# delete $hook[$idx]{$pkg};
1021# }
1022
1023 # remove commands
1024 for my $name (keys %COMMAND) {
1025 my @cb = grep $_->[0] ne $pkg, @{ $COMMAND{$name} };
1026
1027 if (@cb) {
1028 $COMMAND{$name} = \@cb;
1029 } else {
1030 delete $COMMAND{$name};
1031 } 1390 }
1032 } 1391 };
1033 1392
1034 # remove extcmds 1393 $grp
1035 for my $name (grep $EXTCMD{$_}[1] eq $pkg, keys %EXTCMD) {
1036 delete $EXTCMD{$name};
1037 }
1038
1039 if (my $cb = $pkg->can ("unload")) {
1040 eval {
1041 $cb->($pkg);
1042 1
1043 } or warn "$pkg unloaded, but with errors: $@";
1044 }
1045
1046 Symbol::delete_package $pkg;
1047} 1394}
1048 1395
1049sub load_extensions { 1396sub load_extensions {
1397 info "loading extensions...";
1398
1399 cf::sync_job {
1400 my %todo;
1401
1050 for my $ext (<$LIBDIR/*.ext>) { 1402 for my $path (<$LIBDIR/*.ext>) {
1051 next unless -r $ext; 1403 next unless -r $path;
1052 eval { 1404
1053 load_extension $ext; 1405 $path =~ /([^\/\\]+)\.ext$/ or die "$path";
1406 my $base = $1;
1407 my $pkg = $1;
1408 $pkg =~ s/[^[:word:]]/_/g;
1409 $pkg = "ext::$pkg";
1410
1411 open my $fh, "<:utf8", $path
1412 or die "$path: $!";
1413
1414 my $source = do { local $/; <$fh> };
1415
1416 my %ext = (
1417 path => $path,
1418 base => $base,
1419 pkg => $pkg,
1420 );
1421
1422 $ext{meta} = { map { (split /=/, $_, 2)[0, 1] } split /\s+/, $1 }
1423 if $source =~ /\A#!.*?perl.*?#\s*(.*)$/m;
1424
1425 $ext{source} =
1426 "package $pkg; use common::sense;\n"
1427 . "#line 1 \"$path\"\n{\n"
1428 . $source
1429 . "\n};\n1";
1430
1431 $todo{$base} = \%ext;
1432 }
1433
1434 my $pass = 0;
1435 my %done;
1436 while (%todo) {
1437 my $progress;
1438
1439 ++$pass;
1440
1441 ext:
1442 while (my ($k, $v) = each %todo) {
1443 for (split /,\s*/, $v->{meta}{depends}) {
1444 next ext
1445 unless exists $done{$_};
1446 }
1447
1448 trace "... pass $pass, loading '$k' into '$v->{pkg}'\n";
1449
1450 my $active = eval $v->{source};
1451
1452 if (length $@) {
1453 error "$v->{path}: $@\n";
1454 undef $@; # work around perl 5.10.0 utf-8 caching bug
1455
1456 cf::cleanup "mandatory extension '$k' failed to load, exiting."
1457 if exists $v->{meta}{mandatory};
1458
1459 warn "$v->{base}: optional extension cannot be loaded, skipping.\n";
1460 delete $todo{$k};
1461 } else {
1462 $done{$k} = delete $todo{$k};
1463 push @EXTS, $v->{pkg};
1464 $progress = 1;
1465
1466 info "$v->{base}: extension inactive.\n"
1467 unless $active;
1468 }
1054 1 1469 }
1055 } or warn "$ext not loaded: $@"; 1470
1471 unless ($progress) {
1472 warn "cannot load " . (join ", ", keys %todo) . ": unable to resolve dependencies\n";
1473
1474 while (my ($k, $v) = each %todo) {
1475 cf::cleanup "mandatory extension '$k' has unresolved dependencies, exiting."
1476 if exists $v->{meta}{mandatory};
1477 }
1478 }
1479 }
1056 } 1480 };
1057} 1481}
1058 1482
1059############################################################################# 1483#############################################################################
1060# load/save/clean perl data associated with a map
1061 1484
1062*cf::mapsupport::on_clean = sub { 1485=back
1063 my ($map) = @_;
1064
1065 my $path = $map->tmpname;
1066 defined $path or return;
1067
1068 unlink "$path.pst";
1069};
1070
1071cf::map->attach (prio => -10000, package => cf::mapsupport::);
1072
1073#############################################################################
1074# load/save perl data associated with player->ob objects
1075
1076sub all_objects(@) {
1077 @_, map all_objects ($_->inv), @_
1078}
1079
1080# TODO: compatibility cruft, remove when no longer needed
1081cf::player->attach (
1082 on_load => sub {
1083 my ($pl, $path) = @_;
1084
1085 for my $o (all_objects $pl->ob) {
1086 if (my $value = $o->get_ob_key_value ("_perl_data")) {
1087 $o->set_ob_key_value ("_perl_data");
1088
1089 %$o = %{ Storable::thaw pack "H*", $value };
1090 }
1091 }
1092 },
1093);
1094
1095#############################################################################
1096 1486
1097=head2 CORE EXTENSIONS 1487=head2 CORE EXTENSIONS
1098 1488
1099Functions and methods that extend core crossfire objects. 1489Functions and methods that extend core deliantra objects.
1100 1490
1101=cut 1491=cut
1102 1492
1103package cf::player; 1493package cf::player;
1104 1494
1106 1496
1107=head3 cf::player 1497=head3 cf::player
1108 1498
1109=over 4 1499=over 4
1110 1500
1501=item cf::player::num_playing
1502
1503Returns the official number of playing players, as per the Crossfire metaserver rules.
1504
1505=cut
1506
1507sub num_playing {
1508 scalar grep
1509 $_->ob->map
1510 && !$_->hidden
1511 && !$_->ob->flag (cf::FLAG_WIZ),
1512 cf::player::list
1513}
1514
1111=item cf::player::find $login 1515=item cf::player::find $login
1112 1516
1113Returns the given player object, loading it if necessary (might block). 1517Returns the given player object, loading it if necessary (might block).
1114 1518
1115=cut 1519=cut
1116 1520
1117sub playerdir($) { 1521sub playerdir($) {
1118 cf::localdir 1522 "$PLAYERDIR/"
1119 . "/"
1120 . cf::playerdir
1121 . "/"
1122 . (ref $_[0] ? $_[0]->ob->name : $_[0]) 1523 . (ref $_[0] ? $_[0]->ob->name : $_[0])
1123} 1524}
1124 1525
1125sub path($) { 1526sub path($) {
1126 my $login = ref $_[0] ? $_[0]->ob->name : $_[0]; 1527 my $login = ref $_[0] ? $_[0]->ob->name : $_[0];
1127 1528
1128 (playerdir $login) . "/$login.pl" 1529 (playerdir $login) . "/playerdata"
1129} 1530}
1130 1531
1131sub find_active($) { 1532sub find_active($) {
1132 $cf::PLAYER{$_[0]} 1533 $cf::PLAYER{$_[0]}
1133 and $cf::PLAYER{$_[0]}->active 1534 and $cf::PLAYER{$_[0]}->active
1136 1537
1137sub exists($) { 1538sub exists($) {
1138 my ($login) = @_; 1539 my ($login) = @_;
1139 1540
1140 $cf::PLAYER{$login} 1541 $cf::PLAYER{$login}
1141 or cf::sync_job { !aio_stat $login } 1542 or !aio_stat path $login
1142} 1543}
1143 1544
1144sub find($) { 1545sub find($) {
1145 return $cf::PLAYER{$_[0]} || do { 1546 return $cf::PLAYER{$_[0]} || do {
1146 my $login = $_[0]; 1547 my $login = $_[0];
1147 1548
1148 my $guard = cf::lock_acquire "user_find:$login"; 1549 my $guard = cf::lock_acquire "user_find:$login";
1149 1550
1150 $cf::PLAYER{$_[0]} || do { 1551 $cf::PLAYER{$_[0]} || do {
1151 my $pl = load_pl path $login 1552 # rename old playerfiles to new ones
1553 #TODO: remove when no longer required
1554 aio_link +(playerdir $login) . "/$login.pl.pst", (playerdir $login) . "/playerdata.pst";
1555 aio_link +(playerdir $login) . "/$login.pl" , (playerdir $login) . "/playerdata";
1556 aio_unlink +(playerdir $login) . "/$login.pl.pst";
1557 aio_unlink +(playerdir $login) . "/$login.pl";
1558
1559 my $f = new_from_file cf::object::thawer path $login
1152 or return; 1560 or return;
1561
1562 my $pl = cf::player::load_pl $f
1563 or return;
1564
1565 local $cf::PLAYER_LOADING{$login} = $pl;
1566 $f->resolve_delayed_derefs;
1153 $cf::PLAYER{$login} = $pl 1567 $cf::PLAYER{$login} = $pl
1154 } 1568 }
1155 } 1569 }
1156} 1570}
1571
1572cf::player->attach (
1573 on_load => sub {
1574 my ($pl, $path) = @_;
1575
1576 # restore slots saved in save, below
1577 my $slots = delete $pl->{_slots};
1578
1579 $pl->ob->current_weapon ($slots->[0]);
1580 $pl->combat_ob ($slots->[1]);
1581 $pl->ranged_ob ($slots->[2]);
1582 },
1583);
1157 1584
1158sub save($) { 1585sub save($) {
1159 my ($pl) = @_; 1586 my ($pl) = @_;
1160 1587
1161 return if $pl->{deny_save}; 1588 return if $pl->{deny_save};
1166 return if $pl->{deny_save}; 1593 return if $pl->{deny_save};
1167 1594
1168 aio_mkdir playerdir $pl, 0770; 1595 aio_mkdir playerdir $pl, 0770;
1169 $pl->{last_save} = $cf::RUNTIME; 1596 $pl->{last_save} = $cf::RUNTIME;
1170 1597
1598 cf::get_slot 0.01;
1599
1600 # save slots, to be restored later
1601 local $pl->{_slots} = [$pl->ob->current_weapon, $pl->combat_ob, $pl->ranged_ob];
1602
1171 $pl->save_pl ($path); 1603 $pl->save_pl ($path);
1172 Coro::cede; 1604 cf::cede_to_tick;
1173} 1605}
1174 1606
1175sub new($) { 1607sub new($) {
1176 my ($login) = @_; 1608 my ($login) = @_;
1177 1609
1183 $cf::PLAYER{$login} = $self; 1615 $cf::PLAYER{$login} = $self;
1184 1616
1185 $self 1617 $self
1186} 1618}
1187 1619
1620=item $player->send_msg ($channel, $msg, $color, [extra...])
1621
1622=cut
1623
1624sub send_msg {
1625 my $ns = shift->ns
1626 or return;
1627 $ns->send_msg (@_);
1628}
1629
1188=item $pl->quit_character 1630=item $pl->quit_character
1189 1631
1190Nukes the player without looking back. If logged in, the connection will 1632Nukes the player without looking back. If logged in, the connection will
1191be destroyed. May block for a long time. 1633be destroyed. May block for a long time.
1192 1634
1193=cut 1635=cut
1194 1636
1195sub quit_character { 1637sub quit_character {
1196 my ($pl) = @_; 1638 my ($pl) = @_;
1197 1639
1640 my $name = $pl->ob->name;
1641
1198 $pl->{deny_save} = 1; 1642 $pl->{deny_save} = 1;
1199 $pl->password ("*"); # this should lock out the player until we nuked the dir 1643 $pl->password ("*"); # this should lock out the player until we have nuked the dir
1200 1644
1201 $pl->invoke (cf::EVENT_PLAYER_LOGOUT, 1) if $pl->active; 1645 $pl->invoke (cf::EVENT_PLAYER_LOGOUT, 1) if $pl->active;
1202 $pl->deactivate; 1646 $pl->deactivate;
1647 my $killer = cf::arch::get "killer_quit"; $pl->killer ($killer); $killer->destroy;
1203 $pl->invoke (cf::EVENT_PLAYER_QUIT); 1648 $pl->invoke (cf::EVENT_PLAYER_QUIT);
1204 $pl->ns->destroy if $pl->ns; 1649 $pl->ns->destroy if $pl->ns;
1205 1650
1206 my $path = playerdir $pl; 1651 my $path = playerdir $pl;
1207 my $temp = "$path~$cf::RUNTIME~deleting~"; 1652 my $temp = "$path~$cf::RUNTIME~deleting~";
1208 aio_rename $path, $temp; 1653 aio_rename $path, $temp;
1209 delete $cf::PLAYER{$pl->ob->name}; 1654 delete $cf::PLAYER{$pl->ob->name};
1210 $pl->destroy; 1655 $pl->destroy;
1656
1657 my $prefix = qr<^~\Q$name\E/>;
1658
1659 # nuke player maps
1660 $cf::MAP{$_}->nuke for grep /$prefix/, keys %cf::MAP;
1661
1211 IO::AIO::aio_rmtree $temp; 1662 IO::AIO::aio_rmtree $temp;
1663}
1664
1665=item $pl->kick
1666
1667Kicks a player out of the game. This destroys the connection.
1668
1669=cut
1670
1671sub kick {
1672 my ($pl, $kicker) = @_;
1673
1674 $pl->invoke (cf::EVENT_PLAYER_KICK, $kicker);
1675 $pl->killer ("kicked");
1676 $pl->ns->destroy;
1212} 1677}
1213 1678
1214=item cf::player::list_logins 1679=item cf::player::list_logins
1215 1680
1216Returns am arrayref of all valid playernames in the system, can take a 1681Returns am arrayref of all valid playernames in the system, can take a
1217while and may block, so not sync_job-capable, ever. 1682while and may block, so not sync_job-capable, ever.
1218 1683
1219=cut 1684=cut
1220 1685
1221sub list_logins { 1686sub list_logins {
1222 my $dirs = aio_readdir cf::localdir . "/" . cf::playerdir 1687 my $dirs = aio_readdir $PLAYERDIR
1223 or return []; 1688 or return [];
1224 1689
1225 my @logins; 1690 my @logins;
1226 1691
1227 for my $login (@$dirs) { 1692 for my $login (@$dirs) {
1693 my $path = path $login;
1694
1695 # a .pst is a dead give-away for a valid player
1696 # if no pst file found, open and chekc for blocked users
1697 if (aio_stat "$path.pst") {
1228 my $fh = aio_open path $login, Fcntl::O_RDONLY, 0 or next; 1698 my $fh = aio_open $path, Fcntl::O_RDONLY, 0 or next;
1229 aio_read $fh, 0, 512, my $buf, 0 or next; 1699 aio_read $fh, 0, 512, my $buf, 0 or next;
1230 $buf !~ /^password -------------$/ or next; # official not-valid tag 1700 $buf !~ /^password -------------$/m or next; # official not-valid tag
1701 }
1231 1702
1232 utf8::decode $login; 1703 utf8::decode $login;
1233 push @logins, $login; 1704 push @logins, $login;
1234 } 1705 }
1235 1706
1236 \@logins 1707 \@logins
1237} 1708}
1238 1709
1239=item $player->maps 1710=item $player->maps
1240 1711
1712=item cf::player::maps $login
1713
1241Returns an arrayref of cf::path's of all maps that are private for this 1714Returns an arrayref of map paths that are private for this
1242player. May block. 1715player. May block.
1243 1716
1244=cut 1717=cut
1245 1718
1246sub maps($) { 1719sub maps($) {
1247 my ($pl) = @_; 1720 my ($pl) = @_;
1721
1722 $pl = ref $pl ? $pl->ob->name : $pl;
1248 1723
1249 my $files = aio_readdir playerdir $pl 1724 my $files = aio_readdir playerdir $pl
1250 or return; 1725 or return;
1251 1726
1252 my @paths; 1727 my @paths;
1253 1728
1254 for (@$files) { 1729 for (@$files) {
1255 utf8::decode $_; 1730 utf8::decode $_;
1256 next if /\.(?:pl|pst)$/; 1731 next if /\.(?:pl|pst)$/;
1257 next unless /^$PATH_SEP/; 1732 next unless /^$PATH_SEP/o;
1258 1733
1259 s/$PATH_SEP/\//g; 1734 push @paths, cf::map::normalise "~$pl/$_";
1260 push @paths, new cf::path "~" . $pl->ob->name . "/" . $_;
1261 } 1735 }
1262 1736
1263 \@paths 1737 \@paths
1264} 1738}
1265 1739
1740=item $protocol_xml = $player->expand_cfpod ($cfpod)
1741
1742Expand deliantra pod fragments into protocol xml.
1743
1266=item $player->ext_reply ($msgid, $msgtype, %msg) 1744=item $player->ext_reply ($msgid, @msg)
1267 1745
1268Sends an ext reply to the player. 1746Sends an ext reply to the player.
1269 1747
1270=cut 1748=cut
1271 1749
1272sub ext_reply($$$%) { 1750sub ext_reply($$@) {
1273 my ($self, $id, %msg) = @_; 1751 my ($self, $id, @msg) = @_;
1274 1752
1275 $msg{msgid} = $id; 1753 $self->ns->ext_reply ($id, @msg)
1276
1277 $self->send ("ext " . cf::to_json \%msg);
1278} 1754}
1279 1755
1280package cf; 1756=item $player->ext_msg ($type, @msg)
1757
1758Sends an ext event to the client.
1759
1760=cut
1761
1762sub ext_msg($$@) {
1763 my ($self, $type, @msg) = @_;
1764
1765 $self->ns->ext_msg ($type, @msg);
1766}
1767
1768=head3 cf::region
1769
1770=over 4
1771
1772=cut
1773
1774package cf::region;
1775
1776=item cf::region::find_by_path $path
1777
1778Tries to decuce the likely region for a map knowing only its path.
1779
1780=cut
1781
1782sub find_by_path($) {
1783 my ($path) = @_;
1784
1785 $path =~ s/^~[^\/]*//; # skip ~login
1786
1787 my ($match, $specificity);
1788
1789 for my $region (list) {
1790 if ($region->{match} && $path =~ $region->{match}) {
1791 ($match, $specificity) = ($region, $region->specificity)
1792 if $region->specificity > $specificity;
1793 }
1794 }
1795
1796 $match
1797}
1281 1798
1282=back 1799=back
1283
1284 1800
1285=head3 cf::map 1801=head3 cf::map
1286 1802
1287=over 4 1803=over 4
1288 1804
1291package cf::map; 1807package cf::map;
1292 1808
1293use Fcntl; 1809use Fcntl;
1294use Coro::AIO; 1810use Coro::AIO;
1295 1811
1812use overload
1813 '""' => \&as_string,
1814 fallback => 1;
1815
1296our $MAX_RESET = 3600; 1816our $MAX_RESET = 3600;
1297our $DEFAULT_RESET = 3000; 1817our $DEFAULT_RESET = 3000;
1298 1818
1299sub generate_random_map { 1819sub generate_random_map {
1300 my ($path, $rmp) = @_; 1820 my ($self, $rmp) = @_;
1821
1822 my $lock = cf::lock_acquire "generate_random_map"; # the random map generator is NOT reentrant ATM
1301 1823
1302 # mit "rum" bekleckern, nicht 1824 # mit "rum" bekleckern, nicht
1303 cf::map::_create_random_map 1825 $self->_create_random_map (
1304 $path,
1305 $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle}, 1826 $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
1306 $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle}, 1827 $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle}, $rmp->{miningstyle},
1307 $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map}, 1828 $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
1308 $rmp->{exit_on_final_map}, 1829 $rmp->{exit_on_final_map},
1309 $rmp->{xsize}, $rmp->{ysize}, 1830 $rmp->{xsize}, $rmp->{ysize},
1310 $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3}, 1831 $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
1311 $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase}, 1832 $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
1312 $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation}, 1833 $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
1313 $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp}, 1834 $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
1314 $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used}, 1835 $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
1315 (cf::region::find $rmp->{region}) 1836 (cf::region::find $rmp->{region}), $rmp->{custom}
1837 )
1316} 1838}
1317 1839
1318# and all this just because we cannot iterate over 1840=item cf::map->register ($regex, $prio)
1319# all maps in C++...
1320sub change_all_map_light {
1321 my ($change) = @_;
1322 1841
1323 $_->change_map_light ($change) 1842Register a handler for the map path matching the given regex at the
1324 for grep $_->outdoor, values %cf::MAP; 1843givne priority (higher is better, built-in handlers have priority 0, the
1325} 1844default).
1326 1845
1327sub try_load_header($) { 1846=cut
1847
1848sub register {
1849 my (undef, $regex, $prio) = @_;
1850 my $pkg = caller;
1851
1852 push @{"$pkg\::ISA"}, __PACKAGE__;
1853
1854 $EXT_MAP{$pkg} = [$prio, qr<$regex>];
1855}
1856
1857# also paths starting with '/'
1858$EXT_MAP{"cf::map::wrap"} = [0, qr{^(?=/)}];
1859
1860sub thawer_merge {
1861 my ($self, $merge) = @_;
1862
1863 # we have to keep some variables in memory intact
1864 local $self->{path};
1865 local $self->{load_path};
1866
1867 $self->SUPER::thawer_merge ($merge);
1868}
1869
1870sub normalise {
1328 my ($path) = @_; 1871 my ($path, $base) = @_;
1872
1873 $path = "$path"; # make sure its a string
1874
1875 $path =~ s/\.map$//;
1876
1877 # map plan:
1878 #
1879 # /! non-realised random map exit (special hack!)
1880 # {... are special paths that are not being touched
1881 # ?xxx/... are special absolute paths
1882 # ?random/... random maps
1883 # /... normal maps
1884 # ~user/... per-player map of a specific user
1885
1886 $path =~ s/$PATH_SEP/\//go;
1887
1888 # treat it as relative path if it starts with
1889 # something that looks reasonable
1890 if ($path =~ m{^(?:\./|\.\./|\w)}) {
1891 $base or Carp::carp "normalise called with relative path and no base: '$path'";
1892
1893 $base =~ s{[^/]+/?$}{};
1894 $path = "$base/$path";
1895 }
1896
1897 for ($path) {
1898 redo if s{//}{/};
1899 redo if s{/\.?/}{/};
1900 redo if s{/[^/]+/\.\./}{/};
1901 }
1902
1903 $path
1904}
1905
1906sub new_from_path {
1907 my (undef, $path, $base) = @_;
1908
1909 return $path if UNIVERSAL::isa $path, "cf::map"; # already a map object
1910
1911 $path = normalise $path, $base;
1912
1913 for my $pkg (sort { $EXT_MAP{$b}[0] <=> $EXT_MAP{$a}[0] } keys %EXT_MAP) {
1914 if ($path =~ $EXT_MAP{$pkg}[1]) {
1915 my $self = bless cf::map::new, $pkg;
1916 $self->{path} = $path; $self->path ($path);
1917 $self->init; # pass $1 etc.
1918 return $self;
1919 }
1920 }
1921
1922 Carp::cluck "unable to resolve path '$path' (base '$base').";
1923 ()
1924}
1925
1926sub init {
1927 my ($self) = @_;
1928
1929 $self
1930}
1931
1932sub as_string {
1933 my ($self) = @_;
1934
1935 "$self->{path}"
1936}
1937
1938# the displayed name, this is a one way mapping
1939sub visible_name {
1940 &as_string
1941}
1942
1943# the original (read-only) location
1944sub load_path {
1945 my ($self) = @_;
1946
1947 "$MAPDIR/$self->{path}.map"
1948}
1949
1950# the temporary/swap location
1951sub save_path {
1952 my ($self) = @_;
1953
1954 (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/go;
1955 "$TMPDIR/$path.map"
1956}
1957
1958# the unique path, undef == no special unique path
1959sub uniq_path {
1960 my ($self) = @_;
1961
1962 (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/go;
1963 "$UNIQUEDIR/$path"
1964}
1965
1966sub decay_objects {
1967 my ($self) = @_;
1968
1969 return if $self->{deny_reset};
1970
1971 $self->do_decay_objects;
1972}
1973
1974sub unlink_save {
1975 my ($self) = @_;
1976
1977 utf8::encode (my $save = $self->save_path);
1978 IO::AIO::aioreq_pri 4; Coro::AIO::aio_unlink $save;
1979 IO::AIO::aioreq_pri 4; Coro::AIO::aio_unlink "$save.pst";
1980}
1981
1982sub load_header_from($) {
1983 my ($self, $path) = @_;
1329 1984
1330 utf8::encode $path; 1985 utf8::encode $path;
1331 aio_open $path, O_RDONLY, 0 1986 my $f = new_from_file cf::object::thawer $path
1332 or return; 1987 or return;
1333 1988
1334 my $map = cf::map::new 1989 $self->_load_header ($f)
1335 or return; 1990 or return;
1336 1991
1337 # for better error messages only, will be overwritten 1992 local $MAP_LOADING{$self->{path}} = $self;
1338 $map->path ($path); 1993 $f->resolve_delayed_derefs;
1339 1994
1340 $map->load_header ($path) 1995 $self->{load_path} = $path;
1996
1997 1
1998}
1999
2000sub load_header_orig {
2001 my ($self) = @_;
2002
2003 $self->load_header_from ($self->load_path)
2004}
2005
2006sub load_header_temp {
2007 my ($self) = @_;
2008
2009 $self->load_header_from ($self->save_path)
2010}
2011
2012sub prepare_temp {
2013 my ($self) = @_;
2014
2015 $self->last_access ((delete $self->{last_access})
2016 || $cf::RUNTIME); #d#
2017 # safety
2018 $self->{instantiate_time} = $cf::RUNTIME
2019 if $self->{instantiate_time} > $cf::RUNTIME;
2020}
2021
2022sub prepare_orig {
2023 my ($self) = @_;
2024
2025 $self->{load_original} = 1;
2026 $self->{instantiate_time} = $cf::RUNTIME;
2027 $self->last_access ($cf::RUNTIME);
2028 $self->instantiate;
2029}
2030
2031sub load_header {
2032 my ($self) = @_;
2033
2034 if ($self->load_header_temp) {
2035 $self->prepare_temp;
2036 } else {
2037 $self->load_header_orig
1341 or return; 2038 or return;
2039 $self->prepare_orig;
2040 }
1342 2041
1343 $map->{load_path} = $path; 2042 $self->{deny_reset} = 1
2043 if $self->no_reset;
1344 2044
1345 $map 2045 $self->default_region (cf::region::find_by_path $self->{path})
2046 unless $self->default_region;
2047
2048 1
1346} 2049}
1347 2050
1348sub find; 2051sub find;
1349sub find { 2052sub find {
1350 my ($path, $origin) = @_; 2053 my ($path, $origin) = @_;
1351 2054
1352 #warn "find<$path,$origin>\n";#d#
1353
1354 $path = new cf::path $path, $origin && $origin->path; 2055 $path = normalise $path, $origin && $origin->path;
1355 my $key = $path->as_string;
1356 2056
1357 cf::lock_wait "map_find:$key"; 2057 my $guard1 = cf::lock_acquire "map_data:$path";#d#remove
1358
1359 $cf::MAP{$key} || do {
1360 my $guard = cf::lock_acquire "map_find:$key"; 2058 my $guard2 = cf::lock_acquire "map_find:$path";
1361 2059
1362 # do it the slow way 2060 $cf::MAP{$path} || do {
1363 my $map = try_load_header $path->save_path; 2061 my $map = new_from_path cf::map $path
1364
1365 Coro::cede;
1366
1367 if ($map) {
1368 $map->last_access ((delete $map->{last_access})
1369 || $cf::RUNTIME); #d#
1370 # safety
1371 $map->{instantiate_time} = $cf::RUNTIME
1372 if $map->{instantiate_time} > $cf::RUNTIME;
1373 } else {
1374 if (my $rmp = $path->random_map_params) {
1375 $map = generate_random_map $key, $rmp;
1376 } else {
1377 $map = try_load_header $path->load_path;
1378 }
1379
1380 $map or return; 2062 or return;
1381 2063
1382 $map->{load_original} = 1;
1383 $map->{instantiate_time} = $cf::RUNTIME;
1384 $map->last_access ($cf::RUNTIME);
1385 $map->instantiate;
1386
1387 # per-player maps become, after loading, normal maps
1388 $map->per_player (0) if $path->{user_rel};
1389 }
1390
1391 $map->path ($key);
1392 $map->{path} = $path;
1393 $map->{last_save} = $cf::RUNTIME; 2064 $map->{last_save} = $cf::RUNTIME;
1394 2065
1395 Coro::cede; 2066 $map->load_header
2067 or return;
1396 2068
1397 if ($map->should_reset) { 2069 if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?)
2070 # doing this can freeze the server in a sync job, obviously
2071 #$cf::WAIT_FOR_TICK->wait;
2072 undef $guard2;
2073 undef $guard1;
1398 $map->reset; 2074 $map->reset;
1399 undef $guard;
1400 $map = find $path 2075 return find $path;
1401 or return;
1402 } 2076 }
1403 2077
1404 $cf::MAP{$key} = $map 2078 $cf::MAP{$path} = $map
1405 } 2079 }
1406} 2080}
2081
2082sub pre_load { }
2083#sub post_load { } # XS
1407 2084
1408sub load { 2085sub load {
1409 my ($self) = @_; 2086 my ($self) = @_;
1410 2087
2088 local $self->{deny_reset} = 1; # loading can take a long time
2089
1411 my $path = $self->{path}; 2090 my $path = $self->{path};
2091
2092 {
1412 my $guard = cf::lock_acquire "map_load:" . $path->as_string; 2093 my $guard = cf::lock_acquire "map_data:$path";
1413 2094
2095 return unless $self->valid;
1414 return if $self->in_memory != cf::MAP_SWAPPED; 2096 return unless $self->in_memory == cf::MAP_SWAPPED;
1415 2097
1416 $self->in_memory (cf::MAP_LOADING); 2098 $self->in_memory (cf::MAP_LOADING);
1417 2099
1418 $self->alloc; 2100 $self->alloc;
1419 $self->load_objects ($self->{load_path}, 1) 2101
2102 $self->pre_load;
2103 cf::cede_to_tick;
2104
2105 my $f = new_from_file cf::object::thawer $self->{load_path};
2106 $f->skip_block;
2107 $self->_load_objects ($f)
1420 or return; 2108 or return;
1421 2109
1422 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1) 2110 $self->post_load_original
1423 if delete $self->{load_original}; 2111 if delete $self->{load_original};
1424 2112
1425 if (my $uniq = $path->uniq_path) { 2113 if (my $uniq = $self->uniq_path) {
1426 utf8::encode $uniq; 2114 utf8::encode $uniq;
1427 if (aio_open $uniq, O_RDONLY, 0) { 2115 unless (aio_stat $uniq) {
2116 if (my $f = new_from_file cf::object::thawer $uniq) {
1428 $self->clear_unique_items; 2117 $self->clear_unique_items;
1429 $self->load_objects ($uniq, 0); 2118 $self->_load_objects ($f);
2119 $f->resolve_delayed_derefs;
2120 }
1430 } 2121 }
1431 } 2122 }
1432 2123
1433 Coro::cede; 2124 $f->resolve_delayed_derefs;
1434 2125
2126 cf::cede_to_tick;
1435 # now do the right thing for maps 2127 # now do the right thing for maps
1436 $self->link_multipart_objects; 2128 $self->link_multipart_objects;
1437
1438 if ($self->{path}->is_style_map) {
1439 $self->{deny_save} = 1;
1440 $self->{deny_reset} = 1;
1441 } else {
1442 $self->fix_auto_apply;
1443 $self->decay_objects;
1444 $self->update_buttons;
1445 $self->set_darkness_map;
1446 $self->difficulty ($self->estimate_difficulty) 2129 $self->difficulty ($self->estimate_difficulty)
1447 unless $self->difficulty; 2130 unless $self->difficulty;
2131 cf::cede_to_tick;
2132
2133 unless ($self->{deny_activate}) {
2134 $self->decay_objects;
2135 $self->fix_auto_apply;
2136 $self->update_buttons;
2137 cf::cede_to_tick;
1448 $self->activate; 2138 $self->activate;
1449 } 2139 }
1450 2140
1451 Coro::cede; 2141 $self->{last_save} = $cf::RUNTIME;
2142 $self->last_access ($cf::RUNTIME);
1452 2143
1453 $self->in_memory (cf::MAP_IN_MEMORY); 2144 $self->in_memory (cf::MAP_ACTIVE);
2145 }
2146
2147 $self->post_load;
2148}
2149
2150# customize the map for a given player, i.e.
2151# return the _real_ map. used by e.g. per-player
2152# maps to change the path to ~playername/mappath
2153sub customise_for {
2154 my ($self, $ob) = @_;
2155
2156 return find "~" . $ob->name . "/" . $self->{path}
2157 if $self->per_player;
2158
2159# return find "?party/" . $ob->name . "/" . $self->{path}
2160# if $self->per_party;
2161
2162 $self
2163}
2164
2165# find and load all maps in the 3x3 area around a map
2166sub load_neighbours {
2167 my ($map) = @_;
2168
2169 my @neigh; # diagonal neighbours
2170
2171 for (0 .. 3) {
2172 my $neigh = $map->tile_path ($_)
2173 or next;
2174 $neigh = find $neigh, $map
2175 or next;
2176 $neigh->load;
2177
2178 # now find the diagonal neighbours
2179 push @neigh,
2180 [$neigh->tile_path (($_ + 3) % 4), $neigh],
2181 [$neigh->tile_path (($_ + 1) % 4), $neigh];
2182 }
2183
2184 for (grep defined $_->[0], @neigh) {
2185 my ($path, $origin) = @$_;
2186 my $neigh = find $path, $origin
2187 or next;
2188 $neigh->load;
2189 }
1454} 2190}
1455 2191
1456sub find_sync { 2192sub find_sync {
1457 my ($path, $origin) = @_; 2193 my ($path, $origin) = @_;
1458 2194
1459 cf::sync_job { cf::map::find $path, $origin } 2195 return cf::LOG cf::llevError | cf::logBacktrace, "do_find_sync"
2196 if $Coro::current == $Coro::main;
2197
2198 find $path, $origin
1460} 2199}
1461 2200
1462sub do_load_sync { 2201sub do_load_sync {
1463 my ($map) = @_; 2202 my ($map) = @_;
1464 2203
1465 cf::sync_job { $map->load }; 2204 return cf::LOG cf::llevError | cf::logBacktrace, "do_load_sync"
2205 if $Coro::current == $Coro::main;
2206
2207 $map->load;
2208}
2209
2210our %MAP_PREFETCH;
2211our $MAP_PREFETCHER = undef;
2212
2213sub find_async {
2214 my ($path, $origin, $load) = @_;
2215
2216 $path = normalise $path, $origin && $origin->{path};
2217
2218 if (my $map = $cf::MAP{$path}) {
2219 return $map if !$load || $map->in_memory == cf::MAP_ACTIVE;
2220 }
2221
2222 $MAP_PREFETCH{$path} |= $load;
2223
2224 $MAP_PREFETCHER ||= cf::async {
2225 $Coro::current->{desc} = "map prefetcher";
2226
2227 while (%MAP_PREFETCH) {
2228 while (my ($k, $v) = each %MAP_PREFETCH) {
2229 if (my $map = find $k) {
2230 $map->load if $v;
2231 }
2232
2233 delete $MAP_PREFETCH{$k};
2234 }
2235 }
2236 undef $MAP_PREFETCHER;
2237 };
2238 $MAP_PREFETCHER->prio (6);
2239
2240 ()
2241}
2242
2243# common code, used by both ->save and ->swapout
2244sub _save {
2245 my ($self) = @_;
2246
2247 $self->{last_save} = $cf::RUNTIME;
2248
2249 return unless $self->dirty;
2250
2251 my $save = $self->save_path; utf8::encode $save;
2252 my $uniq = $self->uniq_path; utf8::encode $uniq;
2253
2254 $self->{load_path} = $save;
2255
2256 return if $self->{deny_save};
2257
2258 local $self->{last_access} = $self->last_access;#d#
2259
2260 cf::async {
2261 $Coro::current->{desc} = "map player save";
2262 $_->contr->save for $self->players;
2263 };
2264
2265 cf::get_slot 0.02;
2266
2267 if ($uniq) {
2268 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
2269 $self->_save_objects ($uniq, cf::IO_UNIQUES);
2270 } else {
2271 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
2272 }
1466} 2273}
1467 2274
1468sub save { 2275sub save {
1469 my ($self) = @_; 2276 my ($self) = @_;
1470 2277
1471 my $lock = cf::lock_acquire "map_data:" . $self->path; 2278 my $lock = cf::lock_acquire "map_data:$self->{path}";
1472 2279
1473 $self->{last_save} = $cf::RUNTIME; 2280 $self->_save;
1474
1475 return unless $self->dirty;
1476
1477 my $save = $self->{path}->save_path; utf8::encode $save;
1478 my $uniq = $self->{path}->uniq_path; utf8::encode $uniq;
1479
1480 $self->{load_path} = $save;
1481
1482 return if $self->{deny_save};
1483
1484 local $self->{last_access} = $self->last_access;#d#
1485
1486 cf::async {
1487 $_->contr->save for $self->players;
1488 };
1489
1490 if ($uniq) {
1491 $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
1492 $self->save_objects ($uniq, cf::IO_UNIQUES);
1493 } else {
1494 $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
1495 }
1496} 2281}
1497 2282
1498sub swap_out { 2283sub swap_out {
1499 my ($self) = @_; 2284 my ($self) = @_;
1500 2285
1501 # save first because save cedes
1502 $self->save;
1503
1504 my $lock = cf::lock_acquire "map_data:" . $self->path; 2286 my $lock = cf::lock_acquire "map_data:$self->{path}";
1505 2287
2288 return if $self->in_memory != cf::MAP_ACTIVE;
2289 return if $self->{deny_save};
1506 return if $self->players; 2290 return if $self->players;
1507 return if $self->in_memory != cf::MAP_IN_MEMORY;
1508 return if $self->{deny_save};
1509 2291
2292 # first deactivate the map and "unlink" it from the core
2293 $self->deactivate;
2294 $_->clear_links_to ($self) for values %cf::MAP;
2295 $self->in_memory (cf::MAP_SWAPPED);
2296
2297 # then atomically save
2298 $self->_save;
2299
2300 # then free the map
1510 $self->clear; 2301 $self->clear;
1511 $self->in_memory (cf::MAP_SWAPPED);
1512} 2302}
1513 2303
1514sub reset_at { 2304sub reset_at {
1515 my ($self) = @_; 2305 my ($self) = @_;
1516 2306
1517 # TODO: safety, remove and allow resettable per-player maps 2307 # TODO: safety, remove and allow resettable per-player maps
1518 return 1e99 if $self->{path}{user_rel};
1519 return 1e99 if $self->{deny_reset}; 2308 return 1e99 if $self->{deny_reset};
1520 2309
1521 my $time = $self->fixed_resettime ? $self->{instantiate_time} : $self->last_access; 2310 my $time = $self->fixed_resettime ? $self->{instantiate_time} : $self->last_access;
1522 my $to = List::Util::min $MAX_RESET, $self->reset_timeout || $DEFAULT_RESET; 2311 my $to = List::Util::min $MAX_RESET, $self->reset_timeout || $DEFAULT_RESET;
1523 2312
1528 my ($self) = @_; 2317 my ($self) = @_;
1529 2318
1530 $self->reset_at <= $cf::RUNTIME 2319 $self->reset_at <= $cf::RUNTIME
1531} 2320}
1532 2321
1533sub unlink_save {
1534 my ($self) = @_;
1535
1536 utf8::encode (my $save = $self->{path}->save_path);
1537 aioreq_pri 3; IO::AIO::aio_unlink $save;
1538 aioreq_pri 3; IO::AIO::aio_unlink "$save.pst";
1539}
1540
1541sub rename {
1542 my ($self, $new_path) = @_;
1543
1544 $self->unlink_save;
1545
1546 delete $cf::MAP{$self->path};
1547 $self->{path} = new cf::path $new_path;
1548 $self->path ($self->{path}->as_string);
1549 $cf::MAP{$self->path} = $self;
1550
1551 $self->save;
1552}
1553
1554sub reset { 2322sub reset {
1555 my ($self) = @_; 2323 my ($self) = @_;
1556 2324
1557 my $lock = cf::lock_acquire "map_data:" . $self->path; 2325 my $lock = cf::lock_acquire "map_data:$self->{path}";
1558 2326
1559 return if $self->players; 2327 return if $self->players;
1560 return if $self->{path}{user_rel};#d#
1561 2328
1562 warn "resetting map ", $self->path;#d# 2329 cf::trace "resetting map ", $self->path, "\n";
2330
2331 $self->in_memory (cf::MAP_SWAPPED);
2332
2333 # need to save uniques path
2334 unless ($self->{deny_save}) {
2335 my $uniq = $self->uniq_path; utf8::encode $uniq;
2336
2337 $self->_save_objects ($uniq, cf::IO_UNIQUES)
2338 if $uniq;
2339 }
1563 2340
1564 delete $cf::MAP{$self->path}; 2341 delete $cf::MAP{$self->path};
1565 2342
2343 $self->deactivate;
1566 $_->clear_links_to ($self) for values %cf::MAP; 2344 $_->clear_links_to ($self) for values %cf::MAP;
2345 $self->clear;
1567 2346
1568 $self->unlink_save; 2347 $self->unlink_save;
1569 $self->destroy; 2348 $self->destroy;
1570} 2349}
1571 2350
1572my $nuke_counter = "aaaa"; 2351my $nuke_counter = "aaaa";
1573 2352
1574sub nuke { 2353sub nuke {
1575 my ($self) = @_; 2354 my ($self) = @_;
1576 2355
2356 {
2357 my $lock = cf::lock_acquire "map_data:$self->{path}";
2358
2359 delete $cf::MAP{$self->path};
2360
2361 $self->unlink_save;
2362
2363 bless $self, "cf::map::wrap";
2364 delete $self->{deny_reset};
1577 $self->{deny_save} = 1; 2365 $self->{deny_save} = 1;
1578 $self->reset_timeout (1); 2366 $self->reset_timeout (1);
1579 $self->rename ("{nuke}/" . ($nuke_counter++)); 2367 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++));
2368
2369 $cf::MAP{$self->path} = $self;
2370 }
2371
1580 $self->reset; # polite request, might not happen 2372 $self->reset; # polite request, might not happen
1581} 2373}
1582 2374
1583sub customise_for { 2375=item $maps = cf::map::tmp_maps
1584 my ($map, $ob) = @_;
1585 2376
1586 if ($map->per_player) { 2377Returns an arrayref with all map paths of currently instantiated and saved
1587 return cf::map::find "~" . $ob->name . "/" . $map->{path}{path}; 2378maps. May block.
2379
2380=cut
2381
2382sub tmp_maps() {
1588 } 2383 [
1589 2384 map {
1590 $map 2385 utf8::decode $_;
2386 /\.map$/
2387 ? normalise $_
2388 : ()
2389 } @{ aio_readdir $TMPDIR or [] }
2390 ]
1591} 2391}
1592 2392
1593sub emergency_save { 2393=item $maps = cf::map::random_maps
1594 my $freeze_guard = cf::freeze_mainloop;
1595 2394
1596 warn "enter emergency perl save\n"; 2395Returns an arrayref with all map paths of currently instantiated and saved
2396random maps. May block.
1597 2397
1598 cf::sync_job { 2398=cut
1599 warn "begin emergency player save\n";
1600 $_->save for values %cf::PLAYER;
1601 warn "end emergency player save\n";
1602 2399
1603 warn "begin emergency map save\n"; 2400sub random_maps() {
1604 $_->save for values %cf::MAP; 2401 [
1605 warn "end emergency map save\n"; 2402 map {
2403 utf8::decode $_;
2404 /\.map$/
2405 ? normalise "?random/$_"
2406 : ()
2407 } @{ aio_readdir $RANDOMDIR or [] }
2408 ]
2409}
2410
2411=item cf::map::unique_maps
2412
2413Returns an arrayref of paths of all shared maps that have
2414instantiated unique items. May block.
2415
2416=cut
2417
2418sub unique_maps() {
2419 [
2420 map {
2421 utf8::decode $_;
2422 s/\.map$//; # TODO future compatibility hack
2423 /\.pst$/ || !/^$PATH_SEP/o # TODO unique maps apparebntly lack the .map suffix :/
2424 ? ()
2425 : normalise $_
2426 } @{ aio_readdir $UNIQUEDIR or [] }
2427 ]
2428}
2429
2430=item cf::map::static_maps
2431
2432Returns an arrayref if paths of all static maps (all preinstalled F<.map>
2433file in the shared directory excluding F</styles> and F</editor>). May
2434block.
2435
2436=cut
2437
2438sub static_maps() {
2439 my @dirs = "";
2440 my @maps;
2441
2442 while (@dirs) {
2443 my $dir = shift @dirs;
2444
2445 next if $dir eq "/styles" || $dir eq "/editor";
2446
2447 my ($dirs, $files) = Coro::AIO::aio_scandir "$MAPDIR$dir", 2
2448 or return;
2449
2450 for (@$files) {
2451 s/\.map$// or next;
2452 utf8::decode $_;
2453 push @maps, "$dir/$_";
2454 }
2455
2456 push @dirs, map "$dir/$_", @$dirs;
1606 }; 2457 }
1607 2458
1608 warn "leave emergency perl save\n"; 2459 \@maps
2460}
2461
2462=back
2463
2464=head3 cf::object
2465
2466=cut
2467
2468package cf::object;
2469
2470=over 4
2471
2472=item $ob->inv_recursive
2473
2474Returns the inventory of the object I<and> their inventories, recursively,
2475but I<not> the object itself.
2476
2477=cut
2478
2479sub inv_recursive_;
2480sub inv_recursive_ {
2481 map { $_, inv_recursive_ $_->inv } @_
2482}
2483
2484sub inv_recursive {
2485 inv_recursive_ inv $_[0]
2486}
2487
2488=item $ref = $ob->ref
2489
2490Creates and returns a persistent reference to an object that can be stored as a string.
2491
2492=item $ob = cf::object::deref ($refstring)
2493
2494returns the objetc referenced by refstring. may return undef when it cnanot find the object,
2495even if the object actually exists. May block.
2496
2497=cut
2498
2499sub deref {
2500 my ($ref) = @_;
2501
2502 if ($ref =~ m{^player\/(<1\.[0-9a-f]+>)/(.*)$}) {
2503 my ($uuid, $name) = ($1, $2);
2504 my $pl = $cf::PLAYER_LOADING{$name} || cf::player::find $name
2505 or return;
2506 $pl->ob->uuid eq $uuid
2507 or return;
2508
2509 $pl->ob
2510 } else {
2511 warn "$ref: cannot resolve object reference\n";
2512 undef
2513 }
1609} 2514}
1610 2515
1611package cf; 2516package cf;
1612 2517
1613=back 2518=back
1614
1615 2519
1616=head3 cf::object::player 2520=head3 cf::object::player
1617 2521
1618=over 4 2522=over 4
1619 2523
1622Sends a message to the player, as if the npc C<$npc> replied. C<$npc> 2526Sends a message to the player, as if the npc C<$npc> replied. C<$npc>
1623can be C<undef>. Does the right thing when the player is currently in a 2527can be C<undef>. Does the right thing when the player is currently in a
1624dialogue with the given NPC character. 2528dialogue with the given NPC character.
1625 2529
1626=cut 2530=cut
2531
2532our $SAY_CHANNEL = {
2533 id => "say",
2534 title => "Map",
2535 reply => "say ",
2536 tooltip => "Things said to and replied from NPCs near you and other players on the same map only.",
2537};
2538
2539our $CHAT_CHANNEL = {
2540 id => "chat",
2541 title => "Chat",
2542 reply => "chat ",
2543 tooltip => "Player chat and shouts, global to the server.",
2544};
1627 2545
1628# rough implementation of a future "reply" method that works 2546# rough implementation of a future "reply" method that works
1629# with dialog boxes. 2547# with dialog boxes.
1630#TODO: the first argument must go, split into a $npc->reply_to ( method 2548#TODO: the first argument must go, split into a $npc->reply_to ( method
1631sub cf::object::player::reply($$$;$) { 2549sub cf::object::player::reply($$$;$) {
1633 2551
1634 $flags = cf::NDI_BROWN | cf::NDI_UNIQUE unless @_ >= 4; 2552 $flags = cf::NDI_BROWN | cf::NDI_UNIQUE unless @_ >= 4;
1635 2553
1636 if ($self->{record_replies}) { 2554 if ($self->{record_replies}) {
1637 push @{ $self->{record_replies} }, [$npc, $msg, $flags]; 2555 push @{ $self->{record_replies} }, [$npc, $msg, $flags];
2556
1638 } else { 2557 } else {
2558 my $pl = $self->contr;
2559
2560 if ($pl->{npc_dialog} && $pl->{npc_dialog}->{id}) {
2561 my $dialog = $pl->{npc_dialog};
2562 $dialog->{pl}->ext_msg ($dialog->{id}, update => msg => $dialog->{pl}->expand_cfpod ($msg));
2563
2564 } else {
1639 $msg = $npc->name . " says: $msg" if $npc; 2565 $msg = $npc->name . " says: $msg" if $npc;
1640 $self->message ($msg, $flags); 2566 $self->send_msg ($SAY_CHANNEL => $msg, $flags);
2567 }
1641 } 2568 }
2569}
2570
2571=item $object->send_msg ($channel, $msg, $color, [extra...])
2572
2573=cut
2574
2575sub cf::object::send_msg {
2576 my $pl = shift->contr
2577 or return;
2578 $pl->send_msg (@_);
1642} 2579}
1643 2580
1644=item $player_object->may ("access") 2581=item $player_object->may ("access")
1645 2582
1646Returns wether the given player is authorized to access resource "access" 2583Returns wether the given player is authorized to access resource "access"
1659 2596
1660=item $player_object->enter_link 2597=item $player_object->enter_link
1661 2598
1662Freezes the player and moves him/her to a special map (C<{link}>). 2599Freezes the player and moves him/her to a special map (C<{link}>).
1663 2600
1664The player should be reaosnably safe there for short amounts of time. You 2601The player should be reasonably safe there for short amounts of time (e.g.
1665I<MUST> call C<leave_link> as soon as possible, though. 2602for loading a map). You I<MUST> call C<leave_link> as soon as possible,
2603though, as the player cannot control the character while it is on the link
2604map.
2605
2606Will never block.
1666 2607
1667=item $player_object->leave_link ($map, $x, $y) 2608=item $player_object->leave_link ($map, $x, $y)
1668 2609
1669Moves the player out of the specila link map onto the given map. If the 2610Moves the player out of the special C<{link}> map onto the specified
1670map is not valid (or omitted), the player will be moved back to the 2611map. If the map is not valid (or omitted), the player will be moved back
1671location he/she was before the call to C<enter_link>, or, if that fails, 2612to the location he/she was before the call to C<enter_link>, or, if that
1672to the emergency map position. 2613fails, to the emergency map position.
1673 2614
1674Might block. 2615Might block.
1675 2616
1676=cut 2617=cut
2618
2619sub link_map {
2620 unless ($LINK_MAP) {
2621 $LINK_MAP = cf::map::find "{link}"
2622 or cf::cleanup "FATAL: unable to provide {link} map, exiting.";
2623 $LINK_MAP->load;
2624 }
2625
2626 $LINK_MAP
2627}
1677 2628
1678sub cf::object::player::enter_link { 2629sub cf::object::player::enter_link {
1679 my ($self) = @_; 2630 my ($self) = @_;
1680 2631
1681 $self->deactivate_recursive; 2632 $self->deactivate_recursive;
1682 2633
1683 return if $self->map == $LINK_MAP; 2634 ++$self->{_link_recursion};
2635
2636 return if UNIVERSAL::isa $self->map, "ext::map_link";
1684 2637
1685 $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y] 2638 $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y]
1686 if $self->map; 2639 if $self->map && $self->map->{path} ne "{link}";
1687 2640
1688 $self->enter_map ($LINK_MAP, 20, 20); 2641 $self->enter_map ($LINK_MAP || link_map, 3, 3);
1689} 2642}
1690 2643
1691sub cf::object::player::leave_link { 2644sub cf::object::player::leave_link {
1692 my ($self, $map, $x, $y) = @_; 2645 my ($self, $map, $x, $y) = @_;
2646
2647 return unless $self->contr->active;
1693 2648
1694 my $link_pos = delete $self->{_link_pos}; 2649 my $link_pos = delete $self->{_link_pos};
1695 2650
1696 unless ($map) { 2651 unless ($map) {
1697 # restore original map position 2652 # restore original map position
1708 ($x, $y) = (-1, -1) 2663 ($x, $y) = (-1, -1)
1709 unless (defined $x) && (defined $y); 2664 unless (defined $x) && (defined $y);
1710 2665
1711 # use -1 or undef as default coordinates, not 0, 0 2666 # use -1 or undef as default coordinates, not 0, 0
1712 ($x, $y) = ($map->enter_x, $map->enter_y) 2667 ($x, $y) = ($map->enter_x, $map->enter_y)
1713 if $x <=0 && $y <= 0; 2668 if $x <= 0 && $y <= 0;
1714 2669
1715 $map->load; 2670 $map->load;
2671 $map->load_neighbours;
1716 2672
1717 return unless $self->contr->active; 2673 return unless $self->contr->active;
2674
2675 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext
2676 if ($self->enter_map ($map, $x, $y)) {
2677 # entering was successful
2678 delete $self->{_link_recursion};
2679 # only activate afterwards, to support waiting in hooks
1718 $self->activate_recursive; 2680 $self->activate_recursive;
1719 $self->enter_map ($map, $x, $y);
1720}
1721
1722cf::player->attach (
1723 on_logout => sub {
1724 my ($pl) = @_;
1725
1726 # abort map switching before logout
1727 if ($pl->ob->{_link_pos}) {
1728 cf::sync_job {
1729 $pl->ob->leave_link
1730 };
1731 }
1732 }, 2681 }
1733 on_login => sub {
1734 my ($pl) = @_;
1735 2682
1736 # try to abort aborted map switching on player login :) 2683}
1737 # should happen only on crashes
1738 if ($pl->ob->{_link_pos}) {
1739 $pl->ob->enter_link;
1740 (async {
1741 # we need this sleep as the login has a concurrent enter_exit running
1742 # and this sleep increases chances of the player not ending up in scorn
1743 $pl->ob->reply (undef,
1744 "There was an internal problem at your last logout, "
1745 . "the server will try to bring you to your intended destination in a second.",
1746 cf::NDI_RED);
1747 Coro::Timer::sleep 1;
1748 $pl->ob->leave_link;
1749 })->prio (2);
1750 }
1751 },
1752);
1753 2684
1754=item $player_object->goto ($path, $x, $y) 2685=item $player_object->goto ($path, $x, $y[, $check->($map, $x, $y, $player)[, $done->($player)]])
1755 2686
2687Moves the player to the given map-path and coordinates by first freezing
2688her, loading and preparing them map, calling the provided $check callback
2689that has to return the map if sucecssful, and then unfreezes the player on
2690the new (success) or old (failed) map position. In either case, $done will
2691be called at the end of this process.
2692
2693Note that $check will be called with a potentially non-loaded map, so if
2694it needs a loaded map it has to call C<< ->load >>.
2695
1756=cut 2696=cut
2697
2698our $GOTOGEN;
1757 2699
1758sub cf::object::player::goto { 2700sub cf::object::player::goto {
1759 my ($self, $path, $x, $y) = @_; 2701 my ($self, $path, $x, $y, $check, $done) = @_;
1760 2702
1761 $path = new cf::path $path; 2703 if ($self->{_link_recursion} >= $MAX_LINKS) {
1762 $path ne "/" or Carp::cluck ("oy");#d# 2704 error "FATAL: link recursion exceeded, ", $self->name, " goto $path $x $y, redirecting.";
2705 $self->failmsg ("Something went wrong inside the server - please contact an administrator!");
2706 ($path, $x, $y) = @$EMERGENCY_POSITION;
2707 }
2708
2709 # do generation counting so two concurrent goto's will be executed in-order
2710 my $gen = $self->{_goto_generation} = ++$GOTOGEN;
1763 2711
1764 $self->enter_link; 2712 $self->enter_link;
1765 2713
1766 (async { 2714 (async {
1767 my $map = cf::map::find $path->as_string; 2715 $Coro::current->{desc} = "player::goto $path $x $y";
2716
2717 # *tag paths override both path and x|y
2718 if ($path =~ /^\*(.*)$/) {
2719 if (my @obs = grep $_->map, ext::map_tags::find $1) {
2720 my $ob = $obs[rand @obs];
2721
2722 # see if we actually can go there
2723 if (@obs = grep !$self->blocked ($_->map, $_->x, $_->y), $ob, $ob->tail) {
2724 $ob = $obs[rand @obs];
2725 } else {
2726 $self->message ("Wow, it's pretty crowded in there.", cf::NDI_UNIQUE | cf::NDI_RED);
2727 }
2728 # else put us there anyways for now #d#
2729
2730 ($path, $x, $y) = ($ob->map, $ob->x, $ob->y);
2731 } else {
2732 ($path, $x, $y) = (undef, undef, undef);
2733 }
2734 }
2735
2736 my $map = eval {
2737 my $map = defined $path ? cf::map::find $path : undef;
2738
2739 if ($map) {
1768 $map = $map->customise_for ($self) if $map; 2740 $map = $map->customise_for ($self);
1769 2741 $map = $check->($map, $x, $y, $self) if $check && $map;
1770# warn "entering ", $map->path, " at ($x, $y)\n" 2742 } else {
1771# if $map;
1772
1773 $map or $self->message ("The exit to '" . ($path->visible_name) . "' is closed", cf::NDI_UNIQUE | cf::NDI_RED); 2743 $self->message ("The exit to '$path' is closed.", cf::NDI_UNIQUE | cf::NDI_RED);
2744 }
1774 2745
2746 $map
2747 };
2748
2749 if ($@) {
2750 $self->message ("Something went wrong within the server, please report this incident!", cf::NDI_UNIQUE | cf::NDI_RED);
2751 LOG llevError | logBacktrace, Carp::longmess $@;
2752 }
2753
2754 if ($gen == $self->{_goto_generation}) {
2755 delete $self->{_goto_generation};
1775 $self->leave_link ($map, $x, $y); 2756 $self->leave_link ($map, $x, $y);
2757 }
2758
2759 $done->($self) if $done;
1776 })->prio (1); 2760 })->prio (1);
1777} 2761}
1778 2762
1779=item $player_object->enter_exit ($exit_object) 2763=item $player_object->enter_exit ($exit_object)
1780 2764
1782 2766
1783sub parse_random_map_params { 2767sub parse_random_map_params {
1784 my ($spec) = @_; 2768 my ($spec) = @_;
1785 2769
1786 my $rmp = { # defaults 2770 my $rmp = { # defaults
1787 xsize => 10, 2771 xsize => (cf::rndm 15, 40),
1788 ysize => 10, 2772 ysize => (cf::rndm 15, 40),
2773 symmetry => (cf::rndm 1, cf::SYMMETRY_XY),
2774 #layout => string,
1789 }; 2775 };
1790 2776
1791 for (split /\n/, $spec) { 2777 for (split /\n/, $spec) {
1792 my ($k, $v) = split /\s+/, $_, 2; 2778 my ($k, $v) = split /\s+/, $_, 2;
1793 2779
1805 # that depends on the exit object 2791 # that depends on the exit object
1806 2792
1807 my $rmp = parse_random_map_params $exit->msg; 2793 my $rmp = parse_random_map_params $exit->msg;
1808 2794
1809 if ($exit->map) { 2795 if ($exit->map) {
1810 $rmp->{region} = $exit->map->region_name; 2796 $rmp->{region} = $exit->region->name;
1811 $rmp->{origin_map} = $exit->map->path; 2797 $rmp->{origin_map} = $exit->map->path;
1812 $rmp->{origin_x} = $exit->x; 2798 $rmp->{origin_x} = $exit->x;
1813 $rmp->{origin_y} = $exit->y; 2799 $rmp->{origin_y} = $exit->y;
2800
2801 $exit->map->touch;
1814 } 2802 }
1815 2803
1816 $rmp->{random_seed} ||= $exit->random_seed; 2804 $rmp->{random_seed} ||= $exit->random_seed;
1817 2805
1818 my $data = cf::to_json $rmp; 2806 my $data = JSON::XS->new->utf8->pretty->canonical->encode ($rmp);
1819 my $md5 = Digest::MD5::md5_hex $data; 2807 my $md5 = Digest::MD5::md5_hex $data;
2808 my $meta = "$RANDOMDIR/$md5.meta";
1820 2809
1821 if (my $fh = aio_open "$cf::RANDOM_MAPS/$md5.meta", O_WRONLY | O_CREAT, 0666) { 2810 if (my $fh = aio_open "$meta~", O_WRONLY | O_CREAT, 0666) {
1822 aio_write $fh, 0, (length $data), $data, 0; 2811 aio_write $fh, 0, (length $data), $data, 0;
2812 undef $fh;
2813 aio_rename "$meta~", $meta;
1823 2814
2815 my $slaying = "?random/$md5";
2816
2817 if ($exit->valid) {
1824 $exit->slaying ("?random/$md5"); 2818 $exit->slaying ("?random/$md5");
1825 $exit->msg (undef); 2819 $exit->msg (undef);
2820 }
1826 } 2821 }
1827} 2822}
1828 2823
1829sub cf::object::player::enter_exit { 2824sub cf::object::player::enter_exit {
1830 my ($self, $exit) = @_; 2825 my ($self, $exit) = @_;
1832 return unless $self->type == cf::PLAYER; 2827 return unless $self->type == cf::PLAYER;
1833 2828
1834 $self->enter_link; 2829 $self->enter_link;
1835 2830
1836 (async { 2831 (async {
1837 $self->deactivate_recursive; # just to be sure 2832 $Coro::current->{desc} = "enter_exit";
2833
1838 unless (eval { 2834 unless (eval {
2835 $self->deactivate_recursive; # just to be sure
2836
2837 # random map handling
2838 {
2839 my $guard = cf::lock_acquire "exit_prepare:$exit";
2840
1839 prepare_random_map $exit 2841 prepare_random_map $exit
1840 if $exit->slaying eq "/!"; 2842 if $exit->slaying eq "/!";
2843 }
1841 2844
1842 my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path; 2845 my $map = cf::map::normalise $exit->slaying, $exit->map && $exit->map->path;
1843 $self->goto ($path, $exit->stats->hp, $exit->stats->sp); 2846 my $x = $exit->stats->hp;
2847 my $y = $exit->stats->sp;
1844 2848
2849 $self->goto ($map, $x, $y);
2850
2851 # if exit is damned, update players death & WoR home-position
2852 $self->contr->savebed ($map, $x, $y)
2853 if $exit->flag (cf::FLAG_DAMNED);
2854
1845 1; 2855 1
1846 }) { 2856 }) {
1847 $self->message ("Something went wrong deep within the crossfire server. " 2857 $self->message ("Something went wrong deep within the deliantra server. "
1848 . "I'll try to bring you back to the map you were before. " 2858 . "I'll try to bring you back to the map you were before. "
1849 . "Please report this to the dungeon master", 2859 . "Please report this to the dungeon master!",
1850 cf::NDI_UNIQUE | cf::NDI_RED); 2860 cf::NDI_UNIQUE | cf::NDI_RED);
1851 2861
1852 warn "ERROR in enter_exit: $@"; 2862 error "ERROR in enter_exit: $@";
1853 $self->leave_link; 2863 $self->leave_link;
1854 } 2864 }
1855 })->prio (1); 2865 })->prio (1);
1856} 2866}
1857 2867
1868 2878
1869sub cf::client::send_drawinfo { 2879sub cf::client::send_drawinfo {
1870 my ($self, $text, $flags) = @_; 2880 my ($self, $text, $flags) = @_;
1871 2881
1872 utf8::encode $text; 2882 utf8::encode $text;
1873 $self->send_packet (sprintf "drawinfo %d %s", $flags, $text); 2883 $self->send_packet (sprintf "drawinfo %d %s", $flags || cf::NDI_BLACK, $text);
1874} 2884}
1875 2885
2886=item $client->send_big_packet ($pkt)
2887
2888Like C<send_packet>, but tries to compress large packets, and fragments
2889them as required.
2890
2891=cut
2892
2893our $MAXFRAGSIZE = cf::MAXSOCKBUF - 64;
2894
2895sub cf::client::send_big_packet {
2896 my ($self, $pkt) = @_;
2897
2898 # try lzf for large packets
2899 $pkt = "lzf " . Compress::LZF::compress $pkt
2900 if 1024 <= length $pkt and $self->{can_lzf};
2901
2902 # split very large packets
2903 if ($MAXFRAGSIZE < length $pkt and $self->{can_lzf}) {
2904 $self->send_packet ("frag $_") for unpack "(a$MAXFRAGSIZE)*", $pkt;
2905 $pkt = "frag";
2906 }
2907
2908 $self->send_packet ($pkt);
2909}
2910
2911=item $client->send_msg ($channel, $msg, $color, [extra...])
2912
2913Send a drawinfo or msg packet to the client, formatting the msg for the
2914client if neccessary. C<$type> should be a string identifying the type of
2915the message, with C<log> being the default. If C<$color> is negative, suppress
2916the message unless the client supports the msg packet.
2917
2918=cut
2919
2920# non-persistent channels (usually the info channel)
2921our %CHANNEL = (
2922 "c/motd" => {
2923 id => "infobox",
2924 title => "MOTD",
2925 reply => undef,
2926 tooltip => "The message of the day",
2927 },
2928 "c/identify" => {
2929 id => "infobox",
2930 title => "Identify",
2931 reply => undef,
2932 tooltip => "Items recently identified",
2933 },
2934 "c/examine" => {
2935 id => "infobox",
2936 title => "Examine",
2937 reply => undef,
2938 tooltip => "Signs and other items you examined",
2939 },
2940 "c/shopinfo" => {
2941 id => "infobox",
2942 title => "Shop Info",
2943 reply => undef,
2944 tooltip => "What your bargaining skill tells you about the shop",
2945 },
2946 "c/book" => {
2947 id => "infobox",
2948 title => "Book",
2949 reply => undef,
2950 tooltip => "The contents of a note or book",
2951 },
2952 "c/lookat" => {
2953 id => "infobox",
2954 title => "Look",
2955 reply => undef,
2956 tooltip => "What you saw there",
2957 },
2958 "c/who" => {
2959 id => "infobox",
2960 title => "Players",
2961 reply => undef,
2962 tooltip => "Shows players who are currently online",
2963 },
2964 "c/body" => {
2965 id => "infobox",
2966 title => "Body Parts",
2967 reply => undef,
2968 tooltip => "Shows which body parts you posess and are available",
2969 },
2970 "c/statistics" => {
2971 id => "infobox",
2972 title => "Statistics",
2973 reply => undef,
2974 tooltip => "Shows your primary statistics",
2975 },
2976 "c/skills" => {
2977 id => "infobox",
2978 title => "Skills",
2979 reply => undef,
2980 tooltip => "Shows your experience per skill and item power",
2981 },
2982 "c/shopitems" => {
2983 id => "infobox",
2984 title => "Shop Items",
2985 reply => undef,
2986 tooltip => "Shows the items currently for sale in this shop",
2987 },
2988 "c/resistances" => {
2989 id => "infobox",
2990 title => "Resistances",
2991 reply => undef,
2992 tooltip => "Shows your resistances",
2993 },
2994 "c/pets" => {
2995 id => "infobox",
2996 title => "Pets",
2997 reply => undef,
2998 tooltip => "Shows information abotu your pets/a specific pet",
2999 },
3000 "c/perceiveself" => {
3001 id => "infobox",
3002 title => "Perceive Self",
3003 reply => undef,
3004 tooltip => "You gained detailed knowledge about yourself",
3005 },
3006 "c/uptime" => {
3007 id => "infobox",
3008 title => "Uptime",
3009 reply => undef,
3010 tooltip => "How long the server has been running since last restart",
3011 },
3012 "c/mapinfo" => {
3013 id => "infobox",
3014 title => "Map Info",
3015 reply => undef,
3016 tooltip => "Information related to the maps",
3017 },
3018 "c/party" => {
3019 id => "party",
3020 title => "Party",
3021 reply => "gsay ",
3022 tooltip => "Messages and chat related to your party",
3023 },
3024 "c/death" => {
3025 id => "death",
3026 title => "Death",
3027 reply => undef,
3028 tooltip => "Reason for and more info about your most recent death",
3029 },
3030 "c/say" => $SAY_CHANNEL,
3031 "c/chat" => $CHAT_CHANNEL,
3032);
3033
3034sub cf::client::send_msg {
3035 my ($self, $channel, $msg, $color, @extra) = @_;
3036
3037 $msg = $self->pl->expand_cfpod ($msg)
3038 unless $color & cf::NDI_VERBATIM;
3039
3040 $color &= cf::NDI_CLIENT_MASK; # just in case...
3041
3042 # check predefined channels, for the benefit of C
3043 if ($CHANNEL{$channel}) {
3044 $channel = $CHANNEL{$channel};
3045
3046 $self->ext_msg (channel_info => $channel);
3047 $channel = $channel->{id};
3048
3049 } elsif (ref $channel) {
3050 # send meta info to client, if not yet sent
3051 unless (exists $self->{channel}{$channel->{id}}) {
3052 $self->{channel}{$channel->{id}} = $channel;
3053 $self->ext_msg (channel_info => $channel);
3054 }
3055
3056 $channel = $channel->{id};
3057 }
3058
3059 return unless @extra || length $msg;
3060
3061 # default colour, mask it out
3062 $color &= ~(cf::NDI_COLOR_MASK | cf::NDI_DEF)
3063 if $color & cf::NDI_DEF;
3064
3065 my $pkt = "msg "
3066 . $self->{json_coder}->encode (
3067 [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra]
3068 );
3069
3070 $self->send_big_packet ($pkt);
3071}
3072
3073=item $client->ext_msg ($type, @msg)
3074
3075Sends an ext event to the client.
3076
3077=cut
3078
3079sub cf::client::ext_msg($$@) {
3080 my ($self, $type, @msg) = @_;
3081
3082 if ($self->extcmd == 2) {
3083 $self->send_big_packet ("ext " . $self->{json_coder}->encode ([$type, @msg]));
3084 } elsif ($self->extcmd == 1) { # TODO: remove
3085 push @msg, msgtype => "event_$type";
3086 $self->send_big_packet ("ext " . $self->{json_coder}->encode ({@msg}));
3087 }
3088}
3089
3090=item $client->ext_reply ($msgid, @msg)
3091
3092Sends an ext reply to the client.
3093
3094=cut
3095
3096sub cf::client::ext_reply($$@) {
3097 my ($self, $id, @msg) = @_;
3098
3099 if ($self->extcmd == 2) {
3100 $self->send_big_packet ("ext " . $self->{json_coder}->encode (["reply-$id", @msg]));
3101 } elsif ($self->extcmd == 1) {
3102 #TODO: version 1, remove
3103 unshift @msg, msgtype => "reply", msgid => $id;
3104 $self->send_big_packet ("ext " . $self->{json_coder}->encode ({@msg}));
3105 }
3106}
1876 3107
1877=item $success = $client->query ($flags, "text", \&cb) 3108=item $success = $client->query ($flags, "text", \&cb)
1878 3109
1879Queues a query to the client, calling the given callback with 3110Queues a query to the client, calling the given callback with
1880the reply text on a reply. flags can be C<cf::CS_QUERY_YESNO>, 3111the reply text on a reply. flags can be C<cf::CS_QUERY_YESNO>,
1881C<cf::CS_QUERY_SINGLECHAR> or C<cf::CS_QUERY_HIDEINPUT> or C<0>. 3112C<cf::CS_QUERY_SINGLECHAR> or C<cf::CS_QUERY_HIDEINPUT> or C<0>.
1882 3113
1883Queries can fail, so check the return code. Or don't, as queries will become 3114Queries can fail, so check the return code. Or don't, as queries will
1884reliable at some point in the future. 3115become reliable at some point in the future.
1885 3116
1886=cut 3117=cut
1887 3118
1888sub cf::client::query { 3119sub cf::client::query {
1889 my ($self, $flags, $text, $cb) = @_; 3120 my ($self, $flags, $text, $cb) = @_;
1897 utf8::encode $text; 3128 utf8::encode $text;
1898 push @{ $self->{query_queue} }, [(sprintf "query %d %s", $flags, $text), $cb]; 3129 push @{ $self->{query_queue} }, [(sprintf "query %d %s", $flags, $text), $cb];
1899 3130
1900 $self->send_packet ($self->{query_queue}[0][0]) 3131 $self->send_packet ($self->{query_queue}[0][0])
1901 if @{ $self->{query_queue} } == 1; 3132 if @{ $self->{query_queue} } == 1;
3133
3134 1
1902} 3135}
1903 3136
1904cf::client->attach ( 3137cf::client->attach (
3138 on_connect => sub {
3139 my ($ns) = @_;
3140
3141 $ns->{json_coder} = JSON::XS->new->utf8->max_size (1e6)->convert_blessed;
3142 },
1905 on_reply => sub { 3143 on_reply => sub {
1906 my ($ns, $msg) = @_; 3144 my ($ns, $msg) = @_;
1907 3145
1908 # this weird shuffling is so that direct followup queries 3146 # this weird shuffling is so that direct followup queries
1909 # get handled first 3147 # get handled first
1910 my $queue = delete $ns->{query_queue} 3148 my $queue = delete $ns->{query_queue}
1911 or return; # be conservative, not sure how that can happen, but we saw a crash here 3149 or return; # be conservative, not sure how that can happen, but we saw a crash here
1912 3150
1913 (shift @$queue)->[1]->($msg); 3151 (shift @$queue)->[1]->($msg);
3152 return unless $ns->valid; # temporary(?) workaround for callback destroying socket
1914 3153
1915 push @{ $ns->{query_queue} }, @$queue; 3154 push @{ $ns->{query_queue} }, @$queue;
1916 3155
1917 if (@{ $ns->{query_queue} } == @$queue) { 3156 if (@{ $ns->{query_queue} } == @$queue) {
1918 if (@$queue) { 3157 if (@$queue) {
1920 } else { 3159 } else {
1921 $ns->state (ST_PLAYING) if $ns->state == ST_CUSTOM; 3160 $ns->state (ST_PLAYING) if $ns->state == ST_CUSTOM;
1922 } 3161 }
1923 } 3162 }
1924 }, 3163 },
3164 on_exticmd => sub {
3165 my ($ns, $buf) = @_;
3166
3167 my $msg = eval { $ns->{json_coder}->decode ($buf) };
3168
3169 if (ref $msg) {
3170 my ($type, $reply, @payload) =
3171 "ARRAY" eq ref $msg
3172 ? @$msg
3173 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
3174
3175 my @reply;
3176
3177 if (my $cb = $EXTICMD{$type}) {
3178 @reply = $cb->($ns, @payload);
3179 }
3180
3181 $ns->ext_reply ($reply, @reply)
3182 if $reply;
3183
3184 } else {
3185 error "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n";
3186 }
3187
3188 cf::override;
3189 },
1925); 3190);
1926 3191
1927=item $client->async (\&cb) 3192=item $client->async (\&cb)
1928 3193
1929Create a new coroutine, running the specified callback. The coroutine will 3194Create a new coroutine, running the specified callback. The coroutine will
1945 3210
1946 $coro 3211 $coro
1947} 3212}
1948 3213
1949cf::client->attach ( 3214cf::client->attach (
1950 on_destroy => sub { 3215 on_client_destroy => sub {
1951 my ($ns) = @_; 3216 my ($ns) = @_;
1952 3217
1953 $_->cancel for values %{ (delete $ns->{_coro}) || {} }; 3218 $_->cancel for values %{ (delete $ns->{_coro}) || {} };
1954 }, 3219 },
1955); 3220);
1970our $safe = new Safe "safe"; 3235our $safe = new Safe "safe";
1971our $safe_hole = new Safe::Hole; 3236our $safe_hole = new Safe::Hole;
1972 3237
1973$SIG{FPE} = 'IGNORE'; 3238$SIG{FPE} = 'IGNORE';
1974 3239
1975$safe->permit_only (Opcode::opset qw(:base_core :base_mem :base_orig :base_math sort time)); 3240$safe->permit_only (Opcode::opset qw(
3241 :base_core :base_mem :base_orig :base_math :base_loop
3242 grepstart grepwhile mapstart mapwhile
3243 sort time
3244));
1976 3245
1977# here we export the classes and methods available to script code 3246# here we export the classes and methods available to script code
1978 3247
1979=pod 3248=pod
1980 3249
1981The following fucntions and emthods are available within a safe environment: 3250The following functions and methods are available within a safe environment:
1982 3251
1983 cf::object contr pay_amount pay_player map 3252 cf::object
3253 contr pay_amount pay_player map x y force_find force_add destroy
3254 insert remove name archname title slaying race decrease split
3255 value
3256
1984 cf::object::player player 3257 cf::object::player
1985 cf::player peaceful 3258 player
1986 cf::map trigger 3259
3260 cf::player
3261 peaceful
3262
3263 cf::map
3264 trigger
1987 3265
1988=cut 3266=cut
1989 3267
1990for ( 3268for (
1991 ["cf::object" => qw(contr pay_amount pay_player map)], 3269 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y
3270 insert remove inv nrof name archname title slaying race
3271 decrease split destroy change_exp value msg lore send_msg)],
1992 ["cf::object::player" => qw(player)], 3272 ["cf::object::player" => qw(player)],
1993 ["cf::player" => qw(peaceful)], 3273 ["cf::player" => qw(peaceful send_msg)],
1994 ["cf::map" => qw(trigger)], 3274 ["cf::map" => qw(trigger)],
1995) { 3275) {
1996 no strict 'refs';
1997 my ($pkg, @funs) = @$_; 3276 my ($pkg, @funs) = @$_;
1998 *{"safe::$pkg\::$_"} = $safe_hole->wrap (\&{"$pkg\::$_"}) 3277 *{"safe::$pkg\::$_"} = $safe_hole->wrap (\&{"$pkg\::$_"})
1999 for @funs; 3278 for @funs;
2000} 3279}
2001 3280
2016 3295
2017 my $qcode = $code; 3296 my $qcode = $code;
2018 $qcode =~ s/"/‟/g; # not allowed in #line filenames 3297 $qcode =~ s/"/‟/g; # not allowed in #line filenames
2019 $qcode =~ s/\n/\\n/g; 3298 $qcode =~ s/\n/\\n/g;
2020 3299
3300 %vars = (_dummy => 0) unless %vars;
3301
3302 my @res;
2021 local $_; 3303 local $_;
2022 local @safe::cf::_safe_eval_args = values %vars;
2023 3304
2024 my $eval = 3305 my $eval =
2025 "do {\n" 3306 "do {\n"
2026 . "my (" . (join ",", map "\$$_", keys %vars) . ") = \@cf::_safe_eval_args;\n" 3307 . "my (" . (join ",", map "\$$_", keys %vars) . ") = \@cf::_safe_eval_args;\n"
2027 . "#line 0 \"{$qcode}\"\n" 3308 . "#line 0 \"{$qcode}\"\n"
2028 . $code 3309 . $code
2029 . "\n}" 3310 . "\n}"
2030 ; 3311 ;
2031 3312
3313 if ($CFG{safe_eval}) {
2032 sub_generation_inc; 3314 sub_generation_inc;
3315 local @safe::cf::_safe_eval_args = values %vars;
2033 my @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval); 3316 @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval);
2034 sub_generation_inc; 3317 sub_generation_inc;
3318 } else {
3319 local @cf::_safe_eval_args = values %vars;
3320 @res = wantarray ? eval eval : scalar eval $eval;
3321 }
2035 3322
2036 if ($@) { 3323 if ($@) {
2037 warn "$@"; 3324 warn "$@",
2038 warn "while executing safe code '$code'\n"; 3325 "while executing safe code '$code'\n",
2039 warn "with arguments " . (join " ", %vars) . "\n"; 3326 "with arguments " . (join " ", %vars) . "\n";
2040 } 3327 }
2041 3328
2042 wantarray ? @res : $res[0] 3329 wantarray ? @res : $res[0]
2043} 3330}
2044 3331
2058=cut 3345=cut
2059 3346
2060sub register_script_function { 3347sub register_script_function {
2061 my ($fun, $cb) = @_; 3348 my ($fun, $cb) = @_;
2062 3349
2063 no strict 'refs'; 3350 $fun = "safe::$fun" if $CFG{safe_eval};
2064 *{"safe::$fun"} = $safe_hole->wrap ($cb); 3351 *$fun = $safe_hole->wrap ($cb);
2065} 3352}
2066 3353
2067=back 3354=back
2068 3355
2069=cut 3356=cut
2070 3357
2071############################################################################# 3358#############################################################################
3359# the server's init and main functions
2072 3360
2073=head2 EXTENSION DATABASE SUPPORT 3361sub load_facedata($) {
3362 my ($path) = @_;
2074 3363
2075Crossfire maintains a very simple database for extension use. It can 3364 # HACK to clear player env face cache, we need some signal framework
2076currently store anything that can be serialised using Storable, which 3365 # for this (global event?)
2077excludes objects. 3366 %ext::player_env::MUSIC_FACE_CACHE = ();
2078 3367
2079The parameter C<$family> should best start with the name of the extension 3368 my $enc = JSON::XS->new->utf8->canonical->relaxed;
2080using it, it should be unique.
2081 3369
2082=over 4 3370 trace "loading facedata from $path\n";
2083 3371
2084=item $hashref = cf::db_get $family 3372 my $facedata;
3373 0 < aio_load $path, $facedata
3374 or die "$path: $!";
2085 3375
2086Return a hashref for use by the extension C<$family>, which can be 3376 $facedata = Coro::Storable::thaw $facedata;
2087modified. After modifications, you have to call C<cf::db_dirty> or
2088C<cf::db_sync>.
2089 3377
2090=item $value = cf::db_get $family => $key 3378 $facedata->{version} == 2
3379 or cf::cleanup "$path: version mismatch, cannot proceed.";
2091 3380
2092Returns a single value from the database 3381 # patch in the exptable
2093 3382 my $exp_table = $enc->encode ([map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]);
2094=item cf::db_put $family => $hashref 3383 $facedata->{resource}{"res/exp_table"} = {
2095 3384 type => FT_RSRC,
2096Stores the given family hashref into the database. Updates are delayed, if 3385 data => $exp_table,
2097you want the data to be synced to disk immediately, use C<cf::db_sync>. 3386 hash => (Digest::MD5::md5 $exp_table),
2098
2099=item cf::db_put $family => $key => $value
2100
2101Stores the given C<$value> in the family hash. Updates are delayed, if you
2102want the data to be synced to disk immediately, use C<cf::db_sync>.
2103
2104=item cf::db_dirty
2105
2106Marks the database as dirty, to be updated at a later time.
2107
2108=item cf::db_sync
2109
2110Immediately write the database to disk I<if it is dirty>.
2111
2112=cut
2113
2114our $DB;
2115
2116{
2117 my $path = cf::localdir . "/database.pst";
2118
2119 sub db_load() {
2120 $DB = stat $path ? Storable::retrieve $path : { };
2121 }
2122
2123 my $pid;
2124
2125 sub db_save() {
2126 waitpid $pid, 0 if $pid;
2127 if (0 == ($pid = fork)) {
2128 $DB->{_meta}{version} = 1;
2129 Storable::nstore $DB, "$path~";
2130 rename "$path~", $path;
2131 cf::_exit 0 if defined $pid;
2132 }
2133 }
2134
2135 my $dirty;
2136
2137 sub db_sync() {
2138 db_save if $dirty;
2139 undef $dirty;
2140 }
2141
2142 my $idle = Event->idle (min => $TICK * 2.8, max => 10, repeat => 0, data => WF_AUTOCANCEL, cb => sub {
2143 db_sync;
2144 }); 3387 };
3388 cf::cede_to_tick;
2145 3389
2146 sub db_dirty() {
2147 $dirty = 1;
2148 $idle->start;
2149 } 3390 {
3391 my $faces = $facedata->{faceinfo};
2150 3392
2151 sub db_get($;$) { 3393 while (my ($face, $info) = each %$faces) {
2152 @_ >= 2 3394 my $idx = (cf::face::find $face) || cf::face::alloc $face;
2153 ? $DB->{$_[0]}{$_[1]}
2154 : ($DB->{$_[0]} ||= { })
2155 }
2156 3395
2157 sub db_put($$;$) { 3396 cf::face::set_visibility $idx, $info->{visibility};
2158 if (@_ >= 3) { 3397 cf::face::set_magicmap $idx, $info->{magicmap};
2159 $DB->{$_[0]}{$_[1]} = $_[2]; 3398 cf::face::set_data $idx, 0, $info->{data32}, $info->{hash32};
2160 } else { 3399 cf::face::set_data $idx, 1, $info->{data64}, $info->{hash64};
2161 $DB->{$_[0]} = $_[1];
2162 }
2163 db_dirty;
2164 }
2165 3400
2166 cf::global->attach ( 3401 cf::cede_to_tick;
2167 prio => 10000,
2168 on_cleanup => sub {
2169 db_sync;
2170 }, 3402 }
2171 );
2172}
2173 3403
2174############################################################################# 3404 while (my ($face, $info) = each %$faces) {
2175# the server's main() 3405 next unless $info->{smooth};
2176 3406
2177sub cfg_load { 3407 my $idx = cf::face::find $face
3408 or next;
3409
3410 if (my $smooth = cf::face::find $info->{smooth}) {
3411 cf::face::set_smooth $idx, $smooth;
3412 cf::face::set_smoothlevel $idx, $info->{smoothlevel};
3413 } else {
3414 error "smooth face '$info->{smooth}' not found for face '$face'";
3415 }
3416
3417 cf::cede_to_tick;
3418 }
3419 }
3420
3421 {
3422 my $anims = $facedata->{animinfo};
3423
3424 while (my ($anim, $info) = each %$anims) {
3425 cf::anim::set $anim, $info->{frames}, $info->{facings};
3426 cf::cede_to_tick;
3427 }
3428
3429 cf::anim::invalidate_all; # d'oh
3430 }
3431
3432 {
3433 my $res = $facedata->{resource};
3434
3435 while (my ($name, $info) = each %$res) {
3436 if (defined $info->{type}) {
3437 my $idx = (cf::face::find $name) || cf::face::alloc $name;
3438
3439 cf::face::set_data $idx, 0, $info->{data}, $info->{hash};
3440 cf::face::set_type $idx, $info->{type};
3441 } else {
3442 $RESOURCE{$name} = $info; # unused
3443 }
3444
3445 cf::cede_to_tick;
3446 }
3447 }
3448
3449 cf::global->invoke (EVENT_GLOBAL_RESOURCE_UPDATE);
3450
3451 1
3452}
3453
3454register_exticmd fx_want => sub {
3455 my ($ns, $want) = @_;
3456
3457 while (my ($k, $v) = each %$want) {
3458 $ns->fx_want ($k, $v);
3459 }
3460};
3461
3462sub load_resource_file($) {
3463 my $guard = lock_acquire "load_resource_file";
3464
3465 my $status = load_resource_file_ $_[0];
3466 get_slot 0.1, 100;
3467 cf::arch::commit_load;
3468
3469 $status
3470}
3471
3472sub reload_regions {
3473 # HACK to clear player env face cache, we need some signal framework
3474 # for this (global event?)
3475 %ext::player_env::MUSIC_FACE_CACHE = ();
3476
3477 load_resource_file "$MAPDIR/regions"
3478 or die "unable to load regions file\n";
3479
3480 for (cf::region::list) {
3481 $_->{match} = qr/$_->{match}/
3482 if exists $_->{match};
3483 }
3484}
3485
3486sub reload_facedata {
3487 load_facedata "$DATADIR/facedata"
3488 or die "unable to load facedata\n";
3489}
3490
3491sub reload_archetypes {
3492 load_resource_file "$DATADIR/archetypes"
3493 or die "unable to load archetypes\n";
3494}
3495
3496sub reload_treasures {
3497 load_resource_file "$DATADIR/treasures"
3498 or die "unable to load treasurelists\n";
3499}
3500
3501sub reload_sound {
3502 trace "loading sound config from $DATADIR/sound\n";
3503
3504 0 < Coro::AIO::aio_load "$DATADIR/sound", my $data
3505 or die "$DATADIR/sound $!";
3506
3507 my $soundconf = JSON::XS->new->utf8->relaxed->decode ($data);
3508
3509 for (0 .. SOUND_CAST_SPELL_0 - 1) {
3510 my $sound = $soundconf->{compat}[$_]
3511 or next;
3512
3513 my $face = cf::face::find "sound/$sound->[1]";
3514 cf::sound::set $sound->[0] => $face;
3515 cf::sound::old_sound_index $_, $face; # gcfclient-compat
3516 }
3517
3518 while (my ($k, $v) = each %{$soundconf->{event}}) {
3519 my $face = cf::face::find "sound/$v";
3520 cf::sound::set $k => $face;
3521 }
3522}
3523
3524sub reload_resources {
3525 trace "reloading resource files...\n";
3526
3527 reload_facedata;
3528 reload_sound;
3529 reload_archetypes;
3530 reload_regions;
3531 reload_treasures;
3532
3533 trace "finished reloading resource files\n";
3534}
3535
3536sub reload_config {
3537 trace "reloading config file...\n";
3538
2178 open my $fh, "<:utf8", cf::confdir . "/config" 3539 open my $fh, "<:utf8", "$CONFDIR/config"
2179 or return; 3540 or return;
2180 3541
2181 local $/; 3542 local $/;
2182 *CFG = YAML::Syck::Load <$fh>; 3543 *CFG = YAML::XS::Load scalar <$fh>;
2183 3544
2184 $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37]; 3545 $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_104_115", 49, 38];
2185 3546
2186 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset}; 3547 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset};
2187 $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset}; 3548 $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset};
2188 3549
2189 if (exists $CFG{mlockall}) { 3550 if (exists $CFG{mlockall}) {
2191 $CFG{mlockall} ? eval "mlockall()" : eval "munlockall()" 3552 $CFG{mlockall} ? eval "mlockall()" : eval "munlockall()"
2192 and die "WARNING: m(un)lockall failed: $!\n"; 3553 and die "WARNING: m(un)lockall failed: $!\n";
2193 }; 3554 };
2194 warn $@ if $@; 3555 warn $@ if $@;
2195 } 3556 }
3557
3558 trace "finished reloading resource files\n";
3559}
3560
3561sub pidfile() {
3562 sysopen my $fh, $PIDFILE, O_RDWR | O_CREAT
3563 or die "$PIDFILE: $!";
3564 flock $fh, &Fcntl::LOCK_EX
3565 or die "$PIDFILE: flock: $!";
3566 $fh
3567}
3568
3569# make sure only one server instance is running at any one time
3570sub atomic {
3571 my $fh = pidfile;
3572
3573 my $pid = <$fh>;
3574 kill 9, $pid if $pid > 0;
3575
3576 seek $fh, 0, 0;
3577 print $fh $$;
3578}
3579
3580sub main_loop {
3581 trace "EV::loop starting\n";
3582 if (1) {
3583 EV::loop;
3584 }
3585 trace "EV::loop returned\n";
3586 goto &main_loop unless $REALLY_UNLOOP;
2196} 3587}
2197 3588
2198sub main { 3589sub main {
3590 cf::init_globals; # initialise logging
3591
3592 LOG llevInfo, "Welcome to Deliantra, v" . VERSION;
3593 LOG llevInfo, "Copyright (C) 2005-2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team.";
3594 LOG llevInfo, "Copyright (C) 1994 Mark Wedel.";
3595 LOG llevInfo, "Copyright (C) 1992 Frank Tore Johansen.";
3596
3597 $Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority
3598
2199 # we must not ever block the main coroutine 3599 # we must not ever block the main coroutine
2200 local $Coro::idle = sub { 3600 local $Coro::idle = sub {
2201 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d# 3601 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
2202 async { Event::one_event }; 3602 (async {
3603 $Coro::current->{desc} = "IDLE BUG HANDLER";
3604 EV::loop EV::LOOP_ONESHOT;
3605 })->prio (Coro::PRIO_MAX);
2203 }; 3606 };
2204 3607
2205 cfg_load; 3608 evthread_start IO::AIO::poll_fileno;
2206 db_load; 3609
3610 cf::sync_job {
3611 cf::init_experience;
3612 cf::init_anim;
3613 cf::init_attackmess;
3614 cf::init_dynamic;
3615
3616 cf::load_settings;
3617 cf::load_materials;
3618
3619 reload_resources;
3620 reload_config;
3621 db_init;
3622
3623 cf::init_uuid;
3624 cf::init_signals;
3625 cf::init_skills;
3626
3627 cf::init_beforeplay;
3628
3629 atomic;
3630
2207 load_extensions; 3631 load_extensions;
2208 Event::loop; 3632
3633 utime time, time, $RUNTIMEFILE;
3634
3635 # no (long-running) fork's whatsoever before this point(!)
3636 use POSIX ();
3637 POSIX::close delete $ENV{LOCKUTIL_LOCK_FD} if exists $ENV{LOCKUTIL_LOCK_FD};
3638
3639 (pop @POST_INIT)->(0) while @POST_INIT;
3640 };
3641
3642 cf::object::thawer::errors_are_fatal 0;
3643 info "parse errors in files are no longer fatal from this point on.\n";
3644
3645 main_loop;
2209} 3646}
2210 3647
2211############################################################################# 3648#############################################################################
2212# initialisation 3649# initialisation and cleanup
2213 3650
3651# install some emergency cleanup handlers
3652BEGIN {
3653 our %SIGWATCHER = ();
3654 for my $signal (qw(INT HUP TERM)) {
3655 $SIGWATCHER{$signal} = AE::signal $signal, sub {
3656 cf::cleanup "SIG$signal";
3657 };
3658 }
3659}
3660
3661sub write_runtime_sync {
3662 my $t0 = AE::time;
3663
3664 # first touch the runtime file to show we are still running:
3665 # the fsync below can take a very very long time.
3666
3667 IO::AIO::aio_utime $RUNTIMEFILE, undef, undef;
3668
3669 my $guard = cf::lock_acquire "write_runtime";
3670
3671 my $fh = aio_open "$RUNTIMEFILE~", O_WRONLY | O_CREAT | O_TRUNC, 0644
3672 or return;
3673
3674 my $value = $cf::RUNTIME + 90 + 10;
3675 # 10 is the runtime save interval, for a monotonic clock
3676 # 60 allows for the watchdog to kill the server.
3677
3678 (aio_write $fh, 0, (length $value), $value, 0) <= 0
3679 and return;
3680
3681 # always fsync - this file is important
3682 aio_fsync $fh
3683 and return;
3684
3685 # touch it again to show we are up-to-date
3686 aio_utime $fh, undef, undef;
3687
3688 close $fh
3689 or return;
3690
3691 aio_rename "$RUNTIMEFILE~", $RUNTIMEFILE
3692 and return;
3693
3694 trace sprintf "runtime file written (%gs).\n", AE::time - $t0;
3695
3696 1
3697}
3698
3699our $uuid_lock;
3700our $uuid_skip;
3701
3702sub write_uuid_sync($) {
3703 $uuid_skip ||= $_[0];
3704
3705 return if $uuid_lock;
3706 local $uuid_lock = 1;
3707
3708 my $uuid = "$LOCALDIR/uuid";
3709
3710 my $fh = aio_open "$uuid~", O_WRONLY | O_CREAT, 0644
3711 or return;
3712
3713 my $value = uuid_seq uuid_cur;
3714
3715 unless ($value) {
3716 info "cowardly refusing to write zero uuid value!\n";
3717 return;
3718 }
3719
3720 my $value = uuid_str $value + $uuid_skip;
3721 $uuid_skip = 0;
3722
3723 (aio_write $fh, 0, (length $value), $value, 0) <= 0
3724 and return;
3725
3726 # always fsync - this file is important
3727 aio_fsync $fh
3728 and return;
3729
3730 close $fh
3731 or return;
3732
3733 aio_rename "$uuid~", $uuid
3734 and return;
3735
3736 trace "uuid file written ($value).\n";
3737
3738 1
3739
3740}
3741
3742sub write_uuid($$) {
3743 my ($skip, $sync) = @_;
3744
3745 $sync ? write_uuid_sync $skip
3746 : async { write_uuid_sync $skip };
3747}
3748
3749sub emergency_save() {
3750 my $freeze_guard = cf::freeze_mainloop;
3751
3752 info "emergency_perl_save: enter\n";
3753
3754 # this is a trade-off: we want to be very quick here, so
3755 # save all maps without fsync, and later call a global sync
3756 # (which in turn might be very very slow)
3757 local $USE_FSYNC = 0;
3758
3759 cf::sync_job {
3760 cf::write_runtime_sync; # external watchdog should not bark
3761
3762 # use a peculiar iteration method to avoid tripping on perl
3763 # refcount bugs in for. also avoids problems with players
3764 # and maps saved/destroyed asynchronously.
3765 info "emergency_perl_save: begin player save\n";
3766 for my $login (keys %cf::PLAYER) {
3767 my $pl = $cf::PLAYER{$login} or next;
3768 $pl->valid or next;
3769 delete $pl->{unclean_save}; # not strictly necessary, but cannot hurt
3770 $pl->save;
3771 }
3772 info "emergency_perl_save: end player save\n";
3773
3774 cf::write_runtime_sync; # external watchdog should not bark
3775
3776 info "emergency_perl_save: begin map save\n";
3777 for my $path (keys %cf::MAP) {
3778 my $map = $cf::MAP{$path} or next;
3779 $map->valid or next;
3780 $map->save;
3781 }
3782 info "emergency_perl_save: end map save\n";
3783
3784 cf::write_runtime_sync; # external watchdog should not bark
3785
3786 info "emergency_perl_save: begin database checkpoint\n";
3787 BDB::db_env_txn_checkpoint $DB_ENV;
3788 info "emergency_perl_save: end database checkpoint\n";
3789
3790 info "emergency_perl_save: begin write uuid\n";
3791 write_uuid_sync 1;
3792 info "emergency_perl_save: end write uuid\n";
3793
3794 cf::write_runtime_sync; # external watchdog should not bark
3795
3796 trace "emergency_perl_save: syncing database to disk";
3797 BDB::db_env_txn_checkpoint $DB_ENV;
3798
3799 info "emergency_perl_save: starting sync\n";
3800 IO::AIO::aio_sync sub {
3801 info "emergency_perl_save: finished sync\n";
3802 };
3803
3804 cf::write_runtime_sync; # external watchdog should not bark
3805
3806 trace "emergency_perl_save: flushing outstanding aio requests";
3807 while (IO::AIO::nreqs || BDB::nreqs) {
3808 Coro::EV::timer_once 0.01; # let the sync_job do it's thing
3809 }
3810
3811 cf::write_runtime_sync; # external watchdog should not bark
3812 };
3813
3814 info "emergency_perl_save: leave\n";
3815}
3816
3817sub post_cleanup {
3818 my ($make_core) = @_;
3819
3820 IO::AIO::flush;
3821
3822 error Carp::longmess "post_cleanup backtrace"
3823 if $make_core;
3824
3825 my $fh = pidfile;
3826 unlink $PIDFILE if <$fh> == $$;
3827}
3828
3829# a safer delete_package, copied from Symbol
3830sub clear_package($) {
3831 my $pkg = shift;
3832
3833 # expand to full symbol table name if needed
3834 unless ($pkg =~ /^main::.*::$/) {
3835 $pkg = "main$pkg" if $pkg =~ /^::/;
3836 $pkg = "main::$pkg" unless $pkg =~ /^main::/;
3837 $pkg .= '::' unless $pkg =~ /::$/;
3838 }
3839
3840 my($stem, $leaf) = $pkg =~ m/(.*::)(\w+::)$/;
3841 my $stem_symtab = *{$stem}{HASH};
3842
3843 defined $stem_symtab and exists $stem_symtab->{$leaf}
3844 or return;
3845
3846 # clear all symbols
3847 my $leaf_symtab = *{$stem_symtab->{$leaf}}{HASH};
3848 for my $name (keys %$leaf_symtab) {
3849 _gv_clear *{"$pkg$name"};
3850# use PApp::Util; PApp::Util::sv_dump *{"$pkg$name"};
3851 }
3852}
3853
2214sub reload() { 3854sub do_reload_perl() {
2215 # can/must only be called in main 3855 # can/must only be called in main
2216 if ($Coro::current != $Coro::main) { 3856 if (in_main) {
2217 warn "can only reload from main coroutine\n"; 3857 error "can only reload from main coroutine";
2218 return; 3858 return;
2219 } 3859 }
2220 3860
3861 return if $RELOAD++;
3862
3863 my $t1 = AE::time;
3864
3865 while ($RELOAD) {
2221 warn "reloading..."; 3866 info "reloading...";
2222 3867
2223 my $guard = freeze_mainloop; 3868 trace "entering sync_job";
3869
3870 cf::sync_job {
2224 cf::emergency_save; 3871 cf::emergency_save;
2225 3872
2226 eval {
2227 # if anything goes wrong in here, we should simply crash as we already saved
2228
2229 # cancel all watchers
2230 for (Event::all_watchers) {
2231 $_->cancel if $_->data & WF_AUTOCANCEL;
2232 }
2233
2234 # cancel all extension coros 3873 trace "cancelling all extension coros";
2235 $_->cancel for values %EXT_CORO; 3874 $_->cancel for values %EXT_CORO;
2236 %EXT_CORO = (); 3875 %EXT_CORO = ();
2237 3876
2238 # unload all extensions 3877 trace "removing commands";
2239 for (@exts) { 3878 %COMMAND = ();
2240 warn "unloading <$_>";
2241 unload_extension $_;
2242 }
2243 3879
2244 # unload all modules loaded from $LIBDIR 3880 trace "removing ext/exti commands";
2245 while (my ($k, $v) = each %INC) { 3881 %EXTCMD = ();
2246 next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/; 3882 %EXTICMD = ();
2247 3883
2248 warn "removing <$k>"; 3884 trace "unloading/nuking all extensions";
2249 delete $INC{$k}; 3885 for my $pkg (@EXTS) {
3886 trace "... unloading $pkg";
2250 3887
2251 $k =~ s/\.pm$//;
2252 $k =~ s/\//::/g;
2253
2254 if (my $cb = $k->can ("unload_module")) { 3888 if (my $cb = $pkg->can ("unload")) {
3889 eval {
2255 $cb->(); 3890 $cb->($pkg);
3891 1
3892 } or error "$pkg unloaded, but with errors: $@";
3893 }
3894
3895 trace "... clearing $pkg";
3896 clear_package $pkg;
2256 } 3897 }
2257 3898
2258 Symbol::delete_package $k; 3899 trace "unloading all perl modules loaded from $LIBDIR";
3900 while (my ($k, $v) = each %INC) {
3901 next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
3902
3903 trace "... unloading $k";
3904 delete $INC{$k};
3905
3906 $k =~ s/\.pm$//;
3907 $k =~ s/\//::/g;
3908
3909 if (my $cb = $k->can ("unload_module")) {
3910 $cb->();
3911 }
3912
3913 clear_package $k;
2259 } 3914 }
2260 3915
2261 # sync database to disk
2262 cf::db_sync;
2263 IO::AIO::flush;
2264
2265 # get rid of safe::, as good as possible 3916 trace "getting rid of safe::, as good as possible";
2266 Symbol::delete_package "safe::$_" 3917 clear_package "safe::$_"
2267 for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region); 3918 for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region);
2268 3919
2269 # remove register_script_function callbacks 3920 trace "unloading cf.pm \"a bit\"";
2270 # TODO
2271
2272 # unload cf.pm "a bit"
2273 delete $INC{"cf.pm"}; 3921 delete $INC{"cf.pm"};
3922 delete $INC{"cf/$_.pm"} for @EXTRA_MODULES;
2274 3923
2275 # don't, removes xs symbols, too, 3924 # don't, removes xs symbols, too,
2276 # and global variables created in xs 3925 # and global variables created in xs
2277 #Symbol::delete_package __PACKAGE__; 3926 #clear_package __PACKAGE__;
2278 3927
2279 # reload cf.pm 3928 info "unload completed, starting to reload now";
3929
2280 warn "reloading cf.pm"; 3930 trace "reloading cf.pm";
2281 require cf; 3931 require cf;
2282 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt 3932 cf::_connect_to_perl_1;
2283 3933
2284 # load config and database again 3934 trace "loading config and database again";
2285 cf::cfg_load; 3935 cf::reload_config;
2286 cf::db_load;
2287 3936
2288 # load extensions
2289 warn "load extensions"; 3937 trace "loading extensions";
2290 cf::load_extensions; 3938 cf::load_extensions;
2291 3939
2292 # reattach attachments to objects 3940 if ($REATTACH_ON_RELOAD) {
2293 warn "reattach"; 3941 trace "reattaching attachments to objects/players";
2294 _global_reattach; 3942 _global_reattach; # objects, sockets
3943 trace "reattaching attachments to maps";
2295 reattach $_ for values %MAP; 3944 reattach $_ for values %MAP;
3945 trace "reattaching attachments to players";
3946 reattach $_ for values %PLAYER;
3947 }
3948
3949 trace "running post_init jobs";
3950 (pop @POST_INIT)->(1) while @POST_INIT;
3951
3952 trace "leaving sync_job";
3953
3954 1
3955 } or do {
3956 error $@;
3957 cf::cleanup "error while reloading, exiting.";
3958 };
3959
3960 info "reloaded";
3961 --$RELOAD;
3962 }
3963
3964 $t1 = AE::time - $t1;
3965 info "reload completed in ${t1}s\n";
3966};
3967
3968our $RELOAD_WATCHER; # used only during reload
3969
3970sub reload_perl() {
3971 # doing reload synchronously and two reloads happen back-to-back,
3972 # coro crashes during coro_state_free->destroy here.
3973
3974 $RELOAD_WATCHER ||= cf::async {
3975 Coro::AIO::aio_wait cache_extensions;
3976
3977 $RELOAD_WATCHER = AE::timer $TICK * 1.5, 0, sub {
3978 do_reload_perl;
3979 undef $RELOAD_WATCHER;
3980 };
2296 }; 3981 };
2297
2298 if ($@) {
2299 warn $@;
2300 warn "error while reloading, exiting.";
2301 exit 1;
2302 }
2303
2304 warn "reloaded successfully";
2305};
2306
2307#############################################################################
2308
2309unless ($LINK_MAP) {
2310 $LINK_MAP = cf::map::new;
2311
2312 $LINK_MAP->width (41);
2313 $LINK_MAP->height (41);
2314 $LINK_MAP->alloc;
2315 $LINK_MAP->path ("{link}");
2316 $LINK_MAP->{path} = bless { path => "{link}" }, "cf::path";
2317 $LINK_MAP->in_memory (MAP_IN_MEMORY);
2318
2319 # dirty hack because... archetypes are not yet loaded
2320 Event->timer (
2321 after => 10,
2322 cb => sub {
2323 $_[0]->w->cancel;
2324
2325 # provide some exits "home"
2326 my $exit = cf::object::new "exit";
2327
2328 $exit->slaying ($EMERGENCY_POSITION->[0]);
2329 $exit->stats->hp ($EMERGENCY_POSITION->[1]);
2330 $exit->stats->sp ($EMERGENCY_POSITION->[2]);
2331
2332 $LINK_MAP->insert ($exit->clone, 19, 19);
2333 $LINK_MAP->insert ($exit->clone, 19, 20);
2334 $LINK_MAP->insert ($exit->clone, 19, 21);
2335 $LINK_MAP->insert ($exit->clone, 20, 19);
2336 $LINK_MAP->insert ($exit->clone, 20, 21);
2337 $LINK_MAP->insert ($exit->clone, 21, 19);
2338 $LINK_MAP->insert ($exit->clone, 21, 20);
2339 $LINK_MAP->insert ($exit->clone, 21, 21);
2340
2341 $exit->destroy;
2342 });
2343
2344 $LINK_MAP->{deny_save} = 1;
2345 $LINK_MAP->{deny_reset} = 1;
2346
2347 $cf::MAP{$LINK_MAP->path} = $LINK_MAP;
2348} 3982}
2349
2350register "<global>", __PACKAGE__;
2351 3983
2352register_command "reload" => sub { 3984register_command "reload" => sub {
2353 my ($who, $arg) = @_; 3985 my ($who, $arg) = @_;
2354 3986
2355 if ($who->flag (FLAG_WIZ)) { 3987 if ($who->flag (FLAG_WIZ)) {
2356 $who->message ("start of reload.");
2357 reload;
2358 $who->message ("end of reload."); 3988 $who->message ("reloading server.");
3989 async {
3990 $Coro::current->{desc} = "perl_reload";
3991 reload_perl;
3992 };
2359 } 3993 }
2360}; 3994};
2361 3995
2362unshift @INC, $LIBDIR; 3996unshift @INC, $LIBDIR;
2363 3997
2364$TICK_WATCHER = Event->timer ( 3998my $bug_warning = 0;
2365 reentrant => 0, 3999
2366 prio => 0, 4000our @WAIT_FOR_TICK;
2367 at => $NEXT_TICK || $TICK, 4001our @WAIT_FOR_TICK_BEGIN;
2368 data => WF_AUTOCANCEL, 4002
2369 cb => sub { 4003sub wait_for_tick {
4004 return Coro::cede if tick_inhibit || $Coro::current == $Coro::main;
4005
4006 my $signal = new Coro::Signal;
4007 push @WAIT_FOR_TICK, $signal;
4008 $signal->wait;
4009}
4010
4011sub wait_for_tick_begin {
4012 return Coro::cede if tick_inhibit || $Coro::current == $Coro::main;
4013
4014 my $signal = new Coro::Signal;
4015 push @WAIT_FOR_TICK_BEGIN, $signal;
4016 $signal->wait;
4017}
4018
4019sub tick {
4020 if ($Coro::current != $Coro::main) {
4021 Carp::cluck "major BUG: server tick called outside of main coro, skipping it"
4022 unless ++$bug_warning > 10;
4023 return;
4024 }
4025
2370 cf::server_tick; # one server iteration 4026 cf::server_tick; # one server iteration
2371 $RUNTIME += $TICK;
2372 $NEXT_TICK += $TICK;
2373 4027
2374 # if we are delayed by four ticks or more, skip them all 4028 #for(1..3e6){} AE::now_update; $NOW=AE::now; # generate load #d#
2375 $NEXT_TICK = Event::time if Event::time >= $NEXT_TICK + $TICK * 4;
2376 4029
2377 $TICK_WATCHER->at ($NEXT_TICK); 4030 if ($NOW >= $NEXT_RUNTIME_WRITE) {
2378 $TICK_WATCHER->start; 4031 $NEXT_RUNTIME_WRITE = List::Util::max $NEXT_RUNTIME_WRITE + 10, $NOW + 5.;
2379 }, 4032 Coro::async_pool {
2380); 4033 $Coro::current->{desc} = "runtime saver";
2381
2382IO::AIO::max_poll_time $TICK * 0.2;
2383
2384Event->io (
2385 fd => IO::AIO::poll_fileno,
2386 poll => 'r',
2387 prio => 5,
2388 data => WF_AUTOCANCEL,
2389 cb => \&IO::AIO::poll_cb,
2390);
2391
2392Event->timer (
2393 data => WF_AUTOCANCEL,
2394 after => 0,
2395 interval => 10,
2396 cb => sub {
2397 (Coro::unblock_sub {
2398 write_runtime 4034 write_runtime_sync
2399 or warn "ERROR: unable to write runtime file: $!"; 4035 or error "ERROR: unable to write runtime file: $!";
2400 })->(); 4036 };
2401 }, 4037 }
2402); 4038
4039 if (my $sig = shift @WAIT_FOR_TICK_BEGIN) {
4040 $sig->send;
4041 }
4042 while (my $sig = shift @WAIT_FOR_TICK) {
4043 $sig->send;
4044 }
4045
4046 $LOAD = ($NOW - $TICK_START) / $TICK;
4047 $LOADAVG = $LOADAVG * 0.75 + $LOAD * 0.25;
4048
4049 if (0) {
4050 if ($NEXT_TICK) {
4051 my $jitter = $TICK_START - $NEXT_TICK;
4052 $JITTER = $JITTER * 0.75 + $jitter * 0.25;
4053 debug "jitter $JITTER\n";#d#
4054 }
4055 }
4056}
4057
4058{
4059 # configure BDB
4060
4061 BDB::min_parallel 16;
4062 BDB::max_poll_reqs $TICK * 0.1;
4063 $AnyEvent::BDB::WATCHER->priority (1);
4064
4065 unless ($DB_ENV) {
4066 $DB_ENV = BDB::db_env_create;
4067 $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT);
4068 $DB_ENV->set_flags (&BDB::LOG_AUTOREMOVE ) if BDB::VERSION v0, v4.7;
4069 $DB_ENV->log_set_config (&BDB::LOG_AUTO_REMOVE) if BDB::VERSION v4.7;
4070 $DB_ENV->set_timeout (30, BDB::SET_TXN_TIMEOUT);
4071 $DB_ENV->set_timeout (30, BDB::SET_LOCK_TIMEOUT);
4072
4073 cf::sync_job {
4074 eval {
4075 BDB::db_env_open
4076 $DB_ENV,
4077 $BDBDIR,
4078 BDB::INIT_LOCK | BDB::INIT_LOG | BDB::INIT_MPOOL | BDB::INIT_TXN
4079 | BDB::RECOVER | BDB::REGISTER | BDB::USE_ENVIRON | BDB::CREATE,
4080 0666;
4081
4082 cf::cleanup "db_env_open($BDBDIR): $!" if $!;
4083 };
4084
4085 cf::cleanup "db_env_open(db): $@" if $@;
4086 };
4087 }
4088
4089 $BDB_DEADLOCK_WATCHER = EV::periodic 0, 3, 0, sub {
4090 BDB::db_env_lock_detect $DB_ENV, 0, BDB::LOCK_DEFAULT, 0, sub { };
4091 };
4092 $BDB_CHECKPOINT_WATCHER = EV::periodic 0, 60, 0, sub {
4093 BDB::db_env_txn_checkpoint $DB_ENV, 0, 0, 0, sub { };
4094 };
4095 $BDB_TRICKLE_WATCHER = EV::periodic 0, 10, 0, sub {
4096 BDB::db_env_memp_trickle $DB_ENV, 20, 0, sub { };
4097 };
4098}
4099
4100{
4101 # configure IO::AIO
4102
4103 IO::AIO::min_parallel 8;
4104 IO::AIO::max_poll_time $TICK * 0.1;
4105 undef $AnyEvent::AIO::WATCHER;
4106}
4107
4108my $_log_backtrace;
4109
4110sub _log_backtrace {
4111 my ($msg, @addr) = @_;
4112
4113 $msg =~ s/\n//;
4114
4115 # limit the # of concurrent backtraces
4116 if ($_log_backtrace < 2) {
4117 ++$_log_backtrace;
4118 my $perl_bt = Carp::longmess $msg;
4119 async {
4120 $Coro::current->{desc} = "abt $msg";
4121
4122 my @bt = fork_call {
4123 @addr = map { sprintf "%x", $_ } @addr;
4124 my $self = (-f "/proc/$$/exe") ? "/proc/$$/exe" : $^X;
4125 open my $fh, "exec addr2line -C -f -i -e \Q$self\E @addr 2>&1 |"
4126 or die "addr2line: $!";
4127
4128 my @funcs;
4129 my @res = <$fh>;
4130 chomp for @res;
4131 while (@res) {
4132 my ($func, $line) = splice @res, 0, 2, ();
4133 push @funcs, "[$func] $line";
4134 }
4135
4136 @funcs
4137 };
4138
4139 LOG llevInfo, "[ABT] $perl_bt\n";
4140 LOG llevInfo, "[ABT] --- C backtrace follows ---\n";
4141 LOG llevInfo, "[ABT] $_\n" for @bt;
4142 --$_log_backtrace;
4143 };
4144 } else {
4145 LOG llevInfo, "[ABT] $msg\n";
4146 LOG llevInfo, "[ABT] [suppressed]\n";
4147 }
4148}
4149
4150# load additional modules
4151require "cf/$_.pm" for @EXTRA_MODULES;
4152cf::_connect_to_perl_2;
2403 4153
2404END { cf::emergency_save } 4154END { cf::emergency_save }
2405 4155
24061 41561
2407 4157

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines