From 94c2f2b60eb3fe15caddcc20e6e533f488bfc52c Mon Sep 17 00:00:00 2001 From: Jonas Kulla Date: Fri, 3 Jan 2014 07:35:25 +0100 Subject: [PATCH] Add experimental plugin system Plugins define the interface specified in plugin.h and have full access to any functions exposed in mkxp. Specify plugins to be loaded in the config file via 'plugin=/path/to/plugin.so' lines. A sample plugin for the MRI binding is provided. --- binding-mri/binding-mri.cpp | 18 +++- mkxp.pro | 7 +- sample-plugin/main.cpp | 41 +++++++++ sample-plugin/sample-plugin.pro | 18 ++++ src/config.cpp | 2 + src/config.h | 2 + src/plugin.cpp | 144 ++++++++++++++++++++++++++++++++ src/plugin.h | 61 ++++++++++++++ 8 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 sample-plugin/main.cpp create mode 100644 sample-plugin/sample-plugin.pro create mode 100644 src/plugin.cpp create mode 100644 src/plugin.h diff --git a/binding-mri/binding-mri.cpp b/binding-mri/binding-mri.cpp index 168f2fc..73f976a 100644 --- a/binding-mri/binding-mri.cpp +++ b/binding-mri/binding-mri.cpp @@ -26,6 +26,7 @@ #include "filesystem.h" #include "util.h" #include "debugwriter.h" +#include "plugin.h" #include #include @@ -290,6 +291,21 @@ static void mriBindingExecute() mriBindingInit(); + PluginLoader *pluginLoader; + + try + { + pluginLoader = new PluginLoader(PLUGIN_MRI); + } + catch (const Exception &exc) + { + showMsg(exc.msg); + ruby_cleanup(0); + shState->rtData().rqTermAck = true; + + return; + } + std::string &customScript = shState->rtData().config.customScript; if (!customScript.empty()) runCustomScript(customScript.c_str()); @@ -310,7 +326,7 @@ static void mriBindingExecute() } ruby_cleanup(0); - + delete pluginLoader; shState->rtData().rqTermAck = true; } diff --git a/mkxp.pro b/mkxp.pro index 40492ce..8ebb6f7 100644 --- a/mkxp.pro +++ b/mkxp.pro @@ -6,6 +6,7 @@ TARGET = mkxp DEPENDPATH += src shader assets INCLUDEPATH += . src LIBS += -lGL +QMAKE_LFLAGS += -rdynamic CONFIG(release, debug|release): DEFINES += NDEBUG @@ -116,7 +117,8 @@ HEADERS += \ src/sharedstate.h \ src/al-util.h \ src/boost-hash.h \ - src/debugwriter.h + src/debugwriter.h \ + src/plugin.h SOURCES += \ src/main.cpp \ @@ -144,7 +146,8 @@ SOURCES += \ src/config.cpp \ src/tileatlas.cpp \ src/perftimer.cpp \ - src/sharedstate.cpp + src/sharedstate.cpp \ + src/plugin.cpp EMBED = \ shader/transSimple.frag \ diff --git a/sample-plugin/main.cpp b/sample-plugin/main.cpp new file mode 100644 index 0000000..c91e2d6 --- /dev/null +++ b/sample-plugin/main.cpp @@ -0,0 +1,41 @@ + +#include "plugin.h" +#include "debugwriter.h" +#include "exception.h" + +#include "binding-util.h" + +static void pluginInit(); +static void pluginFini(); + +Plugin plugin = +{ + pluginInit, + pluginFini, + PLUGIN_MRI +}; + +/* Callable from ruby scripts as 'plugin_function' */ +RB_METHOD(pluginFunction) +{ + RB_UNUSED_PARAM; + + const char *str = 0; + rb_get_args(argc, argv, "|z", &str RB_ARG_END); + + Debug() << "Sample plugin function:" << str; + + return Qnil; +} + +void pluginInit() +{ + _rb_define_module_function(rb_mKernel, "plugin_function", pluginFunction); + + Debug() << "Sample plugin inited!"; +} + +void pluginFini() +{ + Debug() << "Sample plugin finited!"; +} diff --git a/sample-plugin/sample-plugin.pro b/sample-plugin/sample-plugin.pro new file mode 100644 index 0000000..fd61129 --- /dev/null +++ b/sample-plugin/sample-plugin.pro @@ -0,0 +1,18 @@ +###################################################################### +# Automatically generated by qmake (2.01a) Fri Jan 3 01:40:45 2014 +###################################################################### + +TEMPLATE = lib +QT = +TARGET = sample-plugin +DEPENDPATH += . ../src ../binding-mri +INCLUDEPATH += . ../src ../binding-mri +CONFIG += plugin + +unix { + CONFIG += link_pkgconfig + PKGCONFIG += ruby-2.1 +} + +# Input +SOURCES += main.cpp diff --git a/src/config.cpp b/src/config.cpp index 8f594bf..1660bba 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -81,6 +81,7 @@ void Config::read() podesc.add_options() PO_DESC_ALL ("RTP", po::value()) + ("plugin", po::value()) ; std::ifstream confFile; @@ -103,6 +104,7 @@ void Config::read() PO_DESC_ALL; GUARD_ALL( rtps = vm["RTP"].as(); ); + GUARD_ALL( plugins = vm["plugin"].as(); ); #undef PO_DESC #undef PO_DESC_ALL diff --git a/src/config.h b/src/config.h index 3de9249..92231c1 100644 --- a/src/config.h +++ b/src/config.h @@ -53,6 +53,8 @@ struct Config std::string customScript; std::vector rtps; + std::vector plugins; + /* Game INI contents */ struct { std::string scripts; diff --git a/src/plugin.cpp b/src/plugin.cpp new file mode 100644 index 0000000..d310ea8 --- /dev/null +++ b/src/plugin.cpp @@ -0,0 +1,144 @@ +/* +** plugin.cpp +** +** This file is part of mkxp. +** +** Copyright (C) 2013 Jonas Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#include "plugin.h" + +#include "sharedstate.h" +#include "config.h" +#include "exception.h" + +#include + +#include + +struct LoadedPlugin +{ + void *handle; + Plugin *iface; + bool inited; +}; + +struct PluginLoaderPrivate +{ + std::vector loadedPlugins; + + /* Load so files / lookup interfaces */ + void loadPlugins(const std::vector &plgList, + const char *binding) + { + for (size_t i = 0; i < plgList.size(); ++i) + { + const std::string &plgName = plgList[i]; + + void *handle = SDL_LoadObject(plgName.c_str()); + + if (!handle) + throw Exception(Exception::SDLError, "%s", SDL_GetError()); + + Plugin *iface = static_cast(SDL_LoadFunction(handle, "plugin")); + + if (!iface) + throw Exception(Exception::SDLError, "Error loading plugin '%s' interface: %s", + plgName.c_str(), SDL_GetError()); + + if (strcmp(binding, iface->binding)) + throw Exception(Exception::MKXPError, "Plugin '%s' is incompatible with '%s' binding", + plgName.c_str(), binding); + + LoadedPlugin loaded; + loaded.handle = handle; + loaded.iface = iface; + loaded.inited = false; + + loadedPlugins.push_back(loaded); + } + } + + /* Initialize loaded plugins */ + void initPlugins() + { + for (size_t i = 0; i < loadedPlugins.size(); ++i) + { + LoadedPlugin &plugin = loadedPlugins[i]; + + plugin.iface->init(); + plugin.inited = true; + } + } + + /* Finalize loaded plugins */ + void finiPlugins() + { + for (size_t i = 0; i < loadedPlugins.size(); ++i) + { + LoadedPlugin &plugin = loadedPlugins[i]; + + if (plugin.inited) + plugin.iface->fini(); + } + } + + /* Unload so files */ + void unloadPlugins() + { + for (size_t i = 0; i < loadedPlugins.size(); ++i) + SDL_UnloadObject(loadedPlugins[i].handle); + } +}; + +PluginLoader::PluginLoader(const char *binding) +{ + p = new PluginLoaderPrivate; + + const std::vector &plgList = shState->config().plugins; + + try + { + p->loadPlugins(plgList, binding); + } + catch (const Exception &exc) + { + p->unloadPlugins(); + delete p; + + throw exc; + } + + try + { + p->initPlugins(); + } + catch (const Exception &exc) + { + p->finiPlugins(); + p->unloadPlugins(); + delete p; + + throw exc; + } +} + +PluginLoader::~PluginLoader() +{ + p->finiPlugins(); + p->unloadPlugins(); + delete p; +} diff --git a/src/plugin.h b/src/plugin.h new file mode 100644 index 0000000..429d54b --- /dev/null +++ b/src/plugin.h @@ -0,0 +1,61 @@ +/* +** plugin.h +** +** This file is part of mkxp. +** +** Copyright (C) 2013 Jonas Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#ifndef PLUGIN_H +#define PLUGIN_H + +/* Note: Plugins are meant to be compiled in unison + * with mkxp; there is no API stability whatsoever. */ + +/* Use one of these for 'binding' */ +#define PLUGIN_MRI "mri" +#define PLUGIN_MRUBY "mruby" + +/* Interface that plugins implement via global symbol named 'plugin'*/ +struct Plugin +{ + /* Initialize plugin. May throw 'Exception' instance on error. + * The interpreted environment is set up at this point. */ + void (*init)(); + + /* Finalize plugin. The interpreted environment + * is already torn down at this point. */ + void (*fini)(); + + /* Binding that this plugin applies to (see above) */ + const char *binding; +}; + + +/* For use in main mkxp */ +struct PluginLoaderPrivate; + +class PluginLoader +{ +public: + PluginLoader(const char *binding); + ~PluginLoader(); + +private: + PluginLoaderPrivate *p; +}; + +#endif // PLUGIN_H