1 |
#! perl |
2 |
|
3 |
# this plug-in prefetches maps. everytime a player enters a map, |
4 |
# it will asynchronously prefetch files from disk (it will not load them |
5 |
# into the server, but into the OS cache only). |
6 |
|
7 |
use Errno (); |
8 |
use Time::HiRes; |
9 |
use Fcntl; |
10 |
use IO::AIO; |
11 |
|
12 |
Event->io (fd => IO::AIO::poll_fileno, |
13 |
poll => 'r', |
14 |
cb => \&IO::AIO::poll_cb); |
15 |
|
16 |
# find all potential exit paths, this is slow, so this info is cached |
17 |
sub find_exits { |
18 |
my ($map) = @_; |
19 |
|
20 |
my %exit; |
21 |
|
22 |
# normal exits |
23 |
for my $x (0 .. $map->width - 1) { |
24 |
for my $y (0 .. $map->height - 1) { |
25 |
for (grep $_->type == 66, $map->at ($x, $y)) { |
26 |
my $path = $_->slaying; |
27 |
|
28 |
next if 3 > length $path; |
29 |
|
30 |
# TODO: improve unique exit detection etc. |
31 |
$exit{cf::maps_directory cf::path_combine_and_normalize $map->path, $path}++; |
32 |
} |
33 |
} |
34 |
} |
35 |
|
36 |
# tiled maps |
37 |
for (0..3) { |
38 |
my $path = $map->tile_path ($_) |
39 |
or next; |
40 |
|
41 |
$exit{cf::maps_directory cf::path_combine_and_normalize $map->path, $path}++; |
42 |
} |
43 |
|
44 |
[keys %exit] |
45 |
} |
46 |
|
47 |
my $PREFETCHING; |
48 |
my @PREFETCH; |
49 |
my %FILE_TIMEOUT; |
50 |
|
51 |
sub _prefetch; |
52 |
|
53 |
my $empty_cb = sub { }; |
54 |
|
55 |
sub load_file { |
56 |
my ($path, $cb) = @_; |
57 |
|
58 |
my $NOW = Time::HiRes::time; |
59 |
|
60 |
aio_open $path, O_RDONLY, 0, sub { |
61 |
my $fh = shift |
62 |
or return $cb->(), _prefetch; |
63 |
|
64 |
aio_readahead $fh, 0, -s $fh, sub { |
65 |
my $time = Time::HiRes::time - $NOW; |
66 |
warn "LONG PREFETCH $path $time\n" if $time > 0.3; |
67 |
|
68 |
$cb->(), _prefetch; |
69 |
}; |
70 |
}; |
71 |
} |
72 |
|
73 |
sub prefetch($$;$) { |
74 |
my ($type, $path, $cb) = @_; |
75 |
|
76 |
push @PREFETCH, [$type, $path, $cb || $empty_cb]; |
77 |
_prefetch unless $PREFETCHING; |
78 |
} |
79 |
|
80 |
sub _prefetch { |
81 |
$PREFETCHING = 1; |
82 |
|
83 |
while (@PREFETCH) { |
84 |
my ($type, $path, $cb) = @{ shift @PREFETCH }; |
85 |
|
86 |
my $NOW = Time::HiRes::time; |
87 |
$cb->(), next if $FILE_TIMEOUT{$path} > $NOW; |
88 |
$FILE_TIMEOUT{$path} = $NOW + 60 + rand 60; |
89 |
|
90 |
if ($type eq "map") { |
91 |
if (my $map = cf::map::has_been_loaded $path) { |
92 |
$cb->(), next if $map->in_memory == cf::MAP_IN_MEMORY; |
93 |
|
94 |
prefetch file => $map->tmpname |
95 |
if $map->in_memory == cf::MAP_SWAPPED; |
96 |
} |
97 |
} |
98 |
|
99 |
load_file $path, $cb; |
100 |
return; |
101 |
} |
102 |
|
103 |
$PREFETCHING = 0; |
104 |
} |
105 |
|
106 |
my %MAP_EXITS; |
107 |
|
108 |
sub prefetch_map($) { |
109 |
my ($map) = @_; |
110 |
|
111 |
my $exit = $MAP_EXITS{$map->path} ||= find_exits $map; |
112 |
prefetch map => $_ for @$exit; |
113 |
} |
114 |
|
115 |
sub on_mapenter { |
116 |
my ($ob) = @_; |
117 |
|
118 |
prefetch_map $ob->map; |
119 |
} |
120 |
|
121 |
if (0) { #test# |
122 |
# prefetch a few players/second |
123 |
{ |
124 |
my @players; |
125 |
|
126 |
Event->timer (interval => 0.2, cb => sub { |
127 |
@players = map $_->ob->name, cf::player::list unless @players; |
128 |
my $player = cf::player::find pop @players |
129 |
or return; |
130 |
|
131 |
if (my $map = $player->ob->map) { |
132 |
prefetch_map $map; |
133 |
} |
134 |
|
135 |
prefetch map => +($player->get_savebed)[0]; |
136 |
}); |
137 |
} |
138 |
|
139 |
# prefetch all .pl files every few minutes (thats only a "few" megabytes) |
140 |
Event->timer (after => 1, interval => 600, cb => sub { |
141 |
my $playerdir = cf::localdir . "/" . cf::playerdir; |
142 |
|
143 |
aio_readdir $playerdir, sub { |
144 |
my ($players) = @_; |
145 |
my $prefetch; $prefetch = sub { |
146 |
my $player = pop @$players |
147 |
or return; |
148 |
|
149 |
load_file "$playerdir/$player/$player.pl", $prefetch; |
150 |
}; |
151 |
|
152 |
$prefetch->(); $prefetch->(); |
153 |
}; |
154 |
}); |
155 |
} |
156 |
|
157 |
|
158 |
|