--- deliantra/server/ext/00_map_handling.ext 2006/12/31 10:28:36 1.8 +++ deliantra/server/ext/00_map_handling.ext 2006/12/31 22:23:12 1.12 @@ -20,133 +20,11 @@ $SWAP_TIMEOUT = 5;#d# $SCHEDULE_INTERVAL = 1; -$cf::LINK_MAP ||= do { - my $map = cf::map::new; - - $map->width (1); - $map->height (1); - $map->alloc; - $map->path ("{link}"); - $map->{path} = bless { path => "{link}" }, "cf::path"; - $map->in_memory (cf::MAP_IN_MEMORY); - - $map -}; - -{ - package cf::path; - - sub new { - my ($class, $path, $base) = @_; - - my %res; - - if ($path =~ s{^~([^/]+)?}{}) { - $res{user_rel} = 1; - - if (defined $1) { - $res{user} = $1; - } elsif ($base =~ m{^~([^/]+)/}) { - $res{user} = $1; - } else { - warn "cannot resolve user-relative path without user <$path,$base>\n"; - } - } elsif ($path =~ /^\//) { - # already absolute - } else { - $base =~ s{[^/]+/?$}{}; - return $class->new ("$base/$path"); - } - - for ($path) { - redo if s{/\.?/}{/}; - redo if s{/[^/]+/\.\./}{/}; - } - - $res{path} = $path; - - bless \%res, $class - } - - # the name / primary key / in-game path - sub as_string { - my ($self) = @_; - - $self->{user_rel} - ? "~$self->{user}$self->{path}" - : $self->{path} - } - - # escape the /'s in the path - sub escaped_path { - # ∕ is U+2215 - (my $path = $_[0]{path}) =~ s/\//∕/g; - $path - } - - # the original (read-only) location - sub load_path { - my ($self) = @_; - - sprintf "%s/%s/%s", cf::datadir, cf::mapdir, $self->{path} - } - - # the temporary/swap location - sub save_path { - my ($self) = @_; - - $self->{user_rel} - ? sprintf "%s/%s/%s/%s", cf::localdir, cf::playerdir, $self->{user}, $self->escaped_path - : sprintf "%s/%s/%s", cf::localdir, cf::tmpdir, $self->escaped_path - } - - # the unique path, might be eq to save_path - sub uniq_path { - my ($self) = @_; - - $self->{user_rel} - ? undef - : sprintf "%s/%s/%s", cf::localdir, cf::uniquedir, $self->escaped_path - } -} - -sub write_runtime { - my $runtime = cf::localdir . "/runtime"; - - my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644 - or return; - - my $value = $cf::RUNTIME; - (aio_write $fh, 0, (length $value), $value, 0) <= 0 - and return; - - aio_fsync $fh - and return; - - close $fh - or return; - - aio_rename "$runtime~", $runtime - and return; - - 1 -} - -(Coro::async { - unless (write_runtime) { - warn "unable to write runtime file: $!"; - exit 1; - } -})->prio (Coro::PRIO_MAX); - our $SCHEDULER = cf::coro { Coro::Timer::sleep 3600;#d#TODO#for debugging only while () { Coro::Timer::sleep $SCHEDULE_INTERVAL; - write_runtime - or warn "unable to write runtime file: $!"; - for my $map (values %cf::MAP) { eval { next if $map->in_memory != cf::MAP_IN_MEMORY; @@ -178,7 +56,7 @@ sub generate_random_map { my ($path, $rmp) = @_; - # mit "rum" bekleckert, nicht + # mit "rum" bekleckern, nicht cf::map::_create_random_map $path, $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle}, @@ -191,13 +69,16 @@ $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation}, $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp}, $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used}, - $rmp->{region} + (cf::region::find $rmp->{region}) } sub parse_random_map_params { my ($spec) = @_; - my $rmp; + my $rmp = { # defaults + xsize => 10, + ysize => 10, + }; for (split /\n/, $spec) { my ($k, $v) = split /\s+/, $_, 2; @@ -211,34 +92,30 @@ sub prepare_random_map { my ($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 + my $rmp = parse_random_map_params $exit->msg; - use Data::Dumper; - die Dumper $rmp; -} -sub sync_job(&) { - my ($job) = @_; + if ($exit->map) { + $rmp->{region} = $exit->map->region_name; + $rmp->{origin_map} = $exit->map->path; + $rmp->{origin_x} = $exit->x; + $rmp->{origin_y} = $exit->y; + } - my $done; - my @res; + $rmp->{random_seed} ||= $exit->random_seed; - Carp::confess "nested sync_job" if $cf::FREEZE; + my $data = cf::to_json $rmp; + my $md5 = Digest::MD5::md5_hex $data; - local $cf::FREEZE = 1; + if (my $fh = aio_open "$cf::RANDOM_MAPS/$md5.meta", O_WRONLY | O_CREAT, 0666) { + aio_write $fh, 0, (length $data), $data, 0; - (Coro::async { - @res = eval { $job->() }; - warn $@ if $@; - $done = 1; - })->prio (Coro::PRIO_MAX); - - while (!$done) { - Coro::cede_notself; - Event::one_event unless Coro::nready; + $exit->slaying ("?random/$md5"); + $exit->msg (undef); } - - warn "sync<@res>\n";#d# - wantarray ? @res : $res[0] } # and all this just because we cannot iterate over @@ -283,9 +160,15 @@ my $map = try_load_header $path->save_path; if (!$map) { - $map = try_load_header $path->load_path - or return; + if (my $rmp = $path->random_map_params) { + $map = generate_random_map $key, $rmp; + } else { + $map = try_load_header $path->load_path; + } + $map or return; + + $map->{reset_time} = $cf::RUNTIME + $map->reset_timeout; $map->instantiate; # per-player maps become, after loading, normal maps @@ -324,13 +207,16 @@ # now do the right thing for maps $self->link_multipart_objects; - $self->fix_auto_apply; - $self->decay_objects; - $self->update_buttons; - $self->set_darkness_map; - $self->difficulty ($self->estimate_difficulty) - unless $self->difficulty; - $self->activate; + + unless ($self->{path}->is_style_map) { + $self->fix_auto_apply; + $self->decay_objects; + $self->update_buttons; + $self->set_darkness_map; + $self->difficulty ($self->estimate_difficulty) + unless $self->difficulty; + $self->activate; + } $self->in_memory (cf::MAP_IN_MEMORY); } @@ -340,7 +226,9 @@ warn "load_map_sync<$path, $origin>\n";#d# - sync_job { + cf::abort if $path =~ /CVS/;#d# + + cf::sync_job { my $map = cf::map::find_map $path, $origin or return; $map->load; @@ -351,20 +239,25 @@ sub cf::map::save { my ($self) = @_; - warn "saving map ", $self->path; - my $save = $self->{path}->save_path; utf8::encode $save; my $uniq = $self->{path}->uniq_path; utf8::encode $uniq; + $self->{last_save} = $cf::RUNTIME; + + return unless $self->dirty; + + $self->{load_path} = $save; + + return if $self->{path}->is_style_map; + + warn "saving map ", $self->path; + if ($uniq) { $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS); $self->save_objects ($uniq, cf::IO_UNIQUES); } else { $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES); } - - $self->{load_path} = $save; - $self->{last_save} = $cf::RUNTIME; } sub cf::map::swap_out { @@ -381,11 +274,12 @@ sub cf::map::should_reset { my ($map) = @_; + return;#d# # TODO: safety, remove and allow resettable per-player maps return if $map->{path}{user_rel};#d# return unless $map->reset_timeout; - my $time = $map->fixed_resettime ? $map->reset_time : $map->last_access; + my $time = $map->fixed_resettime ? $map->{reset_time} : $map->last_access; $time + $map->reset_timeout < $cf::RUNTIME } @@ -397,6 +291,7 @@ return if $self->{path}{user_rel};#d# warn "resetting map ", $self->path;#d# + return;#d# utf8::encode (my $save = $self->{path}->save_path); aioreq_pri 3; IO::AIO::aio_unlink $save; @@ -414,42 +309,56 @@ my ($oldmap, $oldx, $oldy) = ($ob->map, $ob->x, $ob->y); - #TODO: do this in the background, freeze the player if required - sync_job { + $ob->enter_map ($LINK_MAP, 20, 20); + $ob->deactivate_recursive; + + (Coro::async { my ($map, $x, $y); + unless (eval { - if ($exit->slaying eq "/!") { - prepare_random_map $exit; - } + prepare_random_map $exit + if $exit->slaying eq "/!"; - my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path; + my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path; - $map = cf::map::find_map $path->as_string; - $map = $map->customise_for ($ob) if $map; - ($x, $y) = ($exit->stats->hp, $exit->stats->sp); + $map = cf::map::find_map $path->as_string; + $map = $map->customise_for ($ob) if $map; + ($x, $y) = ($exit->stats->hp, $exit->stats->sp); - unless ($map) { - $ob->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED); + unless ($map) { + $ob->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED); - # restore original map position - ($map, $x, $y) = ($oldmap, $oldx, $oldy); + # restore original map position + ($map, $x, $y) = ($oldmap, $oldx, $oldy); - unless ($map) { - $map = cf::map::find_map $emergency_position->[0] - or die "FATAL: cannot load emergency map\n"; - $x = $emergency_position->[1]; - $y = $emergency_position->[2]; + unless ($map) { + $map = cf::map::find_map $emergency_position->[0] + or die "FATAL: cannot load emergency map\n"; + $x = $emergency_position->[1]; + $y = $emergency_position->[2]; + } } - } - # use -1, -1 as default coordinates, not 0, 0 - ($x, $y) = ($map->enter_x, $map->enter_y) - if $x <=0 && $y <= 0; + # use -1, -1 as default coordinates, not 0, 0 + ($x, $y) = ($map->enter_x, $map->enter_y) + if $x <=0 && $y <= 0; + + warn "entering ", $map->path, " at ($x, $y)\n";#d# + $map->load; + 1; + }) { + ($map, $x, $y) = ($oldmap, $oldx, $oldy); - warn "entering ", $map->path, " at ($x, $y)\n";#d# - $map->load; + $ob->message ("Something went wrong within the 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); + + warn "ERROR in enter_exit: $@"; + } + $ob->activate_recursive; $ob->enter_map ($map, $x, $y); - } + })->prio (1); } sub cf::map::customise_for { @@ -467,15 +376,11 @@ warn "enter emergency map save\n"; - my $saver = async { + cf::sync_job { warn "begin emergency map save\n"; $_->save for values %cf::MAP; }; - $saver->prio (Coro::PRIO_MAX); - $saver->join; - warn "emergency map save drain\n"; - Event::one_event while IO::AIO::nreqs; warn "end emergency map save\n"; }