--- deliantra/server/lib/cf.pm 2007/01/01 21:19:52 1.118 +++ deliantra/server/lib/cf.pm 2007/01/02 19:18:33 1.123 @@ -183,6 +183,70 @@ JSON::Syck::Dump $_[0] } +=item my $guard = cf::guard { BLOCK } + +Run the given callback when the guard object gets destroyed (useful for +coroutine cancellations). + +You can call C<< ->cancel >> on the guard object to stop the block from +being executed. + +=cut + +sub guard(&) { + bless \(my $cb = $_[0]), cf::guard::; +} + +sub cf::guard::cancel { + ${$_[0]} = sub { }; +} + +sub cf::guard::DESTROY { + ${$_[0]}->(); +} + +=item cf::lock_wait $string + +Wait until the given lock is available. See cf::lock_acquire. + +=item my $lock = cf::lock_acquire $string + +Wait until the given lock is available and then acquires it and returns +a guard object. If the guard object gets destroyed (goes out of scope, +for example when the coroutine gets canceled), the lock is automatically +returned. + +Lock names should begin with a unique identifier (for example, find_map +uses map_find and load_map uses map_load). + +=cut + +our %LOCK; + +sub lock_wait($) { + my ($key) = @_; + + # wait for lock, if any + while ($LOCK{$key}) { + push @{ $LOCK{$key} }, $Coro::current; + Coro::schedule; + } +} + +sub lock_acquire($) { + my ($key) = @_; + + # wait, to be sure we are not locked + lock_wait $key; + + $LOCK{$key} = []; + + cf::guard { + # wake up all waiters, to be on the safe side + $_->ready for @{ delete $LOCK{$key} }; + } +} + =item cf::sync_job { BLOCK } The design of crossfire+ requires that the main coro ($Coro::main) is @@ -1091,7 +1155,8 @@ sub change_all_map_light { my ($change) = @_; - $_->change_map_light ($change) for values %cf::MAP; + $_->change_map_light ($change) + for grep $_->outdoor, values %cf::MAP; } sub try_load_header($) { @@ -1120,7 +1185,11 @@ $path = new cf::path $path, $origin && $origin->path; my $key = $path->as_string; + cf::lock_wait "map_find:$key"; + $cf::MAP{$key} || do { + my $guard = cf::lock_acquire "map_find:$key"; + # do it the slow way my $map = try_load_header $path->save_path; @@ -1144,7 +1213,6 @@ # per-player maps become, after loading, normal maps $map->per_player (0) if $path->{user_rel}; } - #Coro::Timer::sleep 1;#d# $map->path ($key); $map->{path} = $path; @@ -1153,6 +1221,7 @@ if ($map->should_reset) { $map->reset; + undef $guard; $map = find_map $path; } @@ -1163,12 +1232,13 @@ sub load { my ($self) = @_; + my $path = $self->{path}; + my $guard = cf::lock_acquire "map_load:" . $path->as_string; + return if $self->in_memory != cf::MAP_SWAPPED; $self->in_memory (cf::MAP_LOADING); - my $path = $self->{path}; - $self->alloc; $self->load_objects ($self->{load_path}, 1) or return; @@ -1410,13 +1480,14 @@ sub cf::object::player::enter_link { my ($self) = @_; + $self->deactivate_recursive; + return if $self->map == $LINK_MAP; - $self->{_link_pos} = [$self->map->{path}, $self->x, $self->y] + $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y] if $self->map; $self->enter_map ($LINK_MAP, 20, 20); - $self->deactivate_recursive; } sub cf::object::player::leave_link { @@ -1449,6 +1520,34 @@ $self->enter_map ($map, $x, $y); } +cf::player->attach ( + on_logout => sub { + my ($pl) = @_; + + # abort map switching before logout + if ($pl->ob->{_link_pos}) { + cf::sync_job { + $pl->ob->leave_link + }; + } + }, + on_login => sub { + my ($pl) = @_; + + # 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 { + # 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 + Coro::Timer::sleep 1; + $pl->ob->leave_link; + }; + } + }, +); + =item $player_object->goto_map ($path, $x, $y) =cut @@ -1464,8 +1563,8 @@ my $map = cf::map::find_map $path->as_string; $map = $map->customise_for ($self) if $map; - warn "entering ", $map->path, " at ($x, $y)\n" - if $map; +# warn "entering ", $map->path, " at ($x, $y)\n" +# if $map; $map or $self->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED); @@ -1818,14 +1917,12 @@ my $path = cf::localdir . "/database.pst"; sub db_load() { - warn "loading database $path\n";#d# remove later $DB = stat $path ? Storable::retrieve $path : { }; } my $pid; sub db_save() { - warn "saving database $path\n";#d# remove later waitpid $pid, 0 if $pid; if (0 == ($pid = fork)) { $DB->{_meta}{version} = 1;