1
0
Fork 0

(svn r24537) -Feature: Scripts can be suspended even if the game is still progressing, thus break-on-log now works also for Game Scripts.

release/1.3
zuu 2012-09-21 19:58:18 +00:00
parent 2e1936b11c
commit f3f4c562ff
7 changed files with 173 additions and 27 deletions

View File

@ -68,13 +68,30 @@ public:
static void Stop(CompanyID company); static void Stop(CompanyID company);
/** /**
* Suspend an AI for the reminder of the current tick. If the AI is * Suspend the AI and then pause execution of the script. The script
* in a state when it cannot be suspended, it will continue to run * will not be resumed from its suspended state until the script has
* until it can be suspended. * been unpaused.
* @param company The company for which the AI should be suspended. * @param company The company for which the AI should be paused.
* @pre Company::IsValidAiID(company) * @pre Company::IsValidAiID(company)
*/ */
static void Suspend(CompanyID company); static void Pause(CompanyID company);
/**
* Resume execution of the AI. This function will not actually execute
* the script, but set a flag so that the script is executed my the usual
* mechanism that executes the script.
* @param company The company for which the AI should be unpaused.
* @pre Company::IsValidAiID(company)
*/
static void Unpause(CompanyID company);
/**
* Checks if the AI is paused.
* @param company The company for which to check if the AI is paused.
* @pre Company::IsValidAiID(company)
* @return true if the AI is paused, otherwise false.
*/
static bool IsPaused(CompanyID company);
/** /**
* Kill any and all AIs we manage. * Kill any and all AIs we manage.

View File

@ -112,16 +112,37 @@
DeleteWindowById(WC_AI_SETTINGS, company); DeleteWindowById(WC_AI_SETTINGS, company);
} }
/* static */ void AI::Suspend(CompanyID company) /* static */ void AI::Pause(CompanyID company)
{ {
if (_networking && !_network_server) return; /* The reason why dedicated servers are forbidden to execute this
* command is not because it is unsafe, but because there is no way
* for the server owner to unpause the script again. */
if (_network_dedicated) return;
Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE); Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE);
Company::Get(company)->ai_instance->Suspend(); Company::Get(company)->ai_instance->Pause();
cur_company.Restore(); cur_company.Restore();
} }
/* static */ void AI::Unpause(CompanyID company)
{
Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE);
Company::Get(company)->ai_instance->Unpause();
cur_company.Restore();
}
/* static */ bool AI::IsPaused(CompanyID company)
{
Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE);
bool paused = Company::Get(company)->ai_instance->IsPaused();
cur_company.Restore();
return paused;
}
/* static */ void AI::KillAll() /* static */ void AI::KillAll()
{ {
/* It might happen there are no companies .. than we have nothing to loop */ /* It might happen there are no companies .. than we have nothing to loop */

View File

@ -961,11 +961,15 @@ void ShowAIConfigWindow()
* state of the script. (dead or alive) * state of the script. (dead or alive)
* @param button the button to update. * @param button the button to update.
* @param dead true if the script is dead, otherwise false. * @param dead true if the script is dead, otherwise false.
* @param paused true if the script is paused, otherwise false.
* @return true if the colour was changed and the window need to be marked as dirty. * @return true if the colour was changed and the window need to be marked as dirty.
*/ */
static bool SetScriptButtonColour(NWidgetCore &button, bool dead) static bool SetScriptButtonColour(NWidgetCore &button, bool dead, bool paused)
{ {
Colours colour = dead ? COLOUR_RED : COLOUR_GREY; /* Dead scripts are indicated with red background and
* paused scripts are indicated with yellow background. */
Colours colour = dead ? COLOUR_RED :
(paused ? COLOUR_YELLOW : COLOUR_GREY);
if (button.colour != colour) { if (button.colour != colour) {
button.colour = colour; button.colour = colour;
return true; return true;
@ -1035,8 +1039,10 @@ struct AIDebugWindow : public QueryStringBaseWindow {
/* Restore button state from static class variables */ /* Restore button state from static class variables */
if (ai_debug_company == OWNER_DEITY) { if (ai_debug_company == OWNER_DEITY) {
this->LowerWidget(WID_AID_SCRIPT_GAME); this->LowerWidget(WID_AID_SCRIPT_GAME);
this->SetWidgetDisabledState(WID_AID_CONTINUE_BTN, !Game::IsPaused());
} else if (ai_debug_company != INVALID_COMPANY) { } else if (ai_debug_company != INVALID_COMPANY) {
this->LowerWidget(ai_debug_company + WID_AID_COMPANY_BUTTON_START); this->LowerWidget(ai_debug_company + WID_AID_COMPANY_BUTTON_START);
this->SetWidgetDisabledState(WID_AID_CONTINUE_BTN, !AI::IsPaused(ai_debug_company));
} }
this->SetWidgetLoweredState(WID_AID_BREAK_STR_ON_OFF_BTN, this->break_check_enabled); this->SetWidgetLoweredState(WID_AID_BREAK_STR_ON_OFF_BTN, this->break_check_enabled);
this->SetWidgetLoweredState(WID_AID_MATCH_CASE_BTN, this->case_sensitive_break_check); this->SetWidgetLoweredState(WID_AID_MATCH_CASE_BTN, this->case_sensitive_break_check);
@ -1107,11 +1113,12 @@ struct AIDebugWindow : public QueryStringBaseWindow {
dirty = true; dirty = true;
} }
/* Mark dead AIs by red background. */ /* Mark dead/paused AIs by setting the background colour. */
bool dead = valid && Company::Get(i)->ai_instance->IsDead(); bool dead = valid && Company::Get(i)->ai_instance->IsDead();
bool paused = valid && Company::Get(i)->ai_instance->IsPaused();
/* Re-paint if the button was updated. /* Re-paint if the button was updated.
* (note that it is intentional that SetScriptButtonColour is always called) */ * (note that it is intentional that SetScriptButtonColour is always called) */
dirty = SetScriptButtonColour(*button, dead) || dirty; dirty = SetScriptButtonColour(*button, dead, paused) || dirty;
/* Do we need a repaint? */ /* Do we need a repaint? */
if (dirty) this->SetDirty(); if (dirty) this->SetDirty();
@ -1125,8 +1132,9 @@ struct AIDebugWindow : public QueryStringBaseWindow {
/* Set button colour for Game Script. */ /* Set button colour for Game Script. */
GameInstance *game = Game::GetInstance(); GameInstance *game = Game::GetInstance();
bool dead = game != NULL && game->IsDead(); bool dead = game != NULL && game->IsDead();
bool paused = game != NULL && game->IsPaused();
NWidgetCore *button = this->GetWidget<NWidgetCore>(WID_AID_SCRIPT_GAME); NWidgetCore *button = this->GetWidget<NWidgetCore>(WID_AID_SCRIPT_GAME);
if (SetScriptButtonColour(*button, dead)) { if (SetScriptButtonColour(*button, dead, paused)) {
/* Re-paint if the button was updated. */ /* Re-paint if the button was updated. */
this->SetWidgetDirty(WID_AID_SCRIPT_GAME); this->SetWidgetDirty(WID_AID_SCRIPT_GAME);
} }
@ -1243,10 +1251,13 @@ struct AIDebugWindow : public QueryStringBaseWindow {
if (ai_debug_company == OWNER_DEITY) { if (ai_debug_company == OWNER_DEITY) {
this->LowerWidget(WID_AID_SCRIPT_GAME); this->LowerWidget(WID_AID_SCRIPT_GAME);
this->SetWidgetDisabledState(WID_AID_CONTINUE_BTN, !Game::IsPaused());
} else { } else {
this->LowerWidget(ai_debug_company + WID_AID_COMPANY_BUTTON_START); this->LowerWidget(ai_debug_company + WID_AID_COMPANY_BUTTON_START);
this->SetWidgetDisabledState(WID_AID_CONTINUE_BTN, !AI::IsPaused(ai_debug_company));
} }
this->highlight_row = -1; // The highlight of one AI make little sense for another AI.
this->autoscroll = true; this->autoscroll = true;
this->last_vscroll_pos = this->vscroll->GetPosition(); this->last_vscroll_pos = this->vscroll->GetPosition();
this->SetDirty(); this->SetDirty();
@ -1292,8 +1303,35 @@ struct AIDebugWindow : public QueryStringBaseWindow {
break; break;
case WID_AID_CONTINUE_BTN: case WID_AID_CONTINUE_BTN:
/* Unpause */ /* Unpause current AI / game script and mark the corresponding script button dirty. */
DoCommandP(0, PM_PAUSED_NORMAL, 0, CMD_PAUSE); if (ai_debug_company == OWNER_DEITY) {
Game::Unpause();
this->SetWidgetDirty(WID_AID_SCRIPT_GAME);
} else {
AI::Unpause(ai_debug_company);
this->SetWidgetDirty(WID_AID_COMPANY_BUTTON_START + ai_debug_company);
}
/* If the last AI/Game Script is unpaused, unpause the game too. */
if ((_pause_mode & PM_PAUSED_NORMAL) == PM_PAUSED_NORMAL) {
bool all_unpaused = !Game::IsPaused();
if (all_unpaused) {
Company *c;
FOR_ALL_COMPANIES(c) {
if (c->is_ai && AI::IsPaused(c->index)) {
all_unpaused = false;
break;
}
}
if (all_unpaused) {
/* All scripts have been unpaused => unpause the game. */
DoCommandP(0, PM_PAUSED_NORMAL, 0, CMD_PAUSE);
}
}
}
this->highlight_row = -1;
this->SetWidgetDirty(WID_AID_LOG_PANEL);
this->DisableWidget(WID_AID_CONTINUE_BTN); this->DisableWidget(WID_AID_CONTINUE_BTN);
this->RaiseWidget(WID_AID_CONTINUE_BTN); // Disabled widgets don't raise themself this->RaiseWidget(WID_AID_CONTINUE_BTN); // Disabled widgets don't raise themself
break; break;
@ -1334,9 +1372,8 @@ struct AIDebugWindow : public QueryStringBaseWindow {
if (gui_scope && data == -2) { if (gui_scope && data == -2) {
/* The continue button should be disabled when the game is unpaused and /* The continue button should be disabled when the game is unpaused and
* it was previously paused by the break string ( = a line in the log * it was previously paused. */
* was highlighted )*/ if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED && !this->IsWidgetDisabled(WID_AID_CONTINUE_BTN)) {
if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED && this->highlight_row != -1) {
this->DisableWidget(WID_AID_CONTINUE_BTN); this->DisableWidget(WID_AID_CONTINUE_BTN);
this->SetWidgetDirty(WID_AID_CONTINUE_BTN); this->SetWidgetDirty(WID_AID_CONTINUE_BTN);
this->SetWidgetDirty(WID_AID_LOG_PANEL); this->SetWidgetDirty(WID_AID_LOG_PANEL);
@ -1346,7 +1383,7 @@ struct AIDebugWindow : public QueryStringBaseWindow {
/* If the log message is related to the active company tab, check the break string. /* If the log message is related to the active company tab, check the break string.
* This needs to be done in gameloop-scope, so the AI is suspended immediately. */ * This needs to be done in gameloop-scope, so the AI is suspended immediately. */
if (ai_debug_company != OWNER_DEITY && !gui_scope && data == ai_debug_company && this->break_check_enabled && !this->break_string_filter.IsEmpty()) { if (!gui_scope && data == ai_debug_company && this->break_check_enabled && !this->break_string_filter.IsEmpty()) {
/* Get the log instance of the active company */ /* Get the log instance of the active company */
ScriptLog::LogData *log = this->GetLogPointer(); ScriptLog::LogData *log = this->GetLogPointer();
@ -1354,7 +1391,14 @@ struct AIDebugWindow : public QueryStringBaseWindow {
this->break_string_filter.ResetState(); this->break_string_filter.ResetState();
this->break_string_filter.AddLine(log->lines[log->pos]); this->break_string_filter.AddLine(log->lines[log->pos]);
if (this->break_string_filter.GetState()) { if (this->break_string_filter.GetState()) {
AI::Suspend(ai_debug_company); /* Pause execution of script. */
if (ai_debug_company == OWNER_DEITY) {
Game::Pause();
} else {
AI::Pause(ai_debug_company);
}
/* Pause the game. */
if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED) { if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED) {
DoCommandP(0, PM_PAUSED_NORMAL, 1, CMD_PAUSE); DoCommandP(0, PM_PAUSED_NORMAL, 1, CMD_PAUSE);
} }

View File

@ -46,6 +46,26 @@ public:
*/ */
static void Uninitialize(bool keepConfig); static void Uninitialize(bool keepConfig);
/**
* Suspends the Game Script and then pause the execution of the script. The
* script will not be resumed from its suspended state until the script
* has been unpaused.
*/
static void Pause();
/**
* Resume execution of the Game Script. This function will not actually execute
* the script, but set a flag so that the script is executed my the usual
* mechanism that executes the script.
*/
static void Unpause();
/**
* Checks if the Game Script is paused.
* @return true if the Game Script is paused, otherwise false.
*/
static bool IsPaused();
/** /**
* Queue a new event for a Game Script. * Queue a new event for a Game Script.
*/ */

View File

@ -117,6 +117,21 @@
} }
} }
/* static */ void Game::Pause()
{
if (Game::instance != NULL) Game::instance->Pause();
}
/* static */ void Game::Unpause()
{
if (Game::instance != NULL) Game::instance->Unpause();
}
/* static */ bool Game::IsPaused()
{
return Game::instance != NULL? Game::instance->IsPaused() : false;
}
/* static */ void Game::NewEvent(ScriptEvent *event) /* static */ void Game::NewEvent(ScriptEvent *event)
{ {
/* AddRef() and Release() need to be called at least once, so do it here */ /* AddRef() and Release() need to be called at least once, so do it here */

View File

@ -54,6 +54,7 @@ ScriptInstance::ScriptInstance(const char *APIName) :
instance(NULL), instance(NULL),
is_started(false), is_started(false),
is_dead(false), is_dead(false),
is_paused(false),
is_save_data_on_stack(false), is_save_data_on_stack(false),
suspend(0), suspend(0),
callback(NULL) callback(NULL)
@ -165,6 +166,7 @@ void ScriptInstance::GameLoop()
this->Died(); this->Died();
return; return;
} }
if (this->is_paused) return;
this->controller->ticks++; this->controller->ticks++;
if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1. if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
@ -520,10 +522,23 @@ void ScriptInstance::Save()
} }
} }
void ScriptInstance::Suspend() void ScriptInstance::Pause()
{ {
/* Suspend script. */
HSQUIRRELVM vm = this->engine->GetVM(); HSQUIRRELVM vm = this->engine->GetVM();
Squirrel::DecreaseOps(vm, _settings_game.script.script_max_opcode_till_suspend); Squirrel::DecreaseOps(vm, _settings_game.script.script_max_opcode_till_suspend);
this->is_paused = true;
}
void ScriptInstance::Unpause()
{
this->is_paused = false;
}
bool ScriptInstance::IsPaused()
{
return this->is_paused;
} }
/* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm) /* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm)

View File

@ -140,12 +140,24 @@ public:
static void LoadEmpty(); static void LoadEmpty();
/** /**
* Reduces the number of opcodes the script have left to zero. Unless * Suspends the script for the current tick and then pause the execution
* the script is in a state where it cannot suspend it will be suspended * of script. The script will not be resumed from its suspended state
* for the reminder of the current tick. This function is safe to * until the script has been unpaused.
* call from within a function called by the script.
*/ */
void Suspend(); void Pause();
/**
* Checks if the script is paused.
* @return true if the script is paused, otherwise false
*/
bool IsPaused();
/**
* Resume execution of the script. This function will not actually execute
* the script, but set a flag so that the script is executed my the usual
* mechanism that executes the script.
*/
void Unpause();
/** /**
* Get the number of operations the script can execute before being suspended. * Get the number of operations the script can execute before being suspended.
@ -171,7 +183,8 @@ public:
/** /**
* Check if the instance is sleeping, which either happened because the * Check if the instance is sleeping, which either happened because the
* script executed a DoCommand, or executed this.Sleep(). * script executed a DoCommand, executed this.Sleep() or it has been
* paused.
*/ */
bool IsSleeping() { return this->suspend != 0; } bool IsSleeping() { return this->suspend != 0; }
@ -216,6 +229,7 @@ private:
bool is_dead; ///< True if the script has been stopped. bool is_dead; ///< True if the script has been stopped.
bool is_save_data_on_stack; ///< Is the save data still on the squirrel stack? bool is_save_data_on_stack; ///< Is the save data still on the squirrel stack?
int suspend; ///< The amount of ticks to suspend this script before it's allowed to continue. int suspend; ///< The amount of ticks to suspend this script before it's allowed to continue.
bool is_paused; ///< Is the script paused? (a paused script will not be executed until unpaused)
Script_SuspendCallbackProc *callback; ///< Callback that should be called in the next tick the script runs. Script_SuspendCallbackProc *callback; ///< Callback that should be called in the next tick the script runs.
/** /**