[TOC]
Many systems in Banshee are implemented through plugins, libraries that are separate from the core of the engine and can be dynamically loaded or unloaded. If possible, it is the prefered way of extending the engine.
Banshee supports plugins for the following systems:
You can choose which plugins are loaded on Application start-up, by filling out the @ref bs::START_UP_DESC "START_UP_DESC" structure.
START_UP_DESC startUpDesc;
// Required plugins
startUpDesc.renderAPI = "BansheeD3D11RenderAPI";
startUpDesc.renderer = "RenderBeast";
startUpDesc.audio = "BansheeOpenAudio";
startUpDesc.physics = "BansheePhysX";
startUpDesc.input = "BansheeOISInput";
// List of importer plugins we plan on using for importing various resources
startUpDesc.importers.push_back("BansheeFreeImgImporter"); // For importing textures
startUpDesc.importers.push_back("BansheeFBXImporter"); // For importing meshes
startUpDesc.importers.push_back("BansheeFontImporter"); // For importing fonts
startUpDesc.importers.push_back("BansheeSL"); // For importing shaders
// ... also set up primary window in startUpDesc ...
Application::startUp(startUpDesc);
// ... create scene, run main loop, shutdown
In this manual we'll focus on general functionality common to all plugins, while we'll talk about how to implement plugins for specific systems in later manuals.
Plugins are always created as their own projects/libraries. Banshee uses the CMake build system for managing its projects. Therefore the first step you need to take is to create your own CMake project. This involves creating a new folder in the /Source directory (e.g. Source/MyPlugin), with a CMakeLists.txt file inside it. CMakeLists.txt will contain references to needed header & source files, as well as dependencies to any other libraries.
An example CMakeLists.txt might look like so:
# --Source files--
set(SOURCE_FILES
"Include/MyHeader.h"
"Source/MyPlugin.cpp"
)
# --Include directories--
# This plugin depends on BansheeUtility, BansheeCore and BansheeEngine libraries, as well as a third party library named someThirdPartyLib, so we reference the folders where their header files are located. Paths are relative to the plugin folder.
set(INCLUDE_DIRS
"Include"
"../BansheeUtility/Include"
"../BansheeCore/Include"
"../BansheeEngine/Include"
"../../Dependencies/someThirdPartyLib/include")
include_directories(${INCLUDE_DIRS})
# --Target--
# Registers our plugin a specific name (MyPlugin) and with the relevant source files
add_library(MyPlugin SHARED ${SOURCE_FILES})
# --Libraries--
# Registers dependencies on any other libraries
## Register dependency on an external library: someThirdPartyLib
add_library_per_config(MyPlugin someThirdPartyLib Release/someThirdPartyLib Debug/someThirdPartyLib)
## Register dependencies on Banshee libraries
target_link_libraries(MyPlugin PUBLIC BansheeUtility BansheeCore BansheeEngine)
Note that we won't go into details about CMake syntax, it's complex and there are many other guides already written on it.
After creating your project's CMake file, you need to register it with the main CMakeLists.txt present in the /Source directory. Simply append the following line:
# Provided your plugin is located in Source/MyPlugin folder
add_subdirectory(MyPlugin)
If you wish to create a plugin for any of the systems listed above, you will need to implement an informal interface through global "extern" methods. The interface supports three functions:
You may choose to implement some, or none of these, although usually at least loadPlugin() method is needed so the plugin can register itself with the necessary system.
class MyPlugin : Module<MyPlugin>
{
};
extern "C" BS_MYPLUGIN_EXPORT void* loadPlugin()
{
MyPlugin::startUp();
return nullptr; // Not used
}
extern "C" BS_MYPLUGIN_EXPORT void updatePlugin()
{
// Do something every frame
}
extern "C" BS_MYPLUGIN_EXPORT void unloadPlugin()
{
MyPlugin::shutDown();
}
// BS_MYPLUGIN_EXPORT is a macro for a compiler-specific export attribute
// (e.g. __declspec(dllexport) for Visual Studio (MSVC))
After you have your plugin interface, all you need to do is to pass the name of your plugin (as defined in CMake) to one of the entries in START_UP_DESC for it to be loaded.
It's important that all objects created by the plugin are deleted before plugin unload happens. If this doesn't happen, and an object instance is deleted after the plugin has been unloaded you will end up with a corrupt virtual function table and a crash.
You can also create a fully customized plugin that doesn't implement functionality for any existing engine system. The engine has no interface expectations for such plugins, and it's up to you to manually load/unload them, as well as to manually call their functions.
To load a custom plugin you can use:
@ref bs::CoreApplication::unloadPlugin "Application::unloadPlugin()" - Unloads a previously loaded plugin.
DynLib* pluginLib;
gApplication()::loadPlugin("MyPlugin", &pluginLib);
// Do something
gApplication()::unloadPlugin(pluginLib);
Both of those methods internally call DynLibManager which we described earlier. In fact you can also use it directly for loading plugins, as an alternative to this approach.
Once the library is loaded you can use the @ref bs::DynLib "DynLib" object, and its @ref bs::DynLib::getSymbol "DynLib::getSymbol()" method to retrieve a function pointer within the dynamic library, and call into it.
// Retrieve function pointer (symbol)
typedef void* (*LoadPluginFunc)();
LoadPluginFunc loadPluginFunc = (LoadPluginFunc)pluginLib->getSymbol("loadPlugin");
// Call the function
loadPluginFunc();