ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/lib/cf.pm
(Generate patch)

Comparing deliantra/server/lib/cf.pm (file contents):
Revision 1.329 by root, Fri Aug 10 05:38:16 2007 UTC vs.
Revision 1.362 by root, Mon Sep 10 12:44:06 2007 UTC

10use Event; 10use Event;
11use Opcode; 11use Opcode;
12use Safe; 12use Safe;
13use Safe::Hole; 13use Safe::Hole;
14 14
15use Coro 3.61 (); 15use Coro 3.64 ();
16use Coro::State; 16use Coro::State;
17use Coro::Handle; 17use Coro::Handle;
18use Coro::Event; 18use Coro::Event;
19use Coro::Timer; 19use Coro::Timer;
20use Coro::Signal; 20use Coro::Signal;
21use Coro::Semaphore; 21use Coro::Semaphore;
22use Coro::AIO; 22use Coro::AIO;
23use Coro::Storable; 23use Coro::Storable;
24use Coro::Util ();
24 25
25use JSON::XS 1.4 (); 26use JSON::XS ();
26use BDB (); 27use BDB ();
27use Data::Dumper; 28use Data::Dumper;
28use Digest::MD5; 29use Digest::MD5;
29use Fcntl; 30use Fcntl;
30use YAML::Syck (); 31use YAML::Syck ();
86our %CFG; 87our %CFG;
87 88
88our $UPTIME; $UPTIME ||= time; 89our $UPTIME; $UPTIME ||= time;
89our $RUNTIME; 90our $RUNTIME;
90 91
91our %PLAYER; # all users 92our (%PLAYER, %PLAYER_LOADING); # all users
92our %MAP; # all maps 93our (%MAP, %MAP_LOADING ); # all maps
93our $LINK_MAP; # the special {link} map, which is always available 94our $LINK_MAP; # the special {link} map, which is always available
94 95
95# used to convert map paths into valid unix filenames by replacing / by ∕ 96# used to convert map paths into valid unix filenames by replacing / by ∕
96our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons 97our $PATH_SEP = "∕"; # U+2215, chosen purely for visual reasons
97 98
186 $msg .= "\n" 187 $msg .= "\n"
187 unless $msg =~ /\n$/; 188 unless $msg =~ /\n$/;
188 189
189 $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge; 190 $msg =~ s/([\x00-\x08\x0b-\x1f])/sprintf "\\x%02x", ord $1/ge;
190 191
191 utf8::encode $msg;
192 LOG llevError, $msg; 192 LOG llevError, $msg;
193 }; 193 };
194} 194}
195 195
196@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable'; 196@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
271Wait until the given lock is available and then acquires it and returns 271Wait until the given lock is available and then acquires it and returns
272a Coro::guard object. If the guard object gets destroyed (goes out of scope, 272a Coro::guard object. If the guard object gets destroyed (goes out of scope,
273for example when the coroutine gets canceled), the lock is automatically 273for example when the coroutine gets canceled), the lock is automatically
274returned. 274returned.
275 275
276Locks are *not* recursive, locking from the same coro twice results in a
277deadlocked coro.
278
276Lock names should begin with a unique identifier (for example, cf::map::find 279Lock names should begin with a unique identifier (for example, cf::map::find
277uses map_find and cf::map::load uses map_load). 280uses map_find and cf::map::load uses map_load).
278 281
279=item $locked = cf::lock_active $string 282=item $locked = cf::lock_active $string
280 283
281Return true if the lock is currently active, i.e. somebody has locked it. 284Return true if the lock is currently active, i.e. somebody has locked it.
282 285
283=cut 286=cut
284 287
285our %LOCK; 288our %LOCK;
289our %LOCKER;#d#
286 290
287sub lock_wait($) { 291sub lock_wait($) {
288 my ($key) = @_; 292 my ($key) = @_;
293
294 if ($LOCKER{$key} == $Coro::current) {#d#
295 Carp::cluck "lock_wait($key) for already-acquired lock";#d#
296 return;#d#
297 }#d#
289 298
290 # wait for lock, if any 299 # wait for lock, if any
291 while ($LOCK{$key}) { 300 while ($LOCK{$key}) {
292 push @{ $LOCK{$key} }, $Coro::current; 301 push @{ $LOCK{$key} }, $Coro::current;
293 Coro::schedule; 302 Coro::schedule;
299 308
300 # wait, to be sure we are not locked 309 # wait, to be sure we are not locked
301 lock_wait $key; 310 lock_wait $key;
302 311
303 $LOCK{$key} = []; 312 $LOCK{$key} = [];
313 $LOCKER{$key} = $Coro::current;#d#
304 314
305 Coro::guard { 315 Coro::guard {
316 delete $LOCKER{$key};#d#
306 # wake up all waiters, to be on the safe side 317 # wake up all waiters, to be on the safe side
307 $_->ready for @{ delete $LOCK{$key} }; 318 $_->ready for @{ delete $LOCK{$key} };
308 } 319 }
309} 320}
310 321
472Coro::Storable. May, of course, block. Note that the executed sub may 483Coro::Storable. May, of course, block. Note that the executed sub may
473never block itself or use any form of Event handling. 484never block itself or use any form of Event handling.
474 485
475=cut 486=cut
476 487
477sub _store_scalar {
478 open my $fh, ">", \my $buf
479 or die "fork_call: cannot open fh-to-buf in child : $!";
480 Storable::store_fd $_[0], $fh;
481 close $fh;
482
483 $buf
484}
485
486sub fork_call(&@) { 488sub fork_call(&@) {
487 my ($cb, @args) = @_; 489 my ($cb, @args) = @_;
488 490
489# socketpair my $fh1, my $fh2, Socket::AF_UNIX, Socket::SOCK_STREAM, Socket::PF_UNSPEC 491 # we seemingly have to make a local copy of the whole thing,
490# or die "socketpair: $!"; 492 # otherwise perl prematurely frees the stuff :/
491 pipe my $fh1, my $fh2 493 # TODO: investigate and fix (likely this will be rather laborious)
492 or die "pipe: $!";
493 494
494 if (my $pid = fork) { 495 my @res = Coro::Util::fork_eval {
495 close $fh2;
496
497 my $res = (Coro::Handle::unblock $fh1)->readline (undef);
498 warn "pst<$res>" unless $res =~ /^pst/;
499 $res = Coro::Storable::thaw $res;
500
501 waitpid $pid, 0; # should not block anymore, we expect the child to simply behave
502
503 Carp::confess $$res unless "ARRAY" eq ref $res;
504
505 return wantarray ? @$res : $res->[-1];
506 } else {
507 reset_signals; 496 reset_signals;
508 local $SIG{__WARN__}; 497 &$cb
509 local $SIG{__DIE__}; 498 }, @args;
510 # just in case, this hack effectively disables event
511 # in the child. cleaner and slower would be canceling all watchers,
512 # but this works for the time being.
513 local $Coro::idle;
514 $Coro::current->prio (Coro::PRIO_MAX);
515 499
516 eval { 500 wantarray ? @res : $res[-1]
517 close $fh1;
518
519 my @res = eval { $cb->(@args) };
520
521 syswrite $fh2, _store_scalar $@ ? \"$@" : \@res;
522 close $fh2;
523 };
524
525 warn $@ if $@;
526 _exit 0;
527 }
528} 501}
529 502
530=item $value = cf::db_get $family => $key 503=item $value = cf::db_get $family => $key
531 504
532Returns a single value from the environment database. 505Returns a single value from the environment database.
612 if (1) { 585 if (1) {
613 $md5 = 586 $md5 =
614 join "\x00", 587 join "\x00",
615 $processversion, 588 $processversion,
616 map { 589 map {
617 Coro::cede; 590 cf::cede_to_tick;
618 ($src->[$_], Digest::MD5::md5_hex $data[$_]) 591 ($src->[$_], Digest::MD5::md5_hex $data[$_])
619 } 0.. $#$src; 592 } 0.. $#$src;
620 593
621 594
622 my $dbmd5 = db_get cache => "$id/md5"; 595 my $dbmd5 = db_get cache => "$id/md5";
1039); 1012);
1040 1013
1041sub object_freezer_save { 1014sub object_freezer_save {
1042 my ($filename, $rdata, $objs) = @_; 1015 my ($filename, $rdata, $objs) = @_;
1043 1016
1017 my $guard = cf::lock_acquire "io";
1018
1044 sync_job { 1019 sync_job {
1045 if (length $$rdata) { 1020 if (length $$rdata) {
1021 utf8::decode (my $decname = $filename);
1046 warn sprintf "saving %s (%d,%d)\n", 1022 warn sprintf "saving %s (%d,%d)\n",
1047 $filename, length $$rdata, scalar @$objs; 1023 $decname, length $$rdata, scalar @$objs;
1048 1024
1049 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) { 1025 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
1050 chmod SAVE_MODE, $fh; 1026 chmod SAVE_MODE, $fh;
1051 aio_write $fh, 0, (length $$rdata), $$rdata, 0; 1027 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
1052 aio_fsync $fh if $cf::USE_FSYNC; 1028 aio_fsync $fh if $cf::USE_FSYNC;
1071 } 1047 }
1072 } else { 1048 } else {
1073 aio_unlink $filename; 1049 aio_unlink $filename;
1074 aio_unlink "$filename.pst"; 1050 aio_unlink "$filename.pst";
1075 } 1051 }
1076 } 1052 };
1053
1054 undef $guard;
1077} 1055}
1078 1056
1079sub object_freezer_as_string { 1057sub object_freezer_as_string {
1080 my ($rdata, $objs) = @_; 1058 my ($rdata, $objs) = @_;
1081 1059
1086 1064
1087sub object_thawer_load { 1065sub object_thawer_load {
1088 my ($filename) = @_; 1066 my ($filename) = @_;
1089 1067
1090 my ($data, $av); 1068 my ($data, $av);
1069
1070 my $guard = cf::lock_acquire "io";
1091 1071
1092 (aio_load $filename, $data) >= 0 1072 (aio_load $filename, $data) >= 0
1093 or return; 1073 or return;
1094 1074
1095 unless (aio_stat "$filename.pst") { 1075 unless (aio_stat "$filename.pst") {
1096 (aio_load "$filename.pst", $av) >= 0 1076 (aio_load "$filename.pst", $av) >= 0
1097 or return; 1077 or return;
1078
1079 undef $guard;
1098 $av = eval { (Storable::thaw $av)->{objs} }; 1080 $av = eval { (Storable::thaw $av)->{objs} };
1099 } 1081 }
1100 1082
1083 utf8::decode (my $decname = $filename);
1101 warn sprintf "loading %s (%d)\n", 1084 warn sprintf "loading %s (%d,%d)\n",
1102 $filename, length $data, scalar @{$av || []}; 1085 $decname, length $data, scalar @{$av || []};
1086
1103 return ($data, $av); 1087 ($data, $av)
1104} 1088}
1105 1089
1106=head2 COMMAND CALLBACKS 1090=head2 COMMAND CALLBACKS
1107 1091
1108=over 4 1092=over 4
1186 my ($type, $reply, @payload) = 1170 my ($type, $reply, @payload) =
1187 "ARRAY" eq ref $msg 1171 "ARRAY" eq ref $msg
1188 ? @$msg 1172 ? @$msg
1189 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove 1173 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
1190 1174
1175 my @reply;
1176
1191 if (my $cb = $EXTCMD{$type}) { 1177 if (my $cb = $EXTCMD{$type}) {
1192 my @reply = $cb->($pl, @payload); 1178 @reply = $cb->($pl, @payload);
1193
1194 $pl->ext_reply ($reply, @reply)
1195 if $reply;
1196 } 1179 }
1180
1181 $pl->ext_reply ($reply, @reply)
1182 if $reply;
1183
1197 } else { 1184 } else {
1198 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n"; 1185 warn "player " . ($pl->ob->name) . " sent unparseable ext message: <$buf>\n";
1199 } 1186 }
1200 1187
1201 cf::override; 1188 cf::override;
1291 1278
1292=head3 cf::player 1279=head3 cf::player
1293 1280
1294=over 4 1281=over 4
1295 1282
1283=item cf::player::num_playing
1284
1285Returns the official number of playing players, as per the Crossfire metaserver rules.
1286
1287=cut
1288
1289sub num_playing {
1290 scalar grep
1291 $_->ob->map
1292 && !$_->hidden
1293 && !$_->ob->flag (cf::FLAG_WIZ),
1294 cf::player::list
1295}
1296
1296=item cf::player::find $login 1297=item cf::player::find $login
1297 1298
1298Returns the given player object, loading it if necessary (might block). 1299Returns the given player object, loading it if necessary (might block).
1299 1300
1300=cut 1301=cut
1335 aio_link +(playerdir $login) . "/$login.pl.pst", (playerdir $login) . "/playerdata.pst"; 1336 aio_link +(playerdir $login) . "/$login.pl.pst", (playerdir $login) . "/playerdata.pst";
1336 aio_link +(playerdir $login) . "/$login.pl" , (playerdir $login) . "/playerdata"; 1337 aio_link +(playerdir $login) . "/$login.pl" , (playerdir $login) . "/playerdata";
1337 aio_unlink +(playerdir $login) . "/$login.pl.pst"; 1338 aio_unlink +(playerdir $login) . "/$login.pl.pst";
1338 aio_unlink +(playerdir $login) . "/$login.pl"; 1339 aio_unlink +(playerdir $login) . "/$login.pl";
1339 1340
1340 my $pl = load_pl path $login 1341 my $f = new_from_file cf::object::thawer path $login
1341 or return; 1342 or return;
1343
1344 $f->next;
1345 my $pl = cf::player::load_pl $f
1346 or return;
1347 local $cf::PLAYER_LOADING{$login} = $pl;
1348 $f->resolve_delayed_derefs;
1342 $cf::PLAYER{$login} = $pl 1349 $cf::PLAYER{$login} = $pl
1343 } 1350 }
1344 } 1351 }
1345} 1352}
1346 1353
1356 1363
1357 aio_mkdir playerdir $pl, 0770; 1364 aio_mkdir playerdir $pl, 0770;
1358 $pl->{last_save} = $cf::RUNTIME; 1365 $pl->{last_save} = $cf::RUNTIME;
1359 1366
1360 $pl->save_pl ($path); 1367 $pl->save_pl ($path);
1361 Coro::cede; 1368 cf::cede_to_tick;
1362} 1369}
1363 1370
1364sub new($) { 1371sub new($) {
1365 my ($login) = @_; 1372 my ($login) = @_;
1366 1373
1444 or return []; 1451 or return [];
1445 1452
1446 my @logins; 1453 my @logins;
1447 1454
1448 for my $login (@$dirs) { 1455 for my $login (@$dirs) {
1456 my $path = path $login;
1457
1458 # a .pst is a dead give-away for a valid player
1459 unless (-e "$path.pst") {
1449 my $fh = aio_open path $login, Fcntl::O_RDONLY, 0 or next; 1460 my $fh = aio_open $path, Fcntl::O_RDONLY, 0 or next;
1450 aio_read $fh, 0, 512, my $buf, 0 or next; 1461 aio_read $fh, 0, 512, my $buf, 0 or next;
1451 $buf !~ /^password -------------$/m or next; # official not-valid tag 1462 $buf !~ /^password -------------$/m or next; # official not-valid tag
1463 }
1452 1464
1453 utf8::decode $login; 1465 utf8::decode $login;
1454 push @logins, $login; 1466 push @logins, $login;
1455 } 1467 }
1456 1468
1494sub expand_cfpod { 1506sub expand_cfpod {
1495 ((my $self), (local $_)) = @_; 1507 ((my $self), (local $_)) = @_;
1496 1508
1497 # escape & and < 1509 # escape & and <
1498 s/&/&amp;/g; 1510 s/&/&amp;/g;
1499 s/(?<![BIUGH])</&lt;/g; 1511 s/(?<![BIUGHT])</&lt;/g;
1500 1512
1501 # this is buggy, it needs to properly take care of nested <'s 1513 # this is buggy, it needs to properly take care of nested <'s
1502 1514
1503 1 while 1515 1 while
1504 # replace B<>, I<>, U<> etc. 1516 # replace B<>, I<>, U<> etc.
1505 s/B<([^\>]*)>/<b>$1<\/b>/ 1517 s/B<([^\>]*)>/<b>$1<\/b>/
1506 || s/I<([^\>]*)>/<i>$1<\/i>/ 1518 || s/I<([^\>]*)>/<i>$1<\/i>/
1507 || s/U<([^\>]*)>/<u>$1<\/u>/ 1519 || s/U<([^\>]*)>/<u>$1<\/u>/
1520 || s/T<([^\>]*)>/<big><b>$1<\/b><\/big>/
1508 # replace G<male|female> tags 1521 # replace G<male|female> tags
1509 || s{G<([^>|]*)\|([^>]*)>}{ 1522 || s{G<([^>|]*)\|([^>]*)>}{
1510 $self->gender ? $2 : $1 1523 $self->gender ? $2 : $1
1511 }ge 1524 }ge
1512 # replace H<hint text> 1525 # replace H<hint text>
1542=cut 1555=cut
1543 1556
1544sub ext_reply($$@) { 1557sub ext_reply($$@) {
1545 my ($self, $id, @msg) = @_; 1558 my ($self, $id, @msg) = @_;
1546 1559
1547 if ($self->ns->extcmd == 2) { 1560 $self->ns->ext_reply ($id, @msg)
1548 $self->send ("ext " . $self->ns->{json_coder}->encode (["reply-$id", @msg]));
1549 } elsif ($self->ns->extcmd == 1) {
1550 #TODO: version 1, remove
1551 unshift @msg, msgtype => "reply", msgid => $id;
1552 $self->send ("ext " . $self->ns->{json_coder}->encode ({@msg}));
1553 }
1554} 1561}
1555 1562
1556=item $player->ext_msg ($type, @msg) 1563=item $player->ext_msg ($type, @msg)
1557 1564
1558Sends an ext event to the client. 1565Sends an ext event to the client.
1786 1793
1787sub load_header_from($) { 1794sub load_header_from($) {
1788 my ($self, $path) = @_; 1795 my ($self, $path) = @_;
1789 1796
1790 utf8::encode $path; 1797 utf8::encode $path;
1791 #aio_open $path, O_RDONLY, 0 1798 my $f = new_from_file cf::object::thawer $path
1792 # or return;
1793
1794 $self->_load_header ($path)
1795 or return; 1799 or return;
1800
1801 $self->_load_header ($f)
1802 or return;
1803
1804 local $MAP_LOADING{$self->{path}} = $self;
1805 $f->resolve_delayed_derefs;
1796 1806
1797 $self->{load_path} = $path; 1807 $self->{load_path} = $path;
1798 1808
1799 1 1809 1
1800} 1810}
1854sub find { 1864sub find {
1855 my ($path, $origin) = @_; 1865 my ($path, $origin) = @_;
1856 1866
1857 $path = normalise $path, $origin && $origin->path; 1867 $path = normalise $path, $origin && $origin->path;
1858 1868
1869 cf::lock_wait "map_data:$path";#d#remove
1859 cf::lock_wait "map_find:$path"; 1870 cf::lock_wait "map_find:$path";
1860 1871
1861 $cf::MAP{$path} || do { 1872 $cf::MAP{$path} || do {
1862 my $guard = cf::lock_acquire "map_find:$path"; 1873 my $guard1 = cf::lock_acquire "map_find:$path";
1874 my $guard2 = cf::lock_acquire "map_data:$path"; # just for the fun of it
1875
1863 my $map = new_from_path cf::map $path 1876 my $map = new_from_path cf::map $path
1864 or return; 1877 or return;
1865 1878
1866 $map->{last_save} = $cf::RUNTIME; 1879 $map->{last_save} = $cf::RUNTIME;
1867 1880
1869 or return; 1882 or return;
1870 1883
1871 if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?) 1884 if ($map->should_reset) {#d#TODO# disabled, crashy (locking issue?)
1872 # doing this can freeze the server in a sync job, obviously 1885 # doing this can freeze the server in a sync job, obviously
1873 #$cf::WAIT_FOR_TICK->wait; 1886 #$cf::WAIT_FOR_TICK->wait;
1887 undef $guard1;
1888 undef $guard2;
1874 $map->reset; 1889 $map->reset;
1875 undef $guard;
1876 return find $path; 1890 return find $path;
1877 } 1891 }
1878 1892
1879 $cf::MAP{$path} = $map 1893 $cf::MAP{$path} = $map
1880 } 1894 }
1889 local $self->{deny_reset} = 1; # loading can take a long time 1903 local $self->{deny_reset} = 1; # loading can take a long time
1890 1904
1891 my $path = $self->{path}; 1905 my $path = $self->{path};
1892 1906
1893 { 1907 {
1894 my $guard = cf::lock_acquire "map_load:$path"; 1908 my $guard = cf::lock_acquire "map_data:$path";
1895 1909
1910 return unless $self->valid;
1896 return if $self->in_memory != cf::MAP_SWAPPED; 1911 return unless $self->in_memory == cf::MAP_SWAPPED;
1897 1912
1898 $self->in_memory (cf::MAP_LOADING); 1913 $self->in_memory (cf::MAP_LOADING);
1899 1914
1900 $self->alloc; 1915 $self->alloc;
1901 1916
1902 $self->pre_load; 1917 $self->pre_load;
1903 Coro::cede; 1918 cf::cede_to_tick;
1904 1919
1920 my $f = new_from_file cf::object::thawer $self->{load_path};
1921 $f->skip_block;
1905 $self->_load_objects ($self->{load_path}, 1) 1922 $self->_load_objects ($f)
1906 or return; 1923 or return;
1907 1924
1908 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1) 1925 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
1909 if delete $self->{load_original}; 1926 if delete $self->{load_original};
1910 1927
1911 if (my $uniq = $self->uniq_path) { 1928 if (my $uniq = $self->uniq_path) {
1912 utf8::encode $uniq; 1929 utf8::encode $uniq;
1913 if (aio_open $uniq, O_RDONLY, 0) { 1930 unless (aio_stat $uniq) {
1931 if (my $f = new_from_file cf::object::thawer $uniq) {
1914 $self->clear_unique_items; 1932 $self->clear_unique_items;
1915 $self->_load_objects ($uniq, 0); 1933 $self->_load_objects ($f);
1934 $f->resolve_delayed_derefs;
1935 }
1916 } 1936 }
1917 } 1937 }
1918 1938
1919 Coro::cede; 1939 $f->resolve_delayed_derefs;
1940
1941 cf::cede_to_tick;
1920 # now do the right thing for maps 1942 # now do the right thing for maps
1921 $self->link_multipart_objects; 1943 $self->link_multipart_objects;
1922 $self->difficulty ($self->estimate_difficulty) 1944 $self->difficulty ($self->estimate_difficulty)
1923 unless $self->difficulty; 1945 unless $self->difficulty;
1924 Coro::cede; 1946 cf::cede_to_tick;
1925 1947
1926 unless ($self->{deny_activate}) { 1948 unless ($self->{deny_activate}) {
1927 $self->decay_objects; 1949 $self->decay_objects;
1928 $self->fix_auto_apply; 1950 $self->fix_auto_apply;
1929 $self->update_buttons; 1951 $self->update_buttons;
1930 Coro::cede; 1952 cf::cede_to_tick;
1931 $self->set_darkness_map; 1953 $self->set_darkness_map;
1932 Coro::cede; 1954 cf::cede_to_tick;
1933 $self->activate; 1955 $self->activate;
1934 } 1956 }
1935 1957
1936 $self->{last_save} = $cf::RUNTIME; 1958 $self->{last_save} = $cf::RUNTIME;
1937 $self->last_access ($cf::RUNTIME); 1959 $self->last_access ($cf::RUNTIME);
1953 1975
1954 $self 1976 $self
1955} 1977}
1956 1978
1957# find and load all maps in the 3x3 area around a map 1979# find and load all maps in the 3x3 area around a map
1958sub load_diag { 1980sub load_neighbours {
1959 my ($map) = @_; 1981 my ($map) = @_;
1960 1982
1961 my @diag; # diagonal neighbours 1983 my @neigh; # diagonal neighbours
1962 1984
1963 for (0 .. 3) { 1985 for (0 .. 3) {
1964 my $neigh = $map->tile_path ($_) 1986 my $neigh = $map->tile_path ($_)
1965 or next; 1987 or next;
1966 $neigh = find $neigh, $map 1988 $neigh = find $neigh, $map
1967 or next; 1989 or next;
1968 $neigh->load; 1990 $neigh->load;
1969 1991
1992 push @neigh,
1970 push @diag, [$neigh->tile_path (($_ + 3) % 4), $neigh], 1993 [$neigh->tile_path (($_ + 3) % 4), $neigh],
1971 [$neigh->tile_path (($_ + 1) % 4), $neigh]; 1994 [$neigh->tile_path (($_ + 1) % 4), $neigh];
1972 } 1995 }
1973 1996
1974 for (@diag) { 1997 for (grep defined $_->[0], @neigh) {
1998 my ($path, $origin) = @$_;
1975 my $neigh = find @$_ 1999 my $neigh = find $path, $origin
1976 or next; 2000 or next;
1977 $neigh->load; 2001 $neigh->load;
1978 } 2002 }
1979} 2003}
1980 2004
1985} 2009}
1986 2010
1987sub do_load_sync { 2011sub do_load_sync {
1988 my ($map) = @_; 2012 my ($map) = @_;
1989 2013
2014 cf::LOG cf::llevDebug | cf::logBacktrace, "do_load_sync"
2015 if $Coro::current == $Coro::main;
2016
1990 cf::sync_job { $map->load }; 2017 cf::sync_job { $map->load };
1991} 2018}
1992 2019
1993our %MAP_PREFETCH; 2020our %MAP_PREFETCH;
1994our $MAP_PREFETCHER = undef; 2021our $MAP_PREFETCHER = undef;
1995 2022
1996sub find_async { 2023sub find_async {
1997 my ($path, $origin) = @_; 2024 my ($path, $origin, $load) = @_;
1998 2025
1999 $path = normalise $path, $origin && $origin->{path}; 2026 $path = normalise $path, $origin && $origin->{path};
2000 2027
2001 if (my $map = $cf::MAP{$path}) { 2028 if (my $map = $cf::MAP{$path}) {
2002 return $map if $map->in_memory == cf::MAP_IN_MEMORY; 2029 return $map if !$load || $map->in_memory == cf::MAP_IN_MEMORY;
2003 } 2030 }
2004 2031
2005 undef $MAP_PREFETCH{$path}; 2032 $MAP_PREFETCH{$path} |= $load;
2033
2006 $MAP_PREFETCHER ||= cf::async { 2034 $MAP_PREFETCHER ||= cf::async {
2007 while (%MAP_PREFETCH) { 2035 while (%MAP_PREFETCH) {
2008 for my $path (keys %MAP_PREFETCH) { 2036 while (my ($k, $v) = each %MAP_PREFETCH) {
2009 if (my $map = find $path) { 2037 if (my $map = find $k) {
2010 $map->load; 2038 $map->load if $v;
2011 } 2039 }
2012 2040
2013 delete $MAP_PREFETCH{$path}; 2041 delete $MAP_PREFETCH{$k};
2014 } 2042 }
2015 } 2043 }
2016 undef $MAP_PREFETCHER; 2044 undef $MAP_PREFETCHER;
2017 }; 2045 };
2018 $MAP_PREFETCHER->prio (6); 2046 $MAP_PREFETCHER->prio (6);
2021} 2049}
2022 2050
2023sub save { 2051sub save {
2024 my ($self) = @_; 2052 my ($self) = @_;
2025 2053
2026 my $lock = cf::lock_acquire "map_data:" . $self->path; 2054 my $lock = cf::lock_acquire "map_data:$self->{path}";
2027 2055
2028 $self->{last_save} = $cf::RUNTIME; 2056 $self->{last_save} = $cf::RUNTIME;
2029 2057
2030 return unless $self->dirty; 2058 return unless $self->dirty;
2031 2059
2054 my ($self) = @_; 2082 my ($self) = @_;
2055 2083
2056 # save first because save cedes 2084 # save first because save cedes
2057 $self->save; 2085 $self->save;
2058 2086
2059 my $lock = cf::lock_acquire "map_data:" . $self->path; 2087 my $lock = cf::lock_acquire "map_data:$self->{path}";
2060 2088
2061 return if $self->players; 2089 return if $self->players;
2062 return if $self->in_memory != cf::MAP_IN_MEMORY; 2090 return if $self->in_memory != cf::MAP_IN_MEMORY;
2063 return if $self->{deny_save}; 2091 return if $self->{deny_save};
2064 2092
2093 $self->in_memory (cf::MAP_SWAPPED);
2094
2095 $self->deactivate;
2096 $_->clear_links_to ($self) for values %cf::MAP;
2065 $self->clear; 2097 $self->clear;
2066 $self->in_memory (cf::MAP_SWAPPED);
2067} 2098}
2068 2099
2069sub reset_at { 2100sub reset_at {
2070 my ($self) = @_; 2101 my ($self) = @_;
2071 2102
2103 if $uniq; 2134 if $uniq;
2104 } 2135 }
2105 2136
2106 delete $cf::MAP{$self->path}; 2137 delete $cf::MAP{$self->path};
2107 2138
2139 $self->deactivate;
2140 $_->clear_links_to ($self) for values %cf::MAP;
2108 $self->clear; 2141 $self->clear;
2109
2110 $_->clear_links_to ($self) for values %cf::MAP;
2111 2142
2112 $self->unlink_save; 2143 $self->unlink_save;
2113 $self->destroy; 2144 $self->destroy;
2114} 2145}
2115 2146
2116my $nuke_counter = "aaaa"; 2147my $nuke_counter = "aaaa";
2117 2148
2118sub nuke { 2149sub nuke {
2119 my ($self) = @_; 2150 my ($self) = @_;
2120 2151
2152 {
2153 my $lock = cf::lock_acquire "map_data:$self->{path}";
2154
2121 delete $cf::MAP{$self->path}; 2155 delete $cf::MAP{$self->path};
2122 2156
2123 $self->unlink_save; 2157 $self->unlink_save;
2124 2158
2125 bless $self, "cf::map"; 2159 bless $self, "cf::map";
2126 delete $self->{deny_reset}; 2160 delete $self->{deny_reset};
2127 $self->{deny_save} = 1; 2161 $self->{deny_save} = 1;
2128 $self->reset_timeout (1); 2162 $self->reset_timeout (1);
2129 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++)); 2163 $self->path ($self->{path} = "{nuke}/" . ($nuke_counter++));
2130 2164
2131 $cf::MAP{$self->path} = $self; 2165 $cf::MAP{$self->path} = $self;
2166 }
2132 2167
2133 $self->reset; # polite request, might not happen 2168 $self->reset; # polite request, might not happen
2134} 2169}
2135 2170
2136=item $maps = cf::map::tmp_maps 2171=item $maps = cf::map::tmp_maps
2212 2247
2213sub inv_recursive { 2248sub inv_recursive {
2214 inv_recursive_ inv $_[0] 2249 inv_recursive_ inv $_[0]
2215} 2250}
2216 2251
2252=item $ref = $ob->ref
2253
2254creates and returns a persistent reference to an objetc that can be stored as a string.
2255
2256=item $ob = cf::object::deref ($refstring)
2257
2258returns the objetc referenced by refstring. may return undef when it cnanot find the object,
2259even if the object actually exists. May block.
2260
2261=cut
2262
2263sub deref {
2264 my ($ref) = @_;
2265
2266 # temporary compatibility#TODO#remove
2267 $ref =~ s{^<}{player/<};
2268
2269 if ($ref =~ m{^player\/(<1\.\d+>)/(.*)$}) {
2270 my ($uuid, $name) = ($1, $2);
2271 my $pl = $cf::PLAYER_LOADING{$name} || cf::player::find $name
2272 or return;
2273 $pl->ob->uuid eq $uuid
2274 or return;
2275
2276 $pl->ob
2277 } else {
2278 warn "$ref: cannot resolve object reference\n";
2279 undef
2280 }
2281}
2282
2217package cf; 2283package cf;
2218 2284
2219=back 2285=back
2220 2286
2221=head3 cf::object::player 2287=head3 cf::object::player
2349 # use -1 or undef as default coordinates, not 0, 0 2415 # use -1 or undef as default coordinates, not 0, 0
2350 ($x, $y) = ($map->enter_x, $map->enter_y) 2416 ($x, $y) = ($map->enter_x, $map->enter_y)
2351 if $x <=0 && $y <= 0; 2417 if $x <=0 && $y <= 0;
2352 2418
2353 $map->load; 2419 $map->load;
2354 $map->load_diag; 2420 $map->load_neighbours;
2355 2421
2356 return unless $self->contr->active; 2422 return unless $self->contr->active;
2357 $self->activate_recursive; 2423 $self->activate_recursive;
2358 2424
2359 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext 2425 local $self->{_prev_pos} = $link_pos; # ugly hack for rent.ext
2530the message, with C<log> being the default. If C<$color> is negative, suppress 2596the message, with C<log> being the default. If C<$color> is negative, suppress
2531the message unless the client supports the msg packet. 2597the message unless the client supports the msg packet.
2532 2598
2533=cut 2599=cut
2534 2600
2601our %CHANNEL = (
2602 "c/identify" => {
2603 id => "identify",
2604 title => "Identify",
2605 reply => undef,
2606 tooltip => "Items recently identified",
2607 },
2608 "c/examine" => {
2609 id => "examine",
2610 title => "Examine",
2611 reply => undef,
2612 tooltip => "Signs and other items you examined",
2613 },
2614);
2615
2535sub cf::client::send_msg { 2616sub cf::client::send_msg {
2536 my ($self, $channel, $msg, $color, @extra) = @_; 2617 my ($self, $channel, $msg, $color, @extra) = @_;
2537 2618
2538 $msg = $self->pl->expand_cfpod ($msg); 2619 $msg = $self->pl->expand_cfpod ($msg);
2539 2620
2540 $color &= cf::NDI_CLIENT_MASK; # just in case... 2621 $color &= cf::NDI_CLIENT_MASK; # just in case...
2622
2623 # check predefined channels, for the benefit of C
2624 $channel = $CHANNEL{$channel} if $CHANNEL{$channel};
2541 2625
2542 if (ref $channel) { 2626 if (ref $channel) {
2543 # send meta info to client, if not yet sent 2627 # send meta info to client, if not yet sent
2544 unless (exists $self->{channel}{$channel->{id}}) { 2628 unless (exists $self->{channel}{$channel->{id}}) {
2545 $self->{channel}{$channel->{id}} = $channel; 2629 $self->{channel}{$channel->{id}} = $channel;
2546 $self->ext_msg (channel_info => $channel); 2630 $self->ext_msg (channel_info => $channel)
2631 if $self->can_msg;
2547 } 2632 }
2548 2633
2549 $channel = $channel->{id}; 2634 $channel = $channel->{id};
2550 } 2635 }
2551 2636
2573 $color &= cf::NDI_COLOR_MASK; 2658 $color &= cf::NDI_COLOR_MASK;
2574 2659
2575 utf8::encode $msg; 2660 utf8::encode $msg;
2576 2661
2577 if (0 && $msg =~ /\[/) { 2662 if (0 && $msg =~ /\[/) {
2663 # COMMAND/INFO
2578 $self->send_packet ("drawextinfo $color 4 0 $msg") 2664 $self->send_packet ("drawextinfo $color 10 8 $msg")
2579 } else { 2665 } else {
2580 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g; 2666 $msg =~ s/\[\/?(?:b|i|u|fixed|color)[^\]]*\]//g;
2581 $self->send_packet ("drawinfo $color $msg") 2667 $self->send_packet ("drawinfo $color $msg")
2582 } 2668 }
2583 } 2669 }
2591=cut 2677=cut
2592 2678
2593sub cf::client::ext_msg($$@) { 2679sub cf::client::ext_msg($$@) {
2594 my ($self, $type, @msg) = @_; 2680 my ($self, $type, @msg) = @_;
2595 2681
2596 my $extcmd = $self->extcmd;
2597
2598 if ($extcmd == 2) { 2682 if ($self->extcmd == 2) {
2599 $self->send_packet ("ext " . $self->{json_coder}->encode ([$type, @msg])); 2683 $self->send_packet ("ext " . $self->{json_coder}->encode ([$type, @msg]));
2600 } elsif ($extcmd == 1) { # TODO: remove 2684 } elsif ($self->extcmd == 1) { # TODO: remove
2601 push @msg, msgtype => "event_$type"; 2685 push @msg, msgtype => "event_$type";
2686 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2687 }
2688}
2689
2690=item $client->ext_reply ($msgid, @msg)
2691
2692Sends an ext reply to the client.
2693
2694=cut
2695
2696sub cf::client::ext_reply($$@) {
2697 my ($self, $id, @msg) = @_;
2698
2699 if ($self->extcmd == 2) {
2700 $self->send_packet ("ext " . $self->{json_coder}->encode (["reply-$id", @msg]));
2701 } elsif ($self->extcmd == 1) {
2702 #TODO: version 1, remove
2703 unshift @msg, msgtype => "reply", msgid => $id;
2602 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg})); 2704 $self->send_packet ("ext " . $self->{json_coder}->encode ({@msg}));
2603 } 2705 }
2604} 2706}
2605 2707
2606=item $success = $client->query ($flags, "text", \&cb) 2708=item $success = $client->query ($flags, "text", \&cb)
2668 my ($type, $reply, @payload) = 2770 my ($type, $reply, @payload) =
2669 "ARRAY" eq ref $msg 2771 "ARRAY" eq ref $msg
2670 ? @$msg 2772 ? @$msg
2671 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove 2773 : ($msg->{msgtype}, $msg->{msgid}, %$msg); # TODO: version 1, remove
2672 2774
2775 my @reply;
2776
2673 if (my $cb = $EXTICMD{$type}) { 2777 if (my $cb = $EXTICMD{$type}) {
2674 my @reply = $cb->($ns, @payload); 2778 @reply = $cb->($ns, @payload);
2675
2676 $ns->ext_reply ($reply, @reply)
2677 if $reply;
2678 } 2779 }
2780
2781 $ns->ext_reply ($reply, @reply)
2782 if $reply;
2783
2679 } else { 2784 } else {
2680 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n"; 2785 warn "client " . ($ns->pl ? $ns->pl->ob->name : $ns->host) . " sent unparseable exti message: <$buf>\n";
2681 } 2786 }
2682 2787
2683 cf::override; 2788 cf::override;
2744 2849
2745The following functions and methods are available within a safe environment: 2850The following functions and methods are available within a safe environment:
2746 2851
2747 cf::object 2852 cf::object
2748 contr pay_amount pay_player map x y force_find force_add 2853 contr pay_amount pay_player map x y force_find force_add
2749 insert remove 2854 insert remove name archname title slaying race decrease_ob_nr
2750 2855
2751 cf::object::player 2856 cf::object::player
2752 player 2857 player
2753 2858
2754 cf::player 2859 cf::player
2759 2864
2760=cut 2865=cut
2761 2866
2762for ( 2867for (
2763 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y 2868 ["cf::object" => qw(contr pay_amount pay_player map force_find force_add x y
2764 insert remove inv)], 2869 insert remove inv name archname title slaying race
2870 decrease_ob_nr)],
2765 ["cf::object::player" => qw(player)], 2871 ["cf::object::player" => qw(player)],
2766 ["cf::player" => qw(peaceful)], 2872 ["cf::player" => qw(peaceful)],
2767 ["cf::map" => qw(trigger)], 2873 ["cf::map" => qw(trigger)],
2768) { 2874) {
2769 no strict 'refs'; 2875 no strict 'refs';
2845# the server's init and main functions 2951# the server's init and main functions
2846 2952
2847sub load_facedata($) { 2953sub load_facedata($) {
2848 my ($path) = @_; 2954 my ($path) = @_;
2849 2955
2956 # HACK to clear player env face cache, we need some signal framework
2957 # for this (global event?)
2958 %ext::player_env::MUSIC_FACE_CACHE = ();
2959
2960 my $enc = JSON::XS->new->utf8->canonical->relaxed;
2961
2850 warn "loading facedata from $path\n"; 2962 warn "loading facedata from $path\n";
2851 2963
2852 my $facedata; 2964 my $facedata;
2853 0 < aio_load $path, $facedata 2965 0 < aio_load $path, $facedata
2854 or die "$path: $!"; 2966 or die "$path: $!";
2855 2967
2856 $facedata = Coro::Storable::thaw $facedata; 2968 $facedata = Coro::Storable::thaw $facedata;
2857 2969
2858 $facedata->{version} == 2 2970 $facedata->{version} == 2
2859 or cf::cleanup "$path: version mismatch, cannot proceed."; 2971 or cf::cleanup "$path: version mismatch, cannot proceed.";
2972
2973 # patch in the exptable
2974 $facedata->{resource}{"res/exp_table"} = {
2975 type => FT_RSRC,
2976 data => $enc->encode ([map cf::level_to_min_exp $_, 1 .. cf::settings->max_level]),
2977 };
2978 cf::cede_to_tick;
2860 2979
2861 { 2980 {
2862 my $faces = $facedata->{faceinfo}; 2981 my $faces = $facedata->{faceinfo};
2863 2982
2864 while (my ($face, $info) = each %$faces) { 2983 while (my ($face, $info) = each %$faces) {
2865 my $idx = (cf::face::find $face) || cf::face::alloc $face; 2984 my $idx = (cf::face::find $face) || cf::face::alloc $face;
2866 cf::face::set_visibility $idx, $info->{visibility}; 2985 cf::face::set_visibility $idx, $info->{visibility};
2867 cf::face::set_magicmap $idx, $info->{magicmap}; 2986 cf::face::set_magicmap $idx, $info->{magicmap};
2868 cf::face::set_data $idx, 0, $info->{data32}, $info->{chksum32}; 2987 cf::face::set_data $idx, 0, $info->{data32}, Digest::MD5::md5 $info->{data32};
2869 cf::face::set_data $idx, 1, $info->{data64}, $info->{chksum64}; 2988 cf::face::set_data $idx, 1, $info->{data64}, Digest::MD5::md5 $info->{data64};
2870 2989
2871 cf::cede_to_tick; 2990 cf::cede_to_tick;
2872 } 2991 }
2873 2992
2874 while (my ($face, $info) = each %$faces) { 2993 while (my ($face, $info) = each %$faces) {
2899 3018
2900 { 3019 {
2901 # TODO: for gcfclient pleasure, we should give resources 3020 # TODO: for gcfclient pleasure, we should give resources
2902 # that gcfclient doesn't grok a >10000 face index. 3021 # that gcfclient doesn't grok a >10000 face index.
2903 my $res = $facedata->{resource}; 3022 my $res = $facedata->{resource};
2904 my $enc = JSON::XS->new->utf8->canonical;
2905 3023
2906 my $soundconf = delete $res->{"res/sound.conf"}; 3024 my $soundconf = delete $res->{"res/sound.conf"};
2907 3025
2908 while (my ($name, $info) = each %$res) { 3026 while (my ($name, $info) = each %$res) {
2909 my $meta = $enc->encode ({
2910 name => $name,
2911 %{ $info->{meta} || {} },
2912 });
2913
2914 my $idx = (cf::face::find $name) || cf::face::alloc $name; 3027 my $idx = (cf::face::find $name) || cf::face::alloc $name;
3028 my $data;
2915 3029
2916 if ($info->{type} & 1) { 3030 if ($info->{type} & 1) {
2917 # prepend meta info 3031 # prepend meta info
2918 3032
3033 my $meta = $enc->encode ({
3034 name => $name,
3035 %{ $info->{meta} || {} },
3036 });
3037
2919 my $data = pack "(w/a*)*", $meta, $info->{data}; 3038 $data = pack "(w/a*)*", $meta, $info->{data};
2920 my $chk = Digest::MD5::md5 "$info->{chksum},$meta"; # mangle data checksum and metadata
2921
2922 cf::face::set_data $idx, 0, $data, $chk;
2923 } else { 3039 } else {
2924 cf::face::set_data $idx, 0, $info->{data}, $info->{chksum}; 3040 $data = $info->{data};
2925 } 3041 }
2926 3042
3043 cf::face::set_data $idx, 0, $data, Digest::MD5::md5 $data;
2927 cf::face::set_type $idx, $info->{type}; 3044 cf::face::set_type $idx, $info->{type};
2928 3045
2929 cf::cede_to_tick; 3046 cf::cede_to_tick;
2930 } 3047 }
2931 3048
2958 $ns->fx_want ($k, $v); 3075 $ns->fx_want ($k, $v);
2959 } 3076 }
2960}; 3077};
2961 3078
2962sub reload_regions { 3079sub reload_regions {
3080 # HACK to clear player env face cache, we need some signal framework
3081 # for this (global event?)
3082 %ext::player_env::MUSIC_FACE_CACHE = ();
3083
2963 load_resource_file "$MAPDIR/regions" 3084 load_resource_file "$MAPDIR/regions"
2964 or die "unable to load regions file\n"; 3085 or die "unable to load regions file\n";
2965 3086
2966 for (cf::region::list) { 3087 for (cf::region::list) {
2967 $_->{match} = qr/$_->{match}/ 3088 $_->{match} = qr/$_->{match}/
3003 3124
3004sub init { 3125sub init {
3005 reload_resources; 3126 reload_resources;
3006} 3127}
3007 3128
3008sub cfg_load { 3129sub reload_config {
3009 open my $fh, "<:utf8", "$CONFDIR/config" 3130 open my $fh, "<:utf8", "$CONFDIR/config"
3010 or return; 3131 or return;
3011 3132
3012 local $/; 3133 local $/;
3013 *CFG = YAML::Syck::Load <$fh>; 3134 *CFG = YAML::Syck::Load <$fh>;
3033 (async { 3154 (async {
3034 Event::one_event; 3155 Event::one_event;
3035 })->prio (Coro::PRIO_MAX); 3156 })->prio (Coro::PRIO_MAX);
3036 }; 3157 };
3037 3158
3038 cfg_load; 3159 reload_config;
3039 db_init; 3160 db_init;
3040 load_extensions; 3161 load_extensions;
3041 3162
3042 $TICK_WATCHER->start; 3163 $TICK_WATCHER->start;
3043 Event::loop; 3164 Event::loop;
3236 warn "reloading cf.pm"; 3357 warn "reloading cf.pm";
3237 require cf; 3358 require cf;
3238 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt 3359 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
3239 3360
3240 warn "loading config and database again"; 3361 warn "loading config and database again";
3241 cf::cfg_load; 3362 cf::reload_config;
3242 3363
3243 warn "loading extensions"; 3364 warn "loading extensions";
3244 cf::load_extensions; 3365 cf::load_extensions;
3245 3366
3246 warn "reattaching attachments to objects/players"; 3367 warn "reattaching attachments to objects/players";
3339 or die; 3460 or die;
3340 3461
3341 $map->width (50); 3462 $map->width (50);
3342 $map->height (50); 3463 $map->height (50);
3343 $map->alloc; 3464 $map->alloc;
3344 $map->_load_objects ("/tmp/x.map", 1); 3465 $map->_load_objects ("/tmp/x.map", 1); #TODO: does not work
3345 my $t = Event::time - $t; 3466 my $t = Event::time - $t;
3346 3467
3347 #next unless $t < 0.0013;#d# 3468 #next unless $t < 0.0013;#d#
3348 if ($t < $min) { 3469 if ($t < $min) {
3349 $min = $t; 3470 $min = $t;

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines