Petr Mrázek 2012-04-17 21:48:04 +02:00
commit e530707985
8 changed files with 165 additions and 24 deletions

@ -209,7 +209,7 @@ Implemented features:
* ``ref:insert(index,item)``
Inserts a new item at the specified index. To add at the end,
use ``#ref`` as index.
use ``#ref``, or just ``'#'`` as index.
* ``ref:erase(index)``
@ -428,6 +428,11 @@ Currently it defines the following features:
If the thread owns the interactive console, shows a prompt
and returns the entered string. Otherwise returns *nil, error*.
Depending on the context, this function may actually yield the
running coroutine and let the C++ code release the core suspend
lock. Using an explicit ``dfhack.with_suspend`` will prevent
this, forcing the function to block on input with lock held.
* ``dfhack.interpreter([prompt[,env[,history_filename]]])``
Starts an interactive lua interpreter, using the specified prompt
@ -449,6 +454,10 @@ Currently it defines the following features:
Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function.
* ``dfhack.saferesume(coroutine[,args...])``
Compares to coroutine.resume like dfhack.safecall vs pcall.
* ``dfhack.with_suspend(f[,args...])``
Calls ``f`` with arguments after grabbing the DF core suspend lock.
@ -813,3 +822,37 @@ Core context specific functions:
* ``dfhack.is_core_context``
Boolean value; *true* in the core context.
* ``dfhack.onStateChange.foo = function(code)``
Event. Receives the same codes as plugin_onstatechange in C++.
Event type
----------
An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values.
This is intended as an extensible way to add listeners.
This type itself is available in any context, but only the
core context has the actual events defined by C++ code.
Features:
* ``dfhack.event.new()``
Creates a new instance of an event.
* ``event[key] = function``
Sets the function as one of the listeners.
**NOTE**: The ``df.NULL`` key is reserved for the use by
the C++ owner of the event, and has some special semantics.
* ``event(args...)``
Invokes all listeners contained in the event in an arbitrary
order using ``dfhack.safecall``.

@ -344,7 +344,10 @@ ul.auto-toc {
<li><a class="reference internal" href="#maps-module" id="id18">Maps module</a></li>
</ul>
</li>
<li><a class="reference internal" href="#core-interpreter-context" id="id19">Core interpreter context</a></li>
<li><a class="reference internal" href="#core-interpreter-context" id="id19">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id20">Event type</a></li>
</ul>
</li>
</ul>
</li>
</ul>
@ -513,7 +516,7 @@ possible.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref:insert(index,item)</tt></p>
<p>Inserts a new item at the specified index. To add at the end,
use <tt class="docutils literal">#ref</tt> as index.</p>
use <tt class="docutils literal">#ref</tt>, or just <tt class="docutils literal">'#'</tt> as index.</p>
</li>
<li><p class="first"><tt class="docutils literal">ref:erase(index)</tt></p>
<p>Removes the element at the given valid index.</p>
@ -705,6 +708,10 @@ works with DFHack output infrastructure.</p>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.lineedit([prompt[,history_filename]])</span></tt></p>
<p>If the thread owns the interactive console, shows a prompt
and returns the entered string. Otherwise returns <em>nil, error</em>.</p>
<p>Depending on the context, this function may actually yield the
running coroutine and let the C++ code release the core suspend
lock. Using an explicit <tt class="docutils literal">dfhack.with_suspend</tt> will prevent
this, forcing the function to block on input with lock held.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.interpreter([prompt[,env[,history_filename]]])</span></tt></p>
<p>Starts an interactive lua interpreter, using the specified prompt
@ -722,6 +729,9 @@ in C++, and dfhack.safecall.</p>
<p>Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.saferesume(coroutine[,args...])</span></tt></p>
<p>Compares to coroutine.resume like dfhack.safecall vs pcall.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.with_suspend(f[,args...])</span></tt></p>
<p>Calls <tt class="docutils literal">f</tt> with arguments after grabbing the DF core suspend lock.
Suspending is necessary for accessing a consistent state of DF memory.</p>
@ -1021,9 +1031,36 @@ 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>
<p>Boolean value; <em>true</em> in the core context.</p>
</li>
<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>
</li>
</ul>
<div class="section" id="event-type">
<h3><a class="toc-backref" href="#id20">Event type</a></h3>
<p>An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values.
This is intended as an extensible way to add listeners.</p>
<p>This type itself is available in any context, but only the
core context has the actual events defined by C++ code.</p>
<p>Features:</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.event.new()</tt></p>
<p>Creates a new instance of an event.</p>
</li>
<li><p class="first"><tt class="docutils literal">event[key] = function</tt></p>
<p>Sets the function as one of the listeners.</p>
<p><strong>NOTE</strong>: The <tt class="docutils literal">df.NULL</tt> key is reserved for the use by
the C++ owner of the event, and has some special semantics.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">event(args...)</span></tt></p>
<p>Invokes all listeners contained in the event in an arbitrary
order using <tt class="docutils literal">dfhack.safecall</tt>.</p>
</li>
</ul>
</div>
</div>
</div>
</div>
</body>
</html>

@ -1098,6 +1098,18 @@ int DFHack::Lua::NewEvent(lua_State *state)
return 1;
}
static void do_invoke_event(lua_State *L, int argbase, int num_args, int errorfun)
{
for (int i = 0; i < num_args; i++)
lua_pushvalue(L, argbase+i);
if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK)
{
report_error(L);
lua_pop(L, 1);
}
}
static void dfhack_event_invoke(lua_State *L, int base, bool from_c)
{
int event = base+1;
@ -1108,23 +1120,31 @@ static void dfhack_event_invoke(lua_State *L, int base, bool from_c)
lua_insert(L, errorfun);
int argbase = base+3;
lua_pushnil(L);
// stack: |base| event errorfun (args) key cb (args)
// stack: |base| event errorfun (args)
while (lua_next(L, event))
if (!from_c)
{
if (from_c && lua_islightuserdata(L, -1) && !lua_touserdata(L, -1))
continue;
// Invoke the NULL key first
lua_rawgetp(L, event, NULL);
for (int i = 0; i < num_args; i++)
lua_pushvalue(L, argbase+i);
if (lua_isnil(L, -1))
lua_pop(L, 1);
else
do_invoke_event(L, argbase, num_args, errorfun);
}
if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK)
{
report_error(L);
lua_pushnil(L);
// stack: |base| event errorfun (args) key || cb (args)
while (lua_next(L, event))
{
// Skip the NULL key in the main loop
if (lua_islightuserdata(L, -2) && !lua_touserdata(L, -2))
lua_pop(L, 1);
}
else
do_invoke_event(L, argbase, num_args, errorfun);
}
lua_settop(L, base);
@ -1285,13 +1305,29 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
return state;
}
void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) {
if (!State) return;
Lua::Push(State, code);
Lua::InvokeEvent(out, State, (void*)onStateChange, 1);
}
void DFHack::Lua::Core::Init(color_ostream &out)
{
if (State)
return;
State = luaL_newstate();
Lua::Open(out, State);
// Register events
lua_getglobal(State, "dfhack");
MakeEvent(State, (void*)onStateChange);
lua_setfield(State, -2, "onStateChange");
lua_pop(State, 1);
}
void DFHack::Lua::Core::Reset(color_ostream &out, const char *where)

@ -733,8 +733,18 @@ static int lookup_container_field(lua_State *state, int field, const char *mode
* Index verification: number and in range.
*/
static int check_container_index(lua_State *state, int len,
int fidx, int iidx, const char *mode)
int fidx, int iidx, const char *mode,
bool is_insert = false)
{
if (is_insert && len >= 0)
{
if (lua_type(state, iidx) == LUA_TSTRING
&& strcmp(lua_tostring(state, iidx), "#") == 0)
return len;
len++;
}
if (!lua_isnumber(state, iidx))
field_error(state, fidx, "invalid index", mode);
@ -867,8 +877,7 @@ static int method_container_insert(lua_State *state)
auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID);
int len = id->lua_item_count(state, ptr, container_identity::COUNT_LEN);
if (len >= 0) len++;
int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call");
int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call", true);
if (!id->lua_insert(state, UPVAL_METHOD_NAME, ptr, idx, 3))
field_error(state, UPVAL_METHOD_NAME, "not supported", "call");

@ -641,6 +641,8 @@ void PluginManager::OnStateChange(color_ostream &out, state_change_event event)
{
all_plugins[i]->on_state_change(out, event);
}
Lua::Core::onStateChange(out, event);
}
// FIXME: doesn't check name collisions!

@ -278,6 +278,9 @@ namespace DFHack {namespace Lua {
void Init(color_ostream &out);
void Reset(color_ostream &out, const char *where);
// Events signalled by the core
void onStateChange(color_ostream &out, int code);
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 PushVector(const T &arg) { Lua::PushVector(State, arg); }

@ -67,13 +67,13 @@ namespace DFHack
enum state_change_event
{
SC_WORLD_LOADED,
SC_WORLD_UNLOADED,
SC_MAP_LOADED,
SC_MAP_UNLOADED,
SC_VIEWSCREEN_CHANGED,
SC_CORE_INITIALIZED,
SC_BEGIN_UNLOAD
SC_WORLD_LOADED = 0,
SC_WORLD_UNLOADED = 1,
SC_MAP_LOADED = 2,
SC_MAP_UNLOADED = 3,
SC_VIEWSCREEN_CHANGED = 4,
SC_CORE_INITIALIZED = 5,
SC_BEGIN_UNLOAD = 6
};
struct DFHACK_EXPORT CommandReg {
const char *name;

@ -21,6 +21,17 @@ COLOR_LIGHTMAGENTA = 13
COLOR_YELLOW = 14
COLOR_WHITE = 15
-- Events
if dfhack.is_core_context then
SC_WORLD_LOADED = 0
SC_WORLD_UNLOADED = 1
SC_MAP_LOADED = 2
SC_MAP_UNLOADED = 3
SC_VIEWSCREEN_CHANGED = 4
SC_CORE_INITIALIZED = 5
end
-- Error handling
safecall = dfhack.safecall