diff --git a/LUA_API.rst b/LUA_API.rst index bf6c81ec0..dab299741 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -497,3 +497,64 @@ Since the data is hidden in data structures owned by the DF world, and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead. + +C++ function wrappers +===================== + +Thin wrappers around C++ functions, similar to the ones for virtual methods. + +Gui module +---------- + +* ``dfhack.gui.getSelectedWorkshopJob(silent)`` + + When a job is selected in *'q'* mode, returns the job, else + prints error unless silent and returns *nil*. + +* ``dfhack.gui.getSelectedJob(silent)`` + + Returns the job selected in a workshop or unit/jobs screen. + +* ``dfhack.gui.getSelectedUnit(silent)`` + + Returns the unit selected via *'v'*, *'k'*, unit/jobs, or + a full-screen item view of a cage or suchlike. + +* ``dfhack.gui.getSelectedItem(silent)`` + + Returns the item selected via *'v'* ->inventory, *'k'*, *'t'*, or + a full-screen item view of a container. Note that in the + last case, the highlighted *contained item* is returned, not + the container itself. + +* ``dfhack.gui.showAnnouncement(text,color,is_bright)`` + + Adds a regular announcement with given text, color, and brightness. + The is_bright boolean actually seems to invert the brightness. + +* ``dfhack.gui.showPopupAnnouncement(text,color,is_bright)`` + + Pops up a titan-style modal announcement window. + +Job module +---------- + +* ``dfhack.job.cloneJobStruct(job)`` + + Creates a deep copy of the given job. + +* ``dfhack.job.printJobDetails(job)`` + + Prints info about the job. + +* ``dfhack.job.getJobHolder(job)`` + + Returns the building holding the job. + +* ``dfhack.job.is_equal(job1,job2)`` + + Compares important fields in the job and nested item structures. + +* ``dfhack.job.is_item_equal(job_item1,job_item2)`` + + Compares important fields in the job item structures. diff --git a/Lua API.html b/Lua API.html index 25b6f06d6..877bdbd88 100644 --- a/Lua API.html +++ b/Lua API.html @@ -335,6 +335,11 @@ ul.auto-toc {
  • DFHack utilities
  • @@ -753,6 +758,59 @@ and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead.

    +
    +

    C++ function wrappers

    +

    Thin wrappers around C++ functions, similar to the ones for virtual methods.

    +
    +

    Gui module

    + +
    +
    +

    Job module

    + +
    +
    diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 16c60457a..5afd1156b 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -39,6 +39,7 @@ distribution. #include "modules/World.h" #include "modules/Gui.h" +#include "modules/Job.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -46,6 +47,10 @@ distribution. #include "MiscUtils.h" #include "df/job.h" +#include "df/job_item.h" +#include "df/building.h" +#include "df/unit.h" +#include "df/item.h" #include #include @@ -74,6 +79,13 @@ color_ostream *DFHack::Lua::GetOutput(lua_State *L) return rv; } +df::cur_lua_ostream_argument::cur_lua_ostream_argument(lua_State *state) +{ + out = DFHack::Lua::GetOutput(state); + if (!out) + LuaWrapper::field_error(state, UPVAL_METHOD_NAME, "no output stream", "invoke"); +} + static void set_dfhack_output(lua_State *L, color_ostream *p) { lua_pushlightuserdata(L, p); @@ -844,6 +856,39 @@ static void OpenPersistent(lua_State *state) lua_pop(state, 1); } +static void OpenModule(lua_State *state, const char *mname, const LuaWrapper::FunctionReg *reg) +{ + luaL_getsubtable(state, lua_gettop(state), mname); + LuaWrapper::SetFunctionWrappers(state, reg); + 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) } + +static const LuaWrapper::FunctionReg dfhack_gui_module[] = { + WRAPM(Gui, getSelectedWorkshopJob), + WRAPM(Gui, getSelectedJob), + WRAPM(Gui, getSelectedUnit), + WRAPM(Gui, getSelectedItem), + WRAPM(Gui, showAnnouncement), + WRAPM(Gui, showPopupAnnouncement), + { NULL, NULL } +}; + +static bool jobEqual(df::job *job1, df::job *job2) { return *job1 == *job2; } +static bool jobItemEqual(df::job_item *job1, df::job_item *job2) { return *job1 == *job2; } + +static const LuaWrapper::FunctionReg dfhack_job_module[] = { + WRAP(cloneJobStruct), + WRAP(printJobDetails), + WRAP(getJobHolder), + WRAPN(is_equal, jobEqual), + WRAPN(is_item_equal, jobItemEqual), + { NULL, NULL } +}; + lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) { if (!state) @@ -872,7 +917,8 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) OpenPersistent(state); - LuaWrapper::AddMethodWrapper(state, 0, -1, "getSelectedJob", df::wrap_function(&Gui::getSelectedJob)); + OpenModule(state, "gui", dfhack_gui_module); + OpenModule(state, "job", dfhack_job_module); lua_setglobal(state, "dfhack"); diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 2cc929ee9..4213499eb 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -30,6 +30,7 @@ distribution. #include "MemAccess.h" #include "Core.h" +#include "Error.h" #include "VersionInfo.h" #include "tinythread.h" // must be last due to MS stupidity @@ -1022,18 +1023,29 @@ static int meta_call_function(lua_State *state) auto id = (function_identity_base*)lua_touserdata(state, UPVAL_CONTAINER_ID); if (lua_gettop(state) != id->getNumArgs()) field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke"); - id->invoke(state, 1); + + try { + id->invoke(state, 1); + } + catch (Error::NullPointer &e) { + const char *vn = e.varname(); + std::string tmp = stl_sprintf("NULL pointer: %s", vn ? vn : "?"); + field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); + } + catch (std::exception &e) { + std::string tmp = stl_sprintf("C++ exception: %s", e.what()); + field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); + } + return 1; } /** * Create a closure invoking the given function, and add it to the field table. */ -void LuaWrapper::AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, - const char *name, function_identity_base *fun) +static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, + const char *name, function_identity_base *fun) { - field_idx = lua_absindex(state, field_idx); - lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN); if (meta_idx) lua_pushvalue(state, meta_idx); @@ -1046,6 +1058,17 @@ void LuaWrapper::AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, lua_setfield(state, field_idx, name); } +/** + * Wrap functions and add them to the table on the top of the stack. + */ +void LuaWrapper::SetFunctionWrappers(lua_State *state, const FunctionReg *reg) +{ + int base = lua_gettop(state); + + for (; reg && reg->name; ++reg) + AddMethodWrapper(state, 0, base, reg->name, reg->identity); +} + /** * Add fields in the array to the UPVAL_FIELDTABLE candidates on the stack. */ diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 9b26e2a61..b9ff35cfc 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -25,6 +25,7 @@ distribution. #include "Internal.h" #include "Export.h" #include "MiscUtils.h" +#include "Error.h" #ifndef LINUX_BUILD #include @@ -39,6 +40,10 @@ distribution. #include #include +const char *DFHack::Error::NullPointer::what() const throw() { + return "NULL pointer access"; +} + std::string stl_sprintf(const char *fmt, ...) { va_list lst; va_start(lst, fmt); diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 3529d8059..6cd7d42f4 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -50,6 +50,13 @@ namespace df { template::type,void>::value> struct function_wrapper {}; + class cur_lua_ostream_argument { + DFHack::color_ostream *out; + public: + cur_lua_ostream_argument(lua_State *state); + operator DFHack::color_ostream& () { return *out; } + }; + /* * Since templates can't match variable arg count, * a separate specialization is needed for every @@ -69,7 +76,7 @@ namespace df { type v##type; df::identity_traits::get()->lua_write(state, UPVAL_METHOD_NAME, &v##type, base++); #define OSTREAM_ARG DFHack::color_ostream& #define LOAD_OSTREAM(name) \ - DFHack::color_ostream_proxy name(DFHack::Core::getInstance().getConsole()); + cur_lua_ostream_argument name(state); #define INSTANTIATE_RETURN_TYPE(FArgs) \ template struct return_type { typedef RT type; }; \ diff --git a/library/include/Error.h b/library/include/Error.h index 159a84713..0fbc6ba78 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -39,6 +39,18 @@ namespace DFHack * the whole array of DFHack exceptions from the rest */ class DFHACK_EXPORT All : public std::exception{}; + + class DFHACK_EXPORT NullPointer : public All { + const char *varname_; + public: + NullPointer(const char *varname_ = NULL) : varname_(varname_) {} + const char *varname() const { return varname_; } + virtual const char *what() const throw(); + }; + +#define CHECK_NULL_POINTER(var) \ + { if (var == NULL) throw DFHack::Error::NullPointer(#var); } + class DFHACK_EXPORT AllSymbols : public All{}; // Syntax errors and whatnot, the xml can't be read class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 66e77bb1f..479ca0d67 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -220,11 +220,16 @@ namespace DFHack { namespace LuaWrapper { * and the enum itself to the _enum metafield. Pushes the key table on the stack. */ void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum); + + struct FunctionReg { + const char *name; + function_identity_base *identity; + }; + /** - * Create a closure invoking the given function, and add it to the field table. + * Wrap functions and add them to the table on the top of the stack. */ - void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, - const char *name, function_identity_base *fun); + void SetFunctionWrappers(lua_State *state, const FunctionReg *reg); void IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 67652ff15..1101367fc 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -1,12 +1,36 @@ -- Common startup file for all dfhack plugins with lua support -- The global dfhack table is already created by C++ init code. +-- Console color constants + +COLOR_RESET = -1 +COLOR_BLACK = 0 +COLOR_BLUE = 1 +COLOR_GREEN = 2 +COLOR_CYAN = 3 +COLOR_RED = 4 +COLOR_MAGENTA = 5 +COLOR_BROWN = 6 +COLOR_GREY = 7 +COLOR_DARKGREY = 8 +COLOR_LIGHTBLUE = 9 +COLOR_LIGHTGREEN = 10 +COLOR_LIGHTCYAN = 11 +COLOR_LIGHTRED = 12 +COLOR_LIGHTMAGENTA = 13 +COLOR_YELLOW = 14 +COLOR_WHITE = 15 + +-- Error handling + safecall = dfhack.safecall function dfhack.pcall(f, ...) return xpcall(f, dfhack.onerror, ...) end +-- Module loading + function mkmodule(module,env) local pkg = package.loaded[module] if pkg == nil then @@ -31,6 +55,8 @@ function reload(module) dofile(path) end +-- Misc functions + function printall(table) if table == nil then return end for k,v in pairs(table) do diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 2c9a5d1d0..679ecc0e7 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -32,6 +32,7 @@ distribution. using namespace std; #include "Core.h" +#include "Error.h" #include "PluginManager.h" #include "MiscUtils.h" @@ -54,6 +55,8 @@ using namespace df::enums; df::job *DFHack::cloneJobStruct(df::job *job) { + CHECK_NULL_POINTER(job); + df::job *pnew = new df::job(*job); // Clean out transient fields @@ -105,6 +108,9 @@ void DFHack::deleteJobStruct(df::job *job) bool DFHack::operator== (const df::job_item &a, const df::job_item &b) { + CHECK_NULL_POINTER(&a); + CHECK_NULL_POINTER(&b); + if (!(CMP(item_type) && CMP(item_subtype) && CMP(mat_type) && CMP(mat_index) && CMP(flags1.whole) && CMP(quantity) && CMP(vector_id) && @@ -125,6 +131,9 @@ bool DFHack::operator== (const df::job_item &a, const df::job_item &b) bool DFHack::operator== (const df::job &a, const df::job &b) { + CHECK_NULL_POINTER(&a); + CHECK_NULL_POINTER(&b); + if (!(CMP(job_type) && CMP(unk2) && CMP(mat_type) && CMP(mat_index) && CMP(item_subtype) && CMP(item_category.whole) && @@ -141,6 +150,8 @@ bool DFHack::operator== (const df::job &a, const df::job &b) static void print_job_item_details(color_ostream &out, df::job *job, unsigned idx, df::job_item *item) { + CHECK_NULL_POINTER(item); + ItemTypeInfo info(item); out << " Input Item " << (idx+1) << ": " << info.toString(); @@ -175,6 +186,8 @@ static void print_job_item_details(color_ostream &out, df::job *job, unsigned id void DFHack::printJobDetails(color_ostream &out, df::job *job) { + CHECK_NULL_POINTER(job); + out.color(job->flags.bits.suspend ? Console::COLOR_DARKGREY : Console::COLOR_GREY); out << "Job " << job->id << ": " << ENUM_KEY_STR(job_type,job->job_type); if (job->flags.whole) @@ -216,6 +229,8 @@ void DFHack::printJobDetails(color_ostream &out, df::job *job) df::building *DFHack::getJobHolder(df::job *job) { + CHECK_NULL_POINTER(job); + for (size_t i = 0; i < job->references.size(); i++) { VIRTUAL_CAST_VAR(ref, df::general_ref_building_holderst, job->references[i]);