Added plugin loading/unloading/reloading. Many locks. Too many damn locks.

develop
Petr Mrázek 2011-07-18 16:22:49 +02:00
parent fdb5397a1d
commit 84f74bc091
5 changed files with 426 additions and 71 deletions

@ -209,6 +209,7 @@ namespace DFHack
else else
{ {
print("\033c\033[3J\033[H"); print("\033c\033[3J\033[H");
fflush(dfout_C);
} }
} }
/// Position cursor at x,y. 1,1 = top left corner /// Position cursor at x,y. 1,1 = top left corner

@ -43,6 +43,7 @@ using namespace std;
#include "dfhack/PluginManager.h" #include "dfhack/PluginManager.h"
#include "ModuleFactory.h" #include "ModuleFactory.h"
#include "dfhack/modules/Gui.h" #include "dfhack/modules/Gui.h"
#include "dfhack/modules/World.h"
#include "dfhack/SDL_fakes/events.h" #include "dfhack/SDL_fakes/events.h"
@ -130,46 +131,189 @@ int fIOthread(void * iodata)
return 0; return 0;
} }
con.print("DFHack is ready. Have a nice day! Type in '?' or 'help' for help.\n"); con.print("DFHack is ready. Have a nice day! Type in '?' or 'help' for help.\n");
//dfterm << << endl;
int clueless_counter = 0; int clueless_counter = 0;
while (true) while (true)
{ {
string command = ""; string command = "";
con.lineedit("[DFHack]# ",command); int ret = con.lineedit("[DFHack]# ",command);
if(ret == -2)
{
cerr << "Console is shutting down properly." << endl;
return 0;
}
else if(ret == -1)
{
cerr << "Console caught an unspecified error." << endl;
continue;
}
else if(ret)
{
// a proper, non-empty command was entered
con.history_add(command); con.history_add(command);
//con <<"[DFHack]# "; }
//char * line = linenoise("[DFHack]# ", core->con.dfout_C); // cut the input into parts
// dfout <<"[DFHack]# "; vector <string> parts;
/* cheap_tokenise(command,parts);
if (line) if(parts.size() == 0)
{
clueless_counter ++;
continue;
}
string first = parts[0];
parts.erase(parts.begin());
// let's see what we actually got
if(first=="help" || first == "?")
{
if(!parts.size())
{
con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n"
"Some basic editing capabilities are included (single-line text editing).\n"
"The console also has a command history - you can navigate it with Up and Down keys.\n"
"On Windows, you may have to resize your console window. The appropriate menu is accessible\n"
"by clicking on the program icon in the top bar of the window.\n\n"
"Available basic commands:\n"
" help|? [plugin] - This text or help specific to a plugin.\n"
" load plugin|all - Load a plugin by name or load all possible plugins.\n"
" unload plugin|all - Unload a plugin or all loaded plugins.\n"
" reload plugin|all - Reload a plugin or all loaded plugins.\n"
" listp - List plugins with their status.\n"
" listc [plugin] - List commands. Optionally of a single plugin.\n"
" lista - List all commands, sorted by plugin (verbose).\n"
" fpause - Force DF to pause without syncing.\n"
" die - Force DF to close immediately\n"
" cls - Clear the console scrollback.\n"
);
}
else
{
con.printerr("not implemented yet\n");
}
}
else if( first == "load" )
{ {
command=line; if(parts.size())
linenoiseHistoryAdd(line); {
free(line); string & plugname = parts[0];
}*/ if(plugname == "all")
//getline(cin, command); {
if(command=="help" || command == "?") for(int i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
plug->load();
}
}
else
{
Plugin * plug = plug_mgr->getPluginByName(plugname);
if(!plug) con.printerr("No such plugin\n");
plug->load();
}
}
}
else if( first == "reload" )
{
if(parts.size())
{
string & plugname = parts[0];
if(plugname == "all")
{
for(int i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
plug->reload();
}
}
else
{
Plugin * plug = plug_mgr->getPluginByName(plugname);
if(!plug) con.printerr("No such plugin\n");
plug->reload();
}
}
}
else if( first == "unload" )
{
if(parts.size())
{
string & plugname = parts[0];
if(plugname == "all")
{
for(int i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
plug->unload();
}
}
else
{
Plugin * plug = plug_mgr->getPluginByName(plugname);
if(!plug) con.printerr("No such plugin\n");
plug->unload();
}
}
}
else if(first == "listp")
{
for(int i = 0; i < plug_mgr->size();i++)
{
const Plugin * plug = (plug_mgr->operator[](i));
con.print("%s\n", plug->getName().c_str());
}
}
else if(first == "listc")
{
if(parts.size())
{
string & plugname = parts[0];
const Plugin * plug = plug_mgr->getPluginByName(plugname);
if(!plug)
{
con.printerr("There's no plugin called %s!\n",plugname.c_str());
}
else for (int j = 0; j < plug->size();j++)
{
const PluginCommand & pcmd = (plug->operator[](j));
con.print("%12s| %s\n",pcmd.name.c_str(), pcmd.description.c_str());
}
}
else for(int i = 0; i < plug_mgr->size();i++)
{
const Plugin * plug = (plug_mgr->operator[](i));
if(!plug->size())
continue;
for (int j = 0; j < plug->size();j++)
{
const PluginCommand & pcmd = (plug->operator[](j));
con.print("%12s| %s\n",pcmd.name.c_str(), pcmd.description.c_str());
}
}
}
else if(first == "lista")
{ {
con.print("Available commands\n");
con.print("------------------\n");
for(int i = 0; i < plug_mgr->size();i++) for(int i = 0; i < plug_mgr->size();i++)
{ {
const Plugin * plug = (plug_mgr->operator[](i)); const Plugin * plug = (plug_mgr->operator[](i));
if(!plug->size()) if(!plug->size())
continue; continue;
con.print("Plugin %s :\n", plug->getName().c_str()); con.print("%s :\n", plug->getName().c_str());
for (int j = 0; j < plug->size();j++) for (int j = 0; j < plug->size();j++)
{ {
const PluginCommand & pcmd = (plug->operator[](j)); const PluginCommand & pcmd = (plug->operator[](j));
con.print("%12s| %s\n",pcmd.name.c_str(), pcmd.description.c_str()); con.print("%12s| %s\n",pcmd.name.c_str(), pcmd.description.c_str());
//con << setw(12) << pcmd.name << "| " << pcmd.description << endl; //con << setw(12) << pcmd.name << "| " << pcmd.description << endl;
} }
con.print("\n"); //con.print("\n");
} }
} }
else if( command == "" ) else if(first == "fpause")
{ {
clueless_counter++; World * w = core->getWorld();
w->SetPauseState(true);
con.print("The game was forced to pause!");
}
else if(first == "cls")
{
con.clear();
} }
else else
{ {
@ -189,10 +333,12 @@ int fIOthread(void * iodata)
con.printerr("Invalid command.\n"); con.printerr("Invalid command.\n");
clueless_counter ++; clueless_counter ++;
} }
/*
else if(res == CR_FAILURE) else if(res == CR_FAILURE)
{ {
con.printerr("ERROR!\n"); con.printerr("ERROR!\n");
} }
*/
} }
} }
if(clueless_counter == 3) if(clueless_counter == 3)
@ -393,6 +539,7 @@ int Core::SDL_Event(SDL::Event* ev, int orig_return)
if(g->hotkeys && g->df_interface && g->df_menu_state) if(g->hotkeys && g->df_interface && g->df_menu_state)
{ {
t_viewscreen * ws = g->GetCurrentScreen(); t_viewscreen * ws = g->GetCurrentScreen();
// FIXME: put hardcoded values into memory.xml
if(ws->getClassName() == "viewscreen_dwarfmodest" && *g->df_menu_state == 0x23) if(ws->getClassName() == "viewscreen_dwarfmodest" && *g->df_menu_state == 0x23)
return orig_return; return orig_return;
else else

@ -67,71 +67,241 @@ bool hasEnding (std::string const &fullString, std::string const &ending)
return false; return false;
} }
} }
struct Plugin::RefLock
Plugin::Plugin(Core * core, const std::string & file) {
RefLock()
{
refcount = 0;
wakeup = SDL_CreateCond();
mut = SDL_CreateMutex();
}
~RefLock()
{
SDL_DestroyCond(wakeup);
SDL_DestroyMutex(mut);
}
void lock()
{
SDL_mutexP(mut);
}
void unlock()
{
SDL_mutexV(mut);
}
void lock_add()
{
SDL_mutexP(mut);
refcount ++;
SDL_mutexV(mut);
}
void lock_sub()
{
SDL_mutexP(mut);
refcount --;
SDL_CondSignal(wakeup);
SDL_mutexV(mut);
}
void operator++()
{
refcount ++;
}
void operator--()
{
refcount --;
SDL_CondSignal(wakeup);
}
void wait()
{
while(refcount)
{
SDL_CondWait(wakeup, mut);
}
}
SDL::Cond * wakeup;
SDL::Mutex * mut;
int refcount;
};
Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _filename, PluginManager * pm)
{ {
filename = file; filename = filepath;
parent = pm;
name.reserve(_filename.size());
for(int i = 0; i < _filename.size();i++)
{
char ch = _filename[i];
if(ch == '.')
break;
name.append(1,ch);
}
Console & con = core->con; Console & con = core->con;
plugin_lib = 0; plugin_lib = 0;
plugin_init = 0; plugin_init = 0;
plugin_shutdown = 0; plugin_shutdown = 0;
plugin_status = 0; plugin_status = 0;
plugin_onupdate = 0; plugin_onupdate = 0;
loaded = false; state = PS_UNLOADED;
DFLibrary * plug = OpenPlugin(file.c_str()); access = new RefLock();
}
Plugin::~Plugin()
{
if(state == PS_LOADED)
{
unload();
}
delete access;
}
bool Plugin::load()
{
access->lock();
if(state == PS_BROKEN)
{
access->unlock();
return false;
}
else if(state == PS_LOADED)
{
access->unlock();
return true;
}
Core & c = Core::getInstance();
Console & con = c.con;
DFLibrary * plug = OpenPlugin(filename.c_str());
if(!plug) if(!plug)
{ {
con.print("Can't load plugin %s\n", filename.c_str()); con.printerr("Can't load plugin %s\n", filename.c_str());
return; state = PS_BROKEN;
access->unlock();
return false;
} }
const char * (*_PlugName)() =(const char * (*)()) LookupPlugin(plug, "plugin_name"); const char * (*_PlugName)() =(const char * (*)()) LookupPlugin(plug, "plugin_name");
if(!_PlugName) if(!_PlugName)
{ {
con.print("Plugin %s has no name.\n", filename.c_str()); con.printerr("Plugin %s has no name.\n", filename.c_str());
ClosePlugin(plug); ClosePlugin(plug);
return; state = PS_BROKEN;
access->unlock();
return false;
} }
plugin_init = (command_result (*)(Core *, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init"); plugin_init = (command_result (*)(Core *, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init");
if(!plugin_init) if(!plugin_init)
{ {
con.print("Plugin %s has no init function.\n", filename.c_str()); con.printerr("Plugin %s has no init function.\n", filename.c_str());
ClosePlugin(plug); ClosePlugin(plug);
return; state = PS_BROKEN;
access->unlock();
return false;
} }
plugin_status = (command_result (*)(Core *, std::string &)) LookupPlugin(plug, "plugin_status"); plugin_status = (command_result (*)(Core *, std::string &)) LookupPlugin(plug, "plugin_status");
plugin_onupdate = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_onupdate"); plugin_onupdate = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_onupdate");
plugin_shutdown = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_shutdown"); plugin_shutdown = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_shutdown");
name = _PlugName(); name = _PlugName();
plugin_lib = plug; plugin_lib = plug;
loaded = true; if(plugin_init(&c,commands) == CR_OK)
//dfout << "Found plugin " << name << endl;
if(plugin_init(core,commands) == CR_OK)
{ {
/* state = PS_LOADED;
for(int i = 0; i < commands.size();i++) parent->registerCommands(this);
access->unlock();
return true;
}
else
{ {
dfout << commands[i].name << " : " << commands[i].description << std::endl; con.printerr("Plugin %s has failed to initialize properly.\n", filename.c_str());
ClosePlugin(plugin_lib);
state = PS_BROKEN;
access->unlock();
return false;
} }
*/ // not reachable
}
bool Plugin::unload()
{
Core & c = Core::getInstance();
Console & con = c.con;
// get the mutex
access->lock();
// if we are actually loaded
if(state == PS_LOADED)
{
// notify plugin about shutdown
command_result cr = plugin_shutdown(&Core::getInstance());
// wait for all calls to finish
access->wait();
// cleanup...
parent->unregisterCommands(this);
if(cr == CR_OK)
{
ClosePlugin(plugin_lib);
state = PS_UNLOADED;
access->unlock();
return false;
} }
else else
{ {
// horrible! con.printerr("Plugin %s has failed to shutdown!\n",name.c_str());
state = PS_BROKEN;
access->unlock();
return false;
}
}
else if(state == PS_UNLOADED)
{
access->unlock();
return true;
} }
access->unlock();
return false;
} }
Plugin::~Plugin() bool Plugin::reload()
{
if(state != PS_LOADED)
return false;
if(!unload())
return false;
if(!load())
return false;
return true;
}
command_result Plugin::invoke( std::string & command, std::vector <std::string> & parameters)
{ {
if(loaded) Core & c = Core::getInstance();
command_result cr = CR_NOT_IMPLEMENTED;
access->lock_add();
if(state == PS_LOADED)
{ {
plugin_shutdown(&Core::getInstance()); for (int i = 0; i < commands.size();i++)
ClosePlugin(plugin_lib); {
if(commands[i].name == command)
{
cr = commands[i].function(&c, parameters);
break;
}
}
}
access->lock_sub();
return cr;
}
command_result Plugin::on_update()
{
Core & c = Core::getInstance();
command_result cr = CR_NOT_IMPLEMENTED;
access->lock_add();
if(state == PS_LOADED && plugin_onupdate)
{
cr = plugin_onupdate(&c);
} }
access->lock_sub();
return cr;
} }
bool Plugin::isLoaded() const Plugin::plugin_state Plugin::getState() const
{ {
return loaded; return state;
} }
PluginManager::PluginManager(Core * core) PluginManager::PluginManager(Core * core)
@ -143,18 +313,14 @@ PluginManager::PluginManager(Core * core)
string path = core->p->getPath() + "\\plugins\\"; string path = core->p->getPath() + "\\plugins\\";
const string searchstr = ".plug.dll"; const string searchstr = ".plug.dll";
#endif #endif
cmdlist_mutex = SDL_CreateMutex();
vector <string> filez; vector <string> filez;
getdir(path, filez); getdir(path, filez);
for(int i = 0; i < filez.size();i++) for(int i = 0; i < filez.size();i++)
{ {
if(hasEnding(filez[i],searchstr)) if(hasEnding(filez[i],searchstr))
{ {
Plugin * p = new Plugin(core, path + filez[i]); Plugin * p = new Plugin(core, path + filez[i], filez[i], this);
Plugin & pr = *p;
for(int j = 0; j < pr.size();j++)
{
commands[p->commands[j].name] = &pr[j];
}
all_plugins.push_back(p); all_plugins.push_back(p);
} }
} }
@ -162,15 +328,15 @@ PluginManager::PluginManager(Core * core)
PluginManager::~PluginManager() PluginManager::~PluginManager()
{ {
commands.clear();
for(int i = 0; i < all_plugins.size();i++) for(int i = 0; i < all_plugins.size();i++)
{ {
delete all_plugins[i]; delete all_plugins[i];
} }
all_plugins.clear(); all_plugins.clear();
SDL_DestroyMutex(cmdlist_mutex);
} }
const Plugin *PluginManager::getPluginByName (const std::string & name) Plugin *PluginManager::getPluginByName (const std::string & name)
{ {
for(int i = 0; i < all_plugins.size(); i++) for(int i = 0; i < all_plugins.size(); i++)
{ {
@ -183,23 +349,45 @@ const Plugin *PluginManager::getPluginByName (const std::string & name)
// FIXME: handle name collisions... // FIXME: handle name collisions...
command_result PluginManager::InvokeCommand( std::string & command, std::vector <std::string> & parameters) command_result PluginManager::InvokeCommand( std::string & command, std::vector <std::string> & parameters)
{ {
command_result cr = CR_NOT_IMPLEMENTED;
Core * c = &Core::getInstance(); Core * c = &Core::getInstance();
map <string, const PluginCommand *>::iterator iter = commands.find(command); SDL_mutexP(cmdlist_mutex);
if(iter != commands.end()) map <string, Plugin *>::iterator iter = belongs.find(command);
if(iter != belongs.end())
{ {
return iter->second->function(c,parameters); cr = iter->second->invoke(command, parameters);
} }
return CR_NOT_IMPLEMENTED; SDL_mutexV(cmdlist_mutex);
return cr;
} }
void PluginManager::OnUpdate( void ) void PluginManager::OnUpdate( void )
{ {
Core * c = &Core::getInstance();
for(int i = 0; i < all_plugins.size(); i++) for(int i = 0; i < all_plugins.size(); i++)
{ {
if(all_plugins[i]->plugin_onupdate) all_plugins[i]->on_update();
}
}
// FIXME: doesn't check name collisions!
void PluginManager::registerCommands( Plugin * p )
{ {
all_plugins[i]->plugin_onupdate(c); SDL_mutexP(cmdlist_mutex);
vector <PluginCommand> & cmds = p->commands;
for(int i = 0; i < cmds.size();i++)
{
belongs[cmds[i].name] = p;
}
SDL_mutexV(cmdlist_mutex);
} }
// FIXME: doesn't check name collisions!
void PluginManager::unregisterCommands( Plugin * p )
{
SDL_mutexP(cmdlist_mutex);
vector <PluginCommand> & cmds = p->commands;
for(int i = 0; i < cmds.size();i++)
{
belongs.erase(cmds[i].name);
} }
SDL_mutexV(cmdlist_mutex);
} }

@ -28,6 +28,7 @@ distribution.
#include <map> #include <map>
#include <string> #include <string>
#include <vector> #include <vector>
#include "FakeSDL.h"
struct DFLibrary; struct DFLibrary;
namespace DFHack namespace DFHack
{ {
@ -62,11 +63,23 @@ namespace DFHack
}; };
class Plugin class Plugin
{ {
struct RefLock;
enum plugin_state
{
PS_UNLOADED,
PS_LOADED,
PS_BROKEN
};
friend class PluginManager; friend class PluginManager;
public: Plugin(DFHack::Core* core, const std::string& filepath, const std::string& filename, PluginManager * pm);
Plugin(DFHack::Core* core, const std::string& file);
~Plugin(); ~Plugin();
bool isLoaded () const; command_result on_update();
public:
bool load();
bool unload();
bool reload();
command_result invoke( std::string & command, std::vector <std::string> & parameters );
plugin_state getState () const;
const PluginCommand& operator[] (std::size_t index) const const PluginCommand& operator[] (std::size_t index) const
{ {
return commands[index]; return commands[index];
@ -80,11 +93,13 @@ namespace DFHack
return name; return name;
} }
private: private:
RefLock * access;
std::vector <PluginCommand> commands; std::vector <PluginCommand> commands;
std::string filename; std::string filename;
std::string name; std::string name;
DFLibrary * plugin_lib; DFLibrary * plugin_lib;
bool loaded; PluginManager * parent;
plugin_state state;
command_result (*plugin_init)(Core *, std::vector <PluginCommand> &); command_result (*plugin_init)(Core *, std::vector <PluginCommand> &);
command_result (*plugin_status)(Core *, std::string &); command_result (*plugin_status)(Core *, std::string &);
command_result (*plugin_shutdown)(Core *); command_result (*plugin_shutdown)(Core *);
@ -94,14 +109,17 @@ namespace DFHack
{ {
// PRIVATE METHODS // PRIVATE METHODS
friend class Core; friend class Core;
friend class Plugin;
PluginManager(Core * core); PluginManager(Core * core);
~PluginManager(); ~PluginManager();
void OnUpdate( void ); void OnUpdate( void );
void registerCommands( Plugin * p );
void unregisterCommands( Plugin * p );
// PUBLIC METHODS // PUBLIC METHODS
public: public:
const Plugin *getPluginByName (const std::string & name); Plugin *getPluginByName (const std::string & name);
command_result InvokeCommand( std::string & command, std::vector <std::string> & parameters ); command_result InvokeCommand( std::string & command, std::vector <std::string> & parameters );
const Plugin* operator[] (std::size_t index) Plugin* operator[] (std::size_t index)
{ {
if(index >= all_plugins.size()) if(index >= all_plugins.size())
return 0; return 0;
@ -113,7 +131,8 @@ namespace DFHack
} }
// DATA // DATA
private: private:
std::map <std::string, const PluginCommand *> commands; SDL::Mutex * cmdlist_mutex;
std::map <std::string, Plugin *> belongs;
std::vector <Plugin *> all_plugins; std::vector <Plugin *> all_plugins;
std::string plugin_path; std::string plugin_path;
}; };

@ -39,7 +39,7 @@ DFhackCExport command_result plugin_init ( Core * c, std::vector <PluginCommand>
DFhackCExport command_result plugin_shutdown ( Core * c ) DFhackCExport command_result plugin_shutdown ( Core * c )
{ {
return CR_OK; return CR_FAILURE;
} }
DFhackCExport command_result runqt (Core * c, vector <string> & parameters) DFhackCExport command_result runqt (Core * c, vector <string> & parameters)