mirror of https://github.com/OpenTTD/OpenTTD
Add: [Console] schedule command to execute a script file later
The change in https://github.com/OpenTTD/OpenTTD/pull/10655, released as part of OpenTTD 14, changed how auto saving works from every X amount of game time, to every X amount of real time. This is an improvement for normal play, but there are cases where saving every X amount of game time is useful. For example when developing and comparing AIs, or using OpenTTD to conduct experiments such as via https://github.com/michalc/OpenTTDLab which extracts data from savegame files. This change adds a general "schedule" console command that makes it possible for a console script to be scheduled for the future, where that script can, for example, contain a "save" command to save the game. That script can also then reschedule itself or another script, and thus making it possible to re-create the pre-OpenTTD 14 behaviour of saving the game every X amount of game time. (Possibly with a whole "suite" of game scripts that call each other in a chain, each of them hard coded with a different filename to allow all the savegames to be saved with different names, but that's a doable detail) The current change only allows scheduling "on-next-calendar-month", since that is my specific use case, but the syntax is hopefully friendly to this being extended in the future. And by scheduling any console script, it is much more general and so hopefully useful than just for saving games. There was some discussion on what this should be called at https://discord.com/channels/142724111502802944/1008473233844097104/1247278039927361546 (as well as some issues with an alternatively proposed solution using game scripts at https://github.com/OpenTTD/OpenTTD/pull/12750). Instead of "schedule", "trigger" was suggested for something to only be called once, but after writing code and output to the user, it didn't make it immediately clear that "trigger" means "once at some point in the future", and seemed too close to "exec". Also I think "schedule" can be used _both_ for one-off or repeated events in the future (in English it's common to say to "schedule an appointment", even if it's just the one). Or code-wise: schedule on-next-calendar-month my.scr (the only current behaviour) schedule every-calendar-month my.scr (possible future behaviour?) schedule "* 1 * *" my.scr (possible future cron-like behaviour?) Possible future extensions could include some mechanism for cancelling something that has been scheduled, and I have no reason to think that mechanism would be different for something scheduled to repeat or just as a one-off, so I think also it makes sense to that we would have the one command for both. The new "schedule" command: - Only allows a single script to be scheduled - any (correct) usage of the "schedule" command will overwrite the previous, but hopefully the console messages make this clear - Only allows a script to be scheduled once at the start of next month (but this could be extended if useful). - Does not offer a way to unschedule, but hopefully since the script can only be scheduled to run once in the future, and relatively close in the future (at the start of next month), this shouldn't be too much of an annoying missing feature. Co-authored-by: Patric Stout <github@truebrain.nl>pull/12761/head
parent
8c7cf3bc75
commit
dc26e40704
|
@ -29,7 +29,7 @@
|
|||
#include "strings_func.h"
|
||||
#include "viewport_func.h"
|
||||
#include "window_func.h"
|
||||
#include "timer/timer_game_calendar.h"
|
||||
#include "timer/timer.h"
|
||||
#include "company_func.h"
|
||||
#include "gamelog.h"
|
||||
#include "ai/ai.hpp"
|
||||
|
@ -53,6 +53,24 @@
|
|||
/* scriptfile handling */
|
||||
static uint _script_current_depth; ///< Depth of scripts running (used to abort execution when #ConReturn is encountered).
|
||||
|
||||
/* Scheduled execution handling. */
|
||||
static std::string _scheduled_monthly_script; ///< Script scheduled to execute by the 'schedule' console command (empty if no script is scheduled).
|
||||
|
||||
/** Timer that runs every month of game time for the 'schedule' console command. */
|
||||
static IntervalTimer<TimerGameCalendar> _scheduled_monthly_timer = {{TimerGameCalendar::MONTH, TimerGameCalendar::Priority::NONE}, [](auto) {
|
||||
if (_scheduled_monthly_script.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Clear the schedule before rather than after the script to allow the script to itself call
|
||||
* schedule without it getting immediately cleared. */
|
||||
const std::string filename = _scheduled_monthly_script;
|
||||
_scheduled_monthly_script.clear();
|
||||
|
||||
IConsolePrint(CC_DEFAULT, "Executing scheduled script file '{}'...", filename);
|
||||
IConsoleCmdExec(std::string("exec") + " " + filename);
|
||||
}};
|
||||
|
||||
/** File list storage for the console, for caching the last 'ls' command. */
|
||||
class ConsoleFileList : public FileList {
|
||||
public:
|
||||
|
@ -1154,6 +1172,35 @@ DEF_CONSOLE_CMD(ConExec)
|
|||
return true;
|
||||
}
|
||||
|
||||
DEF_CONSOLE_CMD(ConSchedule)
|
||||
{
|
||||
if (argc < 3 || std::string_view(argv[1]) != "on-next-calendar-month") {
|
||||
IConsolePrint(CC_HELP, "Schedule a local script to execute later. Usage: 'schedule on-next-calendar-month <script>'.");
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check if the file exists. It might still go away later, but helpful to show an error now. */
|
||||
if (!FioCheckFileExists(argv[2], BASE_DIR)) {
|
||||
IConsolePrint(CC_ERROR, "Script file '{}' not found.", argv[2]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* We only support a single script scheduled, so we tell the user what's happening if there was already one. */
|
||||
const std::string_view filename = std::string_view(argv[2]);
|
||||
if (!_scheduled_monthly_script.empty() && filename == _scheduled_monthly_script) {
|
||||
IConsolePrint(CC_INFO, "Script file '{}' was already scheduled to execute at the start of next calendar month.", filename);
|
||||
} else if (!_scheduled_monthly_script.empty() && filename != _scheduled_monthly_script) {
|
||||
IConsolePrint(CC_INFO, "Script file '{}' scheduled to execute at the start of next calendar month, replacing the previously scheduled script file '{}'.", filename, _scheduled_monthly_script);
|
||||
} else {
|
||||
IConsolePrint(CC_INFO, "Script file '{}' scheduled to execute at the start of next calendar month.", filename);
|
||||
}
|
||||
|
||||
/* Store the filename to be used by _schedule_timer on the start of next calendar month. */
|
||||
_scheduled_monthly_script = filename;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DEF_CONSOLE_CMD(ConReturn)
|
||||
{
|
||||
if (argc == 0) {
|
||||
|
@ -2721,6 +2768,7 @@ void IConsoleStdLibRegister()
|
|||
IConsole::CmdRegister("echo", ConEcho);
|
||||
IConsole::CmdRegister("echoc", ConEchoC);
|
||||
IConsole::CmdRegister("exec", ConExec);
|
||||
IConsole::CmdRegister("schedule", ConSchedule);
|
||||
IConsole::CmdRegister("exit", ConExit);
|
||||
IConsole::CmdRegister("part", ConPart);
|
||||
IConsole::CmdRegister("help", ConHelp);
|
||||
|
|
Loading…
Reference in New Issue