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.466 by root, Thu Jan 8 03:03:24 2009 UTC vs.
Revision 1.596 by root, Fri Nov 9 20:37:57 2012 UTC

1# 1#
2# This file is part of Deliantra, the Roguelike Realtime MMORPG. 2# This file is part of Deliantra, the Roguelike Realtime MMORPG.
3# 3#
4# Copyright (©) 2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team 4# Copyright (©) 2006,2007,2008,2009,2010,2011,2012 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
5# 5#
6# Deliantra is free software: you can redistribute it and/or modify 6# Deliantra is free software: you can redistribute it and/or modify it under
7# it under the terms of the GNU General Public License as published by 7# the terms of the Affero GNU General Public License as published by the
8# the Free Software Foundation, either version 3 of the License, or 8# Free Software Foundation, either version 3 of the License, or (at your
9# (at your option) any later version. 9# option) any later version.
10# 10#
11# This program is distributed in the hope that it will be useful, 11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details. 14# GNU General Public License for more details.
15# 15#
16# You should have received a copy of the GNU General Public License 16# You should have received a copy of the Affero GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>. 17# and the GNU General Public License along with this program. If not, see
18# 18# <http://www.gnu.org/licenses/>.
19#
19# The authors can be reached via e-mail to <support@deliantra.net> 20# The authors can be reached via e-mail to <support@deliantra.net>
20# 21#
21 22
22package cf; 23package cf;
23 24
24use 5.10.0; 25use common::sense;
25use utf8;
26use strict qw(vars subs);
27 26
28use Symbol; 27use Symbol;
29use List::Util; 28use List::Util;
30use Socket; 29use Socket;
31use EV; 30use EV;
32use Opcode; 31use Opcode;
33use Safe; 32use Safe;
34use Safe::Hole; 33use Safe::Hole;
35use Storable (); 34use Storable ();
36
37use Guard (); 35use Carp ();
36
37use AnyEvent ();
38use AnyEvent::IO ();
39use AnyEvent::DNS ();
40
38use Coro (); 41use Coro ();
39use Coro::State; 42use Coro::State;
40use Coro::Handle; 43use Coro::Handle;
41use Coro::EV; 44use Coro::EV;
42use Coro::AnyEvent; 45use Coro::AnyEvent;
48use Coro::AIO; 51use Coro::AIO;
49use Coro::BDB 1.6; 52use Coro::BDB 1.6;
50use Coro::Storable; 53use Coro::Storable;
51use Coro::Util (); 54use Coro::Util ();
52 55
56use Guard ();
53use JSON::XS 2.01 (); 57use JSON::XS 2.01 ();
54use BDB (); 58use BDB ();
55use Data::Dumper; 59use Data::Dumper;
56use Digest::MD5;
57use Fcntl; 60use Fcntl;
58use YAML (); 61use YAML::XS ();
59use IO::AIO (); 62use IO::AIO ();
60use Time::HiRes; 63use Time::HiRes;
61use Compress::LZF; 64use Compress::LZF;
62use Digest::MD5 (); 65use Digest::MD5 ();
63 66
72 75
73# make sure c-lzf reinitialises itself 76# make sure c-lzf reinitialises itself
74Compress::LZF::set_serializer "Storable", "Storable::net_mstore", "Storable::mretrieve"; 77Compress::LZF::set_serializer "Storable", "Storable::net_mstore", "Storable::mretrieve";
75Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later 78Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later
76 79
80# strictly for debugging
81$SIG{QUIT} = sub { Carp::cluck "SIGQUIT" };
82
77sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload 83sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
84
85our @ORIG_INC;
78 86
79our %COMMAND = (); 87our %COMMAND = ();
80our %COMMAND_TIME = (); 88our %COMMAND_TIME = ();
81 89
82our @EXTS = (); # list of extension package names 90our @EXTS = (); # list of extension package names
83our %EXTCMD = (); 91our %EXTCMD = ();
92our %EXTACMD = ();
84our %EXTICMD = (); 93our %EXTICMD = ();
94our %EXTIACMD = ();
85our %EXT_CORO = (); # coroutines bound to extensions 95our %EXT_CORO = (); # coroutines bound to extensions
86our %EXT_MAP = (); # pluggable maps 96our %EXT_MAP = (); # pluggable maps
87 97
88our $RELOAD; # number of reloads so far, non-zero while in reload 98our $RELOAD; # number of reloads so far, non-zero while in reload
89our @EVENT; 99our @EVENT;
100our @REFLECT; # set by XS
101our %REFLECT; # set by us
90 102
91our $CONFDIR = confdir; 103our $CONFDIR = confdir;
104
92our $DATADIR = datadir; 105our $DATADIR = datadir;
93our $LIBDIR = "$DATADIR/ext"; 106our $LIBDIR = "$DATADIR/ext";
94our $PODDIR = "$DATADIR/pod"; 107our $PODDIR = "$DATADIR/pod";
95our $MAPDIR = "$DATADIR/" . mapdir; 108our $MAPDIR = "$DATADIR/" . mapdir;
109
96our $LOCALDIR = localdir; 110our $LOCALDIR = localdir;
97our $TMPDIR = "$LOCALDIR/" . tmpdir; 111our $TMPDIR = "$LOCALDIR/" . tmpdir;
98our $UNIQUEDIR = "$LOCALDIR/" . uniquedir; 112our $UNIQUEDIR = "$LOCALDIR/" . uniquedir;
99our $PLAYERDIR = "$LOCALDIR/" . playerdir; 113our $PLAYERDIR = "$LOCALDIR/" . playerdir;
100our $RANDOMDIR = "$LOCALDIR/random"; 114our $RANDOMDIR = "$LOCALDIR/random";
101our $BDBDIR = "$LOCALDIR/db"; 115our $BDBDIR = "$LOCALDIR/db";
102our $PIDFILE = "$LOCALDIR/pid"; 116our $PIDFILE = "$LOCALDIR/pid";
103our $RUNTIMEFILE = "$LOCALDIR/runtime"; 117our $RUNTIMEFILE = "$LOCALDIR/runtime";
104 118
105our %RESOURCE; 119#our %RESOURCE; # unused
120
121our $OUTPUT_RATE_MIN = 3000;
122our $OUTPUT_RATE_MAX = 1000000;
123
124our $MAX_LINKS = 32; # how many chained exits to follow
125our $VERBOSE_IO = 1;
106 126
107our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!) 127our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!)
108our $NEXT_RUNTIME_WRITE; # when should the runtime file be written 128our $NEXT_RUNTIME_WRITE; # when should the runtime file be written
109our $NEXT_TICK; 129our $NEXT_TICK;
110our $USE_FSYNC = 1; # use fsync to write maps - default off 130our $USE_FSYNC = 1; # use fsync to write maps - default on
111 131
112our $BDB_DEADLOCK_WATCHER; 132our $BDB_DEADLOCK_WATCHER;
113our $BDB_CHECKPOINT_WATCHER; 133our $BDB_CHECKPOINT_WATCHER;
114our $BDB_TRICKLE_WATCHER; 134our $BDB_TRICKLE_WATCHER;
115our $DB_ENV; 135our $DB_ENV;
116 136
117our @EXTRA_MODULES = qw(pod mapscript); 137our @EXTRA_MODULES = qw(pod match mapscript incloader);
118 138
119our %CFG; 139our %CFG;
140our %EXT_CFG; # cfgkeyname => [var-ref, defaultvalue]
120 141
121our $UPTIME; $UPTIME ||= time; 142our $UPTIME; $UPTIME ||= time;
122our $RUNTIME; 143our $RUNTIME = 0;
144our $SERVER_TICK = 0;
123our $NOW; 145our $NOW;
124 146
125our (%PLAYER, %PLAYER_LOADING); # all users 147our (%PLAYER, %PLAYER_LOADING); # all users
126our (%MAP, %MAP_LOADING ); # all maps 148our (%MAP, %MAP_LOADING ); # all maps
127our $LINK_MAP; # the special {link} map, which is always available 149our $LINK_MAP; # the special {link} map, which is always available
134our $JITTER; # average jitter 156our $JITTER; # average jitter
135our $TICK_START; # for load detecting purposes 157our $TICK_START; # for load detecting purposes
136 158
137our @POST_INIT; 159our @POST_INIT;
138 160
139our $REATTACH_ON_RELOAD; # ste to true to force object reattach on reload (slow) 161our $REATTACH_ON_RELOAD; # set to true to force object reattach on reload (slow)
162our $REALLY_UNLOOP; # never set to true, please :)
163
164our $WAIT_FOR_TICK = new Coro::Signal;
165our @WAIT_FOR_TICK_BEGIN;
140 166
141binmode STDOUT; 167binmode STDOUT;
142binmode STDERR; 168binmode STDERR;
143 169
144# read virtual server time, if available 170# read virtual server time, if available
146 open my $fh, "<", $RUNTIMEFILE 172 open my $fh, "<", $RUNTIMEFILE
147 or die "unable to read $RUNTIMEFILE file: $!"; 173 or die "unable to read $RUNTIMEFILE file: $!";
148 $RUNTIME = <$fh> + 0.; 174 $RUNTIME = <$fh> + 0.;
149} 175}
150 176
177eval "sub TICK() { $TICK } 1" or die;
178
151mkdir $_ 179mkdir $_
152 for $LOCALDIR, $TMPDIR, $UNIQUEDIR, $PLAYERDIR, $RANDOMDIR, $BDBDIR; 180 for $LOCALDIR, $TMPDIR, $UNIQUEDIR, $PLAYERDIR, $RANDOMDIR, $BDBDIR;
153 181
154our $EMERGENCY_POSITION; 182our $EMERGENCY_POSITION;
155 183
156sub cf::map::normalise; 184sub cf::map::normalise;
157 185
186sub in_main() {
187 $Coro::current == $Coro::main
188}
189
158############################################################################# 190#############################################################################
159 191
192%REFLECT = ();
193for (@REFLECT) {
194 my $reflect = JSON::XS::decode_json $_;
195 $REFLECT{$reflect->{class}} = $reflect;
196}
197
198# this is decidedly evil
199$REFLECT{object}{flags} = { map +($_ => undef), grep $_, map /^FLAG_([A-Z0-9_]+)$/ && lc $1, keys %{"cf::"} };
200
201#############################################################################
202
160=head2 GLOBAL VARIABLES 203=head2 GLOBAL VARIABLES
161 204
162=over 4 205=over 4
163 206
164=item $cf::UPTIME 207=item $cf::UPTIME
165 208
166The timestamp of the server start (so not actually an uptime). 209The timestamp of the server start (so not actually an "uptime").
210
211=item $cf::SERVER_TICK
212
213An unsigned integer that starts at zero when the server is started and is
214incremented on every tick.
215
216=item $cf::NOW
217
218The (real) time of the last (current) server tick - updated before and
219after tick processing, so this is useful only as a rough "what time is it
220now" estimate.
221
222=item $cf::TICK
223
224The interval between each server tick, in seconds.
167 225
168=item $cf::RUNTIME 226=item $cf::RUNTIME
169 227
170The time this server has run, starts at 0 and is increased by $cf::TICK on 228The time this server has run, starts at 0 and is increased by $cf::TICK on
171every server tick. 229every server tick.
172 230
173=item $cf::CONFDIR $cf::DATADIR $cf::LIBDIR $cf::PODDIR 231=item $cf::CONFDIR $cf::DATADIR $cf::LIBDIR $cf::PODDIR
174$cf::MAPDIR $cf::LOCALDIR $cf::TMPDIR $cf::UNIQUEDIR 232$cf::MAPDIR $cf::LOCALDIR $cf::TMPDIR $cf::UNIQUEDIR
175$cf::PLAYERDIR $cf::RANDOMDIR $cf::BDBDIR 233$cf::PLAYERDIR $cf::RANDOMDIR $cf::BDBDIR
176 234
177Various directories - "/etc", read-only install directory, perl-library 235Various directories - "/etc", read-only install directory, perl-library
178directory, pod-directory, read-only maps directory, "/var", "/var/tmp", 236directory, pod-directory, read-only maps directory, "/var", "/var/tmp",
179unique-items directory, player file directory, random maps directory and 237unique-items directory, player file directory, random maps directory and
180database environment. 238database environment.
181 239
182=item $cf::NOW
183
184The time of the last (current) server tick.
185
186=item $cf::TICK
187
188The interval between server ticks, in seconds.
189
190=item $cf::LOADAVG 240=item $cf::LOADAVG
191 241
192The current CPU load on the server (alpha-smoothed), as a value between 0 242The current CPU load on the server (alpha-smoothed), as a value between 0
193(none) and 1 (overloaded), indicating how much time is spent on processing 243(none) and 1 (overloaded), indicating how much time is spent on processing
194objects per tick. Healthy values are < 0.5. 244objects per tick. Healthy values are < 0.5.
203from wherever your confdir points to. 253from wherever your confdir points to.
204 254
205=item cf::wait_for_tick, cf::wait_for_tick_begin 255=item cf::wait_for_tick, cf::wait_for_tick_begin
206 256
207These are functions that inhibit the current coroutine one tick. cf::wait_for_tick_begin only 257These are functions that inhibit the current coroutine one tick. cf::wait_for_tick_begin only
208returns directly I<after> the tick processing (and consequently, can only wake one process 258returns directly I<after> the tick processing (and consequently, can only wake one thread
209per tick), while cf::wait_for_tick wakes up all waiters after tick processing. 259per tick), while cf::wait_for_tick wakes up all waiters after tick processing.
210 260
261Note that cf::Wait_for_tick will immediately return when the server is not
262ticking, making it suitable for small pauses in threads that need to run
263when the server is paused. If that is not applicable (i.e. you I<really>
264want to wait, use C<$cf::WAIT_FOR_TICK>).
265
266=item $cf::WAIT_FOR_TICK
267
268Note that C<cf::wait_for_tick> is probably the correct thing to use. This
269variable contains a L<Coro::Signal> that is broadcats after every server
270tick. Calling C<< ->wait >> on it will suspend the caller until after the
271next server tick.
272
273=cut
274
275sub wait_for_tick();
276sub wait_for_tick_begin();
277
211=item @cf::INVOKE_RESULTS 278=item @cf::INVOKE_RESULTS
212 279
213This array contains the results of the last C<invoke ()> call. When 280This array contains the results of the last C<invoke ()> call. When
214C<cf::override> is called C<@cf::INVOKE_RESULTS> is set to the parameters of 281C<cf::override> is called C<@cf::INVOKE_RESULTS> is set to the parameters of
215that call. 282that call.
216 283
284=item %cf::REFLECT
285
286Contains, for each (C++) class name, a hash reference with information
287about object members (methods, scalars, arrays and flags) and other
288metadata, which is useful for introspection.
289
217=back 290=back
218 291
219=cut 292=cut
220 293
221BEGIN { 294sub error(@) { LOG llevError, join "", @_ }
222 *CORE::GLOBAL::warn = sub { 295sub warn (@) { LOG llevWarn , join "", @_ }
296sub info (@) { LOG llevInfo , join "", @_ }
297sub debug(@) { LOG llevDebug, join "", @_ }
298sub trace(@) { LOG llevTrace, join "", @_ }
299
300$Coro::State::WARNHOOK = sub {
223 my $msg = join "", @_; 301 my $msg = join "", @_;
224 302
225 $msg .= "\n" 303 $msg .= "\n"
226 unless $msg =~ /\n$/; 304 unless $msg =~ /\n$/;
227 305
228 $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge; 306 $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge;
229 307
230 LOG llevError, $msg; 308 LOG llevWarn, $msg;
231 }; 309};
232}
233 310
234$Coro::State::DIEHOOK = sub { 311$Coro::State::DIEHOOK = sub {
235 return unless $^S eq 0; # "eq", not "==" 312 return unless $^S eq 0; # "eq", not "=="
236 313
237 if ($Coro::current == $Coro::main) {#d# 314 error Carp::longmess $_[0];
315
316 if (in_main) {#d#
238 warn "DIEHOOK called in main context, Coro bug?\n";#d# 317 error "DIEHOOK called in main context, Coro bug?\n";#d#
239 return;#d# 318 return;#d#
240 }#d# 319 }#d#
241 320
242 # kill coroutine otherwise 321 # kill coroutine otherwise
243 warn Carp::longmess $_[0];
244 Coro::terminate 322 Coro::terminate
245}; 323};
246
247$SIG{__DIE__} = sub { }; #d#?
248 324
249@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable'; 325@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
250@safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable'; 326@safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable';
251@safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable'; 327@safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable';
252@safe::cf::client::ISA = @cf::client::ISA = 'cf::attachable'; 328@safe::cf::client::ISA = @cf::client::ISA = 'cf::attachable';
266)) { 342)) {
267 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg; 343 @{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg;
268} 344}
269 345
270$EV::DIED = sub { 346$EV::DIED = sub {
271 warn "error in event callback: @_"; 347 warn "error in event callback: $@";
272}; 348};
349
350#############################################################################
351
352sub fork_call(&@);
353sub get_slot($;$$);
273 354
274############################################################################# 355#############################################################################
275 356
276=head2 UTILITY FUNCTIONS 357=head2 UTILITY FUNCTIONS
277 358
298 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge; 379 $d =~ s/([\x00-\x07\x09\x0b\x0c\x0e-\x1f])/sprintf "\\x%02x", ord($1)/ge;
299 $d 380 $d
300 } || "[unable to dump $_[0]: '$@']"; 381 } || "[unable to dump $_[0]: '$@']";
301} 382}
302 383
384=item $scalar = cf::load_file $path
385
386Loads the given file from path and returns its contents. Croaks on error
387and can block.
388
389=cut
390
391sub load_file($) {
392 0 <= aio_load $_[0], my $data
393 or Carp::croak "$_[0]: $!";
394
395 $data
396}
397
398=item $success = cf::replace_file $path, $data, $sync
399
400Atomically replaces the file at the given $path with new $data, and
401optionally $sync the data to disk before replacing the file.
402
403=cut
404
405sub replace_file($$;$) {
406 my ($path, $data, $sync) = @_;
407
408 my $lock = cf::lock_acquire ("replace_file:$path");
409
410 my $fh = aio_open "$path~", Fcntl::O_WRONLY | Fcntl::O_CREAT | Fcntl::O_TRUNC, 0644
411 or return;
412
413 $data = $data->() if ref $data;
414
415 length $data == aio_write $fh, 0, (length $data), $data, 0
416 or return;
417
418 !$sync
419 or !aio_fsync $fh
420 or return;
421
422 aio_close $fh
423 and return;
424
425 aio_rename "$path~", $path
426 and return;
427
428 if ($sync) {
429 $path =~ s%/[^/]*$%%;
430 aio_pathsync $path;
431 }
432
433 1
434}
435
303=item $ref = cf::decode_json $json 436=item $ref = cf::decode_json $json
304 437
305Converts a JSON string into the corresponding perl data structure. 438Converts a JSON string into the corresponding perl data structure.
306 439
307=item $json = cf::encode_json $ref 440=item $json = cf::encode_json $ref
313our $json_coder = JSON::XS->new->utf8->max_size (1e6); # accept ~1mb max 446our $json_coder = JSON::XS->new->utf8->max_size (1e6); # accept ~1mb max
314 447
315sub encode_json($) { $json_coder->encode ($_[0]) } 448sub encode_json($) { $json_coder->encode ($_[0]) }
316sub decode_json($) { $json_coder->decode ($_[0]) } 449sub decode_json($) { $json_coder->decode ($_[0]) }
317 450
451=item $ref = cf::decode_storable $scalar
452
453Same as Coro::Storable::thaw, so blocks.
454
455=cut
456
457BEGIN { *decode_storable = \&Coro::Storable::thaw }
458
459=item $ref = cf::decode_yaml $scalar
460
461Same as YAML::XS::Load, but doesn't leak, because it forks (and thus blocks).
462
463=cut
464
465sub decode_yaml($) {
466 fork_call { YAML::XS::Load $_[0] } @_
467}
468
469=item $scalar = cf::unlzf $scalar
470
471Same as Compress::LZF::compress, but takes server ticks into account, so
472blocks.
473
474=cut
475
476sub unlzf($) {
477 # we assume 100mb/s minimum decompression speed (noncompressible data on a ~2ghz machine)
478 cf::get_slot +(length $_[0]) / 100_000_000, 0, "unlzf";
479 Compress::LZF::decompress $_[0]
480}
481
318=item cf::post_init { BLOCK } 482=item cf::post_init { BLOCK }
319 483
320Execute the given codeblock, I<after> all extensions have been (re-)loaded, 484Execute the given codeblock, I<after> all extensions have been (re-)loaded,
321but I<before> the server starts ticking again. 485but I<before> the server starts ticking again.
322 486
323The cdoeblock will have a single boolean argument to indicate whether this 487The codeblock will have a single boolean argument to indicate whether this
324is a reload or not. 488is a reload or not.
325 489
326=cut 490=cut
327 491
328sub post_init(&) { 492sub post_init(&) {
329 push @POST_INIT, shift; 493 push @POST_INIT, shift;
494}
495
496sub _post_init {
497 trace "running post_init jobs";
498
499 # run them in parallel...
500
501 my @join;
502
503 while () {
504 push @join, map &Coro::async ($_, 0), @POST_INIT;
505 @POST_INIT = ();
506
507 @join or last;
508
509 (pop @join)->join;
510 }
330} 511}
331 512
332=item cf::lock_wait $string 513=item cf::lock_wait $string
333 514
334Wait until the given lock is available. See cf::lock_acquire. 515Wait until the given lock is available. See cf::lock_acquire.
373} 554}
374 555
375=item cf::periodic $interval, $cb 556=item cf::periodic $interval, $cb
376 557
377Like EV::periodic, but randomly selects a starting point so that the actions 558Like EV::periodic, but randomly selects a starting point so that the actions
378get spread over timer. 559get spread over time.
379 560
380=cut 561=cut
381 562
382sub periodic($$) { 563sub periodic($$) {
383 my ($interval, $cb) = @_; 564 my ($interval, $cb) = @_;
387 EV::periodic $start, $interval, 0, $cb 568 EV::periodic $start, $interval, 0, $cb
388} 569}
389 570
390=item cf::get_slot $time[, $priority[, $name]] 571=item cf::get_slot $time[, $priority[, $name]]
391 572
392Allocate $time seconds of blocking CPU time at priority C<$priority>: 573Allocate $time seconds of blocking CPU time at priority C<$priority>
393This call blocks and returns only when you have at least C<$time> seconds 574(default: 0): This call blocks and returns only when you have at least
394of cpu time till the next tick. The slot is only valid till the next cede. 575C<$time> seconds of cpu time till the next tick. The slot is only valid
576till the next cede.
577
578Background jobs should use a priority les than zero, interactive jobs
579should use 100 or more.
395 580
396The optional C<$name> can be used to identify the job to run. It might be 581The optional C<$name> can be used to identify the job to run. It might be
397used for statistical purposes and should identify the same time-class. 582used for statistical purposes and should identify the same time-class.
398 583
399Useful for short background jobs. 584Useful for short background jobs.
400 585
401=cut 586=cut
402 587
403our @SLOT_QUEUE; 588our @SLOT_QUEUE;
404our $SLOT_QUEUE; 589our $SLOT_QUEUE;
590our $SLOT_DECAY = 0.9;
405 591
406$SLOT_QUEUE->cancel if $SLOT_QUEUE; 592$SLOT_QUEUE->cancel if $SLOT_QUEUE;
407$SLOT_QUEUE = Coro::async { 593$SLOT_QUEUE = Coro::async {
408 $Coro::current->desc ("timeslot manager"); 594 $Coro::current->desc ("timeslot manager");
409 595
410 my $signal = new Coro::Signal; 596 my $signal = new Coro::Signal;
597 my $busy;
411 598
412 while () { 599 while () {
413 next_job: 600 next_job:
601
414 my $avail = cf::till_tick; 602 my $avail = cf::till_tick;
415 if ($avail > 0.01) { 603
416 for (0 .. $#SLOT_QUEUE) { 604 for (0 .. $#SLOT_QUEUE) {
417 if ($SLOT_QUEUE[$_][0] < $avail) { 605 if ($SLOT_QUEUE[$_][0] <= $avail) {
606 $busy = 0;
418 my $job = splice @SLOT_QUEUE, $_, 1, (); 607 my $job = splice @SLOT_QUEUE, $_, 1, ();
419 $job->[2]->send; 608 $job->[2]->send;
420 Coro::cede; 609 Coro::cede;
421 goto next_job; 610 goto next_job;
422 } 611 } else {
612 $SLOT_QUEUE[$_][0] *= $SLOT_DECAY;
423 } 613 }
424 } 614 }
425 615
426 if (@SLOT_QUEUE) { 616 if (@SLOT_QUEUE) {
427 # we do not use wait_for_tick() as it returns immediately when tick is inactive 617 # we do not use wait_for_tick() as it returns immediately when tick is inactive
428 push @cf::WAIT_FOR_TICK, $signal; 618 $WAIT_FOR_TICK->wait;
429 $signal->wait;
430 } else { 619 } else {
620 $busy = 0;
431 Coro::schedule; 621 Coro::schedule;
432 } 622 }
433 } 623 }
434}; 624};
435 625
436sub get_slot($;$$) { 626sub get_slot($;$$) {
437 return if tick_inhibit || $Coro::current == $Coro::main; 627 return if tick_inhibit || $Coro::current == $Coro::main;
438 628
439 my ($time, $pri, $name) = @_; 629 my ($time, $pri, $name) = @_;
440 630
441 $time = $TICK * .6 if $time > $TICK * .6; 631 $time = clamp $time, 0.01, $TICK * .6;
632
442 my $sig = new Coro::Signal; 633 my $sig = new Coro::Signal;
443 634
444 push @SLOT_QUEUE, [$time, $pri, $sig, $name]; 635 push @SLOT_QUEUE, [$time, $pri, $sig, $name];
445 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE; 636 @SLOT_QUEUE = sort { $b->[1] <=> $a->[1] } @SLOT_QUEUE;
446 $SLOT_QUEUE->ready; 637 $SLOT_QUEUE->ready;
472=cut 663=cut
473 664
474sub sync_job(&) { 665sub sync_job(&) {
475 my ($job) = @_; 666 my ($job) = @_;
476 667
477 if ($Coro::current == $Coro::main) { 668 if (in_main) {
478 my $time = EV::time; 669 my $time = AE::time;
479 670
480 # this is the main coro, too bad, we have to block 671 # this is the main coro, too bad, we have to block
481 # till the operation succeeds, freezing the server :/ 672 # till the operation succeeds, freezing the server :/
482 673
483 LOG llevError, Carp::longmess "sync job";#d# 674 #LOG llevError, Carp::longmess "sync job";#d#
484 675
485 my $freeze_guard = freeze_mainloop; 676 my $freeze_guard = freeze_mainloop;
486 677
487 my $busy = 1; 678 my $busy = 1;
488 my @res; 679 my @res;
489 680
490 (async { 681 (async {
491 $Coro::current->desc ("sync job coro"); 682 $Coro::current->desc ("sync job coro");
492 @res = eval { $job->() }; 683 @res = eval { $job->() };
493 warn $@ if $@; 684 error $@ if $@;
494 undef $busy; 685 undef $busy;
495 })->prio (Coro::PRIO_MAX); 686 })->prio (Coro::PRIO_MAX);
496 687
497 while ($busy) { 688 while ($busy) {
498 if (Coro::nready) { 689 if (Coro::nready) {
500 } else { 691 } else {
501 EV::loop EV::LOOP_ONESHOT; 692 EV::loop EV::LOOP_ONESHOT;
502 } 693 }
503 } 694 }
504 695
505 my $time = EV::time - $time; 696 my $time = AE::time - $time;
506 697
507 $TICK_START += $time; # do not account sync jobs to server load 698 $TICK_START += $time; # do not account sync jobs to server load
508 699
509 wantarray ? @res : $res[0] 700 wantarray ? @res : $res[0]
510 } else { 701 } else {
532 $EXT_CORO{$coro+0} = $coro; 723 $EXT_CORO{$coro+0} = $coro;
533 724
534 $coro 725 $coro
535} 726}
536 727
537=item fork_call { }, $args 728=item fork_call { }, @args
538 729
539Executes the given code block with the given arguments in a seperate 730Executes the given code block with the given arguments in a seperate
540process, returning the results. Everything must be serialisable with 731process, returning the results. Everything must be serialisable with
541Coro::Storable. May, of course, block. Note that the executed sub may 732Coro::Storable. May, of course, block. Note that the executed sub may
542never block itself or use any form of event handling. 733never block itself or use any form of event handling.
543 734
544=cut 735=cut
545 736
737sub post_fork {
738 reset_signals;
739}
740
546sub fork_call(&@) { 741sub fork_call(&@) {
547 my ($cb, @args) = @_; 742 my ($cb, @args) = @_;
548 743
549 # we seemingly have to make a local copy of the whole thing, 744 # we seemingly have to make a local copy of the whole thing,
550 # otherwise perl prematurely frees the stuff :/ 745 # otherwise perl prematurely frees the stuff :/
551 # TODO: investigate and fix (likely this will be rather laborious) 746 # TODO: investigate and fix (likely this will be rather laborious)
552 747
553 my @res = Coro::Util::fork_eval { 748 my @res = Coro::Util::fork_eval {
554 reset_signals; 749 cf::post_fork;
555 &$cb 750 &$cb
556 }, @args; 751 } @args;
557 752
558 wantarray ? @res : $res[-1] 753 wantarray ? @res : $res[-1]
754}
755
756sub objinfo {
757 (
758 "counter value" => cf::object::object_count,
759 "objects created" => cf::object::create_count,
760 "objects destroyed" => cf::object::destroy_count,
761 "freelist size" => cf::object::free_count,
762 "allocated objects" => cf::object::objects_size,
763 "active objects" => cf::object::actives_size,
764 )
559} 765}
560 766
561=item $coin = coin_from_name $name 767=item $coin = coin_from_name $name
562 768
563=cut 769=cut
600within each server. 806within each server.
601 807
602=cut 808=cut
603 809
604sub db_table($) { 810sub db_table($) {
811 cf::error "db_get called from main context"
812 if $Coro::current == $Coro::main;
813
605 my ($name) = @_; 814 my ($name) = @_;
606 my $db = BDB::db_create $DB_ENV; 815 my $db = BDB::db_create $DB_ENV;
607 816
608 eval { 817 eval {
609 $db->set_flags (BDB::CHKSUM); 818 $db->set_flags (BDB::CHKSUM);
619} 828}
620 829
621our $DB; 830our $DB;
622 831
623sub db_init { 832sub db_init {
624 cf::sync_job {
625 $DB ||= db_table "db"; 833 $DB ||= db_table "db";
626 };
627} 834}
628 835
629sub db_get($$) { 836sub db_get($$) {
630 my $key = "$_[0]/$_[1]"; 837 my $key = "$_[0]/$_[1]";
631 838
632 cf::sync_job { 839 cf::error "db_get called from main context"
840 if $Coro::current == $Coro::main;
841
633 BDB::db_get $DB, undef, $key, my $data; 842 BDB::db_get $DB, undef, $key, my $data;
634 843
635 $! ? () 844 $! ? ()
636 : $data 845 : $data
637 }
638} 846}
639 847
640sub db_put($$$) { 848sub db_put($$$) {
641 BDB::dbreq_pri 4; 849 BDB::dbreq_pri 4;
642 BDB::db_put $DB, undef, "$_[0]/$_[1]", $_[2], 0, sub { }; 850 BDB::db_put $DB, undef, "$_[0]/$_[1]", $_[2], 0, sub { };
670 878
671 my @data; 879 my @data;
672 my $md5; 880 my $md5;
673 881
674 for (0 .. $#$src) { 882 for (0 .. $#$src) {
675 0 <= aio_load $src->[$_], $data[$_] 883 $data[$_] = load_file $src->[$_];
676 or Carp::croak "$src->[$_]: $!";
677 } 884 }
678 885
679 # if processing is expensive, check 886 # if processing is expensive, check
680 # checksum first 887 # checksum first
681 if (1) { 888 if (1) {
698 905
699 my $t1 = Time::HiRes::time; 906 my $t1 = Time::HiRes::time;
700 my $data = $process->(\@data); 907 my $data = $process->(\@data);
701 my $t2 = Time::HiRes::time; 908 my $t2 = Time::HiRes::time;
702 909
703 warn "cache: '$id' processed in ", $t2 - $t1, "s\n"; 910 info "cache: '$id' processed in ", $t2 - $t1, "s\n";
704 911
705 db_put cache => "$id/data", $data; 912 db_put cache => "$id/data", $data;
706 db_put cache => "$id/md5" , $md5; 913 db_put cache => "$id/md5" , $md5;
707 db_put cache => "$id/meta", $meta; 914 db_put cache => "$id/meta", $meta;
708 915
718 925
719=cut 926=cut
720 927
721sub datalog($@) { 928sub datalog($@) {
722 my ($type, %kv) = @_; 929 my ($type, %kv) = @_;
723 warn "DATALOG ", JSON::XS->new->ascii->encode ({ %kv, type => $type }); 930 info "DATALOG ", JSON::XS->new->ascii->encode ({ %kv, type => $type });
724} 931}
725 932
726=back 933=back
727 934
728=cut 935=cut
923 1130
924 } elsif (exists $cb_id{$type}) { 1131 } elsif (exists $cb_id{$type}) {
925 _attach_cb $registry, $cb_id{$type}, $prio, shift @arg; 1132 _attach_cb $registry, $cb_id{$type}, $prio, shift @arg;
926 1133
927 } elsif (ref $type) { 1134 } elsif (ref $type) {
928 warn "attaching objects not supported, ignoring.\n"; 1135 error "attaching objects not supported, ignoring.\n";
929 1136
930 } else { 1137 } else {
931 shift @arg; 1138 shift @arg;
932 warn "attach argument '$type' not supported, ignoring.\n"; 1139 error "attach argument '$type' not supported, ignoring.\n";
933 } 1140 }
934 } 1141 }
935} 1142}
936 1143
937sub _object_attach { 1144sub _object_attach {
947 _attach $registry, $klass, @attach; 1154 _attach $registry, $klass, @attach;
948 } 1155 }
949 1156
950 $obj->{$name} = \%arg; 1157 $obj->{$name} = \%arg;
951 } else { 1158 } else {
952 warn "object uses attachment '$name' which is not available, postponing.\n"; 1159 info "object uses attachment '$name' which is not available, postponing.\n";
953 } 1160 }
954 1161
955 $obj->{_attachment}{$name} = undef; 1162 $obj->{_attachment}{$name} = undef;
956} 1163}
957 1164
1016 1223
1017 for (@$callbacks) { 1224 for (@$callbacks) {
1018 eval { &{$_->[1]} }; 1225 eval { &{$_->[1]} };
1019 1226
1020 if ($@) { 1227 if ($@) {
1021 warn "$@";
1022 warn "... while processing $EVENT[$event][0](@_) event, skipping processing altogether.\n"; 1228 error "$@", "... while processing $EVENT[$event][0](@_) event, skipping processing altogether.\n";
1023 override; 1229 override;
1024 } 1230 }
1025 1231
1026 return 1 if $override; 1232 return 1 if $override;
1027 } 1233 }
1106 for (@$attach) { 1312 for (@$attach) {
1107 my ($klass, @attach) = @$_; 1313 my ($klass, @attach) = @$_;
1108 _attach $registry, $klass, @attach; 1314 _attach $registry, $klass, @attach;
1109 } 1315 }
1110 } else { 1316 } else {
1111 warn "object uses attachment '$name' that is not available, postponing.\n"; 1317 info "object uses attachment '$name' that is not available, postponing.\n";
1112 } 1318 }
1113 } 1319 }
1114} 1320}
1115 1321
1116cf::attachable->attach ( 1322cf::attachable->attach (
1143 my ($filename, $rdata, $objs) = @_; 1349 my ($filename, $rdata, $objs) = @_;
1144 1350
1145 sync_job { 1351 sync_job {
1146 if (length $$rdata) { 1352 if (length $$rdata) {
1147 utf8::decode (my $decname = $filename); 1353 utf8::decode (my $decname = $filename);
1148 warn sprintf "saving %s (%d,%d)\n", 1354 trace sprintf "saving %s (%d,%d)\n",
1149 $decname, length $$rdata, scalar @$objs; 1355 $decname, length $$rdata, scalar @$objs
1356 if $VERBOSE_IO;
1150 1357
1151 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) { 1358 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
1152 aio_chmod $fh, SAVE_MODE; 1359 aio_chmod $fh, SAVE_MODE;
1153 aio_write $fh, 0, (length $$rdata), $$rdata, 0; 1360 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
1154 aio_fsync $fh if $cf::USE_FSYNC; 1361 if ($cf::USE_FSYNC) {
1362 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;
1363 aio_fsync $fh;
1364 }
1155 aio_close $fh; 1365 aio_close $fh;
1156 1366
1157 if (@$objs) { 1367 if (@$objs) {
1158 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) { 1368 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) {
1159 aio_chmod $fh, SAVE_MODE; 1369 aio_chmod $fh, SAVE_MODE;
1160 my $data = Coro::Storable::nfreeze { version => 1, objs => $objs }; 1370 my $data = Coro::Storable::nfreeze { version => 1, objs => $objs };
1161 aio_write $fh, 0, (length $data), $data, 0; 1371 aio_write $fh, 0, (length $data), $data, 0;
1162 aio_fsync $fh if $cf::USE_FSYNC; 1372 if ($cf::USE_FSYNC) {
1373 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;
1374 aio_fsync $fh;
1375 }
1163 aio_close $fh; 1376 aio_close $fh;
1164 aio_rename "$filename.pst~", "$filename.pst"; 1377 aio_rename "$filename.pst~", "$filename.pst";
1165 } 1378 }
1166 } else { 1379 } else {
1167 aio_unlink "$filename.pst"; 1380 aio_unlink "$filename.pst";
1170 aio_rename "$filename~", $filename; 1383 aio_rename "$filename~", $filename;
1171 1384
1172 $filename =~ s%/[^/]+$%%; 1385 $filename =~ s%/[^/]+$%%;
1173 aio_pathsync $filename if $cf::USE_FSYNC; 1386 aio_pathsync $filename if $cf::USE_FSYNC;
1174 } else { 1387 } else {
1175 warn "unable to save objects: $filename~: $!\n"; 1388 error "unable to save objects: $filename~: $!\n";
1176 } 1389 }
1177 } else { 1390 } else {
1178 aio_unlink $filename; 1391 aio_unlink $filename;
1179 aio_unlink "$filename.pst"; 1392 aio_unlink "$filename.pst";
1180 } 1393 }
1204 my $st = eval { Coro::Storable::thaw $av }; 1417 my $st = eval { Coro::Storable::thaw $av };
1205 $av = $st->{objs}; 1418 $av = $st->{objs};
1206 } 1419 }
1207 1420
1208 utf8::decode (my $decname = $filename); 1421 utf8::decode (my $decname = $filename);
1209 warn sprintf "loading %s (%d,%d)\n", 1422 trace sprintf "loading %s (%d,%d)\n",
1210 $decname, length $data, scalar @{$av || []}; 1423 $decname, length $data, scalar @{$av || []}
1424 if $VERBOSE_IO;
1211 1425
1212 ($data, $av) 1426 ($data, $av)
1213} 1427}
1214 1428
1215=head2 COMMAND CALLBACKS 1429=head2 COMMAND CALLBACKS
1219=cut 1433=cut
1220 1434
1221############################################################################# 1435#############################################################################
1222# command handling &c 1436# command handling &c
1223 1437
1224=item cf::register_command $name => \&callback($ob,$args); 1438=item cf::register_command $name => \&callback($ob,$args)
1225 1439
1226Register a callback for execution when the client sends the user command 1440Register a callback for execution when the client sends the user command
1227$name. 1441$name.
1228 1442
1229=cut 1443=cut
1235 #warn "registering command '$name/$time' to '$caller'"; 1449 #warn "registering command '$name/$time' to '$caller'";
1236 1450
1237 push @{ $COMMAND{$name} }, [$caller, $cb]; 1451 push @{ $COMMAND{$name} }, [$caller, $cb];
1238} 1452}
1239 1453
1240=item cf::register_extcmd $name => \&callback($pl,$packet); 1454=item cf::register_extcmd $name => \&callback($pl,@args)
1241 1455
1242Register a callback for execution when the client sends an (synchronous) 1456Register a callback for execution when the client sends an (synchronous)
1243extcmd packet. Ext commands will be processed in the order they are 1457extcmd packet. Ext commands will be processed in the order they are
1244received by the server, like other user commands. The first argument is 1458received by the server, like other user commands. The first argument is
1245the logged-in player. Ext commands can only be processed after a player 1459the logged-in player. Ext commands can only be processed after a player
1246has logged in successfully. 1460has logged in successfully.
1247 1461
1248If the callback returns something, it is sent back as if reply was being 1462The values will be sent back to the client.
1249called.
1250 1463
1464=item cf::register_async_extcmd $name => \&callback($pl,$reply->(...),@args)
1465
1466Same as C<cf::register_extcmd>, but instead of returning values, the
1467callback needs to clal the C<$reply> function.
1468
1251=item cf::register_exticmd $name => \&callback($ns,$packet); 1469=item cf::register_exticmd $name => \&callback($ns,@args)
1252 1470
1253Register a callback for execution when the client sends an (asynchronous) 1471Register a callback for execution when the client sends an (asynchronous)
1254exticmd packet. Exti commands are processed by the server as soon as they 1472exticmd packet. Exti commands are processed by the server as soon as they
1255are received, i.e. out of order w.r.t. other commands. The first argument 1473are received, i.e. out of order w.r.t. other commands. The first argument
1256is a client socket. Exti commands can be received anytime, even before 1474is a client socket. Exti commands can be received anytime, even before
1257log-in. 1475log-in.
1258 1476
1259If the callback returns something, it is sent back as if reply was being 1477The values will be sent back to the client.
1260called.
1261 1478
1262=cut 1479=item cf::register_async_exticmd $name => \&callback($ns,$reply->(...),@args)
1263 1480
1481Same as C<cf::register_extcmd>, but instead of returning values, the
1482callback needs to clal the C<$reply> function.
1483
1484=cut
1485
1264sub register_extcmd { 1486sub register_extcmd($$) {
1265 my ($name, $cb) = @_; 1487 my ($name, $cb) = @_;
1266 1488
1267 $EXTCMD{$name} = $cb; 1489 $EXTCMD{$name} = $cb;
1268} 1490}
1269 1491
1270sub register_exticmd { 1492sub register_async_extcmd($$) {
1271 my ($name, $cb) = @_; 1493 my ($name, $cb) = @_;
1272 1494
1495 $EXTACMD{$name} = $cb;
1496}
1497
1498sub register_exticmd($$) {
1499 my ($name, $cb) = @_;
1500
1273 $EXTICMD{$name} = $cb; 1501 $EXTICMD{$name} = $cb;
1274} 1502}
1275 1503
1504sub register_async_exticmd($$) {
1505 my ($name, $cb) = @_;
1506
1507 $EXTIACMD{$name} = $cb;
1508}
1509
1276use File::Glob (); 1510use File::Glob ();
1277 1511
1278cf::player->attach ( 1512cf::player->attach (
1279 on_command => sub { 1513 on_unknown_command => sub {
1280 my ($pl, $name, $params) = @_; 1514 my ($pl, $name, $params) = @_;
1281 1515
1282 my $cb = $COMMAND{$name} 1516 my $cb = $COMMAND{$name}
1283 or return; 1517 or return;
1284 1518
1292 my ($pl, $buf) = @_; 1526 my ($pl, $buf) = @_;
1293 1527
1294 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) }; 1528 my $msg = eval { $pl->ns->{json_coder}->decode ($buf) };
1295 1529
1296 if (ref $msg) { 1530 if (ref $msg) {
1297 my ($type, $reply, @payload) = 1531 my ($type, $reply, @payload) = @$msg; # version 1 used %type, $id, %$hash
1298 "ARRAY" eq ref $msg
1299 ? @$msg
1300 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
1301 1532
1302 my @reply;
1303
1304 if (my $cb = $EXTCMD{$type}) { 1533 if (my $cb = $EXTACMD{$type}) {
1534 $cb->(
1535 $pl,
1536 sub {
1537 $pl->ext_msg ("reply-$reply", @_)
1538 if $reply;
1539 },
1540 @payload
1541 );
1542 } else {
1543 my @reply;
1544
1545 if (my $cb = $EXTCMD{$type}) {
1305 @reply = $cb->($pl, @payload); 1546 @reply = $cb->($pl, @payload);
1547 }
1548
1549 $pl->ext_msg ("reply-$reply", @reply)
1550 if $reply;
1306 } 1551 }
1307 1552
1308 $pl->ext_reply ($reply, @reply)
1309 if $reply;
1310
1311 } else { 1553 } else {
1312 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n"; 1554 error "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
1313 } 1555 }
1314 1556
1315 cf::override; 1557 cf::override;
1316 }, 1558 },
1317); 1559);
1318 1560
1319# "readahead" all extensions 1561# "readahead" all extensions
1320sub cache_extensions { 1562sub cache_extensions {
1321 my $grp = IO::AIO::aio_group; 1563 my $grp = IO::AIO::aio_group;
1322 1564
1323 add $grp IO::AIO::aio_readdir $LIBDIR, sub { 1565 add $grp IO::AIO::aio_readdirx $LIBDIR, IO::AIO::READDIR_STAT_ORDER, sub {
1324 for (grep /\.ext$/, @{$_[0]}) { 1566 for (grep /\.ext$/, @{$_[0]}) {
1325 add $grp IO::AIO::aio_load "$LIBDIR/$_", my $data; 1567 add $grp IO::AIO::aio_load "$LIBDIR/$_", my $data;
1326 } 1568 }
1327 }; 1569 };
1328 1570
1329 $grp 1571 $grp
1330} 1572}
1331 1573
1574sub _ext_cfg_reg($$$$) {
1575 my ($rvar, $varname, $cfgname, $default) = @_;
1576
1577 $cfgname = lc $varname
1578 unless length $cfgname;
1579
1580 $EXT_CFG{$cfgname} = [$rvar, $default];
1581
1582 $$rvar = exists $CFG{$cfgname} ? $CFG{$cfgname} : $default;
1583}
1584
1332sub load_extensions { 1585sub load_extensions {
1586 info "loading extensions...";
1587
1588 %EXT_CFG = ();
1589
1333 cf::sync_job { 1590 cf::sync_job {
1334 my %todo; 1591 my %todo;
1335 1592
1336 for my $path (<$LIBDIR/*.ext>) { 1593 for my $path (<$LIBDIR/*.ext>) {
1337 next unless -r $path; 1594 next unless -r $path;
1355 1612
1356 $ext{meta} = { map { (split /=/, $_, 2)[0, 1] } split /\s+/, $1 } 1613 $ext{meta} = { map { (split /=/, $_, 2)[0, 1] } split /\s+/, $1 }
1357 if $source =~ /\A#!.*?perl.*?#\s*(.*)$/m; 1614 if $source =~ /\A#!.*?perl.*?#\s*(.*)$/m;
1358 1615
1359 $ext{source} = 1616 $ext{source} =
1360 "package $pkg; use 5.10.0; use strict 'vars', 'subs'; use utf8;\n" 1617 "package $pkg; use common::sense;\n"
1361 . "#line 1 \"$path\"\n{\n" 1618 . "#line 1 \"$path\"\n{\n"
1362 . $source 1619 . $source
1363 . "\n};\n1"; 1620 . "\n};\n1";
1364 1621
1365 $todo{$base} = \%ext; 1622 $todo{$base} = \%ext;
1366 } 1623 }
1367 1624
1625 my $pass = 0;
1368 my %done; 1626 my %done;
1369 while (%todo) { 1627 while (%todo) {
1370 my $progress; 1628 my $progress;
1371 1629
1630 ++$pass;
1631
1632 ext:
1372 while (my ($k, $v) = each %todo) { 1633 while (my ($k, $v) = each %todo) {
1373 for (split /,\s*/, $v->{meta}{depends}) { 1634 for (split /,\s*/, $v->{meta}{depends}) {
1374 goto skip 1635 next ext
1375 unless exists $done{$_}; 1636 unless exists $done{$_};
1376 } 1637 }
1377 1638
1378 warn "... loading '$k' into '$v->{pkg}'\n"; 1639 trace "... pass $pass, loading '$k' into '$v->{pkg}'\n";
1379 1640
1380 unless (eval $v->{source}) { 1641 my $source = $v->{source};
1642
1643 # support "CONF varname :confname = default" pseudo-statements
1644 $source =~ s{
1645 ^ CONF \s+ ([^\s:=]+) \s* (?:: \s* ([^\s:=]+) \s* )? = ([^\n#]+)
1646 }{
1647 "our \$$1; BEGIN { cf::_ext_cfg_reg \\\$$1, q\x00$1\x00, q\x00$2\x00, $3 }";
1648 }gmxe;
1649
1650 my $active = eval $source;
1651
1652 if (length $@) {
1381 my $msg = $@ ? "$v->{path}: $@\n" 1653 error "$v->{path}: $@\n";
1382 : "$v->{base}: extension inactive.\n";
1383 1654
1384 if (exists $v->{meta}{mandatory}) {
1385 warn $msg;
1386 cf::cleanup "mandatory extension failed to load, exiting."; 1655 cf::cleanup "mandatory extension '$k' failed to load, exiting."
1387 } 1656 if exists $v->{meta}{mandatory};
1388 1657
1389 warn $msg; 1658 warn "$v->{base}: optional extension cannot be loaded, skipping.\n";
1659 delete $todo{$k};
1660 } else {
1661 $done{$k} = delete $todo{$k};
1662 push @EXTS, $v->{pkg};
1663 $progress = 1;
1664
1665 info "$v->{base}: extension inactive.\n"
1666 unless $active;
1390 } 1667 }
1391
1392 $done{$k} = delete $todo{$k};
1393 push @EXTS, $v->{pkg};
1394 $progress = 1;
1395 } 1668 }
1396 1669
1397 skip: 1670 unless ($progress) {
1398 die "cannot load " . (join ", ", keys %todo) . ": unable to resolve dependencies\n" 1671 warn "cannot load " . (join ", ", keys %todo) . ": unable to resolve dependencies\n";
1399 unless $progress; 1672
1673 while (my ($k, $v) = each %todo) {
1674 cf::cleanup "mandatory extension '$k' has unresolved dependencies, exiting."
1675 if exists $v->{meta}{mandatory};
1676 }
1677
1678 last;
1679 }
1400 } 1680 }
1401 }; 1681 };
1402} 1682}
1403 1683
1404############################################################################# 1684#############################################################################
1488 $cf::PLAYER{$login} = $pl 1768 $cf::PLAYER{$login} = $pl
1489 } 1769 }
1490 } 1770 }
1491} 1771}
1492 1772
1773cf::player->attach (
1774 on_load => sub {
1775 my ($pl, $path) = @_;
1776
1777 # restore slots saved in save, below
1778 my $slots = delete $pl->{_slots};
1779
1780 $pl->ob->current_weapon ($slots->[0]);
1781 $pl->combat_ob ($slots->[1]);
1782 $pl->ranged_ob ($slots->[2]);
1783 },
1784);
1785
1493sub save($) { 1786sub save($) {
1494 my ($pl) = @_; 1787 my ($pl) = @_;
1495 1788
1496 return if $pl->{deny_save}; 1789 return if $pl->{deny_save};
1497 1790
1502 1795
1503 aio_mkdir playerdir $pl, 0770; 1796 aio_mkdir playerdir $pl, 0770;
1504 $pl->{last_save} = $cf::RUNTIME; 1797 $pl->{last_save} = $cf::RUNTIME;
1505 1798
1506 cf::get_slot 0.01; 1799 cf::get_slot 0.01;
1800
1801 # save slots, to be restored later
1802 local $pl->{_slots} = [$pl->ob->current_weapon, $pl->combat_ob, $pl->ranged_ob];
1507 1803
1508 $pl->save_pl ($path); 1804 $pl->save_pl ($path);
1509 cf::cede_to_tick; 1805 cf::cede_to_tick;
1510} 1806}
1511 1807
1545 my $name = $pl->ob->name; 1841 my $name = $pl->ob->name;
1546 1842
1547 $pl->{deny_save} = 1; 1843 $pl->{deny_save} = 1;
1548 $pl->password ("*"); # this should lock out the player until we have nuked the dir 1844 $pl->password ("*"); # this should lock out the player until we have nuked the dir
1549 1845
1550 $pl->invoke (cf::EVENT_PLAYER_LOGOUT, 1) if $pl->active; 1846 $pl->invoke (cf::EVENT_PLAYER_LOGOUT, 1) if $pl->ns;
1551 $pl->deactivate; 1847 $pl->deactivate;
1848
1552 my $killer = cf::arch::get "killer_quit"; $pl->killer ($killer); $killer->destroy; 1849 my $killer = cf::arch::get "killer_quit"; $pl->killer ($killer); $killer->destroy;
1553 $pl->ob->check_score;
1554 $pl->invoke (cf::EVENT_PLAYER_QUIT); 1850 $pl->invoke (cf::EVENT_PLAYER_QUIT) if $pl->ns;
1851 ext::highscore::check ($pl->ob);
1852
1555 $pl->ns->destroy if $pl->ns; 1853 $pl->ns->destroy if $pl->ns;
1556 1854
1557 my $path = playerdir $pl; 1855 my $path = playerdir $pl;
1558 my $temp = "$path~$cf::RUNTIME~deleting~"; 1856 my $temp = "$path~$cf::RUNTIME~deleting~";
1559 aio_rename $path, $temp; 1857 aio_rename $path, $temp;
1613 \@logins 1911 \@logins
1614} 1912}
1615 1913
1616=item $player->maps 1914=item $player->maps
1617 1915
1916=item cf::player::maps $login
1917
1618Returns an arrayref of map paths that are private for this 1918Returns an arrayref of map paths that are private for this
1619player. May block. 1919player. May block.
1620 1920
1621=cut 1921=cut
1622 1922
1643 1943
1644=item $protocol_xml = $player->expand_cfpod ($cfpod) 1944=item $protocol_xml = $player->expand_cfpod ($cfpod)
1645 1945
1646Expand deliantra pod fragments into protocol xml. 1946Expand deliantra pod fragments into protocol xml.
1647 1947
1648=item $player->ext_reply ($msgid, @msg)
1649
1650Sends an ext reply to the player.
1651
1652=cut
1653
1654sub ext_reply($$@) {
1655 my ($self, $id, @msg) = @_;
1656
1657 $self->ns->ext_reply ($id, @msg)
1658}
1659
1660=item $player->ext_msg ($type, @msg) 1948=item $player->ext_msg ($type, @msg)
1661 1949
1662Sends an ext event to the client. 1950Sends an ext event to the client.
1663 1951
1664=cut 1952=cut
1683 1971
1684=cut 1972=cut
1685 1973
1686sub find_by_path($) { 1974sub find_by_path($) {
1687 my ($path) = @_; 1975 my ($path) = @_;
1976
1977 $path =~ s/^~[^\/]*//; # skip ~login
1688 1978
1689 my ($match, $specificity); 1979 my ($match, $specificity);
1690 1980
1691 for my $region (list) { 1981 for my $region (list) {
1692 if ($region->{match} && $path =~ $region->{match}) { 1982 if ($region->{match} && $path =~ $region->{match}) {
1721sub generate_random_map { 2011sub generate_random_map {
1722 my ($self, $rmp) = @_; 2012 my ($self, $rmp) = @_;
1723 2013
1724 my $lock = cf::lock_acquire "generate_random_map"; # the random map generator is NOT reentrant ATM 2014 my $lock = cf::lock_acquire "generate_random_map"; # the random map generator is NOT reentrant ATM
1725 2015
1726 # mit "rum" bekleckern, nicht
1727 $self->_create_random_map ( 2016 $self->_create_random_map ($rmp);
1728 $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
1729 $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
1730 $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
1731 $rmp->{exit_on_final_map},
1732 $rmp->{xsize}, $rmp->{ysize},
1733 $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
1734 $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
1735 $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
1736 $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
1737 $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
1738 (cf::region::find $rmp->{region}), $rmp->{custom}
1739 )
1740} 2017}
1741 2018
1742=item cf::map->register ($regex, $prio) 2019=item cf::map->register ($regex, $prio)
1743 2020
1744Register a handler for the map path matching the given regex at the 2021Register a handler for the map path matching the given regex at the
1749 2026
1750sub register { 2027sub register {
1751 my (undef, $regex, $prio) = @_; 2028 my (undef, $regex, $prio) = @_;
1752 my $pkg = caller; 2029 my $pkg = caller;
1753 2030
1754 no strict;
1755 push @{"$pkg\::ISA"}, __PACKAGE__; 2031 push @{"$pkg\::ISA"}, __PACKAGE__;
1756 2032
1757 $EXT_MAP{$pkg} = [$prio, qr<$regex>]; 2033 $EXT_MAP{$pkg} = [$prio, qr<$regex>];
1758} 2034}
1759 2035
1760# also paths starting with '/' 2036# also paths starting with '/'
1761$EXT_MAP{"cf::map"} = [0, qr{^(?=/)}]; 2037$EXT_MAP{"cf::map::wrap"} = [0, qr{^(?=/)}];
1762 2038
1763sub thawer_merge { 2039sub thawer_merge {
1764 my ($self, $merge) = @_; 2040 my ($self, $merge) = @_;
1765 2041
1766 # we have to keep some variables in memory intact 2042 # we have to keep some variables in memory intact
1771} 2047}
1772 2048
1773sub normalise { 2049sub normalise {
1774 my ($path, $base) = @_; 2050 my ($path, $base) = @_;
1775 2051
1776 $path = "$path"; # make sure its a string 2052 $path = "$path"; # make sure it's a string
1777 2053
1778 $path =~ s/\.map$//; 2054 $path =~ s/\.map$//;
1779 2055
1780 # map plan: 2056 # map plan:
1781 # 2057 #
1796 $base =~ s{[^/]+/?$}{}; 2072 $base =~ s{[^/]+/?$}{};
1797 $path = "$base/$path"; 2073 $path = "$base/$path";
1798 } 2074 }
1799 2075
1800 for ($path) { 2076 for ($path) {
1801 redo if s{//}{/};
1802 redo if s{/\.?/}{/}; 2077 redo if s{/\.?/}{/};
1803 redo if s{/[^/]+/\.\./}{/}; 2078 redo if s{/[^/]+/\.\./}{/};
1804 } 2079 }
1805 2080
1806 $path 2081 $path
1820 $self->init; # pass $1 etc. 2095 $self->init; # pass $1 etc.
1821 return $self; 2096 return $self;
1822 } 2097 }
1823 } 2098 }
1824 2099
1825 Carp::cluck "unable to resolve path '$path' (base '$base')."; 2100 Carp::cluck "unable to resolve path '$path' (base '$base')";
1826 () 2101 ()
1827} 2102}
1828 2103
2104# may re-bless or do other evil things
1829sub init { 2105sub init {
1830 my ($self) = @_; 2106 my ($self) = @_;
1831 2107
1832 $self 2108 $self
1833} 2109}
1898 $self->{load_path} = $path; 2174 $self->{load_path} = $path;
1899 2175
1900 1 2176 1
1901} 2177}
1902 2178
2179# used to laod the header of an original map
1903sub load_header_orig { 2180sub load_header_orig {
1904 my ($self) = @_; 2181 my ($self) = @_;
1905 2182
1906 $self->load_header_from ($self->load_path) 2183 $self->load_header_from ($self->load_path)
1907} 2184}
1908 2185
2186# used to laod the header of an instantiated map
1909sub load_header_temp { 2187sub load_header_temp {
1910 my ($self) = @_; 2188 my ($self) = @_;
1911 2189
1912 $self->load_header_from ($self->save_path) 2190 $self->load_header_from ($self->save_path)
1913} 2191}
1914 2192
2193# called after loading the header from an instantiated map
1915sub prepare_temp { 2194sub prepare_temp {
1916 my ($self) = @_; 2195 my ($self) = @_;
1917 2196
1918 $self->last_access ((delete $self->{last_access}) 2197 $self->last_access ((delete $self->{last_access})
1919 || $cf::RUNTIME); #d# 2198 || $cf::RUNTIME); #d#
1920 # safety 2199 # safety
1921 $self->{instantiate_time} = $cf::RUNTIME 2200 $self->{instantiate_time} = $cf::RUNTIME
1922 if $self->{instantiate_time} > $cf::RUNTIME; 2201 if $self->{instantiate_time} > $cf::RUNTIME;
1923} 2202}
1924 2203
2204# called after loading the header from an original map
1925sub prepare_orig { 2205sub prepare_orig {
1926 my ($self) = @_; 2206 my ($self) = @_;
1927 2207
1928 $self->{load_original} = 1; 2208 $self->{load_original} = 1;
1929 $self->{instantiate_time} = $cf::RUNTIME; 2209 $self->{instantiate_time} = $cf::RUNTIME;
1953 2233
1954sub find; 2234sub find;
1955sub find { 2235sub find {
1956 my ($path, $origin) = @_; 2236 my ($path, $origin) = @_;
1957 2237
2238 cf::cede_to_tick;
2239
1958 $path = normalise $path, $origin && $origin->path; 2240 $path = normalise $path, $origin;
1959 2241
1960 my $guard1 = cf::lock_acquire "map_data:$path";#d#remove 2242 my $guard1 = cf::lock_acquire "map_data:$path";#d#remove
1961 my $guard2 = cf::lock_acquire "map_find:$path"; 2243 my $guard2 = cf::lock_acquire "map_find:$path";
1962 2244
1963 $cf::MAP{$path} || do { 2245 $cf::MAP{$path} || do {
1980 2262
1981 $cf::MAP{$path} = $map 2263 $cf::MAP{$path} = $map
1982 } 2264 }
1983} 2265}
1984 2266
1985sub pre_load { } 2267sub pre_load { }
1986sub post_load { } 2268#sub post_load { } # XS
1987 2269
1988sub load { 2270sub load {
1989 my ($self) = @_; 2271 my ($self) = @_;
1990 2272
1991 local $self->{deny_reset} = 1; # loading can take a long time 2273 local $self->{deny_reset} = 1; # loading can take a long time
1994 2276
1995 { 2277 {
1996 my $guard = cf::lock_acquire "map_data:$path"; 2278 my $guard = cf::lock_acquire "map_data:$path";
1997 2279
1998 return unless $self->valid; 2280 return unless $self->valid;
1999 return unless $self->in_memory == cf::MAP_SWAPPED; 2281 return unless $self->state == cf::MAP_SWAPPED;
2000
2001 $self->in_memory (cf::MAP_LOADING);
2002 2282
2003 $self->alloc; 2283 $self->alloc;
2004 2284
2005 $self->pre_load; 2285 $self->pre_load;
2006 cf::cede_to_tick; 2286 cf::cede_to_tick;
2007 2287
2288 if (exists $self->{load_path}) {
2008 my $f = new_from_file cf::object::thawer $self->{load_path}; 2289 my $f = new_from_file cf::object::thawer $self->{load_path};
2009 $f->skip_block; 2290 $f->skip_block;
2010 $self->_load_objects ($f) 2291 $self->_load_objects ($f)
2011 or return; 2292 or return;
2012 2293
2013 $self->post_load_original 2294 $self->post_load_original
2014 if delete $self->{load_original}; 2295 if delete $self->{load_original};
2015 2296
2016 if (my $uniq = $self->uniq_path) { 2297 if (my $uniq = $self->uniq_path) {
2017 utf8::encode $uniq; 2298 utf8::encode $uniq;
2018 unless (aio_stat $uniq) { 2299 unless (aio_stat $uniq) {
2019 if (my $f = new_from_file cf::object::thawer $uniq) { 2300 if (my $f = new_from_file cf::object::thawer $uniq) {
2020 $self->clear_unique_items; 2301 $self->clear_unique_items;
2021 $self->_load_objects ($f); 2302 $self->_load_objects ($f);
2022 $f->resolve_delayed_derefs; 2303 $f->resolve_delayed_derefs;
2304 }
2023 } 2305 }
2024 } 2306 }
2025 }
2026 2307
2027 $f->resolve_delayed_derefs; 2308 $f->resolve_delayed_derefs;
2309 } else {
2310 $self->post_load_original
2311 if delete $self->{load_original};
2312 }
2313
2314 $self->state (cf::MAP_INACTIVE);
2028 2315
2029 cf::cede_to_tick; 2316 cf::cede_to_tick;
2030 # now do the right thing for maps 2317 # now do the right thing for maps
2031 $self->link_multipart_objects; 2318 $self->link_multipart_objects;
2032 $self->difficulty ($self->estimate_difficulty) 2319 $self->difficulty ($self->estimate_difficulty)
2036 unless ($self->{deny_activate}) { 2323 unless ($self->{deny_activate}) {
2037 $self->decay_objects; 2324 $self->decay_objects;
2038 $self->fix_auto_apply; 2325 $self->fix_auto_apply;
2039 $self->update_buttons; 2326 $self->update_buttons;
2040 cf::cede_to_tick; 2327 cf::cede_to_tick;
2041 $self->activate; 2328 #$self->activate; # no longer activate maps automatically
2042 } 2329 }
2043 2330
2044 $self->{last_save} = $cf::RUNTIME; 2331 $self->{last_save} = $cf::RUNTIME;
2045 $self->last_access ($cf::RUNTIME); 2332 $self->last_access ($cf::RUNTIME);
2046
2047 $self->in_memory (cf::MAP_ACTIVE);
2048 } 2333 }
2049 2334
2050 $self->post_load; 2335 $self->post_load;
2051}
2052 2336
2337 1
2338}
2339
2340# customize the map for a given player, i.e.
2341# return the _real_ map. used by e.g. per-player
2342# maps to change the path to ~playername/mappath
2053sub customise_for { 2343sub customise_for {
2054 my ($self, $ob) = @_; 2344 my ($self, $ob) = @_;
2055 2345
2056 return find "~" . $ob->name . "/" . $self->{path} 2346 return find "~" . $ob->name . "/" . $self->{path}
2057 if $self->per_player; 2347 if $self->per_player;
2060# if $self->per_party; 2350# if $self->per_party;
2061 2351
2062 $self 2352 $self
2063} 2353}
2064 2354
2065# find and load all maps in the 3x3 area around a map
2066sub load_neighbours {
2067 my ($map) = @_;
2068
2069 my @neigh; # diagonal neighbours
2070
2071 for (0 .. 3) {
2072 my $neigh = $map->tile_path ($_)
2073 or next;
2074 $neigh = find $neigh, $map
2075 or next;
2076 $neigh->load;
2077
2078 push @neigh,
2079 [$neigh->tile_path (($_ + 3) % 4), $neigh],
2080 [$neigh->tile_path (($_ + 1) % 4), $neigh];
2081 }
2082
2083 for (grep defined $_->[0], @neigh) {
2084 my ($path, $origin) = @$_;
2085 my $neigh = find $path, $origin
2086 or next;
2087 $neigh->load;
2088 }
2089}
2090
2091sub find_sync { 2355sub find_sync {
2092 my ($path, $origin) = @_; 2356 my ($path, $origin) = @_;
2093 2357
2094 cf::sync_job { find $path, $origin } 2358 # it's a bug to call this from the main context
2359 return cf::LOG cf::llevError | cf::logBacktrace, "do_find_sync"
2360 if $Coro::current == $Coro::main;
2361
2362 find $path, $origin
2095} 2363}
2096 2364
2097sub do_load_sync { 2365sub do_load_sync {
2098 my ($map) = @_; 2366 my ($map) = @_;
2099 2367
2368 # it's a bug to call this from the main context
2100 cf::LOG cf::llevDebug | cf::logBacktrace, "do_load_sync" 2369 return cf::LOG cf::llevError | cf::logBacktrace, "do_load_sync"
2101 if $Coro::current == $Coro::main; 2370 if $Coro::current == $Coro::main;
2102 2371
2103 cf::sync_job { $map->load }; 2372 $map->load;
2104} 2373}
2105 2374
2106our %MAP_PREFETCH; 2375our %MAP_PREFETCH;
2107our $MAP_PREFETCHER = undef; 2376our $MAP_PREFETCHER = undef;
2108 2377
2109sub find_async { 2378sub find_async {
2110 my ($path, $origin, $load) = @_; 2379 my ($path, $origin, $load) = @_;
2111 2380
2112 $path = normalise $path, $origin && $origin->{path}; 2381 $path = normalise $path, $origin;
2113 2382
2114 if (my $map = $cf::MAP{$path}) { 2383 if (my $map = $cf::MAP{$path}) {
2115 return $map if !$load || $map->in_memory == cf::MAP_ACTIVE; 2384 return $map if !$load || $map->linkable;
2116 } 2385 }
2117 2386
2118 $MAP_PREFETCH{$path} |= $load; 2387 $MAP_PREFETCH{$path} |= $load;
2119 2388
2120 $MAP_PREFETCHER ||= cf::async { 2389 $MAP_PREFETCHER ||= cf::async {
2134 $MAP_PREFETCHER->prio (6); 2403 $MAP_PREFETCHER->prio (6);
2135 2404
2136 () 2405 ()
2137} 2406}
2138 2407
2408# common code, used by both ->save and ->swapout
2139sub save { 2409sub _save {
2140 my ($self) = @_; 2410 my ($self) = @_;
2141
2142 my $lock = cf::lock_acquire "map_data:$self->{path}";
2143 2411
2144 $self->{last_save} = $cf::RUNTIME; 2412 $self->{last_save} = $cf::RUNTIME;
2145 2413
2146 return unless $self->dirty; 2414 return unless $self->dirty;
2147 2415
2167 } else { 2435 } else {
2168 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES); 2436 $self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
2169 } 2437 }
2170} 2438}
2171 2439
2440sub save {
2441 my ($self) = @_;
2442
2443 my $lock = cf::lock_acquire "map_data:$self->{path}";
2444
2445 $self->_save;
2446}
2447
2172sub swap_out { 2448sub swap_out {
2173 my ($self) = @_; 2449 my ($self) = @_;
2174 2450
2175 # save first because save cedes
2176 $self->save;
2177
2178 my $lock = cf::lock_acquire "map_data:$self->{path}"; 2451 my $lock = cf::lock_acquire "map_data:$self->{path}";
2179 2452
2453 return if !$self->linkable;
2454 return if $self->{deny_save};
2180 return if $self->players; 2455 return if $self->players;
2181 return if $self->in_memory != cf::MAP_ACTIVE; 2456
2457 # first deactivate the map and "unlink" it from the core
2458 $self->deactivate;
2459 $_->clear_links_to ($self) for values %cf::MAP;
2460 $self->state (cf::MAP_SWAPPED);
2461
2462 # then atomically save
2463 $self->_save;
2464
2465 # then free the map
2466 $self->clear;
2467}
2468
2469sub reset_at {
2470 my ($self) = @_;
2471
2472 # TODO: safety, remove and allow resettable per-player maps
2182 return if $self->{deny_save}; 2473 return 1e99 if $self->{deny_reset};
2183 2474
2475 my $time = $self->fixed_resettime ? $self->{instantiate_time} : $self->last_access;
2476 my $to = List::Util::min $MAX_RESET, $self->reset_timeout || $DEFAULT_RESET;
2477
2478 $time + $to
2479}
2480
2481sub should_reset {
2482 my ($self) = @_;
2483
2484 $self->reset_at <= $cf::RUNTIME
2485}
2486
2487sub reset {
2488 my ($self) = @_;
2489
2490 my $lock = cf::lock_acquire "map_data:$self->{path}";
2491
2492 return if $self->players;
2493
2494 cf::trace "resetting map ", $self->path, "\n";
2495
2184 $self->in_memory (cf::MAP_SWAPPED); 2496 $self->state (cf::MAP_SWAPPED);
2497
2498 # need to save uniques path
2499 unless ($self->{deny_save}) {
2500 my $uniq = $self->uniq_path; utf8::encode $uniq;
2501
2502 $self->_save_objects ($uniq, cf::IO_UNIQUES)
2503 if $uniq;
2504 }
2505
2506 delete $cf::MAP{$self->path};
2185 2507
2186 $self->deactivate; 2508 $self->deactivate;
2187 $_->clear_links_to ($self) for values %cf::MAP; 2509 $_->clear_links_to ($self) for values %cf::MAP;
2188 $self->clear; 2510 $self->clear;
2189}
2190
2191sub reset_at {
2192 my ($self) = @_;
2193
2194 # TODO: safety, remove and allow resettable per-player maps
2195 return 1e99 if $self->{deny_reset};
2196
2197 my $time = $self->fixed_resettime ? $self->{instantiate_time} : $self->last_access;
2198 my $to = List::Util::min $MAX_RESET, $self->reset_timeout || $DEFAULT_RESET;
2199
2200 $time + $to
2201}
2202
2203sub should_reset {
2204 my ($self) = @_;
2205
2206 $self->reset_at <= $cf::RUNTIME
2207}
2208
2209sub reset {
2210 my ($self) = @_;
2211
2212 my $lock = cf::lock_acquire "map_data:$self->{path}";
2213
2214 return if $self->players;
2215
2216 warn "resetting map ", $self->path;
2217
2218 $self->in_memory (cf::MAP_SWAPPED);
2219
2220 # need to save uniques path
2221 unless ($self->{deny_save}) {
2222 my $uniq = $self->uniq_path; utf8::encode $uniq;
2223
2224 $self->_save_objects ($uniq, cf::IO_UNIQUES)
2225 if $uniq;
2226 }
2227
2228 delete $cf::MAP{$self->path};
2229
2230 $self->deactivate;
2231 $_->clear_links_to ($self) for values %cf::MAP;
2232 $self->clear;
2233 2511
2234 $self->unlink_save; 2512 $self->unlink_save;
2235 $self->destroy; 2513 $self->destroy;
2236} 2514}
2237 2515
2245 2523
2246 delete $cf::MAP{$self->path}; 2524 delete $cf::MAP{$self->path};
2247 2525
2248 $self->unlink_save; 2526 $self->unlink_save;
2249 2527
2250 bless $self, "cf::map"; 2528 bless $self, "cf::map::wrap";
2251 delete $self->{deny_reset}; 2529 delete $self->{deny_reset};
2252 $self->{deny_save} = 1; 2530 $self->{deny_save} = 1;
2253 $self->reset_timeout (1); 2531 $self->reset_timeout (1);
2254 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++)); 2532 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++));
2255 2533
2312 : normalise $_ 2590 : normalise $_
2313 } @{ aio_readdir $UNIQUEDIR or [] } 2591 } @{ aio_readdir $UNIQUEDIR or [] }
2314 ] 2592 ]
2315} 2593}
2316 2594
2595=item cf::map::static_maps
2596
2597Returns an arrayref if paths of all static maps (all preinstalled F<.map>
2598file in the shared directory excluding F</styles> and F</editor>). May
2599block.
2600
2601=cut
2602
2603sub static_maps() {
2604 my @dirs = "";
2605 my @maps;
2606
2607 while (@dirs) {
2608 my $dir = shift @dirs;
2609
2610 next if $dir eq "/styles" || $dir eq "/editor";
2611
2612 my ($dirs, $files) = Coro::AIO::aio_scandir "$MAPDIR$dir", 2
2613 or return;
2614
2615 for (@$files) {
2616 s/\.map$// or next;
2617 utf8::decode $_;
2618 push @maps, "$dir/$_";
2619 }
2620
2621 push @dirs, map "$dir/$_", @$dirs;
2622 }
2623
2624 \@maps
2625}
2626
2317=back 2627=back
2318 2628
2319=head3 cf::object 2629=head3 cf::object
2320 2630
2321=cut 2631=cut
2386 2696
2387our $SAY_CHANNEL = { 2697our $SAY_CHANNEL = {
2388 id => "say", 2698 id => "say",
2389 title => "Map", 2699 title => "Map",
2390 reply => "say ", 2700 reply => "say ",
2391 tooltip => "Things said to and replied from npcs near you and other players on the same map only.", 2701 tooltip => "Things said to and replied from NPCs near you and other players on the same map only.",
2392}; 2702};
2393 2703
2394our $CHAT_CHANNEL = { 2704our $CHAT_CHANNEL = {
2395 id => "chat", 2705 id => "chat",
2396 title => "Chat", 2706 title => "Chat",
2453 2763
2454Freezes the player and moves him/her to a special map (C<{link}>). 2764Freezes the player and moves him/her to a special map (C<{link}>).
2455 2765
2456The player should be reasonably safe there for short amounts of time (e.g. 2766The player should be reasonably safe there for short amounts of time (e.g.
2457for loading a map). You I<MUST> call C<leave_link> as soon as possible, 2767for loading a map). You I<MUST> call C<leave_link> as soon as possible,
2458though, as the palyer cannot control the character while it is on the link 2768though, as the player cannot control the character while it is on the link
2459map. 2769map.
2460 2770
2461Will never block. 2771Will never block.
2462 2772
2463=item $player_object->leave_link ($map, $x, $y) 2773=item $player_object->leave_link ($map, $x, $y)
2484sub cf::object::player::enter_link { 2794sub cf::object::player::enter_link {
2485 my ($self) = @_; 2795 my ($self) = @_;
2486 2796
2487 $self->deactivate_recursive; 2797 $self->deactivate_recursive;
2488 2798
2799 ++$self->{_link_recursion};
2800
2489 return if UNIVERSAL::isa $self->map, "ext::map_link"; 2801 return if UNIVERSAL::isa $self->map, "ext::map_link";
2490 2802
2491 $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y] 2803 $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y]
2492 if $self->map && $self->map->{path} ne "{link}"; 2804 if $self->map && $self->map->{path} ne "{link}";
2493 2805
2494 $self->enter_map ($LINK_MAP || link_map, 10, 10); 2806 $self->enter_map ($LINK_MAP || link_map, 3, 3);
2495} 2807}
2496 2808
2497sub cf::object::player::leave_link { 2809sub cf::object::player::leave_link {
2498 my ($self, $map, $x, $y) = @_; 2810 my ($self, $map, $x, $y) = @_;
2499 2811
2516 ($x, $y) = (-1, -1) 2828 ($x, $y) = (-1, -1)
2517 unless (defined $x) && (defined $y); 2829 unless (defined $x) && (defined $y);
2518 2830
2519 # use -1 or undef as default coordinates, not 0, 0 2831 # use -1 or undef as default coordinates, not 0, 0
2520 ($x, $y) = ($map->enter_x, $map->enter_y) 2832 ($x, $y) = ($map->enter_x, $map->enter_y)
2521 if $x <=0 && $y <= 0; 2833 if $x <= 0 && $y <= 0;
2522 2834
2523 $map->load; 2835 $map->load;
2524 $map->load_neighbours;
2525 2836
2526 return unless $self->contr->active; 2837 return unless $self->contr->active;
2527 $self->flag (cf::FLAG_DEBUG, 0);#d# temp
2528 $self->activate_recursive;
2529 2838
2530 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext 2839 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext
2531 $self->enter_map ($map, $x, $y); 2840 if ($self->enter_map ($map, $x, $y)) {
2532} 2841 # entering was successful
2842 delete $self->{_link_recursion};
2843 # only activate afterwards, to support waiting in hooks
2844 $self->activate_recursive;
2845 }
2533 2846
2847}
2848
2534=item $player_object->goto ($path, $x, $y[, $check->($map)[, $done->()]]) 2849=item $player_object->goto ($path, $x, $y[, $check->($map, $x, $y, $player)[, $done->($player)]])
2535 2850
2536Moves the player to the given map-path and coordinates by first freezing 2851Moves the player to the given map-path and coordinates by first freezing
2537her, loading and preparing them map, calling the provided $check callback 2852her, loading and preparing them map, calling the provided $check callback
2538that has to return the map if sucecssful, and then unfreezes the player on 2853that has to return the map if sucecssful, and then unfreezes the player on
2539the new (success) or old (failed) map position. In either case, $done will 2854the new (success) or old (failed) map position. In either case, $done will
2546 2861
2547our $GOTOGEN; 2862our $GOTOGEN;
2548 2863
2549sub cf::object::player::goto { 2864sub cf::object::player::goto {
2550 my ($self, $path, $x, $y, $check, $done) = @_; 2865 my ($self, $path, $x, $y, $check, $done) = @_;
2866
2867 if ($self->{_link_recursion} >= $MAX_LINKS) {
2868 error "FATAL: link recursion exceeded, ", $self->name, " goto $path $x $y, redirecting.";
2869 $self->failmsg ("Something went wrong inside the server - please contact an administrator!");
2870 ($path, $x, $y) = @$EMERGENCY_POSITION;
2871 }
2551 2872
2552 # do generation counting so two concurrent goto's will be executed in-order 2873 # do generation counting so two concurrent goto's will be executed in-order
2553 my $gen = $self->{_goto_generation} = ++$GOTOGEN; 2874 my $gen = $self->{_goto_generation} = ++$GOTOGEN;
2554 2875
2555 $self->enter_link; 2876 $self->enter_link;
2575 ($path, $x, $y) = (undef, undef, undef); 2896 ($path, $x, $y) = (undef, undef, undef);
2576 } 2897 }
2577 } 2898 }
2578 2899
2579 my $map = eval { 2900 my $map = eval {
2580 my $map = defined $path ? cf::map::find $path : undef; 2901 my $map = defined $path ? cf::map::find $path, $self->map : undef;
2581 2902
2582 if ($map) { 2903 if ($map) {
2583 $map = $map->customise_for ($self); 2904 $map = $map->customise_for ($self);
2584 $map = $check->($map) if $check && $map; 2905 $map = $check->($map, $x, $y, $self) if $check && $map;
2585 } else { 2906 } else {
2586 $self->message ("The exit to '$path' is closed.", cf::NDI_UNIQUE | cf::NDI_RED); 2907 $self->message ("The exit to '$path' is closed.", cf::NDI_UNIQUE | cf::NDI_RED);
2587 } 2908 }
2588 2909
2589 $map 2910 $map
2597 if ($gen == $self->{_goto_generation}) { 2918 if ($gen == $self->{_goto_generation}) {
2598 delete $self->{_goto_generation}; 2919 delete $self->{_goto_generation};
2599 $self->leave_link ($map, $x, $y); 2920 $self->leave_link ($map, $x, $y);
2600 } 2921 }
2601 2922
2602 $done->() if $done; 2923 $done->($self) if $done;
2603 })->prio (1); 2924 })->prio (1);
2604} 2925}
2605 2926
2606=item $player_object->enter_exit ($exit_object) 2927=item $player_object->enter_exit ($exit_object)
2607 2928
2675 $Coro::current->{desc} = "enter_exit"; 2996 $Coro::current->{desc} = "enter_exit";
2676 2997
2677 unless (eval { 2998 unless (eval {
2678 $self->deactivate_recursive; # just to be sure 2999 $self->deactivate_recursive; # just to be sure
2679 3000
2680 # random map handling
2681 {
2682 my $guard = cf::lock_acquire "exit_prepare:$exit";
2683
2684 prepare_random_map $exit
2685 if $exit->slaying eq "/!";
2686 }
2687
2688 my $map = cf::map::normalise $exit->slaying, $exit->map && $exit->map->path; 3001 my $map = cf::map::normalise $exit->slaying, $exit->map;
2689 my $x = $exit->stats->hp; 3002 my $x = $exit->stats->hp;
2690 my $y = $exit->stats->sp; 3003 my $y = $exit->stats->sp;
3004
3005 # special map handling
3006 my $slaying = $exit->slaying;
3007
3008 # special map handling
3009 if ($slaying eq "/!") {
3010 my $guard = cf::lock_acquire "exit_prepare:$exit";
3011
3012 prepare_random_map $exit
3013 if $exit->slaying eq "/!"; # need to re-check after getting the lock
3014
3015 $map = $exit->slaying;
3016
3017 } elsif ($slaying eq '!up') {
3018 $map = $exit->map->tile_path (cf::TILE_UP);
3019 $x = $exit->x;
3020 $y = $exit->y;
3021
3022 } elsif ($slaying eq '!down') {
3023 $map = $exit->map->tile_path (cf::TILE_DOWN);
3024 $x = $exit->x;
3025 $y = $exit->y;
3026 }
2691 3027
2692 $self->goto ($map, $x, $y); 3028 $self->goto ($map, $x, $y);
2693 3029
2694 # if exit is damned, update players death & WoR home-position 3030 # if exit is damned, update players death & WoR home-position
2695 $self->contr->savebed ($map, $x, $y) 3031 $self->contr->savebed ($map, $x, $y)
2700 $self->message ("Something went wrong deep within the deliantra server. " 3036 $self->message ("Something went wrong deep within the deliantra server. "
2701 . "I'll try to bring you back to the map you were before. " 3037 . "I'll try to bring you back to the map you were before. "
2702 . "Please report this to the dungeon master!", 3038 . "Please report this to the dungeon master!",
2703 cf::NDI_UNIQUE | cf::NDI_RED); 3039 cf::NDI_UNIQUE | cf::NDI_RED);
2704 3040
2705 warn "ERROR in enter_exit: $@"; 3041 error "ERROR in enter_exit: $@";
2706 $self->leave_link; 3042 $self->leave_link;
2707 } 3043 }
2708 })->prio (1); 3044 })->prio (1);
2709} 3045}
2710 3046
2722sub cf::client::send_drawinfo { 3058sub cf::client::send_drawinfo {
2723 my ($self, $text, $flags) = @_; 3059 my ($self, $text, $flags) = @_;
2724 3060
2725 utf8::encode $text; 3061 utf8::encode $text;
2726 $self->send_packet (sprintf "drawinfo %d %s", $flags || cf::NDI_BLACK, $text); 3062 $self->send_packet (sprintf "drawinfo %d %s", $flags || cf::NDI_BLACK, $text);
3063}
3064
3065=item $client->send_big_packet ($pkt)
3066
3067Like C<send_packet>, but tries to compress large packets, and fragments
3068them as required.
3069
3070=cut
3071
3072our $MAXFRAGSIZE = cf::MAXSOCKBUF - 64;
3073
3074sub cf::client::send_big_packet {
3075 my ($self, $pkt) = @_;
3076
3077 # try lzf for large packets
3078 $pkt = "lzf " . Compress::LZF::compress $pkt
3079 if 1024 <= length $pkt and $self->{can_lzf};
3080
3081 # split very large packets
3082 if ($MAXFRAGSIZE < length $pkt and $self->{can_lzf}) {
3083 $self->send_packet ("frag $_") for unpack "(a$MAXFRAGSIZE)*", $pkt;
3084 $pkt = "frag";
3085 }
3086
3087 $self->send_packet ($pkt);
2727} 3088}
2728 3089
2729=item $client->send_msg ($channel, $msg, $color, [extra...]) 3090=item $client->send_msg ($channel, $msg, $color, [extra...])
2730 3091
2731Send a drawinfo or msg packet to the client, formatting the msg for the 3092Send a drawinfo or msg packet to the client, formatting the msg for the
2735 3096
2736=cut 3097=cut
2737 3098
2738# non-persistent channels (usually the info channel) 3099# non-persistent channels (usually the info channel)
2739our %CHANNEL = ( 3100our %CHANNEL = (
3101 "c/motd" => {
3102 id => "infobox",
3103 title => "MOTD",
3104 reply => undef,
3105 tooltip => "The message of the day",
3106 },
2740 "c/identify" => { 3107 "c/identify" => {
2741 id => "infobox", 3108 id => "infobox",
2742 title => "Identify", 3109 title => "Identify",
2743 reply => undef, 3110 reply => undef,
2744 tooltip => "Items recently identified", 3111 tooltip => "Items recently identified",
2746 "c/examine" => { 3113 "c/examine" => {
2747 id => "infobox", 3114 id => "infobox",
2748 title => "Examine", 3115 title => "Examine",
2749 reply => undef, 3116 reply => undef,
2750 tooltip => "Signs and other items you examined", 3117 tooltip => "Signs and other items you examined",
3118 },
3119 "c/shopinfo" => {
3120 id => "infobox",
3121 title => "Shop Info",
3122 reply => undef,
3123 tooltip => "What your bargaining skill tells you about the shop",
2751 }, 3124 },
2752 "c/book" => { 3125 "c/book" => {
2753 id => "infobox", 3126 id => "infobox",
2754 title => "Book", 3127 title => "Book",
2755 reply => undef, 3128 reply => undef,
2783 id => "infobox", 3156 id => "infobox",
2784 title => "Skills", 3157 title => "Skills",
2785 reply => undef, 3158 reply => undef,
2786 tooltip => "Shows your experience per skill and item power", 3159 tooltip => "Shows your experience per skill and item power",
2787 }, 3160 },
3161 "c/shopitems" => {
3162 id => "infobox",
3163 title => "Shop Items",
3164 reply => undef,
3165 tooltip => "Shows the items currently for sale in this shop",
3166 },
2788 "c/resistances" => { 3167 "c/resistances" => {
2789 id => "infobox", 3168 id => "infobox",
2790 title => "Resistances", 3169 title => "Resistances",
2791 reply => undef, 3170 reply => undef,
2792 tooltip => "Shows your resistances", 3171 tooltip => "Shows your resistances",
2794 "c/pets" => { 3173 "c/pets" => {
2795 id => "infobox", 3174 id => "infobox",
2796 title => "Pets", 3175 title => "Pets",
2797 reply => undef, 3176 reply => undef,
2798 tooltip => "Shows information abotu your pets/a specific pet", 3177 tooltip => "Shows information abotu your pets/a specific pet",
3178 },
3179 "c/perceiveself" => {
3180 id => "infobox",
3181 title => "Perceive Self",
3182 reply => undef,
3183 tooltip => "You gained detailed knowledge about yourself",
2799 }, 3184 },
2800 "c/uptime" => { 3185 "c/uptime" => {
2801 id => "infobox", 3186 id => "infobox",
2802 title => "Uptime", 3187 title => "Uptime",
2803 reply => undef, 3188 reply => undef,
2859 my $pkt = "msg " 3244 my $pkt = "msg "
2860 . $self->{json_coder}->encode ( 3245 . $self->{json_coder}->encode (
2861 [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra] 3246 [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra]
2862 ); 3247 );
2863 3248
2864 # try lzf for large packets
2865 $pkt = "lzf " . Compress::LZF::compress $pkt
2866 if 1024 <= length $pkt and $self->{can_lzf};
2867
2868 # split very large packets
2869 if (8192 < length $pkt and $self->{can_lzf}) {
2870 $self->send_packet ("frag $_") for unpack "(a8192)*", $pkt;
2871 $pkt = "frag";
2872 }
2873
2874 $self->send_packet ($pkt); 3249 $self->send_big_packet ($pkt);
2875} 3250}
2876 3251
2877=item $client->ext_msg ($type, @msg) 3252=item $client->ext_msg ($type, @msg)
2878 3253
2879Sends an ext event to the client. 3254Sends an ext event to the client.
2881=cut 3256=cut
2882 3257
2883sub cf::client::ext_msg($$@) { 3258sub cf::client::ext_msg($$@) {
2884 my ($self, $type, @msg) = @_; 3259 my ($self, $type, @msg) = @_;
2885 3260
2886 if ($self->extcmd == 2) {
2887 $self->send_packet ("ext " . $self->{json_coder}->encode ([$type, @msg])); 3261 $self->send_big_packet ("ext " . $self->{json_coder}->encode ([$type, @msg]));
2888 } elsif ($self->extcmd == 1) { # TODO: remove
2889 push @msg, msgtype => "event_$type";
2890 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2891 }
2892}
2893
2894=item $client->ext_reply ($msgid, @msg)
2895
2896Sends an ext reply to the client.
2897
2898=cut
2899
2900sub cf::client::ext_reply($$@) {
2901 my ($self, $id, @msg) = @_;
2902
2903 if ($self->extcmd == 2) {
2904 $self->send_packet ("ext " . $self->{json_coder}->encode (["reply-$id", @msg]));
2905 } elsif ($self->extcmd == 1) {
2906 #TODO: version 1, remove
2907 unshift @msg, msgtype => "reply", msgid => $id;
2908 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2909 }
2910} 3262}
2911 3263
2912=item $success = $client->query ($flags, "text", \&cb) 3264=item $success = $client->query ($flags, "text", \&cb)
2913 3265
2914Queues a query to the client, calling the given callback with 3266Queues a query to the client, calling the given callback with
2969 my ($ns, $buf) = @_; 3321 my ($ns, $buf) = @_;
2970 3322
2971 my $msg = eval { $ns->{json_coder}->decode ($buf) }; 3323 my $msg = eval { $ns->{json_coder}->decode ($buf) };
2972 3324
2973 if (ref $msg) { 3325 if (ref $msg) {
2974 my ($type, $reply, @payload) = 3326 my ($type, $reply, @payload) = @$msg; # version 1 used %type, $id, %$hash
2975 "ARRAY" eq ref $msg
2976 ? @$msg
2977 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
2978 3327
2979 my @reply;
2980
2981 if (my $cb = $EXTICMD{$type}) { 3328 if (my $cb = $EXTIACMD{$type}) {
3329 $cb->(
3330 $ns,
3331 sub {
3332 $ns->ext_msg ("reply-$reply", @_)
3333 if $reply;
3334 },
3335 @payload
3336 );
3337 } else {
3338 my @reply;
3339
3340 if (my $cb = $EXTICMD{$type}) {
2982 @reply = $cb->($ns, @payload); 3341 @reply = $cb->($ns, @payload);
3342 }
3343
3344 $ns->ext_msg ("reply-$reply", @reply)
3345 if $reply;
2983 } 3346 }
2984
2985 $ns->ext_reply ($reply, @reply)
2986 if $reply;
2987
2988 } else { 3347 } else {
2989 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n"; 3348 error "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n";
2990 } 3349 }
2991 3350
2992 cf::override; 3351 cf::override;
2993 }, 3352 },
2994); 3353);
3014 3373
3015 $coro 3374 $coro
3016} 3375}
3017 3376
3018cf::client->attach ( 3377cf::client->attach (
3019 on_destroy => sub { 3378 on_client_destroy => sub {
3020 my ($ns) = @_; 3379 my ($ns) = @_;
3021 3380
3022 $_->cancel for values %{ (delete $ns->{_coro}) || {} }; 3381 $_->cancel for values %{ (delete $ns->{_coro}) || {} };
3023 }, 3382 },
3024); 3383);
3040our $safe_hole = new Safe::Hole; 3399our $safe_hole = new Safe::Hole;
3041 3400
3042$SIG{FPE} = 'IGNORE'; 3401$SIG{FPE} = 'IGNORE';
3043 3402
3044$safe->permit_only (Opcode::opset qw( 3403$safe->permit_only (Opcode::opset qw(
3045 :base_core :base_mem :base_orig :base_math 3404 :base_core :base_mem :base_orig :base_math :base_loop
3046 grepstart grepwhile mapstart mapwhile 3405 grepstart grepwhile mapstart mapwhile
3047 sort time 3406 sort time
3048)); 3407));
3049 3408
3050# here we export the classes and methods available to script code 3409# here we export the classes and methods available to script code
3075 decrease split destroy change_exp value msg lore send_msg)], 3434 decrease split destroy change_exp value msg lore send_msg)],
3076 ["cf::object::player" => qw(player)], 3435 ["cf::object::player" => qw(player)],
3077 ["cf::player" => qw(peaceful send_msg)], 3436 ["cf::player" => qw(peaceful send_msg)],
3078 ["cf::map" => qw(trigger)], 3437 ["cf::map" => qw(trigger)],
3079) { 3438) {
3080 no strict 'refs';
3081 my ($pkg, @funs) = @$_; 3439 my ($pkg, @funs) = @$_;
3082 *{"safe::$pkg\::$_"} = $safe_hole->wrap (\&{"$pkg\::$_"}) 3440 *{"safe::$pkg\::$_"} = $safe_hole->wrap (\&{"$pkg\::$_"})
3083 for @funs; 3441 for @funs;
3084} 3442}
3085 3443
3102 $qcode =~ s/"/‟/g; # not allowed in #line filenames 3460 $qcode =~ s/"/‟/g; # not allowed in #line filenames
3103 $qcode =~ s/\n/\\n/g; 3461 $qcode =~ s/\n/\\n/g;
3104 3462
3105 %vars = (_dummy => 0) unless %vars; 3463 %vars = (_dummy => 0) unless %vars;
3106 3464
3465 my @res;
3107 local $_; 3466 local $_;
3108 local @safe::cf::_safe_eval_args = values %vars;
3109 3467
3110 my $eval = 3468 my $eval =
3111 "do {\n" 3469 "do {\n"
3112 . "my (" . (join ",", map "\$$_", keys %vars) . ") = \@cf::_safe_eval_args;\n" 3470 . "my (" . (join ",", map "\$$_", keys %vars) . ") = \@cf::_safe_eval_args;\n"
3113 . "#line 0 \"{$qcode}\"\n" 3471 . "#line 0 \"{$qcode}\"\n"
3114 . $code 3472 . $code
3115 . "\n}" 3473 . "\n}"
3116 ; 3474 ;
3117 3475
3476 if ($CFG{safe_eval}) {
3118 sub_generation_inc; 3477 sub_generation_inc;
3478 local @safe::cf::_safe_eval_args = values %vars;
3119 my @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval); 3479 @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval);
3120 sub_generation_inc; 3480 sub_generation_inc;
3481 } else {
3482 local @cf::_safe_eval_args = values %vars;
3483 @res = wantarray ? eval eval : scalar eval $eval;
3484 }
3121 3485
3122 if ($@) { 3486 if ($@) {
3123 warn "$@"; 3487 warn "$@",
3124 warn "while executing safe code '$code'\n"; 3488 "while executing safe code '$code'\n",
3125 warn "with arguments " . (join " ", %vars) . "\n"; 3489 "with arguments " . (join " ", %vars) . "\n";
3126 } 3490 }
3127 3491
3128 wantarray ? @res : $res[0] 3492 wantarray ? @res : $res[0]
3129} 3493}
3130 3494
3144=cut 3508=cut
3145 3509
3146sub register_script_function { 3510sub register_script_function {
3147 my ($fun, $cb) = @_; 3511 my ($fun, $cb) = @_;
3148 3512
3149 no strict 'refs'; 3513 $fun = "safe::$fun" if $CFG{safe_eval};
3150 *{"safe::$fun"} = $safe_hole->wrap ($cb); 3514 *$fun = $safe_hole->wrap ($cb);
3151} 3515}
3152 3516
3153=back 3517=back
3154 3518
3155=cut 3519=cut
3156 3520
3157############################################################################# 3521#############################################################################
3158# the server's init and main functions 3522# the server's init and main functions
3523
3524our %FACEHASH; # hash => idx, #d# HACK for http server
3525
3526# internal api, not fianlised
3527sub set_face {
3528 my ($name, $type, $data) = @_;
3529
3530 my $idx = cf::face::find $name;
3531
3532 if ($idx) {
3533 delete $FACEHASH{cf::face::get_chksum $idx};
3534 } else {
3535 $idx = cf::face::alloc $name;
3536 }
3537
3538 my $hash = cf::face::mangle_chksum Digest::MD5::md5 $data;
3539
3540 cf::face::set_type $idx, $type;
3541 cf::face::set_data $idx, 0, $data, $hash;
3542 cf::face::set_meta $idx, $type & 1 ? undef : undef;
3543 $FACEHASH{$hash} = $idx;#d#
3544
3545 $idx
3546}
3159 3547
3160sub load_facedata($) { 3548sub load_facedata($) {
3161 my ($path) = @_; 3549 my ($path) = @_;
3162 3550
3163 # HACK to clear player env face cache, we need some signal framework 3551 # HACK to clear player env face cache, we need some signal framework
3164 # for this (global event?) 3552 # for this (global event?)
3165 %ext::player_env::MUSIC_FACE_CACHE = (); 3553 %ext::player_env::MUSIC_FACE_CACHE = ();
3166 3554
3167 my $enc = JSON::XS->new->utf8->canonical->relaxed; 3555 my $enc = JSON::XS->new->utf8->canonical->relaxed;
3168 3556
3169 warn "loading facedata from $path\n"; 3557 trace "loading facedata from $path\n";
3170 3558
3171 my $facedata; 3559 my $facedata = decode_storable load_file $path;
3172 0 < aio_load $path, $facedata
3173 or die "$path: $!";
3174
3175 $facedata = Coro::Storable::thaw $facedata;
3176 3560
3177 $facedata->{version} == 2 3561 $facedata->{version} == 2
3178 or cf::cleanup "$path: version mismatch, cannot proceed."; 3562 or cf::cleanup "$path: version mismatch, cannot proceed.";
3179 3563
3180 # patch in the exptable
3181 $facedata->{resource}{"res/exp_table"} = {
3182 type => FT_RSRC,
3183 data => $enc->encode ([map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]),
3184 };
3185 cf::cede_to_tick; 3564 cf::cede_to_tick;
3186 3565
3187 { 3566 {
3188 my $faces = $facedata->{faceinfo}; 3567 my $faces = $facedata->{faceinfo};
3189 3568
3190 while (my ($face, $info) = each %$faces) { 3569 for my $face (sort keys %$faces) {
3570 my $info = $faces->{$face};
3191 my $idx = (cf::face::find $face) || cf::face::alloc $face; 3571 my $idx = (cf::face::find $face) || cf::face::alloc $face;
3192 3572
3193 cf::face::set_visibility $idx, $info->{visibility}; 3573 cf::face::set_visibility $idx, $info->{visibility};
3194 cf::face::set_magicmap $idx, $info->{magicmap}; 3574 cf::face::set_magicmap $idx, $info->{magicmap};
3195 cf::face::set_data $idx, 0, $info->{data32}, Digest::MD5::md5 $info->{data32}; 3575 cf::face::set_data $idx, 0, $info->{data32}, $info->{hash32};
3196 cf::face::set_data $idx, 1, $info->{data64}, Digest::MD5::md5 $info->{data64}; 3576 cf::face::set_data $idx, 1, $info->{data64}, $info->{hash64};
3577 cf::face::set_data $idx, 2, $info->{glyph} , $info->{glyph} ;
3578 $FACEHASH{$info->{hash64}} = $idx;#d#
3197 3579
3198 cf::cede_to_tick; 3580 cf::cede_to_tick;
3199 } 3581 }
3200 3582
3201 while (my ($face, $info) = each %$faces) { 3583 while (my ($face, $info) = each %$faces) {
3206 3588
3207 if (my $smooth = cf::face::find $info->{smooth}) { 3589 if (my $smooth = cf::face::find $info->{smooth}) {
3208 cf::face::set_smooth $idx, $smooth; 3590 cf::face::set_smooth $idx, $smooth;
3209 cf::face::set_smoothlevel $idx, $info->{smoothlevel}; 3591 cf::face::set_smoothlevel $idx, $info->{smoothlevel};
3210 } else { 3592 } else {
3211 warn "smooth face '$info->{smooth}' not found for face '$face'"; 3593 error "smooth face '$info->{smooth}' not found for face '$face'";
3212 } 3594 }
3213 3595
3214 cf::cede_to_tick; 3596 cf::cede_to_tick;
3215 } 3597 }
3216 } 3598 }
3225 3607
3226 cf::anim::invalidate_all; # d'oh 3608 cf::anim::invalidate_all; # d'oh
3227 } 3609 }
3228 3610
3229 { 3611 {
3230 # TODO: for gcfclient pleasure, we should give resources
3231 # that gcfclient doesn't grok a >10000 face index.
3232 my $res = $facedata->{resource}; 3612 my $res = $facedata->{resource};
3233 3613
3234 while (my ($name, $info) = each %$res) { 3614 while (my ($name, $info) = each %$res) {
3235 if (defined $info->{type}) { 3615 if (defined (my $type = $info->{type})) {
3616 # TODO: different hash - must free and use new index, or cache ixface data queue
3236 my $idx = (cf::face::find $name) || cf::face::alloc $name; 3617 my $idx = (cf::face::find $name) || cf::face::alloc $name;
3237 my $data;
3238 3618
3239 if ($info->{type} & 1) {
3240 # prepend meta info
3241
3242 my $meta = $enc->encode ({
3243 name => $name,
3244 %{ $info->{meta} || {} },
3245 });
3246
3247 $data = pack "(w/a*)*", $meta, $info->{data};
3248 } else {
3249 $data = $info->{data};
3250 }
3251
3252 cf::face::set_data $idx, 0, $data, Digest::MD5::md5 $data;
3253 cf::face::set_type $idx, $info->{type}; 3619 cf::face::set_type $idx, $type;
3620 cf::face::set_data $idx, 0, $info->{data}, $info->{hash};
3621 cf::face::set_meta $idx, $type & 1 ? undef : $info->{meta}; # preserve meta unless prepended already
3622 $FACEHASH{$info->{hash}} = $idx;#d#
3254 } else { 3623 } else {
3255 $RESOURCE{$name} = $info; 3624# $RESOURCE{$name} = $info; # unused
3256 } 3625 }
3257 3626
3258 cf::cede_to_tick; 3627 cf::cede_to_tick;
3259 } 3628 }
3260 } 3629 }
3261 3630
3262 cf::global->invoke (EVENT_GLOBAL_RESOURCE_UPDATE); 3631 cf::global->invoke (EVENT_GLOBAL_RESOURCE_UPDATE);
3263 3632
3264 1 3633 1
3265} 3634}
3266
3267cf::global->attach (on_resource_update => sub {
3268 if (my $soundconf = $RESOURCE{"res/sound.conf"}) {
3269 $soundconf = JSON::XS->new->utf8->relaxed->decode ($soundconf->{data});
3270
3271 for (0 .. SOUND_CAST_SPELL_0 - 1) {
3272 my $sound = $soundconf->{compat}[$_]
3273 or next;
3274
3275 my $face = cf::face::find "sound/$sound->[1]";
3276 cf::sound::set $sound->[0] => $face;
3277 cf::sound::old_sound_index $_, $face; # gcfclient-compat
3278 }
3279
3280 while (my ($k, $v) = each %{$soundconf->{event}}) {
3281 my $face = cf::face::find "sound/$v";
3282 cf::sound::set $k => $face;
3283 }
3284 }
3285});
3286 3635
3287register_exticmd fx_want => sub { 3636register_exticmd fx_want => sub {
3288 my ($ns, $want) = @_; 3637 my ($ns, $want) = @_;
3289 3638
3290 while (my ($k, $v) = each %$want) { 3639 while (my ($k, $v) = each %$want) {
3298 my $status = load_resource_file_ $_[0]; 3647 my $status = load_resource_file_ $_[0];
3299 get_slot 0.1, 100; 3648 get_slot 0.1, 100;
3300 cf::arch::commit_load; 3649 cf::arch::commit_load;
3301 3650
3302 $status 3651 $status
3652}
3653
3654sub reload_exp_table {
3655 _reload_exp_table;
3656
3657 set_face "res/exp_table" => FT_RSRC,
3658 JSON::XS->new->utf8->canonical->encode (
3659 [map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]
3660 );
3661}
3662
3663sub reload_materials {
3664 _reload_materials;
3303} 3665}
3304 3666
3305sub reload_regions { 3667sub reload_regions {
3306 # HACK to clear player env face cache, we need some signal framework 3668 # HACK to clear player env face cache, we need some signal framework
3307 # for this (global event?) 3669 # for this (global event?)
3322} 3684}
3323 3685
3324sub reload_archetypes { 3686sub reload_archetypes {
3325 load_resource_file "$DATADIR/archetypes" 3687 load_resource_file "$DATADIR/archetypes"
3326 or die "unable to load archetypes\n"; 3688 or die "unable to load archetypes\n";
3689
3690 set_face "res/skill_info" => FT_RSRC,
3691 JSON::XS->new->utf8->canonical->encode (
3692 [map [cf::arch::skillvec ($_)->name], 0 .. cf::arch::skillvec_size - 1]
3693 );
3694 set_face "res/spell_paths" => FT_RSRC,
3695 JSON::XS->new->utf8->canonical->encode (
3696 [map [cf::spellpathnames ($_)], 0 .. NRSPELLPATHS - 1]
3697 );
3327} 3698}
3328 3699
3329sub reload_treasures { 3700sub reload_treasures {
3330 load_resource_file "$DATADIR/treasures" 3701 load_resource_file "$DATADIR/treasures"
3331 or die "unable to load treasurelists\n"; 3702 or die "unable to load treasurelists\n";
3332} 3703}
3333 3704
3705sub reload_sound {
3706 trace "loading sound config from $DATADIR/sound\n";
3707
3708 my $soundconf = JSON::XS->new->utf8->relaxed->decode (load_file "$DATADIR/sound");
3709
3710 for (0 .. SOUND_CAST_SPELL_0 - 1) {
3711 my $sound = $soundconf->{compat}[$_]
3712 or next;
3713
3714 my $face = cf::face::find "sound/$sound->[1]";
3715 cf::sound::set $sound->[0] => $face;
3716 cf::sound::old_sound_index $_, $face; # gcfclient-compat
3717 }
3718
3719 while (my ($k, $v) = each %{$soundconf->{event}}) {
3720 my $face = cf::face::find "sound/$v";
3721 cf::sound::set $k => $face;
3722 }
3723}
3724
3334sub reload_resources { 3725sub reload_resources {
3335 warn "reloading resource files...\n"; 3726 trace "reloading resource files...\n";
3336 3727
3728 reload_materials;
3337 reload_facedata; 3729 reload_facedata;
3730 reload_exp_table;
3731 reload_sound;
3338 reload_archetypes; 3732 reload_archetypes;
3339 reload_regions; 3733 reload_regions;
3340 reload_treasures; 3734 reload_treasures;
3341 3735
3342 warn "finished reloading resource files\n"; 3736 trace "finished reloading resource files\n";
3343} 3737}
3344 3738
3345sub reload_config { 3739sub reload_config {
3346 open my $fh, "<:utf8", "$CONFDIR/config" 3740 trace "reloading config file...\n";
3347 or return;
3348 3741
3349 local $/; 3742 my $config = load_file "$CONFDIR/config";
3350 *CFG = YAML::Load <$fh>; 3743 utf8::decode $config;
3744 *CFG = decode_yaml $config;
3351 3745
3352 $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37]; 3746 $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_104_115", 49, 38];
3353 3747
3354 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset}; 3748 $cf::map::MAX_RESET = $CFG{map_max_reset} if exists $CFG{map_max_reset};
3355 $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset}; 3749 $cf::map::DEFAULT_RESET = $CFG{map_default_reset} if exists $CFG{map_default_reset};
3356 3750
3357 if (exists $CFG{mlockall}) { 3751 if (exists $CFG{mlockall}) {
3380 3774
3381 seek $fh, 0, 0; 3775 seek $fh, 0, 0;
3382 print $fh $$; 3776 print $fh $$;
3383} 3777}
3384 3778
3779sub main_loop {
3780 trace "EV::loop starting\n";
3781 if (1) {
3782 EV::loop;
3783 }
3784 trace "EV::loop returned\n";
3785 goto &main_loop unless $REALLY_UNLOOP;
3786}
3787
3385sub main { 3788sub main {
3386 cf::init_globals; # initialise logging 3789 cf::init_globals; # initialise logging
3387 3790
3388 LOG llevInfo, "Welcome to Deliantra, v" . VERSION; 3791 LOG llevInfo, "Welcome to Deliantra, v" . VERSION;
3389 LOG llevInfo, "Copyright (C) 2005-2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team."; 3792 LOG llevInfo, "Copyright (C) 2005-2012 Marc Alexander Lehmann / Robin Redeker / the Deliantra team.";
3390 LOG llevInfo, "Copyright (C) 1994 Mark Wedel."; 3793 LOG llevInfo, "Copyright (C) 1994 Mark Wedel.";
3391 LOG llevInfo, "Copyright (C) 1992 Frank Tore Johansen."; 3794 LOG llevInfo, "Copyright (C) 1992 Frank Tore Johansen.";
3392 3795
3393 cf::init_experience;
3394 cf::init_anim;
3395 cf::init_attackmess;
3396 cf::init_dynamic;
3397
3398 $Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority 3796 $Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority
3399 3797
3400 # we must not ever block the main coroutine 3798 # we must not ever block the main coroutine
3401 local $Coro::idle = sub { 3799 $Coro::idle = sub {
3402 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d# 3800 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
3403 (async { 3801 (async {
3404 $Coro::current->{desc} = "IDLE BUG HANDLER"; 3802 $Coro::current->{desc} = "IDLE BUG HANDLER";
3405 EV::loop EV::LOOP_ONESHOT; 3803 EV::loop EV::LOOP_ONESHOT;
3406 })->prio (Coro::PRIO_MAX); 3804 })->prio (Coro::PRIO_MAX);
3407 }; 3805 };
3408 3806
3409 evthread_start IO::AIO::poll_fileno; 3807 evthread_start IO::AIO::poll_fileno;
3410 3808
3411 cf::sync_job { 3809 cf::sync_job {
3810 cf::incloader::init ();
3811
3812 db_init;
3813
3814 cf::init_anim;
3815 cf::init_attackmess;
3816 cf::init_dynamic;
3817
3818 cf::load_settings;
3819
3412 reload_resources; 3820 reload_resources;
3413 reload_config; 3821 reload_config;
3414 db_init;
3415 3822
3416 cf::load_settings;
3417 cf::load_materials;
3418 cf::init_uuid; 3823 cf::init_uuid;
3419 cf::init_signals; 3824 cf::init_signals;
3420 cf::init_commands;
3421 cf::init_skills; 3825 cf::init_skills;
3422 3826
3423 cf::init_beforeplay; 3827 cf::init_beforeplay;
3424 3828
3425 atomic; 3829 atomic;
3427 load_extensions; 3831 load_extensions;
3428 3832
3429 utime time, time, $RUNTIMEFILE; 3833 utime time, time, $RUNTIMEFILE;
3430 3834
3431 # no (long-running) fork's whatsoever before this point(!) 3835 # no (long-running) fork's whatsoever before this point(!)
3836 use POSIX ();
3432 POSIX::close delete $ENV{LOCKUTIL_LOCK_FD} if exists $ENV{LOCKUTIL_LOCK_FD}; 3837 POSIX::close delete $ENV{LOCKUTIL_LOCK_FD} if exists $ENV{LOCKUTIL_LOCK_FD};
3433 3838
3434 (pop @POST_INIT)->(0) while @POST_INIT; 3839 cf::_post_init 0;
3435 }; 3840 };
3436 3841
3437 EV::loop; 3842 cf::object::thawer::errors_are_fatal 0;
3843 info "parse errors in files are no longer fatal from this point on.\n";
3844
3845 AE::postpone {
3846 undef &main; # free gobs of memory :)
3847 };
3848
3849 goto &main_loop;
3438} 3850}
3439 3851
3440############################################################################# 3852#############################################################################
3441# initialisation and cleanup 3853# initialisation and cleanup
3442 3854
3443# install some emergency cleanup handlers 3855# install some emergency cleanup handlers
3444BEGIN { 3856BEGIN {
3445 our %SIGWATCHER = (); 3857 our %SIGWATCHER = ();
3446 for my $signal (qw(INT HUP TERM)) { 3858 for my $signal (qw(INT HUP TERM)) {
3447 $SIGWATCHER{$signal} = EV::signal $signal, sub { 3859 $SIGWATCHER{$signal} = AE::signal $signal, sub {
3448 cf::cleanup "SIG$signal"; 3860 cf::cleanup "SIG$signal";
3449 }; 3861 };
3450 } 3862 }
3451} 3863}
3452 3864
3453sub write_runtime_sync { 3865sub write_runtime_sync {
3866 my $t0 = AE::time;
3867
3454 # first touch the runtime file to show we are still running: 3868 # first touch the runtime file to show we are still running:
3455 # the fsync below can take a very very long time. 3869 # the fsync below can take a very very long time.
3456 3870
3457 IO::AIO::aio_utime $RUNTIMEFILE, undef, undef; 3871 IO::AIO::aio_utime $RUNTIMEFILE, undef, undef;
3458 3872
3459 my $guard = cf::lock_acquire "write_runtime"; 3873 my $guard = cf::lock_acquire "write_runtime";
3460 3874
3461 my $fh = aio_open "$RUNTIMEFILE~", O_WRONLY | O_CREAT, 0644 3875 my $fh = aio_open "$RUNTIMEFILE~", O_WRONLY | O_CREAT | O_TRUNC, 0644
3462 or return; 3876 or return;
3463 3877
3464 my $value = $cf::RUNTIME + 90 + 10; 3878 my $value = $cf::RUNTIME + 90 + 10;
3465 # 10 is the runtime save interval, for a monotonic clock 3879 # 10 is the runtime save interval, for a monotonic clock
3466 # 60 allows for the watchdog to kill the server. 3880 # 60 allows for the watchdog to kill the server.
3479 or return; 3893 or return;
3480 3894
3481 aio_rename "$RUNTIMEFILE~", $RUNTIMEFILE 3895 aio_rename "$RUNTIMEFILE~", $RUNTIMEFILE
3482 and return; 3896 and return;
3483 3897
3484 warn "runtime file written.\n"; 3898 trace sprintf "runtime file written (%gs).\n", AE::time - $t0;
3485 3899
3486 1 3900 1
3487} 3901}
3488 3902
3489our $uuid_lock; 3903our $uuid_lock;
3501 or return; 3915 or return;
3502 3916
3503 my $value = uuid_seq uuid_cur; 3917 my $value = uuid_seq uuid_cur;
3504 3918
3505 unless ($value) { 3919 unless ($value) {
3506 warn "cowardly refusing to write zero uuid value!\n"; 3920 info "cowardly refusing to write zero uuid value!\n";
3507 return; 3921 return;
3508 } 3922 }
3509 3923
3510 my $value = uuid_str $value + $uuid_skip; 3924 my $value = uuid_str $value + $uuid_skip;
3511 $uuid_skip = 0; 3925 $uuid_skip = 0;
3521 or return; 3935 or return;
3522 3936
3523 aio_rename "$uuid~", $uuid 3937 aio_rename "$uuid~", $uuid
3524 and return; 3938 and return;
3525 3939
3526 warn "uuid file written ($value).\n"; 3940 trace "uuid file written ($value).\n";
3527 3941
3528 1 3942 1
3529 3943
3530} 3944}
3531 3945
3537} 3951}
3538 3952
3539sub emergency_save() { 3953sub emergency_save() {
3540 my $freeze_guard = cf::freeze_mainloop; 3954 my $freeze_guard = cf::freeze_mainloop;
3541 3955
3542 warn "emergency_perl_save: enter\n"; 3956 info "emergency_perl_save: enter\n";
3957
3958 # this is a trade-off: we want to be very quick here, so
3959 # save all maps without fsync, and later call a global sync
3960 # (which in turn might be very very slow)
3961 local $USE_FSYNC = 0;
3543 3962
3544 cf::sync_job { 3963 cf::sync_job {
3545 # this is a trade-off: we want to be very quick here, so 3964 cf::write_runtime_sync; # external watchdog should not bark
3546 # save all maps without fsync, and later call a global sync
3547 # (which in turn might be very very slow)
3548 local $USE_FSYNC = 0;
3549 3965
3550 # use a peculiar iteration method to avoid tripping on perl 3966 # use a peculiar iteration method to avoid tripping on perl
3551 # refcount bugs in for. also avoids problems with players 3967 # refcount bugs in for. also avoids problems with players
3552 # and maps saved/destroyed asynchronously. 3968 # and maps saved/destroyed asynchronously.
3553 warn "emergency_perl_save: begin player save\n"; 3969 info "emergency_perl_save: begin player save\n";
3554 for my $login (keys %cf::PLAYER) { 3970 for my $login (keys %cf::PLAYER) {
3555 my $pl = $cf::PLAYER{$login} or next; 3971 my $pl = $cf::PLAYER{$login} or next;
3556 $pl->valid or next; 3972 $pl->valid or next;
3557 delete $pl->{unclean_save}; # not strictly necessary, but cannot hurt 3973 delete $pl->{unclean_save}; # not strictly necessary, but cannot hurt
3558 $pl->save; 3974 $pl->save;
3559 } 3975 }
3560 warn "emergency_perl_save: end player save\n"; 3976 info "emergency_perl_save: end player save\n";
3561 3977
3978 cf::write_runtime_sync; # external watchdog should not bark
3979
3562 warn "emergency_perl_save: begin map save\n"; 3980 info "emergency_perl_save: begin map save\n";
3563 for my $path (keys %cf::MAP) { 3981 for my $path (keys %cf::MAP) {
3564 my $map = $cf::MAP{$path} or next; 3982 my $map = $cf::MAP{$path} or next;
3565 $map->valid or next; 3983 $map->valid or next;
3566 $map->save; 3984 $map->save;
3567 } 3985 }
3568 warn "emergency_perl_save: end map save\n"; 3986 info "emergency_perl_save: end map save\n";
3569 3987
3988 cf::write_runtime_sync; # external watchdog should not bark
3989
3570 warn "emergency_perl_save: begin database checkpoint\n"; 3990 info "emergency_perl_save: begin database checkpoint\n";
3571 BDB::db_env_txn_checkpoint $DB_ENV; 3991 BDB::db_env_txn_checkpoint $DB_ENV;
3572 warn "emergency_perl_save: end database checkpoint\n"; 3992 info "emergency_perl_save: end database checkpoint\n";
3573 3993
3574 warn "emergency_perl_save: begin write uuid\n"; 3994 info "emergency_perl_save: begin write uuid\n";
3575 write_uuid_sync 1; 3995 write_uuid_sync 1;
3576 warn "emergency_perl_save: end write uuid\n"; 3996 info "emergency_perl_save: end write uuid\n";
3997
3998 cf::write_runtime_sync; # external watchdog should not bark
3999
4000 trace "emergency_perl_save: syncing database to disk";
4001 BDB::db_env_txn_checkpoint $DB_ENV;
4002
4003 info "emergency_perl_save: starting sync\n";
4004 IO::AIO::aio_sync sub {
4005 info "emergency_perl_save: finished sync\n";
4006 };
4007
4008 cf::write_runtime_sync; # external watchdog should not bark
4009
4010 trace "emergency_perl_save: flushing outstanding aio requests";
4011 while (IO::AIO::nreqs || BDB::nreqs) {
4012 Coro::AnyEvent::sleep 0.01; # let the sync_job do it's thing
4013 }
4014
4015 cf::write_runtime_sync; # external watchdog should not bark
3577 }; 4016 };
3578 4017
3579 warn "emergency_perl_save: starting sync()\n";
3580 IO::AIO::aio_sync sub {
3581 warn "emergency_perl_save: finished sync()\n";
3582 };
3583
3584 warn "emergency_perl_save: leave\n"; 4018 info "emergency_perl_save: leave\n";
3585} 4019}
3586 4020
3587sub post_cleanup { 4021sub post_cleanup {
3588 my ($make_core) = @_; 4022 my ($make_core) = @_;
3589 4023
4024 IO::AIO::flush;
4025
3590 warn Carp::longmess "post_cleanup backtrace" 4026 error Carp::longmess "post_cleanup backtrace"
3591 if $make_core; 4027 if $make_core;
3592 4028
3593 my $fh = pidfile; 4029 my $fh = pidfile;
3594 unlink $PIDFILE if <$fh> == $$; 4030 unlink $PIDFILE if <$fh> == $$;
3595} 4031}
3615 my $leaf_symtab = *{$stem_symtab->{$leaf}}{HASH}; 4051 my $leaf_symtab = *{$stem_symtab->{$leaf}}{HASH};
3616 for my $name (keys %$leaf_symtab) { 4052 for my $name (keys %$leaf_symtab) {
3617 _gv_clear *{"$pkg$name"}; 4053 _gv_clear *{"$pkg$name"};
3618# use PApp::Util; PApp::Util::sv_dump *{"$pkg$name"}; 4054# use PApp::Util; PApp::Util::sv_dump *{"$pkg$name"};
3619 } 4055 }
3620 warn "cleared package $pkg\n";#d#
3621} 4056}
3622 4057
3623sub do_reload_perl() { 4058sub do_reload_perl() {
3624 # can/must only be called in main 4059 # can/must only be called in main
3625 if ($Coro::current != $Coro::main) { 4060 unless (in_main) {
3626 warn "can only reload from main coroutine"; 4061 error "can only reload from main coroutine";
3627 return; 4062 return;
3628 } 4063 }
3629 4064
3630 return if $RELOAD++; 4065 return if $RELOAD++;
3631 4066
3632 my $t1 = EV::time; 4067 my $t1 = AE::time;
3633 4068
3634 while ($RELOAD) { 4069 while ($RELOAD) {
3635 warn "reloading..."; 4070 cf::get_slot 0.1, -1, "reload_perl";
4071 info "perl_reload: reloading...";
3636 4072
3637 warn "entering sync_job"; 4073 trace "perl_reload: entering sync_job";
3638 4074
3639 cf::sync_job { 4075 cf::sync_job {
3640 cf::write_runtime_sync; # external watchdog should not bark
3641 cf::emergency_save; 4076 #cf::emergency_save;
3642 cf::write_runtime_sync; # external watchdog should not bark
3643 4077
3644 warn "syncing database to disk";
3645 BDB::db_env_txn_checkpoint $DB_ENV;
3646
3647 # if anything goes wrong in here, we should simply crash as we already saved
3648
3649 warn "flushing outstanding aio requests";
3650 while (IO::AIO::nreqs || BDB::nreqs) {
3651 Coro::EV::timer_once 0.01; # let the sync_job do it's thing
3652 }
3653
3654 warn "cancelling all extension coros"; 4078 trace "perl_reload: cancelling all extension coros";
3655 $_->cancel for values %EXT_CORO; 4079 $_->cancel for values %EXT_CORO;
3656 %EXT_CORO = (); 4080 %EXT_CORO = ();
3657 4081
3658 warn "removing commands"; 4082 trace "perl_reload: removing commands";
3659 %COMMAND = (); 4083 %COMMAND = ();
3660 4084
3661 warn "removing ext/exti commands"; 4085 trace "perl_reload: removing ext/exti commands";
3662 %EXTCMD = (); 4086 %EXTCMD = ();
3663 %EXTICMD = (); 4087 %EXTICMD = ();
3664 4088
3665 warn "unloading/nuking all extensions"; 4089 trace "perl_reload: unloading/nuking all extensions";
3666 for my $pkg (@EXTS) { 4090 for my $pkg (@EXTS) {
3667 warn "... unloading $pkg"; 4091 trace "... unloading $pkg";
3668 4092
3669 if (my $cb = $pkg->can ("unload")) { 4093 if (my $cb = $pkg->can ("unload")) {
3670 eval { 4094 eval {
3671 $cb->($pkg); 4095 $cb->($pkg);
3672 1 4096 1
3673 } or warn "$pkg unloaded, but with errors: $@"; 4097 } or error "$pkg unloaded, but with errors: $@";
3674 } 4098 }
3675 4099
3676 warn "... clearing $pkg"; 4100 trace "... clearing $pkg";
3677 clear_package $pkg; 4101 clear_package $pkg;
3678 } 4102 }
3679 4103
3680 warn "unloading all perl modules loaded from $LIBDIR"; 4104 trace "perl_reload: unloading all perl modules loaded from $LIBDIR";
3681 while (my ($k, $v) = each %INC) { 4105 while (my ($k, $v) = each %INC) {
3682 next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/; 4106 next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
3683 4107
3684 warn "... unloading $k"; 4108 trace "... unloading $k";
3685 delete $INC{$k}; 4109 delete $INC{$k};
3686 4110
3687 $k =~ s/\.pm$//; 4111 $k =~ s/\.pm$//;
3688 $k =~ s/\//::/g; 4112 $k =~ s/\//::/g;
3689 4113
3692 } 4116 }
3693 4117
3694 clear_package $k; 4118 clear_package $k;
3695 } 4119 }
3696 4120
3697 warn "getting rid of safe::, as good as possible"; 4121 trace "perl_reload: getting rid of safe::, as good as possible";
3698 clear_package "safe::$_" 4122 clear_package "safe::$_"
3699 for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region); 4123 for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region);
3700 4124
3701 warn "unloading cf.pm \"a bit\""; 4125 trace "perl_reload: unloading cf.pm \"a bit\"";
3702 delete $INC{"cf.pm"}; 4126 delete $INC{"cf.pm"};
3703 delete $INC{"cf/$_.pm"} for @EXTRA_MODULES; 4127 delete $INC{"cf/$_.pm"} for @EXTRA_MODULES;
3704 4128
3705 # don't, removes xs symbols, too, 4129 # don't, removes xs symbols, too,
3706 # and global variables created in xs 4130 # and global variables created in xs
3707 #clear_package __PACKAGE__; 4131 #clear_package __PACKAGE__;
3708 4132
3709 warn "unload completed, starting to reload now"; 4133 info "perl_reload: unload completed, starting to reload now";
3710 4134
3711 warn "reloading cf.pm"; 4135 trace "perl_reload: reloading cf.pm";
3712 require cf; 4136 require cf;
3713 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt 4137 cf::_connect_to_perl_1;
3714 4138
3715 warn "loading config and database again"; 4139 trace "perl_reload: loading config and database again";
3716 cf::reload_config; 4140 cf::reload_config;
3717 4141
3718 warn "loading extensions"; 4142 trace "perl_reload: loading extensions";
3719 cf::load_extensions; 4143 cf::load_extensions;
3720 4144
3721 if ($REATTACH_ON_RELOAD) { 4145 if ($REATTACH_ON_RELOAD) {
3722 warn "reattaching attachments to objects/players"; 4146 trace "perl_reload: reattaching attachments to objects/players";
3723 _global_reattach; # objects, sockets 4147 _global_reattach; # objects, sockets
3724 warn "reattaching attachments to maps"; 4148 trace "perl_reload: reattaching attachments to maps";
3725 reattach $_ for values %MAP; 4149 reattach $_ for values %MAP;
3726 warn "reattaching attachments to players"; 4150 trace "perl_reload: reattaching attachments to players";
3727 reattach $_ for values %PLAYER; 4151 reattach $_ for values %PLAYER;
3728 } 4152 }
3729 4153
3730 warn "running post_init jobs"; 4154 cf::_post_init 1;
3731 (pop @POST_INIT)->(1) while @POST_INIT;
3732 4155
3733 warn "leaving sync_job"; 4156 trace "perl_reload: leaving sync_job";
3734 4157
3735 1 4158 1
3736 } or do { 4159 } or do {
3737 warn $@; 4160 error $@;
3738 cf::cleanup "error while reloading, exiting."; 4161 cf::cleanup "perl_reload: error, exiting.";
3739 }; 4162 };
3740 4163
3741 warn "reloaded";
3742 --$RELOAD; 4164 --$RELOAD;
3743 } 4165 }
3744 4166
3745 $t1 = EV::time - $t1; 4167 $t1 = AE::time - $t1;
3746 warn "reload completed in ${t1}s\n"; 4168 info "perl_reload: completed in ${t1}s\n";
3747}; 4169};
3748 4170
3749our $RELOAD_WATCHER; # used only during reload 4171our $RELOAD_WATCHER; # used only during reload
3750 4172
3751sub reload_perl() { 4173sub reload_perl() {
3753 # coro crashes during coro_state_free->destroy here. 4175 # coro crashes during coro_state_free->destroy here.
3754 4176
3755 $RELOAD_WATCHER ||= cf::async { 4177 $RELOAD_WATCHER ||= cf::async {
3756 Coro::AIO::aio_wait cache_extensions; 4178 Coro::AIO::aio_wait cache_extensions;
3757 4179
3758 $RELOAD_WATCHER = EV::timer $TICK * 1.5, 0, sub { 4180 $RELOAD_WATCHER = AE::timer $TICK * 1.5, 0, sub {
3759 do_reload_perl; 4181 do_reload_perl;
3760 undef $RELOAD_WATCHER; 4182 undef $RELOAD_WATCHER;
3761 }; 4183 };
3762 }; 4184 };
3763} 4185}
3772 reload_perl; 4194 reload_perl;
3773 }; 4195 };
3774 } 4196 }
3775}; 4197};
3776 4198
3777unshift @INC, $LIBDIR; 4199#############################################################################
3778 4200
3779my $bug_warning = 0; 4201my $bug_warning = 0;
3780 4202
3781our @WAIT_FOR_TICK;
3782our @WAIT_FOR_TICK_BEGIN;
3783
3784sub wait_for_tick { 4203sub wait_for_tick() {
3785 return if tick_inhibit || $Coro::current == $Coro::main; 4204 return Coro::AnyEvent::poll if tick_inhibit || $Coro::current == $Coro::main;
3786 4205
3787 my $signal = new Coro::Signal; 4206 $WAIT_FOR_TICK->wait;
3788 push @WAIT_FOR_TICK, $signal;
3789 $signal->wait;
3790} 4207}
3791 4208
3792sub wait_for_tick_begin { 4209sub wait_for_tick_begin() {
3793 return if tick_inhibit || $Coro::current == $Coro::main; 4210 return Coro::AnyEvent::poll if tick_inhibit || $Coro::current == $Coro::main;
3794 4211
3795 my $signal = new Coro::Signal; 4212 my $signal = new Coro::Signal;
3796 push @WAIT_FOR_TICK_BEGIN, $signal; 4213 push @WAIT_FOR_TICK_BEGIN, $signal;
3797 $signal->wait; 4214 $signal->wait;
3798} 4215}
3802 Carp::cluck "major BUG: server tick called outside of main coro, skipping it" 4219 Carp::cluck "major BUG: server tick called outside of main coro, skipping it"
3803 unless ++$bug_warning > 10; 4220 unless ++$bug_warning > 10;
3804 return; 4221 return;
3805 } 4222 }
3806 4223
3807 cf::server_tick; # one server iteration 4224 cf::one_tick; # one server iteration
4225
4226 #for(1..3e6){} AE::now_update; $NOW=AE::now; # generate load #d#
3808 4227
3809 if ($NOW >= $NEXT_RUNTIME_WRITE) { 4228 if ($NOW >= $NEXT_RUNTIME_WRITE) {
3810 $NEXT_RUNTIME_WRITE = List::Util::max $NEXT_RUNTIME_WRITE + 10, $NOW + 5.; 4229 $NEXT_RUNTIME_WRITE = List::Util::max $NEXT_RUNTIME_WRITE + 10, $NOW + 5.;
3811 Coro::async_pool { 4230 Coro::async_pool {
3812 $Coro::current->{desc} = "runtime saver"; 4231 $Coro::current->{desc} = "runtime saver";
3813 write_runtime_sync 4232 write_runtime_sync
3814 or warn "ERROR: unable to write runtime file: $!"; 4233 or error "ERROR: unable to write runtime file: $!";
3815 }; 4234 };
3816 } 4235 }
3817 4236
3818 if (my $sig = shift @WAIT_FOR_TICK_BEGIN) { 4237 if (my $sig = shift @WAIT_FOR_TICK_BEGIN) {
3819 $sig->send; 4238 $sig->send;
3820 } 4239 }
3821 while (my $sig = shift @WAIT_FOR_TICK) { 4240 $WAIT_FOR_TICK->broadcast;
3822 $sig->send;
3823 }
3824 4241
3825 $LOAD = ($NOW - $TICK_START) / $TICK; 4242 $LOAD = ($NOW - $TICK_START) / $TICK;
3826 $LOADAVG = $LOADAVG * 0.75 + $LOAD * 0.25; 4243 $LOADAVG = $LOADAVG * 0.75 + $LOAD * 0.25;
3827 4244
3828 if (0) { 4245 if (0) {
3829 if ($NEXT_TICK) { 4246 if ($NEXT_TICK) {
3830 my $jitter = $TICK_START - $NEXT_TICK; 4247 my $jitter = $TICK_START - $NEXT_TICK;
3831 $JITTER = $JITTER * 0.75 + $jitter * 0.25; 4248 $JITTER = $JITTER * 0.75 + $jitter * 0.25;
3832 warn "jitter $JITTER\n";#d# 4249 debug "jitter $JITTER\n";#d#
3833 } 4250 }
3834 } 4251 }
3835} 4252}
3836 4253
3837{ 4254{
3838 # configure BDB 4255 # configure BDB
4256 info "initialising database";
3839 4257
3840 BDB::min_parallel 8; 4258 BDB::min_parallel 16;
3841 BDB::max_poll_reqs $TICK * 0.1; 4259 BDB::max_poll_reqs $TICK * 0.1;
3842 $AnyEvent::BDB::WATCHER->priority (1); 4260 #$AnyEvent::BDB::WATCHER->priority (1);
3843 4261
3844 unless ($DB_ENV) { 4262 unless ($DB_ENV) {
3845 $DB_ENV = BDB::db_env_create; 4263 $DB_ENV = BDB::db_env_create;
3846 $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT); 4264 $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT);
3847 $DB_ENV->set_flags (&BDB::LOG_AUTOREMOVE ) if BDB::VERSION v0, v4.7; 4265 $DB_ENV->set_flags (&BDB::LOG_AUTOREMOVE ) if BDB::VERSION v0, v4.7;
3872 BDB::db_env_txn_checkpoint $DB_ENV, 0, 0, 0, sub { }; 4290 BDB::db_env_txn_checkpoint $DB_ENV, 0, 0, 0, sub { };
3873 }; 4291 };
3874 $BDB_TRICKLE_WATCHER = EV::periodic 0, 10, 0, sub { 4292 $BDB_TRICKLE_WATCHER = EV::periodic 0, 10, 0, sub {
3875 BDB::db_env_memp_trickle $DB_ENV, 20, 0, sub { }; 4293 BDB::db_env_memp_trickle $DB_ENV, 20, 0, sub { };
3876 }; 4294 };
4295
4296 info "database initialised";
3877} 4297}
3878 4298
3879{ 4299{
3880 # configure IO::AIO 4300 # configure IO::AIO
3881 4301
4302 info "initialising aio";
3882 IO::AIO::min_parallel 8; 4303 IO::AIO::min_parallel 8;
3883 IO::AIO::max_poll_time $TICK * 0.1; 4304 IO::AIO::max_poll_time $TICK * 0.1;
3884 undef $AnyEvent::AIO::WATCHER; 4305 undef $AnyEvent::AIO::WATCHER;
4306 info "aio initialised";
3885} 4307}
3886 4308
3887my $_log_backtrace; 4309our $_log_backtrace;
4310our $_log_backtrace_last;
3888 4311
3889sub _log_backtrace { 4312sub _log_backtrace {
3890 my ($msg, @addr) = @_; 4313 my ($msg, @addr) = @_;
3891 4314
3892 $msg =~ s/\n//; 4315 $msg =~ s/\n$//;
3893 4316
4317 if ($_log_backtrace_last eq $msg) {
4318 LOG llevInfo, "[ABT] $msg\n";
4319 LOG llevInfo, "[ABT] [duplicate, suppressed]\n";
3894 # limit the # of concurrent backtraces 4320 # limit the # of concurrent backtraces
3895 if ($_log_backtrace < 2) { 4321 } elsif ($_log_backtrace < 2) {
4322 $_log_backtrace_last = $msg;
3896 ++$_log_backtrace; 4323 ++$_log_backtrace;
3897 my $perl_bt = Carp::longmess $msg; 4324 my $perl_bt = Carp::longmess $msg;
3898 async { 4325 async {
3899 $Coro::current->{desc} = "abt $msg"; 4326 $Coro::current->{desc} = "abt $msg";
3900 4327
3920 LOG llevInfo, "[ABT] $_\n" for @bt; 4347 LOG llevInfo, "[ABT] $_\n" for @bt;
3921 --$_log_backtrace; 4348 --$_log_backtrace;
3922 }; 4349 };
3923 } else { 4350 } else {
3924 LOG llevInfo, "[ABT] $msg\n"; 4351 LOG llevInfo, "[ABT] $msg\n";
3925 LOG llevInfo, "[ABT] [suppressed]\n"; 4352 LOG llevInfo, "[ABT] [overload, suppressed]\n";
3926 } 4353 }
3927} 4354}
3928 4355
3929# load additional modules 4356# load additional modules
3930eval "use cf::$_" for @EXTRA_MODULES; 4357require "cf/$_.pm" for @EXTRA_MODULES;
4358cf::_connect_to_perl_2;
3931 4359
3932END { cf::emergency_save } 4360END { cf::emergency_save }
3933 4361
39341 43621
3935 4363

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines