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.
This commit is contained in:
Jonas Kulla 2014-01-03 07:35:25 +01:00
parent 3daf805350
commit 94c2f2b60e
8 changed files with 290 additions and 3 deletions

View File

@ -26,6 +26,7 @@
#include "filesystem.h" #include "filesystem.h"
#include "util.h" #include "util.h"
#include "debugwriter.h" #include "debugwriter.h"
#include "plugin.h"
#include <ruby.h> #include <ruby.h>
#include <ruby/encoding.h> #include <ruby/encoding.h>
@ -290,6 +291,21 @@ static void mriBindingExecute()
mriBindingInit(); 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; std::string &customScript = shState->rtData().config.customScript;
if (!customScript.empty()) if (!customScript.empty())
runCustomScript(customScript.c_str()); runCustomScript(customScript.c_str());
@ -310,7 +326,7 @@ static void mriBindingExecute()
} }
ruby_cleanup(0); ruby_cleanup(0);
delete pluginLoader;
shState->rtData().rqTermAck = true; shState->rtData().rqTermAck = true;
} }

View File

@ -6,6 +6,7 @@ TARGET = mkxp
DEPENDPATH += src shader assets DEPENDPATH += src shader assets
INCLUDEPATH += . src INCLUDEPATH += . src
LIBS += -lGL LIBS += -lGL
QMAKE_LFLAGS += -rdynamic
CONFIG(release, debug|release): DEFINES += NDEBUG CONFIG(release, debug|release): DEFINES += NDEBUG
@ -116,7 +117,8 @@ HEADERS += \
src/sharedstate.h \ src/sharedstate.h \
src/al-util.h \ src/al-util.h \
src/boost-hash.h \ src/boost-hash.h \
src/debugwriter.h src/debugwriter.h \
src/plugin.h
SOURCES += \ SOURCES += \
src/main.cpp \ src/main.cpp \
@ -144,7 +146,8 @@ SOURCES += \
src/config.cpp \ src/config.cpp \
src/tileatlas.cpp \ src/tileatlas.cpp \
src/perftimer.cpp \ src/perftimer.cpp \
src/sharedstate.cpp src/sharedstate.cpp \
src/plugin.cpp
EMBED = \ EMBED = \
shader/transSimple.frag \ shader/transSimple.frag \

41
sample-plugin/main.cpp Normal file
View File

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

View File

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

View File

@ -81,6 +81,7 @@ void Config::read()
podesc.add_options() podesc.add_options()
PO_DESC_ALL PO_DESC_ALL
("RTP", po::value<StringVec>()) ("RTP", po::value<StringVec>())
("plugin", po::value<StringVec>())
; ;
std::ifstream confFile; std::ifstream confFile;
@ -103,6 +104,7 @@ void Config::read()
PO_DESC_ALL; PO_DESC_ALL;
GUARD_ALL( rtps = vm["RTP"].as<StringVec>(); ); GUARD_ALL( rtps = vm["RTP"].as<StringVec>(); );
GUARD_ALL( plugins = vm["plugin"].as<StringVec>(); );
#undef PO_DESC #undef PO_DESC
#undef PO_DESC_ALL #undef PO_DESC_ALL

View File

@ -53,6 +53,8 @@ struct Config
std::string customScript; std::string customScript;
std::vector<std::string> rtps; std::vector<std::string> rtps;
std::vector<std::string> plugins;
/* Game INI contents */ /* Game INI contents */
struct { struct {
std::string scripts; std::string scripts;

144
src/plugin.cpp Normal file
View File

@ -0,0 +1,144 @@
/*
** plugin.cpp
**
** This file is part of mkxp.
**
** Copyright (C) 2013 Jonas Kulla <Nyocurio@gmail.com>
**
** 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 <http://www.gnu.org/licenses/>.
*/
#include "plugin.h"
#include "sharedstate.h"
#include "config.h"
#include "exception.h"
#include <SDL_loadso.h>
#include <string.h>
struct LoadedPlugin
{
void *handle;
Plugin *iface;
bool inited;
};
struct PluginLoaderPrivate
{
std::vector<LoadedPlugin> loadedPlugins;
/* Load so files / lookup interfaces */
void loadPlugins(const std::vector<std::string> &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<Plugin*>(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<std::string> &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;
}

61
src/plugin.h Normal file
View File

@ -0,0 +1,61 @@
/*
** plugin.h
**
** This file is part of mkxp.
**
** Copyright (C) 2013 Jonas Kulla <Nyocurio@gmail.com>
**
** 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 <http://www.gnu.org/licenses/>.
*/
#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