--- deliantra/server/lib/cf.pm 2009/04/26 19:42:45 1.470
+++ deliantra/server/lib/cf.pm 2010/04/18 07:08:07 1.524
@@ -1,23 +1,24 @@
-#
+#
# This file is part of Deliantra, the Roguelike Realtime MMORPG.
#
-# Copyright (©) 2006,2007,2008 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
+# Copyright (©) 2006,2007,2008,2009,2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
#
-# Deliantra is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+# Deliantra is free software: you can redistribute it and/or modify it under
+# the terms of the Affero GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# You should have received a copy of the Affero GNU General Public License
+# and the GNU General Public License along with this program. If not, see
+# .
#
# The authors can be reached via e-mail to
-#
+#
package cf;
@@ -33,6 +34,7 @@
use Safe;
use Safe::Hole;
use Storable ();
+use Carp ();
use Guard ();
use Coro ();
@@ -53,9 +55,8 @@
use JSON::XS 2.01 ();
use BDB ();
use Data::Dumper;
-use Digest::MD5;
use Fcntl;
-use YAML ();
+use YAML::XS ();
use IO::AIO ();
use Time::HiRes;
use Compress::LZF;
@@ -74,6 +75,9 @@
Compress::LZF::set_serializer "Storable", "Storable::net_mstore", "Storable::mretrieve";
Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later
+# strictly for debugging
+$SIG{QUIT} = sub { Carp::cluck "SIGQUIT" };
+
sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
our %COMMAND = ();
@@ -87,6 +91,8 @@
our $RELOAD; # number of reloads so far, non-zero while in reload
our @EVENT;
+our @REFLECT; # set by XS
+our %REFLECT; # set by us
our $CONFDIR = confdir;
our $DATADIR = datadir;
@@ -104,6 +110,9 @@
our %RESOURCE;
+our $OUTPUT_RATE_MIN = 4000;
+our $OUTPUT_RATE_MAX = 100000;
+
our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!)
our $NEXT_RUNTIME_WRITE; # when should the runtime file be written
our $NEXT_TICK;
@@ -114,7 +123,7 @@
our $BDB_TRICKLE_WATCHER;
our $DB_ENV;
-our @EXTRA_MODULES = qw(pod mapscript);
+our @EXTRA_MODULES = qw(pod match mapscript);
our %CFG;
@@ -136,7 +145,8 @@
our @POST_INIT;
-our $REATTACH_ON_RELOAD; # ste to true to force object reattach on reload (slow)
+our $REATTACH_ON_RELOAD; # set to true to force object reattach on reload (slow)
+our $REALLY_UNLOOP; # never set to true, please :)
binmode STDOUT;
binmode STDERR;
@@ -157,6 +167,21 @@
sub cf::map::normalise;
+sub in_main() {
+ $Coro::current == $Coro::main
+}
+
+#############################################################################
+
+%REFLECT = ();
+for (@REFLECT) {
+ my $reflect = JSON::XS::decode_json $_;
+ $REFLECT{$reflect->{class}} = $reflect;
+}
+
+# this is decidedly evil
+$REFLECT{object}{flags} = { map +($_ => undef), grep $_, map /^FLAG_([A-Z0-9_]+)$/ && lc $1, keys %{"cf::"} };
+
#############################################################################
=head2 GLOBAL VARIABLES
@@ -212,42 +237,45 @@
=item @cf::INVOKE_RESULTS
-This array contains the results of the last C call. When
+This array contains the results of the last C call. When
C is called C<@cf::INVOKE_RESULTS> is set to the parameters of
that call.
+=item %cf::REFLECT
+
+Contains, for each (C++) class name, a hash reference with information
+about object members (methods, scalars, arrays and flags) and other
+metadata, which is useful for introspection.
+
=back
=cut
-BEGIN {
- *CORE::GLOBAL::warn = sub {
- my $msg = join "", @_;
+$Coro::State::WARNHOOK = sub {
+ my $msg = join "", @_;
- $msg .= "\n"
- unless $msg =~ /\n$/;
+ $msg .= "\n"
+ unless $msg =~ /\n$/;
- $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge;
+ $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge;
- LOG llevError, $msg;
- };
-}
+ LOG llevError, $msg;
+};
$Coro::State::DIEHOOK = sub {
return unless $^S eq 0; # "eq", not "=="
- if ($Coro::current == $Coro::main) {#d#
+ warn Carp::longmess $_[0];
+
+ if (in_main) {#d#
warn "DIEHOOK called in main context, Coro bug?\n";#d#
return;#d#
}#d#
# kill coroutine otherwise
- warn Carp::longmess $_[0];
Coro::terminate
};
-$SIG{__DIE__} = sub { }; #d#?
-
@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
@safe::cf::object::ISA = @cf::object::ISA = 'cf::attachable';
@safe::cf::player::ISA = @cf::player::ISA = 'cf::attachable';
@@ -270,7 +298,7 @@
}
$EV::DIED = sub {
- warn "error in event callback: @_";
+ Carp::cluck "error in event callback: @_";
};
#############################################################################
@@ -377,7 +405,7 @@
=item cf::periodic $interval, $cb
Like EV::periodic, but randomly selects a starting point so that the actions
-get spread over timer.
+get spread over time.
=cut
@@ -404,24 +432,29 @@
our @SLOT_QUEUE;
our $SLOT_QUEUE;
+our $SLOT_DECAY = 0.9;
$SLOT_QUEUE->cancel if $SLOT_QUEUE;
$SLOT_QUEUE = Coro::async {
$Coro::current->desc ("timeslot manager");
my $signal = new Coro::Signal;
+ my $busy;
while () {
next_job:
+
my $avail = cf::till_tick;
- if ($avail > 0.01) {
- for (0 .. $#SLOT_QUEUE) {
- if ($SLOT_QUEUE[$_][0] < $avail) {
- my $job = splice @SLOT_QUEUE, $_, 1, ();
- $job->[2]->send;
- Coro::cede;
- goto next_job;
- }
+
+ for (0 .. $#SLOT_QUEUE) {
+ if ($SLOT_QUEUE[$_][0] <= $avail) {
+ $busy = 0;
+ my $job = splice @SLOT_QUEUE, $_, 1, ();
+ $job->[2]->send;
+ Coro::cede;
+ goto next_job;
+ } else {
+ $SLOT_QUEUE[$_][0] *= $SLOT_DECAY;
}
}
@@ -430,6 +463,7 @@
push @cf::WAIT_FOR_TICK, $signal;
$signal->wait;
} else {
+ $busy = 0;
Coro::schedule;
}
}
@@ -440,7 +474,8 @@
my ($time, $pri, $name) = @_;
- $time = $TICK * .6 if $time > $TICK * .6;
+ $time = clamp $time, 0.01, $TICK * .6;
+
my $sig = new Coro::Signal;
push @SLOT_QUEUE, [$time, $pri, $sig, $name];
@@ -477,7 +512,7 @@
my ($job) = @_;
if ($Coro::current == $Coro::main) {
- my $time = EV::time;
+ my $time = AE::time;
# this is the main coro, too bad, we have to block
# till the operation succeeds, freezing the server :/
@@ -504,7 +539,7 @@
}
}
- my $time = EV::time - $time;
+ my $time = AE::time - $time;
$TICK_START += $time; # do not account sync jobs to server load
@@ -560,6 +595,17 @@
wantarray ? @res : $res[-1]
}
+sub objinfo {
+ (
+ "counter value" => cf::object::object_count,
+ "objects created" => cf::object::create_count,
+ "objects destroyed" => cf::object::destroy_count,
+ "freelist size" => cf::object::free_count,
+ "allocated objects" => cf::object::objects_size,
+ "active objects" => cf::object::actives_size,
+ )
+}
+
=item $coin = coin_from_name $name
=cut
@@ -1153,7 +1199,10 @@
if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
aio_chmod $fh, SAVE_MODE;
aio_write $fh, 0, (length $$rdata), $$rdata, 0;
- aio_fsync $fh if $cf::USE_FSYNC;
+ if ($cf::USE_FSYNC) {
+ 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;
+ aio_fsync $fh;
+ }
aio_close $fh;
if (@$objs) {
@@ -1161,7 +1210,10 @@
aio_chmod $fh, SAVE_MODE;
my $data = Coro::Storable::nfreeze { version => 1, objs => $objs };
aio_write $fh, 0, (length $data), $data, 0;
- aio_fsync $fh if $cf::USE_FSYNC;
+ if ($cf::USE_FSYNC) {
+ 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;
+ aio_fsync $fh;
+ }
aio_close $fh;
aio_rename "$filename.pst~", "$filename.pst";
}
@@ -1278,7 +1330,7 @@
use File::Glob ();
cf::player->attach (
- on_command => sub {
+ on_unknown_command => sub {
my ($pl, $name, $params) = @_;
my $cb = $COMMAND{$name}
@@ -1322,7 +1374,7 @@
sub cache_extensions {
my $grp = IO::AIO::aio_group;
- add $grp IO::AIO::aio_readdir $LIBDIR, sub {
+ add $grp IO::AIO::aio_readdirx $LIBDIR, IO::AIO::READDIR_STAT_ORDER, sub {
for (grep /\.ext$/, @{$_[0]}) {
add $grp IO::AIO::aio_load "$LIBDIR/$_", my $data;
}
@@ -1367,38 +1419,50 @@
$todo{$base} = \%ext;
}
+ my $pass = 0;
my %done;
while (%todo) {
my $progress;
+ ++$pass;
+
+ ext:
while (my ($k, $v) = each %todo) {
for (split /,\s*/, $v->{meta}{depends}) {
- goto skip
+ next ext
unless exists $done{$_};
}
- warn "... loading '$k' into '$v->{pkg}'\n";
+ warn "... pass $pass, loading '$k' into '$v->{pkg}'\n";
- unless (eval $v->{source}) {
- my $msg = $@ ? "$v->{path}: $@\n"
- : "$v->{base}: extension inactive.\n";
-
- if (exists $v->{meta}{mandatory}) {
- warn $msg;
- cf::cleanup "mandatory extension failed to load, exiting.";
- }
-
- warn $msg;
- }
+ my $active = eval $v->{source};
+
+ if (length $@) {
+ warn "$v->{path}: $@\n";
+
+ cf::cleanup "mandatory extension '$k' failed to load, exiting."
+ if exists $v->{meta}{mandatory};
+
+ warn "$v->{base}: optional extension cannot be loaded, skipping.\n";
+ delete $todo{$k};
+ } else {
+ $done{$k} = delete $todo{$k};
+ push @EXTS, $v->{pkg};
+ $progress = 1;
- $done{$k} = delete $todo{$k};
- push @EXTS, $v->{pkg};
- $progress = 1;
+ warn "$v->{base}: extension inactive.\n"
+ unless $active;
+ }
}
- skip:
- die "cannot load " . (join ", ", keys %todo) . ": unable to resolve dependencies\n"
- unless $progress;
+ unless ($progress) {
+ warn "cannot load " . (join ", ", keys %todo) . ": unable to resolve dependencies\n";
+
+ while (my ($k, $v) = each %todo) {
+ cf::cleanup "mandatory extension '$k' has unresolved dependencies, exiting."
+ if exists $v->{meta}{mandatory};
+ }
+ }
}
};
}
@@ -1492,6 +1556,19 @@
}
}
+cf::player->attach (
+ on_load => sub {
+ my ($pl, $path) = @_;
+
+ # restore slots saved in save, below
+ my $slots = delete $pl->{_slots};
+
+ $pl->ob->current_weapon ($slots->[0]);
+ $pl->combat_ob ($slots->[1]);
+ $pl->ranged_ob ($slots->[2]);
+ },
+);
+
sub save($) {
my ($pl) = @_;
@@ -1507,6 +1584,9 @@
cf::get_slot 0.01;
+ # save slots, to be restored later
+ local $pl->{_slots} = [$pl->ob->current_weapon, $pl->combat_ob, $pl->ranged_ob];
+
$pl->save_pl ($path);
cf::cede_to_tick;
}
@@ -1617,6 +1697,8 @@
=item $player->maps
+=item cf::player::maps $login
+
Returns an arrayref of map paths that are private for this
player. May block.
@@ -1688,6 +1770,8 @@
sub find_by_path($) {
my ($path) = @_;
+ $path =~ s/^~[^\/]*//; # skip ~login
+
my ($match, $specificity);
for my $region (list) {
@@ -1728,7 +1812,7 @@
# mit "rum" bekleckern, nicht
$self->_create_random_map (
$rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
- $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
+ $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle}, $rmp->{miningstyle},
$rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
$rmp->{exit_on_final_map},
$rmp->{xsize}, $rmp->{ysize},
@@ -1760,7 +1844,7 @@
}
# also paths starting with '/'
-$EXT_MAP{"cf::map"} = [0, qr{^(?=/)}];
+$EXT_MAP{"cf::map::wrap"} = [0, qr{^(?=/)}];
sub thawer_merge {
my ($self, $merge) = @_;
@@ -1984,8 +2068,8 @@
}
}
-sub pre_load { }
-sub post_load { }
+sub pre_load { }
+#sub post_load { } # XS
sub load {
my ($self) = @_;
@@ -2052,6 +2136,9 @@
$self->post_load;
}
+# customize the map for a given player, i.e.
+# return the _real_ map. used by e.g. per-player
+# maps to change the path to ~playername/mappath
sub customise_for {
my ($self, $ob) = @_;
@@ -2138,11 +2225,10 @@
()
}
-sub save {
+# common code, used by both ->save and ->swapout
+sub _save {
my ($self) = @_;
- my $lock = cf::lock_acquire "map_data:$self->{path}";
-
$self->{last_save} = $cf::RUNTIME;
return unless $self->dirty;
@@ -2171,22 +2257,32 @@
}
}
-sub swap_out {
+sub save {
my ($self) = @_;
- # save first because save cedes
- $self->save;
+ my $lock = cf::lock_acquire "map_data:$self->{path}";
+
+ $self->_save;
+}
+
+sub swap_out {
+ my ($self) = @_;
my $lock = cf::lock_acquire "map_data:$self->{path}";
- return if $self->players;
return if $self->in_memory != cf::MAP_ACTIVE;
return if $self->{deny_save};
+ return if $self->players;
- $self->in_memory (cf::MAP_SWAPPED);
-
+ # first deactivate the map and "unlink" it from the core
$self->deactivate;
$_->clear_links_to ($self) for values %cf::MAP;
+ $self->in_memory (cf::MAP_SWAPPED);
+
+ # then atomically save
+ $self->_save;
+
+ # then free the map
$self->clear;
}
@@ -2215,7 +2311,7 @@
return if $self->players;
- warn "resetting map ", $self->path;
+ warn "resetting map ", $self->path, "\n";
$self->in_memory (cf::MAP_SWAPPED);
@@ -2249,7 +2345,7 @@
$self->unlink_save;
- bless $self, "cf::map";
+ bless $self, "cf::map::wrap";
delete $self->{deny_reset};
$self->{deny_save} = 1;
$self->reset_timeout (1);
@@ -2316,6 +2412,38 @@
]
}
+=item cf::map::static_maps
+
+Returns an arrayref if paths of all static maps (all preinstalled F<.map>
+file in the shared directory excluding F and F). May
+block.
+
+=cut
+
+sub static_maps() {
+ my @dirs = "";
+ my @maps;
+
+ while (@dirs) {
+ my $dir = shift @dirs;
+
+ next if $dir eq "/styles" || $dir eq "/editor";
+
+ my ($dirs, $files) = Coro::AIO::aio_scandir "$MAPDIR$dir", 2
+ or return;
+
+ for (@$files) {
+ s/\.map$// or next;
+ utf8::decode $_;
+ push @maps, "$dir/$_";
+ }
+
+ push @dirs, map "$dir/$_", @$dirs;
+ }
+
+ \@maps
+}
+
=back
=head3 cf::object
@@ -2493,7 +2621,7 @@
$self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y]
if $self->map && $self->map->{path} ne "{link}";
- $self->enter_map ($LINK_MAP || link_map, 10, 10);
+ $self->enter_map ($LINK_MAP || link_map, 3, 3);
}
sub cf::object::player::leave_link {
@@ -2520,17 +2648,18 @@
# use -1 or undef as default coordinates, not 0, 0
($x, $y) = ($map->enter_x, $map->enter_y)
- if $x <=0 && $y <= 0;
+ if $x <= 0 && $y <= 0;
$map->load;
$map->load_neighbours;
return unless $self->contr->active;
- $self->flag (cf::FLAG_DEBUG, 0);#d# temp
- $self->activate_recursive;
local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext
$self->enter_map ($map, $x, $y);
+
+ # only activate afterwards, to support waiting in hooks
+ $self->activate_recursive;
}
=item $player_object->goto ($path, $x, $y[, $check->($map)[, $done->()]])
@@ -2728,6 +2857,31 @@
$self->send_packet (sprintf "drawinfo %d %s", $flags || cf::NDI_BLACK, $text);
}
+=item $client->send_big_packet ($pkt)
+
+Like C, but tries to compress large packets, and fragments
+them as required.
+
+=cut
+
+our $MAXFRAGSIZE = cf::MAXSOCKBUF - 64;
+
+sub cf::client::send_big_packet {
+ my ($self, $pkt) = @_;
+
+ # try lzf for large packets
+ $pkt = "lzf " . Compress::LZF::compress $pkt
+ if 1024 <= length $pkt and $self->{can_lzf};
+
+ # split very large packets
+ if ($MAXFRAGSIZE < length $pkt and $self->{can_lzf}) {
+ $self->send_packet ("frag $_") for unpack "(a$MAXFRAGSIZE)*", $pkt;
+ $pkt = "frag";
+ }
+
+ $self->send_packet ($pkt);
+}
+
=item $client->send_msg ($channel, $msg, $color, [extra...])
Send a drawinfo or msg packet to the client, formatting the msg for the
@@ -2739,6 +2893,12 @@
# non-persistent channels (usually the info channel)
our %CHANNEL = (
+ "c/motd" => {
+ id => "infobox",
+ title => "MOTD",
+ reply => undef,
+ tooltip => "The message of the day",
+ },
"c/identify" => {
id => "infobox",
title => "Identify",
@@ -2751,6 +2911,12 @@
reply => undef,
tooltip => "Signs and other items you examined",
},
+ "c/shopinfo" => {
+ id => "infobox",
+ title => "Shop Info",
+ reply => undef,
+ tooltip => "What your bargaining skill tells you about the shop",
+ },
"c/book" => {
id => "infobox",
title => "Book",
@@ -2805,6 +2971,12 @@
reply => undef,
tooltip => "Shows information abotu your pets/a specific pet",
},
+ "c/perceiveself" => {
+ id => "infobox",
+ title => "Perceive Self",
+ reply => undef,
+ tooltip => "You gained detailed knowledge about yourself",
+ },
"c/uptime" => {
id => "infobox",
title => "Uptime",
@@ -2869,17 +3041,7 @@
[$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra]
);
- # try lzf for large packets
- $pkt = "lzf " . Compress::LZF::compress $pkt
- if 1024 <= length $pkt and $self->{can_lzf};
-
- # split very large packets
- if (8192 < length $pkt and $self->{can_lzf}) {
- $self->send_packet ("frag $_") for unpack "(a8192)*", $pkt;
- $pkt = "frag";
- }
-
- $self->send_packet ($pkt);
+ $self->send_big_packet ($pkt);
}
=item $client->ext_msg ($type, @msg)
@@ -2892,10 +3054,10 @@
my ($self, $type, @msg) = @_;
if ($self->extcmd == 2) {
- $self->send_packet ("ext " . $self->{json_coder}->encode ([$type, @msg]));
+ $self->send_big_packet ("ext " . $self->{json_coder}->encode ([$type, @msg]));
} elsif ($self->extcmd == 1) { # TODO: remove
push @msg, msgtype => "event_$type";
- $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
+ $self->send_big_packet ("ext " . $self->{json_coder}->encode ({@msg}));
}
}
@@ -2909,11 +3071,11 @@
my ($self, $id, @msg) = @_;
if ($self->extcmd == 2) {
- $self->send_packet ("ext " . $self->{json_coder}->encode (["reply-$id", @msg]));
+ $self->send_big_packet ("ext " . $self->{json_coder}->encode (["reply-$id", @msg]));
} elsif ($self->extcmd == 1) {
#TODO: version 1, remove
unshift @msg, msgtype => "reply", msgid => $id;
- $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
+ $self->send_big_packet ("ext " . $self->{json_coder}->encode ({@msg}));
}
}
@@ -3024,7 +3186,7 @@
}
cf::client->attach (
- on_destroy => sub {
+ on_client_destroy => sub {
my ($ns) = @_;
$_->cancel for values %{ (delete $ns->{_coro}) || {} };
@@ -3050,7 +3212,7 @@
$SIG{FPE} = 'IGNORE';
$safe->permit_only (Opcode::opset qw(
- :base_core :base_mem :base_orig :base_math
+ :base_core :base_mem :base_orig :base_math :base_loop
grepstart grepwhile mapstart mapwhile
sort time
));
@@ -3112,8 +3274,8 @@
%vars = (_dummy => 0) unless %vars;
+ my @res;
local $_;
- local @safe::cf::_safe_eval_args = values %vars;
my $eval =
"do {\n"
@@ -3123,9 +3285,15 @@
. "\n}"
;
- sub_generation_inc;
- my @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval);
- sub_generation_inc;
+ if ($CFG{safe_eval}) {
+ sub_generation_inc;
+ local @safe::cf::_safe_eval_args = values %vars;
+ @res = wantarray ? $safe->reval ($eval) : scalar $safe->reval ($eval);
+ sub_generation_inc;
+ } else {
+ local @cf::_safe_eval_args = values %vars;
+ @res = wantarray ? eval eval : scalar eval $eval;
+ }
if ($@) {
warn "$@";
@@ -3154,8 +3322,8 @@
sub register_script_function {
my ($fun, $cb) = @_;
- no strict 'refs';
- *{"safe::$fun"} = $safe_hole->wrap ($cb);
+ $fun = "safe::$fun" if $CFG{safe_eval};
+ *$fun = $safe_hole->wrap ($cb);
}
=back
@@ -3186,9 +3354,11 @@
or cf::cleanup "$path: version mismatch, cannot proceed.";
# patch in the exptable
+ my $exp_table = $enc->encode ([map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]);
$facedata->{resource}{"res/exp_table"} = {
type => FT_RSRC,
- data => $enc->encode ([map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]),
+ data => $exp_table,
+ hash => (Digest::MD5::md5 $exp_table),
};
cf::cede_to_tick;
@@ -3200,8 +3370,8 @@
cf::face::set_visibility $idx, $info->{visibility};
cf::face::set_magicmap $idx, $info->{magicmap};
- cf::face::set_data $idx, 0, $info->{data32}, Digest::MD5::md5 $info->{data32};
- cf::face::set_data $idx, 1, $info->{data64}, Digest::MD5::md5 $info->{data64};
+ cf::face::set_data $idx, 0, $info->{data32}, $info->{hash32};
+ cf::face::set_data $idx, 1, $info->{data64}, $info->{hash64};
cf::cede_to_tick;
}
@@ -3235,29 +3405,13 @@
}
{
- # TODO: for gcfclient pleasure, we should give resources
- # that gcfclient doesn't grok a >10000 face index.
my $res = $facedata->{resource};
while (my ($name, $info) = each %$res) {
if (defined $info->{type}) {
my $idx = (cf::face::find $name) || cf::face::alloc $name;
- my $data;
-
- if ($info->{type} & 1) {
- # prepend meta info
-
- my $meta = $enc->encode ({
- name => $name,
- %{ $info->{meta} || {} },
- });
- $data = pack "(w/a*)*", $meta, $info->{data};
- } else {
- $data = $info->{data};
- }
-
- cf::face::set_data $idx, 0, $data, Digest::MD5::md5 $data;
+ cf::face::set_data $idx, 0, $info->{data}, $info->{hash};
cf::face::set_type $idx, $info->{type};
} else {
$RESOURCE{$name} = $info;
@@ -3351,11 +3505,13 @@
}
sub reload_config {
+ warn "reloading config file...\n";
+
open my $fh, "<:utf8", "$CONFDIR/config"
or return;
local $/;
- *CFG = YAML::Load <$fh>;
+ *CFG = YAML::XS::Load scalar <$fh>;
$EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37];
@@ -3369,6 +3525,8 @@
};
warn $@ if $@;
}
+
+ warn "finished reloading resource files\n";
}
sub pidfile() {
@@ -3390,6 +3548,15 @@
print $fh $$;
}
+sub main_loop {
+ warn "EV::loop starting\n";
+ if (1) {
+ EV::loop;
+ }
+ warn "EV::loop returned\n";
+ goto &main_loop unless $REALLY_UNLOOP;
+}
+
sub main {
cf::init_globals; # initialise logging
@@ -3398,11 +3565,6 @@
LOG llevInfo, "Copyright (C) 1994 Mark Wedel.";
LOG llevInfo, "Copyright (C) 1992 Frank Tore Johansen.";
- cf::init_experience;
- cf::init_anim;
- cf::init_attackmess;
- cf::init_dynamic;
-
$Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority
# we must not ever block the main coroutine
@@ -3417,15 +3579,20 @@
evthread_start IO::AIO::poll_fileno;
cf::sync_job {
+ cf::init_experience;
+ cf::init_anim;
+ cf::init_attackmess;
+ cf::init_dynamic;
+
+ cf::load_settings;
+ cf::load_materials;
+
reload_resources;
reload_config;
db_init;
- cf::load_settings;
- cf::load_materials;
cf::init_uuid;
cf::init_signals;
- cf::init_commands;
cf::init_skills;
cf::init_beforeplay;
@@ -3437,12 +3604,16 @@
utime time, time, $RUNTIMEFILE;
# no (long-running) fork's whatsoever before this point(!)
+ use POSIX ();
POSIX::close delete $ENV{LOCKUTIL_LOCK_FD} if exists $ENV{LOCKUTIL_LOCK_FD};
(pop @POST_INIT)->(0) while @POST_INIT;
};
- EV::loop;
+ cf::object::thawer::errors_are_fatal 0;
+ warn "parse errors in files are no longer fatal from this point on.\n";
+
+ main_loop;
}
#############################################################################
@@ -3452,13 +3623,15 @@
BEGIN {
our %SIGWATCHER = ();
for my $signal (qw(INT HUP TERM)) {
- $SIGWATCHER{$signal} = EV::signal $signal, sub {
+ $SIGWATCHER{$signal} = AE::signal $signal, sub {
cf::cleanup "SIG$signal";
};
}
}
sub write_runtime_sync {
+ my $t0 = AE::time;
+
# first touch the runtime file to show we are still running:
# the fsync below can take a very very long time.
@@ -3466,7 +3639,7 @@
my $guard = cf::lock_acquire "write_runtime";
- my $fh = aio_open "$RUNTIMEFILE~", O_WRONLY | O_CREAT, 0644
+ my $fh = aio_open "$RUNTIMEFILE~", O_WRONLY | O_CREAT | O_TRUNC, 0644
or return;
my $value = $cf::RUNTIME + 90 + 10;
@@ -3489,7 +3662,7 @@
aio_rename "$RUNTIMEFILE~", $RUNTIMEFILE
and return;
- warn "runtime file written.\n";
+ warn sprintf "runtime file written (%gs).\n", AE::time - $t0;
1
}
@@ -3625,19 +3798,18 @@
_gv_clear *{"$pkg$name"};
# use PApp::Util; PApp::Util::sv_dump *{"$pkg$name"};
}
- warn "cleared package $pkg\n";#d#
}
sub do_reload_perl() {
# can/must only be called in main
- if ($Coro::current != $Coro::main) {
+ if (in_main) {
warn "can only reload from main coroutine";
return;
}
return if $RELOAD++;
- my $t1 = EV::time;
+ my $t1 = AE::time;
while ($RELOAD) {
warn "reloading...";
@@ -3718,7 +3890,7 @@
warn "reloading cf.pm";
require cf;
- cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
+ cf::_connect_to_perl_1;
warn "loading config and database again";
cf::reload_config;
@@ -3750,7 +3922,7 @@
--$RELOAD;
}
- $t1 = EV::time - $t1;
+ $t1 = AE::time - $t1;
warn "reload completed in ${t1}s\n";
};
@@ -3763,7 +3935,7 @@
$RELOAD_WATCHER ||= cf::async {
Coro::AIO::aio_wait cache_extensions;
- $RELOAD_WATCHER = EV::timer $TICK * 1.5, 0, sub {
+ $RELOAD_WATCHER = AE::timer $TICK * 1.5, 0, sub {
do_reload_perl;
undef $RELOAD_WATCHER;
};
@@ -3790,7 +3962,7 @@
our @WAIT_FOR_TICK_BEGIN;
sub wait_for_tick {
- return if tick_inhibit || $Coro::current == $Coro::main;
+ return Coro::cede if tick_inhibit || $Coro::current == $Coro::main;
my $signal = new Coro::Signal;
push @WAIT_FOR_TICK, $signal;
@@ -3798,7 +3970,7 @@
}
sub wait_for_tick_begin {
- return if tick_inhibit || $Coro::current == $Coro::main;
+ return Coro::cede if tick_inhibit || $Coro::current == $Coro::main;
my $signal = new Coro::Signal;
push @WAIT_FOR_TICK_BEGIN, $signal;
@@ -3814,6 +3986,8 @@
cf::server_tick; # one server iteration
+ #for(1..3e6){} AE::now_update; $NOW=AE::now; # generate load #d#
+
if ($NOW >= $NEXT_RUNTIME_WRITE) {
$NEXT_RUNTIME_WRITE = List::Util::max $NEXT_RUNTIME_WRITE + 10, $NOW + 5.;
Coro::async_pool {
@@ -3845,7 +4019,7 @@
{
# configure BDB
- BDB::min_parallel 8;
+ BDB::min_parallel 16;
BDB::max_poll_reqs $TICK * 0.1;
$AnyEvent::BDB::WATCHER->priority (1);
@@ -3936,6 +4110,7 @@
# load additional modules
require "cf/$_.pm" for @EXTRA_MODULES;
+cf::_connect_to_perl_2;
END { cf::emergency_save }