--- deliantra/server/lib/cf.pm 2011/02/26 12:50:27 1.559 +++ deliantra/server/lib/cf.pm 2012/01/04 02:48:57 1.578 @@ -1,7 +1,7 @@ # # This file is part of Deliantra, the Roguelike Realtime MMORPG. # -# Copyright (©) 2006,2007,2008,2009,2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team +# Copyright (©) 2006,2007,2008,2009,2010,2011,2012 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 Affero GNU General Public License as published by the @@ -110,7 +110,7 @@ our $PIDFILE = "$LOCALDIR/pid"; our $RUNTIMEFILE = "$LOCALDIR/runtime"; -our %RESOURCE; # unused +#our %RESOURCE; # unused our $OUTPUT_RATE_MIN = 3000; our $OUTPUT_RATE_MAX = 1000000; @@ -133,7 +133,8 @@ our %CFG; our $UPTIME; $UPTIME ||= time; -our $RUNTIME; +our $RUNTIME = 0; +our $SERVER_TICK = 0; our $NOW; our (%PLAYER, %PLAYER_LOADING); # all users @@ -153,6 +154,9 @@ our $REATTACH_ON_RELOAD; # set to true to force object reattach on reload (slow) our $REALLY_UNLOOP; # never set to true, please :) +our $WAIT_FOR_TICK = new Coro::Signal; +our @WAIT_FOR_TICK_BEGIN; + binmode STDOUT; binmode STDERR; @@ -195,7 +199,22 @@ =item $cf::UPTIME -The timestamp of the server start (so not actually an uptime). +The timestamp of the server start (so not actually an "uptime"). + +=item $cf::SERVER_TICK + +An unsigned integer that starts at zero when the server is started and is +incremented on every tick. + +=item $cf::NOW + +The (real) time of the last (current) server tick - updated before and +after tick processing, so this is useful only as a rough "what time is it +now" estimate. + +=item $cf::TICK + +The interval between each server tick, in seconds. =item $cf::RUNTIME @@ -211,14 +230,6 @@ unique-items directory, player file directory, random maps directory and database environment. -=item $cf::NOW - -The time of the last (current) server tick. - -=item $cf::TICK - -The interval between server ticks, in seconds. - =item $cf::LOADAVG The current CPU load on the server (alpha-smoothed), as a value between 0 @@ -240,6 +251,18 @@ returns directly I the tick processing (and consequently, can only wake one thread per tick), while cf::wait_for_tick wakes up all waiters after tick processing. +Note that cf::Wait_for_tick will immediately return when the server is not +ticking, making it suitable for small pauses in threads that need to run +when the server is paused. If that is not applicable (i.e. you I +want to wait, use C<$cf::WAIT_FOR_TICK>). + +=item $cf::WAIT_FOR_TICK + +Note that C is probably the correct thing to use. This +variable contains a L that is broadcats after every server +tick. Calling C<< ->wait >> on it will suspend the caller until after the +next server tick. + =cut sub wait_for_tick(); @@ -319,6 +342,11 @@ ############################################################################# +sub fork_call(&@); +sub get_slot($;$$); + +############################################################################# + =head2 UTILITY FUNCTIONS =over 4 @@ -346,6 +374,20 @@ } || "[unable to dump $_[0]: '$@']"; } +=item $scalar = load_file $path + +Loads the given file from path and returns its contents. Croaks on error +and can block. + +=cut + +sub load_file($) { + 0 <= aio_load $_[0], my $data + or Carp::croak "$_[0]: $!"; + + $data +} + =item $ref = cf::decode_json $json Converts a JSON string into the corresponding perl data structure. @@ -361,18 +403,37 @@ sub encode_json($) { $json_coder->encode ($_[0]) } sub decode_json($) { $json_coder->decode ($_[0]) } -=item $ref = cf::yaml_load $scalar +=item $ref = cf::decode_storable $scalar -Same as YAML::XS::Load, but doesn't leak, because it forks (and thus blocks). +Same as Coro::Storable::thaw, so blocks. =cut -sub fork_call(&@); +BEGIN { *decode_storable = \&Coro::Storable::thaw } + +=item $ref = cf::decode_yaml $scalar + +Same as YAML::XS::Load, but doesn't leak, because it forks (and thus blocks). -sub yaml_load($) { +=cut + +sub decode_yaml($) { fork_call { YAML::XS::Load $_[0] } @_ } +=item $scalar = cf::unlzf $scalar + +Same as Compress::LZF::compress, but takes server ticks into account, so +blocks. + +=cut + +sub unlzf($) { + # we assume 100mb/s minimum decompression speed (noncompressible data on a ~2ghz machine) + cf::get_slot +(length $_[0]) / 100_000_000, 0, "unlzf"; + Compress::LZF::decompress $_[0] +} + =item cf::post_init { BLOCK } Execute the given codeblock, I all extensions have been (re-)loaded, @@ -464,9 +525,13 @@ =item cf::get_slot $time[, $priority[, $name]] -Allocate $time seconds of blocking CPU time at priority C<$priority>: -This call blocks and returns only when you have at least C<$time> seconds -of cpu time till the next tick. The slot is only valid till the next cede. +Allocate $time seconds of blocking CPU time at priority C<$priority> +(default: 0): This call blocks and returns only when you have at least +C<$time> seconds of cpu time till the next tick. The slot is only valid +till the next cede. + +Background jobs should use a priority les than zero, interactive jobs +should use 100 or more. The optional C<$name> can be used to identify the job to run. It might be used for statistical purposes and should identify the same time-class. @@ -505,8 +570,7 @@ if (@SLOT_QUEUE) { # we do not use wait_for_tick() as it returns immediately when tick is inactive - push @cf::WAIT_FOR_TICK, $signal; - $signal->wait; + $WAIT_FOR_TICK->wait; } else { $busy = 0; Coro::schedule; @@ -771,8 +835,7 @@ my $md5; for (0 .. $#$src) { - 0 <= aio_load $src->[$_], $data[$_] - or Carp::croak "$src->[$_]: $!"; + $data[$_] = load_file $src->[$_]; } # if processing is expensive, check @@ -1400,10 +1463,7 @@ my $msg = eval { $pl->ns->{json_coder}->decode ($buf) }; if (ref $msg) { - my ($type, $reply, @payload) = - "ARRAY" eq ref $msg - ? @$msg - : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove + my ($type, $reply, @payload) = @$msg; # version 1 used %type, $id, %$hash my @reply; @@ -1516,6 +1576,8 @@ cf::cleanup "mandatory extension '$k' has unresolved dependencies, exiting." if exists $v->{meta}{mandatory}; } + + last; } } }; @@ -1953,6 +2015,7 @@ () } +# may re-bless or do other evil things sub init { my ($self) = @_; @@ -2027,18 +2090,21 @@ 1 } +# used to laod the header of an original map sub load_header_orig { my ($self) = @_; $self->load_header_from ($self->load_path) } +# used to laod the header of an instantiated map sub load_header_temp { my ($self) = @_; $self->load_header_from ($self->save_path) } +# called after loading the header from an instantiated map sub prepare_temp { my ($self) = @_; @@ -2049,6 +2115,7 @@ if $self->{instantiate_time} > $cf::RUNTIME; } +# called after loading the header from an original map sub prepare_orig { my ($self) = @_; @@ -2084,7 +2151,7 @@ cf::cede_to_tick; - $path = normalise $path, $origin && $origin->path; + $path = normalise $path, $origin; my $guard1 = cf::lock_acquire "map_data:$path";#d#remove my $guard2 = cf::lock_acquire "map_find:$path"; @@ -2125,35 +2192,40 @@ my $guard = cf::lock_acquire "map_data:$path"; return unless $self->valid; - return unless $self->in_memory == cf::MAP_SWAPPED; - - $self->in_memory (cf::MAP_LOADING); + return unless $self->state == cf::MAP_SWAPPED; $self->alloc; $self->pre_load; cf::cede_to_tick; - my $f = new_from_file cf::object::thawer $self->{load_path}; - $f->skip_block; - $self->_load_objects ($f) - or return; + if (exists $self->{load_path}) { + my $f = new_from_file cf::object::thawer $self->{load_path}; + $f->skip_block; + $self->_load_objects ($f) + or return; - $self->post_load_original - if delete $self->{load_original}; + $self->post_load_original + if delete $self->{load_original}; - if (my $uniq = $self->uniq_path) { - utf8::encode $uniq; - unless (aio_stat $uniq) { - if (my $f = new_from_file cf::object::thawer $uniq) { - $self->clear_unique_items; - $self->_load_objects ($f); - $f->resolve_delayed_derefs; + if (my $uniq = $self->uniq_path) { + utf8::encode $uniq; + unless (aio_stat $uniq) { + if (my $f = new_from_file cf::object::thawer $uniq) { + $self->clear_unique_items; + $self->_load_objects ($f); + $f->resolve_delayed_derefs; + } } } + + $f->resolve_delayed_derefs; + } else { + $self->post_load_original + if delete $self->{load_original}; } - $f->resolve_delayed_derefs; + $self->state (cf::MAP_INACTIVE); cf::cede_to_tick; # now do the right thing for maps @@ -2167,13 +2239,11 @@ $self->fix_auto_apply; $self->update_buttons; cf::cede_to_tick; - $self->activate; + #$self->activate; # no longer activate maps automatically } $self->{last_save} = $cf::RUNTIME; $self->last_access ($cf::RUNTIME); - - $self->in_memory (cf::MAP_ACTIVE); } $self->post_load; @@ -2196,36 +2266,10 @@ $self } -# find and load all maps in the 3x3 area around a map -sub load_neighbours { - my ($map) = @_; - - my @neigh; # diagonal neighbours - - for (0 .. 3) { - my $neigh = $map->tile_path ($_) - or next; - $neigh = find $neigh, $map - or next; - $neigh->load; - - # now find the diagonal neighbours - push @neigh, - [$neigh->tile_path (($_ + 3) % 4), $neigh], - [$neigh->tile_path (($_ + 1) % 4), $neigh]; - } - - for (grep defined $_->[0], @neigh) { - my ($path, $origin) = @$_; - my $neigh = find $path, $origin - or next; - $neigh->load; - } -} - sub find_sync { my ($path, $origin) = @_; + # it's a bug to call this from the main context return cf::LOG cf::llevError | cf::logBacktrace, "do_find_sync" if $Coro::current == $Coro::main; @@ -2235,6 +2279,7 @@ sub do_load_sync { my ($map) = @_; + # it's a bug to call this from the main context return cf::LOG cf::llevError | cf::logBacktrace, "do_load_sync" if $Coro::current == $Coro::main; @@ -2247,10 +2292,10 @@ sub find_async { my ($path, $origin, $load) = @_; - $path = normalise $path, $origin && $origin->{path}; + $path = normalise $path, $origin; if (my $map = $cf::MAP{$path}) { - return $map if !$load || $map->in_memory == cf::MAP_ACTIVE; + return $map if !$load || $map->linkable; } $MAP_PREFETCH{$path} |= $load; @@ -2319,14 +2364,14 @@ my $lock = cf::lock_acquire "map_data:$self->{path}"; - return if $self->in_memory != cf::MAP_ACTIVE; + return if !$self->linkable; return if $self->{deny_save}; return if $self->players; # 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); + $self->state (cf::MAP_SWAPPED); # then atomically save $self->_save; @@ -2362,7 +2407,7 @@ cf::trace "resetting map ", $self->path, "\n"; - $self->in_memory (cf::MAP_SWAPPED); + $self->state (cf::MAP_SWAPPED); # need to save uniques path unless ($self->{deny_save}) { @@ -2702,7 +2747,6 @@ if $x <= 0 && $y <= 0; $map->load; - $map->load_neighbours; return unless $self->contr->active; @@ -2768,7 +2812,7 @@ } my $map = eval { - my $map = defined $path ? cf::map::find $path : undef; + my $map = defined $path ? cf::map::find $path, $self->map : undef; if ($map) { $map = $map->customise_for ($self); @@ -2868,17 +2912,32 @@ unless (eval { $self->deactivate_recursive; # just to be sure - # random map handling - { + my $map = cf::map::normalise $exit->slaying, $exit->map; + my $x = $exit->stats->hp; + my $y = $exit->stats->sp; + + # special map handling + my $slaying = $exit->slaying; + + # special map handling + if ($slaying eq "/!") { my $guard = cf::lock_acquire "exit_prepare:$exit"; prepare_random_map $exit - if $exit->slaying eq "/!"; - } + if $exit->slaying eq "/!"; # need to re-check after getting the lock - my $map = cf::map::normalise $exit->slaying, $exit->map && $exit->map->path; - my $x = $exit->stats->hp; - my $y = $exit->stats->sp; + $map = $exit->slaying; + + } elsif ($slaying eq '!up') { + $map = $exit->map->tile_path (cf::TILE_UP); + $x = $exit->x; + $y = $exit->y; + + } elsif ($slaying eq '!down') { + $map = $exit->map->tile_path (cf::TILE_DOWN); + $x = $exit->x; + $y = $exit->y; + } $self->goto ($map, $x, $y); @@ -3130,13 +3189,9 @@ sub cf::client::ext_reply($$@) { my ($self, $id, @msg) = @_; - if ($self->extcmd == 2) { - $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_big_packet ("ext " . $self->{json_coder}->encode ({@msg})); - } + return unless $self->extcmd == 2; + + $self->send_big_packet ("ext " . $self->{json_coder}->encode (["reply-$id", @msg])); } =item $success = $client->query ($flags, "text", \&cb) @@ -3201,10 +3256,7 @@ my $msg = eval { $ns->{json_coder}->decode ($buf) }; if (ref $msg) { - my ($type, $reply, @payload) = - "ARRAY" eq ref $msg - ? @$msg - : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove + my ($type, $reply, @payload) = @$msg; # version 1 used %type, $id, %$hash my @reply; @@ -3403,10 +3455,7 @@ trace "loading facedata from $path\n"; - 0 < aio_load $path, my $facedata - or die "$path: $!"; - - $facedata = Coro::Storable::thaw $facedata; + my $facedata = decode_storable load_file $path; $facedata->{version} == 2 or cf::cleanup "$path: version mismatch, cannot proceed."; @@ -3467,13 +3516,15 @@ my $res = $facedata->{resource}; while (my ($name, $info) = each %$res) { - if (defined $info->{type}) { + if (defined (my $type = delete $info->{type})) { + # TODO: different hash - must free and use new index, or cache ixface data queue my $idx = (cf::face::find $name) || cf::face::alloc $name; - cf::face::set_data $idx, 0, $info->{data}, $info->{hash}; - cf::face::set_type $idx, $info->{type}; + cf::face::set_data $idx, 0, delete $info->{data}, delete $info->{hash}; + cf::face::set_type $idx, $type; + cf::face::set_meta $idx, (keys %$info && (~$type & 1)) ? $info : undef; # any keys left are stashed into meta unless prepended } else { - $RESOURCE{$name} = $info; # unused +# $RESOURCE{$name} = $info; # unused } cf::cede_to_tick; @@ -3535,10 +3586,7 @@ sub reload_sound { trace "loading sound config from $DATADIR/sound\n"; - 0 < Coro::AIO::aio_load "$DATADIR/sound", my $data - or die "$DATADIR/sound $!"; - - my $soundconf = JSON::XS->new->utf8->relaxed->decode ($data); + my $soundconf = JSON::XS->new->utf8->relaxed->decode (load_file "$DATADIR/sound"); for (0 .. SOUND_CAST_SPELL_0 - 1) { my $sound = $soundconf->{compat}[$_] @@ -3572,11 +3620,9 @@ sub reload_config { trace "reloading config file...\n"; - 0 < aio_load "$CONFDIR/config", my $config - or die "$CONFDIR/config: $!"; - + my $config = load_file "$CONFDIR/config"; utf8::decode $config; - *CFG = yaml_load $config; + *CFG = decode_yaml $config; $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_104_115", 49, 38]; @@ -3624,7 +3670,7 @@ cf::init_globals; # initialise logging LOG llevInfo, "Welcome to Deliantra, v" . VERSION; - LOG llevInfo, "Copyright (C) 2005-2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team."; + LOG llevInfo, "Copyright (C) 2005-2012 Marc Alexander Lehmann / Robin Redeker / the Deliantra team."; LOG llevInfo, "Copyright (C) 1994 Mark Wedel."; LOG llevInfo, "Copyright (C) 1992 Frank Tore Johansen."; @@ -3676,8 +3722,7 @@ cf::object::thawer::errors_are_fatal 0; info "parse errors in files are no longer fatal from this point on.\n"; - my $free_main; $free_main = EV::idle sub { - undef $free_main; + AE::postpone { undef &main; # free gobs of memory :) }; @@ -3844,7 +3889,7 @@ trace "emergency_perl_save: flushing outstanding aio requests"; while (IO::AIO::nreqs || BDB::nreqs) { - Coro::EV::timer_once 0.01; # let the sync_job do it's thing + Coro::AnyEvent::sleep 0.01; # let the sync_job do it's thing } cf::write_runtime_sync; # external watchdog should not bark @@ -4035,19 +4080,14 @@ my $bug_warning = 0; -our @WAIT_FOR_TICK; -our @WAIT_FOR_TICK_BEGIN; - sub wait_for_tick() { - return Coro::cede if tick_inhibit || $Coro::current == $Coro::main; + return Coro::AnyEvent::poll if tick_inhibit || $Coro::current == $Coro::main; - my $signal = new Coro::Signal; - push @WAIT_FOR_TICK, $signal; - $signal->wait; + $WAIT_FOR_TICK->wait; } sub wait_for_tick_begin() { - return Coro::cede if tick_inhibit || $Coro::current == $Coro::main; + return Coro::AnyEvent::poll if tick_inhibit || $Coro::current == $Coro::main; my $signal = new Coro::Signal; push @WAIT_FOR_TICK_BEGIN, $signal; @@ -4061,7 +4101,7 @@ return; } - cf::server_tick; # one server iteration + cf::one_tick; # one server iteration #for(1..3e6){} AE::now_update; $NOW=AE::now; # generate load #d# @@ -4077,9 +4117,7 @@ if (my $sig = shift @WAIT_FOR_TICK_BEGIN) { $sig->send; } - while (my $sig = shift @WAIT_FOR_TICK) { - $sig->send; - } + $WAIT_FOR_TICK->broadcast; $LOAD = ($NOW - $TICK_START) / $TICK; $LOADAVG = $LOADAVG * 0.75 + $LOAD * 0.25; @@ -4098,7 +4136,7 @@ BDB::min_parallel 16; BDB::max_poll_reqs $TICK * 0.1; - $AnyEvent::BDB::WATCHER->priority (1); + #$AnyEvent::BDB::WATCHER->priority (1); unless ($DB_ENV) { $DB_ENV = BDB::db_env_create;