From b60809e623d89d3667009330cf50e9b96e0aeb0d Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Sun, 1 Mar 2015 03:10:18 +0100 Subject: [PATCH] don't throw exceptions on :droid, :idle This not only simplifies the playsingle/mp_controller code, it also fixes a bug that actions were aborted when issueing a :droid during an ai turn causing corrupt replays or worse. We still use restart_turn_exception to escape insode the ai code but not during the action. Instead we now check player_type_changed_ after every action, just like we do for ai_end_turn_exception and catch it inside ai::manager::play_turn so that the playsingle/mp_controller code doesn't have to handle this exception anymore. --- src/ai/actions.cpp | 7 +++ src/ai/manager.cpp | 2 + src/game_end_exceptions.hpp | 31 +---------- src/menu_events.cpp | 13 ++--- src/mouse_events.cpp | 16 ++---- src/playmp_controller.cpp | 19 +++---- src/playsingle_controller.cpp | 95 ++++++++++++--------------------- src/playsingle_controller.hpp | 3 ++ src/replay_controller.cpp | 21 ++------ src/whiteboard/move.cpp | 1 + src/whiteboard/side_actions.cpp | 1 + 11 files changed, 71 insertions(+), 138 deletions(-) diff --git a/src/ai/actions.cpp b/src/ai/actions.cpp index 0fe8b597b670..bf4045bbc3b8 100644 --- a/src/ai/actions.cpp +++ b/src/ai/actions.cpp @@ -44,6 +44,7 @@ #include "../mouse_handler_base.hpp" #include "../pathfind/teleport.hpp" #include "../play_controller.hpp" +#include "../playsingle_controller.hpp" #include "../recall_list_manager.hpp" #include "../replay_helper.hpp" #include "../resources.hpp" @@ -104,6 +105,12 @@ void action_result::execute() if(resources::controller->is_end_turn()) { throw ai_end_turn_exception(); } + + if(playsingle_controller* psc = dynamic_cast(resources::controller)) { + if(psc->get_player_type_changed()) { + throw restart_turn_exception(); + } + } is_execution_ = false; } diff --git a/src/ai/manager.cpp b/src/ai/manager.cpp index 1010c69c37e5..77336605909f 100644 --- a/src/ai/manager.cpp +++ b/src/ai/manager.cpp @@ -798,6 +798,8 @@ void manager::play_turn( side_number side ){ } catch (const ai_end_turn_exception&) { } + catch(const restart_turn_exception&) { + } const int turn_end_time= SDL_GetTicks(); DBG_AI_MANAGER << "side " << side << ": number of user interactions: "< #include #include -#include MAKE_ENUM(LEVEL_RESULT, (VICTORY, "victory") @@ -59,13 +58,6 @@ class ai_end_turn_exception IMPLEMENT_LUA_JAILBREAK_EXCEPTION(ai_end_turn_exception) }; - -/** - * Struct used to transmit info caught from an end_turn_exception. - */ -struct restart_turn_struct { -}; - /** * Exception used to signal the end of a player turn. */ @@ -80,10 +72,6 @@ class restart_turn_exception { } const char * what() const throw() { return "restart_turn_exception"; } - restart_turn_struct to_struct() - { - return restart_turn_struct(); - } private: @@ -129,7 +117,7 @@ class end_level_exception /** * The two end_*_exceptions are caught and transformed to this signaling object */ -typedef boost::optional > possible_end_play_signal; +typedef boost::optional possible_end_play_signal; #define HANDLE_END_PLAY_SIGNAL( X )\ do\ @@ -138,8 +126,6 @@ do\ X;\ } catch (end_level_exception & e) {\ return possible_end_play_signal(e.to_struct());\ - } catch (restart_turn_exception & e) {\ - return possible_end_play_signal(e.to_struct());\ }\ }\ while(0) @@ -171,21 +157,6 @@ do\ while(0) -enum END_PLAY_SIGNAL_TYPE {END_TURN, END_LEVEL}; - -class get_signal_type : public boost::static_visitor { -public: - END_PLAY_SIGNAL_TYPE operator()(restart_turn_struct &) const - { - return END_TURN; - } - - END_PLAY_SIGNAL_TYPE operator()(end_level_struct& ) const - { - return END_LEVEL; - } -}; - /** * The non-persistent part of end_level_data */ diff --git a/src/menu_events.cpp b/src/menu_events.cpp index a8cb851f32fc..39c4890c9c9e 100644 --- a/src/menu_events.cpp +++ b/src/menu_events.cpp @@ -61,6 +61,7 @@ #include "menu_events.hpp" #include "mouse_events.hpp" #include "play_controller.hpp" +#include "playsingle_controller.hpp" #include "preferences_display.hpp" #include "replay.hpp" #include "replay_helper.hpp" @@ -2711,9 +2712,9 @@ void console_handler::do_droid() { } menu_handler_.teams()[side - 1].toggle_droid(); if(team_num_ == side) { - //if it is our turn at the moment, we have to indicate to the - //play_controller, that we are no longer in control - throw restart_turn_exception(); + if(playsingle_controller* psc = dynamic_cast(&menu_handler_.pc_)) { + psc->set_player_type_changed(); + } } } else if (menu_handler_.teams()[side - 1].is_local_ai()) { // menu_handler_.teams()[side - 1].make_human(); @@ -2756,9 +2757,9 @@ void console_handler::do_idle() { //toggle the proxy controller between idle / non idle menu_handler_.teams()[side - 1].toggle_idle(); if(team_num_ == side) { - //if it is our turn at the moment, we have to indicate to the - //play_controller, that we are no longer in control - throw restart_turn_exception(); + if(playsingle_controller* psc = dynamic_cast(&menu_handler_.pc_)) { + psc->set_player_type_changed(); + } } } menu_handler_.textbox_info_.close(*menu_handler_.gui_); diff --git a/src/mouse_events.cpp b/src/mouse_events.cpp index 28e31be4e45f..9878a3e7cdf5 100644 --- a/src/mouse_events.cpp +++ b/src/mouse_events.cpp @@ -67,7 +67,6 @@ #include "SDL_mouse.h" // for SDL_GetMouseState #include "SDL_video.h" // for SDL_Color -class restart_turn_exception; namespace gui { class slider; } static lg::log_domain log_engine("engine"); @@ -889,18 +888,9 @@ size_t mouse_handler::move_unit_along_route(const std::vector & st } } - size_t moves = 0; - try { - - LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back() << "\n"; - moves = actions::move_unit_and_record(steps, &pc_.get_undo_stack(), - false, true, &interrupted); - } catch(restart_turn_exception&) { - cursor::set(cursor::NORMAL); - gui().invalidate_game_status(); - throw; - } - + LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back() << "\n"; + size_t moves = actions::move_unit_and_record(steps, &pc_.get_undo_stack(), false, true, &interrupted); + cursor::set(cursor::NORMAL); gui().invalidate_game_status(); diff --git a/src/playmp_controller.cpp b/src/playmp_controller.cpp index 9bf75d804aac..fd53c1abe03f 100644 --- a/src/playmp_controller.cpp +++ b/src/playmp_controller.cpp @@ -128,7 +128,7 @@ possible_end_play_signal playmp_controller::play_human_turn() if (!linger_ || is_host()) { end_turn_enable(true); } - while(!end_turn_) { + while(!end_turn_ && !player_type_changed_) { try { config cfg; @@ -137,6 +137,7 @@ possible_end_play_signal playmp_controller::play_human_turn() HANDLE_END_PLAY_SIGNAL( res = turn_data_.process_network_data(cfg) ); if (res == turn_info::PROCESS_RESTART_TURN) { + player_type_changed_ = true; // Clean undo stack if turn has to be restarted (losing control) if ( undo_stack_->can_undo() ) { @@ -155,7 +156,7 @@ possible_end_play_signal playmp_controller::play_human_turn() while( undo_stack_->can_undo() ) undo_stack_->undo(); - return possible_end_play_signal(restart_turn_struct()); + return boost::none; } else if(res == turn_info::PROCESS_END_LINGER) { @@ -199,7 +200,7 @@ possible_end_play_signal playmp_controller::play_idle_loop() remove_blindfold(); - while (!end_turn_) + while (!end_turn_ && !player_type_changed_) { try { @@ -210,7 +211,8 @@ possible_end_play_signal playmp_controller::play_idle_loop() if (res == turn_info::PROCESS_RESTART_TURN) { - return possible_end_play_signal(restart_turn_struct()); + player_type_changed_ = true; + return boost::none; } } @@ -268,7 +270,7 @@ void playmp_controller::linger() gamestate_.board_.set_all_units_user_end_turn(); set_end_scenario_button(); - + assert(is_regular_game_end()); if ( get_end_level_data_const().transient.reveal_map ) { // Change the view of all players and observers // to see the whole map regardless of shroud and fog. @@ -295,12 +297,6 @@ void playmp_controller::linger() LOG_NG << "caught end-level-exception" << std::endl; reset_end_scenario_button(); throw; - } catch (restart_turn_exception&) { - // thrown if the host leaves the game (sends [leave_game]), we need - // to stay in this loop to stay in linger mode, otherwise the game - // gets aborted - LOG_NG << "caught end-turn-exception" << std::endl; - quit = false; } catch (network::error&) { LOG_NG << "caught network-error-exception" << std::endl; quit = false; @@ -478,6 +474,7 @@ void playmp_controller::do_idle_notification() void playmp_controller::maybe_linger() { // mouse_handler expects at least one team for linger mode to work. + assert(is_regular_game_end()); if (!get_end_level_data_const().transient.linger_mode || gamestate_.board_.teams().empty()) { if(!is_host()) { // If we continue without lingering we need to diff --git a/src/playsingle_controller.cpp b/src/playsingle_controller.cpp index 55e25a7b61ac..8a9722dd57e3 100644 --- a/src/playsingle_controller.cpp +++ b/src/playsingle_controller.cpp @@ -56,7 +56,6 @@ #include "hotkey/hotkey_item.hpp" #include -#include static lg::log_domain log_aitesting("aitesting"); #define LOG_AIT LOG_STREAM(info, log_aitesting) @@ -213,9 +212,6 @@ void playsingle_controller::play_scenario_init() { this->reset_end_level_data(); } return; - } catch (restart_turn_exception &) { - assert(false && "caugh end_turn exception in a bad place... terminating."); - std::terminate(); } replaying_ = (recorder.at_end() == false); @@ -247,9 +243,6 @@ void playsingle_controller::play_scenario_init() { this->reset_end_level_data(); } return; - } catch (restart_turn_exception&) { - assert(false && "caugh end_turn exception in a bad place... terminating."); - std::terminate(); } @@ -264,8 +257,6 @@ void playsingle_controller::play_scenario_init() { this->reset_end_level_data(); } return; - } catch (restart_turn_exception &) { - //someone decided to droid a side during start event. Ignore it. } sync.do_final_checkup(); gui_->recalculate_minimap(); @@ -312,16 +303,10 @@ void playsingle_controller::play_scenario_main_loop() { possible_end_play_signal signal = play_turn(); if (signal) { - switch (boost::apply_visitor( get_signal_type(), *signal )) { - case END_LEVEL: - if(boost::get(*signal).is_quit) { - reset_end_level_data(); - } - return; - case END_TURN: - assert(false && "end turn signal propagated to playsingle_controller::play_scenario_main_loop, terminating"); - std::terminate(); + if(signal->is_quit) { + reset_end_level_data(); } + return; } do_autosaves_ = true; @@ -554,7 +539,7 @@ possible_end_play_signal playsingle_controller::play_turn() possible_end_play_signal playsingle_controller::play_idle_loop() { - while(!end_turn_) { + while(!end_turn_ && !player_type_changed_) { HANDLE_END_PLAY_SIGNAL( play_slice() ); gui_->draw(); SDL_Delay(10); @@ -571,8 +556,6 @@ possible_end_play_signal playsingle_controller::play_side() maybe_do_init_side(); } catch (end_level_exception & e) { return possible_end_play_signal(e.to_struct()); - } catch (restart_turn_exception &) { - // ignore it. Since we don't have started the loop below yet, we dont need to restart it. } //flag used when we fallback from ai and give temporarily control to human bool temporary_human = false; @@ -597,23 +580,18 @@ possible_end_play_signal playsingle_controller::play_side() if (!end_turn_) { signal = play_human_turn(); } - - if (signal) { - switch (boost::apply_visitor(get_signal_type(), *signal)) { - case END_LEVEL: - return signal; - case END_TURN: { - player_type_changed_ = true; - // If new controller is not human, - // reset gui to prev human one - if (!gamestate_.board_.teams()[player_number_-1].is_local_human()) { - int s = find_human_team_before_current_player(); - if (s <= 0) { - s = gui_->playing_side(); - } - update_gui_to_player(s-1); - } + if (signal) { + return signal; + } + if (player_type_changed_) { + // If new controller is not human, + // reset gui to prev human one + if (!gamestate_.board_.teams()[player_number_-1].is_local_human()) { + int s = find_human_team_before_current_player(); + if (s <= 0) { + s = gui_->playing_side(); } + update_gui_to_player(s-1); } } } @@ -631,8 +609,6 @@ possible_end_play_signal playsingle_controller::play_side() temporary_human = true; } catch (end_level_exception &e) { return possible_end_play_signal(e.to_struct()); - } catch (restart_turn_exception&) { - player_type_changed_ = true; } if(!player_type_changed_) { @@ -651,30 +627,27 @@ possible_end_play_signal playsingle_controller::play_side() if (!end_turn_) { signal = play_idle_loop(); } - + if (signal) { - switch (boost::apply_visitor(get_signal_type(), *signal)) { - case END_LEVEL: - return signal; - case END_TURN: { - LOG_NG << "Escaped from idle state with exception!" << std::endl; - player_type_changed_ = true; - // If new controller is not human, - // reset gui to prev human one - if (!gamestate_.board_.teams()[player_number_-1].is_local_human()) { - int s = find_human_team_before_current_player(); - if (s <= 0) { - s = gui_->playing_side(); - } - update_gui_to_player(s-1); - } - else { - //This side was previously not human controlled. - update_gui_to_player(player_number_ - 1); - } + return signal; + } + if (player_type_changed_) { + // If new controller is not human, + // reset gui to prev human one + if (!gamestate_.board_.teams()[player_number_-1].is_local_human()) { + int s = find_human_team_before_current_player(); + if (s <= 0) { + s = gui_->playing_side(); } + update_gui_to_player(s-1); } + else { + //This side was previously not human controlled. + update_gui_to_player(player_number_ - 1); + } + } + } else { assert(current_team().is_empty()); // Do nothing. @@ -694,6 +667,7 @@ void playsingle_controller::before_human_turn() if(end_turn_) { return; } + //TODO: why do we need the next line? ai::manager::raise_turn_started(); if(do_autosaves_ && !is_regular_game_end()) { @@ -732,7 +706,7 @@ possible_end_play_signal playsingle_controller::play_human_turn() { } end_turn_enable(true); - while(!end_turn_) { + while(!end_turn_ && !player_type_changed_) { HANDLE_END_PLAY_SIGNAL( play_slice() ); gui_->draw(); } @@ -937,6 +911,7 @@ bool playsingle_controller::is_host() const void playsingle_controller::maybe_linger() { // mouse_handler expects at least one team for linger mode to work. + assert(is_regular_game_end()); if (get_end_level_data_const().transient.linger_mode && !gamestate_.board_.teams().empty()) { linger(); } diff --git a/src/playsingle_controller.hpp b/src/playsingle_controller.hpp index ce707e78eba3..9ba135195208 100644 --- a/src/playsingle_controller.hpp +++ b/src/playsingle_controller.hpp @@ -67,6 +67,9 @@ class playsingle_controller : public play_controller class hotkey_handler; virtual bool is_end_turn() const { return end_turn_; } std::string describe_result() const; + + bool get_player_type_changed() const { return player_type_changed_; } + void set_player_type_changed() { player_type_changed_ = true; } protected: possible_end_play_signal play_turn(); virtual possible_end_play_signal play_side(); diff --git a/src/replay_controller.cpp b/src/replay_controller.cpp index b0e967b1477f..cc571b28b25c 100644 --- a/src/replay_controller.cpp +++ b/src/replay_controller.cpp @@ -43,7 +43,6 @@ #include #include #include -#include static lg::log_domain log_engine("engine"); #define DBG_NG LOG_STREAM(debug, log_engine) @@ -78,15 +77,7 @@ LEVEL_RESULT play_replay_level(const config& game_config, const tdata_cache & td possible_end_play_signal signal = play_replay_level_main_loop(*rc, is_unit_test); if (signal) { - switch( boost::apply_visitor( get_signal_type(), *signal ) ) { - case END_LEVEL: - DBG_NG << "play_replay_level: end_level_exception" << std::endl; - break; - case END_TURN: - DBG_NG << "Unchecked end_turn_exception signal propogated to replay controller play_replay_level! Terminating." << std::endl; - assert(false && "unchecked end turn exception in replay controller"); - throw 42; - } + DBG_NG << "play_replay_level: end_level_exception" << std::endl; } return VICTORY; @@ -491,15 +482,9 @@ possible_end_play_signal replay_controller::play_replay(){ possible_end_play_signal signal = play_replay_main_loop(); if(signal) { - switch ( boost::apply_visitor(get_signal_type(), *signal)) { - case END_TURN: - return signal; - case END_LEVEL: - if(boost::get(*signal).is_quit) { - return signal; - } + if(signal->is_quit) { + return signal; } - } if (!is_playing_) { diff --git a/src/whiteboard/move.cpp b/src/whiteboard/move.cpp index 4a658593d6a7..cf5ad6e42171 100644 --- a/src/whiteboard/move.cpp +++ b/src/whiteboard/move.cpp @@ -226,6 +226,7 @@ void move::execute(bool& success, bool& complete) events::mouse_handler& mouse_handler = resources::controller->get_mouse_handler_base(); num_steps = mouse_handler.move_unit_along_route(steps, interrupted); } catch (restart_turn_exception&) { + //This exception is thown by the ai code, i dont know whther the whiteboard can interact with the ai so i leave this catch here for now set_arrow_brightness(ARROW_BRIGHTNESS_STANDARD); throw; // we rely on the caller to delete this action } diff --git a/src/whiteboard/side_actions.cpp b/src/whiteboard/side_actions.cpp index 974dbfbc0cdb..7a4c87fa50af 100644 --- a/src/whiteboard/side_actions.cpp +++ b/src/whiteboard/side_actions.cpp @@ -337,6 +337,7 @@ bool side_actions::execute(side_actions::iterator position) try { action->execute(action_successful, action_complete); } catch (restart_turn_exception&) { + //This exception is thown by the ai code, i dont know whther the whiteboard can interact with the ai so i leave this catch here for now synced_erase(position); LOG_WB << "End turn exception caught during execution, deleting action. " << *this << "\n"; //validate actions at next map rebuild