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:
parent
5bc2b5a10c
commit
0ee3b0a6db
9 changed files with 174 additions and 105 deletions
|
@ -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
79
src/exceptions.hpp
Normal 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
|
89
src/game.cpp
89
src/game.cpp
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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(); }
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue