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/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 +- 13 files changed, 878 insertions(+), 35 deletions(-) create mode 100644 src/script/script_instance.cpp create mode 100644 src/script/script_instance.hpp (limited to 'src/script') diff --git a/src/script/api/script_bridge.cpp b/src/script/api/script_bridge.cpp index 27da5be95..7f6c2ad63 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 00993a6ff..a027c903f 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 14da9e164..026e101d9 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 8cc9263c5..5afe76beb 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 b38757cc9..6e6e96d8b 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 7a06f68e3..2e72dd350 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 9af5a2fa7..85303b034 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 98407bf59..e6b7b45cf 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 216d4cf60..ff240200b 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 000000000..fa69932ce --- /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 000000000..232218912 --- /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 a67f3f514..9f8a1513c 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 6009bce52..e8d6de070 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); -- cgit v1.2.3-54-g00ecf