--- deliantra/server/ext/map-random.ext 2007/01/11 00:41:08 1.2 +++ deliantra/server/ext/map-random.ext 2010/04/13 02:39:52 1.41 @@ -1,38 +1,230 @@ -#! perl +#! perl # mandatory -use base "cf::path"; +use Coro::AIO; -__PACKAGE__->register ("random"); +cf::map->register (qr{^\?random/([0-9a-f]{32})}); sub init { my ($self) = @_; - Coro::AIO::aio_load "$cf::RANDOM_MAPS/$self->{path}.meta", my $data; - $self->{random} = cf::from_json $data; - $self->{random}{custom} ||= "$self->{random}{origin_map}+$self->{random}{origin_x}+$self->{random}{origin_y}"; + $self->{random_id} = $1; + + if (0 < Coro::AIO::aio_load "$cf::RANDOMDIR/$self->{random_id}.meta", my $data) { + $self->{random} = cf::decode_json $data; + $self->{random}{custom} ||= "$self->{random}{origin_map}+$self->{random}{origin_x}+$self->{random}{origin_y}"; + } else { + warn "unable to read meta file for $self->{random_id}\n"; + return 0; + } + + 1 +} + +sub thawer_merge { + # we have to keep some variables in memory intact + local $_[0]{random_id}; + local $_[0]{random}; + + $_[0]->SUPER::thawer_merge ($_[1]); } sub visible_name { my ($self) = @_; my $rmp = $self->{random}; - "random map at $rmp->{custom} level $rmp->{dungeon_level}" + "random map at $rmp->{custom}, level $rmp->{dungeon_level}" } sub save_path { my ($self) = @_; - sprintf "%s/%s", $RANDOM_MAPS, $self->{path} + sprintf "%s/%s.map", $cf::RANDOMDIR, $self->{random_id} } sub uniq_path { undef } -sub load_orig { +sub load_header_orig { my ($self) = @_; - cf::map::generate_random_map "$self", $self->{random} + return unless $self->{random}; + + $self->generate_random_map ($self->{random}); + $self->activate; + + 1 +} + +sub select_random_map { + my ($maps, $difficulty) = @_; + + # because I am lazy, I ignore the weighting + + my @maps = keys %$maps; + + cf::map::find "/styles/$maps[cf::rmg_rndm scalar @maps]" +} + +# called by the random map generator +sub find_style_; +sub find_style_($$$) { + my ($path, $difficulty, $recurse) = @_; + + my $map; + + # see if there is a metafile + if (0 < aio_load "$cf::MAPDIR/$path.rmg", my $meta) { + $meta = cf::decode_json $meta; + + # only "maps" is supported + if ($meta->{maps}) { + $map = select_random_map $meta->{maps}, $difficulty; + } + } + + $map = cf::map::find $path + unless aio_stat "$cf::MAPDIR/$path.map"; + + unless ($map) { + # search files and/or dirs + if (my ($dirs, $nondirs) = aio_scandir "$cf::MAPDIR/$path/", 1) { + my @entries = sort grep s/\.(?:map|rmg)$//, @$nondirs; + + if ($difficulty < 0) { + # pick a fully random map, but only a map, do not recurse + $map = cf::map::find "$path/$entries[cf::rmg_rndm scalar @entries]" + if @entries; + } else { + # pick a map with nearest difficulty value ("mapname_.map") + @entries = sort @$dirs + unless @entries || !$recurse; + + my $min_diff = 1e99; + + for my $name (@entries) { + if ($name =~ /_(\d+)$/) { + my $diff = abs $difficulty - $1 + 0.25 + 0.25 * cf::rmg_rndm; # prefer the more difficult version + ($map, $min_diff) = ($name, $diff) if $diff < $min_diff; + } + } + + unless ($map) { + # no map with given pattern found, choose a random map + $map = $entries[cf::rmg_rndm scalar @entries]; + } + + $map = find_style_ "$path/$map", $difficulty + if $map; + } + } + } + + $map +} + +sub find_style($$$$) { + my ($dir, $name, $difficulty, $recurse) = @_; + + cf::cede_to_tick; + + my $map; + + if ($name) { + $map = find_style_ "$dir/$name", $difficulty, $recurse; + } else { + $map = (find_style_ "$dir/default", $difficulty, $recurse) + || (find_style_ $dir, $difficulty, $recurse); + } + + if ($map) { + $map->load; + $map->deactivate; + } + + #warn "return $dir,$name,$difficulty => $map\n" if $difficulty >= 0;#d# + $map +} + +cf::async_ext { + eCoro::current->{desc} = "random map meta file cleaner"; + $Coro::current->nice (1); + + while () { + my $META_TIMEOUT = $cf::CFG{map_random_meta_timeout} || 86400 * 7; + + Coro::AnyEvent::idle_upto $META_TIMEOUT / 10 * 2; + + my ($files) = Coro::AIO::aio_readdirx $cf::RANDOMDIR, IO::AIO::READDIR_STAT_ORDER + or return; + + for my $file (@$files) { + next unless $file =~ /\.meta$/; + + Coro::AIO::aio_stat "$cf::RANDOMDIR/$file" + and next; + + my $age = $cf::NOW - (stat _)[8]; + + if ($age > $META_TIMEOUT) { + warn "resetting random meta data for $file"; + IO::AIO::aio_unlink "$cf::RANDOMDIR/$file"; + } + } + + Coro::AnyEvent::sleep $META_TIMEOUT / 10; + } +}; + +# map generator stresstest, NEVER enable under normal circumstances +if ($ENV{STRESSTEST}) { + cf::async { + my $seed = 0; + while () { + my $map = cf::map::new; + $map->generate_random_map ({ + region => "scorn", + random_seed => $seed++, + xsize => (int rand 100) + 1, + ysize => (int rand 100) + 1, + }); + warn sprintf "%d: %dx%d o# %d\n", $seed, $map->width, $map->height, &cf::object::objects_size;#d# + $map->destroy; + } + }; +} + +# prefetch test, load some ocean-maps +if (0) { + cf::async { + # 0.58 + Coro::Timer::sleep 2; + for my $x (200..219) { + for my $y (200..219) { + (cf::map::find "/world/world_$x\_$y")->load; + } + } + }; +} + +# save test +if (0) { + cf::async { + # 0.080 + Coro::Timer::sleep 2; + my $map = cf::map::find "/mlab/citydeclouds2"; + $map->load_header; + $map->load; + $map->post_load_original; + my $m=100; + for (1..50) { + my $t=AE::time; + $map->_save_objects ("/tmp/x", cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES); + $t = AE::time-$t; + $m=$t if $m>$t; + warn $m; + } + }; } 1