summaryrefslogtreecommitdiff
path: root/library/LuaTools.cpp
diff options
context:
space:
mode:
authorAlexander Gavrilov2012-04-15 19:09:25 +0400
committerAlexander Gavrilov2012-04-15 19:09:25 +0400
commit14709e5d4598e11ddce6f9d2cce734528d842ca5 (patch)
treeb1370d42d766b329b1ce8fc5431b4e7abf04a4b8 /library/LuaTools.cpp
parentcb27a1d83916b333d1a0d1b5aa24a7f371e120af (diff)
downloaddfhack-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.cpp270
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);
+ }
+}