diff --git a/src/script/api/CMakeLists.txt b/src/script/api/CMakeLists.txt index 0cab134930..f41efd068c 100644 --- a/src/script/api/CMakeLists.txt +++ b/src/script/api/CMakeLists.txt @@ -146,6 +146,7 @@ add_files( script_accounting.hpp script_admin.hpp script_airport.hpp + script_asyncmode.hpp script_base.hpp script_basestation.hpp script_bridge.hpp @@ -219,6 +220,7 @@ add_files( script_accounting.cpp script_admin.cpp script_airport.cpp + script_asyncmode.cpp script_base.cpp script_basestation.cpp script_bridge.cpp diff --git a/src/script/api/script_asyncmode.cpp b/src/script/api/script_asyncmode.cpp new file mode 100644 index 0000000000..afad59187d --- /dev/null +++ b/src/script/api/script_asyncmode.cpp @@ -0,0 +1,61 @@ +/* + * 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_asyncmode.cpp Implementation of ScriptAsyncMode. */ + +#include "../../stdafx.h" +#include "script_asyncmode.hpp" +#include "../script_instance.hpp" +#include "../script_fatalerror.hpp" + +#include "../../safeguards.h" + +bool ScriptAsyncMode::AsyncModeProc() +{ + /* In async mode we only return 'true', telling the DoCommand it + * should stop run the command in asynchronous/fire-and-forget mode. */ + return true; +} + +bool ScriptAsyncMode::NonAsyncModeProc() +{ + /* In non-async mode we only return 'false', normal operation. */ + return false; +} + +ScriptAsyncMode::ScriptAsyncMode(HSQUIRRELVM vm) +{ + int nparam = sq_gettop(vm) - 1; + if (nparam < 1) { + throw sq_throwerror(vm, "You need to pass a boolean to the constructor"); + } + + SQBool sqasync; + if (SQ_FAILED(sq_getbool(vm, 2, &sqasync))) { + throw sq_throwerror(vm, "Argument must be a boolean"); + } + + this->last_mode = this->GetDoCommandMode(); + this->last_instance = this->GetDoCommandModeInstance(); + + this->SetDoCommandAsyncMode(sqasync ? &ScriptAsyncMode::AsyncModeProc : &ScriptAsyncMode::NonAsyncModeProc, this); +} + +void ScriptAsyncMode::FinalRelease() +{ + if (this->GetDoCommandAsyncModeInstance() != this) { + /* Ignore this error if the script already died. */ + if (!ScriptObject::GetActiveInstance()->IsDead()) { + throw Script_FatalError("Asyncmode object was removed while it was not the latest *Mode object created."); + } + } +} + +ScriptAsyncMode::~ScriptAsyncMode() +{ + this->SetDoCommandAsyncMode(this->last_mode, this->last_instance); +} diff --git a/src/script/api/script_asyncmode.hpp b/src/script/api/script_asyncmode.hpp new file mode 100644 index 0000000000..ba8014b86d --- /dev/null +++ b/src/script/api/script_asyncmode.hpp @@ -0,0 +1,63 @@ +/* + * 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_asyncmode.hpp Switch the script instance to Async Mode. */ + +#ifndef SCRIPT_ASYNCMODE_HPP +#define SCRIPT_ASYNCMODE_HPP + +#include "script_object.hpp" + +/** + * Class to switch current mode to Async Mode. + * If you create an instance of this class, the mode will be switched to + * either Asynchronous or Non-Asynchronous mode. + * The original mode is stored and recovered from when ever the instance is destroyed. + * In Asynchronous mode all the commands you execute are queued for later execution. The + * system checks if it would be able to execute your requests, and returns what + * the cost would be. The actual cost and whether the command succeeded when the command + * is eventually executed may differ from what was reported to the script. + * @api game + */ +class ScriptAsyncMode : public ScriptObject { +private: + ScriptAsyncModeProc *last_mode; ///< The previous mode we were in. + ScriptObject *last_instance; ///< The previous instance of the mode. + +protected: + static bool AsyncModeProc(); + static bool NonAsyncModeProc(); + +public: +#ifndef DOXYGEN_API + /** + * The constructor wrapper from Squirrel. + */ + ScriptAsyncMode(HSQUIRRELVM vm); +#else + /** + * Creating instance of this class switches the build mode to Asynchronous or Non-Asynchronous (normal). + * @note When the instance is destroyed, it restores the mode that was + * current when the instance was created! + * @param asynchronous Whether the new mode should be Asynchronous, if true, or Non-Asynchronous, if false. + */ + ScriptAsyncMode(bool asynchronous); +#endif /* DOXYGEN_API */ + + /** + * Destroying this instance resets the asynchronous mode to the mode it was + * in when the instance was created. + */ + ~ScriptAsyncMode(); + + /** + * @api -all + */ + virtual void FinalRelease(); +}; + +#endif /* SCRIPT_ASYNCMODE_HPP */ diff --git a/src/script/api/script_object.cpp b/src/script/api/script_object.cpp index 0f78464432..8e3a684240 100644 --- a/src/script/api/script_object.cpp +++ b/src/script/api/script_object.cpp @@ -82,6 +82,22 @@ ScriptObject::ActiveInstance::~ActiveInstance() return GetStorage()->mode_instance; } +/* static */ void ScriptObject::SetDoCommandAsyncMode(ScriptAsyncModeProc *proc, ScriptObject *instance) +{ + GetStorage()->async_mode = proc; + GetStorage()->async_mode_instance = instance; +} + +/* static */ ScriptAsyncModeProc *ScriptObject::GetDoCommandAsyncMode() +{ + return GetStorage()->async_mode; +} + +/* static */ ScriptObject *ScriptObject::GetDoCommandAsyncModeInstance() +{ + return GetStorage()->async_mode_instance; +} + /* static */ void ScriptObject::SetLastCommand(const CommandDataBuffer &data, Commands cmd) { ScriptStorage *s = GetStorage(); @@ -239,7 +255,7 @@ ScriptObject::ActiveInstance::~ActiveInstance() return ScriptObject::GetActiveInstance()->GetDoCommandCallback(); } -std::tuple ScriptObject::DoCommandPrep() +std::tuple ScriptObject::DoCommandPrep() { if (!ScriptObject::CanSuspend()) { throw Script_FatalError("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), Load(), and any valuator."); @@ -248,17 +264,20 @@ std::tuple ScriptObject::DoCommandPrep() /* Are we only interested in the estimate costs? */ bool estimate_only = GetDoCommandMode() != nullptr && !GetDoCommandMode()(); + /* Should the command be executed asynchronously? */ + bool asynchronous = GetDoCommandAsyncMode() != nullptr && GetDoCommandAsyncMode()(); + bool networking = _networking && !_generating_world; if (!ScriptCompanyMode::IsDeity() && !ScriptCompanyMode::IsValid()) { ScriptObject::SetLastError(ScriptError::ERR_PRECONDITION_INVALID_COMPANY); - return { true, estimate_only, networking }; + return { true, estimate_only, asynchronous, networking }; } - return { false, estimate_only, networking }; + return { false, estimate_only, asynchronous, networking }; } -bool ScriptObject::DoCommandProcessResult(const CommandCost &res, Script_SuspendCallbackProc *callback, bool estimate_only) +bool ScriptObject::DoCommandProcessResult(const CommandCost &res, Script_SuspendCallbackProc *callback, bool estimate_only, bool asynchronous) { /* Set the default callback to return a true/false result of the DoCommand */ if (callback == nullptr) callback = &ScriptInstance::DoCommandReturn; @@ -282,8 +301,13 @@ bool ScriptObject::DoCommandProcessResult(const CommandCost &res, Script_Suspend SetLastCost(res.GetCost()); SetLastCommandRes(true); - if (_generating_world) { + if (_generating_world || asynchronous) { IncreaseDoCommandCosts(res.GetCost()); + if (!_generating_world) { + /* Charge a nominal fee for asynchronously executed commands */ + Squirrel *engine = ScriptObject::GetActiveInstance()->engine; + Squirrel::DecreaseOps(engine->GetVM(), 100); + } if (callback != nullptr) { /* Insert return value into to stack and throw a control code that * the return value in the stack should be used. */ diff --git a/src/script/api/script_object.hpp b/src/script/api/script_object.hpp index f4b5694bcc..b2f1f392f9 100644 --- a/src/script/api/script_object.hpp +++ b/src/script/api/script_object.hpp @@ -29,6 +29,11 @@ */ typedef bool (ScriptModeProc)(); +/** + * The callback function for Async Mode-classes. + */ +typedef bool (ScriptAsyncModeProc)(); + /** * Uper-parent object of all API classes. You should never use this class in * your script, as it doesn't publish any public functions. It is used @@ -188,6 +193,21 @@ protected: */ static ScriptObject *GetDoCommandModeInstance(); + /** + * Set the current async mode of your script to this proc. + */ + static void SetDoCommandAsyncMode(ScriptAsyncModeProc *proc, ScriptObject *instance); + + /** + * Get the current async mode your script is currently under. + */ + static ScriptModeProc *GetDoCommandAsyncMode(); + + /** + * Get the instance of the current async mode your script is currently under. + */ + static ScriptObject *GetDoCommandAsyncModeInstance(); + /** * Set the delay of the DoCommand. */ @@ -286,8 +306,8 @@ protected: private: /* Helper functions for DoCommand. */ - static std::tuple DoCommandPrep(); - static bool DoCommandProcessResult(const CommandCost &res, Script_SuspendCallbackProc *callback, bool estimate_only); + static std::tuple DoCommandPrep(); + static bool DoCommandProcessResult(const CommandCost &res, Script_SuspendCallbackProc *callback, bool estimate_only, bool asynchronous); static CommandCallbackData *GetDoCommandCallback(); static Randomizer random_states[OWNER_END]; ///< Random states for each of the scripts (game script uses OWNER_DEITY) }; @@ -338,7 +358,7 @@ namespace ScriptObjectInternal { template bool ScriptObject::ScriptDoCommandHelper::Execute(Script_SuspendCallbackProc *callback, std::tuple args) { - auto [err, estimate_only, networking] = ScriptObject::DoCommandPrep(); + auto [err, estimate_only, asynchronous, networking] = ScriptObject::DoCommandPrep(); if (err) return false; if ((::GetCommandFlags() & CMD_STR_CTRL) == 0) { @@ -363,10 +383,10 @@ bool ScriptObject::ScriptDoCommandHelper Tret res = ::Command::Unsafe((StringID)0, networking ? ScriptObject::GetDoCommandCallback() : nullptr, false, estimate_only, tile, args); if constexpr (std::is_same_v) { - return ScriptObject::DoCommandProcessResult(res, callback, estimate_only); + return ScriptObject::DoCommandProcessResult(res, callback, estimate_only, asynchronous); } else { ScriptObject::SetLastCommandResData(EndianBufferWriter::FromValue(ScriptObjectInternal::RemoveFirstTupleElement(res))); - return ScriptObject::DoCommandProcessResult(std::get<0>(res), callback, estimate_only); + return ScriptObject::DoCommandProcessResult(std::get<0>(res), callback, estimate_only, asynchronous); } } diff --git a/src/script/script_storage.hpp b/src/script/script_storage.hpp index 626c62f025..6f856908d5 100644 --- a/src/script/script_storage.hpp +++ b/src/script/script_storage.hpp @@ -26,6 +26,11 @@ */ typedef bool (ScriptModeProc)(); +/** + * The callback function for Async Mode-classes. + */ +typedef bool (ScriptAsyncModeProc)(); + /** * The storage for each script. It keeps track of important information. */ @@ -34,6 +39,8 @@ friend class ScriptObject; private: ScriptModeProc *mode; ///< The current build mode we are int. class ScriptObject *mode_instance; ///< The instance belonging to the current build mode. + ScriptAsyncModeProc *async_mode; ///< The current command async mode we are in. + class ScriptObject *async_mode_instance; ///< The instance belonging to the current command async mode. CompanyID root_company; ///< The root company, the company that the script really belongs to. CompanyID company; ///< The current company. @@ -61,6 +68,8 @@ public: ScriptStorage() : mode (nullptr), mode_instance (nullptr), + async_mode (nullptr), + async_mode_instance (nullptr), root_company (INVALID_OWNER), company (INVALID_OWNER), delay (1),