Allow plugins to export functions to lua with safe reload support.

- To ensure reload safety functions have to be wrapped. Every call
  checks the loaded state and locks a mutex in Plugin. If the plugin
  is unloaded, calling its functions throws a lua error. Therefore,
  plugins may not create closures or export yieldable functions.

- The set of function argument and return types supported by
  LuaWrapper is severely limited when compared to being compiled
  inside the main library.
  Currently supported types: numbers, bool, std::string, df::foo,
  df::foo*, std::vector<bool>, std::vector<df::foo*>.

- To facilitate postponing initialization until after all plugins
  have been loaded, the core sends a SC_CORE_INITIALIZED event.

- As an example, the burrows plugin now exports its functions.
develop
Alexander Gavrilov 2012-04-14 19:44:07 +04:00
parent 7a34a89f53
commit cb49c92b99
13 changed files with 298 additions and 39 deletions

@ -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<mutex> 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;

@ -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 }
};

@ -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");

@ -32,6 +32,8 @@ distribution.
#include "DataDefs.h"
#include "MiscUtils.h"
#include "LuaWrapper.h"
using namespace DFHack;
#include <string>
@ -107,7 +109,7 @@ struct Plugin::RefLock
void lock_sub()
{
mut->lock();
refcount --;
if (--refcount == 0)
wakeup->notify_one();
mut->unlock();
}
@ -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

@ -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;

@ -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<class U, class V> struct is_same_type { static const bool value = false; };
@ -50,7 +46,7 @@ namespace df {
template<class T, bool isvoid = is_same_type<typename return_type<T>::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);

@ -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<std::string>)
@ -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<void*> 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<bool> container;
@ -400,6 +401,7 @@ namespace df
}
};
#ifdef BUILD_DFHACK_LIB
template<class T>
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<type> { \
template<> struct DFHACK_EXPORT identity_traits<type> { \
static number_identity<type> 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<bool> {
template<> struct DFHACK_EXPORT identity_traits<bool> {
static bool_identity identity;
static bool_identity *get() { return &identity; }
};
template<> struct identity_traits<std::string> {
template<> struct DFHACK_EXPORT identity_traits<std::string> {
static stl_string_identity identity;
static stl_string_identity *get() { return &identity; }
};
template<> struct identity_traits<char*> {
template<> struct DFHACK_EXPORT identity_traits<char*> {
static ptr_string_identity identity;
static ptr_string_identity *get() { return &identity; }
};
template<> struct identity_traits<const char*> {
template<> struct DFHACK_EXPORT identity_traits<const char*> {
static ptr_string_identity identity;
static ptr_string_identity *get() { return &identity; }
};
template<> struct identity_traits<void*> {
template<> struct DFHACK_EXPORT identity_traits<void*> {
static pointer_identity identity;
static pointer_identity *get() { return &identity; }
};
template<> struct identity_traits<std::vector<void*> > {
template<> struct DFHACK_EXPORT identity_traits<std::vector<void*> > {
static stl_ptr_vector_identity identity;
static stl_ptr_vector_identity *get() { return &identity; }
};
template<> struct identity_traits<std::vector<bool> > {
template<> struct DFHACK_EXPORT identity_traits<std::vector<bool> > {
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<class Enum, class FT> struct identity_traits<enum_field<Enum,FT> > {
static primitive_identity *get();
};
#endif
template<class T> struct identity_traits<T *> {
static pointer_identity *get();
};
#ifdef BUILD_DFHACK_LIB
template<class T, int sz> struct identity_traits<T [sz]> {
static container_identity *get();
};
@ -493,11 +499,13 @@ namespace df
template<class T> struct identity_traits<std::vector<T> > {
static container_identity *get();
};
#endif
template<class T> struct identity_traits<std::vector<T*> > {
static stl_ptr_vector_identity *get();
};
#ifdef BUILD_DFHACK_LIB
template<class T> struct identity_traits<std::deque<T> > {
static container_identity *get();
};
@ -518,13 +526,16 @@ namespace df
template<class T> struct identity_traits<enum_list_attr<T> > {
static container_identity *get();
};
#endif
// Container definitions
#ifdef BUILD_DFHACK_LIB
template<class Enum, class FT>
inline primitive_identity *identity_traits<enum_field<Enum,FT> >::get() {
return identity_traits<FT>::get();
}
#endif
template<class T>
inline pointer_identity *identity_traits<T *>::get() {
@ -532,6 +543,7 @@ namespace df
return &identity;
}
#ifdef BUILD_DFHACK_LIB
template<class T, int sz>
inline container_identity *identity_traits<T [sz]>::get() {
static buffer_container_identity identity(sz, identity_traits<T>::get());
@ -544,6 +556,7 @@ namespace df
static stl_container_identity<container> identity("vector", identity_traits<T>::get());
return &identity;
}
#endif
template<class T>
inline stl_ptr_vector_identity *identity_traits<std::vector<T*> >::get() {
@ -551,6 +564,7 @@ namespace df
return &identity;
}
#ifdef BUILD_DFHACK_LIB
template<class T>
inline container_identity *identity_traits<std::deque<T> >::get() {
typedef std::deque<T> container;
@ -576,5 +590,5 @@ namespace df
static enum_list_attr_identity<T> identity(identity_traits<T>::get());
return &identity;
}
}
#endif
}

@ -30,6 +30,7 @@ distribution.
#include <map>
#include "DataDefs.h"
#include "PluginManager.h"
#include <lua.h>
#include <lauxlib.h>
@ -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);

@ -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 <PluginCommand> 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<std::string, LuaCommand*> lua_commands;
static int lua_cmd_wrapper(lua_State *state);
struct LuaFunction {
Plugin *owner;
std::string name;
function_identity_base *identity;
};
std::map<std::string, LuaFunction*> 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 <PluginCommand> &);
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;\
#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 }

@ -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

@ -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)

@ -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 <string> &parameters)
{
CoreSuspender suspend;

@ -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