--- deliantra/server/lib/cf.pm 2006/12/31 18:10:40 1.107 +++ deliantra/server/lib/cf.pm 2006/12/31 21:02:05 1.108 @@ -17,6 +17,7 @@ use Coro::Semaphore; use Coro::AIO; +use Digest::MD5; use Fcntl; use IO::AIO 2.31 (); use YAML::Syck (); @@ -51,6 +52,8 @@ our %MAP; # all maps our $LINK_MAP; # the special {link} map our $FREEZE; +our $RANDOM_MAPS = cf::localdir . "/random"; +our %EXT_CORO; binmode STDOUT; binmode STDERR; @@ -66,8 +69,10 @@ mkdir cf::localdir . "/" . cf::playerdir; mkdir cf::localdir . "/" . cf::tmpdir; mkdir cf::localdir . "/" . cf::uniquedir; +mkdir $RANDOM_MAPS; -our %EXT_CORO; +# a special map that is always available +our $LINK_MAP; ############################################################################# @@ -242,12 +247,137 @@ $coro } +sub write_runtime { + my $runtime = cf::localdir . "/runtime"; + + my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644 + or return; + + my $value = $cf::RUNTIME; + (aio_write $fh, 0, (length $value), $value, 0) <= 0 + and return; + + aio_fsync $fh + and return; + + close $fh + or return; + + aio_rename "$runtime~", $runtime + and return; + + 1 +} + =back =cut ############################################################################# +package cf::path; + +sub new { + my ($class, $path, $base) = @_; + + my $self = bless { }, $class; + + if ($path =~ s{^\?random/}{}) { + $self->{random} = cf::from_json $path; + } else { + if ($path =~ s{^~([^/]+)?}{}) { + $self->{user_rel} = 1; + + if (defined $1) { + $self->{user} = $1; + } elsif ($base =~ m{^~([^/]+)/}) { + $self->{user} = $1; + } else { + warn "cannot resolve user-relative path without user <$path,$base>\n"; + } + } elsif ($path =~ /^\//) { + # already absolute + } else { + $base =~ s{[^/]+/?$}{}; + return $class->new ("$base/$path"); + } + + for ($path) { + redo if s{/\.?/}{/}; + redo if s{/[^/]+/\.\./}{/}; + } + } + + $self->{path} = $path; + + $self +} + +# the name / primary key / in-game path +sub as_string { + my ($self) = @_; + + $self->{user_rel} ? "~$self->{user}$self->{path}" + : $self->{random} ? "?random/$self->{path}" + : $self->{path} +} + +# the displayed name, this is a one way mapping +sub visible_name { + my ($self) = @_; + + $self->{random} ? "?random/$self->{random}{origin_map}+$self->{random}{origin_x}+$self->{random}{origin_y}/$self->{random}{dungeon_level}" + : $self->as_string +} + +# escape the /'s in the path +sub _escaped_path { + # ∕ is U+2215 + (my $path = $_[0]{path}) =~ s/\//∕/g; + $path +} + +# the original (read-only) location +sub load_path { + my ($self) = @_; + + sprintf "%s/%s/%s", cf::datadir, cf::mapdir, $self->{path} +} + +# the temporary/swap location +sub save_path { + my ($self) = @_; + + $self->{user_rel} ? sprintf "%s/%s/%s/%s", cf::localdir, cf::playerdir, $self->{user}, $self->_escaped_path + : $self->{random} ? sprintf "%s/%s", $RANDOM_MAPS, Digest::MD5::md5_hex $self->{path} + : sprintf "%s/%s/%s", cf::localdir, cf::tmpdir, $self->_escaped_path +} + +# the unique path, might be eq to save_path +sub uniq_path { + my ($self) = @_; + + $self->{user_rel} || $self->{random} + ? undef + : sprintf "%s/%s/%s", cf::localdir, cf::uniquedir, $self->_escaped_path +} + +# return random map parameters, or undef +sub random_map_params { + my ($self) = @_; + + $self->{random} +} + +# this is somewhat ugly, but style maps do need special treatment +sub is_style_map { + $_[0]{path} =~ m{^/styles/} +} + +package cf; + +############################################################################# + =head2 ATTACHABLE OBJECTS Many objects in crossfire are so-called attachable objects. That means you can @@ -1274,6 +1404,14 @@ } sub main { + # we must not ever block the main coroutine + local $Coro::idle = sub { + Carp::cluck "FATAL: Coro::idle was called, major BUG\n";#d# + (Coro::unblock_sub { + Event::one_event; + })->(); + }; + cfg_load; db_load; load_extensions; @@ -1375,6 +1513,19 @@ warn "reloaded successfully"; }; +############################################################################# + +unless ($LINK_MAP) { + $LINK_MAP = cf::map::new; + + $LINK_MAP->width (41); + $LINK_MAP->height (41); + $LINK_MAP->alloc; + $LINK_MAP->path ("{link}"); + $LINK_MAP->{path} = bless { path => "{link}" }, "cf::path"; + $LINK_MAP->in_memory (MAP_IN_MEMORY); +} + register "", __PACKAGE__; register_command "perl-reload" => sub { @@ -1412,20 +1563,25 @@ IO::AIO::max_poll_time $TICK * 0.2; -Event->io (fd => IO::AIO::poll_fileno, - poll => 'r', - prio => 5, - data => WF_AUTOCANCEL, - cb => \&IO::AIO::poll_cb); - -# we must not ever block the main coroutine -$Coro::idle = sub { - #Carp::cluck "FATAL: Coro::idle was called, major BUG\n";#d# - warn "FATAL: Coro::idle was called, major BUG\n"; - (Coro::unblock_sub { - Event::one_event; - })->(); -}; +Event->io ( + fd => IO::AIO::poll_fileno, + poll => 'r', + prio => 5, + data => WF_AUTOCANCEL, + cb => \&IO::AIO::poll_cb, +); + +Event->timer ( + data => WF_AUTOCANCEL, + after => 0, + interval => 10, + cb => sub { + (Coro::unblock_sub { + write_runtime + or warn "ERROR: unable to write runtime file: $!"; + })->(); + }, +); 1