GS: Add GSAsyncMode(bool) class to set async mode of script DoCommands

In asynchronous mode, don't wait for result of executed command,
just fire-and-forget, and return estimated cost/result
pull/544/head
Jonathan G Rennison 12 months ago
parent df6c35a48a
commit cd9930542d

@ -29,6 +29,7 @@
<li><a href="#road">Road: GSRoad and AIRoad</a></li>
<li><a href="#company">Company: GSCompany and AICompany</a></li>
<li><a href="#inflation">Inflation: GSInflation and AIInflation</a></li>
<li><a href="#asyncmode">Command Asynchronous Mode: GSAsyncMode</a></li>
</ul>
<h3 id="date">Date: <a href="https://docs.openttd.org/gs-api/classGSDate.html">GSDate Class</a> and <a href="https://docs.openttd.org/ai-api/classAIDate.html">AIDate Class</a></h3>
@ -149,5 +150,24 @@
<div class="methodtext">The inflation factor is a fixed point value (16 bits).</div>
</div>
</div>
<h3 id="asyncmode">Command Asynchronous Mode: GSAsyncMode Class</h3>
<div class="indent">
<h4>Public Constructor:</h4>
<div class="indent">
<div class="code">GSAsyncMode (bool asynchronous)</div>
<div class="methodtext">Creating an instance of this class switches the asynchronous execution mode for commands.</div>
<div class="methodtext">
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.
</div>
<div class="methodtext">
A value of false sets the mode to Non-Asynchronous, this is the normal mode of executing commands.
</div>
<div class="methodtext">The original mode is stored and recovered from when ever the instance is destroyed.</div>
<div class="methodtext">Use in a similar way to the <a href="https://docs.openttd.org/gs-api/classGSTestMode.html">GSTestMode class</a>.</div>
</div>
</div>
</body>
</html>

@ -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;

@ -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);

@ -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

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @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);
}

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @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 */

@ -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);
}

@ -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.
*/

@ -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),

Loading…
Cancel
Save