1
0
Fork 0

(svn r21311) -Fix [FS#4260]: AIs in an infinite loop in e.g. autosave, but also getting settings and such from info.nut, would not be interrupted after a while causing OpenTTD to seem to not respond

release/1.1
rubidium 2010-11-24 17:00:37 +00:00
parent 3330dee7e0
commit 090d6fb8b7
4 changed files with 35 additions and 22 deletions

View File

@ -19,6 +19,9 @@
#include "../debug.h" #include "../debug.h"
#include "../rev.h" #include "../rev.h"
/** Maximum number of operations allowed for getting a particular setting. */
static const int MAX_GET_SETTING_OPS = 100000;
/** Configuration for AI start date, every AI has this setting. */ /** Configuration for AI start date, every AI has this setting. */
AIConfigItem _start_date_config = { AIConfigItem _start_date_config = {
"start_date", "start_date",
@ -78,19 +81,19 @@ static bool CheckAPIVersion(const char *api_version)
if (!info->GetSettings()) return SQ_ERROR; if (!info->GetSettings()) return SQ_ERROR;
} }
if (info->engine->MethodExists(*info->SQ_instance, "MinVersionToLoad")) { if (info->engine->MethodExists(*info->SQ_instance, "MinVersionToLoad")) {
if (!info->engine->CallIntegerMethod(*info->SQ_instance, "MinVersionToLoad", &info->min_loadable_version)) return SQ_ERROR; if (!info->engine->CallIntegerMethod(*info->SQ_instance, "MinVersionToLoad", &info->min_loadable_version, MAX_GET_SETTING_OPS)) return SQ_ERROR;
} else { } else {
info->min_loadable_version = info->GetVersion(); info->min_loadable_version = info->GetVersion();
} }
/* When there is an UseAsRandomAI function, call it. */ /* When there is an UseAsRandomAI function, call it. */
if (info->engine->MethodExists(*info->SQ_instance, "UseAsRandomAI")) { if (info->engine->MethodExists(*info->SQ_instance, "UseAsRandomAI")) {
if (!info->engine->CallBoolMethod(*info->SQ_instance, "UseAsRandomAI", &info->use_as_random)) return SQ_ERROR; if (!info->engine->CallBoolMethod(*info->SQ_instance, "UseAsRandomAI", &info->use_as_random, MAX_GET_SETTING_OPS)) return SQ_ERROR;
} else { } else {
info->use_as_random = true; info->use_as_random = true;
} }
/* Try to get the API version the AI is written for. */ /* Try to get the API version the AI is written for. */
if (info->engine->MethodExists(*info->SQ_instance, "GetAPIVersion")) { if (info->engine->MethodExists(*info->SQ_instance, "GetAPIVersion")) {
if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAPIVersion", &info->api_version)) return SQ_ERROR; if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAPIVersion", &info->api_version, MAX_GET_SETTING_OPS)) return SQ_ERROR;
if (!CheckAPIVersion(info->api_version)) { if (!CheckAPIVersion(info->api_version)) {
DEBUG(ai, 1, "Loading info.nut from (%s.%d): GetAPIVersion returned invalid version", info->GetName(), info->GetVersion()); DEBUG(ai, 1, "Loading info.nut from (%s.%d): GetAPIVersion returned invalid version", info->GetName(), info->GetVersion());
return SQ_ERROR; return SQ_ERROR;
@ -130,7 +133,7 @@ static bool CheckAPIVersion(const char *api_version)
bool AIInfo::GetSettings() bool AIInfo::GetSettings()
{ {
return this->engine->CallMethod(*this->SQ_instance, "GetSettings", NULL, -1); return this->engine->CallMethod(*this->SQ_instance, "GetSettings", NULL, MAX_GET_SETTING_OPS);
} }
AIInfo::AIInfo() : AIInfo::AIInfo() :
@ -360,7 +363,7 @@ int AIInfo::GetSettingDefaultValue(const char *name) const
} }
/* Cache the category */ /* Cache the category */
if (!library->CheckMethod("GetCategory") || !library->engine->CallStringMethodStrdup(*library->SQ_instance, "GetCategory", &library->category)) { if (!library->CheckMethod("GetCategory") || !library->engine->CallStringMethodStrdup(*library->SQ_instance, "GetCategory", &library->category, MAX_GET_SETTING_OPS)) {
delete library; delete library;
return SQ_ERROR; return SQ_ERROR;
} }

View File

@ -76,6 +76,11 @@
#include "../company_base.h" #include "../company_base.h"
#include "../fileio_func.h" #include "../fileio_func.h"
/** The maximum number of operations for saving or loading the data of an AI. */
static const int MAX_SL_OPS = 100000;
/** The maximum number of operations for initial start of an AI. */
static const int MAX_CONSTRUCTOR_OPS = 100000;
AIStorage::~AIStorage() AIStorage::~AIStorage()
{ {
/* Free our pointers */ /* Free our pointers */
@ -344,7 +349,7 @@ void AIInstance::GameLoop()
AIObject::SetAllowDoCommand(false); AIObject::SetAllowDoCommand(false);
/* Run the constructor if it exists. Don't allow any DoCommands in it. */ /* Run the constructor if it exists. Don't allow any DoCommands in it. */
if (this->engine->MethodExists(*this->instance, "constructor")) { if (this->engine->MethodExists(*this->instance, "constructor")) {
if (!this->engine->CallMethod(*this->instance, "constructor", 100000) || this->engine->IsSuspended()) { if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
if (this->engine->IsSuspended()) AILog::Error("This AI took too long to initialize. AI is not started."); if (this->engine->IsSuspended()) AILog::Error("This AI took too long to initialize. AI is not started.");
this->Died(); this->Died();
return; return;
@ -611,7 +616,7 @@ void AIInstance::Save()
bool backup_allow = AIObject::GetAllowDoCommand(); bool backup_allow = AIObject::GetAllowDoCommand();
AIObject::SetAllowDoCommand(false); AIObject::SetAllowDoCommand(false);
try { try {
if (!this->engine->CallMethod(*this->instance, "Save", &savedata)) { if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
/* The script crashed in the Save function. We can't kill /* The script crashed in the Save function. We can't kill
* it here, but do so in the next AI tick. */ * it here, but do so in the next AI tick. */
SaveEmpty(); SaveEmpty();
@ -634,7 +639,7 @@ void AIInstance::Save()
AIObject::SetAllowDoCommand(backup_allow); AIObject::SetAllowDoCommand(backup_allow);
if (!sq_istable(savedata)) { if (!sq_istable(savedata)) {
AILog::Error("Save function should return a table."); AILog::Error(this->engine->IsSuspended() ? "This AI took too long to Save." : "Save function should return a table.");
SaveEmpty(); SaveEmpty();
this->engine->CrashOccurred(); this->engine->CrashOccurred();
return; return;
@ -776,7 +781,7 @@ bool AIInstance::CallLoad()
/* Call the AI load function. sq_call removes the arguments (but not the /* Call the AI load function. sq_call removes the arguments (but not the
* function pointer) from the stack. */ * function pointer) from the stack. */
if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse, 100000))) return false; if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse, MAX_SL_OPS))) return false;
/* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */ /* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
sq_pop(vm, 4); sq_pop(vm, 4);

View File

@ -16,6 +16,11 @@
#include "script_info.hpp" #include "script_info.hpp"
#include "script_scanner.hpp" #include "script_scanner.hpp"
/** Number of operations to get the author and similar information. */
static const int MAX_GET_OPS = 1000;
/** Number of operations to create an instance of an AI. */
static const int MAX_CREATEINSTANCE_OPS = 100000;
ScriptFileInfo::~ScriptFileInfo() ScriptFileInfo::~ScriptFileInfo()
{ {
free((void *)this->author); free((void *)this->author);
@ -66,17 +71,17 @@ bool ScriptFileInfo::CheckMethod(const char *name) const
info->main_script = strdup(scanner->GetMainScript()); info->main_script = strdup(scanner->GetMainScript());
/* Cache the data the info file gives us. */ /* Cache the data the info file gives us. */
if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAuthor", &info->author)) return SQ_ERROR; if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetAuthor", &info->author, MAX_GET_OPS)) return SQ_ERROR;
if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetName", &info->name)) return SQ_ERROR; if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetName", &info->name, MAX_GET_OPS)) return SQ_ERROR;
if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetShortName", &info->short_name)) return SQ_ERROR; if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetShortName", &info->short_name, MAX_GET_OPS)) return SQ_ERROR;
if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDescription", &info->description)) return SQ_ERROR; if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDescription", &info->description, MAX_GET_OPS)) return SQ_ERROR;
if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDate", &info->date)) return SQ_ERROR; if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetDate", &info->date, MAX_GET_OPS)) return SQ_ERROR;
if (!info->engine->CallIntegerMethod(*info->SQ_instance, "GetVersion", &info->version)) return SQ_ERROR; if (!info->engine->CallIntegerMethod(*info->SQ_instance, "GetVersion", &info->version, MAX_GET_OPS)) return SQ_ERROR;
if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "CreateInstance", &info->instance_name)) return SQ_ERROR; if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "CreateInstance", &info->instance_name, MAX_CREATEINSTANCE_OPS)) return SQ_ERROR;
/* The GetURL function is optional. */ /* The GetURL function is optional. */
if (info->engine->MethodExists(*info->SQ_instance, "GetURL")) { if (info->engine->MethodExists(*info->SQ_instance, "GetURL")) {
if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetURL", &info->url)) return SQ_ERROR; if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetURL", &info->url, MAX_GET_OPS)) return SQ_ERROR;
} }
return 0; return 0;

View File

@ -140,11 +140,11 @@ public:
* Call a method of an instance, in various flavors. * Call a method of an instance, in various flavors.
* @return False if the script crashed or returned a wrong type. * @return False if the script crashed or returned a wrong type.
*/ */
bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend = -1); bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend);
bool CallMethod(HSQOBJECT instance, const char *method_name, int suspend = -1) { return this->CallMethod(instance, method_name, NULL, suspend); } bool CallMethod(HSQOBJECT instance, const char *method_name, int suspend) { return this->CallMethod(instance, method_name, NULL, suspend); }
bool CallStringMethodStrdup(HSQOBJECT instance, const char *method_name, const char **res, int suspend = -1); bool CallStringMethodStrdup(HSQOBJECT instance, const char *method_name, const char **res, int suspend);
bool CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend = -1); bool CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend);
bool CallBoolMethod(HSQOBJECT instance, const char *method_name, bool *res, int suspend = -1); bool CallBoolMethod(HSQOBJECT instance, const char *method_name, bool *res, int suspend);
/** /**
* Check if a method exists in an instance. * Check if a method exists in an instance.