diff --git a/library/Core.cpp b/library/Core.cpp index a752ae628..e6b9c45ff 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -861,13 +861,9 @@ int Core::TileUpdate() // should always be from simulation thread! int Core::Update() { - if(!started) - Init(); if(errorstate) return -1; - color_ostream_proxy out(con); - // Pretend this thread has suspended the core in the usual way { lock_guard lock(d->AccessMutex); @@ -877,6 +873,22 @@ int Core::Update() d->df_suspend_depth = 1000; } + // Initialize the core + bool first_update = false; + + if(!started) + { + first_update = true; + Init(); + if(errorstate) + return -1; + } + + color_ostream_proxy out(con); + + if (first_update) + plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED); + // detect if the game was loaded or unloaded in the meantime void *new_wdata = NULL; void *new_mapdata = NULL; diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 471ffd550..5129ba648 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -745,6 +745,22 @@ static int lua_dfhack_with_suspend(lua_State *L) return lua_gettop(L); } +static int dfhack_open_plugin(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TTABLE); + luaL_checktype(L, 2, LUA_TSTRING); + const char *name = lua_tostring(L, 2); + + PluginManager *pmgr = Core::getInstance().getPluginManager(); + Plugin *plugin = pmgr->getPluginByName(name); + + if (!plugin) + luaL_error(L, "plugin not found: '%s'", name); + + plugin->open_lua(L, 1); + return 0; +} + static const luaL_Reg dfhack_funcs[] = { { "print", lua_dfhack_print }, { "println", lua_dfhack_println }, @@ -757,6 +773,7 @@ static const luaL_Reg dfhack_funcs[] = { { "onerror", dfhack_onerror }, { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, + { "open_plugin", dfhack_open_plugin }, { NULL, NULL } }; diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 40269da80..f5ba565d5 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1030,6 +1030,12 @@ static int meta_global_newindex(lua_State *state) static int meta_call_function(lua_State *state) { auto id = (function_identity_base*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + return method_wrapper_core(state, id); +} + +int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id) +{ if (lua_gettop(state) != id->getNumArgs()) field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke"); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index a7faabeec..4d0c06ddf 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -32,6 +32,8 @@ distribution. #include "DataDefs.h" #include "MiscUtils.h" +#include "LuaWrapper.h" + using namespace DFHack; #include @@ -107,8 +109,8 @@ struct Plugin::RefLock void lock_sub() { mut->lock(); - refcount --; - wakeup->notify_one(); + if (--refcount == 0) + wakeup->notify_one(); mut->unlock(); } void wait() @@ -130,6 +132,13 @@ struct Plugin::RefAutolock ~RefAutolock(){ lock->unlock(); }; }; +struct Plugin::RefAutoinc +{ + RefLock * lock; + RefAutoinc(RefLock * lck):lock(lck){ lock->lock_add(); }; + ~RefAutoinc(){ lock->lock_sub(); }; +}; + Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _filename, PluginManager * pm) { filename = filepath; @@ -210,6 +219,7 @@ bool Plugin::load(color_ostream &con) plugin_shutdown = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_shutdown"); plugin_onstatechange = (command_result (*)(color_ostream &, state_change_event)) LookupPlugin(plug, "plugin_onstatechange"); plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect"); + index_lua(plug); this->name = *plug_name; plugin_lib = plug; commands.clear(); @@ -222,6 +232,7 @@ bool Plugin::load(color_ostream &con) else { con.printerr("Plugin %s has failed to initialize properly.\n", filename.c_str()); + reset_lua(); ClosePlugin(plugin_lib); state = PS_BROKEN; return false; @@ -235,13 +246,22 @@ bool Plugin::unload(color_ostream &con) // if we are actually loaded if(state == PS_LOADED) { + // notify the plugin about an attempt to shutdown + if (plugin_onstatechange && + plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) + { + con.printerr("Plugin %s has refused to be unloaded.\n", name.c_str()); + access->unlock(); + return false; + } + // wait for all calls to finish + access->wait(); // notify plugin about shutdown, if it has a shutdown function command_result cr = CR_OK; if(plugin_shutdown) cr = plugin_shutdown(con); - // wait for all calls to finish - access->wait(); // cleanup... + reset_lua(); parent->unregisterCommands(this); commands.clear(); if(cr == CR_OK) @@ -418,6 +438,90 @@ Plugin::plugin_state Plugin::getState() const return state; } +void Plugin::index_lua(DFLibrary *lib) +{ + if (auto cmdlist = (CommandReg*)LookupPlugin(lib, "plugin_lua_commands")) + { + for (; cmdlist->name; ++cmdlist) + { + auto &cmd = lua_commands[cmdlist->name]; + if (!cmd) cmd = new LuaCommand; + cmd->owner = this; + cmd->name = cmdlist->name; + cmd->command = cmdlist->command; + } + } + if (auto funlist = (FunctionReg*)LookupPlugin(lib, "plugin_lua_functions")) + { + for (; funlist->name; ++funlist) + { + auto &cmd = lua_functions[funlist->name]; + if (!cmd) cmd = new LuaFunction; + cmd->owner = this; + cmd->name = funlist->name; + cmd->identity = funlist->identity; + } + } +} + +void Plugin::reset_lua() +{ + for (auto it = lua_commands.begin(); it != lua_commands.end(); ++it) + it->second->command = NULL; + for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) + it->second->identity = NULL; +} + +int Plugin::lua_cmd_wrapper(lua_State *state) +{ + auto cmd = (LuaCommand*)lua_touserdata(state, lua_upvalueindex(1)); + + RefAutoinc lock(cmd->owner->access); + + if (!cmd->command) + luaL_error(state, "plugin command %s() has been unloaded", + (cmd->owner->name+"."+cmd->name).c_str()); + + return cmd->command(state); +} + +int Plugin::lua_fun_wrapper(lua_State *state) +{ + auto cmd = (LuaFunction*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + RefAutoinc lock(cmd->owner->access); + + if (!cmd->identity) + luaL_error(state, "plugin function %s() has been unloaded", + (cmd->owner->name+"."+cmd->name).c_str()); + + return LuaWrapper::method_wrapper_core(state, cmd->identity); +} + +void Plugin::open_lua(lua_State *state, int table) +{ + table = lua_absindex(state, table); + + RefAutolock lock(access); + + for (auto it = lua_commands.begin(); it != lua_commands.end(); ++it) + { + lua_pushlightuserdata(state, it->second); + lua_pushcclosure(state, lua_cmd_wrapper, 1); + lua_setfield(state, table, it->first.c_str()); + } + + for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) + { + lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN); + lua_pushlightuserdata(state, NULL); + lua_pushfstring(state, "%s.%s()", name.c_str(), it->second->name.c_str()); + lua_pushlightuserdata(state, it->second); + lua_pushcclosure(state, lua_fun_wrapper, 4); + lua_setfield(state, table, it->first.c_str()); + } +} + PluginManager::PluginManager(Core * core) { #ifdef LINUX_BUILD diff --git a/library/include/Core.h b/library/include/Core.h index 290a324b9..8c2a284df 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -139,6 +139,8 @@ namespace DFHack static void print(const char *format, ...); static void printerr(const char *format, ...); + PluginManager *getPluginManager() { return plug_mgr; } + private: DFHack::Console con; diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index d78fe9e27..a949e784b 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -32,10 +32,6 @@ 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; }; @@ -50,7 +46,7 @@ namespace df { template::type,void>::value> struct function_wrapper {}; - class cur_lua_ostream_argument { + class DFHACK_EXPORT cur_lua_ostream_argument { DFHack::color_ostream *out; public: cur_lua_ostream_argument(lua_State *state); diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index bab87e1bb..279400f74 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -160,8 +160,6 @@ 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; @@ -171,7 +169,7 @@ namespace df using DFHack::ptr_container_identity; using DFHack::bit_container_identity; - class number_identity_base : public primitive_identity { + class DFHACK_EXPORT number_identity_base : public primitive_identity { const char *name; public: @@ -197,7 +195,7 @@ namespace df virtual void write(void *ptr, double val) { *(T*)ptr = T(val); } }; - class bool_identity : public primitive_identity { + class DFHACK_EXPORT bool_identity : public primitive_identity { public: bool_identity() : primitive_identity(sizeof(bool)) {}; @@ -207,7 +205,7 @@ namespace df virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); }; - class ptr_string_identity : public primitive_identity { + class DFHACK_EXPORT ptr_string_identity : public primitive_identity { public: ptr_string_identity() : primitive_identity(sizeof(char*)) {}; @@ -217,7 +215,7 @@ namespace df virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); }; - class stl_string_identity : public DFHack::constructed_identity { + class DFHACK_EXPORT stl_string_identity : public DFHack::constructed_identity { public: stl_string_identity() : constructed_identity(sizeof(std::string), &allocator_fn) @@ -233,7 +231,7 @@ namespace df virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index); }; - class stl_ptr_vector_identity : public ptr_container_identity { + class DFHACK_EXPORT stl_ptr_vector_identity : public ptr_container_identity { public: typedef std::vector container; @@ -276,6 +274,8 @@ namespace df } }; +// Due to export issues, this stuff can only work in the main dll +#ifdef BUILD_DFHACK_LIB class buffer_container_identity : public container_identity { int size; @@ -370,8 +370,9 @@ namespace df ((container*)ptr)->set(idx, val); } }; +#endif - class stl_bit_vector_identity : public bit_container_identity { + class DFHACK_EXPORT stl_bit_vector_identity : public bit_container_identity { public: typedef std::vector container; @@ -400,6 +401,7 @@ namespace df } }; +#ifdef BUILD_DFHACK_LIB template class enum_list_attr_identity : public container_identity { public: @@ -421,9 +423,10 @@ namespace df return (void*)&((container*)ptr)->items[idx]; } }; +#endif #define NUMBER_IDENTITY_TRAITS(type) \ - template<> struct identity_traits { \ + template<> struct DFHACK_EXPORT identity_traits { \ static number_identity identity; \ static number_identity_base *get() { return &identity; } \ }; @@ -439,37 +442,37 @@ namespace df NUMBER_IDENTITY_TRAITS(uint64_t); NUMBER_IDENTITY_TRAITS(float); - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static bool_identity identity; static bool_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static stl_string_identity identity; static stl_string_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static ptr_string_identity identity; static ptr_string_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static ptr_string_identity identity; static ptr_string_identity *get() { return &identity; } }; - template<> struct identity_traits { + template<> struct DFHACK_EXPORT identity_traits { static pointer_identity identity; static pointer_identity *get() { return &identity; } }; - template<> struct identity_traits > { + template<> struct DFHACK_EXPORT identity_traits > { static stl_ptr_vector_identity identity; static stl_ptr_vector_identity *get() { return &identity; } }; - template<> struct identity_traits > { + template<> struct DFHACK_EXPORT identity_traits > { static stl_bit_vector_identity identity; static stl_bit_vector_identity *get() { return &identity; } }; @@ -478,14 +481,17 @@ namespace df // Container declarations +#ifdef BUILD_DFHACK_LIB template struct identity_traits > { static primitive_identity *get(); }; +#endif template struct identity_traits { static pointer_identity *get(); }; +#ifdef BUILD_DFHACK_LIB template struct identity_traits { static container_identity *get(); }; @@ -493,11 +499,13 @@ namespace df template struct identity_traits > { static container_identity *get(); }; +#endif template struct identity_traits > { static stl_ptr_vector_identity *get(); }; +#ifdef BUILD_DFHACK_LIB template struct identity_traits > { static container_identity *get(); }; @@ -518,13 +526,16 @@ namespace df template struct identity_traits > { static container_identity *get(); }; +#endif // Container definitions +#ifdef BUILD_DFHACK_LIB template inline primitive_identity *identity_traits >::get() { return identity_traits::get(); } +#endif template inline pointer_identity *identity_traits::get() { @@ -532,6 +543,7 @@ namespace df return &identity; } +#ifdef BUILD_DFHACK_LIB template inline container_identity *identity_traits::get() { static buffer_container_identity identity(sz, identity_traits::get()); @@ -544,6 +556,7 @@ namespace df static stl_container_identity identity("vector", identity_traits::get()); return &identity; } +#endif template inline stl_ptr_vector_identity *identity_traits >::get() { @@ -551,6 +564,7 @@ namespace df return &identity; } +#ifdef BUILD_DFHACK_LIB template inline container_identity *identity_traits >::get() { typedef std::deque container; @@ -576,5 +590,5 @@ namespace df static enum_list_attr_identity identity(identity_traits::get()); return &identity; } -} #endif +} diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 479ca0d67..2304b940d 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -30,6 +30,7 @@ distribution. #include #include "DataDefs.h" +#include "PluginManager.h" #include #include @@ -156,7 +157,7 @@ namespace DFHack { namespace LuaWrapper { * Verify that the object is a DF ref with UPVAL_METATABLE. * If everything ok, extract the address. */ - uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode); + DFHACK_EXPORT uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode); bool is_type_compatible(lua_State *state, type_identity *type1, int meta1, type_identity *type2, int meta2, bool exact_equal); @@ -221,16 +222,14 @@ namespace DFHack { namespace LuaWrapper { */ void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum); - struct FunctionReg { - const char *name; - function_identity_base *identity; - }; - /** * Wrap functions and add them to the table on the top of the stack. */ + using DFHack::FunctionReg; void SetFunctionWrappers(lua_State *state, const FunctionReg *reg); + int method_wrapper_core(lua_State *state, function_identity_base *id); + void IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct); void AttachDFGlobals(lua_State *state); diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 8f02099be..554860dee 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -33,6 +33,8 @@ distribution. #include "RemoteClient.h" +typedef struct lua_State lua_State; + struct DFLibrary; namespace tthread { @@ -49,6 +51,7 @@ namespace DFHack class PluginManager; class virtual_identity; class RPCService; + class function_identity_base; enum state_change_event { @@ -56,7 +59,17 @@ namespace DFHack SC_WORLD_UNLOADED, SC_MAP_LOADED, SC_MAP_UNLOADED, - SC_VIEWSCREEN_CHANGED + SC_VIEWSCREEN_CHANGED, + SC_CORE_INITIALIZED, + SC_BEGIN_UNLOAD + }; + struct DFHACK_EXPORT CommandReg { + const char *name; + int (*command)(lua_State*); + }; + struct DFHACK_EXPORT FunctionReg { + const char *name; + function_identity_base *identity; }; struct DFHACK_EXPORT PluginCommand { @@ -102,6 +115,7 @@ namespace DFHack { struct RefLock; struct RefAutolock; + struct RefAutoinc; enum plugin_state { PS_UNLOADED, @@ -138,6 +152,9 @@ namespace DFHack { return name; } + + void open_lua(lua_State *state, int table); + private: RefLock * access; std::vector commands; @@ -147,6 +164,26 @@ namespace DFHack DFLibrary * plugin_lib; PluginManager * parent; plugin_state state; + + struct LuaCommand { + Plugin *owner; + std::string name; + int (*command)(lua_State *state); + }; + std::map lua_commands; + static int lua_cmd_wrapper(lua_State *state); + + struct LuaFunction { + Plugin *owner; + std::string name; + function_identity_base *identity; + }; + std::map lua_functions; + static int lua_fun_wrapper(lua_State *state); + + void index_lua(DFLibrary *lib); + void reset_lua(); + command_result (*plugin_init)(color_ostream &, std::vector &); command_result (*plugin_status)(color_ostream &, std::string &); command_result (*plugin_shutdown)(color_ostream &); @@ -199,5 +236,15 @@ namespace DFHack }; /// You have to have this in every plugin you write - just once. Ideally on top of the main file. -#define DFHACK_PLUGIN(plugin_name) DFhackDataExport const char * version = DFHACK_VERSION;\ -DFhackDataExport const char * name = plugin_name; +#define DFHACK_PLUGIN(plugin_name) \ + DFhackDataExport const char * version = DFHACK_VERSION;\ + DFhackDataExport const char * name = plugin_name; + +#define DFHACK_PLUGIN_LUA_COMMANDS \ + DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = +#define DFHACK_PLUGIN_LUA_FUNCTIONS \ + DFhackCExport const DFHack::FunctionReg plugin_lua_functions[] = + +#define DFHACK_LUA_COMMAND(name) { #name, name } +#define DFHACK_LUA_FUNCTION(name) { #name, df::wrap_function(name) } +#define DFHACK_LUA_END { NULL, NULL } diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 626f60bec..7230a12aa 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -55,6 +55,10 @@ function mkmodule(module,env) error("Not a table in package.loaded["..module.."]") end end + local plugname = string.match(module,'^plugins%.(%w+)$') + if plugname then + dfhack.open_plugin(pkg,plugname) + end setmetatable(pkg, { __index = (env or _G) }) return pkg end diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index d30d25697..9d236fbe6 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -35,6 +35,10 @@ if (BUILD_DWARFEXPORT) add_subdirectory (dwarfexport) endif() +install(DIRECTORY lua/ + DESTINATION ${DFHACK_LUA_DESTINATION}/plugins + FILES_MATCHING PATTERN "*.lua") + # Protobuf FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index c973405ee..74cb57c45 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -2,6 +2,9 @@ #include "Console.h" #include "Export.h" #include "PluginManager.h" +#include "Error.h" + +#include "DataFuncs.h" #include "modules/Gui.h" #include "modules/Job.h" @@ -420,6 +423,9 @@ static df::burrow *findByName(color_ostream &out, std::string name, bool silent static void copyUnits(df::burrow *target, df::burrow *source, bool enable) { + CHECK_NULL_POINTER(target); + CHECK_NULL_POINTER(source); + if (source == target) { if (!enable) @@ -439,6 +445,9 @@ static void copyUnits(df::burrow *target, df::burrow *source, bool enable) static void copyTiles(df::burrow *target, df::burrow *source, bool enable) { + CHECK_NULL_POINTER(target); + CHECK_NULL_POINTER(source); + if (source == target) { if (!enable) @@ -480,6 +489,8 @@ static void copyTiles(df::burrow *target, df::burrow *source, bool enable) static void setTilesByDesignation(df::burrow *target, df::tile_designation d_mask, df::tile_designation d_value, bool enable) { + CHECK_NULL_POINTER(target); + auto &blocks = world->map.map_blocks; for (size_t i = 0; i < blocks.size(); i++) @@ -512,6 +523,8 @@ static void setTilesByDesignation(df::burrow *target, df::tile_designation d_mas static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable) { + CHECK_NULL_POINTER(target); + df::tile_designation mask(0); df::tile_designation value(0); @@ -538,6 +551,14 @@ static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable) return true; } +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(findByName), + DFHACK_LUA_FUNCTION(copyUnits), + DFHACK_LUA_FUNCTION(copyTiles), + DFHACK_LUA_FUNCTION(setTilesByKeyword), + DFHACK_LUA_END +}; + static command_result burrow(color_ostream &out, vector ¶meters) { CoreSuspender suspend; diff --git a/plugins/lua/burrows.lua b/plugins/lua/burrows.lua new file mode 100644 index 000000000..b1d51f6a6 --- /dev/null +++ b/plugins/lua/burrows.lua @@ -0,0 +1,33 @@ +local _ENV = mkmodule('plugins.burrows') + +--[[ + + Native functions: + + * findByName(name) -> burrow + * copyUnits(dest,src,enable) + * copyTiles(dest,src,enable) + * setTilesByKeyword(dest,kwd,enable) -> success + + 'enable' selects between add and remove modes + +--]] + +clearUnits = dfhack.units.clearBurrowMembers + +function isBurrowUnit(burrow,unit) + return dfhack.units.isInBurrow(unit,burrow) +end +function setBurrowUnit(burrow,unit,enable) + return dfhack.units.setInBurrow(unit,burrow,enable) +end + +clearTiles = dfhack.maps.clearBurrowTiles +listBlocks = dfhack.maps.listBurrowBlocks + +isBurrowTile = dfhack.maps.isBurrowTile +setBurrowTile = dfhack.maps.setBurrowTile +isBlockBurrowTile = dfhack.maps.isBlockBurrowTile +setBlockBurrowTile = dfhack.maps.setBlockBurrowTile + +return _ENV \ No newline at end of file