Plugins

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:

  • Audio - Systems for providing audio playback.
  • Importers - Importers that handle conversion of some third party resource format into an engine-ready format.
  • Input - Reports input events (mouse, keyboard, gamepad, etc.)
  • Physics - Runs the physics simulation.
  • Renderer - Determines how is the scene displayed (lighting, shadows, post-processing, etc.).
  • Rendering API - Wrappers for render APIs like DirectX, OpenGL or Vulkan.

You can choose which plugins are loaded on Application start-up, by filling out the 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.

Generating a CMake project

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:

1 # --Source files--
2 set(SOURCE_FILES
3  "Include/MyHeader.h"
4  "Source/MyPlugin.cpp"
5 )
6 
7 # --Include directories--
8 # 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.
9 set(INCLUDE_DIRS
10  "Include"
11  "../BansheeUtility/Include"
12  "../BansheeCore/Include"
13  "../BansheeEngine/Include"
14  "../../Dependencies/someThirdPartyLib/include")
15 
16 include_directories(${INCLUDE_DIRS})
17 
18 # --Target--
19 # Registers our plugin a specific name (MyPlugin) and with the relevant source files
20 add_library(MyPlugin SHARED ${SOURCE_FILES})
21 
22 # --Libraries--
23 # Registers dependencies on any other libraries
24 ## Register dependency on an external library: someThirdPartyLib
25 add_library_per_config(MyPlugin someThirdPartyLib Release/someThirdPartyLib Debug/someThirdPartyLib)
26 
27 ## Register dependencies on Banshee libraries
28 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:

1 # Provided your plugin is located in Source/MyPlugin folder
2 add_subdirectory(MyPlugin)

Plugin interface

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:

  • loadPlugin() - Called when the plugin is initially loaded
  • updatePlugin() - Called every frame
  • unloadPlugin() - Called just before the plugin is unloaded

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.

Fully custom plugins

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:

  • Application::loadPlugin() - Accepts the name of the plugin library and outputs the library object. Optionally you may also pass a parameter to the loadPlugin method, if your plugin defines one.
  • 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 DynLib object, and its 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();