diff options
| author | Alexander Gavrilov | 2012-04-15 19:09:25 +0400 |
|---|---|---|
| committer | Alexander Gavrilov | 2012-04-15 19:09:25 +0400 |
| commit | 14709e5d4598e11ddce6f9d2cce734528d842ca5 (patch) | |
| tree | b1370d42d766b329b1ce8fc5431b4e7abf04a4b8 /library/LuaTools.cpp | |
| parent | cb27a1d83916b333d1a0d1b5aa24a7f371e120af (diff) | |
| download | dfhack-14709e5d4598e11ddce6f9d2cce734528d842ca5.tar.gz dfhack-14709e5d4598e11ddce6f9d2cce734528d842ca5.tar.bz2 dfhack-14709e5d4598e11ddce6f9d2cce734528d842ca5.tar.xz | |
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.
Diffstat (limited to 'library/LuaTools.cpp')
| -rw-r--r-- | library/LuaTools.cpp | 270 |
1 files changed, 266 insertions, 4 deletions
diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 5129ba64..304d79bc 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -58,9 +58,18 @@ distribution. #include <lauxlib.h> #include <lualib.h> +#include <lstate.h> + 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); + } +} |
