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.104 by root, Sat Dec 30 16:56:16 2006 UTC vs.
Revision 1.130 by root, Thu Jan 4 00:08:08 2007 UTC

8use Storable; 8use Storable;
9use Opcode; 9use Opcode;
10use Safe; 10use Safe;
11use Safe::Hole; 11use Safe::Hole;
12 12
13use Coro 3.3; 13use Coro 3.3 ();
14use Coro::Event; 14use Coro::Event;
15use Coro::Timer; 15use Coro::Timer;
16use Coro::Signal; 16use Coro::Signal;
17use Coro::Semaphore; 17use Coro::Semaphore;
18use Coro::AIO;
18 19
20use Digest::MD5;
21use Fcntl;
19use IO::AIO 2.3; 22use IO::AIO 2.31 ();
20use YAML::Syck (); 23use YAML::Syck ();
21use Time::HiRes; 24use Time::HiRes;
22 25
23use Event; $Event::Eval = 1; # no idea why this is required, but it is 26use Event; $Event::Eval = 1; # no idea why this is required, but it is
24 27
47our $RUNTIME; 50our $RUNTIME;
48 51
49our %MAP; # all maps 52our %MAP; # all maps
50our $LINK_MAP; # the special {link} map 53our $LINK_MAP; # the special {link} map
51our $FREEZE; 54our $FREEZE;
55our $RANDOM_MAPS = cf::localdir . "/random";
56our %EXT_CORO;
52 57
53binmode STDOUT; 58binmode STDOUT;
54binmode STDERR; 59binmode STDERR;
55 60
56# read virtual server time, if available 61# read virtual server time, if available
62 67
63mkdir cf::localdir; 68mkdir cf::localdir;
64mkdir cf::localdir . "/" . cf::playerdir; 69mkdir cf::localdir . "/" . cf::playerdir;
65mkdir cf::localdir . "/" . cf::tmpdir; 70mkdir cf::localdir . "/" . cf::tmpdir;
66mkdir cf::localdir . "/" . cf::uniquedir; 71mkdir cf::localdir . "/" . cf::uniquedir;
72mkdir $RANDOM_MAPS;
67 73
68our %EXT_CORO; 74# a special map that is always available
75our $LINK_MAP;
76
77our $EMERGENCY_POSITION = $cf::CFG{emergency_position} || ["/world/world_105_115", 5, 37];
69 78
70############################################################################# 79#############################################################################
71 80
72=head2 GLOBAL VARIABLES 81=head2 GLOBAL VARIABLES
73 82
172sub to_json($) { 181sub to_json($) {
173 $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs 182 $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs
174 JSON::Syck::Dump $_[0] 183 JSON::Syck::Dump $_[0]
175} 184}
176 185
186=item my $guard = cf::guard { BLOCK }
187
188Run the given callback when the guard object gets destroyed (useful for
189coroutine cancellations).
190
191You can call C<< ->cancel >> on the guard object to stop the block from
192being executed.
193
194=cut
195
196sub guard(&) {
197 bless \(my $cb = $_[0]), cf::guard::;
198}
199
200sub cf::guard::cancel {
201 ${$_[0]} = sub { };
202}
203
204sub cf::guard::DESTROY {
205 ${$_[0]}->();
206}
207
208=item cf::lock_wait $string
209
210Wait until the given lock is available. See cf::lock_acquire.
211
212=item my $lock = cf::lock_acquire $string
213
214Wait until the given lock is available and then acquires it and returns
215a guard object. If the guard object gets destroyed (goes out of scope,
216for example when the coroutine gets canceled), the lock is automatically
217returned.
218
219Lock names should begin with a unique identifier (for example, find_map
220uses map_find and load_map uses map_load).
221
222=cut
223
224our %LOCK;
225
226sub lock_wait($) {
227 my ($key) = @_;
228
229 # wait for lock, if any
230 while ($LOCK{$key}) {
231 push @{ $LOCK{$key} }, $Coro::current;
232 Coro::schedule;
233 }
234}
235
236sub lock_acquire($) {
237 my ($key) = @_;
238
239 # wait, to be sure we are not locked
240 lock_wait $key;
241
242 $LOCK{$key} = [];
243
244 cf::guard {
245 # wake up all waiters, to be on the safe side
246 $_->ready for @{ delete $LOCK{$key} };
247 }
248}
249
250=item cf::async { BLOCK }
251
252Like C<Coro::async>, but runs the given BLOCK in an eval and only logs the
253error instead of exiting the server in case of a problem.
254
255=cut
256
257sub async(&) {
258 my ($cb) = @_;
259
260 Coro::async {
261 eval { $cb->() };
262 warn $@ if $@;
263 }
264}
265
266=item cf::sync_job { BLOCK }
267
268The design of crossfire+ requires that the main coro ($Coro::main) is
269always able to handle events or runnable, as crossfire+ is only partly
270reentrant. Thus "blocking" it by e.g. waiting for I/O is not acceptable.
271
272If it must be done, put the blocking parts into C<sync_job>. This will run
273the given BLOCK in another coroutine while waiting for the result. The
274server will be frozen during this time, so the block should either finish
275fast or be very important.
276
277=cut
278
279sub sync_job(&) {
280 my ($job) = @_;
281
282 if ($Coro::current == $Coro::main) {
283 # this is the main coro, too bad, we have to block
284 # till the operation succeeds, freezing the server :/
285
286 # TODO: use suspend/resume instead
287 # (but this is cancel-safe)
288 local $FREEZE = 1;
289
290 my $busy = 1;
291 my @res;
292
293 (Coro::async {
294 @res = eval { $job->() };
295 warn $@ if $@;
296 undef $busy;
297 })->prio (Coro::PRIO_MAX);
298
299 while ($busy) {
300 Coro::cede_notself;
301 Event::one_event unless Coro::nready;
302 }
303
304 wantarray ? @res : $res[0]
305 } else {
306 # we are in another coroutine, how wonderful, everything just works
307
308 $job->()
309 }
310}
311
177=item $coro = cf::coro { BLOCK } 312=item $coro = cf::coro { BLOCK }
178 313
179Creates and returns a new coro. This coro is automcatially being canceled 314Creates and returns a new coro. This coro is automcatially being canceled
180when the extension calling this is being unloaded. 315when the extension calling this is being unloaded.
181 316
182=cut 317=cut
183 318
184sub coro(&) { 319sub coro(&) {
185 my $cb = shift; 320 my $cb = shift;
186 321
187 my $coro; $coro = async { 322 my $coro = &cf::async ($cb);
188 eval {
189 $cb->();
190 };
191 warn $@ if $@;
192 };
193 323
194 $coro->on_destroy (sub { 324 $coro->on_destroy (sub {
195 delete $EXT_CORO{$coro+0}; 325 delete $EXT_CORO{$coro+0};
196 }); 326 });
197 $EXT_CORO{$coro+0} = $coro; 327 $EXT_CORO{$coro+0} = $coro;
198 328
199 $coro 329 $coro
200} 330}
201 331
332sub write_runtime {
333 my $runtime = cf::localdir . "/runtime";
334
335 my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644
336 or return;
337
338 my $value = $cf::RUNTIME + 1 + 10; # 10 is the runtime save interval, for a monotonic clock
339 (aio_write $fh, 0, (length $value), $value, 0) <= 0
340 and return;
341
342 aio_fsync $fh
343 and return;
344
345 close $fh
346 or return;
347
348 aio_rename "$runtime~", $runtime
349 and return;
350
351 1
352}
353
202=back 354=back
203 355
204=cut 356=cut
357
358#############################################################################
359
360package cf::path;
361
362sub new {
363 my ($class, $path, $base) = @_;
364
365 $path = $path->as_string if ref $path;
366
367 my $self = bless { }, $class;
368
369 # {... are special paths that are not touched
370 # ?xxx/... are special absolute paths
371 # ?random/... random maps
372 # /! non-realised random map exit
373 # /... normal maps
374 # ~/... per-player maps without a specific player (DO NOT USE)
375 # ~user/... per-player map of a specific user
376
377 if ($path =~ /^{/) {
378 # fine as it is
379 } elsif ($path =~ s{^\?random/}{}) {
380 Coro::AIO::aio_load "$cf::RANDOM_MAPS/$path.meta", my $data;
381 $self->{random} = cf::from_json $data;
382 } else {
383 if ($path =~ s{^~([^/]+)?}{}) {
384 $self->{user_rel} = 1;
385
386 if (defined $1) {
387 $self->{user} = $1;
388 } elsif ($base =~ m{^~([^/]+)/}) {
389 $self->{user} = $1;
390 } else {
391 warn "cannot resolve user-relative path without user <$path,$base>\n";
392 }
393 } elsif ($path =~ /^\//) {
394 # already absolute
395 } else {
396 $base =~ s{[^/]+/?$}{};
397 return $class->new ("$base/$path");
398 }
399
400 for ($path) {
401 redo if s{/\.?/}{/};
402 redo if s{/[^/]+/\.\./}{/};
403 }
404 }
405
406 $self->{path} = $path;
407
408 $self
409}
410
411# the name / primary key / in-game path
412sub as_string {
413 my ($self) = @_;
414
415 $self->{user_rel} ? "~$self->{user}$self->{path}"
416 : $self->{random} ? "?random/$self->{path}"
417 : $self->{path}
418}
419
420# the displayed name, this is a one way mapping
421sub visible_name {
422 my ($self) = @_;
423
424# if (my $rmp = $self->{random}) {
425# # todo: be more intelligent about this
426# "?random/$rmp->{origin_map}+$rmp->{origin_x}+$rmp->{origin_y}/$rmp->{dungeon_level}"
427# } else {
428 $self->as_string
429# }
430}
431
432# escape the /'s in the path
433sub _escaped_path {
434 # ∕ is U+2215
435 (my $path = $_[0]{path}) =~ s/\//∕/g;
436 $path
437}
438
439# the original (read-only) location
440sub load_path {
441 my ($self) = @_;
442
443 sprintf "%s/%s/%s", cf::datadir, cf::mapdir, $self->{path}
444}
445
446# the temporary/swap location
447sub save_path {
448 my ($self) = @_;
449
450 $self->{user_rel} ? sprintf "%s/%s/%s/%s", cf::localdir, cf::playerdir, $self->{user}, $self->_escaped_path
451 : $self->{random} ? sprintf "%s/%s", $RANDOM_MAPS, $self->{path}
452 : sprintf "%s/%s/%s", cf::localdir, cf::tmpdir, $self->_escaped_path
453}
454
455# the unique path, might be eq to save_path
456sub uniq_path {
457 my ($self) = @_;
458
459 $self->{user_rel} || $self->{random}
460 ? undef
461 : sprintf "%s/%s/%s", cf::localdir, cf::uniquedir, $self->_escaped_path
462}
463
464# return random map parameters, or undef
465sub random_map_params {
466 my ($self) = @_;
467
468 $self->{random}
469}
470
471# this is somewhat ugly, but style maps do need special treatment
472sub is_style_map {
473 $_[0]{path} =~ m{^/styles/}
474}
475
476package cf;
205 477
206############################################################################# 478#############################################################################
207 479
208=head2 ATTACHABLE OBJECTS 480=head2 ATTACHABLE OBJECTS
209 481
563); 835);
564 836
565sub object_freezer_save { 837sub object_freezer_save {
566 my ($filename, $rdata, $objs) = @_; 838 my ($filename, $rdata, $objs) = @_;
567 839
840 sync_job {
568 if (length $$rdata) { 841 if (length $$rdata) {
569 warn sprintf "saving %s (%d,%d)\n", 842 warn sprintf "saving %s (%d,%d)\n",
570 $filename, length $$rdata, scalar @$objs; 843 $filename, length $$rdata, scalar @$objs;
571 844
572 if (open my $fh, ">:raw", "$filename~") { 845 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
573 chmod SAVE_MODE, $fh;
574 syswrite $fh, $$rdata;
575 close $fh;
576
577 if (@$objs && open my $fh, ">:raw", "$filename.pst~") {
578 chmod SAVE_MODE, $fh; 846 chmod SAVE_MODE, $fh;
579 syswrite $fh, Storable::nfreeze { version => 1, objs => $objs }; 847 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
848 aio_fsync $fh;
580 close $fh; 849 close $fh;
850
851 if (@$objs) {
852 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) {
853 chmod SAVE_MODE, $fh;
854 my $data = Storable::nfreeze { version => 1, objs => $objs };
855 aio_write $fh, 0, (length $data), $data, 0;
856 aio_fsync $fh;
857 close $fh;
581 rename "$filename.pst~", "$filename.pst"; 858 aio_rename "$filename.pst~", "$filename.pst";
859 }
860 } else {
861 aio_unlink "$filename.pst";
862 }
863
864 aio_rename "$filename~", $filename;
582 } else { 865 } else {
583 unlink "$filename.pst"; 866 warn "FATAL: $filename~: $!\n";
584 } 867 }
585
586 rename "$filename~", $filename;
587 } else { 868 } else {
588 warn "FATAL: $filename~: $!\n";
589 }
590 } else {
591 unlink $filename; 869 aio_unlink $filename;
592 unlink "$filename.pst"; 870 aio_unlink "$filename.pst";
871 }
593 } 872 }
594} 873}
595 874
596sub object_freezer_as_string { 875sub object_freezer_as_string {
597 my ($rdata, $objs) = @_; 876 my ($rdata, $objs) = @_;
602} 881}
603 882
604sub object_thawer_load { 883sub object_thawer_load {
605 my ($filename) = @_; 884 my ($filename) = @_;
606 885
607 local $/; 886 my ($data, $av);
608 887
609 my $av; 888 (aio_load $filename, $data) >= 0
889 or return;
610 890
611 #TODO: use sysread etc. 891 unless (aio_stat "$filename.pst") {
612 if (open my $data, "<:raw:perlio", $filename) { 892 (aio_load "$filename.pst", $av) >= 0
613 $data = <$data>; 893 or return;
614 if (open my $pst, "<:raw:perlio", "$filename.pst") {
615 $av = eval { (Storable::thaw <$pst>)->{objs} }; 894 $av = eval { (Storable::thaw $av)->{objs} };
616 } 895 }
896
897 warn sprintf "loading %s (%d)\n",
898 $filename, length $data, scalar @{$av || []};#d#
617 return ($data, $av); 899 return ($data, $av);
618 }
619
620 ()
621} 900}
622 901
623############################################################################# 902#############################################################################
624# command handling &c 903# command handling &c
625 904
846 $self->send ("ext " . to_json \%msg); 1125 $self->send ("ext " . to_json \%msg);
847} 1126}
848 1127
849=back 1128=back
850 1129
1130
1131=head3 cf::map
1132
1133=over 4
1134
1135=cut
1136
1137package cf::map;
1138
1139use Fcntl;
1140use Coro::AIO;
1141
1142our $MAX_RESET = 7200;
1143our $DEFAULT_RESET = 3600;
1144
1145sub generate_random_map {
1146 my ($path, $rmp) = @_;
1147
1148 # mit "rum" bekleckern, nicht
1149 cf::map::_create_random_map
1150 $path,
1151 $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
1152 $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
1153 $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
1154 $rmp->{exit_on_final_map},
1155 $rmp->{xsize}, $rmp->{ysize},
1156 $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
1157 $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
1158 $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
1159 $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
1160 $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
1161 (cf::region::find $rmp->{region})
1162}
1163
1164# and all this just because we cannot iterate over
1165# all maps in C++...
1166sub change_all_map_light {
1167 my ($change) = @_;
1168
1169 $_->change_map_light ($change)
1170 for grep $_->outdoor, values %cf::MAP;
1171}
1172
1173sub try_load_header($) {
1174 my ($path) = @_;
1175
1176 utf8::encode $path;
1177 aio_open $path, O_RDONLY, 0
1178 or return;
1179
1180 my $map = cf::map::new
1181 or return;
1182
1183 $map->load_header ($path)
1184 or return;
1185
1186 $map->{load_path} = $path;
1187
1188 $map
1189}
1190
1191sub find_map;
1192sub find_map {
1193 my ($path, $origin) = @_;
1194
1195 #warn "find_map<$path,$origin>\n";#d#
1196
1197 $path = new cf::path $path, $origin && $origin->path;
1198 my $key = $path->as_string;
1199
1200 cf::lock_wait "map_find:$key";
1201
1202 $cf::MAP{$key} || do {
1203 my $guard = cf::lock_acquire "map_find:$key";
1204
1205 # do it the slow way
1206 my $map = try_load_header $path->save_path;
1207
1208 if ($map) {
1209 # safety
1210 $map->{instantiate_time} = $cf::RUNTIME
1211 if $map->{instantiate_time} > $cf::RUNTIME;
1212 } else {
1213 if (my $rmp = $path->random_map_params) {
1214 $map = generate_random_map $key, $rmp;
1215 } else {
1216 $map = try_load_header $path->load_path;
1217 }
1218
1219 $map or return;
1220
1221 $map->{load_original} = 1;
1222 $map->{instantiate_time} = $cf::RUNTIME;
1223 $map->instantiate;
1224
1225 # per-player maps become, after loading, normal maps
1226 $map->per_player (0) if $path->{user_rel};
1227 }
1228
1229 $map->path ($key);
1230 $map->{path} = $path;
1231 $map->{last_save} = $cf::RUNTIME;
1232 $map->last_access ($cf::RUNTIME);
1233
1234 if ($map->should_reset) {
1235 $map->reset;
1236 undef $guard;
1237 $map = find_map $path
1238 or return;
1239 }
1240
1241 $cf::MAP{$key} = $map
1242 }
1243}
1244
1245sub load {
1246 my ($self) = @_;
1247
1248 my $path = $self->{path};
1249 my $guard = cf::lock_acquire "map_load:" . $path->as_string;
1250
1251 return if $self->in_memory != cf::MAP_SWAPPED;
1252
1253 $self->in_memory (cf::MAP_LOADING);
1254
1255 $self->alloc;
1256 $self->load_objects ($self->{load_path}, 1)
1257 or return;
1258
1259 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
1260 if delete $self->{load_original};
1261
1262 if (my $uniq = $path->uniq_path) {
1263 utf8::encode $uniq;
1264 if (aio_open $uniq, O_RDONLY, 0) {
1265 $self->clear_unique_items;
1266 $self->load_objects ($uniq, 0);
1267 }
1268 }
1269
1270 # now do the right thing for maps
1271 $self->link_multipart_objects;
1272
1273 if ($self->{path}->is_style_map) {
1274 $self->{deny_save} = 1;
1275 $self->{deny_reset} = 1;
1276 } else {
1277 $self->fix_auto_apply;
1278 $self->decay_objects;
1279 $self->update_buttons;
1280 $self->set_darkness_map;
1281 $self->difficulty ($self->estimate_difficulty)
1282 unless $self->difficulty;
1283 $self->activate;
1284 }
1285
1286 $self->in_memory (cf::MAP_IN_MEMORY);
1287}
1288
1289sub load_map_sync {
1290 my ($path, $origin) = @_;
1291
1292 #warn "load_map_sync<$path, $origin>\n";#d#
1293
1294 cf::sync_job {
1295 my $map = cf::map::find_map $path, $origin
1296 or return;
1297 $map->load;
1298 $map
1299 }
1300}
1301
1302sub save {
1303 my ($self) = @_;
1304
1305 $self->{last_save} = $cf::RUNTIME;
1306
1307 return unless $self->dirty;
1308
1309 my $save = $self->{path}->save_path; utf8::encode $save;
1310 my $uniq = $self->{path}->uniq_path; utf8::encode $uniq;
1311
1312 $self->{load_path} = $save;
1313
1314 return if $self->{deny_save};
1315
1316 if ($uniq) {
1317 $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
1318 $self->save_objects ($uniq, cf::IO_UNIQUES);
1319 } else {
1320 $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
1321 }
1322}
1323
1324sub swap_out {
1325 my ($self) = @_;
1326
1327 # save first because save cedes
1328 $self->save;
1329
1330 return if $self->players;
1331 return if $self->in_memory != cf::MAP_IN_MEMORY;
1332 return if $self->{deny_save};
1333
1334 $self->clear;
1335 $self->in_memory (cf::MAP_SWAPPED);
1336}
1337
1338sub reset_at {
1339 my ($self) = @_;
1340
1341 # TODO: safety, remove and allow resettable per-player maps
1342 return 1e99 if $self->{path}{user_rel};
1343 return 1e99 if $self->{deny_reset};
1344
1345 my $time = $self->fixed_resettime ? $self->{instantiate_time} : $self->last_access;
1346 my $to = List::Util::min $MAX_RESET, $self->reset_timeout || $DEFAULT_RESET;
1347
1348 $time + $to
1349}
1350
1351sub should_reset {
1352 my ($self) = @_;
1353
1354 $self->reset_at <= $cf::RUNTIME
1355}
1356
1357sub unlink_save {
1358 my ($self) = @_;
1359
1360 utf8::encode (my $save = $self->{path}->save_path);
1361 aioreq_pri 3; IO::AIO::aio_unlink $save;
1362 aioreq_pri 3; IO::AIO::aio_unlink "$save.pst";
1363}
1364
1365sub rename {
1366 my ($self, $new_path) = @_;
1367
1368 $self->unlink_save;
1369
1370 delete $cf::MAP{$self->path};
1371 $self->{path} = new cf::path $new_path;
1372 $self->path ($self->{path}->as_string);
1373 $cf::MAP{$self->path} = $self;
1374
1375 $self->save;
1376}
1377
1378sub reset {
1379 my ($self) = @_;
1380
1381 return if $self->players;
1382 return if $self->{path}{user_rel};#d#
1383
1384 warn "resetting map ", $self->path;#d#
1385
1386 delete $cf::MAP{$self->path};
1387
1388 $_->clear_links_to ($self) for values %cf::MAP;
1389
1390 $self->unlink_save;
1391 $self->destroy;
1392}
1393
1394my $nuke_counter = "aaaa";
1395
1396sub nuke {
1397 my ($self) = @_;
1398
1399 $self->{deny_save} = 1;
1400 $self->reset_timeout (1);
1401 $self->rename ("{nuke}/" . ($nuke_counter++));
1402 $self->reset; # polite request, might not happen
1403}
1404
1405sub customise_for {
1406 my ($map, $ob) = @_;
1407
1408 if ($map->per_player) {
1409 return cf::map::find_map "~" . $ob->name . "/" . $map->{path}{path};
1410 }
1411
1412 $map
1413}
1414
1415sub emergency_save {
1416 local $cf::FREEZE = 1;
1417
1418 warn "enter emergency map save\n";
1419
1420 cf::sync_job {
1421 warn "begin emergency map save\n";
1422 $_->save for values %cf::MAP;
1423 };
1424
1425 warn "end emergency map save\n";
1426}
1427
1428package cf;
1429
1430=back
1431
1432
851=head3 cf::object::player 1433=head3 cf::object::player
852 1434
853=over 4 1435=over 4
854 1436
855=item $player_object->reply ($npc, $msg[, $flags]) 1437=item $player_object->reply ($npc, $msg[, $flags])
890 (ref $cf::CFG{"may_$access"} 1472 (ref $cf::CFG{"may_$access"}
891 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}} 1473 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}}
892 : $cf::CFG{"may_$access"}) 1474 : $cf::CFG{"may_$access"})
893} 1475}
894 1476
1477=item $player_object->enter_link
1478
1479Freezes the player and moves him/her to a special map (C<{link}>).
1480
1481The player should be reaosnably safe there for short amounts of time. You
1482I<MUST> call C<leave_link> as soon as possible, though.
1483
1484=item $player_object->leave_link ($map, $x, $y)
1485
1486Moves the player out of the specila link map onto the given map. If the
1487map is not valid (or omitted), the player will be moved back to the
1488location he/she was before the call to C<enter_link>, or, if that fails,
1489to the emergency map position.
1490
1491Might block.
1492
1493=cut
1494
1495sub cf::object::player::enter_link {
1496 my ($self) = @_;
1497
1498 $self->deactivate_recursive;
1499
1500 return if $self->map == $LINK_MAP;
1501
1502 $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y]
1503 if $self->map;
1504
1505 $self->enter_map ($LINK_MAP, 20, 20);
1506}
1507
1508sub cf::object::player::leave_link {
1509 my ($self, $map, $x, $y) = @_;
1510
1511 my $link_pos = delete $self->{_link_pos};
1512
1513 unless ($map) {
1514 # restore original map position
1515 ($map, $x, $y) = @{ $link_pos || [] };
1516 $map = cf::map::find_map $map;
1517
1518 unless ($map) {
1519 ($map, $x, $y) = @$EMERGENCY_POSITION;
1520 $map = cf::map::find_map $map
1521 or die "FATAL: cannot load emergency map\n";
1522 }
1523 }
1524
1525 ($x, $y) = (-1, -1)
1526 unless (defined $x) && (defined $y);
1527
1528 # use -1 or undef as default coordinates, not 0, 0
1529 ($x, $y) = ($map->enter_x, $map->enter_y)
1530 if $x <=0 && $y <= 0;
1531
1532 $map->load;
1533
1534 $self->activate_recursive;
1535 $self->enter_map ($map, $x, $y);
1536}
1537
1538cf::player->attach (
1539 on_logout => sub {
1540 my ($pl) = @_;
1541
1542 # abort map switching before logout
1543 if ($pl->ob->{_link_pos}) {
1544 cf::sync_job {
1545 $pl->ob->leave_link
1546 };
1547 }
1548 },
1549 on_login => sub {
1550 my ($pl) = @_;
1551
1552 # try to abort aborted map switching on player login :)
1553 # should happen only on crashes
1554 if ($pl->ob->{_link_pos}) {
1555 $pl->ob->enter_link;
1556 cf::async {
1557 # we need this sleep as the login has a concurrent enter_exit running
1558 # and this sleep increases chances of the player not ending up in scorn
1559 Coro::Timer::sleep 1;
1560 $pl->ob->leave_link;
1561 };
1562 }
1563 },
1564);
1565
1566=item $player_object->goto_map ($path, $x, $y)
1567
1568=cut
1569
1570sub cf::object::player::goto_map {
1571 my ($self, $path, $x, $y) = @_;
1572
1573 $self->enter_link;
1574
1575 (cf::async {
1576 $path = new cf::path $path;
1577
1578 my $map = cf::map::find_map $path->as_string;
1579 $map = $map->customise_for ($self) if $map;
1580
1581# warn "entering ", $map->path, " at ($x, $y)\n"
1582# if $map;
1583
1584 $map or $self->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED);
1585
1586 $self->leave_link ($map, $x, $y);
1587 })->prio (1);
1588}
1589
1590=item $player_object->enter_exit ($exit_object)
1591
1592=cut
1593
1594sub parse_random_map_params {
1595 my ($spec) = @_;
1596
1597 my $rmp = { # defaults
1598 xsize => 10,
1599 ysize => 10,
1600 };
1601
1602 for (split /\n/, $spec) {
1603 my ($k, $v) = split /\s+/, $_, 2;
1604
1605 $rmp->{lc $k} = $v if (length $k) && (length $v);
1606 }
1607
1608 $rmp
1609}
1610
1611sub prepare_random_map {
1612 my ($exit) = @_;
1613
1614 # all this does is basically replace the /! path by
1615 # a new random map path (?random/...) with a seed
1616 # that depends on the exit object
1617
1618 my $rmp = parse_random_map_params $exit->msg;
1619
1620 if ($exit->map) {
1621 $rmp->{region} = $exit->map->region_name;
1622 $rmp->{origin_map} = $exit->map->path;
1623 $rmp->{origin_x} = $exit->x;
1624 $rmp->{origin_y} = $exit->y;
1625 }
1626
1627 $rmp->{random_seed} ||= $exit->random_seed;
1628
1629 my $data = cf::to_json $rmp;
1630 my $md5 = Digest::MD5::md5_hex $data;
1631
1632 if (my $fh = aio_open "$cf::RANDOM_MAPS/$md5.meta", O_WRONLY | O_CREAT, 0666) {
1633 aio_write $fh, 0, (length $data), $data, 0;
1634
1635 $exit->slaying ("?random/$md5");
1636 $exit->msg (undef);
1637 }
1638}
1639
1640sub cf::object::player::enter_exit {
1641 my ($self, $exit) = @_;
1642
1643 return unless $self->type == cf::PLAYER;
1644
1645 $self->enter_link;
1646
1647 (cf::async {
1648 unless (eval {
1649 prepare_random_map $exit
1650 if $exit->slaying eq "/!";
1651
1652 my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path;
1653 $self->goto_map ($path, $exit->stats->hp, $exit->stats->sp);
1654
1655 1;
1656 }) {
1657 $self->message ("Something went wrong deep within the crossfire server. "
1658 . "I'll try to bring you back to the map you were before. "
1659 . "Please report this to the dungeon master",
1660 cf::NDI_UNIQUE | cf::NDI_RED);
1661
1662 warn "ERROR in enter_exit: $@";
1663 $self->leave_link;
1664 }
1665 })->prio (1);
1666}
1667
895=head3 cf::client 1668=head3 cf::client
896 1669
897=over 4 1670=over 4
898 1671
899=item $client->send_drawinfo ($text, $flags) 1672=item $client->send_drawinfo ($text, $flags)
942 on_reply => sub { 1715 on_reply => sub {
943 my ($ns, $msg) = @_; 1716 my ($ns, $msg) = @_;
944 1717
945 # this weird shuffling is so that direct followup queries 1718 # this weird shuffling is so that direct followup queries
946 # get handled first 1719 # get handled first
947 my $queue = delete $ns->{query_queue}; 1720 my $queue = delete $ns->{query_queue}
1721 or return; # be conservative, not sure how that can happen, but we saw a crash here
948 1722
949 (shift @$queue)->[1]->($msg); 1723 (shift @$queue)->[1]->($msg);
950 1724
951 push @{ $ns->{query_queue} }, @$queue; 1725 push @{ $ns->{query_queue} }, @$queue;
952 1726
969=cut 1743=cut
970 1744
971sub cf::client::coro { 1745sub cf::client::coro {
972 my ($self, $cb) = @_; 1746 my ($self, $cb) = @_;
973 1747
974 my $coro; $coro = async { 1748 my $coro = &cf::async ($cb);
975 eval {
976 $cb->();
977 };
978 warn $@ if $@;
979 };
980 1749
981 $coro->on_destroy (sub { 1750 $coro->on_destroy (sub {
982 delete $self->{_coro}{$coro+0}; 1751 delete $self->{_coro}{$coro+0};
983 }); 1752 });
984 1753
1156 1925
1157{ 1926{
1158 my $path = cf::localdir . "/database.pst"; 1927 my $path = cf::localdir . "/database.pst";
1159 1928
1160 sub db_load() { 1929 sub db_load() {
1161 warn "loading database $path\n";#d# remove later
1162 $DB = stat $path ? Storable::retrieve $path : { }; 1930 $DB = stat $path ? Storable::retrieve $path : { };
1163 } 1931 }
1164 1932
1165 my $pid; 1933 my $pid;
1166 1934
1167 sub db_save() { 1935 sub db_save() {
1168 warn "saving database $path\n";#d# remove later
1169 waitpid $pid, 0 if $pid; 1936 waitpid $pid, 0 if $pid;
1170 if (0 == ($pid = fork)) { 1937 if (0 == ($pid = fork)) {
1171 $DB->{_meta}{version} = 1; 1938 $DB->{_meta}{version} = 1;
1172 Storable::nstore $DB, "$path~"; 1939 Storable::nstore $DB, "$path~";
1173 rename "$path~", $path; 1940 rename "$path~", $path;
1224 local $/; 1991 local $/;
1225 *CFG = YAML::Syck::Load <$fh>; 1992 *CFG = YAML::Syck::Load <$fh>;
1226} 1993}
1227 1994
1228sub main { 1995sub main {
1996 # we must not ever block the main coroutine
1997 local $Coro::idle = sub {
1998 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
1999 (Coro::unblock_sub {
2000 Event::one_event;
2001 })->();
2002 };
2003
1229 cfg_load; 2004 cfg_load;
1230 db_load; 2005 db_load;
1231 load_extensions; 2006 load_extensions;
1232 Event::loop; 2007 Event::loop;
1233} 2008}
1234 2009
1235############################################################################# 2010#############################################################################
1236# initialisation 2011# initialisation
1237 2012
1238sub _perl_reload() { 2013sub reload() {
2014 # can/must only be called in main
2015 if ($Coro::current != $Coro::main) {
2016 warn "can only reload from main coroutine\n";
2017 return;
2018 }
2019
1239 warn "reloading..."; 2020 warn "reloading...";
1240 2021
2022 local $FREEZE = 1;
2023 cf::emergency_save;
2024
1241 eval { 2025 eval {
1242 local $FREEZE = 1; 2026 # if anything goes wrong in here, we should simply crash as we already saved
1243
1244 cf::emergency_save;
1245 2027
1246 # cancel all watchers 2028 # cancel all watchers
1247 for (Event::all_watchers) { 2029 for (Event::all_watchers) {
1248 $_->cancel if $_->data & WF_AUTOCANCEL; 2030 $_->cancel if $_->data & WF_AUTOCANCEL;
1249 } 2031 }
1308 2090
1309 # reattach attachments to objects 2091 # reattach attachments to objects
1310 warn "reattach"; 2092 warn "reattach";
1311 _global_reattach; 2093 _global_reattach;
1312 }; 2094 };
1313 warn $@ if $@;
1314 2095
1315 warn "reloaded"; 2096 if ($@) {
2097 warn $@;
2098 warn "error while reloading, exiting.";
2099 exit 1;
2100 }
2101
2102 warn "reloaded successfully";
1316}; 2103};
1317 2104
1318sub perl_reload() { 2105#############################################################################
1319 _perl_reload; 2106
2107unless ($LINK_MAP) {
2108 $LINK_MAP = cf::map::new;
2109
2110 $LINK_MAP->width (41);
2111 $LINK_MAP->height (41);
2112 $LINK_MAP->alloc;
2113 $LINK_MAP->path ("{link}");
2114 $LINK_MAP->{path} = bless { path => "{link}" }, "cf::path";
2115 $LINK_MAP->in_memory (MAP_IN_MEMORY);
2116
2117 # dirty hack because... archetypes are not yet loaded
2118 Event->timer (
2119 after => 2,
2120 cb => sub {
2121 $_[0]->w->cancel;
2122
2123 # provide some exits "home"
2124 my $exit = cf::object::new "exit";
2125
2126 $exit->slaying ($EMERGENCY_POSITION->[0]);
2127 $exit->stats->hp ($EMERGENCY_POSITION->[1]);
2128 $exit->stats->sp ($EMERGENCY_POSITION->[2]);
2129
2130 $LINK_MAP->insert ($exit->clone, 19, 19);
2131 $LINK_MAP->insert ($exit->clone, 19, 20);
2132 $LINK_MAP->insert ($exit->clone, 19, 21);
2133 $LINK_MAP->insert ($exit->clone, 20, 19);
2134 $LINK_MAP->insert ($exit->clone, 20, 21);
2135 $LINK_MAP->insert ($exit->clone, 21, 19);
2136 $LINK_MAP->insert ($exit->clone, 21, 20);
2137 $LINK_MAP->insert ($exit->clone, 21, 21);
2138
2139 $exit->destroy;
2140 });
2141
2142 $LINK_MAP->{deny_save} = 1;
2143 $LINK_MAP->{deny_reset} = 1;
2144
2145 $cf::MAP{$LINK_MAP->path} = $LINK_MAP;
1320} 2146}
1321 2147
1322register "<global>", __PACKAGE__; 2148register "<global>", __PACKAGE__;
1323 2149
1324register_command "perl-reload" => sub { 2150register_command "reload" => sub {
1325 my ($who, $arg) = @_; 2151 my ($who, $arg) = @_;
1326 2152
1327 if ($who->flag (FLAG_WIZ)) { 2153 if ($who->flag (FLAG_WIZ)) {
2154 $who->message ("start of reload.");
2155 reload;
1328 $who->message ("reloading..."); 2156 $who->message ("end of reload.");
1329 _perl_reload;
1330 } 2157 }
1331}; 2158};
1332 2159
1333unshift @INC, $LIBDIR; 2160unshift @INC, $LIBDIR;
1334 2161
1353 }, 2180 },
1354); 2181);
1355 2182
1356IO::AIO::max_poll_time $TICK * 0.2; 2183IO::AIO::max_poll_time $TICK * 0.2;
1357 2184
2185Event->io (
1358Event->io (fd => IO::AIO::poll_fileno, 2186 fd => IO::AIO::poll_fileno,
1359 poll => 'r', 2187 poll => 'r',
1360 prio => 5, 2188 prio => 5,
1361 data => WF_AUTOCANCEL, 2189 data => WF_AUTOCANCEL,
1362 cb => \&IO::AIO::poll_cb); 2190 cb => \&IO::AIO::poll_cb,
2191);
1363 2192
1364# we must not ever block the main coroutine 2193Event->timer (
1365$Coro::idle = sub { 2194 data => WF_AUTOCANCEL,
1366 #Carp::cluck "FATAL: Coro::idle was called, major BUG\n";#d# 2195 after => 0,
1367 warn "FATAL: Coro::idle was called, major BUG\n"; 2196 interval => 10,
2197 cb => sub {
1368 (Coro::unblock_sub { 2198 (Coro::unblock_sub {
1369 Event::one_event; 2199 write_runtime
2200 or warn "ERROR: unable to write runtime file: $!";
1370 })->(); 2201 })->();
1371}; 2202 },
2203);
2204
2205END { cf::emergency_save }
1372 2206
13731 22071
1374 2208

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines