--- deliantra/server/lib/cf.pm 2007/01/05 19:12:03 1.139 +++ deliantra/server/lib/cf.pm 2007/01/07 02:39:14 1.143 @@ -49,10 +49,11 @@ our $UPTIME; $UPTIME ||= time; our $RUNTIME; -our %MAP; # all maps +our %PLAYER; # all users +our %MAP; # all maps our $LINK_MAP; # the special {link} map our $RANDOM_MAPS = cf::localdir . "/random"; -our %EXT_CORO; +our %EXT_CORO; # coroutines bound to extensions binmode STDOUT; binmode STDERR; @@ -231,6 +232,16 @@ $guard } +=item cf::async { BLOCK } + +Currently the same as Coro::async_pool, meaning you cannot use +C, C or other gimmicks on these coroutines. The only +thing you are allowed to do is call C on it. + +=cut + +BEGIN { *async = \&Coro::async_pool } + =item cf::sync_job { BLOCK } The design of crossfire+ requires that the main coro ($Coro::main) is @@ -258,17 +269,14 @@ my $busy = 1; my @res; - (Coro::async_pool { + (async { @res = eval { $job->() }; warn $@ if $@; undef $busy; })->prio (Coro::PRIO_MAX); while ($busy) { - unless (Coro::cede) { - Coro::nready ? Event::one_event 0 : Event::one_event; - Coro::cede_notself unless Coro::cede; - } + Coro::cede or Event::one_event; } wantarray ? @res : $res[0] @@ -279,17 +287,17 @@ } } -=item $coro = cf::coro { BLOCK } +=item $coro = cf::async_ext { BLOCK } -Creates (and readies) and returns a new coro. This coro is automcatially -being canceled when the extension calling this is being unloaded. +Like async, but this coro is automcatially being canceled when the +extension calling this is being unloaded. =cut -sub coro(&) { +sub async_ext(&) { my $cb = shift; - my $coro = &Coro::async_pool ($cb); + my $coro = &Coro::async ($cb); $coro->on_destroy (sub { delete $EXT_CORO{$coro+0}; @@ -1066,19 +1074,76 @@ Functions and methods that extend core crossfire objects. +=cut + +package cf::player; + =head3 cf::player =over 4 -=item cf::player::exists $login +=item cf::player::find $login -Returns true when the given account exists. +Returns the given player object, loading it if necessary (might block). =cut -sub cf::player::exists($) { - cf::player::find $_[0] - or -f sprintf "%s/%s/%s/%s.pl", cf::localdir, cf::playerdir, ($_[0]) x 2; +sub path($) { + sprintf "%s/%s/%s/%s.pl", + cf::localdir, cf::playerdir, + (ref $_[0] ? $_[0]->ob->name : $_[0]) x 2 +} + +sub find_active($) { + $cf::PLAYER{$_[0]} + and $cf::PLAYER{$_[0]}->active + and $cf::PLAYER{$_[0]} +} + +sub exists($) { + my ($login) = @_; + + $cf::PLAYER{$login} + or cf::sync_job { !aio_stat $login } +} + +sub find($) { + return $cf::PLAYER{$_[0]} || do { + my $login = $_[0]; + + my $guard = cf::lock_acquire "user_find:$login"; + + $cf::PLAYER{$login} ||= (load_pl path $login or return); + }; +} + +sub save($) { + my ($pl) = @_; + + return if $pl->{deny_save}; + + my $path = path $pl; + my $guard = cf::lock_acquire "user_save:$path"; + + return if $pl->{deny_save}; + $pl->{last_save} = $cf::RUNTIME; + + Coro::cede; + $pl->save_pl ($path); + Coro::cede; +} + +sub new($) { + my ($login) = @_; + + my $self = create; + + $self->ob->name ($login); + $self->{deny_save} = 1; + + $cf::PLAYER{$login} = $self; + + $self } =item $player->ext_reply ($msgid, $msgtype, %msg) @@ -1087,14 +1152,16 @@ =cut -sub cf::player::ext_reply($$$%) { +sub ext_reply($$$%) { my ($self, $id, %msg) = @_; $msg{msgid} = $id; - $self->send ("ext " . to_json \%msg); + $self->send ("ext " . cf::to_json \%msg); } +package cf; + =back @@ -1272,10 +1339,7 @@ sub find_sync { my ($path, $origin) = @_; - cf::sync_job { - my $map = cf::map::find $path, $origin; - $map - } + cf::sync_job { cf::map::find $path, $origin } } sub do_load_sync { @@ -1302,6 +1366,10 @@ local $self->{last_access} = $self->last_access;#d# + cf::async { + $_->contr->save for $self->players; + }; + if ($uniq) { $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS); $self->save_objects ($uniq, cf::IO_UNIQUES); @@ -1524,6 +1592,7 @@ $map->load; + return unless $self->contr->active; $self->activate_recursive; $self->enter_map ($map, $x, $y); } @@ -1545,11 +1614,14 @@ # try to abort aborted map switching on player login :) # should happen only on crashes if ($pl->ob->{_link_pos}) { - $pl->ob->enter_link; - (Coro::async_pool { + (async { # we need this sleep as the login has a concurrent enter_exit running # and this sleep increases chances of the player not ending up in scorn + $pl->ob->reply (undef, + "There was an internal problem at your last logout, " + . "the server will try to bring you to your intended destination in a second.", + cf::NDI_RED); Coro::Timer::sleep 1; $pl->ob->leave_link; })->prio (2); @@ -1566,7 +1638,7 @@ $self->enter_link; - (Coro::async_pool { + (async { $path = new cf::path $path; my $map = cf::map::find $path->as_string; @@ -1638,7 +1710,7 @@ $self->enter_link; - (Coro::async_pool { + (async { $self->deactivate_recursive; # just to be sure unless (eval { prepare_random_map $exit @@ -1729,7 +1801,7 @@ }, ); -=item $client->coro (\&cb) +=item $client->async (\&cb) Create a new coroutine, running the specified callback. The coroutine will be automatically cancelled when the client gets destroyed (e.g. on logout, @@ -1737,10 +1809,10 @@ =cut -sub cf::client::coro { +sub cf::client::async { my ($self, $cb) = @_; - my $coro = &Coro::async_pool ($cb); + my $coro = &Coro::async ($cb); $coro->on_destroy (sub { delete $self->{_coro}{$coro+0}; @@ -2004,7 +2076,7 @@ # 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# - Coro::async_pool { Event::one_event }; + async { Event::one_event }; }; cfg_load; @@ -2122,7 +2194,7 @@ # dirty hack because... archetypes are not yet loaded Event->timer ( - after => 2, + after => 10, cb => sub { $_[0]->w->cancel;