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.101 by root, Mon Dec 25 14:43:23 2006 UTC vs.
Revision 1.111 by root, Mon Jan 1 12:28:47 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 $FREEZE;
55our $RANDOM_MAPS = cf::localdir . "/random";
56our %EXT_CORO;
57
58binmode STDOUT;
59binmode STDERR;
60
61# read virtual server time, if available
62unless ($RUNTIME || !-e cf::localdir . "/runtime") {
63 open my $fh, "<", cf::localdir . "/runtime"
64 or die "unable to read runtime file: $!";
65 $RUNTIME = <$fh> + 0.;
66}
67
68mkdir cf::localdir;
69mkdir cf::localdir . "/" . cf::playerdir;
70mkdir cf::localdir . "/" . cf::tmpdir;
71mkdir cf::localdir . "/" . cf::uniquedir;
72mkdir $RANDOM_MAPS;
73
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];
46 78
47############################################################################# 79#############################################################################
48 80
49=head2 GLOBAL VARIABLES 81=head2 GLOBAL VARIABLES
50 82
51=over 4 83=over 4
52 84
53=item $cf::UPTIME 85=item $cf::UPTIME
54 86
55The timestamp of the server start (so not actually an uptime). 87The timestamp of the server start (so not actually an uptime).
88
89=item $cf::RUNTIME
90
91The time this server has run, starts at 0 and is increased by $cf::TICK on
92every server tick.
56 93
57=item $cf::LIBDIR 94=item $cf::LIBDIR
58 95
59The perl library directory, where extensions and cf-specific modules can 96The perl library directory, where extensions and cf-specific modules can
60be found. It will be added to C<@INC> automatically. 97be found. It will be added to C<@INC> automatically.
98
99=item $cf::NOW
100
101The time of the last (current) server tick.
61 102
62=item $cf::TICK 103=item $cf::TICK
63 104
64The interval between server ticks, in seconds. 105The interval between server ticks, in seconds.
65 106
73=cut 114=cut
74 115
75BEGIN { 116BEGIN {
76 *CORE::GLOBAL::warn = sub { 117 *CORE::GLOBAL::warn = sub {
77 my $msg = join "", @_; 118 my $msg = join "", @_;
119 utf8::encode $msg;
120
78 $msg .= "\n" 121 $msg .= "\n"
79 unless $msg =~ /\n$/; 122 unless $msg =~ /\n$/;
80 123
81 print STDERR "cfperl: $msg";
82 LOG llevError, "cfperl: $msg"; 124 LOG llevError, "cfperl: $msg";
83 }; 125 };
84} 126}
85 127
86@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable'; 128@safe::cf::global::ISA = @cf::global::ISA = 'cf::attachable';
139sub to_json($) { 181sub to_json($) {
140 $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs 182 $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs
141 JSON::Syck::Dump $_[0] 183 JSON::Syck::Dump $_[0]
142} 184}
143 185
186=item cf::sync_job { BLOCK }
187
188The design of crossfire+ requires that the main coro ($Coro::main) is
189always able to handle events or runnable, as crossfire+ is only partly
190reentrant. Thus "blocking" it by e.g. waiting for I/O is not acceptable.
191
192If it must be done, put the blocking parts into C<sync_job>. This will run
193the given BLOCK in another coroutine while waiting for the result. The
194server will be frozen during this time, so the block should either finish
195fast or be very important.
196
197=cut
198
199sub sync_job(&) {
200 my ($job) = @_;
201
202 my $busy = 1;
203 my @res;
204
205 my $coro = Coro::async {
206 @res = eval { $job->() };
207 warn $@ if $@;
208 undef $busy;
209 };
210
211 if ($Coro::current == $Coro::main) {
212 # TODO: use suspend/resume instead
213 local $FREEZE = 1;
214 $coro->prio (Coro::PRIO_MAX);
215 while ($busy) {
216 Coro::cede_notself;
217 Event::one_event unless Coro::nready;
218 }
219 } else {
220 $coro->join;
221 }
222
223 wantarray ? @res : $res[0]
224}
225
226=item $coro = cf::coro { BLOCK }
227
228Creates and returns a new coro. This coro is automcatially being canceled
229when the extension calling this is being unloaded.
230
231=cut
232
233sub coro(&) {
234 my $cb = shift;
235
236 my $coro; $coro = async {
237 eval {
238 $cb->();
239 };
240 warn $@ if $@;
241 };
242
243 $coro->on_destroy (sub {
244 delete $EXT_CORO{$coro+0};
245 });
246 $EXT_CORO{$coro+0} = $coro;
247
248 $coro
249}
250
251sub write_runtime {
252 my $runtime = cf::localdir . "/runtime";
253
254 my $fh = aio_open "$runtime~", O_WRONLY | O_CREAT, 0644
255 or return;
256
257 my $value = $cf::RUNTIME;
258 (aio_write $fh, 0, (length $value), $value, 0) <= 0
259 and return;
260
261 aio_fsync $fh
262 and return;
263
264 close $fh
265 or return;
266
267 aio_rename "$runtime~", $runtime
268 and return;
269
270 1
271}
272
144=back 273=back
145 274
146=cut 275=cut
276
277#############################################################################
278
279package cf::path;
280
281sub new {
282 my ($class, $path, $base) = @_;
283
284 $path = $path->as_string if ref $path;
285
286 my $self = bless { }, $class;
287
288 if ($path =~ s{^\?random/}{}) {
289 Coro::AIO::aio_load "$cf::RANDOM_MAPS/$path.meta", my $data;
290 $self->{random} = cf::from_json $data;
291 } else {
292 if ($path =~ s{^~([^/]+)?}{}) {
293 $self->{user_rel} = 1;
294
295 if (defined $1) {
296 $self->{user} = $1;
297 } elsif ($base =~ m{^~([^/]+)/}) {
298 $self->{user} = $1;
299 } else {
300 warn "cannot resolve user-relative path without user <$path,$base>\n";
301 }
302 } elsif ($path =~ /^\//) {
303 # already absolute
304 } else {
305 $base =~ s{[^/]+/?$}{};
306 return $class->new ("$base/$path");
307 }
308
309 for ($path) {
310 redo if s{/\.?/}{/};
311 redo if s{/[^/]+/\.\./}{/};
312 }
313 }
314
315 $self->{path} = $path;
316
317 $self
318}
319
320# the name / primary key / in-game path
321sub as_string {
322 my ($self) = @_;
323
324 $self->{user_rel} ? "~$self->{user}$self->{path}"
325 : $self->{random} ? "?random/$self->{path}"
326 : $self->{path}
327}
328
329# the displayed name, this is a one way mapping
330sub visible_name {
331 my ($self) = @_;
332
333# if (my $rmp = $self->{random}) {
334# # todo: be more intelligent about this
335# "?random/$rmp->{origin_map}+$rmp->{origin_x}+$rmp->{origin_y}/$rmp->{dungeon_level}"
336# } else {
337 $self->as_string
338# }
339}
340
341# escape the /'s in the path
342sub _escaped_path {
343 # ∕ is U+2215
344 (my $path = $_[0]{path}) =~ s/\//∕/g;
345 $path
346}
347
348# the original (read-only) location
349sub load_path {
350 my ($self) = @_;
351
352 sprintf "%s/%s/%s", cf::datadir, cf::mapdir, $self->{path}
353}
354
355# the temporary/swap location
356sub save_path {
357 my ($self) = @_;
358
359 $self->{user_rel} ? sprintf "%s/%s/%s/%s", cf::localdir, cf::playerdir, $self->{user}, $self->_escaped_path
360 : $self->{random} ? sprintf "%s/%s", $RANDOM_MAPS, $self->{path}
361 : sprintf "%s/%s/%s", cf::localdir, cf::tmpdir, $self->_escaped_path
362}
363
364# the unique path, might be eq to save_path
365sub uniq_path {
366 my ($self) = @_;
367
368 $self->{user_rel} || $self->{random}
369 ? undef
370 : sprintf "%s/%s/%s", cf::localdir, cf::uniquedir, $self->_escaped_path
371}
372
373# return random map parameters, or undef
374sub random_map_params {
375 my ($self) = @_;
376
377 $self->{random}
378}
379
380# this is somewhat ugly, but style maps do need special treatment
381sub is_style_map {
382 $_[0]{path} =~ m{^/styles/}
383}
384
385package cf;
147 386
148############################################################################# 387#############################################################################
149 388
150=head2 ATTACHABLE OBJECTS 389=head2 ATTACHABLE OBJECTS
151 390
454=cut 693=cut
455 694
456############################################################################# 695#############################################################################
457# object support 696# object support
458 697
698sub reattach {
699 # basically do the same as instantiate, without calling instantiate
700 my ($obj) = @_;
701
702 my $registry = $obj->registry;
703
704 @$registry = ();
705
706 delete $obj->{_attachment} unless scalar keys %{ $obj->{_attachment} || {} };
707
708 for my $name (keys %{ $obj->{_attachment} || {} }) {
709 if (my $attach = $attachment{$name}) {
710 for (@$attach) {
711 my ($klass, @attach) = @$_;
712 _attach $registry, $klass, @attach;
713 }
714 } else {
715 warn "object uses attachment '$name' that is not available, postponing.\n";
716 }
717 }
718}
719
459cf::attachable->attach ( 720cf::attachable->attach (
460 prio => -1000000, 721 prio => -1000000,
461 on_instantiate => sub { 722 on_instantiate => sub {
462 my ($obj, $data) = @_; 723 my ($obj, $data) = @_;
463 724
467 my ($name, $args) = @$_; 728 my ($name, $args) = @$_;
468 729
469 $obj->attach ($name, %{$args || {} }); 730 $obj->attach ($name, %{$args || {} });
470 } 731 }
471 }, 732 },
472 on_reattach => sub { 733 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 { 734 on_clone => sub {
493 my ($src, $dst) = @_; 735 my ($src, $dst) = @_;
494 736
495 @{$dst->registry} = @{$src->registry}; 737 @{$dst->registry} = @{$src->registry};
496 738
502); 744);
503 745
504sub object_freezer_save { 746sub object_freezer_save {
505 my ($filename, $rdata, $objs) = @_; 747 my ($filename, $rdata, $objs) = @_;
506 748
749 sync_job {
507 if (length $$rdata) { 750 if (length $$rdata) {
508 warn sprintf "saving %s (%d,%d)\n", 751 warn sprintf "saving %s (%d,%d)\n",
509 $filename, length $$rdata, scalar @$objs; 752 $filename, length $$rdata, scalar @$objs;
510 753
511 if (open my $fh, ">:raw", "$filename~") { 754 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; 755 chmod SAVE_MODE, $fh;
518 syswrite $fh, Storable::nfreeze { version => 1, objs => $objs }; 756 aio_write $fh, 0, (length $$rdata), $$rdata, 0;
757 aio_fsync $fh;
519 close $fh; 758 close $fh;
759
760 if (@$objs) {
761 if (my $fh = aio_open "$filename.pst~", O_WRONLY | O_CREAT, 0600) {
762 chmod SAVE_MODE, $fh;
763 my $data = Storable::nfreeze { version => 1, objs => $objs };
764 aio_write $fh, 0, (length $data), $data, 0;
765 aio_fsync $fh;
766 close $fh;
520 rename "$filename.pst~", "$filename.pst"; 767 aio_rename "$filename.pst~", "$filename.pst";
768 }
769 } else {
770 aio_unlink "$filename.pst";
771 }
772
773 aio_rename "$filename~", $filename;
521 } else { 774 } else {
522 unlink "$filename.pst"; 775 warn "FATAL: $filename~: $!\n";
523 } 776 }
524
525 rename "$filename~", $filename;
526 } else { 777 } else {
527 warn "FATAL: $filename~: $!\n";
528 }
529 } else {
530 unlink $filename; 778 aio_unlink $filename;
531 unlink "$filename.pst"; 779 aio_unlink "$filename.pst";
780 }
532 } 781 }
533} 782}
534 783
535sub object_freezer_as_string { 784sub object_freezer_as_string {
536 my ($rdata, $objs) = @_; 785 my ($rdata, $objs) = @_;
541} 790}
542 791
543sub object_thawer_load { 792sub object_thawer_load {
544 my ($filename) = @_; 793 my ($filename) = @_;
545 794
546 local $/; 795 my ($data, $av);
547 796
548 my $av; 797 (aio_load $filename, $data) >= 0
798 or return;
549 799
550 #TODO: use sysread etc. 800 unless (aio_stat "$filename.pst") {
551 if (open my $data, "<:raw:perlio", $filename) { 801 (aio_load "$filename.pst", $av) >= 0
552 $data = <$data>; 802 or return;
553 if (open my $pst, "<:raw:perlio", "$filename.pst") {
554 $av = eval { (Storable::thaw <$pst>)->{objs} }; 803 $av = eval { (Storable::thaw <$av>)->{objs} };
555 } 804 }
805
556 return ($data, $av); 806 return ($data, $av);
557 }
558
559 ()
560} 807}
561 808
562############################################################################# 809#############################################################################
563# command handling &c 810# command handling &c
564 811
785 $self->send ("ext " . to_json \%msg); 1032 $self->send ("ext " . to_json \%msg);
786} 1033}
787 1034
788=back 1035=back
789 1036
1037
1038=head3 cf::map
1039
1040=over 4
1041
1042=cut
1043
1044package cf::map;
1045
1046use Fcntl;
1047use Coro::AIO;
1048
1049our $MAX_RESET = 7200;
1050our $DEFAULT_RESET = 3600;
1051$MAX_RESET = 10;#d#
1052$DEFAULT_RESET = 10;#d#
1053
1054sub generate_random_map {
1055 my ($path, $rmp) = @_;
1056
1057 # mit "rum" bekleckern, nicht
1058 cf::map::_create_random_map
1059 $path,
1060 $rmp->{wallstyle}, $rmp->{wall_name}, $rmp->{floorstyle}, $rmp->{monsterstyle},
1061 $rmp->{treasurestyle}, $rmp->{layoutstyle}, $rmp->{doorstyle}, $rmp->{decorstyle},
1062 $rmp->{origin_map}, $rmp->{final_map}, $rmp->{exitstyle}, $rmp->{this_map},
1063 $rmp->{exit_on_final_map},
1064 $rmp->{xsize}, $rmp->{ysize},
1065 $rmp->{expand2x}, $rmp->{layoutoptions1}, $rmp->{layoutoptions2}, $rmp->{layoutoptions3},
1066 $rmp->{symmetry}, $rmp->{difficulty}, $rmp->{difficulty_given}, $rmp->{difficulty_increase},
1067 $rmp->{dungeon_level}, $rmp->{dungeon_depth}, $rmp->{decoroptions}, $rmp->{orientation},
1068 $rmp->{origin_y}, $rmp->{origin_x}, $rmp->{random_seed}, $rmp->{total_map_hp},
1069 $rmp->{map_layout_style}, $rmp->{treasureoptions}, $rmp->{symmetry_used},
1070 (cf::region::find $rmp->{region})
1071}
1072
1073# and all this just because we cannot iterate over
1074# all maps in C++...
1075sub change_all_map_light {
1076 my ($change) = @_;
1077
1078 $_->change_map_light ($change) for values %cf::MAP;
1079}
1080
1081sub try_load_header($) {
1082 my ($path) = @_;
1083
1084 utf8::encode $path;
1085 aio_open $path, O_RDONLY, 0
1086 or return;
1087
1088 my $map = cf::map::new
1089 or return;
1090
1091 $map->load_header ($path)
1092 or return;
1093
1094 $map->{load_path} = $path;
1095 use Data::Dumper; warn Dumper $map;#d#
1096
1097 $map
1098}
1099
1100sub find_map {
1101 my ($path, $origin) = @_;
1102
1103 #warn "find_map<$path,$origin>\n";#d#
1104
1105 $path = ref $path ? $path : new cf::path $path, $origin && $origin->path;
1106 my $key = $path->as_string;
1107
1108 $cf::MAP{$key} || do {
1109 # do it the slow way
1110 my $map = try_load_header $path->save_path;
1111
1112 if ($map) {
1113 # safety
1114 $map->{instantiate_time} = $cf::RUNTIME
1115 if $map->{instantiate_time} > $cf::RUNTIME;
1116 } else {
1117 if (my $rmp = $path->random_map_params) {
1118 $map = generate_random_map $key, $rmp;
1119 } else {
1120 $map = try_load_header $path->load_path;
1121 }
1122
1123 $map or return;
1124
1125 $map->{load_original} = 1;
1126 $map->{instantiate_time} = $cf::RUNTIME;
1127 $map->instantiate;
1128
1129 # per-player maps become, after loading, normal maps
1130 $map->per_player (0) if $path->{user_rel};
1131 }
1132
1133 $map->path ($key);
1134 $map->{path} = $path;
1135 $map->last_access ($cf::RUNTIME);
1136
1137 $map->reset if $map->should_reset;
1138
1139 $cf::MAP{$key} = $map
1140 }
1141}
1142
1143sub load {
1144 my ($self) = @_;
1145
1146 return if $self->in_memory != cf::MAP_SWAPPED;
1147
1148 $self->in_memory (cf::MAP_LOADING);
1149
1150 my $path = $self->{path};
1151
1152 $self->alloc;
1153 $self->load_objects ($self->{load_path}, 1)
1154 or return;
1155
1156 $self->set_object_flag (cf::FLAG_OBJ_ORIGINAL, 1) if delete $self->{load_original};
1157
1158 if (my $uniq = $path->uniq_path) {
1159 utf8::encode $uniq;
1160 if (aio_open $uniq, O_RDONLY, 0) {
1161 $self->clear_unique_items;
1162 $self->load_objects ($uniq, 0);
1163 }
1164 }
1165
1166 # now do the right thing for maps
1167 $self->link_multipart_objects;
1168
1169 if ($self->{path}->is_style_map) {
1170 $self->{deny_save} = 1;
1171 $self->{deny_reset} = 1;
1172 } else {
1173 $self->fix_auto_apply;
1174 $self->decay_objects;
1175 $self->update_buttons;
1176 $self->set_darkness_map;
1177 $self->difficulty ($self->estimate_difficulty)
1178 unless $self->difficulty;
1179 $self->activate;
1180 }
1181
1182 $self->in_memory (cf::MAP_IN_MEMORY);
1183}
1184
1185sub load_map_sync {
1186 my ($path, $origin) = @_;
1187
1188 #warn "load_map_sync<$path, $origin>\n";#d#
1189
1190 cf::sync_job {
1191 my $map = cf::map::find_map $path, $origin
1192 or return;
1193 $map->load;
1194 $map
1195 }
1196}
1197
1198sub save {
1199 my ($self) = @_;
1200
1201 my $save = $self->{path}->save_path; utf8::encode $save;
1202 my $uniq = $self->{path}->uniq_path; utf8::encode $uniq;
1203
1204 $self->{last_save} = $cf::RUNTIME;
1205
1206 return unless $self->dirty;
1207
1208 $self->{load_path} = $save;
1209
1210 return if $self->{deny_save};
1211
1212 warn "saving map ", $self->path;
1213
1214 if ($uniq) {
1215 $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS);
1216 $self->save_objects ($uniq, cf::IO_UNIQUES);
1217 } else {
1218 $self->save_objects ($save, cf::IO_HEADER | cf::IO_OBJECTS | cf::IO_UNIQUES);
1219 }
1220}
1221
1222sub swap_out {
1223 my ($self) = @_;
1224
1225 return if $self->players;
1226 return if $self->in_memory != cf::MAP_IN_MEMORY;
1227 return if $self->{deny_save};
1228
1229 $self->save;
1230 $self->clear;
1231 $self->in_memory (cf::MAP_SWAPPED);
1232}
1233
1234sub should_reset {
1235 my ($map) = @_;
1236
1237 # TODO: safety, remove and allow resettable per-player maps
1238 return if $map->{path}{user_rel};#d#
1239 return if $map->{deny_reset};
1240 #return unless $map->reset_timeout;
1241
1242 my $time = $map->fixed_resettime ? $map->{instantiate_time} : $map->last_access;
1243 my $to = $map->reset_timeout || $DEFAULT_RESET;
1244 $to = $MAX_RESET if $to > $MAX_RESET;
1245
1246 $time + $to < $cf::RUNTIME
1247}
1248
1249sub unlink_save {
1250 my ($self) = @_;
1251
1252 utf8::encode (my $save = $self->{path}->save_path);
1253 aioreq_pri 3; IO::AIO::aio_unlink $save;
1254 aioreq_pri 3; IO::AIO::aio_unlink "$save.pst";
1255}
1256
1257sub reset {
1258 my ($self) = @_;
1259
1260 return if $self->players;
1261 return if $self->{path}{user_rel};#d#
1262
1263 warn "resetting map ", $self->path;#d#
1264
1265 delete $cf::MAP{$self->path};
1266
1267 $_->clear_links_to ($self) for values %cf::MAP;
1268
1269 $self->unlink_save;
1270 $self->destroy;
1271}
1272
1273sub customise_for {
1274 my ($map, $ob) = @_;
1275
1276 if ($map->per_player) {
1277 return cf::map::find_map "~" . $ob->name . "/" . $map->{path}{path};
1278 }
1279
1280 $map
1281}
1282
1283sub emergency_save {
1284 local $cf::FREEZE = 1;
1285
1286 warn "enter emergency map save\n";
1287
1288 cf::sync_job {
1289 warn "begin emergency map save\n";
1290 $_->save for values %cf::MAP;
1291 };
1292
1293 warn "end emergency map save\n";
1294}
1295
1296package cf;
1297
1298=back
1299
1300
790=head3 cf::object::player 1301=head3 cf::object::player
791 1302
792=over 4 1303=over 4
793 1304
794=item $player_object->reply ($npc, $msg[, $flags]) 1305=item $player_object->reply ($npc, $msg[, $flags])
827 1338
828 $self->flag (cf::FLAG_WIZ) || 1339 $self->flag (cf::FLAG_WIZ) ||
829 (ref $cf::CFG{"may_$access"} 1340 (ref $cf::CFG{"may_$access"}
830 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}} 1341 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}}
831 : $cf::CFG{"may_$access"}) 1342 : $cf::CFG{"may_$access"})
1343}
1344
1345sub cf::object::player::enter_link {
1346 my ($self) = @_;
1347
1348 return if $self->map == $LINK_MAP;
1349
1350 $self->{_link_pos} = [$self->map->{path}, $self->x, $self->y]
1351 if $self->map;
1352
1353 $self->enter_map ($LINK_MAP, 20, 20);
1354 $self->deactivate_recursive;
1355}
1356
1357sub cf::object::player::leave_link {
1358 my ($self, $map, $x, $y) = @_;
1359
1360 my $link_pos = delete $self->{_link_pos};
1361
1362 unless ($map) {
1363 $self->message ("The exit is closed", cf::NDI_UNIQUE | cf::NDI_RED);
1364
1365 # restore original map position
1366 ($map, $x, $y) = @{ $link_pos || [] };
1367 $map = cf::map::find_map $map;
1368
1369 unless ($map) {
1370 ($map, $x, $y) = @$EMERGENCY_POSITION;
1371 $map = cf::map::find_map $map
1372 or die "FATAL: cannot load emergency map\n";
1373 }
1374 }
1375
1376 ($x, $y) = (-1, -1)
1377 unless (defined $x) && (defined $y);
1378
1379 # use -1 or undef as default coordinates, not 0, 0
1380 ($x, $y) = ($map->enter_x, $map->enter_y)
1381 if $x <=0 && $y <= 0;
1382
1383 $map->load;
1384
1385 $self->activate_recursive;
1386 $self->enter_map ($map, $x, $y);
1387}
1388
1389=item $player_object->goto_map ($map, $x, $y)
1390
1391=cut
1392
1393sub cf::object::player::goto_map {
1394 my ($self, $path, $x, $y) = @_;
1395
1396 $self->enter_link;
1397
1398 (Coro::async {
1399 $path = new cf::path $path;
1400
1401 my $map = cf::map::find_map $path->as_string;
1402 $map = $map->customise_for ($self) if $map;
1403
1404 warn "entering ", $map->path, " at ($x, $y)\n"
1405 if $map;
1406
1407 $self->leave_link ($map, $x, $y);
1408 })->prio (1);
1409}
1410
1411=item $player_object->enter_exit ($exit_object)
1412
1413=cut
1414
1415sub parse_random_map_params {
1416 my ($spec) = @_;
1417
1418 my $rmp = { # defaults
1419 xsize => 10,
1420 ysize => 10,
1421 };
1422
1423 for (split /\n/, $spec) {
1424 my ($k, $v) = split /\s+/, $_, 2;
1425
1426 $rmp->{lc $k} = $v if (length $k) && (length $v);
1427 }
1428
1429 $rmp
1430}
1431
1432sub prepare_random_map {
1433 my ($exit) = @_;
1434
1435 # all this does is basically replace the /! path by
1436 # a new random map path (?random/...) with a seed
1437 # that depends on the exit object
1438
1439 my $rmp = parse_random_map_params $exit->msg;
1440
1441 if ($exit->map) {
1442 $rmp->{region} = $exit->map->region_name;
1443 $rmp->{origin_map} = $exit->map->path;
1444 $rmp->{origin_x} = $exit->x;
1445 $rmp->{origin_y} = $exit->y;
1446 }
1447
1448 $rmp->{random_seed} ||= $exit->random_seed;
1449
1450 my $data = cf::to_json $rmp;
1451 my $md5 = Digest::MD5::md5_hex $data;
1452
1453 if (my $fh = aio_open "$cf::RANDOM_MAPS/$md5.meta", O_WRONLY | O_CREAT, 0666) {
1454 aio_write $fh, 0, (length $data), $data, 0;
1455
1456 $exit->slaying ("?random/$md5");
1457 $exit->msg (undef);
1458 }
1459}
1460
1461sub cf::object::player::enter_exit {
1462 my ($self, $exit) = @_;
1463
1464 return unless $self->type == cf::PLAYER;
1465
1466 $self->enter_link;
1467
1468 (Coro::async {
1469 unless (eval {
1470
1471 prepare_random_map $exit
1472 if $exit->slaying eq "/!";
1473
1474 my $path = new cf::path $exit->slaying, $exit->map && $exit->map->path;
1475 $self->goto_map ($path, $exit->stats->hp, $exit->stats->sp);
1476
1477 1;
1478 }) {
1479 $self->message ("Something went wrong deep within the crossfire server. "
1480 . "I'll try to bring you back to the map you were before. "
1481 . "Please report this to the dungeon master",
1482 cf::NDI_UNIQUE | cf::NDI_RED);
1483
1484 warn "ERROR in enter_exit: $@";
1485 $self->leave_link;
1486 }
1487 })->prio (1);
832} 1488}
833 1489
834=head3 cf::client 1490=head3 cf::client
835 1491
836=over 4 1492=over 4
913 my $coro; $coro = async { 1569 my $coro; $coro = async {
914 eval { 1570 eval {
915 $cb->(); 1571 $cb->();
916 }; 1572 };
917 warn $@ if $@; 1573 warn $@ if $@;
1574 };
1575
1576 $coro->on_destroy (sub {
918 delete $self->{_coro}{$coro+0}; 1577 delete $self->{_coro}{$coro+0};
919 }; 1578 });
920 1579
921 $self->{_coro}{$coro+0} = $coro; 1580 $self->{_coro}{$coro+0} = $coro;
1581
1582 $coro
922} 1583}
923 1584
924cf::client->attach ( 1585cf::client->attach (
925 on_destroy => sub { 1586 on_destroy => sub {
926 my ($ns) = @_; 1587 my ($ns) = @_;
1158 local $/; 1819 local $/;
1159 *CFG = YAML::Syck::Load <$fh>; 1820 *CFG = YAML::Syck::Load <$fh>;
1160} 1821}
1161 1822
1162sub main { 1823sub main {
1824 # we must not ever block the main coroutine
1825 local $Coro::idle = sub {
1826 Carp::cluck "FATAL: Coro::idle was called, major BUG\n";#d#
1827 (Coro::unblock_sub {
1828 Event::one_event;
1829 })->();
1830 };
1831
1163 cfg_load; 1832 cfg_load;
1164 db_load; 1833 db_load;
1165 load_extensions; 1834 load_extensions;
1166 Event::loop; 1835 Event::loop;
1167} 1836}
1168 1837
1169############################################################################# 1838#############################################################################
1170# initialisation 1839# initialisation
1171 1840
1172sub _perl_reload(&) { 1841sub reload() {
1173 my ($msg) = @_; 1842 # can/must only be called in main
1843 if ($Coro::current != $Coro::main) {
1844 warn "can only reload from main coroutine\n";
1845 return;
1846 }
1174 1847
1175 $msg->("reloading..."); 1848 warn "reloading...";
1849
1850 local $FREEZE = 1;
1851 cf::emergency_save;
1176 1852
1177 eval { 1853 eval {
1854 # if anything goes wrong in here, we should simply crash as we already saved
1855
1178 # cancel all watchers 1856 # cancel all watchers
1179 for (Event::all_watchers) { 1857 for (Event::all_watchers) {
1180 $_->cancel if $_->data & WF_AUTOCANCEL; 1858 $_->cancel if $_->data & WF_AUTOCANCEL;
1181 } 1859 }
1182 1860
1861 # cancel all extension coros
1862 $_->cancel for values %EXT_CORO;
1863 %EXT_CORO = ();
1864
1183 # unload all extensions 1865 # unload all extensions
1184 for (@exts) { 1866 for (@exts) {
1185 $msg->("unloading <$_>"); 1867 warn "unloading <$_>";
1186 unload_extension $_; 1868 unload_extension $_;
1187 } 1869 }
1188 1870
1189 # unload all modules loaded from $LIBDIR 1871 # unload all modules loaded from $LIBDIR
1190 while (my ($k, $v) = each %INC) { 1872 while (my ($k, $v) = each %INC) {
1191 next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/; 1873 next unless $v =~ /^\Q$LIBDIR\E\/.*\.pm$/;
1192 1874
1193 $msg->("removing <$k>"); 1875 warn "removing <$k>";
1194 delete $INC{$k}; 1876 delete $INC{$k};
1195 1877
1196 $k =~ s/\.pm$//; 1878 $k =~ s/\.pm$//;
1197 $k =~ s/\//::/g; 1879 $k =~ s/\//::/g;
1198 1880
1203 Symbol::delete_package $k; 1885 Symbol::delete_package $k;
1204 } 1886 }
1205 1887
1206 # sync database to disk 1888 # sync database to disk
1207 cf::db_sync; 1889 cf::db_sync;
1890 IO::AIO::flush;
1208 1891
1209 # get rid of safe::, as good as possible 1892 # get rid of safe::, as good as possible
1210 Symbol::delete_package "safe::$_" 1893 Symbol::delete_package "safe::$_"
1211 for qw(cf::object cf::object::player cf::player cf::map cf::party cf::region); 1894 for qw(cf::attachable cf::object cf::object::player cf::client cf::player cf::map cf::party cf::region);
1212 1895
1213 # remove register_script_function callbacks 1896 # remove register_script_function callbacks
1214 # TODO 1897 # TODO
1215 1898
1216 # unload cf.pm "a bit" 1899 # unload cf.pm "a bit"
1219 # don't, removes xs symbols, too, 1902 # don't, removes xs symbols, too,
1220 # and global variables created in xs 1903 # and global variables created in xs
1221 #Symbol::delete_package __PACKAGE__; 1904 #Symbol::delete_package __PACKAGE__;
1222 1905
1223 # reload cf.pm 1906 # reload cf.pm
1224 $msg->("reloading cf.pm"); 1907 warn "reloading cf.pm";
1225 require cf; 1908 require cf;
1226 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt 1909 cf::_connect_to_perl; # nominally unnecessary, but cannot hurt
1227 1910
1228 # load config and database again 1911 # load config and database again
1229 cf::cfg_load; 1912 cf::cfg_load;
1230 cf::db_load; 1913 cf::db_load;
1231 1914
1232 # load extensions 1915 # load extensions
1233 $msg->("load extensions"); 1916 warn "load extensions";
1234 cf::load_extensions; 1917 cf::load_extensions;
1235 1918
1236 # reattach attachments to objects 1919 # reattach attachments to objects
1237 $msg->("reattach"); 1920 warn "reattach";
1238 _global_reattach; 1921 _global_reattach;
1239 }; 1922 };
1240 $msg->($@) if $@;
1241 1923
1242 $msg->("reloaded"); 1924 if ($@) {
1925 warn $@;
1926 warn "error while reloading, exiting.";
1927 exit 1;
1928 }
1929
1930 warn "reloaded successfully";
1243}; 1931};
1244 1932
1245sub perl_reload() { 1933#############################################################################
1246 _perl_reload { 1934
1247 warn $_[0]; 1935unless ($LINK_MAP) {
1248 print "$_[0]\n"; 1936 $LINK_MAP = cf::map::new;
1249 }; 1937
1938 $LINK_MAP->width (41);
1939 $LINK_MAP->height (41);
1940 $LINK_MAP->alloc;
1941 $LINK_MAP->path ("{link}");
1942 $LINK_MAP->{path} = bless { path => "{link}" }, "cf::path";
1943 $LINK_MAP->in_memory (MAP_IN_MEMORY);
1944
1945 # dirty hack because... archetypes are not yet loaded
1946 Event->timer (
1947 after => 2,
1948 cb => sub {
1949 $_[0]->w->cancel;
1950
1951 # provide some exits "home"
1952 my $exit = cf::object::new "exit";
1953
1954 $exit->slaying ($EMERGENCY_POSITION->[0]);
1955 $exit->stats->hp ($EMERGENCY_POSITION->[1]);
1956 $exit->stats->sp ($EMERGENCY_POSITION->[2]);
1957
1958 $LINK_MAP->insert ($exit->clone, 19, 19);
1959 $LINK_MAP->insert ($exit->clone, 19, 20);
1960 $LINK_MAP->insert ($exit->clone, 19, 21);
1961 $LINK_MAP->insert ($exit->clone, 20, 19);
1962 $LINK_MAP->insert ($exit->clone, 20, 21);
1963 $LINK_MAP->insert ($exit->clone, 21, 19);
1964 $LINK_MAP->insert ($exit->clone, 21, 20);
1965 $LINK_MAP->insert ($exit->clone, 21, 21);
1966
1967 $exit->destroy;
1968 });
1969
1970 $LINK_MAP->{deny_save} = 1;
1971 $LINK_MAP->{deny_reset} = 1;
1972
1973 $cf::MAP{$LINK_MAP->path} = $LINK_MAP;
1250} 1974}
1251 1975
1252register "<global>", __PACKAGE__; 1976register "<global>", __PACKAGE__;
1253 1977
1254register_command "perl-reload" => sub { 1978register_command "reload" => sub {
1255 my ($who, $arg) = @_; 1979 my ($who, $arg) = @_;
1256 1980
1257 if ($who->flag (FLAG_WIZ)) { 1981 if ($who->flag (FLAG_WIZ)) {
1258 _perl_reload { 1982 $who->message ("start of reload.");
1259 warn $_[0]; 1983 reload;
1260 $who->message ($_[0]); 1984 $who->message ("end of reload.");
1261 };
1262 } 1985 }
1263}; 1986};
1264 1987
1265unshift @INC, $LIBDIR; 1988unshift @INC, $LIBDIR;
1266 1989
1267$TICK_WATCHER = Event->timer ( 1990$TICK_WATCHER = Event->timer (
1991 reentrant => 0,
1268 prio => 0, 1992 prio => 0,
1269 at => $NEXT_TICK || 1, 1993 at => $NEXT_TICK || $TICK,
1270 data => WF_AUTOCANCEL, 1994 data => WF_AUTOCANCEL,
1271 cb => sub { 1995 cb => sub {
1996 unless ($FREEZE) {
1272 cf::server_tick; # one server iteration 1997 cf::server_tick; # one server iteration
1998 $RUNTIME += $TICK;
1999 }
1273 2000
1274 my $NOW = Event::time;
1275 $NEXT_TICK += $TICK; 2001 $NEXT_TICK += $TICK;
1276 2002
1277 # if we are delayed by four ticks or more, skip them all 2003 # if we are delayed by four ticks or more, skip them all
1278 $NEXT_TICK = $NOW if $NOW >= $NEXT_TICK + $TICK * 4; 2004 $NEXT_TICK = Event::time if Event::time >= $NEXT_TICK + $TICK * 4;
1279 2005
1280 $TICK_WATCHER->at ($NEXT_TICK); 2006 $TICK_WATCHER->at ($NEXT_TICK);
1281 $TICK_WATCHER->start; 2007 $TICK_WATCHER->start;
1282 }, 2008 },
1283); 2009);
1284 2010
1285IO::AIO::max_poll_time $TICK * 0.2; 2011IO::AIO::max_poll_time $TICK * 0.2;
1286 2012
2013Event->io (
1287Event->io (fd => IO::AIO::poll_fileno, 2014 fd => IO::AIO::poll_fileno,
1288 poll => 'r', 2015 poll => 'r',
1289 prio => 5, 2016 prio => 5,
1290 data => WF_AUTOCANCEL, 2017 data => WF_AUTOCANCEL,
1291 cb => \&IO::AIO::poll_cb); 2018 cb => \&IO::AIO::poll_cb,
2019);
2020
2021Event->timer (
2022 data => WF_AUTOCANCEL,
2023 after => 0,
2024 interval => 10,
2025 cb => sub {
2026 (Coro::unblock_sub {
2027 write_runtime
2028 or warn "ERROR: unable to write runtime file: $!";
2029 })->();
2030 },
2031);
1292 2032
12931 20331
1294 2034

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines