From 14709e5d4598e11ddce6f9d2cce734528d842ca5 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 15 Apr 2012 19:09:25 +0400 Subject: [PATCH] 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. --- LUA_API.rst | 14 ++ Lua API.html | 13 ++ library/Core.cpp | 17 ++ library/LuaApi.cpp | 45 ++---- library/LuaTools.cpp | 270 +++++++++++++++++++++++++++++++- library/LuaTypes.cpp | 18 ++- library/PluginManager.cpp | 60 +++++-- library/include/Core.h | 2 + library/include/DataFuncs.h | 8 +- library/include/DataIdentity.h | 6 +- library/include/LuaTools.h | 174 +++++++++++++++++++- library/include/LuaWrapper.h | 6 + library/include/PluginManager.h | 23 ++- library/lua/dfhack.lua | 4 + plugins/Dfusion/dfusion.cpp | 7 + plugins/burrows.cpp | 28 +++- 16 files changed, 632 insertions(+), 63 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index d5af5e7a4..e68a87a66 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -799,3 +799,17 @@ Maps module * ``dfhack.maps.setBlockBurrowTile(burrow,block,x,y,enable)`` 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. diff --git a/Lua API.html b/Lua API.html index 66385840b..38a375d86 100644 --- a/Lua API.html +++ b/Lua API.html @@ -344,6 +344,7 @@ ul.auto-toc {
  • Maps module
  • +
  • Core interpreter context
  • @@ -1010,6 +1011,18 @@ Returns false in case of error.

    +
    +

    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:

    + +
    diff --git a/library/Core.cpp b/library/Core.cpp index e6b9c45ff..16d90bfb1 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -49,6 +49,8 @@ using namespace std; #include "modules/Graphic.h" #include "modules/Windows.h" #include "RemoteServer.h" +#include "LuaTools.h" + using namespace DFHack; #include "df/ui.h" @@ -701,6 +703,9 @@ bool Core::Init() virtual_identity::Init(this); df::global::InitGlobals(); + // initialize common lua context + Lua::Core::Init(con); + // create mutex for syncing with interactive tasks misc_data_mutex=new mutex(); cerr << "Initializing Plugins.\n"; @@ -803,6 +808,13 @@ void *Core::GetData( std::string key ) } } +bool Core::isSuspended(void) +{ + lock_guard lock(d->AccessMutex); + + return (d->df_suspend_depth > 0 && d->df_suspend_thread == this_thread::get_id()); +} + void Core::Suspend() { auto tid = this_thread::get_id(); @@ -882,10 +894,13 @@ int Core::Update() Init(); if(errorstate) return -1; + Lua::Core::Reset(con, "core init"); } color_ostream_proxy out(con); + Lua::Core::Reset(out, "DF code execution"); + if (first_update) plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED); @@ -976,6 +991,8 @@ int Core::Update() assert(d->df_suspend_depth == 0); // destroy condition delete nc; + // check lua stack depth + Lua::Core::Reset(con, "suspend"); } return 0; diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 917e44d38..5f2195760 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -74,30 +74,7 @@ distribution. using namespace DFHack; using namespace DFHack::LuaWrapper; -template -void push_pointer_vector(lua_State *state, const std::vector &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 -T *get_checked_arg(lua_State *state, int arg) -{ - luaL_checkany(state, arg); - - auto ptr = Lua::GetDFObject(state, arg); - if (!ptr) - luaL_argerror(state, arg, "invalid type"); - return ptr; -} - -int push_pos(lua_State *state, df::coord pos) +int Lua::PushPosXYZ(lua_State *state, df::coord pos) { if (!pos.isValid()) { @@ -570,9 +547,9 @@ static void OpenModule(lua_State *state, const char *mname, lua_pop(state, 1); } -#define WRAPM(module, function) { #function, df::wrap_function(module::function) } -#define WRAP(function) { #function, df::wrap_function(function) } -#define WRAPN(name, function) { #name, df::wrap_function(function) } +#define WRAPM(module, function) { #function, df::wrap_function(module::function,true) } +#define WRAP(function) { #function, df::wrap_function(function,true) } +#define WRAPN(name, function) { #name, df::wrap_function(function,true) } static const LuaWrapper::FunctionReg dfhack_module[] = { WRAPM(Translation, TranslateName), @@ -613,7 +590,7 @@ static int job_listNewlyCreated(lua_State *state) if (Job::listNewlyCreated(&pvec, &nxid)) { lua_pushinteger(state, nxid); - push_pointer_vector(state, pvec); + Lua::PushVector(state, pvec); return 2; } else @@ -642,7 +619,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { static int units_getPosition(lua_State *state) { - return push_pos(state, Units::getPosition(get_checked_arg(state,1))); + return Lua::PushPosXYZ(state, Units::getPosition(Lua::CheckDFObject(state,1))); } 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) { - return push_pos(state, Items::getPosition(get_checked_arg(state,1))); + return Lua::PushPosXYZ(state, Items::getPosition(Lua::CheckDFObject(state,1))); } static int items_getContainedItems(lua_State *state) { std::vector pvec; - Items::getContainedItems(get_checked_arg(state,1),&pvec); - push_pointer_vector(state, pvec); + Items::getContainedItems(Lua::CheckDFObject(state,1),&pvec); + Lua::PushVector(state, pvec); return 1; } @@ -719,8 +696,8 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { static int maps_listBurrowBlocks(lua_State *state) { std::vector pvec; - Maps::listBurrowBlocks(&pvec, get_checked_arg(state,1)); - push_pointer_vector(state, pvec); + Maps::listBurrowBlocks(&pvec, Lua::CheckDFObject(state,1)); + Lua::PushVector(state, pvec); return 1; } diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 5129ba648..304d79bcc 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -58,9 +58,18 @@ distribution. #include #include +#include + using namespace DFHack; 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) { 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); } +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; 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) { + AssertCoreSuspend(L); + int base = lua_gettop(L) - nargs; 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; } -bool DFHack::Lua::Require(color_ostream &out, lua_State *state, - const std::string &module, bool setglobal) +static int DFHACK_LOADED_TOKEN = 0; + +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_pushstring(state, module.c_str()); + lua_pushstring(state, module); + + return Lua::SafeCall(out, state, 1, 1); +} - if (!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; if (setglobal) @@ -471,6 +551,8 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std int nargs, int nres, bool perr, const char *debug_tag, int env_idx) { + AssertCoreSuspend(state); + if (!debug_tag) debug_tag = code.c_str(); 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, const char *prompt, int env, const char *hfile) { + AssertCoreSuspend(state); + if (!out.is_console()) return false; if (!lua_checkstack(state, 20)) @@ -761,6 +845,15 @@ static int dfhack_open_plugin(lua_State *L) 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[] = { { "print", lua_dfhack_print }, { "println", lua_dfhack_println }, @@ -777,6 +870,132 @@ static const luaL_Reg dfhack_funcs[] = { { 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 * ************************/ @@ -798,6 +1017,9 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) // Create the dfhack global lua_newtable(state); + lua_pushboolean(state, IsCoreContext(state)); + lua_setfield(state, -2, "is_core_context"); + // Create the metatable for exceptions lua_newtable(state); 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_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 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"); + // 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 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; } +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); + } +} diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index f5ba565d5..c5ffe0f6b 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1036,7 +1036,9 @@ static int meta_call_function(lua_State *state) 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"); try { @@ -1056,10 +1058,10 @@ 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, - const char *name, function_identity_base *fun) +void LuaWrapper::PushFunctionWrapper(lua_State *state, int meta_idx, + const char *name, function_identity_base *fun) { lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN); if (meta_idx) @@ -1069,7 +1071,15 @@ static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, lua_pushfstring(state, "%s()", name); lua_pushlightuserdata(state, fun); 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); } diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 4d0c06ddf..edc7fb9f8 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -33,6 +33,7 @@ distribution. #include "MiscUtils.h" #include "LuaWrapper.h" +#include "LuaTools.h" using namespace DFHack; @@ -380,6 +381,7 @@ command_result Plugin::on_update(color_ostream &out) if(state == PS_LOADED && plugin_onupdate) { cr = plugin_onupdate(out); + Lua::Core::Reset(out, "plugin_onupdate"); } access->lock_sub(); 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) { cr = plugin_onstatechange(out, event); + Lua::Core::Reset(out, "plugin_onstatechange"); } access->lock_sub(); return cr; @@ -445,9 +448,7 @@ void Plugin::index_lua(DFLibrary *lib) for (; cmdlist->name; ++cmdlist) { auto &cmd = lua_commands[cmdlist->name]; - if (!cmd) cmd = new LuaCommand; - cmd->owner = this; - cmd->name = cmdlist->name; + if (!cmd) cmd = new LuaCommand(this,cmdlist->name); cmd->command = cmdlist->command; } } @@ -456,12 +457,22 @@ void Plugin::index_lua(DFLibrary *lib) for (; funlist->name; ++funlist) { auto &cmd = lua_functions[funlist->name]; - if (!cmd) cmd = new LuaFunction; - cmd->owner = this; - cmd->name = funlist->name; + if (!cmd) cmd = new LuaFunction(this,funlist->name); 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() @@ -470,6 +481,11 @@ void Plugin::reset_lua() it->second->command = NULL; for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) 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) @@ -513,13 +529,35 @@ void Plugin::open_lua(lua_State *state, int table) for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) { - lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN); - lua_pushlightuserdata(state, NULL); - lua_pushfstring(state, "%s.%s()", name.c_str(), it->second->name.c_str()); - lua_pushlightuserdata(state, it->second); - lua_pushcclosure(state, lua_fun_wrapper, 4); + 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_pushlightuserdata(state, NULL); + lua_pushfstring(state, "%s.%s()", name.c_str(), fn->name.c_str()); + lua_pushlightuserdata(state, fn); + lua_pushcclosure(state, lua_fun_wrapper, 4); } PluginManager::PluginManager(Core * core) diff --git a/library/include/Core.h b/library/include/Core.h index f8ac8e783..d18ebfa71 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -90,6 +90,8 @@ namespace DFHack static Core instance; return instance; } + /// check if the activity lock is owned by this thread + bool isSuspended(void); /// try to acquire the activity lock void Suspend(void); /// return activity lock diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index a949e784b..8b917ad70 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -161,15 +161,15 @@ INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (vA1,vA2,vA3,vA4,vA5), public: typedef function_wrapper wrapper; - function_identity(T ptr) - : function_identity_base(wrapper::num_args), ptr(ptr) {}; + function_identity(T ptr, bool vararg) + : function_identity_base(wrapper::num_args, vararg), ptr(ptr) {}; virtual void invoke(lua_State *state, int base) { wrapper::execute(state, base, ptr); } }; template - 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 - return new function_identity(ptr); + return new function_identity(ptr, vararg); } } \ No newline at end of file diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index 279400f74..a775c85b2 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -39,13 +39,17 @@ namespace DFHack { class DFHACK_EXPORT function_identity_base : public type_identity { int num_args; + bool vararg; 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; } int getNumArgs() { return num_args; } + bool adjustArgs() { return vararg; } + std::string getFullName() { return "function"; } virtual void invoke(lua_State *state, int base) = 0; diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 7e3ed1683..70a2e5fca 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -34,18 +34,33 @@ distribution. #include #include -namespace DFHack { namespace Lua { +namespace DFHack { + class function_identity_base; +} + +namespace DFHack {namespace Lua { /** * Create or initialize a lua interpreter with access to DFHack tools. */ 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, 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. */ @@ -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); + /** + * 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(). * Return behavior is of SafeCall below. @@ -102,6 +123,14 @@ namespace DFHack { namespace Lua { return (T*)GetDFObject(state, df::identity_traits::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 + T *CheckDFObject(lua_State *state, int val_index, bool exact_type = false) { + return (T*)CheckDFObject(state, df::identity_traits::get(), val_index, exact_type); + } + /** * 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, 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 inline void Push(lua_State *state, T *ptr) { + PushDFObject(state, ptr); + } + + template + 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 inline void Push(T &arg) { Lua::Push(State, arg); } + template inline void Push(const T &arg) { Lua::Push(State, arg); } + template 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); \ + } \ + } diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 2304b940d..97b2e6980 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -222,6 +222,12 @@ namespace DFHack { namespace LuaWrapper { */ 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. */ diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 080e66e66..a51d90747 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -51,6 +51,9 @@ namespace DFHack class virtual_identity; class RPCService; class function_identity_base; + namespace Lua { + class Notification; + } // anon type, pretty much struct DFLibrary; @@ -80,6 +83,10 @@ namespace DFHack const char *name; function_identity_base *identity; }; + struct DFHACK_EXPORT EventReg { + const char *name; + Lua::Notification *event; + }; struct DFHACK_EXPORT PluginCommand { typedef command_result (*command_function)(color_ostream &out, std::vector &); @@ -178,6 +185,7 @@ namespace DFHack Plugin *owner; std::string name; int (*command)(lua_State *state); + LuaCommand(Plugin *owner, std::string name) : owner(owner), name(name) {} }; std::map lua_commands; static int lua_cmd_wrapper(lua_State *state); @@ -186,9 +194,19 @@ namespace DFHack Plugin *owner; std::string name; function_identity_base *identity; + LuaFunction(Plugin *owner, std::string name) : owner(owner), name(name) {} }; std::map lua_functions; 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 lua_events; void index_lua(DFLibrary *lib); void reset_lua(); @@ -253,7 +271,10 @@ namespace DFHack DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = #define DFHACK_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_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 } diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 7230a12aa..63de69864 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -105,6 +105,10 @@ function xyz2pos(x,y,z) end end +function dfhack.event:__tostring() + return "" +end + function dfhack.persistent:__tostring() return "" diff --git a/plugins/Dfusion/dfusion.cpp b/plugins/Dfusion/dfusion.cpp index 0886d2a34..cfd2e27cd 100644 --- a/plugins/Dfusion/dfusion.cpp +++ b/plugins/Dfusion/dfusion.cpp @@ -121,6 +121,13 @@ command_result lua_run_file (color_ostream &out, std::vector ¶ } command_result lua_run (color_ostream &out, std::vector ¶meters) { + if (!parameters.empty() && parameters[0] == "--core-context") + { + CoreSuspender suspend; + Lua::InterpreterLoop(out, Lua::Core::State); + return CR_OK; + } + mymutex->lock(); lua::state s=lua::glua::Get(); diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index 74cb57c45..a629e30c1 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -5,6 +5,7 @@ #include "Error.h" #include "DataFuncs.h" +#include "LuaTools.h" #include "modules/Gui.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); +DEFINE_LUA_EVENT_1(onBurrowRename, handle_burrow_rename, df::burrow*); + static void detect_burrow_renames(color_ostream &out) { 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); name_burrow_id = -1; if (burrow) - handle_burrow_rename(out, burrow); + onBurrowRename(out, burrow); } } @@ -151,6 +154,9 @@ static std::map diggers; static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos, 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) { 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) { - 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) // 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) { int id = -1; @@ -552,6 +569,7 @@ static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable) } DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(renameBurrow), DFHACK_LUA_FUNCTION(findByName), DFHACK_LUA_FUNCTION(copyUnits), DFHACK_LUA_FUNCTION(copyTiles), @@ -559,6 +577,12 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { 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 ¶meters) { CoreSuspender suspend;