#! perl # this plug-in prefetches maps. everytime a player enters a map, # it will asynchronously prefetch files from disk (it will not load them # into the server, but into the OS cache only). use Errno (); use Time::HiRes; use Fcntl; use IO::AIO; # find all potential exit paths, this is slow, so this info is cached sub find_exits { my ($map) = @_; my %exit; # normal exits for my $x (0 .. $map->width - 1) { for my $y (0 .. $map->height - 1) { for (grep $_->type == 66, $map->at ($x, $y)) { my $path = $_->slaying; next if 3 > length $path; # TODO: improve unique exit detection etc. $exit{cf::maps_directory cf::path_combine_and_normalize $map->path, $path}++; } } } # tiled maps for (0..3) { my $path = $map->tile_path ($_) or next; $exit{cf::maps_directory cf::path_combine_and_normalize $map->path, $path}++; } [keys %exit] } my $PREFETCHING; my @PREFETCH; my %FILE_TIMEOUT; sub _prefetch; my $empty_cb = sub { }; sub load_file { my ($path, $cb) = @_; my $NOW = Time::HiRes::time; aio_open $path, O_RDONLY, 0, sub { my $fh = shift or return $cb->(), _prefetch; aio_readahead $fh, 0, -s $fh, sub { my $time = Time::HiRes::time - $NOW; warn "LONG PREFETCH $path $time\n" if $time > 0.3; $cb->(), _prefetch; }; }; } sub prefetch($$;$) { my ($type, $path, $cb) = @_; push @PREFETCH, [$type, $path, $cb || $empty_cb]; _prefetch unless $PREFETCHING; } sub _prefetch { $PREFETCHING = 1; while (@PREFETCH) { my ($type, $path, $cb) = @{ shift @PREFETCH }; my $NOW = Time::HiRes::time; $cb->(), next if $FILE_TIMEOUT{$path} > $NOW; $FILE_TIMEOUT{$path} = $NOW + 60 + rand 60; if ($type eq "map") { if (my $map = cf::map::has_been_loaded $path) { $cb->(), next if $map->in_memory == cf::MAP_IN_MEMORY; prefetch file => $map->tmpname if $map->in_memory == cf::MAP_SWAPPED; } } load_file $path, $cb; return; } $PREFETCHING = 0; } my %MAP_EXITS; sub prefetch_map($) { my ($map) = @_; my $exit = $MAP_EXITS{$map->path} ||= find_exits $map; prefetch map => $_ for @$exit; } cf::attach_to_players prio => -900, on_map_change => sub { my ($pl, $new, $x, $x) = @_; prefetch_map $new; }, ; if (0) { #test# # prefetch a few players/second { my @players; Event->timer (interval => 0.2, cb => sub { @players = map $_->ob->name, cf::player::list unless @players; my $player = cf::player::find pop @players or return; if (my $map = $player->ob->map) { prefetch_map $map; } prefetch map => +($player->get_savebed)[0]; }); } # prefetch all .pl files every few minutes (thats only a "few" megabytes) Event->timer (after => 1, interval => 600, cb => sub { my $playerdir = cf::localdir . "/" . cf::playerdir; aio_readdir $playerdir, sub { my ($players) = @_; my $prefetch; $prefetch = sub { my $player = pop @$players or return; load_file "$playerdir/$player/$player.pl", $prefetch; }; $prefetch->(); $prefetch->(); }; }); }