From 3da8b5097a4643d531182173df36ca4d3b45a4e2 Mon Sep 17 00:00:00 2001 From: truebrain Date: Tue, 29 Nov 2011 23:21:33 +0000 Subject: (svn r23360) -Codechange: move AIInstance to ScriptInstance, making it reusable by other script API instances --- src/ai/ai_instance.cpp | 622 +------------------------------------------------ 1 file changed, 8 insertions(+), 614 deletions(-) (limited to 'src/ai/ai_instance.cpp') diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp index a4a30334f..bf6af96f3 100644 --- a/src/ai/ai_instance.cpp +++ b/src/ai/ai_instance.cpp @@ -80,105 +80,25 @@ #include "../company_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; - -ScriptStorage::~ScriptStorage() -{ - /* Free our pointers */ - if (event_data != NULL) ScriptEventController::FreeEventPointer(); - if (log_data != NULL) ScriptLog::FreeLogPointer(); -} - -/** - * Callback called by squirrel when an AI uses "print" and for error messages. - * @param error_msg Is this an error message? - * @param message The actual message text. - */ -static void PrintFunc(bool error_msg, const SQChar *message) -{ - /* Convert to OpenTTD internal capable string */ - ScriptController::Print(error_msg, SQ2OTTD(message)); -} - AIInstance::AIInstance() : - controller(NULL), - storage(NULL), - engine(NULL), - instance(NULL), - is_started(false), - is_dead(false), - is_save_data_on_stack(false), - suspend(0), - callback(NULL) -{ - this->storage = new ScriptStorage(); - this->engine = new Squirrel("AI"); - this->engine->SetPrintFunction(&PrintFunc); -} + ScriptInstance("AI") +{} void AIInstance::Initialize(AIInfo *info) { - ScriptObject::ActiveInstance active(this); - - this->controller = new ScriptController(); + this->versionAPI = info->GetAPIVersion(); /* Register the AIController (including the "import" command) */ SQAIController_Register(this->engine); - /* Register the API functions and classes */ - this->RegisterAPI(); - - if (!this->LoadCompatibilityScripts(info->GetAPIVersion())) { - this->Died(); - return; - } - - try { - ScriptObject::SetAllowDoCommand(false); - /* Load and execute the script for this AI */ - const char *main_script = info->GetMainScript(); - if (strcmp(main_script, "%_dummy") == 0) { - extern void AI_CreateAIDummy(HSQUIRRELVM vm); - AI_CreateAIDummy(this->engine->GetVM()); - } else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) { - if (this->engine->IsSuspended()) ScriptLog::Error("This AI took too long to load script. AI is not started."); - this->Died(); - return; - } - - /* Create the main-class */ - this->instance = MallocT(1); - if (!this->engine->CreateClassInstance(info->GetInstanceName(), this->controller, this->instance)) { - this->Died(); - return; - } - ScriptObject::SetAllowDoCommand(true); - } catch (Script_FatalError e) { - this->is_dead = true; - this->engine->ThrowError(e.GetErrorMessage()); - this->engine->ResumeError(); - this->Died(); - } -} - -AIInstance::~AIInstance() -{ - ScriptObject::ActiveInstance active(this); - - if (instance != NULL) this->engine->ReleaseObject(this->instance); - if (engine != NULL) delete this->engine; - delete this->storage; - delete this->controller; - free(this->instance); + ScriptInstance::Initialize(info->GetMainScript(), info->GetInstanceName()); } void AIInstance::RegisterAPI() { + ScriptInstance::RegisterAPI(); + /* Register all classes */ - squirrel_register_std(this->engine); SQAIList_Register(this->engine); SQAIAccounting_Register(this->engine); SQAIAirport_Register(this->engine); @@ -266,7 +186,7 @@ void AIInstance::RegisterAPI() SQAIWaypointList_Register(this->engine); SQAIWaypointList_Vehicle_Register(this->engine); - this->engine->SetGlobalPointer(this->engine); + if (!this->LoadCompatibilityScripts(this->versionAPI)) this->Died(); } bool AIInstance::LoadCompatibilityScripts(const char *api_version) @@ -291,21 +211,9 @@ bool AIInstance::LoadCompatibilityScripts(const char *api_version) return true; } -void AIInstance::Continue() -{ - assert(this->suspend < 0); - this->suspend = -this->suspend - 1; -} - void AIInstance::Died() { - DEBUG(ai, 0, "The AI died unexpectedly."); - this->is_dead = true; - - if (this->instance != NULL) this->engine->ReleaseObject(this->instance); - delete this->engine; - this->instance = NULL; - this->engine = NULL; + ScriptInstance::Died(); ShowAIDebugWindow(_current_company); @@ -319,517 +227,3 @@ void AIInstance::Died() } } } - -void AIInstance::GameLoop() -{ - ScriptObject::ActiveInstance active(this); - - if (this->IsDead()) return; - if (this->engine->HasScriptCrashed()) { - /* The script crashed during saving, kill it here. */ - this->Died(); - return; - } - this->controller->ticks++; - - if (this->suspend < -1) this->suspend++; // Multiplayer suspend, increase up to -1. - if (this->suspend < 0) return; // Multiplayer suspend, wait for Continue(). - if (--this->suspend > 0) return; // Singleplayer suspend, decrease to 0. - - /* If there is a callback to call, call that first */ - if (this->callback != NULL) { - if (this->is_save_data_on_stack) { - sq_poptop(this->engine->GetVM()); - this->is_save_data_on_stack = false; - } - try { - this->callback(this); - } catch (Script_Suspend e) { - this->suspend = e.GetSuspendTime(); - this->callback = e.GetSuspendCallback(); - - return; - } - } - - this->suspend = 0; - this->callback = NULL; - - if (!this->is_started) { - try { - ScriptObject::SetAllowDoCommand(false); - /* Run the constructor if it exists. Don't allow any DoCommands in it. */ - if (this->engine->MethodExists(*this->instance, "constructor")) { - if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) { - if (this->engine->IsSuspended()) ScriptLog::Error("This AI took too long to initialize. AI is not started."); - this->Died(); - return; - } - } - if (!this->CallLoad() || this->engine->IsSuspended()) { - if (this->engine->IsSuspended()) ScriptLog::Error("This AI took too long in the Load function. AI is not started."); - this->Died(); - return; - } - ScriptObject::SetAllowDoCommand(true); - /* Start the AI by calling Start() */ - if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.ai.ai_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died(); - } catch (Script_Suspend e) { - this->suspend = e.GetSuspendTime(); - this->callback = e.GetSuspendCallback(); - } catch (Script_FatalError e) { - this->is_dead = true; - this->engine->ThrowError(e.GetErrorMessage()); - this->engine->ResumeError(); - this->Died(); - } - - this->is_started = true; - return; - } - if (this->is_save_data_on_stack) { - sq_poptop(this->engine->GetVM()); - this->is_save_data_on_stack = false; - } - - /* Continue the VM */ - try { - if (!this->engine->Resume(_settings_game.ai.ai_max_opcode_till_suspend)) this->Died(); - } catch (Script_Suspend e) { - this->suspend = e.GetSuspendTime(); - this->callback = e.GetSuspendCallback(); - } catch (Script_FatalError e) { - this->is_dead = true; - this->engine->ThrowError(e.GetErrorMessage()); - this->engine->ResumeError(); - this->Died(); - } -} - -void AIInstance::CollectGarbage() const -{ - if (this->is_started && !this->IsDead()) this->engine->CollectGarbage(); -} - -/* static */ void AIInstance::DoCommandReturn(AIInstance *instance) -{ - instance->engine->InsertResult(ScriptObject::GetLastCommandRes()); -} - -/* static */ void AIInstance::DoCommandReturnVehicleID(AIInstance *instance) -{ - instance->engine->InsertResult(ScriptObject::GetNewVehicleID()); -} - -/* static */ void AIInstance::DoCommandReturnSignID(AIInstance *instance) -{ - instance->engine->InsertResult(ScriptObject::GetNewSignID()); -} - -/* static */ void AIInstance::DoCommandReturnGroupID(AIInstance *instance) -{ - instance->engine->InsertResult(ScriptObject::GetNewGroupID()); -} - -ScriptStorage *AIInstance::GetStorage() -{ - return this->storage; -} - -void *AIInstance::GetLogPointer() -{ - ScriptObject::ActiveInstance active(this); - - return ScriptObject::GetLogPointer(); -} - -/* - * All data is stored in the following format: - * First 1 byte indicating if there is a data blob at all. - * 1 byte indicating the type of data. - * The data itself, this differs per type: - * - integer: a binary representation of the integer (int32). - * - string: First one byte with the string length, then a 0-terminated char - * array. The string can't be longer than 255 bytes (including - * terminating '\0'). - * - array: All data-elements of the array are saved recursive in this - * format, and ended with an element of the type - * SQSL_ARRAY_TABLE_END. - * - table: All key/value pairs are saved in this format (first key 1, then - * value 1, then key 2, etc.). All keys and values can have an - * arbitrary type (as long as it is supported by the save function - * of course). The table is ended with an element of the type - * SQSL_ARRAY_TABLE_END. - * - bool: A single byte with value 1 representing true and 0 false. - * - null: No data. - */ - -/** The type of the data that follows in the savegame. */ -enum SQSaveLoadType { - SQSL_INT = 0x00, ///< The following data is an integer. - SQSL_STRING = 0x01, ///< The following data is an string. - SQSL_ARRAY = 0x02, ///< The following data is an array. - SQSL_TABLE = 0x03, ///< The following data is an table. - SQSL_BOOL = 0x04, ///< The following data is a boolean. - SQSL_NULL = 0x05, ///< A null variable. - SQSL_ARRAY_TABLE_END = 0xFF, ///< Marks the end of an array or table, no data follows. -}; - -static byte _ai_sl_byte; ///< Used as source/target by the AI saveload code to store/load a single byte. - -/** SaveLoad array that saves/loads exactly one byte. */ -static const SaveLoad _ai_byte[] = { - SLEG_VAR(_ai_sl_byte, SLE_UINT8), - SLE_END() -}; - -static const uint AISAVE_MAX_DEPTH = 25; ///< The maximum recursive depth for items stored in the savegame. - -/* static */ bool AIInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test) -{ - if (max_depth == 0) { - ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved."); - return false; - } - - switch (sq_gettype(vm, index)) { - case OT_INTEGER: { - if (!test) { - _ai_sl_byte = SQSL_INT; - SlObject(NULL, _ai_byte); - } - SQInteger res; - sq_getinteger(vm, index, &res); - if (!test) { - int value = (int)res; - SlArray(&value, 1, SLE_INT32); - } - return true; - } - - case OT_STRING: { - if (!test) { - _ai_sl_byte = SQSL_STRING; - SlObject(NULL, _ai_byte); - } - const SQChar *res; - sq_getstring(vm, index, &res); - /* @bug if a string longer than 512 characters is given to SQ2OTTD, the - * internal buffer overflows. */ - const char *buf = SQ2OTTD(res); - size_t len = strlen(buf) + 1; - if (len >= 255) { - ScriptLog::Error("Maximum string length is 254 chars. No data saved."); - return false; - } - if (!test) { - _ai_sl_byte = (byte)len; - SlObject(NULL, _ai_byte); - SlArray(const_cast(buf), len, SLE_CHAR); - } - return true; - } - - case OT_ARRAY: { - if (!test) { - _ai_sl_byte = SQSL_ARRAY; - SlObject(NULL, _ai_byte); - } - sq_pushnull(vm); - while (SQ_SUCCEEDED(sq_next(vm, index - 1))) { - /* Store the value */ - bool res = SaveObject(vm, -1, max_depth - 1, test); - sq_pop(vm, 2); - if (!res) { - sq_pop(vm, 1); - return false; - } - } - sq_pop(vm, 1); - if (!test) { - _ai_sl_byte = SQSL_ARRAY_TABLE_END; - SlObject(NULL, _ai_byte); - } - return true; - } - - case OT_TABLE: { - if (!test) { - _ai_sl_byte = SQSL_TABLE; - SlObject(NULL, _ai_byte); - } - sq_pushnull(vm); - while (SQ_SUCCEEDED(sq_next(vm, index - 1))) { - /* Store the key + value */ - bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test); - sq_pop(vm, 2); - if (!res) { - sq_pop(vm, 1); - return false; - } - } - sq_pop(vm, 1); - if (!test) { - _ai_sl_byte = SQSL_ARRAY_TABLE_END; - SlObject(NULL, _ai_byte); - } - return true; - } - - case OT_BOOL: { - if (!test) { - _ai_sl_byte = SQSL_BOOL; - SlObject(NULL, _ai_byte); - } - SQBool res; - sq_getbool(vm, index, &res); - if (!test) { - _ai_sl_byte = res ? 1 : 0; - SlObject(NULL, _ai_byte); - } - return true; - } - - case OT_NULL: { - if (!test) { - _ai_sl_byte = SQSL_NULL; - SlObject(NULL, _ai_byte); - } - return true; - } - - default: - ScriptLog::Error("You tried to save an unsupported type. No data saved."); - return false; - } -} - -/* static */ void AIInstance::SaveEmpty() -{ - _ai_sl_byte = 0; - SlObject(NULL, _ai_byte); -} - -void AIInstance::Save() -{ - ScriptObject::ActiveInstance active(this); - - /* Don't save data if the AI didn't start yet or if it crashed. */ - if (this->engine == NULL || this->engine->HasScriptCrashed()) { - SaveEmpty(); - return; - } - - HSQUIRRELVM vm = this->engine->GetVM(); - if (this->is_save_data_on_stack) { - _ai_sl_byte = 1; - SlObject(NULL, _ai_byte); - /* Save the data that was just loaded. */ - SaveObject(vm, -1, AISAVE_MAX_DEPTH, false); - } else if (!this->is_started) { - SaveEmpty(); - return; - } else if (this->engine->MethodExists(*this->instance, "Save")) { - HSQOBJECT savedata; - /* We don't want to be interrupted during the save function. */ - bool backup_allow = ScriptObject::GetAllowDoCommand(); - ScriptObject::SetAllowDoCommand(false); - try { - if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) { - /* The script crashed in the Save function. We can't kill - * it here, but do so in the next AI tick. */ - SaveEmpty(); - this->engine->CrashOccurred(); - return; - } - } catch (Script_FatalError e) { - /* If we don't mark the AI as dead here cleaning up the squirrel - * stack could throw Script_FatalError again. */ - this->is_dead = true; - this->engine->ThrowError(e.GetErrorMessage()); - this->engine->ResumeError(); - SaveEmpty(); - /* We can't kill the AI here, so mark it as crashed (not dead) and - * kill it in the next AI tick. */ - this->is_dead = false; - this->engine->CrashOccurred(); - return; - } - ScriptObject::SetAllowDoCommand(backup_allow); - - if (!sq_istable(savedata)) { - ScriptLog::Error(this->engine->IsSuspended() ? "This AI took too long to Save." : "Save function should return a table."); - SaveEmpty(); - this->engine->CrashOccurred(); - return; - } - sq_pushobject(vm, savedata); - if (SaveObject(vm, -1, AISAVE_MAX_DEPTH, true)) { - _ai_sl_byte = 1; - SlObject(NULL, _ai_byte); - SaveObject(vm, -1, AISAVE_MAX_DEPTH, false); - this->is_save_data_on_stack = true; - } else { - SaveEmpty(); - this->engine->CrashOccurred(); - } - } else { - ScriptLog::Warning("Save function is not implemented"); - _ai_sl_byte = 0; - SlObject(NULL, _ai_byte); - } -} - -void AIInstance::Suspend() -{ - HSQUIRRELVM vm = this->engine->GetVM(); - Squirrel::DecreaseOps(vm, _settings_game.ai.ai_max_opcode_till_suspend); -} - -/* static */ bool AIInstance::LoadObjects(HSQUIRRELVM vm) -{ - SlObject(NULL, _ai_byte); - switch (_ai_sl_byte) { - case SQSL_INT: { - int value; - SlArray(&value, 1, SLE_INT32); - if (vm != NULL) sq_pushinteger(vm, (SQInteger)value); - return true; - } - - case SQSL_STRING: { - SlObject(NULL, _ai_byte); - static char buf[256]; - SlArray(buf, _ai_sl_byte, SLE_CHAR); - if (vm != NULL) sq_pushstring(vm, OTTD2SQ(buf), -1); - return true; - } - - case SQSL_ARRAY: { - if (vm != NULL) sq_newarray(vm, 0); - while (LoadObjects(vm)) { - if (vm != NULL) sq_arrayappend(vm, -2); - /* The value is popped from the stack by squirrel. */ - } - return true; - } - - case SQSL_TABLE: { - if (vm != NULL) sq_newtable(vm); - while (LoadObjects(vm)) { - LoadObjects(vm); - if (vm != NULL) sq_rawset(vm, -3); - /* The key (-2) and value (-1) are popped from the stack by squirrel. */ - } - return true; - } - - case SQSL_BOOL: { - SlObject(NULL, _ai_byte); - if (vm != NULL) sq_pushinteger(vm, (SQBool)(_ai_sl_byte != 0)); - return true; - } - - case SQSL_NULL: { - if (vm != NULL) sq_pushnull(vm); - return true; - } - - case SQSL_ARRAY_TABLE_END: { - return false; - } - - default: NOT_REACHED(); - } -} - -/* static */ void AIInstance::LoadEmpty() -{ - SlObject(NULL, _ai_byte); - /* Check if there was anything saved at all. */ - if (_ai_sl_byte == 0) return; - - LoadObjects(NULL); -} - -void AIInstance::Load(int version) -{ - ScriptObject::ActiveInstance active(this); - - if (this->engine == NULL || version == -1) { - LoadEmpty(); - return; - } - HSQUIRRELVM vm = this->engine->GetVM(); - - SlObject(NULL, _ai_byte); - /* Check if there was anything saved at all. */ - if (_ai_sl_byte == 0) return; - - sq_pushinteger(vm, version); - LoadObjects(vm); - this->is_save_data_on_stack = true; -} - -bool AIInstance::CallLoad() -{ - HSQUIRRELVM vm = this->engine->GetVM(); - /* Is there save data that we should load? */ - if (!this->is_save_data_on_stack) return true; - /* Whatever happens, after CallLoad the savegame data is removed from the stack. */ - this->is_save_data_on_stack = false; - - if (!this->engine->MethodExists(*this->instance, "Load")) { - ScriptLog::Warning("Loading failed: there was data for the AI to load, but the AI does not have a Load() function."); - - /* Pop the savegame data and version. */ - sq_pop(vm, 2); - return true; - } - - /* Go to the instance-root */ - sq_pushobject(vm, *this->instance); - /* Find the function-name inside the script */ - sq_pushstring(vm, OTTD2SQ("Load"), -1); - /* Change the "Load" string in a function pointer */ - sq_get(vm, -2); - /* Push the main instance as "this" object */ - sq_pushobject(vm, *this->instance); - /* Push the version data and savegame data as arguments */ - sq_push(vm, -5); - sq_push(vm, -5); - - /* Call the AI load function. sq_call removes the arguments (but not the - * function pointer) from the stack. */ - 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. */ - sq_pop(vm, 4); - return true; -} - -SQInteger AIInstance::GetOpsTillSuspend() -{ - return this->engine->GetOpsTillSuspend(); -} - -void AIInstance::DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2) -{ - ScriptObject::ActiveInstance active(this); - - ScriptObject::SetLastCommandRes(result.Succeeded()); - - if (result.Failed()) { - ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage())); - } else { - ScriptObject::IncreaseDoCommandCosts(result.GetCost()); - ScriptObject::SetLastCost(result.GetCost()); - } -} - -void AIInstance::InsertEvent(class ScriptEvent *event) -{ - ScriptObject::ActiveInstance active(this); - - ScriptEventController::InsertEvent(event); -} -- cgit v1.2.3-54-g00ecf