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.100 by root, Mon Dec 25 11:25:49 2006 UTC vs.
Revision 1.136 by root, Fri Jan 5 01:06:28 2007 UTC

8use Storable; 8use Storable;
9use Opcode; 9use Opcode;
10use Safe; 10use Safe;
11use Safe::Hole; 11use Safe::Hole;
12 12
13use Coro; 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
25# work around bug in YAML::Syck - bad news for perl6, will it be as broken wrt. unicode? 28# work around bug in YAML::Syck - bad news for perl6, will it be as broken wrt. unicode?
26$YAML::Syck::ImplicitUnicode = 1; 29$YAML::Syck::ImplicitUnicode = 1;
27 30
28$Coro::main->prio (Coro::PRIO_MIN); 31$Coro::main->prio (2); # run main coroutine ("the server") with very high priority
29 32
30sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload 33sub WF_AUTOCANCEL () { 1 } # automatically cancel this watcher on reload
31 34
32our %COMMAND = (); 35our %COMMAND = ();
33our %COMMAND_TIME = (); 36our %COMMAND_TIME = ();
37our $LIBDIR = datadir . "/ext"; 40our $LIBDIR = datadir . "/ext";
38 41
39our $TICK = MAX_TIME * 1e-6; 42our $TICK = MAX_TIME * 1e-6;
40our $TICK_WATCHER; 43our $TICK_WATCHER;
41our $NEXT_TICK; 44our $NEXT_TICK;
45our $NOW;
42 46
43our %CFG; 47our %CFG;
44 48
45our $UPTIME; $UPTIME ||= time; 49our $UPTIME; $UPTIME ||= time;
50our $RUNTIME;
51
52our %MAP; # all maps
53our $LINK_MAP; # the special {link} map
54our $RANDOM_MAPS = cf::localdir . "/random";
55our %EXT_CORO;
56
57binmode STDOUT;
58binmode STDERR;
59
60# read virtual server time, if available
61unless ($RUNTIME || !-e cf::localdir . "/runtime") {
62 open my $fh, "<", cf::localdir . "/runtime"
63 or die "unable to read runtime file: $!";
64 $RUNTIME = <$fh> + 0.;
65}
66
67mkdir cf::localdir;
68mkdir cf::localdir . "/" . cf::playerdir;
69mkdir cf::localdir . "/" . cf::tmpdir;
70mkdir cf::localdir . "/" . cf::uniquedir;
71mkdir $RANDOM_MAPS;
72
73# a special map that is always available
74our $LINK_MAP;
75our $EMERGENCY_POSITION;
46 76
47############################################################################# 77#############################################################################
48 78
49=head2 GLOBAL VARIABLES 79=head2 GLOBAL VARIABLES
50 80
51=over 4 81=over 4
52 82
53=item $cf::UPTIME 83=item $cf::UPTIME
54 84
55The timestamp of the server start (so not actually an uptime). 85The timestamp of the server start (so not actually an uptime).
86
87=item $cf::RUNTIME
88
89The time this server has run, starts at 0 and is increased by $cf::TICK on
90every server tick.
56 91
57=item $cf::LIBDIR 92=item $cf::LIBDIR
58 93
59The perl library directory, where extensions and cf-specific modules can 94The perl library directory, where extensions and cf-specific modules can
60be found. It will be added to C<@INC> automatically. 95be found. It will be added to C<@INC> automatically.
96
97=item $cf::NOW
98
99The time of the last (current) server tick.
61 100
62=item $cf::TICK 101=item $cf::TICK
63 102
64The interval between server ticks, in seconds. 103The interval between server ticks, in seconds.
65 104
73=cut 112=cut
74 113
75BEGIN { 114BEGIN {
76 *CORE::GLOBAL::warn = sub { 115 *CORE::GLOBAL::warn = sub {
77 my $msg = join "", @_; 116 my $msg = join "", @_;
117 utf8::encode $msg;
118
78 $msg .= "\n" 119 $msg .= "\n"
79 unless $msg =~ /\n$/; 120 unless $msg =~ /\n$/;
80 121
81 print STDERR "cfperl: $msg";
82 LOG llevError, "cfperl: $msg"; 122 LOG llevError, "cfperl: $msg";
83 }; 123 };
84} 124}
85 125
86@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable'; 126@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
139sub to_json($) { 179sub to_json($) {
140 $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs 180 $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs
141 JSON::Syck::Dump $_[0] 181 JSON::Syck::Dump $_[0]
142} 182}
143 183
184=item cf::lock_wait $string
185
186Wait until the given lock is available. See cf::lock_acquire.
187
188=item my $lock = cf::lock_acquire $string
189
190Wait until the given lock is available and then acquires it and returns
191a Coro::guard object. If the guard object gets destroyed (goes out of scope,
192for example when the coroutine gets canceled), the lock is automatically
193returned.
194
195Lock names should begin with a unique identifier (for example, cf::map::find
196uses map_find and cf::map::load uses map_load).
197
198=cut
199
200our %LOCK;
201
202sub lock_wait($) {
203 my ($key) = @_;
204
205 # wait for lock, if any
206 while ($LOCK{$key}) {
207 push @{ $LOCK{$key} }, $Coro::current;
208 Coro::schedule;
209 }
210}
211
212sub lock_acquire($) {
213 my ($key) = @_;
214
215 # wait, to be sure we are not locked
216 lock_wait $key;
217
218 $LOCK{$key} = [];
219
220 Coro::guard {
221 # wake up all waiters, to be on the safe side
222 $_->ready for @{ delete $LOCK{$key} };
223 }
224}
225
226=item cf::async { BLOCK }
227
228Like C<Coro::async>, but runs the given BLOCK in an eval and only logs the
229error instead of exiting the server in case of a problem.
230
231=cut
232
233sub async(&) {
234 my ($cb) = @_;
235
236 Coro::async {
237 eval { $cb->() };
238 warn $@ if $@;
239 }
240}
241
242sub freeze_mainloop {
243 return unless $TICK_WATCHER->is_active;
244
245 my $guard = Coro::guard { $TICK_WATCHER->start };
246 $TICK_WATCHER->stop;
247 $guard
248}
249
250=item cf::sync_job { BLOCK }
251
252The design of crossfire+ requires that the main coro ($Coro::main) is
253always able to handle events or runnable, as crossfire+ is only partly
254reentrant. Thus "blocking" it by e.g. waiting for I/O is not acceptable.
255
256If it must be done, put the blocking parts into C<sync_job>. This will run
257the given BLOCK in another coroutine while waiting for the result. The
258server will be frozen during this time, so the block should either finish
259fast or be very important.
260
261=cut
262
263sub sync_job(&) {
264 my ($job) = @_;
265
266 if ($Coro::current == $Coro::main) {
267 # this is the main coro, too bad, we have to block
268 # till the operation succeeds, freezing the server :/
269
270 # TODO: use suspend/resume instead
271 # (but this is cancel-safe)
272 my $freeze_guard = freeze_mainloop;
273
274 my $busy = 1;
275 my @res;
276
277 (Coro::async {
278 @res = eval { $job->() };
279 warn $@ if $@;
280 undef $busy;
281 })->prio (Coro::PRIO_MAX);
282
283 while ($busy) {
284 Coro::cede_notself;
285 Event::one_event unless Coro::nready;
286 }
287
288 wantarray ? @res : $res[0]
289 } else {
290 # we are in another coroutine, how wonderful, everything just works
291
292 $job->()
293 }
294}
295
296=item $coro = cf::coro { BLOCK }
297
298Creates and returns a new coro. This coro is automcatially being canceled
299when the extension calling this is being unloaded.
300
301=cut
302
303sub coro(&) {
304 my $cb = shift;
305
306 my $coro = &cf::async ($cb);
307
308 $coro->on_destroy (sub {
309 delete $EXT_CORO{$coro+0};
310 });
311 $EXT_CORO{$coro+0} = $coro;
312
313 $coro
314}
315
316sub write_runtime {
317 my $runtime = cf::localdir . "/runtime";
318
319 my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644
320 or return;
321
322 my $value = $cf::RUNTIME + 1 + 10; # 10 is the runtime save interval, for a monotonic clock
323 (aio_write $fh, 0, (length $value), $value, 0) <= 0
324 and return;
325
326 aio_fsync $fh
327 and return;
328
329 close $fh
330 or return;
331
332 aio_rename "$runtime~", $runtime
333 and return;
334
335 1
336}
337
144=back 338=back
145 339
146=cut 340=cut
341
342#############################################################################
343
344package cf::path;
345
346sub new {
347 my ($class, $path, $base) = @_;
348
349 $path = $path->as_string if ref $path;
350
351 my $self = bless { }, $class;
352
353 # {... are special paths that are not touched
354 # ?xxx/... are special absolute paths
355 # ?random/... random maps
356 # /! non-realised random map exit
357 # /... normal maps
358 # ~/... per-player maps without a specific player (DO NOT USE)
359 # ~user/... per-player map of a specific user
360
361 if ($path =~ /^{/) {
362 # fine as it is
363 } elsif ($path =~ s{^\?random/}{}) {
364 Coro::AIO::aio_load "$cf::RANDOM_MAPS/$path.meta", my $data;
365 $self->{random} = cf::from_json $data;
366 } else {
367 if ($path =~ s{^~([^/]+)?}{}) {
368 $self->{user_rel} = 1;
369
370 if (defined $1) {
371 $self->{user} = $1;
372 } elsif ($base =~ m{^~([^/]+)/}) {
373 $self->{user} = $1;
374 } else {
375 warn "cannot resolve user-relative path without user <$path,$base>\n";
376 }
377 } elsif ($path =~ /^\//) {
378 # already absolute
379 } else {
380 $base =~ s{[^/]+/?$}{};
381 return $class->new ("$base/$path");
382 }
383
384 for ($path) {
385 redo if s{/\.?/}{/};
386 redo if s{/[^/]+/\.\./}{/};
387 }
388 }
389
390 $self->{path} = $path;
391
392 $self
393}
394
395# the name / primary key / in-game path
396sub as_string {
397 my ($self) = @_;
398
399 $self->{user_rel} ? "~$self->{user}$self->{path}"
400 : $self->{random} ? "?random/$self->{path}"
401 : $self->{path}
402}
403
404# the displayed name, this is a one way mapping
405sub visible_name {
406 my ($self) = @_;
407
408# if (my $rmp = $self->{random}) {
409# # todo: be more intelligent about this
410# "?random/$rmp->{origin_map}+$rmp->{origin_x}+$rmp->{origin_y}/$rmp->{dungeon_level}"
411# } else {
412 $self->as_string
413# }
414}
415
416# escape the /'s in the path
417sub _escaped_path {
418 # ∕ is U+2215
419 (my $path = $_[0]{path}) =~ s/\//∕/g;
420 $path
421}
422
423# the original (read-only) location
424sub load_path {
425 my ($self) = @_;
426
427 sprintf "%s/%s/%s", cf::datadir, cf::mapdir, $self->{path}
428}
429
430# the temporary/swap location
431sub save_path {
432 my ($self) = @_;
433
434 $self->{user_rel} ? sprintf "%s/%s/%s/%s", cf::localdir, cf::playerdir, $self->{user}, $self->_escaped_path
435 : $self->{random} ? sprintf "%s/%s", $RANDOM_MAPS, $self->{path}
436 : sprintf "%s/%s/%s", cf::localdir, cf::tmpdir, $self->_escaped_path
437}
438
439# the unique path, might be eq to save_path
440sub uniq_path {
441 my ($self) = @_;
442
443 $self->{user_rel} || $self->{random}
444 ? undef
445 : sprintf "%s/%s/%s", cf::localdir, cf::uniquedir, $self->_escaped_path
446}
447
448# return random map parameters, or undef
449sub random_map_params {
450 my ($self) = @_;
451
452 $self->{random}
453}
454
455# this is somewhat ugly, but style maps do need special treatment
456sub is_style_map {
457 $_[0]{path} =~ m{^/styles/}
458}
459
460package cf;
147 461
148############################################################################# 462#############################################################################
149 463
150=head2 ATTACHABLE OBJECTS 464=head2 ATTACHABLE OBJECTS
151 465
454=cut 768=cut
455 769
456############################################################################# 770#############################################################################
457# object support 771# object support
458 772
773sub reattach {
774 # basically do the same as instantiate, without calling instantiate
775 my ($obj) = @_;
776
777 my $registry = $obj->registry;
778
779 @$registry = ();
780
781 delete $obj->{_attachment} unless scalar keys %{ $obj->{_attachment} || {} };
782
783 for my $name (keys %{ $obj->{_attachment} || {} }) {
784 if (my $attach = $attachment{$name}) {
785 for (@$attach) {
786 my ($klass, @attach) = @$_;
787 _attach $registry, $klass, @attach;
788 }
789 } else {
790 warn "object uses attachment '$name' that is not available, postponing.\n";
791 }
792 }
793}
794
459cf::attachable->attach ( 795cf::attachable->attach (
460 prio => -1000000, 796 prio => -1000000,
461 on_instantiate => sub { 797 on_instantiate => sub {
462 my ($obj, $data) = @_; 798 my ($obj, $data) = @_;
463 799
467 my ($name, $args) = @$_; 803 my ($name, $args) = @$_;
468 804
469 $obj->attach ($name, %{$args || {} }); 805 $obj->attach ($name, %{$args || {} });
470 } 806 }
471 }, 807 },
472 on_reattach => sub { 808 on_reattach => \&reattach,
473 # basically do the same as instantiate, without calling instantiate
474 my ($obj) = @_;
475 my $registry = $obj->registry;
476
477 @$registry = ();
478
479 delete $obj->{_attachment} unless scalar keys %{ $obj->{_attachment} || {} };
480
481 for my $name (keys %{ $obj->{_attachment} || {} }) {
482 if (my $attach = $attachment{$name}) {
483 for (@$attach) {
484 my ($klass, @attach) = @$_;
485 _attach $registry, $klass, @attach;
486 }
487 } else {
488 warn "object uses attachment '$name' that is not available, postponing.\n";
489 }
490 }
491 },
492 on_clone => sub { 809 on_clone => sub {
493 my ($src, $dst) = @_; 810 my ($src, $dst) = @_;
494 811
495 @{$dst->registry} = @{$src->registry}; 812 @{$dst->registry} = @{$src->registry};
496 813
502); 819);
503 820
504sub object_freezer_save { 821sub object_freezer_save {
505 my ($filename, $rdata, $objs) = @_; 822 my ($filename, $rdata, $objs) = @_;
506 823
824 sync_job {
507 if (length $$rdata) { 825 if (length $$rdata) {
508 warn sprintf "saving %s (%d,%d)\n", 826 warn sprintf "saving %s (%d,%d)\n",
509 $filename, length $$rdata, scalar @$objs; 827 $filename, length $$rdata, scalar @$objs;
510 828
511 if (open my $fh, ">:raw", "$filename~") { 829 if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) {
512 chmod SAVE_MODE, $fh;
513 syswrite $fh, $$rdata;
514 close $fh;
515
516 if (@$objs && open my $fh, ">:raw", "$filename.pst~") {
517 chmod SAVE_MODE, $fh; 830 chmod SAVE_MODE, $fh;
518 syswrite $fh, Storable::nfreeze { version => 1, objs => $objs }; 831 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
832 aio_fsync $fh;
519 close $fh; 833 close $fh;
834
835 if (@$objs) {
836 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) {
837 chmod SAVE_MODE, $fh;
838 my $data = Storable::nfreeze { version => 1, objs => $objs };
839 aio_write $fh, 0, (length $data), $data, 0;
840 aio_fsync $fh;
841 close $fh;
520 rename "$filename.pst~", "$filename.pst"; 842 aio_rename "$filename.pst~", "$filename.pst";
843 }
844 } else {
845 aio_unlink "$filename.pst";
846 }
847
848 aio_rename "$filename~", $filename;
521 } else { 849 } else {
522 unlink "$filename.pst"; 850 warn "FATAL: $filename~: $!\n";
523 } 851 }
524
525 rename "$filename~", $filename;
526 } else { 852 } else {
527 warn "FATAL: $filename~: $!\n";
528 }
529 } else {
530 unlink $filename; 853 aio_unlink $filename;
531 unlink "$filename.pst"; 854 aio_unlink "$filename.pst";
855 }
532 } 856 }
533} 857}
534 858
535sub object_freezer_as_string { 859sub object_freezer_as_string {
536 my ($rdata, $objs) = @_; 860 my ($rdata, $objs) = @_;
541} 865}
542 866
543sub object_thawer_load { 867sub object_thawer_load {
544 my ($filename) = @_; 868 my ($filename) = @_;
545 869
546 local $/; 870 my ($data, $av);
547 871
548 my $av; 872 (aio_load $filename, $data) >= 0
873 or return;
549 874
550 #TODO: use sysread etc. 875 unless (aio_stat "$filename.pst") {
551 if (open my $data, "<:raw:perlio", $filename) { 876 (aio_load "$filename.pst", $av) >= 0
552 $data = <$data>; 877 or return;
553 if (open my $pst, "<:raw:perlio", "$filename.pst") {
554 $av = eval { (Storable::thaw <$pst>)->{objs} }; 878 $av = eval { (Storable::thaw $av)->{objs} };
555 } 879 }
880
881 warn sprintf "loading %s (%d)\n",
882 $filename, length $data, scalar @{$av || []};#d#
556 return ($data, $av); 883 return ($data, $av);
557 }
558
559 ()
560} 884}
561 885
562############################################################################# 886#############################################################################
563# command handling &c 887# command handling &c
564 888
785 $self->send ("ext " . to_json \%msg); 1109 $self->send ("ext " . to_json \%msg);
786} 1110}
787 1111
788=back 1112=back
789 1113
1114
1115=head3 cf::map
1116
1117=over 4
1118
1119=cut
1120
1121package cf::map;
1122
1123use Fcntl;
1124use Coro::AIO;
1125
1126our $MAX_RESET = 3600;
1127our $DEFAULT_RESET = 3000;
1128
1129sub generate_random_map {
1130 my ($path, $rmp) = @_;
1131
1132 # mit "rum" bekleckern, nicht
1133 cf::map::_create_random_map
1134 $path,
1135 $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
1136 $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
1137 $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
1138 $rmp->{exit_on_final_map},
1139 $rmp->{xsize}, $rmp->{ysize},
1140 $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
1141 $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
1142 $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
1143 $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
1144 $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
1145 (cf::region::find $rmp->{region})
1146}
1147
1148# and all this just because we cannot iterate over
1149# all maps in C++...
1150sub change_all_map_light {
1151 my ($change) = @_;
1152
1153 $_->change_map_light ($change)
1154 for grep $_->outdoor, values %cf::MAP;
1155}
1156
1157sub try_load_header($) {
1158 my ($path) = @_;
1159
1160 utf8::encode $path;
1161 aio_open $path, O_RDONLY, 0
1162 or return;
1163
1164 my $map = cf::map::new
1165 or return;
1166
1167 # for better error messages only, will be overwritten
1168 $map->path ($path);
1169
1170 $map->load_header ($path)
1171 or return;
1172
1173 $map->{load_path} = $path;
1174
1175 $map
1176}
1177
1178sub find;
1179sub find {
1180 my ($path, $origin) = @_;
1181
1182 #warn "find<$path,$origin>\n";#d#
1183
1184 $path = new cf::path $path, $origin && $origin->path;
1185 my $key = $path->as_string;
1186
1187 cf::lock_wait "map_find:$key";
1188
1189 $cf::MAP{$key} || do {
1190 my $guard = cf::lock_acquire "map_find:$key";
1191
1192 # do it the slow way
1193 my $map = try_load_header $path->save_path;
1194
1195 Coro::cede;
1196
1197 if ($map) {
1198 $map->last_access ((delete $map->{last_access})
1199 || $cf::RUNTIME); #d#
1200 # safety
1201 $map->{instantiate_time} = $cf::RUNTIME
1202 if $map->{instantiate_time} > $cf::RUNTIME;
1203 } else {
1204 if (my $rmp = $path->random_map_params) {
1205 $map = generate_random_map $key, $rmp;
1206 } else {
1207 $map = try_load_header $path->load_path;
1208 }
1209
1210 $map or return;
1211
1212 $map->{load_original} = 1;
1213 $map->{instantiate_time} = $cf::RUNTIME;
1214 $map->last_access ($cf::RUNTIME);
1215 $map->instantiate;
1216
1217 # per-player maps become, after loading, normal maps
1218 $map->per_player (0) if $path->{user_rel};
1219 }
1220
1221 $map->path ($key);
1222 $map->{path} = $path;
1223 $map->{last_save} = $cf::RUNTIME;
1224
1225 Coro::cede;
1226
1227 if ($map->should_reset) {
1228 $map->reset;
1229 undef $guard;
1230 $map = find $path
1231 or return;
1232 }
1233
1234 $cf::MAP{$key} = $map
1235 }
1236}
1237
1238sub load {
1239 my ($self) = @_;
1240
1241 my $path = $self->{path};
1242 my $guard = cf::lock_acquire "map_load:" . $path->as_string;
1243
1244 return if $self->in_memory != cf::MAP_SWAPPED;
1245
1246 $self->in_memory (cf::MAP_LOADING);
1247
1248 $self->alloc;
1249 $self->load_objects ($self->{load_path}, 1)
1250 or return;
1251
1252 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1)
1253 if delete $self->{load_original};
1254
1255 if (my $uniq = $path->uniq_path) {
1256 utf8::encode $uniq;
1257 if (aio_open $uniq, O_RDONLY, 0) {
1258 $self->clear_unique_items;
1259 $self->load_objects ($uniq, 0);
1260 }
1261 }
1262
1263 Coro::cede;
1264
1265 # now do the right thing for maps
1266 $self->link_multipart_objects;
1267
1268 if ($self->{path}->is_style_map) {
1269 $self->{deny_save} = 1;
1270 $self->{deny_reset} = 1;
1271 } else {
1272 $self->fix_auto_apply;
1273 $self->decay_objects;
1274 $self->update_buttons;
1275 $self->set_darkness_map;
1276 $self->difficulty ($self->estimate_difficulty)
1277 unless $self->difficulty;
1278 $self->activate;
1279 }
1280
1281 Coro::cede;
1282
1283 $self->in_memory (cf::MAP_IN_MEMORY);
1284}
1285
1286sub find_sync {
1287 my ($path, $origin) = @_;
1288
1289 cf::sync_job { cf::map::find $path, $origin }
1290}
1291
1292sub do_load_sync {
1293 my ($map) = @_;
1294
1295 cf::sync_job { $map->load };
1296}
1297
1298sub save {
1299 my ($self) = @_;
1300
1301 $self->{last_save} = $cf::RUNTIME;
1302
1303 return unless $self->dirty;
1304
1305 my $save = $self->{path}->save_path; utf8::encode $save;
1306 my $uniq = $self->{path}->uniq_path; utf8::encode $uniq;
1307
1308 $self->{load_path} = $save;
1309
1310 return if $self->{deny_save};
1311
1312 local $self->{last_access} = $self->last_access;#d#
1313
1314 if ($uniq) {
1315 $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
1316 $self->save_objects ($uniq, cf::IO_UNIQUES);
1317 } else {
1318 $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
1319 }
1320}
1321
1322sub swap_out {
1323 my ($self) = @_;
1324
1325 # save first because save cedes
1326 $self->save;
1327
1328 return if $self->players;
1329 return if $self->in_memory != cf::MAP_IN_MEMORY;
1330 return if $self->{deny_save};
1331
1332 $self->clear;
1333 $self->in_memory (cf::MAP_SWAPPED);
1334}
1335
1336sub reset_at {
1337 my ($self) = @_;
1338
1339 # TODO: safety, remove and allow resettable per-player maps
1340 return 1e99 if $self->{path}{user_rel};
1341 return 1e99 if $self->{deny_reset};
1342
1343 my $time = $self->fixed_resettime ? $self->{instantiate_time} : $self->last_access;
1344 my $to = List::Util::min $MAX_RESET, $self->reset_timeout || $DEFAULT_RESET;
1345
1346 $time + $to
1347}
1348
1349sub should_reset {
1350 my ($self) = @_;
1351
1352 $self->reset_at <= $cf::RUNTIME
1353}
1354
1355sub unlink_save {
1356 my ($self) = @_;
1357
1358 utf8::encode (my $save = $self->{path}->save_path);
1359 aioreq_pri 3; IO::AIO::aio_unlink $save;
1360 aioreq_pri 3; IO::AIO::aio_unlink "$save.pst";
1361}
1362
1363sub rename {
1364 my ($self, $new_path) = @_;
1365
1366 $self->unlink_save;
1367
1368 delete $cf::MAP{$self->path};
1369 $self->{path} = new cf::path $new_path;
1370 $self->path ($self->{path}->as_string);
1371 $cf::MAP{$self->path} = $self;
1372
1373 $self->save;
1374}
1375
1376sub reset {
1377 my ($self) = @_;
1378
1379 return if $self->players;
1380 return if $self->{path}{user_rel};#d#
1381
1382 warn "resetting map ", $self->path;#d#
1383
1384 delete $cf::MAP{$self->path};
1385
1386 $_->clear_links_to ($self) for values %cf::MAP;
1387
1388 $self->unlink_save;
1389 $self->destroy;
1390}
1391
1392my $nuke_counter = "aaaa";
1393
1394sub nuke {
1395 my ($self) = @_;
1396
1397 $self->{deny_save} = 1;
1398 $self->reset_timeout (1);
1399 $self->rename ("{nuke}/" . ($nuke_counter++));
1400 $self->reset; # polite request, might not happen
1401}
1402
1403sub customise_for {
1404 my ($map, $ob) = @_;
1405
1406 if ($map->per_player) {
1407 return cf::map::find "~" . $ob->name . "/" . $map->{path}{path};
1408 }
1409
1410 $map
1411}
1412
1413sub emergency_save {
1414 my $freeze_guard = cf::freeze_mainloop;
1415
1416 warn "enter emergency map save\n";
1417
1418 cf::sync_job {
1419 warn "begin emergency map save\n";
1420 $_->save for values %cf::MAP;
1421 };
1422
1423 warn "end emergency map save\n";
1424}
1425
1426package cf;
1427
1428=back
1429
1430
790=head3 cf::object::player 1431=head3 cf::object::player
791 1432
792=over 4 1433=over 4
793 1434
794=item $player_object->reply ($npc, $msg[, $flags]) 1435=item $player_object->reply ($npc, $msg[, $flags])
829 (ref $cf::CFG{"may_$access"} 1470 (ref $cf::CFG{"may_$access"}
830 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}} 1471 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}}
831 : $cf::CFG{"may_$access"}) 1472 : $cf::CFG{"may_$access"})
832} 1473}
833 1474
1475=item $player_object->enter_link
1476
1477Freezes the player and moves him/her to a special map (C<{link}>).
1478
1479The player should be reaosnably safe there for short amounts of time. You
1480I<MUST> call C<leave_link> as soon as possible, though.
1481
1482=item $player_object->leave_link ($map, $x, $y)
1483
1484Moves the player out of the specila link map onto the given map. If the
1485map is not valid (or omitted), the player will be moved back to the
1486location he/she was before the call to C<enter_link>, or, if that fails,
1487to the emergency map position.
1488
1489Might block.
1490
1491=cut
1492
1493sub cf::object::player::enter_link {
1494 my ($self) = @_;
1495
1496 $self->deactivate_recursive;
1497
1498 return if $self->map == $LINK_MAP;
1499
1500 $self->{_link_pos} ||= [$self->map->{path}, $self->x, $self->y]
1501 if $self->map;
1502
1503 $self->enter_map ($LINK_MAP, 20, 20);
1504}
1505
1506sub cf::object::player::leave_link {
1507 my ($self, $map, $x, $y) = @_;
1508
1509 my $link_pos = delete $self->{_link_pos};
1510
1511 unless ($map) {
1512 # restore original map position
1513 ($map, $x, $y) = @{ $link_pos || [] };
1514 $map = cf::map::find $map;
1515
1516 unless ($map) {
1517 ($map, $x, $y) = @$EMERGENCY_POSITION;
1518 $map = cf::map::find $map
1519 or die "FATAL: cannot load emergency map\n";
1520 }
1521 }
1522
1523 ($x, $y) = (-1, -1)
1524 unless (defined $x) && (defined $y);
1525
1526 # use -1 or undef as default coordinates, not 0, 0
1527 ($x, $y) = ($map->enter_x, $map->enter_y)
1528 if $x <=0 && $y <= 0;
1529
1530 $map->load;
1531
1532 $self->activate_recursive;
1533 $self->enter_map ($map, $x, $y);
1534}
1535
1536cf::player->attach (
1537 on_logout => sub {
1538 my ($pl) = @_;
1539
1540 # abort map switching before logout
1541 if ($pl->ob->{_link_pos}) {
1542 cf::sync_job {
1543 $pl->ob->leave_link
1544 };
1545 }
1546 },
1547 on_login => sub {
1548 my ($pl) = @_;
1549
1550 # try to abort aborted map switching on player login :)
1551 # should happen only on crashes
1552 if ($pl->ob->{_link_pos}) {
1553 $pl->ob->enter_link;
1554 cf::async {
1555 # we need this sleep as the login has a concurrent enter_exit running
1556 # and this sleep increases chances of the player not ending up in scorn
1557 Coro::Timer::sleep 1;
1558 $pl->ob->leave_link;
1559 };
1560 }
1561 },
1562);
1563
1564=item $player_object->goto ($path, $x, $y)
1565
1566=cut
1567
1568sub cf::object::player::goto {
1569 my ($self, $path, $x, $y) = @_;
1570
1571 $self->enter_link;
1572
1573 (cf::async {
1574 $path = new cf::path $path;
1575
1576 my $map = cf::map::find $path->as_string;
1577 $map = $map->customise_for ($self) if $map;
1578
1579# warn "entering ", $map->path, " at ($x, $y)\n"
1580# if $map;
1581
1582 $map or $self->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED);
1583
1584 $self->leave_link ($map, $x, $y);
1585 })->prio (1);
1586}
1587
1588=item $player_object->enter_exit ($exit_object)
1589
1590=cut
1591
1592sub parse_random_map_params {
1593 my ($spec) = @_;
1594
1595 my $rmp = { # defaults
1596 xsize => 10,
1597 ysize => 10,
1598 };
1599
1600 for (split /\n/, $spec) {
1601 my ($k, $v) = split /\s+/, $_, 2;
1602
1603 $rmp->{lc $k} = $v if (length $k) && (length $v);
1604 }
1605
1606 $rmp
1607}
1608
1609sub prepare_random_map {
1610 my ($exit) = @_;
1611
1612 # all this does is basically replace the /! path by
1613 # a new random map path (?random/...) with a seed
1614 # that depends on the exit object
1615
1616 my $rmp = parse_random_map_params $exit->msg;
1617
1618 if ($exit->map) {
1619 $rmp->{region} = $exit->map->region_name;
1620 $rmp->{origin_map} = $exit->map->path;
1621 $rmp->{origin_x} = $exit->x;
1622 $rmp->{origin_y} = $exit->y;
1623 }
1624
1625 $rmp->{random_seed} ||= $exit->random_seed;
1626
1627 my $data = cf::to_json $rmp;
1628 my $md5 = Digest::MD5::md5_hex $data;
1629
1630 if (my $fh = aio_open "$cf::RANDOM_MAPS/$md5.meta", O_WRONLY | O_CREAT, 0666) {
1631 aio_write $fh, 0, (length $data), $data, 0;
1632
1633 $exit->slaying ("?random/$md5");
1634 $exit->msg (undef);
1635 }
1636}
1637
1638sub cf::object::player::enter_exit {
1639 my ($self, $exit) = @_;
1640
1641 return unless $self->type == cf::PLAYER;
1642
1643 $self->enter_link;
1644
1645 (cf::async {
1646 $self->deactivate_recursive; # just to be sure
1647 unless (eval {
1648 prepare_random_map $exit
1649 if $exit->slaying eq "/!";
1650
1651 my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path;
1652 $self->goto ($path, $exit->stats->hp, $exit->stats->sp);
1653
1654 1;
1655 }) {
1656 $self->message ("Something went wrong deep within the crossfire server. "
1657 . "I'll try to bring you back to the map you were before. "
1658 . "Please report this to the dungeon master",
1659 cf::NDI_UNIQUE | cf::NDI_RED);
1660
1661 warn "ERROR in enter_exit: $@";
1662 $self->leave_link;
1663 }
1664 })->prio (1);
1665}
1666
834=head3 cf::client 1667=head3 cf::client
835 1668
836=over 4 1669=over 4
837 1670
838=item $client->send_drawinfo ($text, $flags) 1671=item $client->send_drawinfo ($text, $flags)
881 on_reply => sub { 1714 on_reply => sub {
882 my ($ns, $msg) = @_; 1715 my ($ns, $msg) = @_;
883 1716
884 # this weird shuffling is so that direct followup queries 1717 # this weird shuffling is so that direct followup queries
885 # get handled first 1718 # get handled first
886 my $queue = delete $ns->{query_queue}; 1719 my $queue = delete $ns->{query_queue}
1720 or return; # be conservative, not sure how that can happen, but we saw a crash here
887 1721
888 (shift @$queue)->[1]->($msg); 1722 (shift @$queue)->[1]->($msg);
889 1723
890 push @{ $ns->{query_queue} }, @$queue; 1724 push @{ $ns->{query_queue} }, @$queue;
891 1725
908=cut 1742=cut
909 1743
910sub cf::client::coro { 1744sub cf::client::coro {
911 my ($self, $cb) = @_; 1745 my ($self, $cb) = @_;
912 1746
913 my $coro; $coro = async { 1747 my $coro = &cf::async ($cb);
914 eval { 1748
915 $cb->(); 1749 $coro->on_destroy (sub {
916 };
917 warn $@ if $@;
918 delete $self->{_coro}{$coro+0}; 1750 delete $self->{_coro}{$coro+0};
919 }; 1751 });
920 1752
921 $self->{_coro}{$coro+0} = $coro; 1753 $self->{_coro}{$coro+0} = $coro;
1754
1755 $coro
922} 1756}
923 1757
924cf::client->attach ( 1758cf::client->attach (
925 on_destroy => sub { 1759 on_destroy => sub {
926 my ($ns) = @_; 1760 my ($ns) = @_;
1090 1924
1091{ 1925{
1092 my $path = cf::localdir . "/database.pst"; 1926 my $path = cf::localdir . "/database.pst";
1093 1927
1094 sub db_load() { 1928 sub db_load() {
1095 warn "loading database $path\n";#d# remove later
1096 $DB = stat $path ? Storable::retrieve $path : { }; 1929 $DB = stat $path ? Storable::retrieve $path : { };
1097 } 1930 }
1098 1931
1099 my $pid; 1932 my $pid;
1100 1933
1101 sub db_save() { 1934 sub db_save() {
1102 warn "saving database $path\n";#d# remove later
1103 waitpid $pid, 0 if $pid; 1935 waitpid $pid, 0 if $pid;
1104 if (0 == ($pid = fork)) { 1936 if (0 == ($pid = fork)) {
1105 $DB->{_meta}{version} = 1; 1937 $DB->{_meta}{version} = 1;
1106 Storable::nstore $DB, "$path~"; 1938 Storable::nstore $DB, "$path~";
1107 rename "$path~", $path; 1939 rename "$path~", $path;
1155 open my $fh, "<:utf8", cf::confdir . "/config" 1987 open my $fh, "<:utf8", cf::confdir . "/config"
1156 or return; 1988 or return;
1157 1989
1158 local $/; 1990 local $/;
1159 *CFG = YAML::Syck::Load <$fh>; 1991 *CFG = YAML::Syck::Load <$fh>;
1992
1993 $EMERGENCY_POSITION = $CFG{emergency_position} || ["/world/world_105_115", 5, 37];
1994
1995 if (exists $CFG{mlockall}) {
1996 eval {
1997 $CFG{mlockall} ? &mlockall : &munlockall
1998 and die "WARNING: m(un)lockall failed: $!\n";
1999 };
2000 warn $@ if $@;
2001 }
1160} 2002}
1161 2003
1162sub main { 2004sub main {
2005 # we must not ever block the main coroutine
2006 local $Coro::idle = sub {
2007 Carp::cluck "FATAL: Coro::idle was called, major BUG, use cf::sync_job!\n";#d#
2008 (Coro::unblock_sub {
2009 Event::one_event;
2010 })->();
2011 };
2012
1163 cfg_load; 2013 cfg_load;
1164 db_load; 2014 db_load;
1165 load_extensions; 2015 load_extensions;
1166 Event::loop; 2016 Event::loop;
1167} 2017}
1168 2018
1169############################################################################# 2019#############################################################################
1170# initialisation 2020# initialisation
1171 2021
1172sub _perl_reload(&) { 2022sub reload() {
1173 my ($msg) = @_; 2023 # can/must only be called in main
2024 if ($Coro::current != $Coro::main) {
2025 warn "can only reload from main coroutine\n";
2026 return;
2027 }
1174 2028
1175 $msg->("reloading..."); 2029 warn "reloading...";
2030
2031 my $guard = freeze_mainloop;
2032 cf::emergency_save;
1176 2033
1177 eval { 2034 eval {
2035 # if anything goes wrong in here, we should simply crash as we already saved
2036
1178 # cancel all watchers 2037 # cancel all watchers
1179 for (Event::all_watchers) { 2038 for (Event::all_watchers) {
1180 $_->cancel if $_->data & WF_AUTOCANCEL; 2039 $_->cancel if $_->data & WF_AUTOCANCEL;
1181 } 2040 }
1182 2041
2042 # cancel all extension coros
2043 $_->cancel for values %EXT_CORO;
2044 %EXT_CORO = ();
2045
1183 # unload all extensions 2046 # unload all extensions
1184 for (@exts) { 2047 for (@exts) {
1185 $msg->("unloading <$_>"); 2048 warn "unloading <$_>";
1186 unload_extension $_; 2049 unload_extension $_;
1187 } 2050 }
1188 2051
1189 # unload all modules loaded from $LIBDIR 2052 # unload all modules loaded from $LIBDIR
1190 while (my ($k, $v) = each %INC) { 2053 while (my ($k, $v) = each %INC) {
1191 next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/; 2054 next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
1192 2055
1193 $msg->("removing <$k>"); 2056 warn "removing <$k>";
1194 delete $INC{$k}; 2057 delete $INC{$k};
1195 2058
1196 $k =~ s/\.pm$//; 2059 $k =~ s/\.pm$//;
1197 $k =~ s/\//::/g; 2060 $k =~ s/\//::/g;
1198 2061
1203 Symbol::delete_package $k; 2066 Symbol::delete_package $k;
1204 } 2067 }
1205 2068
1206 # sync database to disk 2069 # sync database to disk
1207 cf::db_sync; 2070 cf::db_sync;
2071 IO::AIO::flush;
1208 2072
1209 # get rid of safe::, as good as possible 2073 # get rid of safe::, as good as possible
1210 Symbol::delete_package "safe::$_" 2074 Symbol::delete_package "safe::$_"
1211 for qw(cf::object cf::object::player cf::player cf::map cf::party cf::region); 2075 for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region);
1212 2076
1213 # remove register_script_function callbacks 2077 # remove register_script_function callbacks
1214 # TODO 2078 # TODO
1215 2079
1216 # unload cf.pm "a bit" 2080 # unload cf.pm "a bit"
1219 # don't, removes xs symbols, too, 2083 # don't, removes xs symbols, too,
1220 # and global variables created in xs 2084 # and global variables created in xs
1221 #Symbol::delete_package __PACKAGE__; 2085 #Symbol::delete_package __PACKAGE__;
1222 2086
1223 # reload cf.pm 2087 # reload cf.pm
1224 $msg->("reloading cf.pm"); 2088 warn "reloading cf.pm";
1225 require cf; 2089 require cf;
1226 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt 2090 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
1227
1228 2091
1229 # load config and database again 2092 # load config and database again
1230 cf::cfg_load; 2093 cf::cfg_load;
1231 cf::db_load; 2094 cf::db_load;
1232 2095
1233 # load extensions 2096 # load extensions
1234 $msg->("load extensions"); 2097 warn "load extensions";
1235 cf::load_extensions; 2098 cf::load_extensions;
1236 2099
1237 # reattach attachments to objects 2100 # reattach attachments to objects
1238 $msg->("reattach"); 2101 warn "reattach";
1239 _global_reattach; 2102 _global_reattach;
1240 }; 2103 };
1241 $msg->($@) if $@;
1242 2104
1243 $msg->("reloaded"); 2105 if ($@) {
2106 warn $@;
2107 warn "error while reloading, exiting.";
2108 exit 1;
2109 }
2110
2111 warn "reloaded successfully";
1244}; 2112};
1245 2113
1246sub perl_reload() { 2114#############################################################################
1247 _perl_reload { 2115
1248 warn $_[0]; 2116unless ($LINK_MAP) {
1249 print "$_[0]\n"; 2117 $LINK_MAP = cf::map::new;
1250 }; 2118
2119 $LINK_MAP->width (41);
2120 $LINK_MAP->height (41);
2121 $LINK_MAP->alloc;
2122 $LINK_MAP->path ("{link}");
2123 $LINK_MAP->{path} = bless { path => "{link}" }, "cf::path";
2124 $LINK_MAP->in_memory (MAP_IN_MEMORY);
2125
2126 # dirty hack because... archetypes are not yet loaded
2127 Event->timer (
2128 after => 2,
2129 cb => sub {
2130 $_[0]->w->cancel;
2131
2132 # provide some exits "home"
2133 my $exit = cf::object::new "exit";
2134
2135 $exit->slaying ($EMERGENCY_POSITION->[0]);
2136 $exit->stats->hp ($EMERGENCY_POSITION->[1]);
2137 $exit->stats->sp ($EMERGENCY_POSITION->[2]);
2138
2139 $LINK_MAP->insert ($exit->clone, 19, 19);
2140 $LINK_MAP->insert ($exit->clone, 19, 20);
2141 $LINK_MAP->insert ($exit->clone, 19, 21);
2142 $LINK_MAP->insert ($exit->clone, 20, 19);
2143 $LINK_MAP->insert ($exit->clone, 20, 21);
2144 $LINK_MAP->insert ($exit->clone, 21, 19);
2145 $LINK_MAP->insert ($exit->clone, 21, 20);
2146 $LINK_MAP->insert ($exit->clone, 21, 21);
2147
2148 $exit->destroy;
2149 });
2150
2151 $LINK_MAP->{deny_save} = 1;
2152 $LINK_MAP->{deny_reset} = 1;
2153
2154 $cf::MAP{$LINK_MAP->path} = $LINK_MAP;
1251} 2155}
1252 2156
1253register "<global>", __PACKAGE__; 2157register "<global>", __PACKAGE__;
1254 2158
1255register_command "perl-reload" => sub { 2159register_command "reload" => sub {
1256 my ($who, $arg) = @_; 2160 my ($who, $arg) = @_;
1257 2161
1258 if ($who->flag (FLAG_WIZ)) { 2162 if ($who->flag (FLAG_WIZ)) {
1259 _perl_reload { 2163 $who->message ("start of reload.");
1260 warn $_[0]; 2164 reload;
1261 $who->message ($_[0]); 2165 $who->message ("end of reload.");
1262 };
1263 } 2166 }
1264}; 2167};
1265 2168
1266unshift @INC, $LIBDIR; 2169unshift @INC, $LIBDIR;
1267 2170
1268$TICK_WATCHER = Event->timer ( 2171$TICK_WATCHER = Event->timer (
2172 reentrant => 0,
1269 prio => 0, 2173 prio => 0,
1270 at => $NEXT_TICK || 1, 2174 at => $NEXT_TICK || $TICK,
1271 data => WF_AUTOCANCEL, 2175 data => WF_AUTOCANCEL,
1272 cb => sub { 2176 cb => sub {
1273 cf::server_tick; # one server iteration 2177 cf::server_tick; # one server iteration
1274 2178 $RUNTIME += $TICK;
1275 my $NOW = Event::time;
1276 $NEXT_TICK += $TICK; 2179 $NEXT_TICK += $TICK;
1277 2180
1278 # if we are delayed by four ticks or more, skip them all 2181 # if we are delayed by four ticks or more, skip them all
1279 $NEXT_TICK = $NOW if $NOW >= $NEXT_TICK + $TICK * 4; 2182 $NEXT_TICK = Event::time if Event::time >= $NEXT_TICK + $TICK * 4;
1280 2183
1281 $TICK_WATCHER->at ($NEXT_TICK); 2184 $TICK_WATCHER->at ($NEXT_TICK);
1282 $TICK_WATCHER->start; 2185 $TICK_WATCHER->start;
1283 }, 2186 },
1284); 2187);
1285 2188
1286IO::AIO::max_poll_time $TICK * 0.2; 2189IO::AIO::max_poll_time $TICK * 0.2;
1287 2190
2191Event->io (
1288Event->io (fd => IO::AIO::poll_fileno, 2192 fd => IO::AIO::poll_fileno,
1289 poll => 'r', 2193 poll => 'r',
1290 prio => 5, 2194 prio => 5,
1291 data => WF_AUTOCANCEL, 2195 data => WF_AUTOCANCEL,
1292 cb => \&IO::AIO::poll_cb); 2196 cb => \&IO::AIO::poll_cb,
2197);
2198
2199Event->timer (
2200 data => WF_AUTOCANCEL,
2201 after => 0,
2202 interval => 10,
2203 cb => sub {
2204 (Coro::unblock_sub {
2205 write_runtime
2206 or warn "ERROR: unable to write runtime file: $!";
2207 })->();
2208 },
2209);
2210
2211END { cf::emergency_save }
1293 2212
12941 22131
1295 2214

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines