Export the onStateChange event to core lua context & add some docs.

develop
Alexander Gavrilov 2012-04-17 11:45:09 +04:00
parent 7946cafc86
commit 3beb2ebf25
7 changed files with 151 additions and 19 deletions

@ -428,6 +428,11 @@ Currently it defines the following features:
If the thread owns the interactive console, shows a prompt If the thread owns the interactive console, shows a prompt
and returns the entered string. Otherwise returns *nil, error*. 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]]])`` * ``dfhack.interpreter([prompt[,env[,history_filename]]])``
Starts an interactive lua interpreter, using the specified prompt 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 Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function. returning. Intended as a convenience function.
* ``dfhack.saferesume(coroutine[,args...])``
Compares to coroutine.resume like dfhack.safecall vs pcall.
* ``dfhack.with_suspend(f[,args...])`` * ``dfhack.with_suspend(f[,args...])``
Calls ``f`` with arguments after grabbing the DF core suspend lock. Calls ``f`` with arguments after grabbing the DF core suspend lock.
@ -813,3 +822,37 @@ Core context specific functions:
* ``dfhack.is_core_context`` * ``dfhack.is_core_context``
Boolean value; *true* in the 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> <li><a class="reference internal" href="#maps-module" id="id18">Maps module</a></li>
</ul> </ul>
</li> </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> </ul>
</li> </li>
</ul> </ul>
@ -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> <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 <p>If the thread owns the interactive console, shows a prompt
and returns the entered string. Otherwise returns <em>nil, error</em>.</p> 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>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.interpreter([prompt[,env[,history_filename]]])</span></tt></p> <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 <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 <p>Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function.</p> returning. Intended as a convenience function.</p>
</li> </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> <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. <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> 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> <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.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> </ul>
</div> </div>
</div> </div>
</div> </div>
</div>
</body> </body>
</html> </html>

@ -1098,6 +1098,18 @@ int DFHack::Lua::NewEvent(lua_State *state)
return 1; 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) static void dfhack_event_invoke(lua_State *L, int base, bool from_c)
{ {
int event = base+1; 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); lua_insert(L, errorfun);
int argbase = base+3; 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)) // Invoke the NULL key first
continue; lua_rawgetp(L, event, NULL);
for (int i = 0; i < num_args; i++) if (lua_isnil(L, -1))
lua_pushvalue(L, argbase+i); lua_pop(L, 1);
else
do_invoke_event(L, argbase, num_args, errorfun);
}
if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK) lua_pushnil(L);
{
report_error(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); lua_pop(L, 1);
} else
do_invoke_event(L, argbase, num_args, errorfun);
} }
lua_settop(L, base); lua_settop(L, base);
@ -1285,13 +1305,29 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
return 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) void DFHack::Lua::Core::Init(color_ostream &out)
{ {
if (State) if (State)
return; return;
State = luaL_newstate(); State = luaL_newstate();
Lua::Open(out, State); 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) void DFHack::Lua::Core::Reset(color_ostream &out, const char *where)

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

@ -278,6 +278,9 @@ namespace DFHack {namespace Lua {
void Init(color_ostream &out); void Init(color_ostream &out);
void Reset(color_ostream &out, const char *where); 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(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); }
template<class T> inline void PushVector(const T &arg) { Lua::PushVector(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 enum state_change_event
{ {
SC_WORLD_LOADED, SC_WORLD_LOADED = 0,
SC_WORLD_UNLOADED, SC_WORLD_UNLOADED = 1,
SC_MAP_LOADED, SC_MAP_LOADED = 2,
SC_MAP_UNLOADED, SC_MAP_UNLOADED = 3,
SC_VIEWSCREEN_CHANGED, SC_VIEWSCREEN_CHANGED = 4,
SC_CORE_INITIALIZED, SC_CORE_INITIALIZED = 5,
SC_BEGIN_UNLOAD SC_BEGIN_UNLOAD = 6
}; };
struct DFHACK_EXPORT CommandReg { struct DFHACK_EXPORT CommandReg {
const char *name; const char *name;

@ -21,6 +21,17 @@ COLOR_LIGHTMAGENTA = 13
COLOR_YELLOW = 14 COLOR_YELLOW = 14
COLOR_WHITE = 15 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 -- Error handling
safecall = dfhack.safecall safecall = dfhack.safecall