--- deliantra/server/lib/cf.pm 2009/01/08 04:35:04 1.467 +++ deliantra/server/lib/cf.pm 2009/10/16 01:56:41 1.487 @@ -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 # -# 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; @@ -55,7 +56,7 @@ 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; @@ -107,14 +113,14 @@ our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!) our $NEXT_RUNTIME_WRITE; # when should the runtime file be written our $NEXT_TICK; -our $USE_FSYNC = 1; # use fsync to write maps - default off +our $USE_FSYNC = 1; # use fsync to write maps - default on our $BDB_DEADLOCK_WATCHER; our $BDB_CHECKPOINT_WATCHER; our $BDB_TRICKLE_WATCHER; our $DB_ENV; -our @EXTRA_MODULES = qw(pod mapscript); +our @EXTRA_MODULES = qw(pod match mapscript); our %CFG; @@ -136,7 +142,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; @@ -159,6 +166,17 @@ ############################################################################# +%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 =over 4 @@ -216,38 +234,41 @@ 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 "==" + warn Carp::longmess $_[0]; + if ($Coro::current == $Coro::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'; @@ -1153,7 +1174,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 +1185,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"; } @@ -1322,7 +1349,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; } @@ -2215,7 +2242,7 @@ return if $self->players; - warn "resetting map ", $self->path; + warn "resetting map ", $self->path, "\n"; $self->in_memory (cf::MAP_SWAPPED); @@ -2390,7 +2417,7 @@ id => "say", title => "Map", reply => "say ", - tooltip => "Things said to and replied from npcs near you and other players on the same map only.", + tooltip => "Things said to and replied from NPCs near you and other players on the same map only.", }; our $CHAT_CHANNEL = { @@ -2526,11 +2553,12 @@ $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->()]]) @@ -2739,6 +2767,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 +2785,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", @@ -2787,6 +2827,12 @@ reply => undef, tooltip => "Shows your experience per skill and item power", }, + "c/shopitems" => { + id => "infobox", + title => "Shop Items", + reply => undef, + tooltip => "Shows the items currently for sale in this shop", + }, "c/resistances" => { id => "infobox", title => "Resistances", @@ -2799,6 +2845,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", @@ -3345,11 +3397,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]; @@ -3363,6 +3417,8 @@ }; warn $@ if $@; } + + warn "finished reloading resource files\n"; } sub pidfile() { @@ -3384,6 +3440,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 @@ -3431,12 +3496,13 @@ 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; + main_loop; } ############################################################################# @@ -3712,7 +3778,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; @@ -3930,6 +3996,7 @@ # load additional modules require "cf/$_.pm" for @EXTRA_MODULES; +cf::_connect_to_perl_2; END { cf::emergency_save }