--- deliantra/server/lib/cf.pm 2008/01/14 10:12:12 1.408
+++ deliantra/server/lib/cf.pm 2008/09/20 00:09:27 1.449
@@ -1,26 +1,50 @@
+#
+# 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.
+#
+# 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 .
+#
+# The authors can be reached via e-mail to
+#
+
package cf;
+use 5.10.0;
use utf8;
-use strict;
+use strict "vars", "subs";
use Symbol;
use List::Util;
use Socket;
-use EV 1.86;
+use EV;
use Opcode;
use Safe;
use Safe::Hole;
use Storable ();
-use Coro 4.32 ();
+use Coro ();
use Coro::State;
use Coro::Handle;
use Coro::EV;
+use Coro::AnyEvent;
use Coro::Timer;
use Coro::Signal;
use Coro::Semaphore;
+use Coro::AnyEvent;
use Coro::AIO;
-use Coro::BDB;
+use Coro::BDB 1.6;
use Coro::Storable;
use Coro::Util ();
@@ -30,19 +54,24 @@
use Digest::MD5;
use Fcntl;
use YAML ();
-use IO::AIO 2.51 ();
+use IO::AIO ();
use Time::HiRes;
use Compress::LZF;
use Digest::MD5 ();
+AnyEvent::detect;
+
# configure various modules to our taste
#
$Storable::canonical = 1; # reduce rsync transfers
Coro::State::cctx_stacksize 256000; # 1-2MB stack, for deep recursions in maze generator
-Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later
$Coro::main->prio (Coro::PRIO_MAX); # run main coroutine ("the server") with very high priority
+# make sure c-lzf reinitialises itself
+Compress::LZF::set_serializer "Storable", "Storable::net_mstore", "Storable::mretrieve";
+Compress::LZF::sfreeze_cr { }; # prime Compress::LZF so it does not use require later
+
sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
our %COMMAND = ();
@@ -57,27 +86,27 @@
our $RELOAD; # number of reloads so far
our @EVENT;
-our $CONFDIR = confdir;
-our $DATADIR = datadir;
-our $LIBDIR = "$DATADIR/ext";
-our $PODDIR = "$DATADIR/pod";
-our $MAPDIR = "$DATADIR/" . mapdir;
-our $LOCALDIR = localdir;
-our $TMPDIR = "$LOCALDIR/" . tmpdir;
-our $UNIQUEDIR = "$LOCALDIR/" . uniquedir;
-our $PLAYERDIR = "$LOCALDIR/" . playerdir;
-our $RANDOMDIR = "$LOCALDIR/random";
-our $BDBDIR = "$LOCALDIR/db";
+our $CONFDIR = confdir;
+our $DATADIR = datadir;
+our $LIBDIR = "$DATADIR/ext";
+our $PODDIR = "$DATADIR/pod";
+our $MAPDIR = "$DATADIR/" . mapdir;
+our $LOCALDIR = localdir;
+our $TMPDIR = "$LOCALDIR/" . tmpdir;
+our $UNIQUEDIR = "$LOCALDIR/" . uniquedir;
+our $PLAYERDIR = "$LOCALDIR/" . playerdir;
+our $RANDOMDIR = "$LOCALDIR/random";
+our $BDBDIR = "$LOCALDIR/db";
+our $PIDFILE = "$LOCALDIR/pid";
+our $RUNTIMEFILE = "$LOCALDIR/runtime";
+
our %RESOURCE;
our $TICK = MAX_TIME * 1e-6; # this is a CONSTANT(!)
-our $TICK_WATCHER;
-our $AIO_POLL_WATCHER;
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 $BDB_POLL_WATCHER;
our $BDB_DEADLOCK_WATCHER;
our $BDB_CHECKPOINT_WATCHER;
our $BDB_TRICKLE_WATCHER;
@@ -98,15 +127,16 @@
our $LOAD; # a number between 0 (idle) and 1 (too many objects)
our $LOADAVG; # same thing, but with alpha-smoothing
-our $tick_start; # for load detecting purposes
+our $JITTER; # average jitter
+our $TICK_START; # for load detecting purposes
binmode STDOUT;
binmode STDERR;
# read virtual server time, if available
-unless ($RUNTIME || !-e "$LOCALDIR/runtime") {
- open my $fh, "<", "$LOCALDIR/runtime"
- or die "unable to read runtime file: $!";
+unless ($RUNTIME || !-e $RUNTIMEFILE) {
+ open my $fh, "<", $RUNTIMEFILE
+ or die "unable to read $RUNTIMEFILE file: $!";
$RUNTIME = <$fh> + 0.;
}
@@ -194,10 +224,20 @@
}
$Coro::State::DIEHOOK = sub {
+ return unless $^S eq 0; # "eq", not "=="
+
+ 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;
+ 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';
@@ -213,9 +253,9 @@
cf::object cf::object::player
cf::client cf::player
cf::arch cf::living
- cf::map cf::party cf::region
+ cf::map cf::mapspace
+ cf::party cf::region
)) {
- no strict 'refs';
@{"safe::$pkg\::wrap::ISA"} = @{"$pkg\::wrap::ISA"} = $pkg;
}
@@ -331,13 +371,9 @@
}
sub freeze_mainloop {
- return unless $TICK_WATCHER->is_active;
+ tick_inhibit_inc;
- my $guard = Coro::guard {
- $TICK_WATCHER->start;
- };
- $TICK_WATCHER->stop;
- $guard
+ Coro::guard \&tick_inhibit_dec;
}
=item cf::periodic $interval, $cb
@@ -402,6 +438,8 @@
};
sub get_slot($;$$) {
+ return if tick_inhibit || $Coro::current == $Coro::main;
+
my ($time, $pri, $name) = @_;
$time = $TICK * .6 if $time > $TICK * .6;
@@ -448,8 +486,6 @@
LOG llevError, Carp::longmess "sync job";#d#
- # TODO: use suspend/resume instead
- # (but this is cancel-safe)
my $freeze_guard = freeze_mainloop;
my $busy = 1;
@@ -470,12 +506,9 @@
}
}
- $time = EV::time - $time;
+ my $time = EV::time - $time;
- LOG llevError | logBacktrace, Carp::longmess "long sync job"
- if $time > $TICK * 0.5 && $TICK_WATCHER->is_active;
-
- $tick_start += $time; # do not account sync jobs to server load
+ $TICK_START += $time; # do not account sync jobs to server load
wantarray ? @res : $res[0]
} else {
@@ -529,6 +562,33 @@
wantarray ? @res : $res[-1]
}
+=item $coin = coin_from_name $name
+
+=cut
+
+our %coin_alias = (
+ "silver" => "silvercoin",
+ "silvercoin" => "silvercoin",
+ "silvercoins" => "silvercoin",
+ "gold" => "goldcoin",
+ "goldcoin" => "goldcoin",
+ "goldcoins" => "goldcoin",
+ "platinum" => "platinacoin",
+ "platinumcoin" => "platinacoin",
+ "platinumcoins" => "platinacoin",
+ "platina" => "platinacoin",
+ "platinacoin" => "platinacoin",
+ "platinacoins" => "platinacoin",
+ "royalty" => "royalty",
+ "royalties" => "royalty",
+);
+
+sub coin_from_name($) {
+ $coin_alias{$_[0]}
+ ? cf::arch::find $coin_alias{$_[0]}
+ : undef
+}
+
=item $value = cf::db_get $family => $key
Returns a single value from the environment database.
@@ -675,7 +735,7 @@
=head2 ATTACHABLE OBJECTS
-Many objects in crossfire are so-called attachable objects. That means you can
+Many objects in deliantra are so-called attachable objects. That means you can
attach callbacks/event handlers (a collection of which is called an "attachment")
to it. All such attachable objects support the following methods.
@@ -735,7 +795,7 @@
Register an attachment by C<$name> through which attachable objects of the
given CLASS can refer to this attachment.
-Some classes such as crossfire maps and objects can specify attachments
+Some classes such as deliantra maps and objects can specify attachments
that are attached at load/instantiate time, thus the need for a name.
These calls expect any number of the following handler/hook descriptions:
@@ -1036,7 +1096,8 @@
# basically do the same as instantiate, without calling instantiate
my ($obj) = @_;
- bless $obj, ref $obj; # re-bless in case extensions have been reloaded
+ # no longer needed after getting rid of delete_package?
+ #bless $obj, ref $obj; # re-bless in case extensions have been reloaded
my $registry = $obj->registry;
@@ -1092,18 +1153,18 @@
$decname, length $$rdata, scalar @$objs;
if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
- chmod SAVE_MODE, $fh;
+ aio_chmod $fh, SAVE_MODE;
aio_write $fh, 0, (length $$rdata), $$rdata, 0;
aio_fsync $fh if $cf::USE_FSYNC;
- close $fh;
+ aio_close $fh;
if (@$objs) {
if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) {
- chmod SAVE_MODE, $fh;
+ 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;
- close $fh;
+ aio_close $fh;
aio_rename "$filename.pst~", "$filename.pst";
}
} else {
@@ -1282,7 +1343,7 @@
if $source =~ /\A#!.*?perl.*?#\s*(.*)$/m;
$ext{source} =
- "package $pkg; use strict; use utf8;\n"
+ "package $pkg; use 5.10.0; use strict 'vars', 'subs'; use utf8;\n"
. "#line 1 \"$path\"\n{\n"
. $source
. "\n};\n1";
@@ -1308,8 +1369,7 @@
if (exists $v->{meta}{mandatory}) {
warn $msg;
- warn "mandatory extension failed to load, exiting.\n";
- exit 1;
+ cf::cleanup "mandatory extension failed to load, exiting.";
}
warn $msg;
@@ -1333,7 +1393,7 @@
=head2 CORE EXTENSIONS
-Functions and methods that extend core crossfire objects.
+Functions and methods that extend core deliantra objects.
=cut
@@ -1408,6 +1468,7 @@
my $pl = cf::player::load_pl $f
or return;
+
local $cf::PLAYER_LOADING{$login} = $pl;
$f->resolve_delayed_derefs;
$cf::PLAYER{$login} = $pl
@@ -1428,6 +1489,8 @@
aio_mkdir playerdir $pl, 0770;
$pl->{last_save} = $cf::RUNTIME;
+ cf::get_slot 0.01;
+
$pl->save_pl ($path);
cf::cede_to_tick;
}
@@ -1468,10 +1531,12 @@
my $name = $pl->ob->name;
$pl->{deny_save} = 1;
- $pl->password ("*"); # this should lock out the player until we nuked the dir
+ $pl->password ("*"); # this should lock out the player until we have nuked the dir
$pl->invoke (cf::EVENT_PLAYER_LOGOUT, 1) if $pl->active;
$pl->deactivate;
+ my $killer = cf::arch::get "killer_quit"; $pl->killer ($killer); $killer->destroy;
+ $pl->ob->check_score;
$pl->invoke (cf::EVENT_PLAYER_QUIT);
$pl->ns->destroy if $pl->ns;
@@ -1520,7 +1585,8 @@
my $path = path $login;
# a .pst is a dead give-away for a valid player
- unless (-e "$path.pst") {
+ # if no pst file found, open and chekc for blocked users
+ if (aio_stat "$path.pst") {
my $fh = aio_open $path, Fcntl::O_RDONLY, 0 or next;
aio_read $fh, 0, 512, my $buf, 0 or next;
$buf !~ /^password -------------$/m or next; # official not-valid tag
@@ -1561,98 +1627,9 @@
\@paths
}
-=item $protocol_xml = $player->expand_cfpod ($crossfire_pod)
-
-Expand crossfire pod fragments into protocol xml.
-
-=cut
-
-use re 'eval';
-
-my $group;
-my $interior; $interior = qr{
- # match a pod interior sequence sans C<< >>
- (?:
- \ (.*?)\ (?{ $group = $^N })
- | < (??{$interior}) >
- )
-}x;
-
-sub expand_cfpod {
- my ($self, $pod) = @_;
-
- my $xml;
-
- while () {
- if ($pod =~ /\G( (?: [^BCGHITU]+ | .(?!<) )+ )/xgcs) {
- $group = $1;
-
- $group =~ s/&/&/g;
- $group =~ s/</g;
-
- $xml .= $group;
- } elsif ($pod =~ m%\G
- ([BCGHITU])
- <
- (?:
- ([^<>]*) (?{ $group = $^N })
- | < $interior >
- )
- >
- %gcsx
- ) {
- my ($code, $data) = ($1, $group);
-
- if ($code eq "B") {
- $xml .= "" . expand_cfpod ($self, $data) . "";
- } elsif ($code eq "I") {
- $xml .= "" . expand_cfpod ($self, $data) . "";
- } elsif ($code eq "U") {
- $xml .= "" . expand_cfpod ($self, $data) . "";
- } elsif ($code eq "C") {
- $xml .= "" . expand_cfpod ($self, $data) . "";
- } elsif ($code eq "T") {
- $xml .= "" . expand_cfpod ($self, $data) . "";
- } elsif ($code eq "G") {
- my ($male, $female) = split /\|/, $data;
- $data = $self->gender ? $female : $male;
- $xml .= expand_cfpod ($self, $data);
- } elsif ($code eq "H") {
- $xml .= ("[" . expand_cfpod ($self, $data) . " (Use hintmode to suppress hints)]",
- "[Hint suppressed, see hintmode]",
- "")
- [$self->{hintmode}];
- } else {
- $xml .= "error processing '$code($data)' directive";
- }
- } else {
- if ($pod =~ /\G(.+)/) {
- warn "parse error while expanding $pod (at $1)";
- }
- last;
- }
- }
-
- for ($xml) {
- # create single paragraphs (very hackish)
- s/(?<=\S)\n(?=\w)/ /g;
-
- # compress some whitespace
- s/\s+\n/\n/g; # ws line-ends
- s/\n\n+/\n/g; # double lines
- s/^\n+//; # beginning lines
- s/\n+$//; # ending lines
- }
-
- $xml
-}
-
-no re 'eval';
+=item $protocol_xml = $player->expand_cfpod ($cfpod)
-sub hintmode {
- $_[0]{hintmode} = $_[1] if @_ > 1;
- $_[0]{hintmode}
-}
+Expand deliantra pod fragments into protocol xml.
=item $player->ext_reply ($msgid, @msg)
@@ -1729,6 +1706,9 @@
sub generate_random_map {
my ($self, $rmp) = @_;
+
+ my $lock = cf::lock_acquire "generate_random_map"; # the random map generator is NOT reentrant ATM
+
# mit "rum" bekleckern, nicht
$self->_create_random_map (
$rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
@@ -1860,7 +1840,7 @@
sub save_path {
my ($self) = @_;
- (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/g;
+ (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/go;
"$TMPDIR/$path.map"
}
@@ -1868,7 +1848,7 @@
sub uniq_path {
my ($self) = @_;
- (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/g;
+ (my $path = $_[0]{path}) =~ s/\//$PATH_SEP/go;
"$UNIQUEDIR/$path"
}
@@ -1976,8 +1956,8 @@
cf::lock_wait "map_find:$path";
$cf::MAP{$path} || do {
- my $guard1 = cf::lock_acquire "map_find:$path";
- my $guard2 = cf::lock_acquire "map_data:$path"; # just for the fun of it
+ my $guard1 = cf::lock_acquire "map_data:$path"; # just for the fun of it
+ my $guard2 = cf::lock_acquire "map_find:$path";
my $map = new_from_path cf::map $path
or return;
@@ -1990,8 +1970,8 @@
if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?)
# doing this can freeze the server in a sync job, obviously
#$cf::WAIT_FOR_TICK->wait;
- undef $guard1;
undef $guard2;
+ undef $guard1;
$map->reset;
return find $path;
}
@@ -2028,7 +2008,7 @@
$self->_load_objects ($f)
or return;
- $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
+ $self->post_load_original
if delete $self->{load_original};
if (my $uniq = $self->uniq_path) {
@@ -2064,7 +2044,7 @@
$self->{last_save} = $cf::RUNTIME;
$self->last_access ($cf::RUNTIME);
- $self->in_memory (cf::MAP_IN_MEMORY);
+ $self->in_memory (cf::MAP_ACTIVE);
}
$self->post_load;
@@ -2132,7 +2112,7 @@
$path = normalise $path, $origin && $origin->{path};
if (my $map = $cf::MAP{$path}) {
- return $map if !$load || $map->in_memory == cf::MAP_IN_MEMORY;
+ return $map if !$load || $map->in_memory == cf::MAP_ACTIVE;
}
$MAP_PREFETCH{$path} |= $load;
@@ -2179,6 +2159,8 @@
$_->contr->save for $self->players;
};
+ cf::get_slot 0.02;
+
if ($uniq) {
$self->_save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
$self->_save_objects ($uniq, cf::IO_UNIQUES);
@@ -2196,7 +2178,7 @@
my $lock = cf::lock_acquire "map_data:$self->{path}";
return if $self->players;
- return if $self->in_memory != cf::MAP_IN_MEMORY;
+ return if $self->in_memory != cf::MAP_ACTIVE;
return if $self->{deny_save};
$self->in_memory (cf::MAP_SWAPPED);
@@ -2324,15 +2306,14 @@
[
map {
utf8::decode $_;
- /\.map$/
- ? normalise $_
- : ()
+ s/\.map$//; # TODO future compatibility hack
+ /\.pst$/ || !/^$PATH_SEP/o # TODO unique maps apparebntly lack the .map suffix :/
+ ? ()
+ : normalise $_
} @{ aio_readdir $UNIQUEDIR or [] }
]
}
-package cf;
-
=back
=head3 cf::object
@@ -2345,7 +2326,8 @@
=item $ob->inv_recursive
-Returns the inventory of the object _and_ their inventories, recursively.
+Returns the inventory of the object I their inventories, recursively,
+but I the object itself.
=cut
@@ -2360,7 +2342,7 @@
=item $ref = $ob->ref
-creates and returns a persistent reference to an objetc that can be stored as a string.
+Creates and returns a persistent reference to an object that can be stored as a string.
=item $ob = cf::object::deref ($refstring)
@@ -2402,6 +2384,20 @@
=cut
+our $SAY_CHANNEL = {
+ id => "say",
+ title => "Map",
+ reply => "say ",
+ tooltip => "Things said to and replied from npcs near you and other players on the same map only.",
+};
+
+our $CHAT_CHANNEL = {
+ id => "chat",
+ title => "Chat",
+ reply => "chat ",
+ tooltip => "Player chat and shouts, global to the server.",
+};
+
# rough implementation of a future "reply" method that works
# with dialog boxes.
#TODO: the first argument must go, split into a $npc->reply_to ( method
@@ -2422,7 +2418,7 @@
} else {
$msg = $npc->name . " says: $msg" if $npc;
- $self->message ($msg, $flags);
+ $self->send_msg ($SAY_CHANNEL => $msg, $flags);
}
}
}
@@ -2457,8 +2453,10 @@
Freezes the player and moves him/her to a special map (C<{link}>).
-The player should be reasonably safe there for short amounts of time. You
-I call C as soon as possible, though.
+The player should be reasonably safe there for short amounts of time (e.g.
+for loading a map). You I call C as soon as possible,
+though, as the palyer cannot control the character while it is on the link
+map.
Will never block.
@@ -2526,6 +2524,7 @@
$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
@@ -2540,6 +2539,9 @@
the new (success) or old (failed) map position. In either case, $done will
be called at the end of this process.
+Note that $check will be called with a potentially non-loaded map, so if
+it needs a loaded map it has to call C<< ->load >>.
+
=cut
our $GOTOGEN;
@@ -2627,8 +2629,6 @@
sub prepare_random_map {
my ($exit) = @_;
- my $guard = cf::lock_acquire "exit_prepare:$exit";
-
# all this does is basically replace the /! path by
# a new random map path (?random/...) with a seed
# that depends on the exit object
@@ -2640,11 +2640,13 @@
$rmp->{origin_map} = $exit->map->path;
$rmp->{origin_x} = $exit->x;
$rmp->{origin_y} = $exit->y;
+
+ $exit->map->touch;
}
$rmp->{random_seed} ||= $exit->random_seed;
- my $data = cf::encode_json $rmp;
+ my $data = JSON::XS->new->utf8->pretty->canonical->encode ($rmp);
my $md5 = Digest::MD5::md5_hex $data;
my $meta = "$RANDOMDIR/$md5.meta";
@@ -2653,8 +2655,12 @@
undef $fh;
aio_rename "$meta~", $meta;
- $exit->slaying ("?random/$md5");
- $exit->msg (undef);
+ my $slaying = "?random/$md5";
+
+ if ($exit->valid) {
+ $exit->slaying ("?random/$md5");
+ $exit->msg (undef);
+ }
}
}
@@ -2663,33 +2669,35 @@
return unless $self->type == cf::PLAYER;
- if ($exit->slaying eq "/!") {
- #TODO: this should de-fi-ni-te-ly not be a sync-job
- # the problem is that $exit might not survive long enough
- # so it needs to be done right now, right here
- cf::sync_job { prepare_random_map $exit };
- }
-
- my $slaying = cf::map::normalise $exit->slaying, $exit->map && $exit->map->path;
- my $hp = $exit->stats->hp;
- my $sp = $exit->stats->sp;
-
$self->enter_link;
- # if exit is damned, update players death & WoR home-position
- $self->contr->savebed ($slaying, $hp, $sp)
- if $exit->flag (FLAG_DAMNED);
-
(async {
- $Coro::current->{desc} = "enter_exit $slaying $hp $sp";
+ $Coro::current->{desc} = "enter_exit";
- $self->deactivate_recursive; # just to be sure
unless (eval {
- $self->goto ($slaying, $hp, $sp);
+ $self->deactivate_recursive; # just to be sure
- 1;
+ # random map handling
+ {
+ my $guard = cf::lock_acquire "exit_prepare:$exit";
+
+ prepare_random_map $exit
+ if $exit->slaying eq "/!";
+ }
+
+ my $map = cf::map::normalise $exit->slaying, $exit->map && $exit->map->path;
+ my $x = $exit->stats->hp;
+ my $y = $exit->stats->sp;
+
+ $self->goto ($map, $x, $y);
+
+ # if exit is damned, update players death & WoR home-position
+ $self->contr->savebed ($map, $x, $y)
+ if $exit->flag (cf::FLAG_DAMNED);
+
+ 1
}) {
- $self->message ("Something went wrong deep within the crossfire server. "
+ $self->message ("Something went wrong deep within the deliantra server. "
. "I'll try to bring you back to the map you were before. "
. "Please report this to the dungeon master!",
cf::NDI_UNIQUE | cf::NDI_RED);
@@ -2777,12 +2785,19 @@
reply => undef,
tooltip => "Information related to the maps",
},
+ "c/party" => {
+ id => "party",
+ title => "Party",
+ reply => "gsay ",
+ tooltip => "Messages and chat related to your party",
+ },
);
sub cf::client::send_msg {
my ($self, $channel, $msg, $color, @extra) = @_;
- $msg = $self->pl->expand_cfpod ($msg);
+ $msg = $self->pl->expand_cfpod ($msg)
+ unless $color & cf::NDI_VERBATIM;
$color &= cf::NDI_CLIENT_MASK; # just in case...
@@ -2813,8 +2828,22 @@
$color &= ~(cf::NDI_COLOR_MASK | cf::NDI_DEF)
if $color & cf::NDI_DEF;
- $self->send_packet ("msg " . $self->{json_coder}->encode (
- [$color & cf::NDI_CLIENT_MASK, $channel, $msg, @extra]));
+ my $pkt = "msg "
+ . $self->{json_coder}->encode (
+ [$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);
} else {
if ($color >= 0) {
# replace some tags by gcfclient-compatible ones
@@ -3023,7 +3052,7 @@
cf::object
contr pay_amount pay_player map x y force_find force_add destroy
- insert remove name archname title slaying race decrease_ob_nr
+ insert remove name archname title slaying race decrease split
cf::object::player
player
@@ -3038,8 +3067,8 @@
for (
["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y
- insert remove inv name archname title slaying race
- decrease_ob_nr destroy)],
+ insert remove inv nrof name archname title slaying race
+ decrease split destroy change_exp)],
["cf::object::player" => qw(player)],
["cf::player" => qw(peaceful)],
["cf::map" => qw(trigger)],
@@ -3257,6 +3286,16 @@
}
};
+sub load_resource_file($) {
+ my $guard = lock_acquire "load_resource_file";
+
+ my $status = load_resource_file_ $_[0];
+ get_slot 0.1, 100;
+ cf::arch::commit_load;
+
+ $status
+}
+
sub reload_regions {
# HACK to clear player env face cache, we need some signal framework
# for this (global event?)
@@ -3279,11 +3318,6 @@
sub reload_archetypes {
load_resource_file "$DATADIR/archetypes"
or die "unable to load archetypes\n";
- #d# NEED to laod twice to resolve forward references
- # this really needs to be done in an extra post-pass
- # (which needs to be synchronous, so solve it differently)
- load_resource_file "$DATADIR/archetypes"
- or die "unable to load archetypes\n";
}
sub reload_treasures {
@@ -3294,16 +3328,19 @@
sub reload_resources {
warn "reloading resource files...\n";
- reload_regions;
reload_facedata;
- #reload_archetypes;#d#
reload_archetypes;
+ reload_regions;
reload_treasures;
warn "finished reloading resource files\n";
}
sub init {
+ my $guard = freeze_mainloop;
+
+ evthread_start IO::AIO::poll_fileno;
+
reload_resources;
}
@@ -3328,7 +3365,28 @@
}
}
+sub pidfile() {
+ sysopen my $fh, $PIDFILE, O_RDWR | O_CREAT
+ or die "$PIDFILE: $!";
+ flock $fh, &Fcntl::LOCK_EX
+ or die "$PIDFILE: flock: $!";
+ $fh
+}
+
+# make sure only one server instance is running at any one time
+sub atomic {
+ my $fh = pidfile;
+
+ my $pid = <$fh>;
+ kill 9, $pid if $pid > 0;
+
+ seek $fh, 0, 0;
+ print $fh $$;
+}
+
sub main {
+ atomic;
+
# we must not ever block the main coroutine
local $Coro::idle = sub {
Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
@@ -3338,12 +3396,20 @@
})->prio (Coro::PRIO_MAX);
};
- reload_config;
- db_init;
- load_extensions;
+ {
+ my $guard = freeze_mainloop;
+ reload_config;
+ db_init;
+ load_extensions;
+
+ $Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority
+ }
+
+ utime time, time, $RUNTIMEFILE;
+
+ # no (long-running) fork's whatsoever before this point(!)
+ POSIX::close delete $ENV{LOCKUTIL_LOCK_FD} if exists $ENV{LOCKUTIL_LOCK_FD};
- $TICK_WATCHER->start;
- $Coro::current->prio (Coro::PRIO_MAX); # give the main loop max. priority
EV::loop;
}
@@ -3360,17 +3426,15 @@
}
}
-sub write_runtime {
- my $runtime = "$LOCALDIR/runtime";
-
+sub write_runtime_sync {
# first touch the runtime file to show we are still running:
# the fsync below can take a very very long time.
- IO::AIO::aio_utime $runtime, undef, undef;
+ IO::AIO::aio_utime $RUNTIMEFILE, undef, undef;
my $guard = cf::lock_acquire "write_runtime";
- my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644
+ my $fh = aio_open "$RUNTIMEFILE~", O_WRONLY | O_CREAT, 0644
or return;
my $value = $cf::RUNTIME + 90 + 10;
@@ -3390,7 +3454,7 @@
close $fh
or return;
- aio_rename "$runtime~", $runtime
+ aio_rename "$RUNTIMEFILE~", $RUNTIMEFILE
and return;
warn "runtime file written.\n";
@@ -3398,6 +3462,49 @@
1
}
+our $uuid_lock;
+our $uuid_skip;
+
+sub write_uuid_sync($) {
+ $uuid_skip ||= $_[0];
+
+ return if $uuid_lock;
+ local $uuid_lock = 1;
+
+ my $uuid = "$LOCALDIR/uuid";
+
+ my $fh = aio_open "$uuid~", O_WRONLY | O_CREAT, 0644
+ or return;
+
+ my $value = uuid_str $uuid_skip + uuid_seq uuid_cur;
+ $uuid_skip = 0;
+
+ (aio_write $fh, 0, (length $value), $value, 0) <= 0
+ and return;
+
+ # always fsync - this file is important
+ aio_fsync $fh
+ and return;
+
+ close $fh
+ or return;
+
+ aio_rename "$uuid~", $uuid
+ and return;
+
+ warn "uuid file written ($value).\n";
+
+ 1
+
+}
+
+sub write_uuid($$) {
+ my ($skip, $sync) = @_;
+
+ $sync ? write_uuid_sync $skip
+ : async { write_uuid_sync $skip };
+}
+
sub emergency_save() {
my $freeze_guard = cf::freeze_mainloop;
@@ -3427,6 +3534,10 @@
warn "begin emergency database checkpoint\n";
BDB::db_env_txn_checkpoint $DB_ENV;
warn "end emergency database checkpoint\n";
+
+ warn "begin write uuid\n";
+ write_uuid_sync 1;
+ warn "end write uuid\n";
};
warn "leave emergency perl save\n";
@@ -3437,8 +3548,39 @@
warn Carp::longmess "post_cleanup backtrace"
if $make_core;
+
+ my $fh = pidfile;
+ unlink $PIDFILE if <$fh> == $$;
+}
+
+# a safer delete_package, copied from Symbol
+sub clear_package($) {
+ my $pkg = shift;
+
+ # expand to full symbol table name if needed
+ unless ($pkg =~ /^main::.*::$/) {
+ $pkg = "main$pkg" if $pkg =~ /^::/;
+ $pkg = "main::$pkg" unless $pkg =~ /^main::/;
+ $pkg .= '::' unless $pkg =~ /::$/;
+ }
+
+ my($stem, $leaf) = $pkg =~ m/(.*::)(\w+::)$/;
+ my $stem_symtab = *{$stem}{HASH};
+
+ defined $stem_symtab and exists $stem_symtab->{$leaf}
+ or return;
+
+ # clear all symbols
+ my $leaf_symtab = *{$stem_symtab->{$leaf}}{HASH};
+ for my $name (keys %$leaf_symtab) {
+ _gv_clear *{"$pkg$name"};
+# use PApp::Util; PApp::Util::sv_dump *{"$pkg$name"};
+ }
+ warn "cleared package #$pkg\n";#d#
}
+our $RELOAD; # how many times to reload
+
sub do_reload_perl() {
# can/must only be called in main
if ($Coro::current != $Coro::main) {
@@ -3446,115 +3588,113 @@
return;
}
- warn "reloading...";
+ return if $RELOAD++;
- warn "entering sync_job";
+ while ($RELOAD) {
+ warn "reloading...";
- cf::sync_job {
- cf::write_runtime; # external watchdog should not bark
- cf::emergency_save;
- cf::write_runtime; # external watchdog should not bark
+ warn "entering sync_job";
- warn "syncing database to disk";
- BDB::db_env_txn_checkpoint $DB_ENV;
+ cf::sync_job {
+ cf::write_runtime_sync; # external watchdog should not bark
+ cf::emergency_save;
+ cf::write_runtime_sync; # external watchdog should not bark
- # if anything goes wrong in here, we should simply crash as we already saved
+ warn "syncing database to disk";
+ BDB::db_env_txn_checkpoint $DB_ENV;
- warn "flushing outstanding aio requests";
- for (;;) {
- BDB::flush;
- IO::AIO::flush;
- Coro::cede_notself;
- last unless IO::AIO::nreqs || BDB::nreqs;
- warn "iterate...";
- }
+ # if anything goes wrong in here, we should simply crash as we already saved
- ++$RELOAD;
+ warn "flushing outstanding aio requests";
+ while (IO::AIO::nreqs || BDB::nreqs) {
+ Coro::EV::timer_once 0.01; # let the sync_job do it's thing
+ }
- warn "cancelling all extension coros";
- $_->cancel for values %EXT_CORO;
- %EXT_CORO = ();
+ warn "cancelling all extension coros";
+ $_->cancel for values %EXT_CORO;
+ %EXT_CORO = ();
- warn "removing commands";
- %COMMAND = ();
+ warn "removing commands";
+ %COMMAND = ();
- warn "removing ext/exti commands";
- %EXTCMD = ();
- %EXTICMD = ();
+ warn "removing ext/exti commands";
+ %EXTCMD = ();
+ %EXTICMD = ();
- warn "unloading/nuking all extensions";
- for my $pkg (@EXTS) {
- warn "... unloading $pkg";
+ warn "unloading/nuking all extensions";
+ for my $pkg (@EXTS) {
+ warn "... unloading $pkg";
+
+ if (my $cb = $pkg->can ("unload")) {
+ eval {
+ $cb->($pkg);
+ 1
+ } or warn "$pkg unloaded, but with errors: $@";
+ }
- if (my $cb = $pkg->can ("unload")) {
- eval {
- $cb->($pkg);
- 1
- } or warn "$pkg unloaded, but with errors: $@";
+ warn "... clearing $pkg";
+ clear_package $pkg;
}
- warn "... nuking $pkg";
- Symbol::delete_package $pkg;
- }
+ warn "unloading all perl modules loaded from $LIBDIR";
+ while (my ($k, $v) = each %INC) {
+ next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
- warn "unloading all perl modules loaded from $LIBDIR";
- while (my ($k, $v) = each %INC) {
- next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
+ warn "... unloading $k";
+ delete $INC{$k};
- warn "... unloading $k";
- delete $INC{$k};
+ $k =~ s/\.pm$//;
+ $k =~ s/\//::/g;
- $k =~ s/\.pm$//;
- $k =~ s/\//::/g;
+ if (my $cb = $k->can ("unload_module")) {
+ $cb->();
+ }
- if (my $cb = $k->can ("unload_module")) {
- $cb->();
+ clear_package $k;
}
- Symbol::delete_package $k;
- }
+ warn "getting rid of safe::, as good as possible";
+ clear_package "safe::$_"
+ for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region);
- warn "getting rid of safe::, as good as possible";
- Symbol::delete_package "safe::$_"
- for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region);
+ warn "unloading cf.pm \"a bit\"";
+ delete $INC{"cf.pm"};
+ delete $INC{"cf/pod.pm"};
- warn "unloading cf.pm \"a bit\"";
- delete $INC{"cf.pm"};
- delete $INC{"cf/pod.pm"};
+ # don't, removes xs symbols, too,
+ # and global variables created in xs
+ #clear_package __PACKAGE__;
- # don't, removes xs symbols, too,
- # and global variables created in xs
- #Symbol::delete_package __PACKAGE__;
+ warn "unload completed, starting to reload now";
- warn "unload completed, starting to reload now";
+ warn "reloading cf.pm";
+ require cf;
+ cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
- warn "reloading cf.pm";
- require cf;
- cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
+ warn "loading config and database again";
+ cf::reload_config;
- warn "loading config and database again";
- cf::reload_config;
+ warn "loading extensions";
+ cf::load_extensions;
- warn "loading extensions";
- cf::load_extensions;
+ warn "reattaching attachments to objects/players";
+ _global_reattach; # objects, sockets
+ warn "reattaching attachments to maps";
+ reattach $_ for values %MAP;
+ warn "reattaching attachments to players";
+ reattach $_ for values %PLAYER;
- warn "reattaching attachments to objects/players";
- _global_reattach; # objects, sockets
- warn "reattaching attachments to maps";
- reattach $_ for values %MAP;
- warn "reattaching attachments to players";
- reattach $_ for values %PLAYER;
+ warn "leaving sync_job";
- warn "leaving sync_job";
-
- 1
- } or do {
- warn $@;
- warn "error while reloading, exiting.";
- exit 1;
- };
+ 1
+ } or do {
+ warn $@;
+ cf::cleanup "error while reloading, exiting.";
+ };
- warn "reloaded";
+ warn "reloaded";
+ --$RELOAD;
+ }
};
our $RELOAD_WATCHER; # used only during reload
@@ -3564,8 +3704,8 @@
# coro crashes during coro_state_free->destroy here.
$RELOAD_WATCHER ||= EV::timer 0, 0, sub {
- undef $RELOAD_WATCHER;
do_reload_perl;
+ undef $RELOAD_WATCHER;
};
}
@@ -3589,8 +3729,7 @@
our @WAIT_FOR_TICK_BEGIN;
sub wait_for_tick {
- return unless $TICK_WATCHER->is_active;
- return if $Coro::current == $Coro::main;
+ return if tick_inhibit || $Coro::current == $Coro::main;
my $signal = new Coro::Signal;
push @WAIT_FOR_TICK, $signal;
@@ -3598,33 +3737,27 @@
}
sub wait_for_tick_begin {
- return unless $TICK_WATCHER->is_active;
- return if $Coro::current == $Coro::main;
+ return if tick_inhibit || $Coro::current == $Coro::main;
my $signal = new Coro::Signal;
push @WAIT_FOR_TICK_BEGIN, $signal;
$signal->wait;
}
-$TICK_WATCHER = EV::periodic_ns 0, $TICK, 0, sub {
+sub tick {
if ($Coro::current != $Coro::main) {
Carp::cluck "major BUG: server tick called outside of main coro, skipping it"
unless ++$bug_warning > 10;
return;
}
- $NOW = $tick_start = EV::now;
-
cf::server_tick; # one server iteration
- $RUNTIME += $TICK;
- $NEXT_TICK = $_[0]->at;
-
if ($NOW >= $NEXT_RUNTIME_WRITE) {
$NEXT_RUNTIME_WRITE = List::Util::max $NEXT_RUNTIME_WRITE + 10, $NOW + 5.;
Coro::async_pool {
$Coro::current->{desc} = "runtime saver";
- write_runtime
+ write_runtime_sync
or warn "ERROR: unable to write runtime file: $!";
};
}
@@ -3636,24 +3769,30 @@
$sig->send;
}
- $LOAD = ($NOW - $tick_start) / $TICK;
+ $LOAD = ($NOW - $TICK_START) / $TICK;
$LOADAVG = $LOADAVG * 0.75 + $LOAD * 0.25;
- _post_tick;
-};
-$TICK_WATCHER->priority (EV::MAXPRI);
+ if (0) {
+ if ($NEXT_TICK) {
+ my $jitter = $TICK_START - $NEXT_TICK;
+ $JITTER = $JITTER * 0.75 + $jitter * 0.25;
+ warn "jitter $JITTER\n";#d#
+ }
+ }
+}
{
# configure BDB
BDB::min_parallel 8;
BDB::max_poll_reqs $TICK * 0.1;
- $Coro::BDB::WATCHER->priority (1);
+ $AnyEvent::BDB::WATCHER->priority (1);
unless ($DB_ENV) {
$DB_ENV = BDB::db_env_create;
- $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT | BDB::TXN_NOSYNC
- | BDB::LOG_AUTOREMOVE, 1);
+ $DB_ENV->set_flags (BDB::AUTO_COMMIT | BDB::REGION_INIT);
+ $DB_ENV->set_flags (&BDB::LOG_AUTOREMOVE ) if BDB::VERSION v0, v4.7;
+ $DB_ENV->log_set_config (&BDB::LOG_AUTO_REMOVE) if BDB::VERSION v4.7;
$DB_ENV->set_timeout (30, BDB::SET_TXN_TIMEOUT);
$DB_ENV->set_timeout (30, BDB::SET_LOCK_TIMEOUT);
@@ -3689,7 +3828,7 @@
IO::AIO::min_parallel 8;
IO::AIO::max_poll_time $TICK * 0.1;
- $Coro::AIO::WATCHER->priority (1);
+ undef $AnyEvent::AIO::WATCHER;
}
my $_log_backtrace;
@@ -3702,6 +3841,7 @@
# limit the # of concurrent backtraces
if ($_log_backtrace < 2) {
++$_log_backtrace;
+ my $perl_bt = Carp::longmess $msg;
async {
$Coro::current->{desc} = "abt $msg";
@@ -3722,7 +3862,8 @@
@funcs
};
- LOG llevInfo, "[ABT] $msg\n";
+ LOG llevInfo, "[ABT] $perl_bt\n";
+ LOG llevInfo, "[ABT] --- C backtrace follows ---\n";
LOG llevInfo, "[ABT] $_\n" for @bt;
--$_log_backtrace;
};