From d1b27418a6a39aa15d51465e61975c5230dcc9d6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 5 Apr 2012 11:32:23 +0400 Subject: [PATCH 01/10] Add a World::GetPersistentData version that auto-adds if not found. --- library/include/DataFuncs.h | 4 ++++ library/include/DataIdentity.h | 4 +++- library/include/modules/World.h | 8 ++++++++ library/modules/World.cpp | 15 +++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 822dcd45d..109b2c176 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -32,6 +32,10 @@ distribution. #include "DataIdentity.h" #include "LuaWrapper.h" +#ifndef BUILD_DFHACK_LIB +#error Due to export issues this header is internal to the main library. +#endif + namespace df { // A very simple and stupid implementation of some stuff from boost template struct is_same_type { static const bool value = false; }; diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index c83b2092a..bab87e1bb 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -160,6 +160,8 @@ namespace DFHack }; } +// Due to export issues, this stuff can only work in the main dll +#ifdef BUILD_DFHACK_LIB namespace df { using DFHack::function_identity_base; @@ -575,4 +577,4 @@ namespace df return &identity; } } - +#endif diff --git a/library/include/modules/World.h b/library/include/modules/World.h index 9ed6a3ed9..e7b17ce41 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -139,8 +139,16 @@ namespace DFHack PersistentDataItem AddPersistentData(const std::string &key); PersistentDataItem GetPersistentData(const std::string &key); PersistentDataItem GetPersistentData(int entry_id); + // Calls GetPersistentData(key); if not found, adds and sets added to true. + // The result can still be not isValid() e.g. if the world is not loaded. + PersistentDataItem GetPersistentData(const std::string &key, bool *added); + // Lists all items with the given key. + // If prefix is true, search for keys starting with key+"/". + // GetPersistentData(&vec,"",true) returns all items. + // Items have alphabetic order by key; same key ordering is undefined. void GetPersistentData(std::vector *vec, const std::string &key, bool prefix = false); + // Deletes the item; returns true if success. bool DeletePersistentData(const PersistentDataItem &item); void ClearPersistentCache(); diff --git a/library/modules/World.cpp b/library/modules/World.cpp index d570abaec..ca5d0aee0 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -300,6 +300,21 @@ PersistentDataItem World::GetPersistentData(int entry_id) return PersistentDataItem(); } +PersistentDataItem World::GetPersistentData(const std::string &key, bool *added) +{ + *added = false; + + PersistentDataItem rv = GetPersistentData(key); + + if (!rv.isValid()) + { + *added = true; + rv = AddPersistentData(key); + } + + return rv; +} + void World::GetPersistentData(std::vector *vec, const std::string &key, bool prefix) { if (!BuildPersistentCache()) From 3afed43cdb435c788964d983a358d97ee291c2fc Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 5 Apr 2012 11:59:39 +0400 Subject: [PATCH 02/10] Experimental: try wrapping a dfhack api function. --- library/LuaTools.cpp | 6 ++++++ library/LuaTypes.cpp | 11 ++++++++--- library/LuaWrapper.cpp | 5 ++++- library/include/DataFuncs.h | 22 ++++++++++++++++++++-- library/include/LuaWrapper.h | 5 +++++ 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 5a50d0dd2..16c60457a 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -35,14 +35,18 @@ distribution. // must be last due to MS stupidity #include "DataDefs.h" #include "DataIdentity.h" +#include "DataFuncs.h" #include "modules/World.h" +#include "modules/Gui.h" #include "LuaWrapper.h" #include "LuaTools.h" #include "MiscUtils.h" +#include "df/job.h" + #include #include #include @@ -868,6 +872,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)); + lua_setglobal(state, "dfhack"); // load dfhack.lua diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 6d5961f8d..2cc929ee9 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1029,11 +1029,16 @@ static int meta_call_function(lua_State *state) /** * Create a closure invoking the given function, and add it to the field table. */ -static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, - const char *name, function_identity_base *fun) +void LuaWrapper::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); - lua_pushvalue(state, meta_idx); + if (meta_idx) + lua_pushvalue(state, meta_idx); + else + lua_pushlightuserdata(state, NULL); // can't be a metatable lua_pushfstring(state, "%s()", name); lua_pushlightuserdata(state, fun); lua_pushcclosure(state, meta_call_function, 4); diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 818e6b9e1..2f3958efe 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -53,7 +53,10 @@ static luaL_Reg no_functions[] = { { NULL, NULL } }; */ void LuaWrapper::field_error(lua_State *state, int index, const char *err, const char *mode) { - lua_getfield(state, UPVAL_METATABLE, "__metatable"); + if (lua_islightuserdata(state, UPVAL_METATABLE)) + lua_pushstring(state, "(global)"); + else + lua_getfield(state, UPVAL_METATABLE, "__metatable"); const char *cname = lua_tostring(state, -1); const char *fname = index ? lua_tostring(state, index) : "*"; luaL_error(state, "Cannot %s field %s.%s: %s.", diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 109b2c176..3529d8059 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -67,10 +67,15 @@ namespace df { CT *self = (CT*)DFHack::LuaWrapper::get_object_addr(state, base++, UPVAL_METHOD_NAME, "invoke"); #define LOAD_ARG(type) \ 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()); -#define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \ +#define INSTANTIATE_RETURN_TYPE(FArgs) \ template struct return_type { typedef RT type; }; \ - template struct return_type { typedef RT type; }; \ + template struct return_type { typedef RT type; }; + +#define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \ template struct function_wrapper { \ static const bool is_method = false; \ static const int num_args = Count; \ @@ -96,24 +101,35 @@ namespace df { #define FW_TARGSC #define FW_TARGS +INSTANTIATE_RETURN_TYPE(()) INSTANTIATE_WRAPPERS(0, (), (), ;) +INSTANTIATE_WRAPPERS(0, (OSTREAM_ARG), (out), LOAD_OSTREAM(out);) #undef FW_TARGS #undef FW_TARGSC #define FW_TARGSC FW_TARGS, #define FW_TARGS class A1 +INSTANTIATE_RETURN_TYPE((A1)) INSTANTIATE_WRAPPERS(1, (A1), (vA1), LOAD_ARG(A1);) +INSTANTIATE_WRAPPERS(1, (OSTREAM_ARG,A1), (out,vA1), LOAD_OSTREAM(out); LOAD_ARG(A1);) #undef FW_TARGS #define FW_TARGS class A1, class A2 +INSTANTIATE_RETURN_TYPE((A1,A2)) INSTANTIATE_WRAPPERS(2, (A1,A2), (vA1,vA2), LOAD_ARG(A1); LOAD_ARG(A2);) +INSTANTIATE_WRAPPERS(2, (OSTREAM_ARG,A1,A2), (out,vA1,vA2), + LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3 +INSTANTIATE_RETURN_TYPE((A1,A2,A3)) INSTANTIATE_WRAPPERS(3, (A1,A2,A3), (vA1,vA2,vA3), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);) +INSTANTIATE_WRAPPERS(3, (OSTREAM_ARG,A1,A2,A3), (out,vA1,vA2,vA3), + LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3, class A4 +INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4)) INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (vA1,vA2,vA3,vA4), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);) #undef FW_TARGS @@ -124,6 +140,8 @@ INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (vA1,vA2,vA3,vA4), #undef INVOKE_RV #undef LOAD_CLASS #undef LOAD_ARG +#undef OSTREAM_ARG +#undef LOAD_OSTREAM template class function_identity : public function_identity_base { diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 72aa0e12e..66e77bb1f 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -220,6 +220,11 @@ 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); + /** + * Create a closure invoking the given function, and add it to the field table. + */ + void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, + const char *name, function_identity_base *fun); void IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct); From 59f411e4016405ebef0a928c002098586be77ebb Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 5 Apr 2012 12:32:10 +0400 Subject: [PATCH 03/10] Oops, forgot return statement. --- library/modules/World.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/modules/World.cpp b/library/modules/World.cpp index ca5d0aee0..6f6a69a59 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -254,6 +254,8 @@ bool World::BuildPersistentCache() d->persistent_index.insert(T_persistent_item(hfvec[i]->name.first_name, -hfvec[i]->id)); } + + return true; } PersistentDataItem World::AddPersistentData(const std::string &key) From 28a741082f8b0981806b8a63589279627bd8e39e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 5 Apr 2012 18:10:16 +0400 Subject: [PATCH 04/10] Encode & decode names in utf-8 for transfer in remote messages. That's the encoding required by the protobuf spec. --- LICENSE | 24 ++++++ library/MiscUtils.cpp | 161 +++++++++++++++++++++++++++++++++++- library/RemoteTools.cpp | 14 ++-- library/include/MiscUtils.h | 4 + plugins/rename.cpp | 10 ++- 5 files changed, 201 insertions(+), 12 deletions(-) diff --git a/LICENSE b/LICENSE index 5ea59addf..96ab022d9 100644 --- a/LICENSE +++ b/LICENSE @@ -113,3 +113,27 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------- + +See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +Copyright (c) 2008-2010 Bjoern Hoehrmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 8247cd002..9b26e2a61 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -37,6 +37,7 @@ distribution. #include #include +#include std::string stl_sprintf(const char *fmt, ...) { va_list lst; @@ -149,4 +150,162 @@ uint64_t GetTimeMs64() return ret; } -#endif \ No newline at end of file +#endif + +/* Character decoding */ + +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 12 + +static const uint8_t utf8d[] = { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, +}; + +static inline uint32_t +decode(uint32_t* state, uint32_t* codep, uint8_t byte) { + uint32_t type = utf8d[byte]; + + *codep = (*state != UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +/* Character encoding */ + +static inline int encode(uint8_t *out, uint16_t c) { + if (c <= 0x7F) + { + out[0] = c; + return 1; + } + else if (c <= 0x7FF) + { + out[0] = (0xC0 | (c >> 6)); + out[1] = (0x80 | (c & 0x3F)); + return 2; + } + else /*if (c <= 0xFFFF)*/ + { + out[0] = (0xE0 | (c >> 12)); + out[1] = (0x80 | ((c >> 6) & 0x3F)); + out[2] = (0x80 | (c & 0x3F)); + return 3; + } +} + +/* CP437 */ + +static uint16_t character_table[256] = { + 0, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, // + 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C, + 0x25BA, 0x25C4, 0x2195, 0x203C, 0xB6, 0xA7, 0x25AC, 0x21A8, // + 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, // + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x2302, + 0xC7, 0xFC, 0xE9, 0xE2, 0xE4, 0xE0, 0xE5, 0xE7, // + 0xEA, 0xEB, 0xE8, 0xEF, 0xEE, 0xEC, 0xC4, 0xC5, + 0xC9, 0xE6, 0xC6, 0xF4, 0xF6, 0xF2, 0xFB, 0xF9, // + 0xFF, 0xD6, 0xDC, 0xA2, 0xA3, 0xA5, 0x20A7, 0x192, + 0xE1, 0xED, 0xF3, 0xFA, 0xF1, 0xD1, 0xAA, 0xBA, // + 0xBF, 0x2310, 0xAC, 0xBD, 0xBC, 0xA1, 0xAB, 0xBB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, // + 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, // + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, // + 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x3B1, 0xDF, 0x393, 0x3C0, 0x3A3, 0x3C3, 0xB5, 0x3C4, // + 0x3A6, 0x398, 0x3A9, 0x3B4, 0x221E, 0x3C6, 0x3B5, 0x2229, + 0x2261, 0xB1, 0x2265, 0x2264, 0x2320, 0x2321, 0xF7, 0x2248, // + 0xB0, 0x2219, 0xB7, 0x221A, 0x207F, 0xB2, 0x25A0, 0xA0 +}; + +std::string DF2UTF(const std::string &in) +{ + std::string out; + out.reserve(in.size()); + + uint8_t buf[4]; + for (size_t i = 0; i < in.size(); i++) + { + int cnt = encode(buf, character_table[(uint8_t)in[i]]); + out.append(&buf[0], &buf[cnt]); + } + + return out; +} + +std::string UTF2DF(const std::string &in) +{ + // Unicode to normal lookup table + static std::map ctable; + + if (ctable.empty()) + { + for (uint16_t i = 0; i < 256; i++) + if (character_table[i] != i) + ctable[character_table[i]] = char(i); + } + + // Actual conversion loop + size_t size = in.size(); + std::string out(size, char(0)); + + uint32_t codepoint = 0; + uint32_t state = UTF8_ACCEPT, prev = UTF8_ACCEPT; + uint32_t pos = 0; + + for (unsigned i = 0; i < size; prev = state, i++) { + switch (decode(&state, &codepoint, uint8_t(in[i]))) { + case UTF8_ACCEPT: + if (codepoint < 256 && character_table[codepoint] == codepoint) { + out[pos++] = char(codepoint); + } else { + char v = ctable[codepoint]; + out[pos++] = v ? v : '?'; + } + break; + + case UTF8_REJECT: + out[pos++] = '?'; + if (prev != UTF8_ACCEPT) --i; + state = UTF8_ACCEPT; + break; + } + } + + if (pos != size) + out.resize(pos); + return out; +} diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 229e5a70c..01c110643 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -221,30 +221,30 @@ void DFHack::describeMaterial(BasicMaterialInfo *info, const MaterialInfo &mat, void DFHack::describeName(NameInfo *info, df::language_name *name) { if (!name->first_name.empty()) - info->set_first_name(name->first_name); + info->set_first_name(DF2UTF(name->first_name)); if (!name->nickname.empty()) - info->set_nickname(name->nickname); + info->set_nickname(DF2UTF(name->nickname)); if (name->language >= 0) info->set_language_id(name->language); std::string lname = Translation::TranslateName(name, false, true); if (!lname.empty()) - info->set_last_name(lname); + info->set_last_name(DF2UTF(lname)); lname = Translation::TranslateName(name, true, true); if (!lname.empty()) - info->set_english_name(lname); + info->set_english_name(DF2UTF(lname)); } void DFHack::describeNameTriple(NameTriple *info, const std::string &name, const std::string &plural, const std::string &adj) { - info->set_normal(name); + info->set_normal(DF2UTF(name)); if (!plural.empty() && plural != name) - info->set_plural(plural); + info->set_plural(DF2UTF(plural)); if (!adj.empty() && adj != name) - info->set_adjective(adj); + info->set_adjective(DF2UTF(adj)); } void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index e5ecb25f4..c2a153eb1 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -279,3 +279,7 @@ DFHACK_EXPORT uint64_t GetTimeMs64(); DFHACK_EXPORT std::string stl_sprintf(const char *fmt, ...); DFHACK_EXPORT std::string stl_vsprintf(const char *fmt, va_list args); + +// Conversion between CP437 and UTF-8 +DFHACK_EXPORT std::string UTF2DF(const std::string &in); +DFHACK_EXPORT std::string DF2UTF(const std::string &in); diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 8dacf62a9..d983d0962 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -20,6 +20,8 @@ #include "RemoteServer.h" #include "rename.pb.h" +#include "MiscUtils.h" + #include using std::vector; @@ -128,9 +130,9 @@ static command_result RenameSquad(color_ostream &stream, const RenameSquadIn *in return CR_NOT_FOUND; if (in->has_nickname()) - set_nickname(&squad->name, in->nickname()); + set_nickname(&squad->name, UTF2DF(in->nickname())); if (in->has_alias()) - squad->alias = in->alias(); + squad->alias = UTF2DF(in->alias()); return CR_OK; } @@ -142,9 +144,9 @@ static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in) return CR_NOT_FOUND; if (in->has_nickname()) - setUnitNickname(unit, in->nickname()); + setUnitNickname(unit, UTF2DF(in->nickname())); if (in->has_profession()) - unit->custom_profession = in->profession(); + unit->custom_profession = UTF2DF(in->profession()); return CR_OK; } From 9eed9f0d246f44a51266a05e0107ea22fea54e73 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 5 Apr 2012 19:55:59 +0400 Subject: [PATCH 05/10] Wrap a few utility functions defined on the c++ side for lua. --- LUA_API.rst | 61 ++++++++++++++++++++++++++++++++++++ Lua API.html | 58 ++++++++++++++++++++++++++++++++++ library/LuaTools.cpp | 48 +++++++++++++++++++++++++++- library/LuaTypes.cpp | 33 ++++++++++++++++--- library/MiscUtils.cpp | 5 +++ library/include/DataFuncs.h | 9 +++++- library/include/Error.h | 12 +++++++ library/include/LuaWrapper.h | 11 +++++-- library/lua/dfhack.lua | 26 +++++++++++++++ library/modules/Job.cpp | 15 +++++++++ 10 files changed, 268 insertions(+), 10 deletions(-) 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

    +
      +
    • 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/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]); From 903e9ee716e4f84f692eadcc60190223ace250d8 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 6 Apr 2012 11:21:28 +0400 Subject: [PATCH 06/10] Export a few more functions to lua. --- LUA_API.rst | 23 ++++++++++++++++++++++ Lua API.html | 23 ++++++++++++++++++++++ library/LuaTools.cpp | 35 ++++++++++++++++++++++++++++++--- library/RemoteTools.cpp | 2 +- library/include/modules/Units.h | 2 +- library/lua/dfhack.lua | 7 ++++--- library/modules/Translation.cpp | 3 +++ library/modules/Units.cpp | 27 ++++++++++++++----------- 8 files changed, 103 insertions(+), 19 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index dab299741..422ba6b49 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -503,6 +503,10 @@ C++ function wrappers Thin wrappers around C++ functions, similar to the ones for virtual methods. +* ``dfhack.TranslateName(name,in_english,only_last_name)`` + + Convert a language_name or only the last name part to string. + Gui module ---------- @@ -558,3 +562,22 @@ Job module * ``dfhack.job.is_item_equal(job_item1,job_item2)`` Compares important fields in the job item structures. + +Units module +------------ + +* ``dfhack.units.getVisibleName(unit)`` + + Returns the name visible in game, accounting for false identities. + +* ``dfhack.units.isDead(unit)`` + + The unit is completely dead and passive. + +* ``dfhack.units.isAlive(unit)`` + + The unit isn't dead or undead. + +* ``dfhack.units.isSane(unit)`` + + The unit is capable of rational action, i.e. not dead, insane or zombie. diff --git a/Lua API.html b/Lua API.html index 877bdbd88..ab64eea9a 100644 --- a/Lua API.html +++ b/Lua API.html @@ -338,6 +338,7 @@ ul.auto-toc {
  • C++ function wrappers
  • @@ -761,6 +762,11 @@ 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.

    +
      +
    • dfhack.TranslateName(name,in_english,only_last_name)

      +

      Convert a language_name or only the last name part to string.

      +
    • +

    Gui module

      @@ -810,6 +816,23 @@ The is_bright boolean actually seems to invert the brightness.

    +
    +

    Units module

    +
      +
    • dfhack.units.getVisibleName(unit)

      +

      Returns the name visible in game, accounting for false identities.

      +
    • +
    • dfhack.units.isDead(unit)

      +

      The unit is completely dead and passive.

      +
    • +
    • dfhack.units.isAlive(unit)

      +

      The unit isn't dead or undead.

      +
    • +
    • dfhack.units.isSane(unit)

      +

      The unit is capable of rational action, i.e. not dead, insane or zombie.

      +
    • +
    +
    diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 5afd1156b..5a87c931d 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -40,6 +40,8 @@ distribution. #include "modules/World.h" #include "modules/Gui.h" #include "modules/Job.h" +#include "modules/Translation.h" +#include "modules/Units.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -641,9 +643,9 @@ static const luaL_Reg dfhack_funcs[] = { { NULL, NULL } }; -/* - * Per-world persistent configuration storage. - */ +/************************************************** + * Per-world persistent configuration storage API * + **************************************************/ static PersistentDataItem persistent_by_struct(lua_State *state, int idx) { @@ -783,6 +785,7 @@ static int dfhack_persistent_save(lua_State *state) lua_settop(state, 1); + // Retrieve existing or create a new entry PersistentDataItem ref; bool added = false; @@ -804,6 +807,7 @@ static int dfhack_persistent_save(lua_State *state) ref = Core::getInstance().getWorld()->GetPersistentData(str); } + // Auto-add if not found if (!ref.isValid()) { ref = Core::getInstance().getWorld()->AddPersistentData(str); @@ -812,6 +816,7 @@ static int dfhack_persistent_save(lua_State *state) added = true; } + // Copy data from lua to C++ memory lua_getfield(state, 1, "value"); if (const char *str = lua_tostring(state, -1)) ref.val() = str; @@ -830,6 +835,7 @@ static int dfhack_persistent_save(lua_State *state) } lua_pop(state, 1); + // Reinitialize lua from C++ and return read_persistent(state, ref, false); lua_pushboolean(state, added); return 2; @@ -856,6 +862,10 @@ static void OpenPersistent(lua_State *state) lua_pop(state, 1); } +/************************ + * Wrappers for C++ API * + ************************/ + static void OpenModule(lua_State *state, const char *mname, const LuaWrapper::FunctionReg *reg) { luaL_getsubtable(state, lua_gettop(state), mname); @@ -867,6 +877,11 @@ static void OpenModule(lua_State *state, const char *mname, const LuaWrapper::Fu #define WRAP(function) { #function, df::wrap_function(&function) } #define WRAPN(name, function) { #name, df::wrap_function(&function) } +static const LuaWrapper::FunctionReg dfhack_module[] = { + WRAPM(Translation, TranslateName), + { NULL, NULL } +}; + static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getSelectedWorkshopJob), WRAPM(Gui, getSelectedJob), @@ -889,6 +904,18 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { { NULL, NULL } }; +static const LuaWrapper::FunctionReg dfhack_units_module[] = { + WRAPM(Units, getVisibleName), + WRAPM(Units, isDead), + WRAPM(Units, isAlive), + WRAPM(Units, isSane), + { NULL, NULL } +}; + +/************************ + * Main Open function * + ************************/ + lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) { if (!state) @@ -917,8 +944,10 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) OpenPersistent(state); + LuaWrapper::SetFunctionWrappers(state, dfhack_module); OpenModule(state, "gui", dfhack_gui_module); OpenModule(state, "job", dfhack_job_module); + OpenModule(state, "units", dfhack_units_module); lua_setglobal(state, "dfhack"); diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 01c110643..00344d6a2 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -256,7 +256,7 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, info->set_pos_y(unit->pos.y); info->set_pos_z(unit->pos.z); - auto name = Units::GetVisibleName(unit); + auto name = Units::getVisibleName(unit); if (name->has_name) describeName(info->mutable_name(), name); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 0fd75bf1f..b966a39be 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -193,7 +193,7 @@ DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target); DFHACK_EXPORT bool RemoveOwnedItemByIdx(const uint32_t index, int32_t id); DFHACK_EXPORT bool RemoveOwnedItemByPtr(df::unit * unit, int32_t id); -DFHACK_EXPORT df::language_name *GetVisibleName(df::unit *unit); +DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 1101367fc..9e2cdf257 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -58,9 +58,10 @@ end -- Misc functions function printall(table) - if table == nil then return end - for k,v in pairs(table) do - print(k," = "..tostring(v)) + if type(table) == 'table' or df.isvalid(table) == 'ref' then + for k,v in pairs(table) do + print(string.format("%-23s\t = %s",tostring(k),tostring(v))) + end end end diff --git a/library/modules/Translation.cpp b/library/modules/Translation.cpp index 7b5fa654c..bc0724707 100644 --- a/library/modules/Translation.cpp +++ b/library/modules/Translation.cpp @@ -35,6 +35,7 @@ using namespace std; #include "Types.h" #include "ModuleFactory.h" #include "Core.h" +#include "Error.h" using namespace DFHack; using namespace df::enums; @@ -93,6 +94,8 @@ void addNameWord (string &out, const string &word) string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart) { + CHECK_NULL_POINTER(name); + string out; string word; diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index f6259bd3b..3dc5d1b04 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -527,8 +527,10 @@ void Units::CopyNameTo(df::unit * creature, df::language_name * target) Translation::copyName(&creature->name, target); } -df::language_name *Units::GetVisibleName(df::unit *unit) +df::language_name *Units::getVisibleName(df::unit *unit) { + CHECK_NULL_POINTER(unit); + df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); if (figure) @@ -555,11 +557,15 @@ df::language_name *Units::GetVisibleName(df::unit *unit) bool DFHack::Units::isDead(df::unit *unit) { + CHECK_NULL_POINTER(unit); + return unit->flags1.bits.dead; } bool DFHack::Units::isAlive(df::unit *unit) { + CHECK_NULL_POINTER(unit); + return !unit->flags1.bits.dead && !unit->flags3.bits.ghostly && !unit->curse.add_tags1.bits.NOT_LIVING; @@ -567,23 +573,22 @@ bool DFHack::Units::isAlive(df::unit *unit) bool DFHack::Units::isSane(df::unit *unit) { + CHECK_NULL_POINTER(unit); + if (unit->flags1.bits.dead || unit->flags3.bits.ghostly || unit->curse.add_tags1.bits.OPPOSED_TO_LIFE || unit->curse.add_tags1.bits.CRAZED) return false; - if (unit->flags1.bits.has_mood) + switch (unit->mood) { - switch (unit->mood) - { - case mood_type::Melancholy: - case mood_type::Raving: - case mood_type::Berserk: - return false; - default: - break; - } + case mood_type::Melancholy: + case mood_type::Raving: + case mood_type::Berserk: + return false; + default: + break; } return true; From 2640addf49d48092104b1ba14518895a5765359d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 6 Apr 2012 18:00:54 +0400 Subject: [PATCH 07/10] Split LuaTools.cpp to separate core utils from general dfhack api. --- library/CMakeLists.txt | 1 + library/LuaApi.cpp | 345 +++++++++++++++++++++++++++++++++++++++++ library/LuaTools.cpp | 278 +-------------------------------- 3 files changed, 349 insertions(+), 275 deletions(-) create mode 100644 library/LuaApi.cpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index d0410850c..b4ea751e7 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -59,6 +59,7 @@ DataDefs.cpp LuaWrapper.cpp LuaTypes.cpp LuaTools.cpp +LuaApi.cpp DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp new file mode 100644 index 000000000..45680f8ef --- /dev/null +++ b/library/LuaApi.cpp @@ -0,0 +1,345 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "Internal.h" + +#include +#include +#include + +#include "MemAccess.h" +#include "Core.h" +#include "VersionInfo.h" +#include "tinythread.h" +// must be last due to MS stupidity +#include "DataDefs.h" +#include "DataIdentity.h" +#include "DataFuncs.h" + +#include "modules/World.h" +#include "modules/Gui.h" +#include "modules/Job.h" +#include "modules/Translation.h" +#include "modules/Units.h" + +#include "LuaWrapper.h" +#include "LuaTools.h" + +#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 +#include + +using namespace DFHack; +using namespace DFHack::LuaWrapper; + +/************************************************** + * Per-world persistent configuration storage API * + **************************************************/ + +static PersistentDataItem persistent_by_struct(lua_State *state, int idx) +{ + lua_getfield(state, idx, "entry_id"); + int id = lua_tointeger(state, -1); + lua_pop(state, 1); + + PersistentDataItem ref = Core::getInstance().getWorld()->GetPersistentData(id); + + if (ref.isValid()) + { + lua_getfield(state, idx, "key"); + const char *str = lua_tostring(state, -1); + if (!str || str != ref.key()) + luaL_argerror(state, idx, "inconsistent id and key"); + lua_pop(state, 1); + } + + return ref; +} + +static int read_persistent(lua_State *state, PersistentDataItem ref, bool create) +{ + if (!ref.isValid()) + { + lua_pushnil(state); + lua_pushstring(state, "entry not found"); + return 2; + } + + if (create) + lua_createtable(state, 0, 4); + + lua_pushvalue(state, lua_upvalueindex(1)); + lua_setmetatable(state, -2); + + lua_pushinteger(state, ref.entry_id()); + lua_setfield(state, -2, "entry_id"); + lua_pushstring(state, ref.key().c_str()); + lua_setfield(state, -2, "key"); + lua_pushstring(state, ref.val().c_str()); + lua_setfield(state, -2, "value"); + + lua_createtable(state, PersistentDataItem::NumInts, 0); + for (int i = 0; i < PersistentDataItem::NumInts; i++) + { + lua_pushinteger(state, ref.ival(i)); + lua_rawseti(state, -2, i+1); + } + lua_setfield(state, -2, "ints"); + + return 1; +} + +static PersistentDataItem get_persistent(lua_State *state) +{ + luaL_checkany(state, 1); + + if (lua_istable(state, 1)) + { + if (!lua_getmetatable(state, 1) || + !lua_rawequal(state, -1, lua_upvalueindex(1))) + luaL_argerror(state, 1, "invalid table type"); + + lua_settop(state, 1); + + return persistent_by_struct(state, 1); + } + else + { + const char *str = luaL_checkstring(state, 1); + + return Core::getInstance().getWorld()->GetPersistentData(str); + } +} + +static int dfhack_persistent_get(lua_State *state) +{ + CoreSuspender suspend; + + auto ref = get_persistent(state); + + return read_persistent(state, ref, !lua_istable(state, 1)); +} + +static int dfhack_persistent_delete(lua_State *state) +{ + CoreSuspender suspend; + + auto ref = get_persistent(state); + + bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); + + lua_pushboolean(state, ok); + return 1; +} + +static int dfhack_persistent_get_all(lua_State *state) +{ + CoreSuspender suspend; + + const char *str = luaL_checkstring(state, 1); + bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false); + + std::vector data; + Core::getInstance().getWorld()->GetPersistentData(&data, str, prefix); + + if (data.empty()) + { + lua_pushnil(state); + } + else + { + lua_createtable(state, data.size(), 0); + for (size_t i = 0; i < data.size(); ++i) + { + read_persistent(state, data[i], true); + lua_rawseti(state, -2, i+1); + } + } + + return 1; +} + +static int dfhack_persistent_save(lua_State *state) +{ + CoreSuspender suspend; + + lua_settop(state, 2); + luaL_checktype(state, 1, LUA_TTABLE); + bool add = lua_toboolean(state, 2); + + lua_getfield(state, 1, "key"); + const char *str = lua_tostring(state, -1); + if (!str) + luaL_argerror(state, 1, "no key field"); + + lua_settop(state, 1); + + // Retrieve existing or create a new entry + PersistentDataItem ref; + bool added = false; + + if (add) + { + ref = Core::getInstance().getWorld()->AddPersistentData(str); + added = true; + } + else if (lua_getmetatable(state, 1)) + { + if (!lua_rawequal(state, -1, lua_upvalueindex(1))) + return luaL_argerror(state, 1, "invalid table type"); + lua_pop(state, 1); + + ref = persistent_by_struct(state, 1); + } + else + { + ref = Core::getInstance().getWorld()->GetPersistentData(str); + } + + // Auto-add if not found + if (!ref.isValid()) + { + ref = Core::getInstance().getWorld()->AddPersistentData(str); + if (!ref.isValid()) + luaL_error(state, "cannot create persistent entry"); + added = true; + } + + // Copy data from lua to C++ memory + lua_getfield(state, 1, "value"); + if (const char *str = lua_tostring(state, -1)) + ref.val() = str; + lua_pop(state, 1); + + lua_getfield(state, 1, "ints"); + if (lua_istable(state, -1)) + { + for (int i = 0; i < PersistentDataItem::NumInts; i++) + { + lua_rawgeti(state, -1, i+1); + if (lua_isnumber(state, -1)) + ref.ival(i) = lua_tointeger(state, -1); + lua_pop(state, 1); + } + } + lua_pop(state, 1); + + // Reinitialize lua from C++ and return + read_persistent(state, ref, false); + lua_pushboolean(state, added); + return 2; +} + +static const luaL_Reg dfhack_persistent_funcs[] = { + { "get", dfhack_persistent_get }, + { "delete", dfhack_persistent_delete }, + { "get_all", dfhack_persistent_get_all }, + { "save", dfhack_persistent_save }, + { NULL, NULL } +}; + +static void OpenPersistent(lua_State *state) +{ + luaL_getsubtable(state, lua_gettop(state), "persistent"); + + lua_dup(state); + luaL_setfuncs(state, dfhack_persistent_funcs, 1); + + lua_dup(state); + lua_setfield(state, -2, "__index"); + + lua_pop(state, 1); +} + +/************************ + * Wrappers for C++ API * + ************************/ + +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_module[] = { + WRAPM(Translation, TranslateName), + { NULL, NULL } +}; + +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 } +}; + +static const LuaWrapper::FunctionReg dfhack_units_module[] = { + WRAPM(Units, getVisibleName), + WRAPM(Units, isDead), + WRAPM(Units, isAlive), + WRAPM(Units, isSane), + { NULL, NULL } +}; + +/************************ + * Main Open function * + ************************/ + +void OpenDFHackApi(lua_State *state) +{ + OpenPersistent(state); + + LuaWrapper::SetFunctionWrappers(state, dfhack_module); + OpenModule(state, "gui", dfhack_gui_module); + OpenModule(state, "job", dfhack_job_module); + OpenModule(state, "units", dfhack_units_module); +} diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 5a87c931d..1e7a83b30 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -643,279 +643,12 @@ static const luaL_Reg dfhack_funcs[] = { { NULL, NULL } }; -/************************************************** - * Per-world persistent configuration storage API * - **************************************************/ - -static PersistentDataItem persistent_by_struct(lua_State *state, int idx) -{ - lua_getfield(state, idx, "entry_id"); - int id = lua_tointeger(state, -1); - lua_pop(state, 1); - - PersistentDataItem ref = Core::getInstance().getWorld()->GetPersistentData(id); - - if (ref.isValid()) - { - lua_getfield(state, idx, "key"); - const char *str = lua_tostring(state, -1); - if (!str || str != ref.key()) - luaL_argerror(state, idx, "inconsistent id and key"); - lua_pop(state, 1); - } - - return ref; -} - -static int read_persistent(lua_State *state, PersistentDataItem ref, bool create) -{ - if (!ref.isValid()) - { - lua_pushnil(state); - lua_pushstring(state, "entry not found"); - return 2; - } - - if (create) - lua_createtable(state, 0, 4); - - lua_pushvalue(state, lua_upvalueindex(1)); - lua_setmetatable(state, -2); - - lua_pushinteger(state, ref.entry_id()); - lua_setfield(state, -2, "entry_id"); - lua_pushstring(state, ref.key().c_str()); - lua_setfield(state, -2, "key"); - lua_pushstring(state, ref.val().c_str()); - lua_setfield(state, -2, "value"); - - lua_createtable(state, PersistentDataItem::NumInts, 0); - for (int i = 0; i < PersistentDataItem::NumInts; i++) - { - lua_pushinteger(state, ref.ival(i)); - lua_rawseti(state, -2, i+1); - } - lua_setfield(state, -2, "ints"); - - return 1; -} - -static PersistentDataItem get_persistent(lua_State *state) -{ - luaL_checkany(state, 1); - - if (lua_istable(state, 1)) - { - if (!lua_getmetatable(state, 1) || - !lua_rawequal(state, -1, lua_upvalueindex(1))) - luaL_argerror(state, 1, "invalid table type"); - - lua_settop(state, 1); - - return persistent_by_struct(state, 1); - } - else - { - const char *str = luaL_checkstring(state, 1); - - return Core::getInstance().getWorld()->GetPersistentData(str); - } -} - -static int dfhack_persistent_get(lua_State *state) -{ - CoreSuspender suspend; - - auto ref = get_persistent(state); - - return read_persistent(state, ref, !lua_istable(state, 1)); -} - -static int dfhack_persistent_delete(lua_State *state) -{ - CoreSuspender suspend; - - auto ref = get_persistent(state); - - bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); - - lua_pushboolean(state, ok); - return 1; -} - -static int dfhack_persistent_get_all(lua_State *state) -{ - CoreSuspender suspend; - - const char *str = luaL_checkstring(state, 1); - bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false); - - std::vector data; - Core::getInstance().getWorld()->GetPersistentData(&data, str, prefix); - - if (data.empty()) - { - lua_pushnil(state); - } - else - { - lua_createtable(state, data.size(), 0); - for (size_t i = 0; i < data.size(); ++i) - { - read_persistent(state, data[i], true); - lua_rawseti(state, -2, i+1); - } - } - - return 1; -} - -static int dfhack_persistent_save(lua_State *state) -{ - CoreSuspender suspend; - - lua_settop(state, 2); - luaL_checktype(state, 1, LUA_TTABLE); - bool add = lua_toboolean(state, 2); - - lua_getfield(state, 1, "key"); - const char *str = lua_tostring(state, -1); - if (!str) - luaL_argerror(state, 1, "no key field"); - - lua_settop(state, 1); - - // Retrieve existing or create a new entry - PersistentDataItem ref; - bool added = false; - - if (add) - { - ref = Core::getInstance().getWorld()->AddPersistentData(str); - added = true; - } - else if (lua_getmetatable(state, 1)) - { - if (!lua_rawequal(state, -1, lua_upvalueindex(1))) - return luaL_argerror(state, 1, "invalid table type"); - lua_pop(state, 1); - - ref = persistent_by_struct(state, 1); - } - else - { - ref = Core::getInstance().getWorld()->GetPersistentData(str); - } - - // Auto-add if not found - if (!ref.isValid()) - { - ref = Core::getInstance().getWorld()->AddPersistentData(str); - if (!ref.isValid()) - luaL_error(state, "cannot create persistent entry"); - added = true; - } - - // Copy data from lua to C++ memory - lua_getfield(state, 1, "value"); - if (const char *str = lua_tostring(state, -1)) - ref.val() = str; - lua_pop(state, 1); - - lua_getfield(state, 1, "ints"); - if (lua_istable(state, -1)) - { - for (int i = 0; i < PersistentDataItem::NumInts; i++) - { - lua_rawgeti(state, -1, i+1); - if (lua_isnumber(state, -1)) - ref.ival(i) = lua_tointeger(state, -1); - lua_pop(state, 1); - } - } - lua_pop(state, 1); - - // Reinitialize lua from C++ and return - read_persistent(state, ref, false); - lua_pushboolean(state, added); - return 2; -} - -static const luaL_Reg dfhack_persistent_funcs[] = { - { "get", dfhack_persistent_get }, - { "delete", dfhack_persistent_delete }, - { "get_all", dfhack_persistent_get_all }, - { "save", dfhack_persistent_save }, - { NULL, NULL } -}; - -static void OpenPersistent(lua_State *state) -{ - luaL_getsubtable(state, lua_gettop(state), "persistent"); - - lua_dup(state); - luaL_setfuncs(state, dfhack_persistent_funcs, 1); - - lua_dup(state); - lua_setfield(state, -2, "__index"); - - lua_pop(state, 1); -} - -/************************ - * Wrappers for C++ API * - ************************/ - -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_module[] = { - WRAPM(Translation, TranslateName), - { NULL, NULL } -}; - -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 } -}; - -static const LuaWrapper::FunctionReg dfhack_units_module[] = { - WRAPM(Units, getVisibleName), - WRAPM(Units, isDead), - WRAPM(Units, isAlive), - WRAPM(Units, isSane), - { NULL, NULL } -}; - /************************ * Main Open function * ************************/ +void OpenDFHackApi(lua_State *state); + lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) { if (!state) @@ -942,12 +675,7 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) // Initialize the dfhack global luaL_setfuncs(state, dfhack_funcs, 0); - OpenPersistent(state); - - LuaWrapper::SetFunctionWrappers(state, dfhack_module); - OpenModule(state, "gui", dfhack_gui_module); - OpenModule(state, "job", dfhack_job_module); - OpenModule(state, "units", dfhack_units_module); + OpenDFHackApi(state); lua_setglobal(state, "dfhack"); From 0daafef690d6a9e44f7caa7f61b89a63a3870cf9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 6 Apr 2012 19:56:19 +0400 Subject: [PATCH 08/10] Wrap MaterialInfo for lua. --- LUA_API.rst | 53 +++++++ Lua API.html | 60 ++++++- library/LuaApi.cpp | 233 ++++++++++++++++++++++++++++ library/LuaWrapper.cpp | 3 + library/include/modules/Materials.h | 1 + library/lua/dfhack.lua | 4 + library/modules/Materials.cpp | 12 +- 7 files changed, 357 insertions(+), 9 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 422ba6b49..0ec837a2d 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -498,6 +498,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. +Material info lookup +==================== + +A material info record has fields: + +* ``type``, ``index``, ``material`` + + DF material code pair, and a reference to the material object. + +* ``mode`` + + One of ``'builtin'``, ``'inorganic'``, ``'plant'``, ``'creature'``. + +* ``inorganic``, ``plant``, ``creature`` + + If the material is of the matching type, contains a reference to the raw object. + +* ``figure`` + + For a specific creature material contains a ref to the historical figure. + +Functions: + +* ``dfhack.matinfo.decode(type,index)`` + + Looks up material info for the given number pair; if not found, returs *nil*. + +* ``....decode(matinfo)``, ``....decode(item)``, ``....decode(obj)`` + + Uses ``matinfo.type``/``matinfo.index``, item getter vmethods, + or ``obj.mat_type``/``obj.mat_index`` to get the code pair. + +* ``dfhack.matinfo.find(token[,token...])`` + + Looks up material by a token string, or a pre-split string token sequence. + +* ``dfhack.matinfo.getToken(...)``, ``info:getToken()`` + + Applies ``decode`` and constructs a string token. + +* ``info:toString([temperature[,named]])`` + + Returns the human-readable name at the given temperature. + +* ``info:getCraftClass()`` + + Returns the classification used for craft skills. + +* ``info:matches(obj)`` + + Checks if the material matches job_material_category or job_item. + Accept dfhack_material_category auto-assign table. + C++ function wrappers ===================== diff --git a/Lua API.html b/Lua API.html index ab64eea9a..379432a78 100644 --- a/Lua API.html +++ b/Lua API.html @@ -335,10 +335,11 @@ ul.auto-toc {
  • DFHack utilities
    • Persistent configuration storage
    • -
    • C++ function wrappers @@ -759,8 +760,51 @@ 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.

      +
      +

      Material info lookup

      +

      A material info record has fields:

      +
        +
      • type, index, material

        +

        DF material code pair, and a reference to the material object.

        +
      • +
      • mode

        +

        One of 'builtin', 'inorganic', 'plant', 'creature'.

        +
      • +
      • inorganic, plant, creature

        +

        If the material is of the matching type, contains a reference to the raw object.

        +
      • +
      • figure

        +

        For a specific creature material contains a ref to the historical figure.

        +
      • +
      +

      Functions:

      +
        +
      • dfhack.matinfo.decode(type,index)

        +

        Looks up material info for the given number pair; if not found, returs nil.

        +
      • +
      • ....decode(matinfo), ....decode(item), ....decode(obj)

        +

        Uses matinfo.type/matinfo.index, item getter vmethods, +or obj.mat_type/obj.mat_index to get the code pair.

        +
      • +
      • dfhack.matinfo.find(token[,token...])

        +

        Looks up material by a token string, or a pre-split string token sequence.

        +
      • +
      • dfhack.matinfo.getToken(...), info:getToken()

        +

        Applies decode and constructs a string token.

        +
      • +
      • info:toString([temperature[,named]])

        +

        Returns the human-readable name at the given temperature.

        +
      • +
      • info:getCraftClass()

        +

        Returns the classification used for craft skills.

        +
      • +
      • info:matches(obj)

        +

        Checks if the material matches job_material_category or job_item.

        +
      • +
      +
      -

      C++ function wrappers

      +

      C++ function wrappers

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

      • dfhack.TranslateName(name,in_english,only_last_name)

        @@ -768,7 +812,7 @@ However, currently every entry has a 180+-byte dead-weight overhead.

      -

      Gui module

      +

      Gui module

      • dfhack.gui.getSelectedWorkshopJob(silent)

        When a job is selected in 'q' mode, returns the job, else @@ -797,7 +841,7 @@ The is_bright boolean actually seems to invert the brightness.

      -

      Job module

      +

      Job module

      • dfhack.job.cloneJobStruct(job)

        Creates a deep copy of the given job.

        @@ -817,7 +861,7 @@ The is_bright boolean actually seems to invert the brightness.

      -

      Units module

      +

      Units module

      • dfhack.units.getVisibleName(unit)

        Returns the name visible in game, accounting for false identities.

        diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 45680f8ef..f1d86580a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -42,6 +42,7 @@ distribution. #include "modules/Job.h" #include "modules/Translation.h" #include "modules/Units.h" +#include "modules/Materials.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -53,6 +54,13 @@ distribution. #include "df/building.h" #include "df/unit.h" #include "df/item.h" +#include "df/material.h" +#include "df/historical_figure.h" +#include "df/plant_raw.h" +#include "df/creature_raw.h" +#include "df/inorganic_raw.h" +#include "df/dfhack_material_category.h" +#include "df/job_material_category.h" #include #include @@ -280,6 +288,230 @@ static void OpenPersistent(lua_State *state) lua_pop(state, 1); } +/************************ + * Material info lookup * + ************************/ + +static void push_matinfo(lua_State *state, MaterialInfo &info) +{ + if (!info.isValid()) + { + lua_pushnil(state); + return; + } + + lua_newtable(state); + lua_pushvalue(state, lua_upvalueindex(1)); + lua_setmetatable(state, -2); + + lua_pushinteger(state, info.type); + lua_setfield(state, -2, "type"); + lua_pushinteger(state, info.index); + lua_setfield(state, -2, "index"); + +#define SETOBJ(name) { \ + Lua::PushDFObject(state, info.name); \ + lua_setfield(state, -2, #name); \ +} + SETOBJ(material); + if (info.plant) SETOBJ(plant); + if (info.creature) SETOBJ(creature); + if (info.inorganic) SETOBJ(inorganic); + if (info.figure) SETOBJ(figure); +#undef SETOBJ + + if (info.mode != MaterialInfo::Builtin) + { + lua_pushinteger(state, info.subtype); + lua_setfield(state, -2, "subtype"); + } + + const char *id = "builtin"; + switch (info.mode) + { + case MaterialInfo::Plant: id = "plant"; break; + case MaterialInfo::Creature: id = "creature"; break; + case MaterialInfo::Inorganic: id = "inorganic"; break; + } + + lua_pushstring(state, id); + lua_setfield(state, -2, "mode"); +} + +static int dfhack_matinfo_find(lua_State *state) +{ + MaterialInfo info; + int argc = lua_gettop(state); + + if (argc == 1) + info.find(luaL_checkstring(state, 1)); + else + { + std::vector tokens; + + for (int i = 1; i < argc; i++) + tokens.push_back(luaL_checkstring(state, i)); + + info.find(tokens); + } + + push_matinfo(state, info); + return 1; +} + +static bool decode_matinfo(lua_State *state, MaterialInfo *info, bool numpair = false) +{ + int curtop = lua_gettop(state); + + luaL_checkany(state, 1); + + if (!lua_isnumber(state, 1)) + { + if (lua_isnil(state, 1)) + return false; + + if (lua_getmetatable(state, 1)) + { + if (lua_rawequal(state, -1, lua_upvalueindex(1))) + { + lua_getfield(state, 1, "type"); + lua_getfield(state, 1, "index"); + goto int_pair; + } + + lua_pop(state, 1); + } + + if (lua_isuserdata(state, 1)) + { + if (auto item = Lua::GetDFObject(state, 1)) + return info->decode(item); + if (auto mvec = Lua::GetDFObject(state, 1)) + return info->decode(*mvec, luaL_checkint(state, 2)); + } + + lua_getfield(state, 1, "mat_type"); + lua_getfield(state, 1, "mat_index"); + goto int_pair; + } + else + { + if (!numpair) + luaL_argerror(state, 1, "material info object expected"); + + if (curtop < 2) + lua_settop(state, 2); + } + +int_pair: + { + int ok; + int type = lua_tointegerx(state, -2, &ok); + if (!ok) + luaL_argerror(state, 1, "material id is not a number"); + int index = lua_tointegerx(state, -1, &ok); + if (!ok) + index = -1; + + lua_settop(state, curtop); + + return info->decode(type, index); + } +} + +static int dfhack_matinfo_decode(lua_State *state) +{ + MaterialInfo info; + decode_matinfo(state, &info, true); + push_matinfo(state, info); + return 1; +} + +static int dfhack_matinfo_getToken(lua_State *state) +{ + MaterialInfo info; + decode_matinfo(state, &info, true); + auto str = info.getToken(); + lua_pushstring(state, str.c_str()); + return 1; +} + +static int dfhack_matinfo_toString(lua_State *state) +{ + MaterialInfo info; + decode_matinfo(state, &info); + + lua_settop(state, 3); + auto str = info.toString(luaL_optint(state, 2, 10015), lua_toboolean(state, 3)); + lua_pushstring(state, str.c_str()); + return 1; +} + +static int dfhack_matinfo_getCraftClass(lua_State *state) +{ + MaterialInfo info; + if (decode_matinfo(state, &info, true)) + lua_pushinteger(state, info.getCraftClass()); + else + lua_pushnil(state); + return 1; +} + +static int dfhack_matinfo_matches(lua_State *state) +{ + MaterialInfo info; + if (!decode_matinfo(state, &info)) + luaL_argerror(state, 1, "material info object expected"); + + luaL_checkany(state, 2); + + if (lua_isuserdata(state, 2)) + { + if (auto mc = Lua::GetDFObject(state, 2)) + lua_pushboolean(state, info.matches(*mc)); + else if (auto mc = Lua::GetDFObject(state, 2)) + lua_pushboolean(state, info.matches(*mc)); + else if (auto mc = Lua::GetDFObject(state, 2)) + lua_pushboolean(state, info.matches(*mc)); + else + luaL_argerror(state, 2, "material category object expected"); + } + else if (lua_istable(state, 2)) + { + df::dfhack_material_category tmp; + if (!Lua::AssignDFObject(*Lua::GetOutput(state), state, &tmp, 2, false)) + lua_error(state); + lua_pushboolean(state, info.matches(tmp)); + } + else + luaL_argerror(state, 2, "material category object expected"); + + return 1; +} + +static const luaL_Reg dfhack_matinfo_funcs[] = { + { "find", dfhack_matinfo_find }, + { "decode", dfhack_matinfo_decode }, + { "getToken", dfhack_matinfo_getToken }, + { "toString", dfhack_matinfo_toString }, + { "getCraftClass", dfhack_matinfo_getCraftClass }, + { "matches", dfhack_matinfo_matches }, + { NULL, NULL } +}; + +static void OpenMatinfo(lua_State *state) +{ + luaL_getsubtable(state, lua_gettop(state), "matinfo"); + + lua_dup(state); + luaL_setfuncs(state, dfhack_matinfo_funcs, 1); + + lua_dup(state); + lua_setfield(state, -2, "__index"); + + lua_pop(state, 1); +} + /************************ * Wrappers for C++ API * ************************/ @@ -337,6 +569,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { void OpenDFHackApi(lua_State *state) { OpenPersistent(state); + OpenMatinfo(state); LuaWrapper::SetFunctionWrappers(state, dfhack_module); OpenModule(state, "gui", dfhack_gui_module); diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 2f3958efe..1c3b61c15 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -699,6 +699,9 @@ static int meta_assign(lua_State *state) { type_identity *id = get_object_identity(state, 1, "df.assign()", false); + if (lua_getmetatable(state, 2)) + luaL_error(state, "cannot use lua tables with metatable in df.assign()"); + int base = lua_gettop(state); // x:assign{ assign = foo } => x:assign(foo) diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index f3c526d76..3d70dc9fa 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -114,6 +114,7 @@ namespace DFHack } bool find(const std::string &token); + bool find(const std::vector &tokens); bool findBuiltin(const std::string &token); bool findInorganic(const std::string &token); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 9e2cdf257..d662efed7 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -70,5 +70,9 @@ function dfhack.persistent:__tostring() ..self.value.."\":"..table.concat(self.ints,",")..">" end +function dfhack.matinfo:__tostring() + return "" +end + -- Feed the table back to the require() mechanism. return dfhack diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 8f5739c7e..0172e24f8 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -164,6 +164,13 @@ bool MaterialInfo::find(const std::string &token) { std::vector items; split_string(&items, token, ":"); + return find(items); +} + +bool MaterialInfo::find(const std::vector &items) +{ + if (items.empty()) + return false; if (items[0] == "INORGANIC" && items.size() > 1) return findInorganic(vector_get(items,1)); @@ -204,8 +211,11 @@ bool MaterialInfo::findBuiltin(const std::string &token) df::world_raws &raws = world->raws; for (int i = 1; i < NUM_BUILTIN; i++) - if (raws.mat_table.builtin[i]->id == token) + { + auto obj = raws.mat_table.builtin[i]; + if (obj && obj->id == token) return decode(i, -1); + } return decode(-1); } From e74788cb26edd41e484071ac3a371422c6be8773 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 7 Apr 2012 14:21:38 +0400 Subject: [PATCH 09/10] Add a generic facility for object finalization during stack unwind. Supports two modes of finalization: - try {...} finally {...} - try {...} catch { ...; throw } Argument passing discipline is designed with vararg tail calls in mind. --- LUA_API.rst | 25 ++++++ Lua API.html | 22 +++++- library/LuaTools.cpp | 167 +++++++++++++++++++++++++++++++++++------ library/lua/dfhack.lua | 15 ++++ 4 files changed, 203 insertions(+), 26 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 0ec837a2d..57a3fa16c 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -456,6 +456,31 @@ Currently it defines the following features: to group operations together in one big critical section. A plugin can choose to run all lua code inside a C++-side suspend lock. +* ``dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])`` + + Invokes ``fn`` with ``args``, and after it returns or throws an + error calls ``cleanup_fn`` with ``cleanup_args``. Any return values from + ``fn`` are propagated, and errors are re-thrown. + + The ``num_cleanup_args`` integer specifies the number of ``cleanup_args``, + and the ``always`` boolean specifies if cleanup should be called in any case, + or only in case of an error. + +* ``dfhack.with_finalize(cleanup_fn,fn[,args...])`` + + Calls ``fn`` with arguments, then finalizes with ``cleanup_fn``. + Implemented using ``call_with_finalizer(0,true,...)``. + +* ``dfhack.with_onerror(cleanup_fn,fn[,args...])`` + + Calls ``fn`` with arguments, then finalizes with ``cleanup_fn`` on any thrown error. + Implemented using ``call_with_finalizer(0,false,...)``. + +* ``dfhack.with_temp_object(obj,fn[,args...])`` + + Calls ``fn(obj,args...)``, then finalizes with ``obj:delete()``. + + Persistent configuration storage ================================ diff --git a/Lua API.html b/Lua API.html index 379432a78..19f912b72 100644 --- a/Lua API.html +++ b/Lua API.html @@ -724,6 +724,25 @@ the lock. It is safe to nest suspends.

        to group operations together in one big critical section. A plugin can choose to run all lua code inside a C++-side suspend lock.

      • +
      • dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])

        +

        Invokes fn with args, and after it returns or throws an +error calls cleanup_fn with cleanup_args. Any return values from +fn are propagated, and errors are re-thrown.

        +

        The num_cleanup_args integer specifies the number of cleanup_args, +and the always boolean specifies if cleanup should be called in any case, +or only in case of an error.

        +
      • +
      • dfhack.with_finalize(cleanup_fn,fn[,args...])

        +

        Calls fn with arguments, then finalizes with cleanup_fn. +Implemented using call_with_finalizer(0,true,...).

        +
      • +
      • dfhack.with_onerror(cleanup_fn,fn[,args...])

        +

        Calls fn with arguments, then finalizes with cleanup_fn on any thrown error. +Implemented using call_with_finalizer(0,false,...).

        +
      • +
      • dfhack.with_temp_object(obj,fn[,args...])

        +

        Calls fn(obj,args...), then finalizes with obj:delete().

        +

      Persistent configuration storage

      @@ -799,7 +818,8 @@ or obj.mat_type/o

      Returns the classification used for craft skills.

    • info:matches(obj)

      -

      Checks if the material matches job_material_category or job_item.

      +

      Checks if the material matches job_material_category or job_item. +Accept dfhack_material_category auto-assign table.

    diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 1e7a83b30..471ffd550 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -226,10 +226,14 @@ static int lua_dfhack_lineedit(lua_State *S) static int DFHACK_EXCEPTION_META_TOKEN = 0; -static void error_tostring(lua_State *L) +static void error_tostring(lua_State *L, bool keep_old = false) { lua_getglobal(L, "tostring"); - lua_pushvalue(L, -2); + if (keep_old) + lua_pushvalue(L, -2); + else + lua_swap(L); + bool ok = lua_pcall(L, 1, 1, 0) == LUA_OK; const char *msg = lua_tostring(L, -1); @@ -248,8 +252,7 @@ static void error_tostring(lua_State *L) static void report_error(lua_State *L, color_ostream *out = NULL) { - lua_dup(L); - error_tostring(L); + error_tostring(L, true); const char *msg = lua_tostring(L, -1); assert(msg); @@ -290,7 +293,7 @@ static bool convert_to_exception(lua_State *L) lua_setfield(L, base, "message"); else { - error_tostring(L); + error_tostring(L, true); lua_setfield(L, base, "message"); lua_setfield(L, base, "object"); } @@ -346,24 +349,51 @@ static int dfhack_exception_tostring(lua_State *L) if (!lua_isstring(L, -1)) lua_pop(L, 2); + lua_pushstring(L, "\ncaused by:\n"); + lua_getfield(L, 1, "cause"); + if (lua_isnil(L, -1)) + lua_pop(L, 2); + else + error_tostring(L); + lua_concat(L, lua_gettop(L) - base); return 1; } -static int finish_dfhack_safecall (lua_State *L, bool success) +static void push_simple_error(lua_State *L, const char *str) { - if (!lua_checkstack(L, 2)) + lua_pushstring(L, str); + + if (lua_checkstack(L, 5)) + convert_to_exception(L); + + if (lua_checkstack(L, LUA_MINSTACK)) + { + luaL_traceback(L, L, NULL, 1); + lua_setfield(L, -2, "stacktrace"); + } +} + +static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space = 2) +{ + if (!lua_checkstack(L, space)) { - lua_settop(L, 0); /* create space for return values */ + lua_settop(L, base-1); /* create space for return values */ lua_pushboolean(L, 0); - lua_pushstring(L, "stack overflow in dfhack.safecall()"); - success = false; + push_simple_error(L, "stack overflow"); + return false; } else { lua_pushboolean(L, success); - lua_replace(L, 1); /* put first result in first slot */ + lua_replace(L, base); /* put first result in first slot */ + return true; } +} + +static int finish_dfhack_safecall (lua_State *L, bool success) +{ + success = do_finish_pcall(L, success); if (!success) report_error(L); @@ -599,32 +629,118 @@ static int lua_dfhack_interpreter(lua_State *state) return 1; } -static int lua_dfhack_with_suspend(lua_State *L) +static bool do_invoke_cleanup(lua_State *L, int nargs, int errorfun, bool success) { - int rv = lua_getctx(L, NULL); + bool ok = lua_pcall(L, nargs, 0, errorfun) == LUA_OK; - // Non-resume entry point: - if (rv == LUA_OK) + if (!ok) { - int nargs = lua_gettop(L); + // If finalization failed, attach the previous error + if (lua_istable(L, -1) && !success) + { + lua_swap(L); + lua_setfield(L, -2, "cause"); + } - luaL_checktype(L, 1, LUA_TFUNCTION); + success = false; + } + + return success; +} - Core::getInstance().Suspend(); +static int finish_dfhack_cleanup (lua_State *L, bool success) +{ + int nargs = lua_tointeger(L, 1); + bool always = lua_toboolean(L, 2); + int rvbase = 4+nargs; + + // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [rvals/error...] - lua_pushcfunction(L, dfhack_onerror); - lua_insert(L, 1); + int numret = lua_gettop(L) - rvbase; + + if (!success || always) + { + if (numret > 0) + { + if (numret == 1) + { + // Inject the only result instead of pulling cleanup args + lua_insert(L, 4); + } + else if (!lua_checkstack(L, nargs+1)) + { + success = false; + lua_settop(L, rvbase); + push_simple_error(L, "stack overflow"); + lua_insert(L, 4); + } + else + { + for (int i = 0; i <= nargs; i++) + lua_pushvalue(L, 4+i); + } + } - rv = lua_pcallk(L, nargs-1, LUA_MULTRET, 1, 0, lua_dfhack_with_suspend); + success = do_invoke_cleanup(L, nargs, 3, success); } - // Return, resume, or error entry point: - lua_remove(L, 1); + if (!success) + lua_error(L); + + return numret; +} + +static int dfhack_cleanup_cont (lua_State *L) +{ + int status = lua_getctx(L, NULL); + return finish_dfhack_cleanup(L, (status == LUA_YIELD)); +} - Core::getInstance().Resume(); +static int dfhack_call_with_finalizer (lua_State *L) +{ + int nargs = luaL_checkint(L, 1); + if (nargs < 0) + luaL_argerror(L, 1, "invalid cleanup argument count"); + luaL_checktype(L, 3, LUA_TFUNCTION); - if (rv != LUA_OK && rv != LUA_YIELD) + // Inject errorfun + lua_pushcfunction(L, dfhack_onerror); + lua_insert(L, 3); + + int rvbase = 4+nargs; // rvbase+1 points to the function argument + + if (lua_gettop(L) < rvbase) + luaL_error(L, "not enough arguments even to invoke cleanup"); + + // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [fun] [args...] + + // Not enough stack to call and post-cleanup, or nothing to call? + bool no_args = lua_gettop(L) == rvbase; + + if (!lua_checkstack(L, nargs+2) || no_args) + { + push_simple_error(L, no_args ? "fn argument expected" : "stack overflow"); + lua_insert(L, 4); + + // stack: ... [errorfun] [error] [cleanup fun] [cleanup args...] + do_invoke_cleanup(L, nargs, 3, false); lua_error(L); + } + + // Actually invoke + + // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [fun] [args...] + int status = lua_pcallk(L, lua_gettop(L)-rvbase-1, LUA_MULTRET, 3, 0, dfhack_cleanup_cont); + return finish_dfhack_cleanup(L, (status == LUA_OK)); +} + +static int lua_dfhack_with_suspend(lua_State *L) +{ + int nargs = lua_gettop(L); + luaL_checktype(L, 1, LUA_TFUNCTION); + + CoreSuspender suspend; + lua_call(L, nargs-1, LUA_MULTRET); return lua_gettop(L); } @@ -639,6 +755,7 @@ static const luaL_Reg dfhack_funcs[] = { { "interpreter", lua_dfhack_interpreter }, { "safecall", lua_dfhack_safecall }, { "onerror", dfhack_onerror }, + { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, { NULL, NULL } }; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index d662efed7..b37183cb6 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -29,6 +29,21 @@ function dfhack.pcall(f, ...) return xpcall(f, dfhack.onerror, ...) end +function dfhack.with_finalize(...) + return dfhack.call_with_finalizer(0,true,...) +end +function dfhack.with_onerror(...) + return dfhack.call_with_finalizer(0,false,...) +end + +local function call_delete(obj) + if obj then obj:delete() end +end + +function dfhack.with_temp_object(obj,fn,...) + return dfhack.call_with_finalizer(1,true,call_delete,obj,fn,obj,...) +end + -- Module loading function mkmodule(module,env) From 45ae2ed67f9aba4d635a7565253bd9098a87bbde Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 7 Apr 2012 19:08:30 +0400 Subject: [PATCH 10/10] Pull a few utility functions into the core and publish to lua. --- LUA_API.rst | 10 ++++- Lua API.html | 8 +++- library/LuaApi.cpp | 3 ++ library/include/modules/Translation.h | 2 + library/include/modules/Units.h | 8 ++++ library/modules/Gui.cpp | 4 ++ library/modules/Translation.cpp | 15 +++++++ library/modules/Units.cpp | 59 ++++++++++++++++++++++++++ plugins/advtools.cpp | 33 +++------------ plugins/rename.cpp | 60 +++------------------------ 10 files changed, 117 insertions(+), 85 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 57a3fa16c..71cb6030f 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -644,9 +644,17 @@ Job module Units module ------------ +* ``dfhack.units.setNickname(unit,nick)`` + + Sets the unit's nickname properly. + * ``dfhack.units.getVisibleName(unit)`` - Returns the name visible in game, accounting for false identities. + Returns the language_name object visible in game, accounting for false identities. + +* ``dfhack.units.getNemesis(unit)`` + + Returns the nemesis record of the unit if it has one, or *nil*. * ``dfhack.units.isDead(unit)`` diff --git a/Lua API.html b/Lua API.html index 19f912b72..569940f56 100644 --- a/Lua API.html +++ b/Lua API.html @@ -883,8 +883,14 @@ The is_bright boolean actually seems to invert the brightness.

    Units module

      +
    • dfhack.units.setNickname(unit,nick)

      +

      Sets the unit's nickname properly.

      +
    • dfhack.units.getVisibleName(unit)

      -

      Returns the name visible in game, accounting for false identities.

      +

      Returns the language_name object visible in game, accounting for false identities.

      +
    • +
    • dfhack.units.getNemesis(unit)

      +

      Returns the nemesis record of the unit if it has one, or nil.

    • dfhack.units.isDead(unit)

      The unit is completely dead and passive.

      diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index f1d86580a..5e55460d7 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -55,6 +55,7 @@ distribution. #include "df/unit.h" #include "df/item.h" #include "df/material.h" +#include "df/nemesis_record.h" #include "df/historical_figure.h" #include "df/plant_raw.h" #include "df/creature_raw.h" @@ -555,7 +556,9 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { }; static const LuaWrapper::FunctionReg dfhack_units_module[] = { + WRAPM(Units, setNickname), WRAPM(Units, getVisibleName), + WRAPM(Units, getNemesis), WRAPM(Units, isDead), WRAPM(Units, isAlive), WRAPM(Units, isSane), diff --git a/library/include/modules/Translation.h b/library/include/modules/Translation.h index 19a3ed9a6..6d74431e3 100644 --- a/library/include/modules/Translation.h +++ b/library/include/modules/Translation.h @@ -50,6 +50,8 @@ DFHACK_EXPORT bool IsValid (); DFHACK_EXPORT bool readName(t_name & name, df::language_name * address); DFHACK_EXPORT bool copyName(df::language_name * address, df::language_name * target); +DFHACK_EXPORT void setNickname(df::language_name *name, std::string nick); + // translate a name using the loaded dictionaries DFHACK_EXPORT std::string TranslateName (const df::language_name * name, bool inEnglish = true, bool onlyLastPart = false); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index b966a39be..838d1d596 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -33,6 +33,11 @@ distribution. #include "DataDefs.h" #include "df/unit.h" +namespace df +{ + struct nemesis_record; +} + /** * \defgroup grp_units Unit module parts * @ingroup grp_modules @@ -193,8 +198,11 @@ DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target); DFHACK_EXPORT bool RemoveOwnedItemByIdx(const uint32_t index, int32_t id); DFHACK_EXPORT bool RemoveOwnedItemByPtr(df::unit * unit, int32_t id); +DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); +DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); + DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit); DFHACK_EXPORT bool isSane(df::unit *unit); diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index aae2a90b9..592f27c0b 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -46,6 +46,7 @@ using namespace DFHack; #include "df/global_objects.h" #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dungeonmodest.h" +#include "df/viewscreen_dungeon_monsterstatusst.h" #include "df/viewscreen_joblistst.h" #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_itemst.h" @@ -284,6 +285,9 @@ static df::unit *getAnyUnit(df::viewscreen *top) if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitlistst, top)) return vector_get(screen->units[screen->page], screen->cursor_pos[screen->page]); + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_dungeon_monsterstatusst, top)) + return screen->unit; + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_itemst, top)) { df::general_ref *ref = vector_get(screen->entry_ref, screen->cursor_pos); diff --git a/library/modules/Translation.cpp b/library/modules/Translation.cpp index bc0724707..6b055a4ac 100644 --- a/library/modules/Translation.cpp +++ b/library/modules/Translation.cpp @@ -92,6 +92,21 @@ void addNameWord (string &out, const string &word) out.append(upper); } +void Translation::setNickname(df::language_name *name, std::string nick) +{ + CHECK_NULL_POINTER(name); + + if (!name->has_name) + { + *name = df::language_name(); + + name->language = 0; + name->has_name = true; + } + + name->nickname = nick; +} + string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart) { CHECK_NULL_POINTER(name); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 3dc5d1b04..e210a9028 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -48,6 +48,8 @@ using namespace std; #include "df/world.h" #include "df/ui.h" #include "df/unit_inventory_item.h" +#include "df/unit_soul.h" +#include "df/nemesis_record.h" #include "df/historical_entity.h" #include "df/historical_figure.h" #include "df/historical_figure_info.h" @@ -527,6 +529,47 @@ void Units::CopyNameTo(df::unit * creature, df::language_name * target) Translation::copyName(&creature->name, target); } +void Units::setNickname(df::unit *unit, std::string nick) +{ + CHECK_NULL_POINTER(unit); + + // There are >=3 copies of the name, and the one + // in the unit is not the authoritative one. + // This is the reason why military units often + // lose nicknames set from Dwarf Therapist. + Translation::setNickname(&unit->name, nick); + + if (unit->status.current_soul) + Translation::setNickname(&unit->status.current_soul->name, nick); + + df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); + if (figure) + { + Translation::setNickname(&figure->name, nick); + + // v0.34.01: added the vampire's assumed identity + if (figure->info && figure->info->reputation) + { + auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity); + + if (identity) + { + auto id_hfig = df::historical_figure::find(identity->histfig_id); + + if (id_hfig) + { + // Even DF doesn't do this bit, because it's apparently + // only used for demons masquerading as gods, so you + // can't ever change their nickname in-game. + Translation::setNickname(&id_hfig->name, nick); + } + else + Translation::setNickname(&identity->name, nick); + } + } + } +} + df::language_name *Units::getVisibleName(df::unit *unit) { CHECK_NULL_POINTER(unit); @@ -555,6 +598,22 @@ df::language_name *Units::getVisibleName(df::unit *unit) return &unit->name; } +df::nemesis_record *Units::getNemesis(df::unit *unit) +{ + if (!unit) + return NULL; + + for (unsigned i = 0; i < unit->refs.size(); i++) + { + df::nemesis_record *rv = unit->refs[i]->getNemesis(); + if (rv && rv->unit == unit) + return rv; + } + + return NULL; +} + + bool DFHack::Units::isDead(df::unit *unit) { CHECK_NULL_POINTER(unit); diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index 4fc1b4e5d..ffb428fd8 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -8,6 +8,8 @@ #include "modules/Materials.h" #include "modules/Maps.h" #include "modules/Items.h" +#include "modules/Gui.h" +#include "modules/Units.h" #include "DataDefs.h" #include "df/world.h" @@ -159,31 +161,6 @@ static bool bodyswap_hotkey(df::viewscreen *top) !!virtual_cast(top); } -df::unit *getCurUnit() -{ - auto top = Core::getTopViewscreen(); - - if (VIRTUAL_CAST_VAR(ms, df::viewscreen_dungeon_monsterstatusst, top)) - return ms->unit; - - return NULL; -} - -df::nemesis_record *getNemesis(df::unit *unit) -{ - if (!unit) - return NULL; - - for (unsigned i = 0; i < unit->refs.size(); i++) - { - df::nemesis_record *rv = unit->refs[i]->getNemesis(); - if (rv && rv->unit == unit) - return rv; - } - - return NULL; -} - bool bodySwap(color_ostream &out, df::unit *player) { if (!player) @@ -219,7 +196,7 @@ df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap) if (restore_swap) { df::unit *ctl = world->units.other[0][0]; - auto ctl_nemesis = getNemesis(ctl); + auto ctl_nemesis = Units::getNemesis(ctl); if (ctl_nemesis != real_nemesis) { @@ -672,8 +649,8 @@ command_result adv_bodyswap (color_ostream &out, std::vector & par return CR_FAILURE; // Get the unit to swap to - auto new_unit = getCurUnit(); - auto new_nemesis = getNemesis(new_unit); + auto new_unit = Gui::getSelectedUnit(out, true); + auto new_nemesis = Units::getNemesis(new_unit); if (!new_nemesis) { diff --git a/plugins/rename.cpp b/plugins/rename.cpp index d983d0962..7302aea60 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -4,6 +4,8 @@ #include "PluginManager.h" #include "modules/Gui.h" +#include "modules/Translation.h" +#include "modules/Units.h" #include "DataDefs.h" #include "df/ui.h" @@ -59,19 +61,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -static void set_nickname(df::language_name *name, std::string nick) -{ - if (!name->has_name) - { - *name = df::language_name(); - - name->language = 0; - name->has_name = true; - } - - name->nickname = nick; -} - static df::squad *getSquadByIndex(unsigned idx) { auto entity = df::historical_entity::find(ui->group_id); @@ -84,45 +73,6 @@ static df::squad *getSquadByIndex(unsigned idx) return df::squad::find(entity->squads[idx]); } -void setUnitNickname(df::unit *unit, const std::string &nick) -{ - // There are >=3 copies of the name, and the one - // in the unit is not the authoritative one. - // This is the reason why military units often - // lose nicknames set from Dwarf Therapist. - set_nickname(&unit->name, nick); - - if (unit->status.current_soul) - set_nickname(&unit->status.current_soul->name, nick); - - df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); - if (figure) - { - set_nickname(&figure->name, nick); - - // v0.34.01: added the vampire's assumed identity - if (figure->info && figure->info->reputation) - { - auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity); - - if (identity) - { - auto id_hfig = df::historical_figure::find(identity->histfig_id); - - if (id_hfig) - { - // Even DF doesn't do this bit, because it's apparently - // only used for demons masquerading as gods, so you - // can't ever change their nickname in-game. - set_nickname(&id_hfig->name, nick); - } - else - set_nickname(&identity->name, nick); - } - } - } -} - static command_result RenameSquad(color_ostream &stream, const RenameSquadIn *in) { df::squad *squad = df::squad::find(in->squad_id()); @@ -130,7 +80,7 @@ static command_result RenameSquad(color_ostream &stream, const RenameSquadIn *in return CR_NOT_FOUND; if (in->has_nickname()) - set_nickname(&squad->name, UTF2DF(in->nickname())); + Translation::setNickname(&squad->name, UTF2DF(in->nickname())); if (in->has_alias()) squad->alias = UTF2DF(in->alias()); @@ -144,7 +94,7 @@ static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in) return CR_NOT_FOUND; if (in->has_nickname()) - setUnitNickname(unit, UTF2DF(in->nickname())); + Units::setNickname(unit, UTF2DF(in->nickname())); if (in->has_profession()) unit->custom_profession = UTF2DF(in->profession()); @@ -204,7 +154,7 @@ static command_result rename(color_ostream &out, vector ¶meters) if (!unit) return CR_WRONG_USAGE; - setUnitNickname(unit, parameters[1]); + Units::setNickname(unit, parameters[1]); } else if (cmd == "unit-profession") {