From 7e01b004e9675fec417bee0eead7d3d0702395e6 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Fri, 4 May 2012 20:59:06 +0400
Subject: [PATCH] Implement timeouts in the core lua context, and quicksave
script.
---
LUA_API.rst | 20 ++++++
Lua API.html | 17 +++++
library/Core.cpp | 4 ++
library/LuaApi.cpp | 7 +-
library/LuaTools.cpp | 129 +++++++++++++++++++++++++++++++++++++
library/PluginManager.cpp | 2 +-
library/include/LuaTools.h | 2 +
scripts/quicksave.lua | 28 ++++++++
8 files changed, 207 insertions(+), 2 deletions(-)
create mode 100644 scripts/quicksave.lua
diff --git a/LUA_API.rst b/LUA_API.rst
index d9488d60f..938664379 100644
--- a/LUA_API.rst
+++ b/LUA_API.rst
@@ -598,6 +598,14 @@ One notable difference is that these explicit wrappers allow argument count
adjustment according to the usual lua rules, so trailing false/nil arguments
can be omitted.
+* ``dfhack.isWorldLoaded()``
+
+ Checks if the world is loaded.
+
+* ``dfhack.isMapLoaded()``
+
+ Checks if the world and map are loaded.
+
* ``dfhack.TranslateName(name[,in_english,only_last_name])``
Convert a language_name or only the last name part to string.
@@ -950,6 +958,18 @@ Core context specific functions:
Boolean value; *true* in the core context.
+* ``dfhack.timeout(time,mode,callback)``
+
+ Arranges for the callback to be called once the specified
+ period of time passes. The ``mode`` argument specifies the
+ unit of time used, and may be one of ``'frames'`` (raw FPS),
+ ``'ticks'`` (unpaused FPS), ``'days'``, ``'months'``,
+ ``'years'`` (in-game time). All timers other than
+ ``'frames'`` are cancelled when the world is unloaded,
+ and cannot be queued until it is loaded again.
+ Returns the timer id, or *nil* if unsuccessful due to
+ world being unloaded.
+
* ``dfhack.onStateChange.foo = function(code)``
Event. Receives the same codes as plugin_onstatechange in C++.
diff --git a/Lua API.html b/Lua API.html
index 40706d14f..b96d12156 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -855,6 +855,12 @@ One notable difference is that these explicit wrappers allow argument count
adjustment according to the usual lua rules, so trailing false/nil arguments
can be omitted.
+dfhack.isWorldLoaded()
+Checks if the world is loaded.
+
+dfhack.isMapLoaded()
+Checks if the world and map are loaded.
+
dfhack.TranslateName(name[,in_english,only_last_name])
Convert a language_name or only the last name part to string.
@@ -1151,6 +1157,17 @@ only context that can receive events from DF and plugins.
dfhack.is_core_context
Boolean value; true in the core context.
+dfhack.timeout(time,mode,callback)
+Arranges for the callback to be called once the specified
+period of time passes. The mode argument specifies the
+unit of time used, and may be one of 'frames' (raw FPS),
+'ticks' (unpaused FPS), 'days', 'months',
+'years' (in-game time). All timers other than
+'frames' are cancelled when the world is unloaded,
+and cannot be queued until it is loaded again.
+Returns the timer id, or nil if unsuccessful due to
+world being unloaded.
+
dfhack.onStateChange.foo = function(code)
Event. Receives the same codes as plugin_onstatechange in C++.
diff --git a/library/Core.cpp b/library/Core.cpp
index 60e380598..d7e4435ce 100644
--- a/library/Core.cpp
+++ b/library/Core.cpp
@@ -355,6 +355,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
{
string help = getLuaHelp(filename);
con.print("%s: %s\n", parts[0].c_str(), help.c_str());
+ return CR_OK;
}
con.printerr("Unknown command: %s\n", parts[0].c_str());
}
@@ -1077,6 +1078,9 @@ int Core::Update()
// notify all the plugins that a game tick is finished
plug_mgr->OnUpdate(out);
+ // process timers in lua
+ Lua::Core::onUpdate(out);
+
// Release the fake suspend lock
{
lock_guard lock(d->AccessMutex);
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index 828e54cbc..ea723df64 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -570,9 +570,14 @@ static void OpenModule(lua_State *state, const char *mname,
#define WRAP(function) { #function, df::wrap_function(function,true) }
#define WRAPN(name, function) { #name, df::wrap_function(function,true) }
-/***** Translation module *****/
+/***** DFHack module *****/
+
+static bool isWorldLoaded() { return Core::getInstance().isWorldLoaded(); }
+static bool isMapLoaded() { return Core::getInstance().isMapLoaded(); }
static const LuaWrapper::FunctionReg dfhack_module[] = {
+ WRAP(isWorldLoaded),
+ WRAP(isMapLoaded),
WRAPM(Translation, TranslateName),
{ NULL, NULL }
};
diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp
index 17ad0abde..10ff51ba1 100644
--- a/library/LuaTools.cpp
+++ b/library/LuaTools.cpp
@@ -53,6 +53,7 @@ distribution.
#include "df/building.h"
#include "df/unit.h"
#include "df/item.h"
+#include "df/world.h"
#include
#include
@@ -1412,13 +1413,135 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
return state;
}
+static int next_timeout_id = 0;
+static int frame_idx = 0;
+static std::multimap frame_timers;
+static std::multimap tick_timers;
+
+int DFHACK_TIMEOUTS_TOKEN = 0;
+
+static const char *const timeout_modes[] = {
+ "frames", "ticks", "days", "months", "years", NULL
+};
+
+int dfhack_timeout(lua_State *L)
+{
+ using df::global::world;
+ using df::global::enabler;
+
+ lua_Number time = luaL_checknumber(L, 1);
+ int mode = luaL_checkoption(L, 2, NULL, timeout_modes);
+ luaL_checktype(L, 3, LUA_TFUNCTION);
+ lua_settop(L, 3);
+
+ if (mode > 0 && !Core::getInstance().isWorldLoaded())
+ {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ switch (mode)
+ {
+ case 2:
+ time *= 1200;
+ break;
+ case 3:
+ time *= 33600;
+ break;
+ case 4:
+ time *= 403200;
+ break;
+ default:;
+ }
+
+ int id = next_timeout_id++;
+ int delta = time;
+
+ if (delta <= 0)
+ luaL_error(L, "Invalid timeout: %d", delta);
+
+ if (mode)
+ tick_timers.insert(std::pair(world->frame_counter+delta, id));
+ else
+ frame_timers.insert(std::pair(frame_idx+delta, id));
+
+ lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
+ lua_swap(L);
+ lua_rawseti(L, -2, id);
+
+ lua_pushinteger(L, id);
+ return 1;
+}
+
+static void cancel_tick_timers()
+{
+ using Lua::Core::State;
+
+ Lua::StackUnwinder frame(State);
+ lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
+
+ for (auto it = tick_timers.begin(); it != tick_timers.end(); ++it)
+ {
+ lua_pushnil(State);
+ lua_rawseti(State, frame[1], it->second);
+ }
+
+ tick_timers.clear();
+}
+
void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) {
if (!State) return;
+ switch (code)
+ {
+ case SC_MAP_UNLOADED:
+ case SC_WORLD_UNLOADED:
+ cancel_tick_timers();
+ break;
+
+ default:;
+ }
+
Lua::Push(State, code);
Lua::InvokeEvent(out, State, (void*)onStateChange, 1);
}
+void DFHack::Lua::Core::onUpdate(color_ostream &out)
+{
+ using df::global::world;
+
+ Lua::StackUnwinder frame(State);
+ lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
+
+ frame_idx++;
+
+ while (!frame_timers.empty() &&
+ frame_timers.begin()->first <= frame_idx)
+ {
+ int id = frame_timers.begin()->second;
+ frame_timers.erase(frame_timers.begin());
+
+ lua_rawgeti(State, frame[1], id);
+ lua_pushnil(State);
+ lua_rawseti(State, frame[1], id);
+
+ Lua::SafeCall(out, State, 0, 0);
+ }
+
+ while (!tick_timers.empty() &&
+ tick_timers.begin()->first <= world->frame_counter)
+ {
+ int id = tick_timers.begin()->second;
+ tick_timers.erase(tick_timers.begin());
+
+ lua_rawgeti(State, frame[1], id);
+ lua_pushnil(State);
+ lua_rawseti(State, frame[1], id);
+
+ Lua::SafeCall(out, State, 0, 0);
+ }
+}
+
void DFHack::Lua::Core::Init(color_ostream &out)
{
if (State)
@@ -1428,12 +1551,18 @@ void DFHack::Lua::Core::Init(color_ostream &out)
Lua::Open(out, State);
+ lua_newtable(State);
+ lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
+
// Register events
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN);
MakeEvent(State, (void*)onStateChange);
lua_setfield(State, -2, "onStateChange");
+ lua_pushcfunction(State, dfhack_timeout);
+ lua_setfield(State, -2, "timeout");
+
lua_pop(State, 1);
}
diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp
index 69b8bfe0d..b8619d503 100644
--- a/library/PluginManager.cpp
+++ b/library/PluginManager.cpp
@@ -591,7 +591,7 @@ command_result PluginManager::InvokeCommand(color_ostream &out, const std::strin
bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen *top)
{
Plugin *plugin = getPluginByCommand(command);
- return plugin ? plugin->can_invoke_hotkey(command, top) : false;
+ return plugin ? plugin->can_invoke_hotkey(command, top) : true;
}
void PluginManager::OnUpdate(color_ostream &out)
diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h
index 2ffc5c58a..669d4d38b 100644
--- a/library/include/LuaTools.h
+++ b/library/include/LuaTools.h
@@ -333,6 +333,8 @@ namespace DFHack {namespace Lua {
// Events signalled by the core
void onStateChange(color_ostream &out, int code);
+ // Signals timers
+ void onUpdate(color_ostream &out);
template inline void Push(T &arg) { Lua::Push(State, arg); }
template inline void Push(const T &arg) { Lua::Push(State, arg); }
diff --git a/scripts/quicksave.lua b/scripts/quicksave.lua
new file mode 100644
index 000000000..c54cc730b
--- /dev/null
+++ b/scripts/quicksave.lua
@@ -0,0 +1,28 @@
+-- Makes the game immediately save the state.
+
+if not dfhack.isMapLoaded() then
+ dfhack.printerr("World and map aren't loaded.")
+ return
+end
+
+local ui_main = df.global.ui.main
+local flags4 = df.global.d_init.flags4
+
+local function restore_autobackup()
+ if ui_main.autosave_request and dfhack.isMapLoaded() then
+ dfhack.timeout(10, 'frames', restore_autobackup)
+ else
+ flags4.AUTOBACKUP = true
+ end
+end
+
+-- Request auto-save
+ui_main.autosave_request = true
+
+-- And since it will overwrite the backup, disable it temporarily
+if flags4.AUTOBACKUP then
+ flags4.AUTOBACKUP = false
+ restore_autobackup()
+end
+
+print 'The game should save the state now.'