#
# This file is part of Deliantra, the Roguelike Realtime MMORPG.
#
# Copyright (©) 2005,2006,2007,2008,2009,2010,2011,2012 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
#
# Deliantra is free software: you can redistribute it and/or modify it under
# the terms of the Affero GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the Affero GNU General Public License
# and the GNU General Public License along with this program. If not, see
# .
#
# The authors can be reached via e-mail to
#
package cf::incloader;
use common::sense;
our %FILE; # pre-loaded files
our $S1; # disk-size
our $S2; # memory-size
our $PID = $$;
sub find_inc($) {
for my $dir (@cf::ORIG_INC) {
ref $dir and next;
my $path = "$dir/$_[0]";
return $path
unless Coro::AIO::aio_stat $path;
}
undef
}
# async inc loader. yay.
sub inc_loader {
my $mod = $_[1];
my ($path, $data);
if (my $file = delete $FILE{$mod}) {
# we already have it in memory..
cf::debug "incloader: using preloaded file for $mod";
$path = $file->[0];
$data = Compress::LZF::decompress $file->[1];
} else {
# we need to load it from disk.
return if $$ != $PID; # do not do this in child processes
if (cf::in_main && !cf::tick_inhibit) {
Carp::cluck "ERROR: attempted synchronous perl module load ($mod)";
return; # do it blockingly
} else {
cf::debug "incloader: loading perl module $mod";
}
# find real file
$path = find_inc $mod;
defined $path
or Carp::croak "Can't locate $mod in \@INC";
0 <= Coro::AIO::aio_load $path, $data
or next;
# hackish way to pre-cache .so/.bs files
if ($path =~ s/\.pm$//) {
my @c = split /\//, $path;
for (reverse 2 .. $#c) {
my $a = join "/", @c[0..$_-1];
my $b = join "/", @c[$_..$#c];
Coro::AIO::aio_stat "$a/auto/$b/."
and next;
-f _
or next;
$path = "$a/auto/$b/";
cf::debug "incloader: pre-caching $path";
my $files = Coro::AIO::aio_readdir $path;
my $grp = IO::AIO::aio_group;
for my $file (@$files) {
next if $file =~ /(^\.packlist|\.h$|\.xst$)/;
add $grp IO::AIO::aio_open "$path/$file", IO::AIO::O_RDONLY, 0, sub {
my ($fh) = @_
or return;
add $grp IO::AIO::aio_stat $fh, sub {
my $size = -s _;
add $grp IO::AIO::aio_readahead $fh, 0, $size;
cf::debug "incloader: pre-caching $path/$file ($size)";
};
};
}
Coro::AIO::aio_wait $grp;
last;
}
}
}
# avoid upgrade when prepending #line, perlio is bugged
utf8::downgrade $path, 1;
$data = "#line 1 $path\n$data";
open my $fh, "<", \$data or die;
cf::get_slot 0.1, 10, "\@INC loader" if $PID == $$;
$fh
}
# load file/directory, so we can load it synchronously
sub preload_($$$) {
my ($path, $name, $regex) = @_;
if (exists $INC{$name}) {
cf::debug "incloader: $name already loaded, not preloading.";
} else {
Coro::AIO::aio_stat $path
and return;
if (-d _) {
&preload_ ("$path/$_", "$name/$_", $regex)
for @{ Coro::AIO::aio_readdir $path };
} elsif ($name =~ $regex) {
0 <= Coro::AIO::aio_load $path, my $data
or die "$path: $!";
$S1 += length $data;
$data =~ s/^(?:#[^\n]*\n)+//;
$data = Compress::LZF::compress $data;
$S2 += (length $path) + (length $data) + 96;
$FILE{$name} = [$path, $data];
}
}
}
sub preload($;$) {
my ($name, $regex) = @_;
my $path = find_inc $name;
defined $path
or return;
preload_ $path, $name, $regex;
}
sub preload_stuff {
# load some stuff so perl doesn't load it later... :/
eval { &Storable::nstore_fd };
preload "utf8_heavy.pl";
preload "unicore", qr/\.pl$/;
cf::info "incloader: preloaded $S1 octets from disk, cache now $S2 octets.";
}
sub init {
# save original @INC
@cf::ORIG_INC = ($cf::LIBDIR, @INC) unless @cf::ORIG_INC;
# make sure we can do scalar-opens
open my $dummy, "<", \my $dummy2;
preload_stuff;
@INC = (\&inc_loader, @cf::ORIG_INC); # @ORIG_INC is needed for DynaLoader, AutoLoad etc.
cf::debug "incloader: module loading will be asynchronous from this point on.";
}
1