From 3da8b5097a4643d531182173df36ca4d3b45a4e2 Mon Sep 17 00:00:00 2001 From: truebrain Date: Tue, 29 Nov 2011 23:21:33 +0000 Subject: [PATCH] (svn r23360) -Codechange: move AIInstance to ScriptInstance, making it reusable by other script API instances --- bin/ai/regression/regression.txt | 2 +- projects/openttd_vs100.vcxproj | 2 + projects/openttd_vs100.vcxproj.filters | 6 + projects/openttd_vs80.vcproj | 8 + projects/openttd_vs90.vcproj | 8 + source.list | 2 + src/ai/ai_instance.cpp | 622 +---------------------- src/ai/ai_instance.hpp | 165 +------ src/ai/api/squirrel_export.sh | 1 - src/script/api/script_bridge.cpp | 8 +- src/script/api/script_controller.hpp | 2 +- src/script/api/script_group.cpp | 2 +- src/script/api/script_object.cpp | 12 +- src/script/api/script_object.hpp | 20 +- src/script/api/script_order.cpp | 4 +- src/script/api/script_sign.cpp | 2 +- src/script/api/script_tunnel.cpp | 8 +- src/script/api/script_vehicle.cpp | 4 +- src/script/script_instance.cpp | 653 +++++++++++++++++++++++++ src/script/script_instance.hpp | 194 ++++++++ src/script/script_suspend.hpp | 2 +- src/script/squirrel.hpp | 2 +- 22 files changed, 918 insertions(+), 811 deletions(-) create mode 100644 src/script/script_instance.cpp create mode 100644 src/script/script_instance.hpp diff --git a/bin/ai/regression/regression.txt b/bin/ai/regression/regression.txt index dc82f90934..41c855814f 100644 --- a/bin/ai/regression/regression.txt +++ b/bin/ai/regression/regression.txt @@ -8852,4 +8852,4 @@ ERROR: IsEnd() is invalid as Begin() is never called -1 > 2147483647: false -2147483648 > 2147483647: false 13725 > -2147483648: true -ERROR: The AI died unexpectedly. +ERROR: The script died unexpectedly. diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj index f784c367bb..e6ffe876ee 100644 --- a/projects/openttd_vs100.vcxproj +++ b/projects/openttd_vs100.vcxproj @@ -791,6 +791,8 @@ + + diff --git a/projects/openttd_vs100.vcxproj.filters b/projects/openttd_vs100.vcxproj.filters index 6855de2909..9ee1f3b7ea 100644 --- a/projects/openttd_vs100.vcxproj.filters +++ b/projects/openttd_vs100.vcxproj.filters @@ -1596,6 +1596,12 @@ Script + + Script + + + Script + Script diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj index 3ab38db6e9..c395f4a1ce 100644 --- a/projects/openttd_vs80.vcproj +++ b/projects/openttd_vs80.vcproj @@ -2470,6 +2470,14 @@ RelativePath=".\..\src\script\script_info.hpp" > + + + + diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj index 23bc274732..439c1b2d7b 100644 --- a/projects/openttd_vs90.vcproj +++ b/projects/openttd_vs90.vcproj @@ -2467,6 +2467,14 @@ RelativePath=".\..\src\script\script_info.hpp" > + + + + diff --git a/source.list b/source.list index b57ef93f93..4ec934717f 100644 --- a/source.list +++ b/source.list @@ -556,6 +556,8 @@ table/water_land.h script/script_fatalerror.hpp script/script_info.cpp script/script_info.hpp +script/script_instance.cpp +script/script_instance.hpp script/script_scanner.cpp script/script_scanner.hpp script/script_storage.hpp diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp index a4a30334f2..bf6af96f3e 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); -} diff --git a/src/ai/ai_instance.hpp b/src/ai/ai_instance.hpp index 60caaefd2a..560155b2c2 100644 --- a/src/ai/ai_instance.hpp +++ b/src/ai/ai_instance.hpp @@ -13,19 +13,12 @@ #define AI_INSTANCE_HPP #include -#include "../script/script_suspend.hpp" +#include "../script/script_instance.hpp" /** Runtime information about an AI like a pointer to the squirrel vm and the current state. */ -class AIInstance { +class AIInstance : public ScriptInstance { public: - friend class ScriptObject; - friend class ScriptController; - - /** - * Create a new AI. - */ AIInstance(); - ~AIInstance(); /** * Initialize the AI and prepare it for its first run. @@ -33,164 +26,16 @@ public: */ void Initialize(class AIInfo *info); - /** - * An AI in multiplayer waits for the server to handle his DoCommand. - * It keeps waiting for this until this function is called. - */ - void Continue(); - - /** - * Run the GameLoop of an AI. - */ - void GameLoop(); - - /** - * Let the VM collect any garbage. - */ - void CollectGarbage() const; - - /** - * Get the storage of this AI. - */ - class ScriptStorage *GetStorage(); - - /** - * Get the log pointer of this AI. - */ - void *GetLogPointer(); - - /** - * Return a true/false reply for a DoCommand. - */ - static void DoCommandReturn(AIInstance *instance); - - /** - * Return a VehicleID reply for a DoCommand. - */ - static void DoCommandReturnVehicleID(AIInstance *instance); - - /** - * Return a SignID reply for a DoCommand. - */ - static void DoCommandReturnSignID(AIInstance *instance); - - /** - * Return a GroupID reply for a DoCommand. - */ - static void DoCommandReturnGroupID(AIInstance *instance); - - /** - * Get the controller attached to the instance. - */ - class ScriptController *GetController() { return controller; } - - /** - * Return the "this AI died" value - */ - inline bool IsDead() const { return this->is_dead; } - - /** - * Call the AI Save function and save all data in the savegame. - */ - void Save(); - - /** - * Don't save any data in the savegame. - */ - static void SaveEmpty(); - - /** - * Load data from a savegame and store it on the stack. - * @param version The version of the AI when saving, or -1 if this was - * not the original AI saving the game. - */ - void Load(int version); - - /** - * Load and discard data from a savegame. - */ - static void LoadEmpty(); - - /** - * Reduces the number of opcodes the AI have left to zero. Unless - * the AI is in a state where it cannot suspend it will be suspended - * for the reminder of the current tick. This function is safe to - * call from within a function called by the AI. - */ - void Suspend(); - - /** - * Get the number of operations the AI can execute before being suspended. - * This function is safe to call from within a function called by the AI. - * @return The number of operations to execute. - */ - SQInteger GetOpsTillSuspend(); - - /** - * DoCommand callback function for all commands executed by AIs. - * @param result The result of the command. - * @param tile The tile on which the command was executed. - * @param p1 p1 as given to DoCommandPInternal. - * @param p2 p2 as given to DoCommandPInternal. - */ - void DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2); - - /** - * Insert an event for this AI. - * @param event The event to insert. - */ - void InsertEvent(class ScriptEvent *event); - private: - class ScriptController *controller; ///< The AI main class. - class ScriptStorage *storage; ///< Some global information for each running AI. - class Squirrel *engine; ///< A wrapper around the squirrel vm. - SQObject *instance; ///< Squirrel-pointer to the AI main class. + const char *versionAPI; ///< Current API used by this script. - bool is_started; ///< Is the AIs constructor executed? - bool is_dead; ///< True if the AI has been stopped. - bool is_save_data_on_stack; ///< Is the save data still on the squirrel stack? - int suspend; ///< The amount of ticks to suspend this AI before it's allowed to continue. - Script_SuspendCallbackProc *callback; ///< Callback that should be called in the next tick the AI runs. - - /** - * Register all API functions to the VM. - */ - void RegisterAPI(); + /* virtual */ void RegisterAPI(); + /* virtual */ void Died(); /** * Load squirrel scripts to emulate an older API. */ bool LoadCompatibilityScripts(const char *api_version); - - /** - * Tell the AI it died. - */ - void Died(); - - /** - * Call the AI Load function if it exists and data was loaded - * from a savegame. - */ - bool CallLoad(); - - /** - * Save one object (int / string / array / table) to the savegame. - * @param vm The virtual machine to get all the data from. - * @param index The index on the squirrel stack of the element to save. - * @param max_depth The maximum depth recursive arrays / tables will be stored - * with before an error is returned. - * @param test If true, don't really store the data but only check if it is - * valid. - * @return True if the saving was successful. - */ - static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test); - - /** - * Load all objects from a savegame. - * @return True if the loading was successful. - */ - static bool LoadObjects(HSQUIRRELVM vm); }; #endif /* AI_INSTANCE_HPP */ diff --git a/src/ai/api/squirrel_export.sh b/src/ai/api/squirrel_export.sh index 725890951a..4cb66a7e2c 100755 --- a/src/ai/api/squirrel_export.sh +++ b/src/ai/api/squirrel_export.sh @@ -91,7 +91,6 @@ echo " /\/\* Register all classes \*\// { print \$0 gsub(\"^.*/\", \"\") - print \" squirrel_register_std(this->engine);\" \$0 # List needs to be registered with squirrel before all List subclasses. print \" SQ${apiuc}List_Register(this->engine);\" \$0 split(\"`grep '^void SQ'${apiuc}'.*_Register(Squirrel \*engine)$' *.hpp.sq | grep -v 'SQ'${apiuc}'List_Register' | sed 's/^.*void //;s/Squirrel \*/this->/;s/$/;/;s/_Register/0000Register/g;' | sort | sed 's/0000Register/_Register/g' | tr -d '\r' | tr '\n' ' '`\", regs, \" \") diff --git a/src/script/api/script_bridge.cpp b/src/script/api/script_bridge.cpp index 27da5be95c..7f6c2ad632 100644 --- a/src/script/api/script_bridge.cpp +++ b/src/script/api/script_bridge.cpp @@ -39,10 +39,10 @@ * Helper function to connect a just built bridge to nearby roads. * @param instance The AI we have to built the road for. */ -static void _DoCommandReturnBuildBridge2(class AIInstance *instance) +static void _DoCommandReturnBuildBridge2(class ScriptInstance *instance) { if (!ScriptBridge::_BuildBridgeRoad2()) { - AIInstance::DoCommandReturn(instance); + ScriptInstance::DoCommandReturn(instance); return; } @@ -55,10 +55,10 @@ static void _DoCommandReturnBuildBridge2(class AIInstance *instance) * Helper function to connect a just built bridge to nearby roads. * @param instance The AI we have to built the road for. */ -static void _DoCommandReturnBuildBridge1(class AIInstance *instance) +static void _DoCommandReturnBuildBridge1(class ScriptInstance *instance) { if (!ScriptBridge::_BuildBridgeRoad1()) { - AIInstance::DoCommandReturn(instance); + ScriptInstance::DoCommandReturn(instance); return; } diff --git a/src/script/api/script_controller.hpp b/src/script/api/script_controller.hpp index 00993a6ff1..a027c903f3 100644 --- a/src/script/api/script_controller.hpp +++ b/src/script/api/script_controller.hpp @@ -22,7 +22,7 @@ */ class ScriptController { friend class AIScanner; - friend class AIInstance; + friend class ScriptInstance; public: /** diff --git a/src/script/api/script_group.cpp b/src/script/api/script_group.cpp index 14da9e1647..026e101d96 100644 --- a/src/script/api/script_group.cpp +++ b/src/script/api/script_group.cpp @@ -29,7 +29,7 @@ /* static */ ScriptGroup::GroupID ScriptGroup::CreateGroup(ScriptVehicle::VehicleType vehicle_type) { - if (!ScriptObject::DoCommand(0, (::VehicleType)vehicle_type, 0, CMD_CREATE_GROUP, NULL, &AIInstance::DoCommandReturnGroupID)) return GROUP_INVALID; + if (!ScriptObject::DoCommand(0, (::VehicleType)vehicle_type, 0, CMD_CREATE_GROUP, NULL, &ScriptInstance::DoCommandReturnGroupID)) return GROUP_INVALID; /* In case of test-mode, we return GroupID 0 */ return (ScriptGroup::GroupID)0; diff --git a/src/script/api/script_object.cpp b/src/script/api/script_object.cpp index 8cc9263c59..5afe76beb3 100644 --- a/src/script/api/script_object.cpp +++ b/src/script/api/script_object.cpp @@ -22,7 +22,7 @@ #include "script_error.hpp" /** - * Get the storage associated with the current AIInstance. + * Get the storage associated with the current ScriptInstance. * @return The storage. */ static ScriptStorage *GetStorage() @@ -31,9 +31,9 @@ static ScriptStorage *GetStorage() } -/* static */ AIInstance *ScriptObject::ActiveInstance::active = NULL; +/* static */ ScriptInstance *ScriptObject::ActiveInstance::active = NULL; -ScriptObject::ActiveInstance::ActiveInstance(AIInstance *instance) +ScriptObject::ActiveInstance::ActiveInstance(ScriptInstance *instance) { this->last_active = ScriptObject::ActiveInstance::active; ScriptObject::ActiveInstance::active = instance; @@ -44,7 +44,7 @@ ScriptObject::ActiveInstance::~ActiveInstance() ScriptObject::ActiveInstance::active = this->last_active; } -/* static */ AIInstance *ScriptObject::GetActiveInstance() +/* static */ ScriptInstance *ScriptObject::GetActiveInstance() { assert(ScriptObject::ActiveInstance::active != NULL); return ScriptObject::ActiveInstance::active; @@ -225,14 +225,14 @@ ScriptObject::ActiveInstance::~ActiveInstance() return GetStorage()->callback_value[index]; } -/* static */ bool ScriptObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, AISuspendCallbackProc *callback) +/* static */ bool ScriptObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, Script_SuspendCallbackProc *callback) { if (!ScriptObject::CanSuspend()) { throw Script_FatalError("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), Load(), and any valuator."); } /* Set the default callback to return a true/false result of the DoCommand */ - if (callback == NULL) callback = &AIInstance::DoCommandReturn; + if (callback == NULL) callback = &ScriptInstance::DoCommandReturn; /* Are we only interested in the estimate costs? */ bool estimate_only = GetDoCommandMode() != NULL && !GetDoCommandMode()(); diff --git a/src/script/api/script_object.hpp b/src/script/api/script_object.hpp index b38757cc9b..6e6e96d8b4 100644 --- a/src/script/api/script_object.hpp +++ b/src/script/api/script_object.hpp @@ -17,11 +17,7 @@ #include "../../rail_type.h" #include "script_types.hpp" - -/** - * The callback function when an AI suspends. - */ -typedef void (AISuspendCallbackProc)(class AIInstance *instance); +#include "../script_suspend.hpp" /** * The callback function for Mode-classes. @@ -30,12 +26,12 @@ typedef bool (ScriptModeProc)(); /** * Uper-parent object of all API classes. You should never use this class in - * your AI, as it doesn't publish any public functions. It is used + * your script, as it doesn't publish any public functions. It is used * internally to have a common place to handle general things, like internal * command processing, and command-validation checks. */ class ScriptObject : public SimpleCountedObject { -friend class AIInstance; +friend class ScriptInstance; #ifndef DOXYGEN_AI_DOCS protected: /** @@ -47,12 +43,12 @@ protected: class ActiveInstance { friend class ScriptObject; public: - ActiveInstance(AIInstance *instance); + ActiveInstance(ScriptInstance *instance); ~ActiveInstance(); private: - AIInstance *last_active; ///< The active instance before we go instantiated. + ScriptInstance *last_active; ///< The active instance before we go instantiated. - static AIInstance *active; ///< The global current active instance. + static ScriptInstance *active; ///< The global current active instance. }; public: @@ -66,13 +62,13 @@ public: * Get the currently active instance. * @return The instance. */ - static class AIInstance *GetActiveInstance(); + static class ScriptInstance *GetActiveInstance(); protected: /** * Executes a raw DoCommand for the AI. */ - static bool DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text = NULL, AISuspendCallbackProc *callback = NULL); + static bool DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text = NULL, Script_SuspendCallbackProc *callback = NULL); /** * Sets the DoCommand costs counter to a value. diff --git a/src/script/api/script_order.cpp b/src/script/api/script_order.cpp index 7a06f68e3d..2e72dd350c 100644 --- a/src/script/api/script_order.cpp +++ b/src/script/api/script_order.cpp @@ -546,10 +546,10 @@ static int ScriptOrderPositionToRealOrderPosition(VehicleID vehicle_id, ScriptOr * between the wanted and the current order. * @param instance The AI we are doing the callback for. */ -static void _DoCommandReturnSetOrderFlags(class AIInstance *instance) +static void _DoCommandReturnSetOrderFlags(class ScriptInstance *instance) { ScriptObject::SetLastCommandRes(ScriptOrder::_SetOrderFlags()); - AIInstance::DoCommandReturn(instance); + ScriptInstance::DoCommandReturn(instance); } /* static */ bool ScriptOrder::_SetOrderFlags() diff --git a/src/script/api/script_sign.cpp b/src/script/api/script_sign.cpp index 9af5a2fa7b..85303b034a 100644 --- a/src/script/api/script_sign.cpp +++ b/src/script/api/script_sign.cpp @@ -69,7 +69,7 @@ EnforcePrecondition(INVALID_SIGN, !::StrEmpty(text)); EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_SIGN_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG); - if (!ScriptObject::DoCommand(location, 0, 0, CMD_PLACE_SIGN, text, &AIInstance::DoCommandReturnSignID)) return INVALID_SIGN; + if (!ScriptObject::DoCommand(location, 0, 0, CMD_PLACE_SIGN, text, &ScriptInstance::DoCommandReturnSignID)) return INVALID_SIGN; /* In case of test-mode, we return SignID 0 */ return 0; diff --git a/src/script/api/script_tunnel.cpp b/src/script/api/script_tunnel.cpp index 98407bf594..e6b7b45cf3 100644 --- a/src/script/api/script_tunnel.cpp +++ b/src/script/api/script_tunnel.cpp @@ -50,10 +50,10 @@ * Helper function to connect a just built tunnel to nearby roads. * @param instance The AI we have to built the road for. */ -static void _DoCommandReturnBuildTunnel2(class AIInstance *instance) +static void _DoCommandReturnBuildTunnel2(class ScriptInstance *instance) { if (!ScriptTunnel::_BuildTunnelRoad2()) { - AIInstance::DoCommandReturn(instance); + ScriptInstance::DoCommandReturn(instance); return; } @@ -66,10 +66,10 @@ static void _DoCommandReturnBuildTunnel2(class AIInstance *instance) * Helper function to connect a just built tunnel to nearby roads. * @param instance The AI we have to built the road for. */ -static void _DoCommandReturnBuildTunnel1(class AIInstance *instance) +static void _DoCommandReturnBuildTunnel1(class ScriptInstance *instance) { if (!ScriptTunnel::_BuildTunnelRoad1()) { - AIInstance::DoCommandReturn(instance); + ScriptInstance::DoCommandReturn(instance); return; } diff --git a/src/script/api/script_vehicle.cpp b/src/script/api/script_vehicle.cpp index 216d4cf60a..ff240200b5 100644 --- a/src/script/api/script_vehicle.cpp +++ b/src/script/api/script_vehicle.cpp @@ -60,7 +60,7 @@ EnforcePreconditionCustomError(VEHICLE_INVALID, !ScriptGameSettings::IsDisabledVehicleType((ScriptVehicle::VehicleType)type), ScriptVehicle::ERR_VEHICLE_BUILD_DISABLED); - if (!ScriptObject::DoCommand(depot, engine_id, 0, ::GetCmdBuildVeh(type), NULL, &AIInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID; + if (!ScriptObject::DoCommand(depot, engine_id, 0, ::GetCmdBuildVeh(type), NULL, &ScriptInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID; /* In case of test-mode, we return VehicleID 0 */ return 0; @@ -70,7 +70,7 @@ { EnforcePrecondition(false, IsValidVehicle(vehicle_id)); - if (!ScriptObject::DoCommand(depot, vehicle_id, share_orders, CMD_CLONE_VEHICLE, NULL, &AIInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID; + if (!ScriptObject::DoCommand(depot, vehicle_id, share_orders, CMD_CLONE_VEHICLE, NULL, &ScriptInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID; /* In case of test-mode, we return VehicleID 0 */ return 0; diff --git a/src/script/script_instance.cpp b/src/script/script_instance.cpp new file mode 100644 index 0000000000..fa69932ced --- /dev/null +++ b/src/script/script_instance.cpp @@ -0,0 +1,653 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file script_instance.cpp Implementation of ScriptInstance. */ + +#include "../stdafx.h" +#include "../debug.h" +#include "../saveload/saveload.h" +#include "../gui.h" + +#include "../script/squirrel_class.hpp" + +#include "script_fatalerror.hpp" +#include "script_storage.hpp" +#include "script_instance.hpp" + +#include "api/script_controller.hpp" +#include "api/script_error.hpp" +#include "api/script_event.hpp" +#include "api/script_log.hpp" + +#include "../company_base.h" +#include "../company_func.h" +#include "../fileio_func.h" + +/** The maximum number of operations for saving or loading the data of a script. */ +static const int MAX_SL_OPS = 100000; +/** The maximum number of operations for initial start of a script. */ +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 a script 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)); +} + +ScriptInstance::ScriptInstance(const char *APIName) : + engine(NULL), + controller(NULL), + storage(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(APIName); + this->engine->SetPrintFunction(&PrintFunc); +} + +void ScriptInstance::Initialize(const char *main_script, const char *instance_name) +{ + ScriptObject::ActiveInstance active(this); + + this->controller = new ScriptController(); + + /* Register the API functions and classes */ + this->engine->SetGlobalPointer(this->engine); + this->RegisterAPI(); + + try { + ScriptObject::SetAllowDoCommand(false); + /* Load and execute the script for this script */ + 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 script 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(instance_name, 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(); + } +} + +void ScriptInstance::RegisterAPI() +{ + squirrel_register_std(this->engine); +} + +ScriptInstance::~ScriptInstance() +{ + 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); +} + +void ScriptInstance::Continue() +{ + assert(this->suspend < 0); + this->suspend = -this->suspend - 1; +} + +void ScriptInstance::Died() +{ + DEBUG(ai, 0, "The script died unexpectedly."); + this->is_dead = true; + + if (this->instance != NULL) this->engine->ReleaseObject(this->instance); + delete this->engine; + this->instance = NULL; + this->engine = NULL; +} + +void ScriptInstance::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 script took too long to initialize. Script is not started."); + this->Died(); + return; + } + } + if (!this->CallLoad() || this->engine->IsSuspended()) { + if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started."); + this->Died(); + return; + } + ScriptObject::SetAllowDoCommand(true); + /* Start the script 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 ScriptInstance::CollectGarbage() const +{ + if (this->is_started && !this->IsDead()) this->engine->CollectGarbage(); +} + +/* static */ void ScriptInstance::DoCommandReturn(ScriptInstance *instance) +{ + instance->engine->InsertResult(ScriptObject::GetLastCommandRes()); +} + +/* static */ void ScriptInstance::DoCommandReturnVehicleID(ScriptInstance *instance) +{ + instance->engine->InsertResult(ScriptObject::GetNewVehicleID()); +} + +/* static */ void ScriptInstance::DoCommandReturnSignID(ScriptInstance *instance) +{ + instance->engine->InsertResult(ScriptObject::GetNewSignID()); +} + +/* static */ void ScriptInstance::DoCommandReturnGroupID(ScriptInstance *instance) +{ + instance->engine->InsertResult(ScriptObject::GetNewGroupID()); +} + +ScriptStorage *ScriptInstance::GetStorage() +{ + return this->storage; +} + +void *ScriptInstance::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 _script_sl_byte; ///< Used as source/target by the script saveload code to store/load a single byte. + +/** SaveLoad array that saves/loads exactly one byte. */ +static const SaveLoad _script_byte[] = { + SLEG_VAR(_script_sl_byte, SLE_UINT8), + SLE_END() +}; + +static const uint SCRIPTSAVE_MAX_DEPTH = 25; ///< The maximum recursive depth for items stored in the savegame. + +/* static */ bool ScriptInstance::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) { + _script_sl_byte = SQSL_INT; + SlObject(NULL, _script_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) { + _script_sl_byte = SQSL_STRING; + SlObject(NULL, _script_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) { + _script_sl_byte = (byte)len; + SlObject(NULL, _script_byte); + SlArray(const_cast(buf), len, SLE_CHAR); + } + return true; + } + + case OT_ARRAY: { + if (!test) { + _script_sl_byte = SQSL_ARRAY; + SlObject(NULL, _script_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) { + _script_sl_byte = SQSL_ARRAY_TABLE_END; + SlObject(NULL, _script_byte); + } + return true; + } + + case OT_TABLE: { + if (!test) { + _script_sl_byte = SQSL_TABLE; + SlObject(NULL, _script_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) { + _script_sl_byte = SQSL_ARRAY_TABLE_END; + SlObject(NULL, _script_byte); + } + return true; + } + + case OT_BOOL: { + if (!test) { + _script_sl_byte = SQSL_BOOL; + SlObject(NULL, _script_byte); + } + SQBool res; + sq_getbool(vm, index, &res); + if (!test) { + _script_sl_byte = res ? 1 : 0; + SlObject(NULL, _script_byte); + } + return true; + } + + case OT_NULL: { + if (!test) { + _script_sl_byte = SQSL_NULL; + SlObject(NULL, _script_byte); + } + return true; + } + + default: + ScriptLog::Error("You tried to save an unsupported type. No data saved."); + return false; + } +} + +/* static */ void ScriptInstance::SaveEmpty() +{ + _script_sl_byte = 0; + SlObject(NULL, _script_byte); +} + +void ScriptInstance::Save() +{ + ScriptObject::ActiveInstance active(this); + + /* Don't save data if the script 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) { + _script_sl_byte = 1; + SlObject(NULL, _script_byte); + /* Save the data that was just loaded. */ + SaveObject(vm, -1, SCRIPTSAVE_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 script tick. */ + SaveEmpty(); + this->engine->CrashOccurred(); + return; + } + } catch (Script_FatalError e) { + /* If we don't mark the script 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 script here, so mark it as crashed (not dead) and + * kill it in the next script tick. */ + this->is_dead = false; + this->engine->CrashOccurred(); + return; + } + ScriptObject::SetAllowDoCommand(backup_allow); + + if (!sq_istable(savedata)) { + ScriptLog::Error(this->engine->IsSuspended() ? "This script took too long to Save." : "Save function should return a table."); + SaveEmpty(); + this->engine->CrashOccurred(); + return; + } + sq_pushobject(vm, savedata); + if (SaveObject(vm, -1, SCRIPTSAVE_MAX_DEPTH, true)) { + _script_sl_byte = 1; + SlObject(NULL, _script_byte); + SaveObject(vm, -1, SCRIPTSAVE_MAX_DEPTH, false); + this->is_save_data_on_stack = true; + } else { + SaveEmpty(); + this->engine->CrashOccurred(); + } + } else { + ScriptLog::Warning("Save function is not implemented"); + _script_sl_byte = 0; + SlObject(NULL, _script_byte); + } +} + +void ScriptInstance::Suspend() +{ + HSQUIRRELVM vm = this->engine->GetVM(); + Squirrel::DecreaseOps(vm, _settings_game.ai.ai_max_opcode_till_suspend); +} + +/* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm) +{ + SlObject(NULL, _script_byte); + switch (_script_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, _script_byte); + static char buf[256]; + SlArray(buf, _script_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, _script_byte); + if (vm != NULL) sq_pushinteger(vm, (SQBool)(_script_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 ScriptInstance::LoadEmpty() +{ + SlObject(NULL, _script_byte); + /* Check if there was anything saved at all. */ + if (_script_sl_byte == 0) return; + + LoadObjects(NULL); +} + +void ScriptInstance::Load(int version) +{ + ScriptObject::ActiveInstance active(this); + + if (this->engine == NULL || version == -1) { + LoadEmpty(); + return; + } + HSQUIRRELVM vm = this->engine->GetVM(); + + SlObject(NULL, _script_byte); + /* Check if there was anything saved at all. */ + if (_script_sl_byte == 0) return; + + sq_pushinteger(vm, version); + LoadObjects(vm); + this->is_save_data_on_stack = true; +} + +bool ScriptInstance::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 script to load, but the script 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 script 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 ScriptInstance::GetOpsTillSuspend() +{ + return this->engine->GetOpsTillSuspend(); +} + +void ScriptInstance::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 ScriptInstance::InsertEvent(class ScriptEvent *event) +{ + ScriptObject::ActiveInstance active(this); + + ScriptEventController::InsertEvent(event); +} diff --git a/src/script/script_instance.hpp b/src/script/script_instance.hpp new file mode 100644 index 0000000000..2322189127 --- /dev/null +++ b/src/script/script_instance.hpp @@ -0,0 +1,194 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file script_instance.hpp The ScriptInstance tracks a script. */ + +#ifndef SCRIPT_INSTANCE_HPP +#define SCRIPT_INSTANCE_HPP + +#include +#include "script_suspend.hpp" + +/** Runtime information about a script like a pointer to the squirrel vm and the current state. */ +class ScriptInstance { +public: + friend class ScriptObject; + friend class ScriptController; + + /** + * Create a new script. + */ + ScriptInstance(const char *APIName); + virtual ~ScriptInstance(); + + /** + * Initialize the script and prepare it for its first run. + * @param main_script The name of the script to load. + * @param instance_name The name of the instance out of the script to load. + */ + void Initialize(const char *main_script, const char *instance_name); + + /** + * A script in multiplayer waits for the server to handle his DoCommand. + * It keeps waiting for this until this function is called. + */ + void Continue(); + + /** + * Run the GameLoop of a script. + */ + void GameLoop(); + + /** + * Let the VM collect any garbage. + */ + void CollectGarbage() const; + + /** + * Get the storage of this script. + */ + class ScriptStorage *GetStorage(); + + /** + * Get the log pointer of this script. + */ + void *GetLogPointer(); + + /** + * Return a true/false reply for a DoCommand. + */ + static void DoCommandReturn(ScriptInstance *instance); + + /** + * Return a VehicleID reply for a DoCommand. + */ + static void DoCommandReturnVehicleID(ScriptInstance *instance); + + /** + * Return a SignID reply for a DoCommand. + */ + static void DoCommandReturnSignID(ScriptInstance *instance); + + /** + * Return a GroupID reply for a DoCommand. + */ + static void DoCommandReturnGroupID(ScriptInstance *instance); + + /** + * Get the controller attached to the instance. + */ + class ScriptController *GetController() { return controller; } + + /** + * Return the "this script died" value + */ + inline bool IsDead() const { return this->is_dead; } + + /** + * Call the script Save function and save all data in the savegame. + */ + void Save(); + + /** + * Don't save any data in the savegame. + */ + static void SaveEmpty(); + + /** + * Load data from a savegame and store it on the stack. + * @param version The version of the script when saving, or -1 if this was + * not the original script saving the game. + */ + void Load(int version); + + /** + * Load and discard data from a savegame. + */ + static void LoadEmpty(); + + /** + * Reduces the number of opcodes the script have left to zero. Unless + * the script is in a state where it cannot suspend it will be suspended + * for the reminder of the current tick. This function is safe to + * call from within a function called by the script. + */ + void Suspend(); + + /** + * Get the number of operations the script can execute before being suspended. + * This function is safe to call from within a function called by the script. + * @return The number of operations to execute. + */ + SQInteger GetOpsTillSuspend(); + + /** + * DoCommand callback function for all commands executed by scripts. + * @param result The result of the command. + * @param tile The tile on which the command was executed. + * @param p1 p1 as given to DoCommandPInternal. + * @param p2 p2 as given to DoCommandPInternal. + */ + void DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2); + + /** + * Insert an event for this script. + * @param event The event to insert. + */ + void InsertEvent(class ScriptEvent *event); + +protected: + class Squirrel *engine; ///< A wrapper around the squirrel vm. + + /** + * Register all API functions to the VM. + */ + virtual void RegisterAPI(); + + /** + * Tell the script it died. + */ + virtual void Died(); + +private: + class ScriptController *controller; ///< The script main class. + class ScriptStorage *storage; ///< Some global information for each running script. + SQObject *instance; ///< Squirrel-pointer to the script main class. + + bool is_started; ///< Is the scripts constructor executed? + 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? + int suspend; ///< The amount of ticks to suspend this script before it's allowed to continue. + Script_SuspendCallbackProc *callback; ///< Callback that should be called in the next tick the script runs. + + /** + * Call the script Load function if it exists and data was loaded + * from a savegame. + */ + bool CallLoad(); + + /** + * Save one object (int / string / array / table) to the savegame. + * @param vm The virtual machine to get all the data from. + * @param index The index on the squirrel stack of the element to save. + * @param max_depth The maximum depth recursive arrays / tables will be stored + * with before an error is returned. + * @param test If true, don't really store the data but only check if it is + * valid. + * @return True if the saving was successful. + */ + static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test); + + /** + * Load all objects from a savegame. + * @return True if the loading was successful. + */ + static bool LoadObjects(HSQUIRRELVM vm); +}; + +#endif /* SCRIPT_INSTANCE_HPP */ diff --git a/src/script/script_suspend.hpp b/src/script/script_suspend.hpp index a67f3f514c..9f8a1513c9 100644 --- a/src/script/script_suspend.hpp +++ b/src/script/script_suspend.hpp @@ -15,7 +15,7 @@ /** * The callback function when a script suspends. */ -typedef void (Script_SuspendCallbackProc)(class AIInstance *instance); +typedef void (Script_SuspendCallbackProc)(class ScriptInstance *instance); /** * A throw-class that is given when the script wants to suspend. diff --git a/src/script/squirrel.hpp b/src/script/squirrel.hpp index 6009bce526..e8d6de0709 100644 --- a/src/script/squirrel.hpp +++ b/src/script/squirrel.hpp @@ -68,7 +68,7 @@ protected: public: friend class AIScanner; - friend class AIInstance; + friend class ScriptInstance; friend class ScriptController; friend void squirrel_register_std(Squirrel *engine);