diff options
| author | Alexander Gavrilov | 2012-08-19 14:27:44 +0400 |
|---|---|---|
| committer | Alexander Gavrilov | 2012-08-19 14:27:44 +0400 |
| commit | 30f71ff5106d271d04bfa26b976441cfa9b2abf6 (patch) | |
| tree | ced0b058f615b90a75c495be1f4db6045100b972 /library | |
| parent | b8ee52131bccd174f06c9124980bbcb8df7c80e4 (diff) | |
| download | dfhack-30f71ff5106d271d04bfa26b976441cfa9b2abf6.tar.gz dfhack-30f71ff5106d271d04bfa26b976441cfa9b2abf6.tar.bz2 dfhack-30f71ff5106d271d04bfa26b976441cfa9b2abf6.tar.xz | |
Implement support for lua-backed viewscreens.
Diffstat (limited to 'library')
| -rw-r--r-- | library/LuaApi.cpp | 148 | ||||
| -rw-r--r-- | library/LuaTypes.cpp | 22 | ||||
| -rw-r--r-- | library/PluginManager.cpp | 2 | ||||
| -rw-r--r-- | library/include/LuaTools.h | 10 | ||||
| -rw-r--r-- | library/include/modules/Screen.h | 37 | ||||
| -rw-r--r-- | library/lua/dfhack.lua | 6 | ||||
| -rw-r--r-- | library/modules/Screen.cpp | 262 |
7 files changed, 486 insertions, 1 deletions
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 108dba88..d296a2e5 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -39,6 +39,7 @@ distribution. #include "modules/World.h" #include "modules/Gui.h" +#include "modules/Screen.h" #include "modules/Job.h" #include "modules/Translation.h" #include "modules/Units.h" @@ -84,6 +85,8 @@ distribution. using namespace DFHack; using namespace DFHack::LuaWrapper; +using Screen::Pen; + void dfhack_printerr(lua_State *S, const std::string &str); void Lua::Push(lua_State *state, const Units::NoblePosition &pos) @@ -179,6 +182,48 @@ static df::coord CheckCoordXYZ(lua_State *state, int base, bool vararg = false) return p; } +template<class T> +static bool get_int_field(lua_State *L, T *pf, int idx, const char *name, int defval) +{ + lua_getfield(L, idx, name); + bool nil = lua_isnil(L, -1); + if (nil) *pf = T(defval); + else if (lua_isnumber(L, -1)) *pf = T(lua_tointeger(L, -1)); + else luaL_error(L, "Field %s is not a number.", name); + lua_pop(L, 1); + return !nil; +} + +static void decode_pen(lua_State *L, Pen &pen, int idx) +{ + get_int_field(L, &pen.ch, idx, "ch", 0); + get_int_field(L, &pen.fg, idx, "fg", 7); + get_int_field(L, &pen.bg, idx, "bg", 0); + + lua_getfield(L, idx, "bold"); + if (lua_isnil(L, -1)) + { + pen.bold = (pen.fg & 8) != 0; + pen.fg &= 7; + } + else pen.bold = lua_toboolean(L, -1); + lua_pop(L, 1); + + get_int_field(L, &pen.tile, idx, "tile", 0); + + bool tcolor = get_int_field(L, &pen.tile_fg, idx, "tile_fg", 7); + tcolor = get_int_field(L, &pen.tile_bg, idx, "tile_bg", 0) || tcolor; + + if (tcolor) + pen.tile_mode = Pen::TileColor; + else + { + lua_getfield(L, idx, "tile_color"); + pen.tile_mode = (lua_toboolean(L, -1) ? Pen::CharColor : Pen::AsIs); + lua_pop(L, 1); + } +} + /************************************************** * Per-world persistent configuration storage API * **************************************************/ @@ -1019,6 +1064,108 @@ static const luaL_Reg dfhack_constructions_funcs[] = { { NULL, NULL } }; +/***** Screen module *****/ + +static const LuaWrapper::FunctionReg dfhack_screen_module[] = { + WRAPM(Screen, clear), + WRAPM(Screen, invalidate), + { NULL, NULL } +}; + +static int screen_getMousePos(lua_State *L) +{ + auto pos = Screen::getMousePos(); + lua_pushinteger(L, pos.x); + lua_pushinteger(L, pos.y); + return 2; +} + +static int screen_getWindowSize(lua_State *L) +{ + auto pos = Screen::getWindowSize(); + lua_pushinteger(L, pos.x); + lua_pushinteger(L, pos.y); + return 2; +} + +static int screen_paintTile(lua_State *L) +{ + Pen pen; + decode_pen(L, pen, 1); + int x = luaL_checkint(L, 2); + int y = luaL_checkint(L, 3); + if (lua_gettop(L) >= 4 && !lua_isnil(L, 4)) + pen.ch = luaL_checkint(L, 4); + if (lua_gettop(L) >= 5 && !lua_isnil(L, 5)) + pen.tile = luaL_checkint(L, 5); + lua_pushboolean(L, Screen::paintTile(pen, x, y)); + return 1; +} + +static int screen_paintString(lua_State *L) +{ + Pen pen; + decode_pen(L, pen, 1); + int x = luaL_checkint(L, 2); + int y = luaL_checkint(L, 3); + const char *text = luaL_checkstring(L, 4); + lua_pushboolean(L, Screen::paintString(pen, x, y, text)); + return 1; +} + +static int screen_fillRect(lua_State *L) +{ + Pen pen; + decode_pen(L, pen, 1); + int x1 = luaL_checkint(L, 2); + int y1 = luaL_checkint(L, 3); + int x2 = luaL_checkint(L, 4); + int y2 = luaL_checkint(L, 5); + lua_pushboolean(L, Screen::fillRect(pen, x1, y1, x2, y2)); + return 1; +} + +namespace { + +int screen_show(lua_State *L) +{ + df::viewscreen *before = NULL; + if (lua_gettop(L) >= 2) + before = Lua::CheckDFObject<df::viewscreen>(L, 2); + + df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, true); + lua_pushboolean(L, Screen::show(screen, before)); + return 1; +} + +static int screen_dismiss(lua_State *L) +{ + df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false); + Screen::dismiss(screen); + return 0; +} + +static int screen_isDismissed(lua_State *L) +{ + df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false); + lua_pushboolean(L, Screen::isDismissed(screen)); + return 1; +} + +} + +static const luaL_Reg dfhack_screen_funcs[] = { + { "getMousePos", screen_getMousePos }, + { "getWindowSize", screen_getWindowSize }, + { "paintTile", screen_paintTile }, + { "paintString", screen_paintString }, + { "fillRect", screen_fillRect }, + { "show", &Lua::CallWithCatchWrapper<screen_show> }, + { "dismiss", screen_dismiss }, + { "isDismissed", screen_isDismissed }, + { NULL, NULL } +}; + /***** Internal module *****/ static void *checkaddr(lua_State *L, int idx, bool allow_null = false) @@ -1252,5 +1399,6 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_funcs); OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs); OpenModule(state, "constructions", dfhack_constructions_module); + OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs); OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs); } diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 53523c3f..e7197796 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -37,6 +37,7 @@ distribution. #include "DataDefs.h" #include "DataIdentity.h" #include "LuaWrapper.h" +#include "LuaTools.h" #include "DataFuncs.h" #include "MiscUtils.h" @@ -1067,6 +1068,27 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id return 1; } +int Lua::CallWithCatch(lua_State *state, int (*fn)(lua_State*), const char *context) +{ + if (!context) + context = "native code"; + + try { + return fn(state); + } + catch (Error::NullPointer &e) { + const char *vn = e.varname(); + return luaL_error(state, "%s: NULL pointer: %s", context, vn ? vn : "?"); + } + catch (Error::InvalidArgument &e) { + const char *vn = e.expr(); + return luaL_error(state, "%s: Invalid argument; expected: %s", context, vn ? vn : "?"); + } + catch (std::exception &e) { + return luaL_error(state, "%s: C++ exception: %s", context, e.what()); + } +} + /** * Push a closure invoking the given function. */ diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index ff752431..668b2aec 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -467,7 +467,7 @@ int Plugin::lua_cmd_wrapper(lua_State *state) luaL_error(state, "plugin command %s() has been unloaded", (cmd->owner->name+"."+cmd->name).c_str()); - return cmd->command(state); + return Lua::CallWithCatch(state, cmd->command, cmd->name.c_str()); } int Plugin::lua_fun_wrapper(lua_State *state) diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index d3c7a65d..897e7032 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -192,6 +192,16 @@ namespace DFHack {namespace Lua { } /** + * Call through to the function with try/catch for C++ exceptions. + */ + DFHACK_EXPORT int CallWithCatch(lua_State *, int (*fn)(lua_State*), const char *context = NULL); + + template<int (*cb)(lua_State*)> + int CallWithCatchWrapper(lua_State *state) { + return CallWithCatch(state, cb); + } + + /** * Invoke lua function via pcall. Returns true if success. * If an error is signalled, and perr is true, it is printed and popped from the stack. */ diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 7c5c8ba5..0e4963fb 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -95,6 +95,12 @@ namespace DFHack /// Fills a rectangle with one pen. Possibly more efficient than a loop over paintTile. DFHACK_EXPORT bool fillRect(const Pen &pen, int x1, int y1, int x2, int y2); + /// Wipes the screen to full black + DFHACK_EXPORT bool clear(); + + /// Requests repaint + DFHACK_EXPORT bool invalidate(); + /// Find a loaded graphics tile from graphics raws. DFHACK_EXPORT bool findGraphicsTile(const std::string &page, int x, int y, int *ptile, int *pgs = NULL); @@ -111,6 +117,37 @@ namespace DFHack static bool is_instance(df::viewscreen *screen); + virtual bool is_lua_screen() { return false; } virtual std::string getFocusString() = 0; }; + + class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen { + std::string focus; + + void update_focus(lua_State *L, int idx); + + bool safe_call_lua(int (*pf)(lua_State *), int args, int rvs); + static dfhack_lua_viewscreen *get_self(lua_State *L); + + static int do_destroy(lua_State *L); + static int do_render(lua_State *L); + static int do_notify(lua_State *L); + static int do_input(lua_State *L); + + public: + dfhack_lua_viewscreen(lua_State *L, int table_idx); + virtual ~dfhack_lua_viewscreen(); + + static df::viewscreen *get_pointer(lua_State *L, int idx, bool make); + + virtual bool is_lua_screen() { return true; } + virtual std::string getFocusString() { return focus; } + + virtual void render(); + virtual void logic(); + virtual void help(); + virtual void resize(int w, int h); + virtual void feed(std::set<df::interface_key> *keys); + virtual bool key_conflict(df::interface_key key); + }; } diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 86ea1459..8c8a75aa 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -188,6 +188,12 @@ function dfhack.buildings.getSize(bld) return bld.x2+1-x, bld.y2+1-y, bld.centerx-x, bld.centery-y end +dfhack.screen.__index = dfhack.screen + +function dfhack.screen:__tostring() + return "<lua viewscreen: "..tostring(self._native)..">" +end + -- Interactive local print_banner = true diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index f6155bd6..ac45bb86 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -38,7 +38,10 @@ using namespace std; #include "ModuleFactory.h" #include "Core.h" #include "PluginManager.h" +#include "LuaTools.h" + #include "MiscUtils.h" + using namespace DFHack; #include "DataDefs.h" @@ -46,15 +49,21 @@ using namespace DFHack; #include "df/texture_handler.h" #include "df/tile_page.h" #include "df/interfacest.h" +#include "df/enabler.h" using namespace df::enums; using df::global::init; using df::global::gps; using df::global::texture; using df::global::gview; +using df::global::enabler; using Screen::Pen; +/* + * Screen painting API. + */ + df::coord2d Screen::getMousePos() { if (!gps) return df::coord2d(); @@ -136,6 +145,21 @@ bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2) return true; } +bool Screen::clear() +{ + if (!gps) return false; + + return fillRect(Pen(' ',0,0,false), 0, 0, gps->dimx-1, gps->dimy-1); +} + +bool Screen::invalidate() +{ + if (!enabler) return false; + + enabler->flag.bits.render = true; + return true; +} + bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs) { if (!gps || !texture || x < 0 || y < 0) return false; @@ -197,6 +221,10 @@ bool Screen::isDismissed(df::viewscreen *screen) return screen->breakdown_level != interface_breakdown_types::NONE; } +/* + * Base DFHack viewscreen. + */ + static std::set<df::viewscreen*> dfhack_screens; dfhack_viewscreen::dfhack_viewscreen() @@ -213,3 +241,237 @@ bool dfhack_viewscreen::is_instance(df::viewscreen *screen) { return dfhack_screens.count(screen) != 0; } + +/* + * Lua-backed viewscreen. + */ + +static int DFHACK_LUA_VS_TOKEN = 0; + +df::viewscreen *dfhack_lua_viewscreen::get_pointer(lua_State *L, int idx, bool make) +{ + df::viewscreen *screen; + + if (lua_istable(L, idx)) + { + if (!Lua::IsCoreContext(L)) + luaL_error(L, "only the core context can create lua screens"); + + lua_rawgetp(L, idx, &DFHACK_LUA_VS_TOKEN); + + if (!lua_isnil(L, -1)) + { + if (make) + luaL_error(L, "this screen is already on display"); + + screen = (df::viewscreen*)lua_touserdata(L, -1); + } + else + { + if (!make) + luaL_error(L, "this screen is not on display"); + + screen = new dfhack_lua_viewscreen(L, idx); + } + + lua_pop(L, 1); + } + else + screen = Lua::CheckDFObject<df::viewscreen>(L, idx); + + return screen; +} + +bool dfhack_lua_viewscreen::safe_call_lua(int (*pf)(lua_State *), int args, int rvs) +{ + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); + + auto L = Lua::Core::State; + lua_pushcfunction(L, pf); + if (args > 0) lua_insert(L, -args-1); + lua_pushlightuserdata(L, this); + if (args > 0) lua_insert(L, -args-1); + + return Lua::Core::SafeCall(out, args+1, rvs); +} + +dfhack_lua_viewscreen *dfhack_lua_viewscreen::get_self(lua_State *L) +{ + auto self = (dfhack_lua_viewscreen*)lua_touserdata(L, 1); + lua_rawgetp(L, LUA_REGISTRYINDEX, self); + if (!lua_istable(L, -1)) return NULL; + return self; +} + +int dfhack_lua_viewscreen::do_destroy(lua_State *L) +{ + auto self = get_self(L); + if (!self) return 0; + + lua_pushnil(L); + lua_rawsetp(L, LUA_REGISTRYINDEX, self); + + lua_pushnil(L); + lua_rawsetp(L, -2, &DFHACK_LUA_VS_TOKEN); + lua_pushnil(L); + lua_setfield(L, -2, "_native"); + + lua_getfield(L, -1, "onDestroy"); + lua_pushvalue(L, -2); + lua_call(L, 1, 0); + return 0; +} + +void dfhack_lua_viewscreen::update_focus(lua_State *L, int idx) +{ + lua_getfield(L, idx, "focus_path"); + auto str = lua_tostring(L, -1); + if (!str) str = ""; + focus = str; + lua_pop(L, 1); + + if (focus.empty()) + focus = "lua"; + else + focus = "lua/"+focus; +} + +int dfhack_lua_viewscreen::do_render(lua_State *L) +{ + auto self = get_self(L); + if (!self) return 0; + + lua_getfield(L, -1, "onRender"); + + if (lua_isnil(L, -1)) + { + Screen::clear(); + return 0; + } + + lua_pushvalue(L, -2); + lua_call(L, 1, 0); + return 0; +} + +int dfhack_lua_viewscreen::do_notify(lua_State *L) +{ + int args = lua_gettop(L); + + auto self = get_self(L); + if (!self) return 0; + + lua_pushvalue(L, 2); + lua_gettable(L, -2); + if (lua_isnil(L, -1)) + return 0; + + // self field args table fn -> table fn table args + lua_replace(L, 1); + lua_copy(L, -1, 2); + lua_insert(L, 1); + lua_call(L, args-1, 1); + + self->update_focus(L, 1); + return 1; +} + +int dfhack_lua_viewscreen::do_input(lua_State *L) +{ + auto self = get_self(L); + if (!self) return 0; + + auto keys = (std::set<df::interface_key>*)lua_touserdata(L, 2); + + lua_getfield(L, -1, "onInput"); + + if (lua_isnil(L, -1)) + { + if (keys->count(interface_key::LEAVESCREEN)) + Screen::dismiss(self); + + return 0; + } + + lua_pushvalue(L, -2); + + if (keys->empty()) + lua_pushnil(L); + else + { + lua_createtable(L, 0, keys->size()); + + for (auto it = keys->begin(); it != keys->end(); ++it) + { + if (auto name = enum_item_raw_key(*it)) + lua_pushstring(L, name); + else + lua_pushinteger(L, *it); + + lua_pushboolean(L, true); + lua_rawset(L, -3); + } + } + + lua_call(L, 2, 0); + self->update_focus(L, -1); + return 0; +} + +dfhack_lua_viewscreen::dfhack_lua_viewscreen(lua_State *L, int table_idx) +{ + assert(Lua::IsCoreContext(L)); + + Lua::PushDFObject(L, (df::viewscreen*)this); + lua_setfield(L, table_idx, "_native"); + lua_pushlightuserdata(L, this); + lua_rawsetp(L, table_idx, &DFHACK_LUA_VS_TOKEN); + + lua_pushvalue(L, table_idx); + lua_rawsetp(L, LUA_REGISTRYINDEX, this); + + update_focus(L, table_idx); +} + +dfhack_lua_viewscreen::~dfhack_lua_viewscreen() +{ + safe_call_lua(do_destroy, 0, 0); +} + +void dfhack_lua_viewscreen::render() +{ + safe_call_lua(do_render, 0, 0); +} + +void dfhack_lua_viewscreen::logic() +{ + lua_pushstring(Lua::Core::State, "onIdle"); + safe_call_lua(do_notify, 1, 0); +} + +void dfhack_lua_viewscreen::help() +{ + lua_pushstring(Lua::Core::State, "onHelp"); + safe_call_lua(do_notify, 1, 0); +} + +void dfhack_lua_viewscreen::resize(int w, int h) +{ + auto L = Lua::Core::State; + lua_pushstring(L, "onResize"); + lua_pushinteger(L, w); + lua_pushinteger(L, h); + safe_call_lua(do_notify, 3, 0); +} + +void dfhack_lua_viewscreen::feed(std::set<df::interface_key> *keys) +{ + lua_pushlightuserdata(Lua::Core::State, keys); + safe_call_lua(do_input, 1, 0); +} + +bool dfhack_lua_viewscreen::key_conflict(df::interface_key key) +{ + return key == interface_key::MOVIES || key == interface_key::OPTIONS; +} |
