The plugin system

The Machinery is built around a plugin model. All features, even the built-in ones, are provided through plugins. You can extend The Machinery by writing your own plugins.

When The Machinery launches, it loads all the plugins named tm_*.dll in its plugins/ folder. If you write your own plugins, name them so that they start with tm_ and put them in this folder, they will be loaded together with the built-in plugins.

Note: When you create a new plugin via the Engine, the premake file will not copy the plugin into your global plugin folder. The reason behind this is that we do not know if you want to create a plugin asset.

Table of Content

What are the types of plugins?

In The Machinery you can have 2 type of plugins: Engine Plugins and Plugin Assets.

Engine PluginsPlugin Assets
StorageStored in SDK_DIR/pluginsIn your project via drag&drop or imported
AvailabilityAll projectsOnly the project they were imported in
Hot-Reload Support?YesYes
Collaboration SupportNo, unless the client also loads them. They are not syncronized.Yes they are automatically synchronized since they are part of the project.

What are plugins?

In The Machinery is a Shared Library (.dll or .so) that contains the plugin entry function TM_DLL_EXPORT void tm_load_plugin(struct tm_api_registry_api *reg, bool load) . This is the only function that needs to be there. Otherwise the Engine could not load your plugin. Every plugin is a collection of API's or Interfaces. They together form a plugin and give the Engine the Extensibility and flexibility.

About API's

The Machinery is organized into individual APIs that can be called to perform specific tasks. A plugin is a DLL that exposes one or several of these APIs. In order to implement its functionality, the plugin may in turn rely on APIs exposed by other plugins.

A central object called the API registry is used to keep track of all the APIs. When you want to use an API from another plugin, you ask the API registry for it. Similarly, you expose your APIs to the world by registering them with the API registry.

This may seem a bit abstract at this point, so let’s look at a concrete example, unicode.h which exposes an API for encoding and decoding Unicode strings:

{{$include {TM_SDK_DIR}/foundation/unicode.h:0:97}}

Let’s go through this.

First, the code includes <api_types.h>. This is a shared header with common type declarations, it includes things like <stdbool.h> and <stdint.h> and also defines a few The Machinery specific types, such as tm_vec3_t.

In The Machinery we have a rule that header files can't include other header files (except for <api_types.h>). This helps keep compile times down, but it also simplifies the structure of the code. When you read a header file you don’t have to follow a long chain of other header files to understand what is happening.

Next follows a block of forward struct declarations (in this case only one).

Next, we have the name of this API defined as a constant tm_unicode_api, followed by the struct tm_unicode_api that defines the functions in the API.

To use this API, you would first use the API registry to query for the API pointer, then using that pointer, call the functions of the API:

static struct tm_unicode_api *tm_unicode_api;
#include <foundation/api_registry.h>
#include <foundation/unicode.h>

static void demo_usage(char *utf8, uint32_t codepoint)
{
    tm_unicode_api->utf8_encode(utf8, codepoint);
    //more code...
}

TM_DLL_EXPORT void tm_load_plugin(struct tm_api_registry_api *reg, bool load)
{
    tm_unicode_api = tm_get_api(reg, tm_unicode_api);
}

The different APIs that you can query for and use are documented in their respective header files, and in the apidoc.md.html documentation file (which is just extracted from the headers). Consult these files for information on how to use the various APIs that are available in The Machinery.

In addition to APIs defined in header files, The Machinery also contains some header files with inline functions that you can include directly into your implementation files. For example <math.inl> provides common mathematical operations on vectors and matrices, while <carray.inl> provides a “stretchy-buffer” implementation (i.e. a C version of C++’s std::vector).

About Interfaces

We also add an implementation of the unit test interface to the registry. The API registry has support for both APIs and interfaces. The difference is that APIs only have a single implementation, whereas interfaces can have many implementations. For example, all code that can be unit-tested implements the unit test interface. Unit test programs can query the API registry to find all these implementations and run all the unit tests.

To extend the editor you add implementations to the interfaces used by the editor. For example, you can add implementations of the tm_the_truth_create_types_i in order to create new data types in The Truth, and add implementations of the tm_entity_create_component_i in order to define new entity components. See the sample plugin examples.

It does not matter in which order the plugins are loaded. If you query for a plugin that hasn’t yet been registered, you get a pointer to a nulled struct back. When the plugin is loaded, that struct is filled in with the actual function pointers. As long as you don’t call the functions before the plugin that implements them has been loaded, you are good. (You can test this by checking for NULL pointers in the struct.)