Factored the whole exception handling.

Implemented sticky exceptions that can be rethrown at will.

Used them to ensure that quitting the game, loading a new game, and
leaving to title screen, work correctly when WML is being executed.
This commit is contained in:
Guillaume Melquiond 2010-07-31 10:52:26 +00:00
parent 5bc2b5a10c
commit 0ee3b0a6db
9 changed files with 174 additions and 105 deletions

View file

@ -60,7 +60,7 @@ editor_map editor_map::from_string(const config& terrain_cfg, const std::string&
} catch (incorrect_map_format_exception& e) {
throw wrap_exc("format", e.msg_, "");
} catch (twml_exception& e) {
throw wrap_exc("wml", e.user_message, "");
throw wrap_exc("wml", e.message, "");
} catch (config::error& e) {
throw wrap_exc("config", e.message, "");
}

79
src/exceptions.hpp Normal file
View file

@ -0,0 +1,79 @@
/* $Id$ */
/*
Copyright (C) 2010 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef EXCEPTIONS_HPP_INCLUDED
#define EXCEPTIONS_HPP_INCLUDED
#include <exception>
#include <string>
namespace game {
/**
* Base class for all the errors encountered by the engine.
* It provides a field for storing custom messages related to the actual
* error.
*/
struct error : std::exception
{
std::string message;
error() : message() {}
error(const std::string &msg) : message(msg) {}
~error() throw() {}
const char *what() const throw()
{
return message.c_str();
}
};
/**
* Base class for all the exceptions for changing the control flow.
* Its message only carries a description of the exception.
* It also handles sticky exceptions that are automatically rethrown if
* lost; such exceptions cannot have any embedded payload, since it would
* still be lost.
*/
struct exception : std::exception
{
const char *message;
/**
* Rethrows the current sticky exception, if any.
*/
static void rethrow();
/**
* Marks an exception of name @a sticky as a rethrow candidate.
* @note The value should be set to NULL in order to discard the
* sticky exception once it has been handled.
*/
static const char *sticky;
exception(const char *msg, const char *stick = NULL) : message(msg)
{
sticky = stick;
}
~exception() throw() {}
const char *what() const throw()
{
return message;
}
};
}
#endif

View file

@ -132,6 +132,21 @@ static bool less_campaigns_rank(const config &a, const config &b) {
return a["rank"].to_int(1000) < b["rank"].to_int(1000);
}
char const *game::exception::sticky;
void game::exception::rethrow()
{
if (!sticky) return;
if (strcmp(sticky, "quit") == 0) throw CVideo::quit();
if (strcmp(sticky, "load game") == 0) throw game::load_game_exception();
if (strcmp(sticky, "end level") == 0) throw end_level_exception(QUIT);
throw game::exception("Unknown exception", "unknown");
}
std::string game::load_game_exception::game;
bool game::load_game_exception::show_replay;
bool game::load_game_exception::cancel_orders;
namespace {
struct jump_to_campaign_info
{
@ -165,7 +180,7 @@ public:
void reload_changed_game_config();
bool is_loading() const;
void clear_loaded_game() {loaded_game_.clear();};
void clear_loaded_game() { game::load_game_exception::game.clear(); }
bool load_game();
void set_tutorial();
@ -233,10 +248,6 @@ private:
game_state state_;
std::string loaded_game_;
bool loaded_game_show_replay_;
bool loaded_game_cancel_orders_;
std::string multiplayer_server_;
bool jump_to_multiplayer_;
jump_to_campaign_info jump_to_campaign_;
@ -273,9 +284,6 @@ game_controller::game_controller(int argc, char** argv) :
old_defines_map_(),
disp_(NULL),
state_(),
loaded_game_(),
loaded_game_show_replay_(false),
loaded_game_cancel_orders_(false),
multiplayer_server_(),
jump_to_multiplayer_(false),
jump_to_campaign_(false,-1,"","")
@ -353,10 +361,10 @@ game_controller::game_controller(int argc, char** argv) :
} else if(val == "--load" || val == "-l") {
if(arg_+1 != argc_) {
++arg_;
loaded_game_ = argv_[arg_];
game::load_game_exception::game = argv_[arg_];
}
} else if(val == "--with-replay") {
loaded_game_show_replay_ = true;
game::load_game_exception::show_replay = true;
} else if(val == "--nogui") {
no_gui_ = true;
@ -471,7 +479,7 @@ game_controller::game_controller(int argc, char** argv) :
if(arg_+1 != argc_) {
if (argv_[arg_ + 1][0] != '-') {
++arg_;
loaded_game_ = argv_[arg_];
game::load_game_exception::game = argv_[arg_];
}
}
#endif
@ -660,9 +668,6 @@ bool game_controller::play_test()
upload_log nolog(false);
play_game(disp(),state_,game_config(),nolog);
} catch(game::load_game_exception& e) {
loaded_game_ = e.game;
loaded_game_show_replay_ = e.show_replay;
loaded_game_cancel_orders_ = e.cancel_orders;
test_mode_ = false;
return true;
}
@ -892,13 +897,8 @@ bool game_controller::play_multiplayer_mode()
state_.snapshot = level;
play_game(disp(), state_, game_config(), log);
} catch(game::error& e) {
std::cerr << "caught error: '" << e.message << "'\n";
} catch(game::load_game_exception& e) {
//the user's trying to load a game, so go into the normal title screen loop and load one
loaded_game_ = e.game;
loaded_game_show_replay_ = e.show_replay;
loaded_game_cancel_orders_ = e.cancel_orders;
return true;
} catch(twml_exception& e) {
e.show(disp());
@ -914,7 +914,7 @@ bool game_controller::play_multiplayer_mode()
bool game_controller::is_loading() const
{
return loaded_game_.empty() == false;
return !game::load_game_exception::game.empty();
}
bool game_controller::load_game()
@ -922,7 +922,7 @@ bool game_controller::load_game()
savegame::loadgame load(disp(), game_config(), state_);
try {
load.load_game(loaded_game_, loaded_game_show_replay_, loaded_game_cancel_orders_);
load.load_game(game::load_game_exception::game, game::load_game_exception::show_replay, game::load_game_exception::cancel_orders);
cache_.clear_defines();
game_config::scoped_preproc_define dificulty_def(state_.classification().difficulty);
@ -950,7 +950,7 @@ bool game_controller::load_game()
load.set_gamestate();
} catch(load_game_cancelled_exception&) {
loaded_game_ = "";
clear_loaded_game();
return false;
} catch(config::error& e) {
if(e.message.empty()) {
@ -960,6 +960,9 @@ bool game_controller::load_game()
gui2::show_error_message(disp().video(), _("The file you have tried to load is corrupt: '") + e.message + '\'');
}
return false;
} catch(twml_exception& e) {
e.show(disp());
return false;
} catch(game::error& e) {
if(e.message.empty()) {
gui2::show_error_message(disp().video(), _("The file you have tried to load is corrupt"));
@ -971,9 +974,6 @@ bool game_controller::load_game()
} catch(io_exception&) {
gui2::show_error_message(disp().video(), _("File I/O Error while reading the game"));
return false;
} catch(twml_exception& e) {
e.show(disp());
return false;
}
recorder = replay(state_.replay_data);
recorder.start_replay();
@ -1225,12 +1225,12 @@ bool game_controller::goto_editor()
{
if(jump_to_editor_){
jump_to_editor_ = false;
if (start_editor(normalize_path(loaded_game_)) ==
if (start_editor(normalize_path(game::load_game_exception::game)) ==
editor::EXIT_QUIT_TO_DESKTOP)
{
return false;
}
loaded_game_ = "";
clear_loaded_game();
}
return true;
}
@ -1410,9 +1410,6 @@ bool game_controller::play_multiplayer()
gui2::show_error_message(disp().video(), std::string(_("The game map could not be loaded: ")) + e.msg_);
} catch(game::load_game_exception& e) {
//this will make it so next time through the title screen loop, this game is loaded
loaded_game_ = e.game;
loaded_game_show_replay_ = e.show_replay;
loaded_game_cancel_orders_ = e.cancel_orders;
} catch(twml_exception& e) {
e.show(disp());
}
@ -1632,16 +1629,9 @@ void game_controller::launch_game(RELOAD_GAME_DATA reload)
about::show_about(disp(),state_.classification().campaign);
}
loaded_game_ = "";
loaded_game_show_replay_ = false;
loaded_game_cancel_orders_ = false;
} catch(game::load_game_exception& e) {
clear_loaded_game();
} catch (game::load_game_exception &) {
//this will make it so next time through the title screen loop, this game is loaded
loaded_game_ = e.game;
loaded_game_show_replay_ = e.show_replay;
loaded_game_cancel_orders_ = e.cancel_orders;
} catch(twml_exception& e) {
e.show(disp());
}
@ -1656,16 +1646,9 @@ void game_controller::play_replay()
try {
::play_replay(disp(),state_,game_config(),video_);
loaded_game_ = "";
loaded_game_show_replay_ = false;
loaded_game_cancel_orders_ = false;
} catch(game::load_game_exception& e) {
clear_loaded_game();
} catch (game::load_game_exception &) {
//this will make it so next time through the title screen loop, this game is loaded
loaded_game_ = e.game;
loaded_game_show_replay_ = e.show_replay;
loaded_game_cancel_orders_ = e.cancel_orders;
} catch(twml_exception& e) {
e.show(disp());
}
@ -2152,7 +2135,9 @@ static int do_gameloop(int argc, char** argv)
LOG_CONFIG << "time elapsed: "<< (SDL_GetTicks() - start_ticks) << " ms\n";
for(;;){
for (;;)
{
game::exception::sticky = NULL;
// reset the TC, since a game can modify it, and it may be used
// by images in add-ons or campaigns dialogs
@ -2361,14 +2346,14 @@ int main(int argc, char** argv)
//just means the game should quit
} catch(end_level_exception&) {
std::cerr << "caught end_level_exception (quitting)\n";
} catch(twml_exception& e) {
std::cerr << "WML exception:\nUser message: "
<< e.message << "\nDev message: " << e.dev_message << '\n';
} catch(game::error &) {
// A message has already been displayed.
} catch(std::bad_alloc&) {
std::cerr << "Ran out of memory. Aborted.\n";
return ENOMEM;
} catch(twml_exception& e) {
std::cerr << "WML exception:\nUser message: "
<< e.user_message << "\nDev message: " << e.dev_message << '\n';
} catch(game_logic::formula_error& e) {
std::cerr << "Formula error found in " << e.filename << ":" << e.line
<< "\nIn formula " << e.formula

View file

@ -22,7 +22,7 @@
#ifndef GAME_END_EXCEPTIONS_HPP_INCLUDED
#define GAME_END_EXCEPTIONS_HPP_INCLUDED
#include <string>
#include "exceptions.hpp"
enum LEVEL_RESULT {
NONE,
@ -36,18 +36,21 @@ enum LEVEL_RESULT {
/**
* Exception used to signal the end of a player turn.
*/
struct end_turn_exception
struct end_turn_exception : game::exception
{
end_turn_exception(unsigned r = 0): redo(r) {}
end_turn_exception(unsigned r = 0)
: game::exception("End turn"), redo(r) {}
unsigned redo;
};
/**
* Exception used to signal the end of a scenario.
*/
struct end_level_exception
struct end_level_exception : game::exception
{
end_level_exception(LEVEL_RESULT res): result(res) {}
end_level_exception(LEVEL_RESULT res)
: game::exception("End level", res == QUIT ? "end level" : NULL)
, result(res) {}
LEVEL_RESULT result;
};

View file

@ -15,57 +15,59 @@
#ifndef GAME_ERRORS_HPP_INCLUDED
#define GAME_ERRORS_HPP_INCLUDED
#include <exception>
#include <string>
#include "exceptions.hpp"
namespace game {
struct error : std::exception
{
std::string message;
error() :
message()
{}
error(const std::string& msg) : message(msg)
{}
~error() throw() {}
const char *what() const throw()
{
return message.c_str();
}
};
struct mp_server_error : public error {
mp_server_error(const std::string& msg) : error("MP server error: " + msg) {}
};
//an exception object used when loading a game fails.
/**
* Error used when game loading fails.
*/
struct load_game_failed : public error {
load_game_failed() {}
load_game_failed(const std::string& msg) : error("load_game_failed: " + msg) {}
};
//an exception object used when saving a game fails.
/**
* Error used when game saving fails.
*/
struct save_game_failed : public error {
save_game_failed() {}
save_game_failed(const std::string& msg) : error("save_game_failed: " + msg) {}
};
//an exception object used for any general game error.
//e.g. data files are corrupt.
/**
* Error used for any general game error, e.g. data files are corrupt.
*/
struct game_error : public error {
game_error(const std::string& msg) : error("game_error: " + msg) {}
};
//an exception object used to signal that the user has decided to abort
//a game, and load another game instead
struct load_game_exception {
load_game_exception(const std::string& game, bool show_replay, bool cancel_orders)
: game(game), show_replay(show_replay), cancel_orders(cancel_orders) {}
std::string game;
bool show_replay;
bool cancel_orders;
/**
* Exception used to signal that the user has decided to abort a game,
* and to load another game instead.
*/
struct load_game_exception : exception
{
load_game_exception()
: exception("Abort the current game and load a new one", "load game")
{
}
load_game_exception(const std::string &game_, bool show_replay_, bool cancel_orders_)
: exception("Abort the current game and load a new one", "load game")
{
game = game_;
show_replay = show_replay_;
cancel_orders = cancel_orders_;
}
static std::string game;
static bool show_replay;
static bool cancel_orders;
};
}

View file

@ -413,6 +413,8 @@ bool luaW_pcall(lua_State *L
// Call the function.
int res = lua_pcall(L, nArgs, nRets, -2 - nArgs);
game::exception::rethrow();
if (res)
{
char const *m = lua_tostring(L, -1);

View file

@ -15,6 +15,7 @@
#define VIDEO_HPP_INCLUDED
#include "events.hpp"
#include "exceptions.hpp"
#include "SDL.h"
#include <boost/utility.hpp>
@ -66,9 +67,15 @@ class CVideo : private boost::noncopyable {
bool isFullScreen() const;
struct error {};
struct error : game::error
{
error() : game::error("Video initialization failed") {}
};
struct quit {};
struct quit : game::exception
{
quit() : game::exception("Exit game", "quit") {}
};
//functions to allow changing video modes when 16BPP is emulated
void setBpp( int bpp );

View file

@ -45,7 +45,7 @@ void twml_exception::show(display &disp)
// The extra spaces between the \n are needed, otherwise the dialog doesn't show
// an empty line.
sstr << _("An error due to possibly invalid WML occurred\nThe error message is :")
<< "\n" << user_message << "\n \n"
<< "\n" << message << "\n \n"
<< _("When reporting the bug please include the following error message :")
<< "\n" << dev_message;

View file

@ -21,8 +21,7 @@
#ifndef WML_EXCEPTION_HPP_INCLUDED
#define WML_EXCEPTION_HPP_INCLUDED
#include <exception>
#include <string>
#include "exceptions.hpp"
class display;
@ -59,17 +58,12 @@ void wml_exception(const char* cond, const char* file,
int line, const char *function, const std::string &message);
/** Helper class, don't construct this directly. */
struct twml_exception: std::exception
struct twml_exception: game::error
{
twml_exception(const std::string &user_msg, const std::string &dev_msg)
: user_message(user_msg), dev_message(dev_msg) {}
~twml_exception() throw() {}
: game::error(user_msg), dev_message(dev_msg) {}
/**
* The message for the user explaining what went wrong. This message can
* be translated so the user gets a explanation in his/her native tongue.
*/
std::string user_message;
~twml_exception() throw() {}
/**
* The message for developers telling which problem was triggered, this
@ -83,9 +77,6 @@ struct twml_exception: std::exception
* @param disp The display object to show the message on.
*/
void show(display &disp);
const char *what() const throw()
{ return user_message.c_str(); }
};
/**