Add an official core lua context, and allow plugins to send events to it.

- This context requires core suspend lock and asserts it in a few places.
- Special 'event' objects are introduced. They can be invoked as
  functions, in which case they iterate all their fields and call
  them as functions. Errors are printed and consumed.
- When a plugin is opened by the core context, events registered in
  a special array are linked to it. The system is organized so as to
  avoid even trying to pass the event to lua if the module isn't loaded.
develop
Alexander Gavrilov 2012-04-15 19:09:25 +04:00
parent cb27a1d839
commit 14709e5d45
16 changed files with 632 additions and 63 deletions

@ -799,3 +799,17 @@ Maps module
* ``dfhack.maps.setBlockBurrowTile(burrow,block,x,y,enable)`` * ``dfhack.maps.setBlockBurrowTile(burrow,block,x,y,enable)``
Adds or removes the tile from the burrow. Returns *false* if invalid coords. Adds or removes the tile from the burrow. Returns *false* if invalid coords.
Core interpreter context
========================
While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the
only context that can receive events from DF and plugins.
Core context specific functions:
* ``dfhack.is_core_context``
Boolean value; *true* in the core context.

@ -344,6 +344,7 @@ 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>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -1010,6 +1011,18 @@ Returns <em>false</em> in case of error.</p>
</ul> </ul>
</div> </div>
</div> </div>
<div class="section" id="core-interpreter-context">
<h2><a class="toc-backref" href="#id19">Core interpreter context</a></h2>
<p>While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the
only context that can receive events from DF and plugins.</p>
<p>Core context specific functions:</p>
<ul>
<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>
</ul>
</div>
</div> </div>
</div> </div>
</body> </body>

@ -49,6 +49,8 @@ using namespace std;
#include "modules/Graphic.h" #include "modules/Graphic.h"
#include "modules/Windows.h" #include "modules/Windows.h"
#include "RemoteServer.h" #include "RemoteServer.h"
#include "LuaTools.h"
using namespace DFHack; using namespace DFHack;
#include "df/ui.h" #include "df/ui.h"
@ -701,6 +703,9 @@ bool Core::Init()
virtual_identity::Init(this); virtual_identity::Init(this);
df::global::InitGlobals(); df::global::InitGlobals();
// initialize common lua context
Lua::Core::Init(con);
// create mutex for syncing with interactive tasks // create mutex for syncing with interactive tasks
misc_data_mutex=new mutex(); misc_data_mutex=new mutex();
cerr << "Initializing Plugins.\n"; cerr << "Initializing Plugins.\n";
@ -803,6 +808,13 @@ void *Core::GetData( std::string key )
} }
} }
bool Core::isSuspended(void)
{
lock_guard<mutex> lock(d->AccessMutex);
return (d->df_suspend_depth > 0 && d->df_suspend_thread == this_thread::get_id());
}
void Core::Suspend() void Core::Suspend()
{ {
auto tid = this_thread::get_id(); auto tid = this_thread::get_id();
@ -882,10 +894,13 @@ int Core::Update()
Init(); Init();
if(errorstate) if(errorstate)
return -1; return -1;
Lua::Core::Reset(con, "core init");
} }
color_ostream_proxy out(con); color_ostream_proxy out(con);
Lua::Core::Reset(out, "DF code execution");
if (first_update) if (first_update)
plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED); plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED);
@ -976,6 +991,8 @@ int Core::Update()
assert(d->df_suspend_depth == 0); assert(d->df_suspend_depth == 0);
// destroy condition // destroy condition
delete nc; delete nc;
// check lua stack depth
Lua::Core::Reset(con, "suspend");
} }
return 0; return 0;

@ -74,30 +74,7 @@ distribution.
using namespace DFHack; using namespace DFHack;
using namespace DFHack::LuaWrapper; using namespace DFHack::LuaWrapper;
template<class T> int Lua::PushPosXYZ(lua_State *state, df::coord pos)
void push_pointer_vector(lua_State *state, const std::vector<T*> &pvec)
{
lua_createtable(state,pvec.size(),0);
for (size_t i = 0; i < pvec.size(); i++)
{
Lua::PushDFObject(state, pvec[i]);
lua_rawseti(state, -2, i+1);
}
}
template<class T>
T *get_checked_arg(lua_State *state, int arg)
{
luaL_checkany(state, arg);
auto ptr = Lua::GetDFObject<T>(state, arg);
if (!ptr)
luaL_argerror(state, arg, "invalid type");
return ptr;
}
int push_pos(lua_State *state, df::coord pos)
{ {
if (!pos.isValid()) if (!pos.isValid())
{ {
@ -570,9 +547,9 @@ static void OpenModule(lua_State *state, const char *mname,
lua_pop(state, 1); lua_pop(state, 1);
} }
#define WRAPM(module, function) { #function, df::wrap_function(module::function) } #define WRAPM(module, function) { #function, df::wrap_function(module::function,true) }
#define WRAP(function) { #function, df::wrap_function(function) } #define WRAP(function) { #function, df::wrap_function(function,true) }
#define WRAPN(name, function) { #name, df::wrap_function(function) } #define WRAPN(name, function) { #name, df::wrap_function(function,true) }
static const LuaWrapper::FunctionReg dfhack_module[] = { static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAPM(Translation, TranslateName), WRAPM(Translation, TranslateName),
@ -613,7 +590,7 @@ static int job_listNewlyCreated(lua_State *state)
if (Job::listNewlyCreated(&pvec, &nxid)) if (Job::listNewlyCreated(&pvec, &nxid))
{ {
lua_pushinteger(state, nxid); lua_pushinteger(state, nxid);
push_pointer_vector(state, pvec); Lua::PushVector(state, pvec);
return 2; return 2;
} }
else else
@ -642,7 +619,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
static int units_getPosition(lua_State *state) static int units_getPosition(lua_State *state)
{ {
return push_pos(state, Units::getPosition(get_checked_arg<df::unit>(state,1))); return Lua::PushPosXYZ(state, Units::getPosition(Lua::CheckDFObject<df::unit>(state,1)));
} }
static const luaL_Reg dfhack_units_funcs[] = { static const luaL_Reg dfhack_units_funcs[] = {
@ -673,14 +650,14 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
static int items_getPosition(lua_State *state) static int items_getPosition(lua_State *state)
{ {
return push_pos(state, Items::getPosition(get_checked_arg<df::item>(state,1))); return Lua::PushPosXYZ(state, Items::getPosition(Lua::CheckDFObject<df::item>(state,1)));
} }
static int items_getContainedItems(lua_State *state) static int items_getContainedItems(lua_State *state)
{ {
std::vector<df::item*> pvec; std::vector<df::item*> pvec;
Items::getContainedItems(get_checked_arg<df::item>(state,1),&pvec); Items::getContainedItems(Lua::CheckDFObject<df::item>(state,1),&pvec);
push_pointer_vector(state, pvec); Lua::PushVector(state, pvec);
return 1; return 1;
} }
@ -719,8 +696,8 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
static int maps_listBurrowBlocks(lua_State *state) static int maps_listBurrowBlocks(lua_State *state)
{ {
std::vector<df::map_block*> pvec; std::vector<df::map_block*> pvec;
Maps::listBurrowBlocks(&pvec, get_checked_arg<df::burrow>(state,1)); Maps::listBurrowBlocks(&pvec, Lua::CheckDFObject<df::burrow>(state,1));
push_pointer_vector(state, pvec); Lua::PushVector(state, pvec);
return 1; return 1;
} }

@ -58,9 +58,18 @@ distribution.
#include <lauxlib.h> #include <lauxlib.h>
#include <lualib.h> #include <lualib.h>
#include <lstate.h>
using namespace DFHack; using namespace DFHack;
using namespace DFHack::LuaWrapper; using namespace DFHack::LuaWrapper;
lua_State *DFHack::Lua::Core::State = NULL;
inline void AssertCoreSuspend(lua_State *state)
{
assert(!Lua::IsCoreContext(state) || DFHack::Core::getInstance().isSuspended());
}
void DFHack::Lua::PushDFObject(lua_State *state, type_identity *type, void *ptr) void DFHack::Lua::PushDFObject(lua_State *state, type_identity *type, void *ptr)
{ {
push_object_internal(state, type, ptr, false); push_object_internal(state, type, ptr, false);
@ -71,6 +80,36 @@ void *DFHack::Lua::GetDFObject(lua_State *state, type_identity *type, int val_in
return get_object_internal(state, type, val_index, exact_type, false); return get_object_internal(state, type, val_index, exact_type, false);
} }
void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type)
{
if (lua_type(state, val_index) == LUA_TNONE)
{
if (val_index > 0)
luaL_argerror(state, val_index, "pointer expected");
else
luaL_error(state, "at index %d: pointer expected", val_index);
}
if (lua_isnil(state, val_index))
return NULL;
void *rv = get_object_internal(state, type, val_index, exact_type, false);
if (!rv)
{
std::string error = "invalid pointer type";
if (type)
error += "; expected: " + type->getFullName();
if (val_index > 0)
luaL_argerror(state, val_index, error.c_str());
else
luaL_error(state, "at index %d: %s", val_index, error.c_str());
}
return rv;
}
static int DFHACK_OSTREAM_TOKEN = 0; static int DFHACK_OSTREAM_TOKEN = 0;
color_ostream *DFHack::Lua::GetOutput(lua_State *L) color_ostream *DFHack::Lua::GetOutput(lua_State *L)
@ -418,6 +457,8 @@ static int lua_dfhack_safecall (lua_State *L)
bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres, bool perr) bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres, bool perr)
{ {
AssertCoreSuspend(L);
int base = lua_gettop(L) - nargs; int base = lua_gettop(L) - nargs;
color_ostream *cur_out = Lua::GetOutput(L); color_ostream *cur_out = Lua::GetOutput(L);
@ -440,13 +481,52 @@ bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres
return ok; return ok;
} }
bool DFHack::Lua::Require(color_ostream &out, lua_State *state, static int DFHACK_LOADED_TOKEN = 0;
const std::string &module, bool setglobal)
bool DFHack::Lua::PushModule(color_ostream &out, lua_State *state, const char *module)
{ {
AssertCoreSuspend(state);
// Check if it is already loaded
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_LOADED_TOKEN);
lua_pushstring(state, module);
lua_rawget(state, -2);
if (lua_toboolean(state, -1))
{
lua_remove(state, -2);
return true;
}
lua_pop(state, 2);
lua_getglobal(state, "require"); lua_getglobal(state, "require");
lua_pushstring(state, module.c_str()); lua_pushstring(state, module);
if (!Lua::SafeCall(out, state, 1, 1)) return Lua::SafeCall(out, state, 1, 1);
}
bool DFHack::Lua::PushModulePublic(color_ostream &out, lua_State *state,
const char *module, const char *name)
{
if (!PushModule(out, state, module))
return false;
if (!lua_istable(state, -1))
{
lua_pop(state, 1);
return false;
}
lua_pushstring(state, name);
lua_rawget(state, -2);
lua_remove(state, -2);
return true;
}
bool DFHack::Lua::Require(color_ostream &out, lua_State *state,
const std::string &module, bool setglobal)
{
if (!PushModule(out, state, module.c_str()))
return false; return false;
if (setglobal) if (setglobal)
@ -471,6 +551,8 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std
int nargs, int nres, bool perr, int nargs, int nres, bool perr,
const char *debug_tag, int env_idx) const char *debug_tag, int env_idx)
{ {
AssertCoreSuspend(state);
if (!debug_tag) if (!debug_tag)
debug_tag = code.c_str(); debug_tag = code.c_str();
if (env_idx) if (env_idx)
@ -507,6 +589,8 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std
bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state,
const char *prompt, int env, const char *hfile) const char *prompt, int env, const char *hfile)
{ {
AssertCoreSuspend(state);
if (!out.is_console()) if (!out.is_console())
return false; return false;
if (!lua_checkstack(state, 20)) if (!lua_checkstack(state, 20))
@ -761,6 +845,15 @@ static int dfhack_open_plugin(lua_State *L)
return 0; return 0;
} }
bool Lua::IsCoreContext(lua_State *state)
{
// This uses a private field of the lua state to
// evaluate the condition without accessing the lua
// stack, and thus requiring a lock on the core state.
return state && Lua::Core::State &&
state->l_G == Lua::Core::State->l_G;
}
static const luaL_Reg dfhack_funcs[] = { static const luaL_Reg dfhack_funcs[] = {
{ "print", lua_dfhack_print }, { "print", lua_dfhack_print },
{ "println", lua_dfhack_println }, { "println", lua_dfhack_println },
@ -777,6 +870,132 @@ static const luaL_Reg dfhack_funcs[] = {
{ NULL, NULL } { NULL, NULL }
}; };
/************
* Events *
************/
static int DFHACK_EVENT_META_TOKEN = 0;
int DFHack::Lua::NewEvent(lua_State *state)
{
lua_newtable(state);
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN);
lua_setmetatable(state, -2);
return 1;
}
static void dfhack_event_invoke(lua_State *L, int base, bool from_c)
{
int event = base+1;
int num_args = lua_gettop(L)-event;
int errorfun = base+2;
lua_pushcfunction(L, dfhack_onerror);
lua_insert(L, errorfun);
int argbase = base+3;
lua_pushnil(L);
// stack: |base| event errorfun (args) key cb (args)
while (lua_next(L, event))
{
if (from_c && lua_islightuserdata(L, -1) && !lua_touserdata(L, -1))
continue;
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);
}
}
lua_settop(L, base);
}
static int dfhack_event_call(lua_State *state)
{
luaL_checktype(state, 1, LUA_TTABLE);
luaL_checkstack(state, lua_gettop(state)+2, "stack overflow in event dispatch");
dfhack_event_invoke(state, 0, false);
return 0;
}
void DFHack::Lua::InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args)
{
AssertCoreSuspend(state);
int base = lua_gettop(state) - num_args;
if (!lua_checkstack(state, num_args+4))
{
out.printerr("Stack overflow in Lua::InvokeEvent");
lua_settop(state, base);
return;
}
lua_rawgetp(state, LUA_REGISTRYINDEX, key);
if (!lua_istable(state, -1))
{
if (!lua_isnil(state, -1))
out.printerr("Invalid event object in Lua::InvokeEvent");
lua_settop(state, base);
return;
}
lua_insert(state, base+1);
color_ostream *cur_out = Lua::GetOutput(state);
set_dfhack_output(state, &out);
dfhack_event_invoke(state, base, true);
set_dfhack_output(state, cur_out);
}
void DFHack::Lua::CreateEvent(lua_State *state, void *key)
{
lua_rawgetp(state, LUA_REGISTRYINDEX, key);
if (lua_isnil(state, -1))
{
lua_pop(state, 1);
NewEvent(state);
}
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, key);
}
void DFHack::Lua::Notification::invoke(color_ostream &out, int nargs)
{
assert(state);
InvokeEvent(out, state, key, nargs);
}
void DFHack::Lua::Notification::bind(lua_State *state, void *key)
{
this->state = state;
this->key = key;
}
void DFHack::Lua::Notification::bind(lua_State *state, const char *name)
{
CreateEvent(state, this);
if (handler)
{
PushFunctionWrapper(state, 0, name, handler);
lua_rawsetp(state, -2, NULL);
}
this->state = state;
this->key = this;
}
/************************ /************************
* Main Open function * * Main Open function *
************************/ ************************/
@ -798,6 +1017,9 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
// Create the dfhack global // Create the dfhack global
lua_newtable(state); lua_newtable(state);
lua_pushboolean(state, IsCoreContext(state));
lua_setfield(state, -2, "is_core_context");
// Create the metatable for exceptions // Create the metatable for exceptions
lua_newtable(state); lua_newtable(state);
lua_pushcfunction(state, dfhack_exception_tostring); lua_pushcfunction(state, dfhack_exception_tostring);
@ -806,6 +1028,15 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN); lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
lua_setfield(state, -2, "exception"); lua_setfield(state, -2, "exception");
lua_newtable(state);
lua_pushcfunction(state, dfhack_event_call);
lua_setfield(state, -2, "__call");
lua_pushcfunction(state, Lua::NewEvent);
lua_setfield(state, -2, "new");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN);
lua_setfield(state, -2, "event");
// Initialize the dfhack global // Initialize the dfhack global
luaL_setfuncs(state, dfhack_funcs, 0); luaL_setfuncs(state, dfhack_funcs, 0);
@ -813,9 +1044,40 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_setglobal(state, "dfhack"); lua_setglobal(state, "dfhack");
// stash the loaded module table into our own registry key
lua_getglobal(state, "package");
assert(lua_istable(state, -1));
lua_getfield(state, -1, "loaded");
assert(lua_istable(state, -1));
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_LOADED_TOKEN);
lua_pop(state, 1);
// load dfhack.lua // load dfhack.lua
Require(out, state, "dfhack"); Require(out, state, "dfhack");
lua_settop(state, 0);
if (!lua_checkstack(state, 64))
out.printerr("Could not extend initial lua stack size to 64 items.\n");
return state; return state;
} }
void DFHack::Lua::Core::Init(color_ostream &out)
{
if (State)
return;
State = luaL_newstate();
Lua::Open(out, State);
}
void DFHack::Lua::Core::Reset(color_ostream &out, const char *where)
{
int top = lua_gettop(State);
if (top != 0)
{
out.printerr("Common lua context stack top left at %d after %s.\n", top, where);
lua_settop(State, 0);
}
}

@ -1036,7 +1036,9 @@ static int meta_call_function(lua_State *state)
int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id) int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id)
{ {
if (lua_gettop(state) != id->getNumArgs()) if (id->adjustArgs())
lua_settop(state, id->getNumArgs());
else if (lua_gettop(state) != id->getNumArgs())
field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke"); field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke");
try { try {
@ -1056,9 +1058,9 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id
} }
/** /**
* Create a closure invoking the given function, and add it to the field table. * Push a closure invoking the given function.
*/ */
static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, void LuaWrapper::PushFunctionWrapper(lua_State *state, int meta_idx,
const char *name, function_identity_base *fun) const char *name, function_identity_base *fun)
{ {
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN); lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN);
@ -1069,7 +1071,15 @@ static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx,
lua_pushfstring(state, "%s()", name); lua_pushfstring(state, "%s()", name);
lua_pushlightuserdata(state, fun); lua_pushlightuserdata(state, fun);
lua_pushcclosure(state, meta_call_function, 4); lua_pushcclosure(state, meta_call_function, 4);
}
/**
* Create a closure invoking the given function, and add it to the field table.
*/
static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx,
const char *name, function_identity_base *fun)
{
PushFunctionWrapper(state, meta_idx, name, fun);
lua_setfield(state, field_idx, name); lua_setfield(state, field_idx, name);
} }

@ -33,6 +33,7 @@ distribution.
#include "MiscUtils.h" #include "MiscUtils.h"
#include "LuaWrapper.h" #include "LuaWrapper.h"
#include "LuaTools.h"
using namespace DFHack; using namespace DFHack;
@ -380,6 +381,7 @@ command_result Plugin::on_update(color_ostream &out)
if(state == PS_LOADED && plugin_onupdate) if(state == PS_LOADED && plugin_onupdate)
{ {
cr = plugin_onupdate(out); cr = plugin_onupdate(out);
Lua::Core::Reset(out, "plugin_onupdate");
} }
access->lock_sub(); access->lock_sub();
return cr; return cr;
@ -392,6 +394,7 @@ command_result Plugin::on_state_change(color_ostream &out, state_change_event ev
if(state == PS_LOADED && plugin_onstatechange) if(state == PS_LOADED && plugin_onstatechange)
{ {
cr = plugin_onstatechange(out, event); cr = plugin_onstatechange(out, event);
Lua::Core::Reset(out, "plugin_onstatechange");
} }
access->lock_sub(); access->lock_sub();
return cr; return cr;
@ -445,9 +448,7 @@ void Plugin::index_lua(DFLibrary *lib)
for (; cmdlist->name; ++cmdlist) for (; cmdlist->name; ++cmdlist)
{ {
auto &cmd = lua_commands[cmdlist->name]; auto &cmd = lua_commands[cmdlist->name];
if (!cmd) cmd = new LuaCommand; if (!cmd) cmd = new LuaCommand(this,cmdlist->name);
cmd->owner = this;
cmd->name = cmdlist->name;
cmd->command = cmdlist->command; cmd->command = cmdlist->command;
} }
} }
@ -456,12 +457,22 @@ void Plugin::index_lua(DFLibrary *lib)
for (; funlist->name; ++funlist) for (; funlist->name; ++funlist)
{ {
auto &cmd = lua_functions[funlist->name]; auto &cmd = lua_functions[funlist->name];
if (!cmd) cmd = new LuaFunction; if (!cmd) cmd = new LuaFunction(this,funlist->name);
cmd->owner = this;
cmd->name = funlist->name;
cmd->identity = funlist->identity; cmd->identity = funlist->identity;
} }
} }
if (auto evlist = (EventReg*)LookupPlugin(lib, "plugin_lua_events"))
{
for (; evlist->name; ++evlist)
{
auto &cmd = lua_events[evlist->name];
if (!cmd) cmd = new LuaEvent(this,evlist->name);
cmd->handler.identity = evlist->event->get_handler();
cmd->event = evlist->event;
if (cmd->active)
cmd->event->bind(Lua::Core::State, cmd);
}
}
} }
void Plugin::reset_lua() void Plugin::reset_lua()
@ -470,6 +481,11 @@ void Plugin::reset_lua()
it->second->command = NULL; it->second->command = NULL;
for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it)
it->second->identity = NULL; it->second->identity = NULL;
for (auto it = lua_events.begin(); it != lua_events.end(); ++it)
{
it->second->handler.identity = NULL;
it->second->event = NULL;
}
} }
int Plugin::lua_cmd_wrapper(lua_State *state) int Plugin::lua_cmd_wrapper(lua_State *state)
@ -512,14 +528,36 @@ void Plugin::open_lua(lua_State *state, int table)
} }
for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it)
{
push_function(state, it->second);
lua_setfield(state, table, it->first.c_str());
}
if (Lua::IsCoreContext(state))
{
for (auto it = lua_events.begin(); it != lua_events.end(); ++it)
{
Lua::CreateEvent(state, it->second);
push_function(state, &it->second->handler);
lua_rawsetp(state, -2, NULL);
it->second->active = true;
if (it->second->event)
it->second->event->bind(state, it->second);
lua_setfield(state, table, it->first.c_str());
}
}
}
void Plugin::push_function(lua_State *state, LuaFunction *fn)
{ {
lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN); lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN);
lua_pushlightuserdata(state, NULL); lua_pushlightuserdata(state, NULL);
lua_pushfstring(state, "%s.%s()", name.c_str(), it->second->name.c_str()); lua_pushfstring(state, "%s.%s()", name.c_str(), fn->name.c_str());
lua_pushlightuserdata(state, it->second); lua_pushlightuserdata(state, fn);
lua_pushcclosure(state, lua_fun_wrapper, 4); lua_pushcclosure(state, lua_fun_wrapper, 4);
lua_setfield(state, table, it->first.c_str());
}
} }
PluginManager::PluginManager(Core * core) PluginManager::PluginManager(Core * core)

@ -90,6 +90,8 @@ namespace DFHack
static Core instance; static Core instance;
return instance; return instance;
} }
/// check if the activity lock is owned by this thread
bool isSuspended(void);
/// try to acquire the activity lock /// try to acquire the activity lock
void Suspend(void); void Suspend(void);
/// return activity lock /// return activity lock

@ -161,15 +161,15 @@ INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (vA1,vA2,vA3,vA4,vA5),
public: public:
typedef function_wrapper<T> wrapper; typedef function_wrapper<T> wrapper;
function_identity(T ptr) function_identity(T ptr, bool vararg)
: function_identity_base(wrapper::num_args), ptr(ptr) {}; : function_identity_base(wrapper::num_args, vararg), ptr(ptr) {};
virtual void invoke(lua_State *state, int base) { wrapper::execute(state, base, ptr); } virtual void invoke(lua_State *state, int base) { wrapper::execute(state, base, ptr); }
}; };
template<class T> template<class T>
inline function_identity_base *wrap_function(T ptr) { inline function_identity_base *wrap_function(T ptr, bool vararg = false) {
// bah, but didn't have any idea how to allocate statically // bah, but didn't have any idea how to allocate statically
return new function_identity<T>(ptr); return new function_identity<T>(ptr, vararg);
} }
} }

@ -39,13 +39,17 @@ namespace DFHack
{ {
class DFHACK_EXPORT function_identity_base : public type_identity { class DFHACK_EXPORT function_identity_base : public type_identity {
int num_args; int num_args;
bool vararg;
public: public:
function_identity_base(int num_args) : type_identity(0), num_args(num_args) {}; function_identity_base(int num_args, bool vararg = false)
: type_identity(0), num_args(num_args), vararg(vararg) {};
virtual identity_type type() { return IDTYPE_FUNCTION; } virtual identity_type type() { return IDTYPE_FUNCTION; }
int getNumArgs() { return num_args; } int getNumArgs() { return num_args; }
bool adjustArgs() { return vararg; }
std::string getFullName() { return "function"; } std::string getFullName() { return "function"; }
virtual void invoke(lua_State *state, int base) = 0; virtual void invoke(lua_State *state, int base) = 0;

@ -34,6 +34,10 @@ distribution.
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
namespace DFHack {
class function_identity_base;
}
namespace DFHack {namespace Lua { namespace DFHack {namespace Lua {
/** /**
* Create or initialize a lua interpreter with access to DFHack tools. * Create or initialize a lua interpreter with access to DFHack tools.
@ -41,11 +45,22 @@ namespace DFHack { namespace Lua {
DFHACK_EXPORT lua_State *Open(color_ostream &out, lua_State *state = NULL); DFHACK_EXPORT lua_State *Open(color_ostream &out, lua_State *state = NULL);
/** /**
* Load a module using require(). * Load a module using require(). Leaves the stack as is.
*/ */
DFHACK_EXPORT bool Require(color_ostream &out, lua_State *state, DFHACK_EXPORT bool Require(color_ostream &out, lua_State *state,
const std::string &module, bool setglobal = false); const std::string &module, bool setglobal = false);
/**
* Push the module table, loading it using require() if necessary.
*/
DFHACK_EXPORT bool PushModule(color_ostream &out, lua_State *state, const char *module);
/**
* Push the public object name exported by the module. Uses PushModule.
*/
DFHACK_EXPORT bool PushModulePublic(color_ostream &out, lua_State *state,
const char *module, const char *name);
/** /**
* Check if the object at the given index is NIL or NULL. * Check if the object at the given index is NIL or NULL.
*/ */
@ -79,6 +94,12 @@ namespace DFHack { namespace Lua {
*/ */
DFHACK_EXPORT void *GetDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false); DFHACK_EXPORT void *GetDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false);
/**
* Check that the value is a wrapped DF object of the given type, and if so return the pointer.
* Otherwise throw an argument type error.
*/
DFHACK_EXPORT void *CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false);
/** /**
* Assign the value at val_index to the target of given identity using df.assign(). * Assign the value at val_index to the target of given identity using df.assign().
* Return behavior is of SafeCall below. * Return behavior is of SafeCall below.
@ -102,6 +123,14 @@ namespace DFHack { namespace Lua {
return (T*)GetDFObject(state, df::identity_traits<T>::get(), val_index, exact_type); return (T*)GetDFObject(state, df::identity_traits<T>::get(), val_index, exact_type);
} }
/**
* Check that the value is a wrapped DF object of the correct type, and if so return the pointer. Otherwise throw an argument type error.
*/
template<class T>
T *CheckDFObject(lua_State *state, int val_index, bool exact_type = false) {
return (T*)CheckDFObject(state, df::identity_traits<T>::get(), val_index, exact_type);
}
/** /**
* Assign the value at val_index to the target using df.assign(). * Assign the value at val_index to the target using df.assign().
*/ */
@ -134,5 +163,146 @@ namespace DFHack { namespace Lua {
*/ */
DFHACK_EXPORT bool InterpreterLoop(color_ostream &out, lua_State *state, DFHACK_EXPORT bool InterpreterLoop(color_ostream &out, lua_State *state,
const char *prompt = NULL, int env = 0, const char *hfile = NULL); const char *prompt = NULL, int env = 0, const char *hfile = NULL);
/**
* Push utility functions
*/
#define NUMBER_PUSH(type) inline void Push(lua_State *state, type value) { lua_pushnumber(state, value); }
NUMBER_PUSH(char)
NUMBER_PUSH(int8_t) NUMBER_PUSH(uint8_t)
NUMBER_PUSH(int16_t) NUMBER_PUSH(uint16_t)
NUMBER_PUSH(int32_t) NUMBER_PUSH(uint32_t)
NUMBER_PUSH(int64_t) NUMBER_PUSH(uint64_t)
NUMBER_PUSH(float) NUMBER_PUSH(double)
#undef NUMBER_PUSH
inline void Push(lua_State *state, bool value) {
lua_pushboolean(state, value);
}
inline void Push(lua_State *state, const std::string &str) {
lua_pushlstring(state, str.data(), str.size());
}
inline void Push(lua_State *state, df::coord &obj) { PushDFObject(state, &obj); }
inline void Push(lua_State *state, df::coord2d &obj) { PushDFObject(state, &obj); }
template<class T> inline void Push(lua_State *state, T *ptr) {
PushDFObject(state, ptr);
}
template<class T>
void PushVector(lua_State *state, const T &pvec)
{
lua_createtable(state,pvec.size(),0);
for (size_t i = 0; i < pvec.size(); i++)
{
Push(state, pvec[i]);
lua_rawseti(state, -2, i+1);
}
}
DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos);
DFHACK_EXPORT bool IsCoreContext(lua_State *state);
DFHACK_EXPORT int NewEvent(lua_State *state);
DFHACK_EXPORT void CreateEvent(lua_State *state, void *key);
DFHACK_EXPORT void InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args);
/**
* Namespace for the common lua interpreter state.
* All accesses must be done under CoreSuspender.
*/
namespace Core {
DFHACK_EXPORT extern lua_State *State;
// Not exported; for use by the Core class
void Init(color_ostream &out);
void Reset(color_ostream &out, const char *where);
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); }
inline bool SafeCall(color_ostream &out, int nargs, int nres, bool perr = true) {
return Lua::SafeCall(out, State, nargs, nres, perr);
}
inline bool PushModule(color_ostream &out, const char *module) {
return Lua::PushModule(out, State, module);
}
inline bool PushModulePublic(color_ostream &out, const char *module, const char *name) {
return Lua::PushModulePublic(out, State, module, name);
}
}
class DFHACK_EXPORT Notification {
lua_State *state;
void *key;
function_identity_base *handler;
public:
Notification(function_identity_base *handler = NULL)
: state(NULL), key(NULL), handler(handler) {}
lua_State *get_state() { return state; }
function_identity_base *get_handler() { return handler; }
void invoke(color_ostream &out, int nargs);
void bind(lua_State *state, const char *name);
void bind(lua_State *state, void *key);
};
}} }}
#define DEFINE_LUA_EVENT_0(name, handler) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out) { \
handler(out); \
if (name##_event.get_state()) { \
name##_event.invoke(out, 0); \
} \
}
#define DEFINE_LUA_EVENT_1(name, handler, arg_type1) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out, arg_type1 arg1) { \
handler(out, arg1); \
if (auto state = name##_event.get_state()) { \
DFHack::Lua::Push(state, arg1); \
name##_event.invoke(out, 1); \
} \
}
#define DEFINE_LUA_EVENT_2(name, handler, arg_type1, arg_type2) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2) { \
handler(out, arg1, arg2); \
if (auto state = name##_event.get_state()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
name##_event.invoke(out, 2); \
} \
}
#define DEFINE_LUA_EVENT_3(name, handler, arg_type1, arg_type2, arg_type3) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3) { \
handler(out, arg1, arg2, arg3); \
if (auto state = name##_event.get_state()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
DFHack::Lua::Push(state, arg3); \
name##_event.invoke(out, 3); \
} \
}
#define DEFINE_LUA_EVENT_4(name, handler, arg_type1, arg_type2, arg_type3, arg_type4) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3, arg_type4 arg4) { \
handler(out, arg1, arg2, arg3, arg4); \
if (auto state = name##_event.get_state()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
DFHack::Lua::Push(state, arg3); \
DFHack::Lua::Push(state, arg4); \
name##_event.invoke(out, 4); \
} \
}

@ -222,6 +222,12 @@ namespace DFHack { namespace LuaWrapper {
*/ */
void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum); void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum);
/**
* Push a closure invoking the given function.
*/
void PushFunctionWrapper(lua_State *state, int meta_idx,
const char *name, function_identity_base *fun);
/** /**
* Wrap functions and add them to the table on the top of the stack. * Wrap functions and add them to the table on the top of the stack.
*/ */

@ -51,6 +51,9 @@ namespace DFHack
class virtual_identity; class virtual_identity;
class RPCService; class RPCService;
class function_identity_base; class function_identity_base;
namespace Lua {
class Notification;
}
// anon type, pretty much // anon type, pretty much
struct DFLibrary; struct DFLibrary;
@ -80,6 +83,10 @@ namespace DFHack
const char *name; const char *name;
function_identity_base *identity; function_identity_base *identity;
}; };
struct DFHACK_EXPORT EventReg {
const char *name;
Lua::Notification *event;
};
struct DFHACK_EXPORT PluginCommand struct DFHACK_EXPORT PluginCommand
{ {
typedef command_result (*command_function)(color_ostream &out, std::vector <std::string> &); typedef command_result (*command_function)(color_ostream &out, std::vector <std::string> &);
@ -178,6 +185,7 @@ namespace DFHack
Plugin *owner; Plugin *owner;
std::string name; std::string name;
int (*command)(lua_State *state); int (*command)(lua_State *state);
LuaCommand(Plugin *owner, std::string name) : owner(owner), name(name) {}
}; };
std::map<std::string, LuaCommand*> lua_commands; std::map<std::string, LuaCommand*> lua_commands;
static int lua_cmd_wrapper(lua_State *state); static int lua_cmd_wrapper(lua_State *state);
@ -186,9 +194,19 @@ namespace DFHack
Plugin *owner; Plugin *owner;
std::string name; std::string name;
function_identity_base *identity; function_identity_base *identity;
LuaFunction(Plugin *owner, std::string name) : owner(owner), name(name) {}
}; };
std::map<std::string, LuaFunction*> lua_functions; std::map<std::string, LuaFunction*> lua_functions;
static int lua_fun_wrapper(lua_State *state); static int lua_fun_wrapper(lua_State *state);
void push_function(lua_State *state, LuaFunction *fn);
struct LuaEvent {
LuaFunction handler;
Lua::Notification *event;
bool active;
LuaEvent(Plugin *owner, std::string name) : handler(owner,name), active(false) {}
};
std::map<std::string, LuaEvent*> lua_events;
void index_lua(DFLibrary *lib); void index_lua(DFLibrary *lib);
void reset_lua(); void reset_lua();
@ -253,7 +271,10 @@ namespace DFHack
DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = DFhackCExport const DFHack::CommandReg plugin_lua_commands[] =
#define DFHACK_PLUGIN_LUA_FUNCTIONS \ #define DFHACK_PLUGIN_LUA_FUNCTIONS \
DFhackCExport const DFHack::FunctionReg plugin_lua_functions[] = DFhackCExport const DFHack::FunctionReg plugin_lua_functions[] =
#define DFHACK_PLUGIN_LUA_EVENTS \
DFhackCExport const DFHack::EventReg plugin_lua_events[] =
#define DFHACK_LUA_COMMAND(name) { #name, name } #define DFHACK_LUA_COMMAND(name) { #name, name }
#define DFHACK_LUA_FUNCTION(name) { #name, df::wrap_function(name) } #define DFHACK_LUA_FUNCTION(name) { #name, df::wrap_function(name,true) }
#define DFHACK_LUA_EVENT(name) { #name, &name##_event }
#define DFHACK_LUA_END { NULL, NULL } #define DFHACK_LUA_END { NULL, NULL }

@ -105,6 +105,10 @@ function xyz2pos(x,y,z)
end end
end end
function dfhack.event:__tostring()
return "<event>"
end
function dfhack.persistent:__tostring() function dfhack.persistent:__tostring()
return "<persistent "..self.entry_id..":"..self.key.."=\"" return "<persistent "..self.entry_id..":"..self.key.."=\""
..self.value.."\":"..table.concat(self.ints,",")..">" ..self.value.."\":"..table.concat(self.ints,",")..">"

@ -121,6 +121,13 @@ command_result lua_run_file (color_ostream &out, std::vector <std::string> &para
} }
command_result lua_run (color_ostream &out, std::vector <std::string> &parameters) command_result lua_run (color_ostream &out, std::vector <std::string> &parameters)
{ {
if (!parameters.empty() && parameters[0] == "--core-context")
{
CoreSuspender suspend;
Lua::InterpreterLoop(out, Lua::Core::State);
return CR_OK;
}
mymutex->lock(); mymutex->lock();
lua::state s=lua::glua::Get(); lua::state s=lua::glua::Get();

@ -5,6 +5,7 @@
#include "Error.h" #include "Error.h"
#include "DataFuncs.h" #include "DataFuncs.h"
#include "LuaTools.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/Job.h" #include "modules/Job.h"
@ -121,6 +122,8 @@ static int name_burrow_id = -1;
static void handle_burrow_rename(color_ostream &out, df::burrow *burrow); static void handle_burrow_rename(color_ostream &out, df::burrow *burrow);
DEFINE_LUA_EVENT_1(onBurrowRename, handle_burrow_rename, df::burrow*);
static void detect_burrow_renames(color_ostream &out) static void detect_burrow_renames(color_ostream &out)
{ {
if (ui->main.mode == ui_sidebar_mode::Burrows && if (ui->main.mode == ui_sidebar_mode::Burrows &&
@ -134,7 +137,7 @@ static void detect_burrow_renames(color_ostream &out)
auto burrow = df::burrow::find(name_burrow_id); auto burrow = df::burrow::find(name_burrow_id);
name_burrow_id = -1; name_burrow_id = -1;
if (burrow) if (burrow)
handle_burrow_rename(out, burrow); onBurrowRename(out, burrow);
} }
} }
@ -151,6 +154,9 @@ static std::map<int,DigJob> diggers;
static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos, static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos,
df::tiletype old_tile, df::tiletype new_tile); df::tiletype old_tile, df::tiletype new_tile);
DEFINE_LUA_EVENT_4(onDigComplete, handle_dig_complete,
df::job_type, df::coord, df::tiletype, df::tiletype);
static void detect_digging(color_ostream &out) static void detect_digging(color_ostream &out)
{ {
for (auto it = diggers.begin(); it != diggers.end();) for (auto it = diggers.begin(); it != diggers.end();)
@ -172,7 +178,7 @@ static void detect_digging(color_ostream &out)
if (new_tile != it->second.old_tile) if (new_tile != it->second.old_tile)
{ {
handle_dig_complete(out, it->second.job, pos, it->second.old_tile, new_tile); onDigComplete(out, it->second.job, pos, it->second.old_tile, new_tile);
//if (worker && !worker->job.current_job) //if (worker && !worker->job.current_job)
// worker->counters.think_counter = worker->counters.job_counter = 0; // worker->counters.think_counter = worker->counters.job_counter = 0;
@ -410,6 +416,17 @@ static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord
} }
} }
static void renameBurrow(color_ostream &out, df::burrow *burrow, std::string name)
{
CHECK_NULL_POINTER(burrow);
// The event makes this absolutely necessary
CoreSuspender suspend;
burrow->name = name;
onBurrowRename(out, burrow);
}
static df::burrow *findByName(color_ostream &out, std::string name, bool silent = false) static df::burrow *findByName(color_ostream &out, std::string name, bool silent = false)
{ {
int id = -1; int id = -1;
@ -552,6 +569,7 @@ static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable)
} }
DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(renameBurrow),
DFHACK_LUA_FUNCTION(findByName), DFHACK_LUA_FUNCTION(findByName),
DFHACK_LUA_FUNCTION(copyUnits), DFHACK_LUA_FUNCTION(copyUnits),
DFHACK_LUA_FUNCTION(copyTiles), DFHACK_LUA_FUNCTION(copyTiles),
@ -559,6 +577,12 @@ DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_END DFHACK_LUA_END
}; };
DFHACK_PLUGIN_LUA_EVENTS {
DFHACK_LUA_EVENT(onBurrowRename),
DFHACK_LUA_EVENT(onDigComplete),
DFHACK_LUA_END
};
static command_result burrow(color_ostream &out, vector <string> &parameters) static command_result burrow(color_ostream &out, vector <string> &parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;