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.107 by root, Sun Dec 31 18:10:40 2006 UTC vs.
Revision 1.135 by root, Thu Jan 4 20:29:46 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; 18use Coro::AIO;
19 19
20use Digest::MD5;
20use Fcntl; 21use Fcntl;
21use IO::AIO 2.31 (); 22use IO::AIO 2.31 ();
22use YAML::Syck (); 23use YAML::Syck ();
23use Time::HiRes; 24use Time::HiRes;
24 25
48our $UPTIME; $UPTIME ||= time; 49our $UPTIME; $UPTIME ||= time;
49our $RUNTIME; 50our $RUNTIME;
50 51
51our %MAP; # all maps 52our %MAP; # all maps
52our $LINK_MAP; # the special {link} map 53our $LINK_MAP; # the special {link} map
53our $FREEZE; 54our $RANDOM_MAPS = cf::localdir . "/random";
55our %EXT_CORO;
54 56
55binmode STDOUT; 57binmode STDOUT;
56binmode STDERR; 58binmode STDERR;
57 59
58# read virtual server time, if available 60# read virtual server time, if available
64 66
65mkdir cf::localdir; 67mkdir cf::localdir;
66mkdir cf::localdir . "/" . cf::playerdir; 68mkdir cf::localdir . "/" . cf::playerdir;
67mkdir cf::localdir . "/" . cf::tmpdir; 69mkdir cf::localdir . "/" . cf::tmpdir;
68mkdir cf::localdir . "/" . cf::uniquedir; 70mkdir cf::localdir . "/" . cf::uniquedir;
71mkdir $RANDOM_MAPS;
69 72
70our %EXT_CORO; 73# a special map that is always available
74our $LINK_MAP;
75our $EMERGENCY_POSITION;
71 76
72############################################################################# 77#############################################################################
73 78
74=head2 GLOBAL VARIABLES 79=head2 GLOBAL VARIABLES
75 80
174sub to_json($) { 179sub to_json($) {
175 $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs 180 $JSON::Syck::ImplicitUnicode = 0; # work around JSON::Syck bugs
176 JSON::Syck::Dump $_[0] 181 JSON::Syck::Dump $_[0]
177} 182}
178 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
179=item cf::sync_job { BLOCK } 250=item cf::sync_job { BLOCK }
180 251
181The design of crossfire+ requires that the main coro ($Coro::main) is 252The design of crossfire+ requires that the main coro ($Coro::main) is
182always able to handle events or runnable, as crossfire+ is only partly 253always able to handle events or runnable, as crossfire+ is only partly
183reentrant. Thus "blocking" it by e.g. waiting for I/O is not acceptable. 254reentrant. Thus "blocking" it by e.g. waiting for I/O is not acceptable.
190=cut 261=cut
191 262
192sub sync_job(&) { 263sub sync_job(&) {
193 my ($job) = @_; 264 my ($job) = @_;
194 265
195 my $busy = 1;
196 my @res;
197
198 # TODO: use suspend/resume instead
199 local $FREEZE = 1;
200
201 my $coro = Coro::async {
202 @res = eval { $job->() };
203 warn $@ if $@;
204 undef $busy;
205 };
206
207 if ($Coro::current == $Coro::main) { 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;
208 $coro->prio (Coro::PRIO_MAX); 281 })->prio (Coro::PRIO_MAX);
282
209 while ($busy) { 283 while ($busy) {
210 Coro::cede_notself; 284 Coro::cede_notself;
211 Event::one_event unless Coro::nready; 285 Event::one_event unless Coro::nready;
212 } 286 }
287
288 wantarray ? @res : $res[0]
213 } else { 289 } else {
214 $coro->join; 290 # we are in another coroutine, how wonderful, everything just works
291
292 $job->()
215 } 293 }
216
217 wantarray ? @res : $res[0]
218} 294}
219 295
220=item $coro = cf::coro { BLOCK } 296=item $coro = cf::coro { BLOCK }
221 297
222Creates and returns a new coro. This coro is automcatially being canceled 298Creates and returns a new coro. This coro is automcatially being canceled
225=cut 301=cut
226 302
227sub coro(&) { 303sub coro(&) {
228 my $cb = shift; 304 my $cb = shift;
229 305
230 my $coro; $coro = async { 306 my $coro = &cf::async ($cb);
231 eval {
232 $cb->();
233 };
234 warn $@ if $@;
235 };
236 307
237 $coro->on_destroy (sub { 308 $coro->on_destroy (sub {
238 delete $EXT_CORO{$coro+0}; 309 delete $EXT_CORO{$coro+0};
239 }); 310 });
240 $EXT_CORO{$coro+0} = $coro; 311 $EXT_CORO{$coro+0} = $coro;
241 312
242 $coro 313 $coro
243} 314}
244 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
245=back 338=back
246 339
247=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;
248 461
249############################################################################# 462#############################################################################
250 463
251=head2 ATTACHABLE OBJECTS 464=head2 ATTACHABLE OBJECTS
252 465
660 or return; 873 or return;
661 874
662 unless (aio_stat "$filename.pst") { 875 unless (aio_stat "$filename.pst") {
663 (aio_load "$filename.pst", $av) >= 0 876 (aio_load "$filename.pst", $av) >= 0
664 or return; 877 or return;
665 $av = eval { (Storable::thaw <$av>)->{objs} }; 878 $av = eval { (Storable::thaw $av)->{objs} };
666 } 879 }
667 880
881 warn sprintf "loading %s (%d)\n",
882 $filename, length $data, scalar @{$av || []};#d#
668 return ($data, $av); 883 return ($data, $av);
669} 884}
670 885
671############################################################################# 886#############################################################################
672# command handling &c 887# command handling &c
894 $self->send ("ext " . to_json \%msg); 1109 $self->send ("ext " . to_json \%msg);
895} 1110}
896 1111
897=back 1112=back
898 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
899=head3 cf::object::player 1431=head3 cf::object::player
900 1432
901=over 4 1433=over 4
902 1434
903=item $player_object->reply ($npc, $msg[, $flags]) 1435=item $player_object->reply ($npc, $msg[, $flags])
938 (ref $cf::CFG{"may_$access"} 1470 (ref $cf::CFG{"may_$access"}
939 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}} 1471 ? scalar grep $self->name eq $_, @{$cf::CFG{"may_$access"}}
940 : $cf::CFG{"may_$access"}) 1472 : $cf::CFG{"may_$access"})
941} 1473}
942 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_map ($path, $x, $y)
1565
1566=cut
1567
1568sub cf::object::player::goto_map {
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_map ($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
943=head3 cf::client 1667=head3 cf::client
944 1668
945=over 4 1669=over 4
946 1670
947=item $client->send_drawinfo ($text, $flags) 1671=item $client->send_drawinfo ($text, $flags)
990 on_reply => sub { 1714 on_reply => sub {
991 my ($ns, $msg) = @_; 1715 my ($ns, $msg) = @_;
992 1716
993 # this weird shuffling is so that direct followup queries 1717 # this weird shuffling is so that direct followup queries
994 # get handled first 1718 # get handled first
995 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
996 1721
997 (shift @$queue)->[1]->($msg); 1722 (shift @$queue)->[1]->($msg);
998 1723
999 push @{ $ns->{query_queue} }, @$queue; 1724 push @{ $ns->{query_queue} }, @$queue;
1000 1725
1017=cut 1742=cut
1018 1743
1019sub cf::client::coro { 1744sub cf::client::coro {
1020 my ($self, $cb) = @_; 1745 my ($self, $cb) = @_;
1021 1746
1022 my $coro; $coro = async { 1747 my $coro = &cf::async ($cb);
1023 eval {
1024 $cb->();
1025 };
1026 warn $@ if $@;
1027 };
1028 1748
1029 $coro->on_destroy (sub { 1749 $coro->on_destroy (sub {
1030 delete $self->{_coro}{$coro+0}; 1750 delete $self->{_coro}{$coro+0};
1031 }); 1751 });
1032 1752
1204 1924
1205{ 1925{
1206 my $path = cf::localdir . "/database.pst"; 1926 my $path = cf::localdir . "/database.pst";
1207 1927
1208 sub db_load() { 1928 sub db_load() {
1209 warn "loading database $path\n";#d# remove later
1210 $DB = stat $path ? Storable::retrieve $path : { }; 1929 $DB = stat $path ? Storable::retrieve $path : { };
1211 } 1930 }
1212 1931
1213 my $pid; 1932 my $pid;
1214 1933
1215 sub db_save() { 1934 sub db_save() {
1216 warn "saving database $path\n";#d# remove later
1217 waitpid $pid, 0 if $pid; 1935 waitpid $pid, 0 if $pid;
1218 if (0 == ($pid = fork)) { 1936 if (0 == ($pid = fork)) {
1219 $DB->{_meta}{version} = 1; 1937 $DB->{_meta}{version} = 1;
1220 Storable::nstore $DB, "$path~"; 1938 Storable::nstore $DB, "$path~";
1221 rename "$path~", $path; 1939 rename "$path~", $path;
1269 open my $fh, "<:utf8", cf::confdir . "/config" 1987 open my $fh, "<:utf8", cf::confdir . "/config"
1270 or return; 1988 or return;
1271 1989
1272 local $/; 1990 local $/;
1273 *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 }
1274} 2002}
1275 2003
1276sub 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
1277 cfg_load; 2013 cfg_load;
1278 db_load; 2014 db_load;
1279 load_extensions; 2015 load_extensions;
1280 Event::loop; 2016 Event::loop;
1281} 2017}
1282 2018
1283############################################################################# 2019#############################################################################
1284# initialisation 2020# initialisation
1285 2021
1286sub perl_reload() { 2022sub reload() {
1287 # can/must only be called in main 2023 # can/must only be called in main
1288 if ($Coro::current != $Coro::main) { 2024 if ($Coro::current != $Coro::main) {
1289 warn "can only reload from main coroutine\n"; 2025 warn "can only reload from main coroutine\n";
1290 return; 2026 return;
1291 } 2027 }
1292 2028
1293 warn "reloading..."; 2029 warn "reloading...";
1294 2030
1295 local $FREEZE = 1; 2031 my $guard = freeze_mainloop;
1296 cf::emergency_save; 2032 cf::emergency_save;
1297 2033
1298 eval { 2034 eval {
1299 # if anything goes wrong in here, we should simply crash as we already saved 2035 # if anything goes wrong in here, we should simply crash as we already saved
1300 2036
1373 } 2109 }
1374 2110
1375 warn "reloaded successfully"; 2111 warn "reloaded successfully";
1376}; 2112};
1377 2113
2114#############################################################################
2115
2116unless ($LINK_MAP) {
2117 $LINK_MAP = cf::map::new;
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;
2155}
2156
1378register "<global>", __PACKAGE__; 2157register "<global>", __PACKAGE__;
1379 2158
1380register_command "perl-reload" => sub { 2159register_command "reload" => sub {
1381 my ($who, $arg) = @_; 2160 my ($who, $arg) = @_;
1382 2161
1383 if ($who->flag (FLAG_WIZ)) { 2162 if ($who->flag (FLAG_WIZ)) {
1384 $who->message ("start of reload."); 2163 $who->message ("start of reload.");
1385 perl_reload; 2164 reload;
1386 $who->message ("end of reload."); 2165 $who->message ("end of reload.");
1387 } 2166 }
1388}; 2167};
1389 2168
1390unshift @INC, $LIBDIR; 2169unshift @INC, $LIBDIR;
1393 reentrant => 0, 2172 reentrant => 0,
1394 prio => 0, 2173 prio => 0,
1395 at => $NEXT_TICK || $TICK, 2174 at => $NEXT_TICK || $TICK,
1396 data => WF_AUTOCANCEL, 2175 data => WF_AUTOCANCEL,
1397 cb => sub { 2176 cb => sub {
1398 unless ($FREEZE) {
1399 cf::server_tick; # one server iteration 2177 cf::server_tick; # one server iteration
1400 $RUNTIME += $TICK; 2178 $RUNTIME += $TICK;
1401 }
1402
1403 $NEXT_TICK += $TICK; 2179 $NEXT_TICK += $TICK;
1404 2180
1405 # 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
1406 $NEXT_TICK = Event::time if Event::time >= $NEXT_TICK + $TICK * 4; 2182 $NEXT_TICK = Event::time if Event::time >= $NEXT_TICK + $TICK * 4;
1407 2183
1410 }, 2186 },
1411); 2187);
1412 2188
1413IO::AIO::max_poll_time $TICK * 0.2; 2189IO::AIO::max_poll_time $TICK * 0.2;
1414 2190
2191Event->io (
1415Event->io (fd => IO::AIO::poll_fileno, 2192 fd => IO::AIO::poll_fileno,
1416 poll => 'r', 2193 poll => 'r',
1417 prio => 5, 2194 prio => 5,
1418 data => WF_AUTOCANCEL, 2195 data => WF_AUTOCANCEL,
1419 cb => \&IO::AIO::poll_cb); 2196 cb => \&IO::AIO::poll_cb,
2197);
1420 2198
1421# we must not ever block the main coroutine 2199Event->timer (
1422$Coro::idle = sub { 2200 data => WF_AUTOCANCEL,
1423 #Carp::cluck "FATAL: Coro::idle was called, major BUG\n";#d# 2201 after => 0,
1424 warn "FATAL: Coro::idle was called, major BUG\n"; 2202 interval => 10,
2203 cb => sub {
1425 (Coro::unblock_sub { 2204 (Coro::unblock_sub {
1426 Event::one_event; 2205 write_runtime
2206 or warn "ERROR: unable to write runtime file: $!";
1427 })->(); 2207 })->();
1428}; 2208 },
2209);
2210
2211END { cf::emergency_save }
1429 2212
14301 22131
1431 2214

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines