diff --git a/docs/script-additions.html b/docs/script-additions.html index 0e1626aa63..f22079d3e5 100644 --- a/docs/script-additions.html +++ b/docs/script-additions.html @@ -29,6 +29,7 @@
  • Road: GSRoad and AIRoad
  • Company: GSCompany and AICompany
  • Inflation: GSInflation and AIInflation
  • +
  • Command Asynchronous Mode: GSAsyncMode
  • Date: GSDate Class and AIDate Class

    @@ -149,5 +150,24 @@
    The inflation factor is a fixed point value (16 bits).
    + +

    Command Asynchronous Mode: GSAsyncMode Class

    +
    +

    Public Constructor:

    +
    +
    GSAsyncMode (bool asynchronous)
    +
    Creating an instance of this class switches the asynchronous execution mode for commands.
    +
    + A value of true sets the mode to Asynchronous, the commands you execute are queued for later execution, and the script + is not delayed waiting for the command result. The estimated result is returned to the script. + The actual cost and whether the command succeeded when the command is eventually executed may differ from what was returned to the script. +
    +
    + A value of false sets the mode to Non-Asynchronous, this is the normal mode of executing commands. +
    +
    The original mode is stored and recovered from when ever the instance is destroyed.
    +
    Use in a similar way to the GSTestMode class.
    +
    +
    diff --git a/src/command.cpp b/src/command.cpp index 982d229e8a..33fdeb3369 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -589,6 +589,7 @@ enum CommandLogEntryFlag : uint16 { CLEF_TWICE = 0x100, ///< command logged twice (only sending and execution) CLEF_RANDOM = 0x200, ///< command changed random seed CLEF_ORDER_BACKUP = 0x400, ///< command changed order backups + CLEF_SCRIPT_ASYNC = 0x800, ///< command run by AI/game script - asynchronous }; DECLARE_ENUM_AS_BIT_SET(CommandLogEntryFlag) @@ -650,6 +651,11 @@ static void DumpSubCommandLogEntry(char *&buffer, const char *last, const Comman return entry.log_flags & flag ? c : '-'; }; + auto script_fc = [&]() -> char { + if (!(entry.log_flags & CLEF_SCRIPT)) return '-'; + return (entry.log_flags & CLEF_SCRIPT_ASYNC) ? 'A' : 'a'; + }; + YearMonthDay ymd; ConvertDateToYMD(entry.date, &ymd); buffer += seprintf(buffer, last, "%4i-%02i-%02i, %2i, %3i", ymd.year, ymd.month + 1, ymd.day, entry.date_fract, entry.tick_skip_counter); @@ -658,7 +664,7 @@ static void DumpSubCommandLogEntry(char *&buffer, const char *last, const Comman } buffer += seprintf(buffer, last, " | %c%c%c%c%c%c%c%c%c%c%c | ", fc(CLEF_ORDER_BACKUP, 'o'), fc(CLEF_RANDOM, 'r'), fc(CLEF_TWICE, '2'), - fc(CLEF_SCRIPT, 'a'), fc(CLEF_AUX_DATA, 'b'), fc(CLEF_MY_CMD, 'm'), fc(CLEF_ONLY_SENDING, 's'), + script_fc(), fc(CLEF_AUX_DATA, 'b'), fc(CLEF_MY_CMD, 'm'), fc(CLEF_ONLY_SENDING, 's'), fc(CLEF_ESTIMATE_ONLY, 'e'), fc(CLEF_TEXT, 't'), fc(CLEF_GENERATING_WORLD, 'g'), fc(CLEF_CMD_FAILED, 'f')); buffer += seprintf(buffer, last, " %7d x %7d, p1: 0x%08X, p2: 0x%08X, ", TileX(entry.tile), TileY(entry.tile), entry.p1, entry.p2); @@ -1024,7 +1030,7 @@ bool DoCommandPEx(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint32 cmd, C return res.Succeeded(); } -CommandCost DoCommandPScript(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only, const CommandAuxiliaryBase *aux_data) +CommandCost DoCommandPScript(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only, bool asynchronous, const CommandAuxiliaryBase *aux_data) { GameRandomSeedChecker random_state; uint order_backup_update_counter = OrderBackup::GetUpdateCounter(); @@ -1033,6 +1039,7 @@ CommandCost DoCommandPScript(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, ui CommandLogEntryFlag log_flags; log_flags = CLEF_SCRIPT; + if (asynchronous) log_flags |= CLEF_SCRIPT_ASYNC; if (!StrEmpty(text)) log_flags |= CLEF_TEXT; if (estimate_only) log_flags |= CLEF_ESTIMATE_ONLY; if (_networking && !(cmd & CMD_NETWORK_COMMAND)) log_flags |= CLEF_ONLY_SENDING; diff --git a/src/command_func.h b/src/command_func.h index e1d3b400dc..b306aeaeb0 100644 --- a/src/command_func.h +++ b/src/command_func.h @@ -55,7 +55,7 @@ inline bool DoCommandP(const CommandContainer *container, bool my_cmd = true) return DoCommandPEx(container->tile, container->p1, container->p2, container->p3, container->cmd, container->callback, container->text.c_str(), container->aux_data.get(), my_cmd); } -CommandCost DoCommandPScript(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only, const CommandAuxiliaryBase *aux_data); +CommandCost DoCommandPScript(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only, bool asynchronous, const CommandAuxiliaryBase *aux_data); CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only, const CommandAuxiliaryBase *aux_data); void NetworkSendCommand(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint32 cmd, CommandCallback *callback, const char *text, CompanyID company, const CommandAuxiliaryBase *aux_data); diff --git a/src/script/api/CMakeLists.txt b/src/script/api/CMakeLists.txt index 7d5cd17cd7..844db3a99f 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..8f95cbb1ef --- /dev/null +++ b/src/script/api/script_asyncmode.hpp @@ -0,0 +1,68 @@ +/* + * 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 + /** + * Generate a text from string. You can set parameters to the instance which + * can be required for the string. + * @param string The string of the text. + */ + /** + * 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 reset the building 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 8beed08a52..bff12ca46d 100644 --- a/src/script/api/script_object.cpp +++ b/src/script/api/script_object.cpp @@ -86,6 +86,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(TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint32 cmd) { ScriptStorage *s = GetStorage(); @@ -340,6 +356,9 @@ ScriptObject::ActiveInstance::~ActiveInstance() /* Are we only interested in the estimate costs? */ bool estimate_only = GetDoCommandMode() != nullptr && !GetDoCommandMode()(); + /* Are we only interested in the estimate costs? */ + bool asynchronous = GetDoCommandAsyncMode() != nullptr && GetDoCommandAsyncMode()() && GetActiveInstance()->GetScriptType() == ST_GS; + /* Only set p2 when the command does not come from the network. */ if (GetCommandFlags(cmd) & CMD_CLIENT_ID && p2 == 0) p2 = UINT32_MAX; @@ -350,7 +369,9 @@ ScriptObject::ActiveInstance::~ActiveInstance() if (!estimate_only && _networking && !_generating_world) SetLastCommand(tile, p1, p2, p3, cmd); /* Try to perform the command. */ - CommandCost res = ::DoCommandPScript(tile, p1, p2, p3, cmd, (_networking && !_generating_world) ? ScriptObject::GetActiveInstance()->GetDoCommandCallback() : nullptr, text, false, estimate_only, aux_data); + CommandCost res = ::DoCommandPScript(tile, p1, p2, p3, cmd, + (_networking && !_generating_world && !asynchronous) ? ScriptObject::GetActiveInstance()->GetDoCommandCallback() : nullptr, + text, false, estimate_only, asynchronous, aux_data); /* We failed; set the error and bail out */ if (res.Failed()) { @@ -372,11 +393,12 @@ ScriptObject::ActiveInstance::~ActiveInstance() SetLastCommandResultData(res.GetResultData()); SetLastCommandRes(true); - if (_generating_world) { + if (_generating_world || asynchronous) { IncreaseDoCommandCosts(res.GetCost()); if (callback != nullptr) { /* Insert return value into to stack and throw a control code that * the return value in the stack should be used. */ + if (!_generating_world) ScriptController::DecreaseOps(100); callback(GetActiveInstance()); throw SQInteger(1); } diff --git a/src/script/api/script_object.hpp b/src/script/api/script_object.hpp index 2109b7db2d..deea660518 100644 --- a/src/script/api/script_object.hpp +++ b/src/script/api/script_object.hpp @@ -28,6 +28,11 @@ struct CommandAuxiliaryBase; */ 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 @@ -173,6 +178,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. */ diff --git a/src/script/script_storage.hpp b/src/script/script_storage.hpp index 4f1978c8fe..79f4784d20 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. @@ -73,6 +80,8 @@ public: ScriptStorage() : mode (nullptr), mode_instance (nullptr), + async_mode (nullptr), + async_mode_instance (nullptr), root_company (INVALID_OWNER), company (INVALID_OWNER), delay (1),