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