Implement timeouts in the core lua context, and quicksave script.

develop
Alexander Gavrilov 2012-05-04 20:59:06 +04:00
parent d4d6349f48
commit 7e01b004e9
8 changed files with 207 additions and 2 deletions

@ -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 adjustment according to the usual lua rules, so trailing false/nil arguments
can be omitted. 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])`` * ``dfhack.TranslateName(name[,in_english,only_last_name])``
Convert a language_name or only the last name part to string. 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. 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)`` * ``dfhack.onStateChange.foo = function(code)``
Event. Receives the same codes as plugin_onstatechange in C++. Event. Receives the same codes as plugin_onstatechange in C++.

@ -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 adjustment according to the usual lua rules, so trailing false/nil arguments
can be omitted.</p> can be omitted.</p>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.isWorldLoaded()</tt></p>
<p>Checks if the world is loaded.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.isMapLoaded()</tt></p>
<p>Checks if the world and map are loaded.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.TranslateName(name[,in_english,only_last_name])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.TranslateName(name[,in_english,only_last_name])</span></tt></p>
<p>Convert a language_name or only the last name part to string.</p> <p>Convert a language_name or only the last name part to string.</p>
</li> </li>
@ -1151,6 +1157,17 @@ only context that can receive events from DF and plugins.</p>
<li><p class="first"><tt class="docutils literal">dfhack.is_core_context</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.is_core_context</tt></p>
<p>Boolean value; <em>true</em> in the core context.</p> <p>Boolean value; <em>true</em> in the core context.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.timeout(time,mode,callback)</tt></p>
<p>Arranges for the callback to be called once the specified
period of time passes. The <tt class="docutils literal">mode</tt> argument specifies the
unit of time used, and may be one of <tt class="docutils literal">'frames'</tt> (raw FPS),
<tt class="docutils literal">'ticks'</tt> (unpaused FPS), <tt class="docutils literal">'days'</tt>, <tt class="docutils literal">'months'</tt>,
<tt class="docutils literal">'years'</tt> (in-game time). All timers other than
<tt class="docutils literal">'frames'</tt> are cancelled when the world is unloaded,
and cannot be queued until it is loaded again.
Returns the timer id, or <em>nil</em> if unsuccessful due to
world being unloaded.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.onStateChange.foo = function(code)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.onStateChange.foo = function(code)</tt></p>
<p>Event. Receives the same codes as plugin_onstatechange in C++.</p> <p>Event. Receives the same codes as plugin_onstatechange in C++.</p>
</li> </li>

@ -355,6 +355,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
{ {
string help = getLuaHelp(filename); string help = getLuaHelp(filename);
con.print("%s: %s\n", parts[0].c_str(), help.c_str()); 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()); 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 // notify all the plugins that a game tick is finished
plug_mgr->OnUpdate(out); plug_mgr->OnUpdate(out);
// process timers in lua
Lua::Core::onUpdate(out);
// Release the fake suspend lock // Release the fake suspend lock
{ {
lock_guard<mutex> lock(d->AccessMutex); lock_guard<mutex> lock(d->AccessMutex);

@ -570,9 +570,14 @@ static void OpenModule(lua_State *state, const char *mname,
#define WRAP(function) { #function, df::wrap_function(function,true) } #define WRAP(function) { #function, df::wrap_function(function,true) }
#define WRAPN(name, function) { #name, 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[] = { static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(isWorldLoaded),
WRAP(isMapLoaded),
WRAPM(Translation, TranslateName), WRAPM(Translation, TranslateName),
{ NULL, NULL } { NULL, NULL }
}; };

@ -53,6 +53,7 @@ distribution.
#include "df/building.h" #include "df/building.h"
#include "df/unit.h" #include "df/unit.h"
#include "df/item.h" #include "df/item.h"
#include "df/world.h"
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
@ -1412,13 +1413,135 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
return state; return state;
} }
static int next_timeout_id = 0;
static int frame_idx = 0;
static std::multimap<int,int> frame_timers;
static std::multimap<int,int> 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<int,int>(world->frame_counter+delta, id));
else
frame_timers.insert(std::pair<int,int>(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) { void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) {
if (!State) return; if (!State) return;
switch (code)
{
case SC_MAP_UNLOADED:
case SC_WORLD_UNLOADED:
cancel_tick_timers();
break;
default:;
}
Lua::Push(State, code); Lua::Push(State, code);
Lua::InvokeEvent(out, State, (void*)onStateChange, 1); 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) void DFHack::Lua::Core::Init(color_ostream &out)
{ {
if (State) if (State)
@ -1428,12 +1551,18 @@ void DFHack::Lua::Core::Init(color_ostream &out)
Lua::Open(out, State); Lua::Open(out, State);
lua_newtable(State);
lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
// Register events // Register events
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN); lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN);
MakeEvent(State, (void*)onStateChange); MakeEvent(State, (void*)onStateChange);
lua_setfield(State, -2, "onStateChange"); lua_setfield(State, -2, "onStateChange");
lua_pushcfunction(State, dfhack_timeout);
lua_setfield(State, -2, "timeout");
lua_pop(State, 1); lua_pop(State, 1);
} }

@ -591,7 +591,7 @@ command_result PluginManager::InvokeCommand(color_ostream &out, const std::strin
bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen *top) bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen *top)
{ {
Plugin *plugin = getPluginByCommand(command); 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) void PluginManager::OnUpdate(color_ostream &out)

@ -333,6 +333,8 @@ namespace DFHack {namespace Lua {
// Events signalled by the core // Events signalled by the core
void onStateChange(color_ostream &out, int code); void onStateChange(color_ostream &out, int code);
// Signals timers
void onUpdate(color_ostream &out);
template<class T> inline void Push(T &arg) { Lua::Push(State, arg); } template<class T> inline void Push(T &arg) { Lua::Push(State, arg); }
template<class T> inline void Push(const T &arg) { Lua::Push(State, arg); } template<class T> inline void Push(const T &arg) { Lua::Push(State, arg); }

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