summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortruebrain <truebrain@openttd.org>2011-11-29 23:21:33 +0000
committertruebrain <truebrain@openttd.org>2011-11-29 23:21:33 +0000
commit3da8b5097a4643d531182173df36ca4d3b45a4e2 (patch)
treef310e6fd8a61909b60111f6b2e906e0ab6ff8231 /src
parent75c4bd280a720592ec4df26efbedd9df5baa2d8f (diff)
downloadopenttd-3da8b5097a4643d531182173df36ca4d3b45a4e2.tar.xz
(svn r23360) -Codechange: move AIInstance to ScriptInstance, making it reusable by other script API instances
Diffstat (limited to 'src')
-rw-r--r--src/ai/ai_instance.cpp622
-rw-r--r--src/ai/ai_instance.hpp165
-rwxr-xr-xsrc/ai/api/squirrel_export.sh1
-rw-r--r--src/script/api/script_bridge.cpp8
-rw-r--r--src/script/api/script_controller.hpp2
-rw-r--r--src/script/api/script_group.cpp2
-rw-r--r--src/script/api/script_object.cpp12
-rw-r--r--src/script/api/script_object.hpp20
-rw-r--r--src/script/api/script_order.cpp4
-rw-r--r--src/script/api/script_sign.cpp2
-rw-r--r--src/script/api/script_tunnel.cpp8
-rw-r--r--src/script/api/script_vehicle.cpp4
-rw-r--r--src/script/script_instance.cpp653
-rw-r--r--src/script/script_instance.hpp194
-rw-r--r--src/script/script_suspend.hpp2
-rw-r--r--src/script/squirrel.hpp2
16 files changed, 891 insertions, 810 deletions
diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp
index a4a30334f..bf6af96f3 100644
--- a/src/ai/ai_instance.cpp
+++ b/src/ai/ai_instance.cpp
@@ -80,105 +80,25 @@
#include "../company_func.h"
#include "../fileio_func.h"
-/** The maximum number of operations for saving or loading the data of an AI. */
-static const int MAX_SL_OPS = 100000;
-/** The maximum number of operations for initial start of an AI. */
-static const int MAX_CONSTRUCTOR_OPS = 100000;
-
-ScriptStorage::~ScriptStorage()
-{
- /* Free our pointers */
- if (event_data != NULL) ScriptEventController::FreeEventPointer();
- if (log_data != NULL) ScriptLog::FreeLogPointer();
-}
-
-/**
- * Callback called by squirrel when an AI uses "print" and for error messages.
- * @param error_msg Is this an error message?
- * @param message The actual message text.
- */
-static void PrintFunc(bool error_msg, const SQChar *message)
-{
- /* Convert to OpenTTD internal capable string */
- ScriptController::Print(error_msg, SQ2OTTD(message));
-}
-
AIInstance::AIInstance() :
- controller(NULL),
- storage(NULL),
- engine(NULL),
- instance(NULL),
- is_started(false),
- is_dead(false),
- is_save_data_on_stack(false),
- suspend(0),
- callback(NULL)
-{
- this->storage = new ScriptStorage();
- this->engine = new Squirrel("AI");
- this->engine->SetPrintFunction(&PrintFunc);
-}
+ ScriptInstance("AI")
+{}
void AIInstance::Initialize(AIInfo *info)
{
- ScriptObject::ActiveInstance active(this);
-
- this->controller = new ScriptController();
+ this->versionAPI = info->GetAPIVersion();
/* Register the AIController (including the "import" command) */
SQAIController_Register(this->engine);
- /* Register the API functions and classes */
- this->RegisterAPI();
-
- if (!this->LoadCompatibilityScripts(info->GetAPIVersion())) {
- this->Died();
- return;
- }
-
- try {
- ScriptObject::SetAllowDoCommand(false);
- /* Load and execute the script for this AI */
- const char *main_script = info->GetMainScript();
- if (strcmp(main_script, "%_dummy") == 0) {
- extern void AI_CreateAIDummy(HSQUIRRELVM vm);
- AI_CreateAIDummy(this->engine->GetVM());
- } else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) {
- if (this->engine->IsSuspended()) ScriptLog::Error("This AI took too long to load script. AI is not started.");
- this->Died();
- return;
- }
-
- /* Create the main-class */
- this->instance = MallocT<SQObject>(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<char *>(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 60caaefd2..560155b2c 100644
--- a/src/ai/ai_instance.hpp
+++ b/src/ai/ai_instance.hpp
@@ -13,19 +13,12 @@
#define AI_INSTANCE_HPP
#include <squirrel.h>
-#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 725890951..4cb66a7e2 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 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @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<SQObject>(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<char *>(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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file script_instance.hpp The ScriptInstance tracks a script. */
+
+#ifndef SCRIPT_INSTANCE_HPP
+#define SCRIPT_INSTANCE_HPP
+
+#include <squirrel.h>
+#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);