GLK: ADRIFT: Refactoring load serializer to not use longjmp

This commit is contained in:
Paul Gilbert 2019-09-19 19:54:09 -07:00
parent 994afedd33
commit 41c7d31fb6
7 changed files with 123 additions and 139 deletions

View File

@ -124,7 +124,7 @@ void SaveSerializer::save() {
if (task > 0)
writeBool(gs_task_done(_game, task - 1));
else
writeBool(FALSE);
writeBool(false);
}
/* Save NPCs information. */
@ -177,8 +177,8 @@ void SaveSerializer::save() {
* references.
*/
flush(TRUE);
_callback = NULL;
_opaque = NULL;
_callback = nullptr;
_opaque = nullptr;
}
void SaveSerializer::flush(sc_bool is_final) {
@ -225,20 +225,13 @@ void SaveSerializer::writeUint(sc_uint value) {
}
void SaveSerializer::writeBool(sc_bool boolean) {
// Write a 1 for TRUE, 0 for FALSE
// Write a 1 for TRUE, 0 for false
writeString(boolean ? "1" : "0");
}
/*--------------------------------------------------------------------------*/
/* TAS input file line counter. */
static sc_tafref_t ser_tas = NULL;
static sc_int ser_tasline = 0;
/* Restore error jump buffer. */
static jmp_buf ser_tas_error;
static sc_var_setref_t new_vars; /* For setjmp safety */
static sc_gameref_t new_game; /* For setjmp safety */
#define CHECK if (context._break) goto ser_tas_error
bool LoadSerializer::load() {
const sc_filterref_t filter = gs_get_filter(_game);
@ -246,144 +239,128 @@ bool LoadSerializer::load() {
sc_vartype_t vt_key[3];
sc_int index_, var_count;
const sc_char *gamename;
sc_var_setref_t new_vars = nullptr;
sc_gameref_t new_game = nullptr;
Context context;
/* Create a TAF (TAS) reference from callbacks, for reader functions. */
// Create a TAF (TAS) reference from callbacks, for reader functions
ser_tas = taf_create_tas(_callback, _opaque);
if (!ser_tas)
return FALSE;
return false;
/* Reset line counter for error messages. */
// Reset line counter for error messages.
ser_tasline = 1;
new_game = NULL;
new_vars = NULL;
/* Set up error handling jump buffer, and handle errors. */
if (setjmp(ser_tas_error) != 0) {
/* Destroy any temporary _game and variables. */
if (new_game)
gs_destroy(new_game);
if (new_vars)
var_destroy(new_vars);
/* Destroy the TAF (TAS) file and return fail status. */
taf_destroy(ser_tas);
ser_tas = NULL;
return FALSE;
}
/*
* Read the _game name, and compare with the one in the _game. Fail if
* they don't match exactly. A tighter check than this would perhaps be
* preferable, say, something based on the TAF file header, but this isn't
* in the save file format.
*/
// Read the _game name, and compare with the one in the _game. Fail if they don't match exactly.
// A tighter check than this would perhaps be preferable, say, something based on the TAF file
// header, but this isn't in the save file format.
vt_key[0].string = "Globals";
vt_key[1].string = "GameName";
gamename = prop_get_string(bundle, "S<-ss", vt_key);
if (strcmp(readString(), gamename) != 0)
longjmp(ser_tas_error, 1);
if (strcmp(readString(context), gamename) != 0 || context._break)
goto ser_tas_error;
/* Read and verify the counts in the saved _game. */
if (readInt() != gs_room_count(_game)
|| readInt() != gs_object_count(_game)
|| readInt() != gs_task_count(_game)
|| readInt() != gs_event_count(_game)
|| readInt() != gs_npc_count(_game))
longjmp(ser_tas_error, 1);
// Read and verify the counts in the saved _game.
if ((readInt(context) != gs_room_count(_game) || context._break)
|| (readInt(context) != gs_object_count(_game) || context._break)
|| (readInt(context) != gs_task_count(_game) || context._break)
|| (readInt(context) != gs_event_count(_game) || context._break)
|| (readInt(context) != gs_npc_count(_game) || context._break))
goto ser_tas_error;
/* Create a variables set and _game to restore into. */
// Create a variables set and _game to restore into.
new_vars = var_create(bundle);
new_game = gs_create(new_vars, bundle, filter);
var_register_game(new_vars, new_game);
/* All set to load TAF (TAS) data into the new _game. */
// All set to load TAF (TAS) data into the new _game.
/* Restore the score and player information. */
new_game->score = readInt();
gs_set_playerroom(new_game, readInt() - 1);
gs_set_playerparent(new_game, readInt());
gs_set_playerposition(new_game, readInt());
// Restore the score and player information.
new_game->score = readInt(context); CHECK;
gs_set_playerroom(new_game, readInt(context) - 1); CHECK;
gs_set_playerparent(new_game, readInt(context)); CHECK;
gs_set_playerposition(new_game, readInt(context)); CHECK;
/* Skip player gender. */
(void)readInt();
// Skip player gender.
(void)readInt(context); CHECK;
/* Skip encumbrance details, not currently maintained by the _game. */
(void)readInt();
(void)readInt();
(void)readInt();
(void)readInt();
// Skip encumbrance details, not currently maintained by the _game.
(void)readInt(context); CHECK;
(void)readInt(context); CHECK;
(void)readInt(context); CHECK;
(void)readInt(context); CHECK;
/* Restore rooms information. */
for (index_ = 0; index_ < gs_room_count(new_game); index_++)
gs_set_room_seen(new_game, index_, readBool());
// Restore rooms information.
for (index_ = 0; index_ < gs_room_count(new_game); index_++) {
gs_set_room_seen(new_game, index_, readBool(context)); CHECK;
}
/* Restore objects information. */
// Restore objects information.
for (index_ = 0; index_ < gs_object_count(new_game); index_++) {
sc_int openable, currentstate;
/* Bypass mutators for position and parent. Fix later? */
new_game->objects[index_].position = readInt();
gs_set_object_seen(new_game, index_, readBool());
new_game->objects[index_].parent = readInt();
// Bypass mutators for position and parent. Fix later?
new_game->objects[index_].position = readInt(context); CHECK;
gs_set_object_seen(new_game, index_, readBool(context)); CHECK;
new_game->objects[index_].parent = readInt(context); CHECK;
vt_key[0].string = "Objects";
vt_key[1].integer = index_;
vt_key[2].string = "Openable";
openable = prop_get_integer(bundle, "I<-sis", vt_key);
gs_set_object_openness(new_game, index_,
openable != 0 ? readInt() : 0);
gs_set_object_openness(new_game, index_, openable != 0 ? readInt(context) : 0); CHECK;
vt_key[2].string = "CurrentState";
currentstate = prop_get_integer(bundle, "I<-sis", vt_key);
gs_set_object_state(new_game, index_,
currentstate != 0 ? readInt() : 0);
currentstate != 0 ? readInt(context) : 0); CHECK;
gs_set_object_unmoved(new_game, index_, readBool());
gs_set_object_unmoved(new_game, index_, readBool(context)); CHECK;
}
/* Restore tasks information. */
// Restore tasks information.
for (index_ = 0; index_ < gs_task_count(new_game); index_++) {
gs_set_task_done(new_game, index_, readBool());
gs_set_task_scored(new_game, index_, readBool());
gs_set_task_done(new_game, index_, readBool(context)); CHECK;
gs_set_task_scored(new_game, index_, readBool(context)); CHECK;
}
/* Restore events information. */
// Restore events information.
for (index_ = 0; index_ < gs_event_count(new_game); index_++) {
sc_int startertype, task;
/* Restore first event details. */
gs_set_event_time(new_game, index_, readInt());
task = readInt();
gs_set_event_state(new_game, index_, readInt() + 1);
// Restore first event details.
gs_set_event_time(new_game, index_, readInt(context)); CHECK;
task = readInt(context); CHECK;
gs_set_event_state(new_game, index_, readInt(context) + 1); CHECK;
/* Verify and restore the starter task, if any. */
// Verify and restore the starter task, if any.
if (task > 0) {
vt_key[0].string = "Events";
vt_key[1].integer = index_;
vt_key[2].string = "StarterType";
startertype = prop_get_integer(bundle, "I<-sis", vt_key);
if (startertype != 3)
longjmp(ser_tas_error, 1);
goto ser_tas_error;
/* Restore task state. */
gs_set_task_done(new_game, task - 1, readBool());
// Restore task state.
gs_set_task_done(new_game, task - 1, readBool(context)); CHECK;
} else {
(void)readBool(context); CHECK;
}
else
(void)readBool();
}
/* Restore NPCs information. */
// Restore NPCs information.
for (index_ = 0; index_ < gs_npc_count(new_game); index_++) {
sc_int walk;
gs_set_npc_location(new_game, index_, readInt());
gs_set_npc_seen(new_game, index_, readBool());
gs_set_npc_location(new_game, index_, readInt(context)); CHECK;
gs_set_npc_seen(new_game, index_, readBool(context)); CHECK;
for (walk = 0; walk < gs_npc_walkstep_count(new_game, index_); walk++)
gs_set_npc_walkstep(new_game, index_, walk, readInt());
gs_set_npc_walkstep(new_game, index_, walk, readInt(context)); CHECK;
}
/* Restore each variable. */
// Restore each variable.
vt_key[0].string = "Variables";
var_count = prop_get_child_count(bundle, "I<-s", vt_key);
@ -400,11 +377,11 @@ bool LoadSerializer::load() {
switch (var_type) {
case TAFVAR_NUMERIC:
var_put_integer(new_vars, name, readInt());
var_put_integer(new_vars, name, readInt(context)); CHECK;
break;
case TAFVAR_STRING:
var_put_string(new_vars, name, readString());
var_put_string(new_vars, name, readString(context)); CHECK;
break;
default:
@ -412,102 +389,108 @@ bool LoadSerializer::load() {
}
}
/* Restore timing information. */
var_set_elapsed_seconds(new_vars, readUint());
// Restore timing information.
var_set_elapsed_seconds(new_vars, readUint(context)); CHECK;
/* Restore turns count. */
new_game->turns = (sc_int)readUint();
// Restore turns count.
new_game->turns = (sc_int)readUint(context); CHECK;
/*
* Resources tweak -- set requested to match those in the current _game
* so that they remain unchanged by the gs_copy() of new_game onto
* _game. This way, both the requested and the active resources in the
* _game are unchanged by restore.
/* Resources tweak -- set requested to match those in the current _game so that they remain
* unchanged by the gs_copy() of new_game onto game. This way, both the requested and the
* active resources in the game are unchanged by restore.
*/
new_game->requested_sound = _game->requested_sound;
new_game->requested_graphic = _game->requested_graphic;
/*
* Quitter tweak -- set the quit jump buffer in the new _game to be the
/* Quitter tweak -- set the quit jump buffer in the new _game to be the
* same as the current one, so that it remains unchanged by gs_copy(). The
* one in the new _game is still the unset one from gs_create().
*/
memcpy(&new_game->quitter, &_game->quitter, sizeof(_game->quitter));
/*
* If we got this far, we successfully restored the _game from the file.
/* If we got this far, we successfully restored the _game from the file.
* As our final act, copy the new _game onto the old one.
*/
new_game->temporary = _game->temporary;
new_game->undo = _game->undo;
gs_copy(_game, new_game);
/* Done with the temporary _game and variables. */
// Done with the temporary _game and variables.
gs_destroy(new_game);
var_destroy(new_vars);
/* Done with TAF (TAS) file; destroy it and return successfully. */
// Done with TAF (TAS) file; destroy it and return successfully
taf_destroy(ser_tas);
ser_tas = NULL;
return TRUE;
return true;
ser_tas_error:
// Destroy any temporary _game and variables
if (new_game)
gs_destroy(new_game);
if (new_vars)
var_destroy(new_vars);
// Destroy the TAF (TAS) file and return fail status
taf_destroy(ser_tas);
return false;
}
const sc_char *LoadSerializer::readString() {
const sc_char *LoadSerializer::readString(CONTEXT) {
const sc_char *string;
/* Get the next line, and complain if absent. */
string = taf_next_line(ser_tas);
if (!string) {
sc_error("readString: out of TAS data at line %ld\n", ser_tasline);
longjmp(ser_tas_error, 1);
LONG_JUMP0
}
ser_tasline++;
return string;
}
sc_int LoadSerializer::readInt() {
sc_int LoadSerializer::readInt(CONTEXT) {
const sc_char *string;
sc_int value;
// Get line, and scan for a single integer; return it
string = readString();
R0FUNC0(readString, string)
if (sscanf(string, "%ld", &value) != 1) {
sc_error("readInt: invalid integer at line %ld\n", ser_tasline - 1);
longjmp(ser_tas_error, 1);
LONG_JUMP0
}
return value;
}
sc_uint LoadSerializer::readUint() {
sc_uint LoadSerializer::readUint(CONTEXT) {
const sc_char *string;
sc_uint value;
// Get line, and scan for a single integer; return it
string = readString();
R0FUNC0(readString, string)
if (sscanf(string, "%lu", &value) != 1) {
sc_error("readUint: invalid integer at line %ld\n", ser_tasline - 1);
longjmp(ser_tas_error, 1);
LONG_JUMP0
}
return value;
}
sc_bool LoadSerializer::readBool(void) {
sc_bool LoadSerializer::readBool(CONTEXT) {
const sc_char *string;
sc_uint value;
// Get line, and scan for a single integer; check it's a valid-looking flag, and return it.
string = readString();
R0FUNC0(readString, string)
string = readString(context);
if (sscanf(string, "%lu", &value) != 1) {
sc_error("readBool:"
" invalid boolean at line %ld\n", ser_tasline - 1);
longjmp(ser_tas_error, 1);
sc_error("readBool: invalid boolean at line %ld\n", ser_tasline - 1);
LONG_JUMP0
}
if (value != 0 && value != 1) {
sc_error("readBool:"
" warning: suspect boolean at line %ld\n", ser_tasline - 1);
sc_error("readBool: warning: suspect boolean at line %ld\n", ser_tasline - 1);
LONG_JUMP0
}
return value != 0;

View File

@ -26,6 +26,7 @@
#include "common/memstream.h"
#include "common/str.h"
#include "glk/adrift/scprotos.h"
#include "glk/jumps.h"
namespace Glk {
namespace Adrift {
@ -102,32 +103,34 @@ private:
sc_gameref_t _game;
sc_read_callbackref_t _callback;
void *_opaque;
sc_tafref_t ser_tas;
sc_int ser_tasline;
private:
/**
* Reads a string
*/
const sc_char *readString();
const sc_char *readString(CONTEXT);
/**
* Read a signed integer
*/
sc_int readInt();
sc_int readInt(CONTEXT);
/**
* Read an unsigned integer
*/
sc_uint readUint();
sc_uint readUint(CONTEXT);
/**
* Read a boolean
*/
sc_bool readBool();
sc_bool readBool(CONTEXT);
public:
/**
* Constructor
*/
LoadSerializer(sc_gameref_t game, sc_read_callbackref_t callback, void *opaque) :
_game(game), _callback(callback), _opaque(opaque) {
_game(game), _callback(callback), _opaque(opaque), ser_tas(nullptr), ser_tasline(0) {
assert(callback);
}

View File

@ -27,7 +27,7 @@
*/
#include "glk/alan2/types.h"
#include "glk/alan2/jumps.h"
#include "glk/jumps.h"
namespace Glk {
namespace Alan2 {

View File

@ -23,7 +23,7 @@
#ifndef GLK_ALAN2_INTER
#define GLK_ALAN2_INTER
#include "glk/alan2/jumps.h"
#include "glk/jumps.h"
namespace Glk {
namespace Alan2 {

View File

@ -27,7 +27,7 @@
#include "common/file.h"
#include "glk/alan2/types.h"
#include "glk/alan2/jumps.h"
#include "glk/jumps.h"
namespace Glk {
namespace Alan2 {

View File

@ -25,7 +25,7 @@
/* Parse data for ALAN interpreter module. */
#include "engines/glk/alan2/jumps.h"
#include "engines/glk/jumps.h"
namespace Glk {
namespace Alan2 {

View File

@ -20,15 +20,14 @@
*
*/
#ifndef GLK_ALAN2_JUMPS
#define GLK_ALAN2_JUMPS
#ifndef GLK_JUMPS
#define GLK_JUMPS
/* This provides a simplified version of the ScummVM coroutines to allow for automated
* breakouts to the main game loop from subroutinese rather than using unportable setjmps
*/
namespace Glk {
namespace Alan2 {
/**
* Context used for flagging when a break to the outer game loop
@ -64,7 +63,6 @@ struct Context {
#define LONG_JUMP { context._break = true; return; }
#define LONG_JUMP0 { context._break = true; return 0; }
} // End of namespace Alan2
} // End of namespace Glk
#endif