From 84f74bc091a9e1fd9b9046ff4bde986ead4f49b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Mon, 18 Jul 2011 16:22:49 +0200 Subject: [PATCH] Added plugin loading/unloading/reloading. Many locks. Too many damn locks. --- library/Console-linux.cpp | 1 + library/Core.cpp | 187 +++++++++++++++-- library/PluginManager.cpp | 274 +++++++++++++++++++++---- library/include/dfhack/PluginManager.h | 33 ++- plugins/qtplug/qtplug.cpp | 2 +- 5 files changed, 426 insertions(+), 71 deletions(-) diff --git a/library/Console-linux.cpp b/library/Console-linux.cpp index ae0167a07..f96972a90 100644 --- a/library/Console-linux.cpp +++ b/library/Console-linux.cpp @@ -209,6 +209,7 @@ namespace DFHack else { print("\033c\033[3J\033[H"); + fflush(dfout_C); } } /// Position cursor at x,y. 1,1 = top left corner diff --git a/library/Core.cpp b/library/Core.cpp index ad7e4986b..ff63a98ea 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -43,6 +43,7 @@ using namespace std; #include "dfhack/PluginManager.h" #include "ModuleFactory.h" #include "dfhack/modules/Gui.h" +#include "dfhack/modules/World.h" #include "dfhack/SDL_fakes/events.h" @@ -130,46 +131,189 @@ int fIOthread(void * iodata) return 0; } con.print("DFHack is ready. Have a nice day! Type in '?' or 'help' for help.\n"); - //dfterm << << endl; int clueless_counter = 0; while (true) { string command = ""; - con.lineedit("[DFHack]# ",command); - con.history_add(command); - //con <<"[DFHack]# "; - //char * line = linenoise("[DFHack]# ", core->con.dfout_C); - // dfout <<"[DFHack]# "; - /* - if (line) + int ret = con.lineedit("[DFHack]# ",command); + if(ret == -2) { - command=line; - linenoiseHistoryAdd(line); - free(line); - }*/ - //getline(cin, command); - if(command=="help" || command == "?") + 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); + } + // cut the input into parts + vector parts; + cheap_tokenise(command,parts); + 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" ) + { + 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->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++) { const Plugin * plug = (plug_mgr->operator[](i)); if(!plug->size()) 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++) { const PluginCommand & pcmd = (plug->operator[](j)); con.print("%12s| %s\n",pcmd.name.c_str(), pcmd.description.c_str()); //con << setw(12) << pcmd.name << "| " << pcmd.description << endl; } - con.print("\n"); + //con.print("\n"); } } - else if( command == "" ) + else if(first == "fpause") + { + World * w = core->getWorld(); + w->SetPauseState(true); + con.print("The game was forced to pause!"); + } + else if(first == "cls") { - clueless_counter++; + con.clear(); } else { @@ -189,10 +333,12 @@ int fIOthread(void * iodata) con.printerr("Invalid command.\n"); clueless_counter ++; } + /* else if(res == CR_FAILURE) { con.printerr("ERROR!\n"); } + */ } } 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) { t_viewscreen * ws = g->GetCurrentScreen(); + // FIXME: put hardcoded values into memory.xml if(ws->getClassName() == "viewscreen_dwarfmodest" && *g->df_menu_state == 0x23) return orig_return; else diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 1d34d8add..4caff02ea 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -67,71 +67,241 @@ bool hasEnding (std::string const &fullString, std::string const &ending) return false; } } - -Plugin::Plugin(Core * core, const std::string & file) +struct Plugin::RefLock +{ + 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; plugin_lib = 0; plugin_init = 0; plugin_shutdown = 0; plugin_status = 0; plugin_onupdate = 0; - loaded = false; - DFLibrary * plug = OpenPlugin(file.c_str()); + state = PS_UNLOADED; + 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) { - con.print("Can't load plugin %s\n", filename.c_str()); - return; + con.printerr("Can't load plugin %s\n", filename.c_str()); + state = PS_BROKEN; + access->unlock(); + return false; } const char * (*_PlugName)() =(const char * (*)()) LookupPlugin(plug, "plugin_name"); 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); - return; + state = PS_BROKEN; + access->unlock(); + return false; } plugin_init = (command_result (*)(Core *, std::vector &)) LookupPlugin(plug, "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); - return; + state = PS_BROKEN; + access->unlock(); + return false; } plugin_status = (command_result (*)(Core *, std::string &)) LookupPlugin(plug, "plugin_status"); plugin_onupdate = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_onupdate"); plugin_shutdown = (command_result (*)(Core *)) LookupPlugin(plug, "plugin_shutdown"); name = _PlugName(); plugin_lib = plug; - loaded = true; - //dfout << "Found plugin " << name << endl; - if(plugin_init(core,commands) == CR_OK) + if(plugin_init(&c,commands) == CR_OK) + { + state = PS_LOADED; + parent->registerCommands(this); + access->unlock(); + return true; + } + else { - /* - for(int i = 0; i < commands.size();i++) + 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 { - dfout << commands[i].name << " : " << commands[i].description << std::endl; + con.printerr("Plugin %s has failed to shutdown!\n",name.c_str()); + state = PS_BROKEN; + access->unlock(); + return false; } - */ } - else + else if(state == PS_UNLOADED) { - // horrible! + 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 & parameters) { - if(loaded) + Core & c = Core::getInstance(); + command_result cr = CR_NOT_IMPLEMENTED; + access->lock_add(); + if(state == PS_LOADED) { - plugin_shutdown(&Core::getInstance()); - ClosePlugin(plugin_lib); + for (int i = 0; i < commands.size();i++) + { + if(commands[i].name == command) + { + cr = commands[i].function(&c, parameters); + break; + } + } } + access->lock_sub(); + return cr; } -bool Plugin::isLoaded() const +command_result Plugin::on_update() { - return loaded; + 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; +} + +Plugin::plugin_state Plugin::getState() const +{ + return state; } PluginManager::PluginManager(Core * core) @@ -143,18 +313,14 @@ PluginManager::PluginManager(Core * core) string path = core->p->getPath() + "\\plugins\\"; const string searchstr = ".plug.dll"; #endif + cmdlist_mutex = SDL_CreateMutex(); vector filez; getdir(path, filez); for(int i = 0; i < filez.size();i++) { if(hasEnding(filez[i],searchstr)) { - Plugin * p = new Plugin(core, path + filez[i]); - Plugin & pr = *p; - for(int j = 0; j < pr.size();j++) - { - commands[p->commands[j].name] = &pr[j]; - } + Plugin * p = new Plugin(core, path + filez[i], filez[i], this); all_plugins.push_back(p); } } @@ -162,15 +328,15 @@ PluginManager::PluginManager(Core * core) PluginManager::~PluginManager() { - commands.clear(); for(int i = 0; i < all_plugins.size();i++) { delete all_plugins[i]; } 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++) { @@ -183,23 +349,45 @@ const Plugin *PluginManager::getPluginByName (const std::string & name) // FIXME: handle name collisions... command_result PluginManager::InvokeCommand( std::string & command, std::vector & parameters) { + command_result cr = CR_NOT_IMPLEMENTED; Core * c = &Core::getInstance(); - map ::iterator iter = commands.find(command); - if(iter != commands.end()) + SDL_mutexP(cmdlist_mutex); + map ::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 ) { - Core * c = &Core::getInstance(); for(int i = 0; i < all_plugins.size(); i++) { - if(all_plugins[i]->plugin_onupdate) - { - all_plugins[i]->plugin_onupdate(c); - } + all_plugins[i]->on_update(); + } +} +// FIXME: doesn't check name collisions! +void PluginManager::registerCommands( Plugin * p ) +{ + SDL_mutexP(cmdlist_mutex); + vector & 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 & cmds = p->commands; + for(int i = 0; i < cmds.size();i++) + { + belongs.erase(cmds[i].name); + } + SDL_mutexV(cmdlist_mutex); +} \ No newline at end of file diff --git a/library/include/dfhack/PluginManager.h b/library/include/dfhack/PluginManager.h index 13ce3484a..b176f4108 100644 --- a/library/include/dfhack/PluginManager.h +++ b/library/include/dfhack/PluginManager.h @@ -28,6 +28,7 @@ distribution. #include #include #include +#include "FakeSDL.h" struct DFLibrary; namespace DFHack { @@ -62,11 +63,23 @@ namespace DFHack }; class Plugin { + struct RefLock; + enum plugin_state + { + PS_UNLOADED, + PS_LOADED, + PS_BROKEN + }; friend class PluginManager; - public: - Plugin(DFHack::Core* core, const std::string& file); + Plugin(DFHack::Core* core, const std::string& filepath, const std::string& filename, PluginManager * pm); ~Plugin(); - bool isLoaded () const; + command_result on_update(); + public: + bool load(); + bool unload(); + bool reload(); + command_result invoke( std::string & command, std::vector & parameters ); + plugin_state getState () const; const PluginCommand& operator[] (std::size_t index) const { return commands[index]; @@ -80,11 +93,13 @@ namespace DFHack return name; } private: + RefLock * access; std::vector commands; std::string filename; std::string name; DFLibrary * plugin_lib; - bool loaded; + PluginManager * parent; + plugin_state state; command_result (*plugin_init)(Core *, std::vector &); command_result (*plugin_status)(Core *, std::string &); command_result (*plugin_shutdown)(Core *); @@ -94,14 +109,17 @@ namespace DFHack { // PRIVATE METHODS friend class Core; + friend class Plugin; PluginManager(Core * core); ~PluginManager(); void OnUpdate( void ); + void registerCommands( Plugin * p ); + void unregisterCommands( Plugin * p ); // PUBLIC METHODS public: - const Plugin *getPluginByName (const std::string & name); + Plugin *getPluginByName (const std::string & name); command_result InvokeCommand( std::string & command, std::vector & parameters ); - const Plugin* operator[] (std::size_t index) + Plugin* operator[] (std::size_t index) { if(index >= all_plugins.size()) return 0; @@ -113,7 +131,8 @@ namespace DFHack } // DATA private: - std::map commands; + SDL::Mutex * cmdlist_mutex; + std::map belongs; std::vector all_plugins; std::string plugin_path; }; diff --git a/plugins/qtplug/qtplug.cpp b/plugins/qtplug/qtplug.cpp index 2fa24a200..7ca0be120 100644 --- a/plugins/qtplug/qtplug.cpp +++ b/plugins/qtplug/qtplug.cpp @@ -39,7 +39,7 @@ DFhackCExport command_result plugin_init ( Core * c, std::vector DFhackCExport command_result plugin_shutdown ( Core * c ) { - return CR_OK; + return CR_FAILURE; } DFhackCExport command_result runqt (Core * c, vector & parameters)