summaryrefslogtreecommitdiff
path: root/library
diff options
context:
space:
mode:
authorAlexander Gavrilov2012-08-19 14:27:44 +0400
committerAlexander Gavrilov2012-08-19 14:27:44 +0400
commit30f71ff5106d271d04bfa26b976441cfa9b2abf6 (patch)
treeced0b058f615b90a75c495be1f4db6045100b972 /library
parentb8ee52131bccd174f06c9124980bbcb8df7c80e4 (diff)
downloaddfhack-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.cpp148
-rw-r--r--library/LuaTypes.cpp22
-rw-r--r--library/PluginManager.cpp2
-rw-r--r--library/include/LuaTools.h10
-rw-r--r--library/include/modules/Screen.h37
-rw-r--r--library/lua/dfhack.lua6
-rw-r--r--library/modules/Screen.cpp262
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;
+}