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