Skip to content

Latest commit

 

History

History
226 lines (182 loc) · 7.22 KB

File metadata and controls

226 lines (182 loc) · 7.22 KB

Module implementation

A Module is a dynamic library which contains a single class loaded at runtime. It means that you must compile every module independently. For that you must implement your modules deriving from oZ::IModule interface. Last, you will need an instantiation function:

extern "C" IModule *CreateModule(void) { return MyModule(); }

// you may want to use the OPEN_ZIA_MAKE_ENTRY_POINT macro:
OPEN_ZIA_MAKE_ENTRY_POINT(MyModule);

The API already does the dynamic library loading for you so you can focus more on creating modules.

Module

In this section we will take a closer look at the interface class oZ::IModule, and all its components.

// Module interface
class IModule;
// Module polymorphic instance
using ModulePtr = std::shared_ptr<IModule>

We use shared pointers to store instantiated modules. This allows holding a reference to a module while asserting its existence.

Modules callbacks

Each module can register multiple callback functions at any state of the pipeline. The enumeration of each callback is oZ::State.

Use the different states and the oZ::Priority enumeration to sort modules callback. Each state of the pipeline are triggered in this order:

BeforeParse -> Parse -> AfterParse -> BeforeInterpret -> Interpret -> AfterInterpret -> Completed

Each callback must take a oZ::Context in parameter and return a boolean. If you return false from the callback, the pipeline will not trigger other modules' callbacks from the current oZ::State and go straight for the next one. At any time, you if an error is set using oZ::Context::setErrorState, the pipeline will trigger the special oZ::State::Error sate callback.

bool MyModule::myCallback(oZ::Context &context)
{
	if (hasError(context)) {
		context.setErrorState();
		return false; // If you return true here, the pipeline will finish to trigger every callback of the current state and not immediatly call Error callbacks
	}
	return true;
}

Must have functions

Each module have a set of virtual function to be overriden. Actually, only 2 of them are pure virtual :

// Get the raw string name of module instance
virtual const char *getName(void) const = 0;

// Register module's callbacks to the pipeline
virtual void onRegisterCallbacks(Pipeline &pipeline) = 0;

HowTo: HTTP module

Let's see a simple example with an independent HTTP Module:

class HTTPModule : public oZ::IModule
{
public:
	// Get the raw string name of module instance
	virtual const char *getName(void) const { return "HTTPModule"; }

	// Register module's callbacks to the pipeline
	virtual void onRegisterCallbacks(oZ::Pipeline &pipeline) {
		pipeline.registerCallback(
			oZ::State::Parse, // Callback triggered when parsing the HTTP request
			oZ::Priority::ASAP, // As soon as possible
			this, &HTTPModule::onParseHeader // Actual function callback
		);
	}
	bool onParseHeader(oZ::Context &context) {
		const auto &byteArray = context.getByteArray();
		auto &request = context.getRequest();
		auto &header = request.getHeader();
		// Parse 'byteArray' to fill 'header' and 'request' data
		return true; // Tell the pipeline we continue to process this state
	}
};

And that's it ! You don't have to implement anything else to create an independent module.

Optional functions

However, there are more virtual functions for more complex needs. These functions are default-implemented to let you the choice of using them or not.

// Callback triggered when a client is connected
virtual void onConnected(Context &context);

// Callback triggered when a client is disconnected
virtual void onDisconnected(Context &context);

// Callback triggered when a client sent a message
virtual MessageState onMessageAvaible(Context &context);

// Get the list of dependencies (as a vector of raw string, see function getName above)
virtual Dependencies getDependencies(void) const noexcept;

// Given a pipeline reference, find each dependent module to store them internally
virtual void onRetreiveDependencies(Pipeline &pipeline);

// Given a directory path (where all configs are), load module's configuration file
virtual void onLoadConfigurationFile(const std::string &directory);

HowTo: Networking

If you implement networking as a standalone module, you need to use Pipeline::onConnection, Pipeline::onDisconnection, IModule::onConnection, IModule::onDisconnection, IModule::onMessageAvaible to implement it as a standalone module.

// Networking module
class NetModule : public oZ::IModule
{
public:
	virtual void onConnection(oZ::Context &context) {
		// Do your connection stuff
	}

	virtual void onDisconnection(oZ::Context &context) {
		// Do your disconnection stuff
	}

	virtual MessageState onMessageAvaible(oZ::Context &context) {
		// Fill 'context.getPacket().getByteArray()' by reading is socket
		return MessageState::Done; // Returns Ready to tell pipeline that you filled the context
	}
	// ...
};

// Client structure
struct Client
{
	oZ::Context context {}; // Client's reusable context
};

void MyServer::onClientConnected(Client &client)
{
	_pipeline.onConnection(client.context);
}

void MyServer::onClientDisconnected(Client &client)
{
	_pipeline.onDisconnection(client.context);
}

void MyServer::onClientReadable(Client &client)
{
	client.context.clear();
	switch (_pipeline.onMessageAvaible(client.context)) {
	case MessageState::Readable:
		// The message has not been handled by any module
		break;
	case MessageState::Done:
		// The message has been processed by the pipeline
		break;
	case MessageState::Disconnection:
		// The message was a TCP disconnection
		break;
	}
}

HowTo: Dependencies

Let's first see how dependencies are handled given a short example:

class ChildModule : public oZ::IModule
{
public:
	virtual const char *getName(void) const { return "Child"; }
	virtual void onRegisterCallbacks(oZ::Pipeline &pipeline) { ... }
};

class RootModule : public oZ::IModule
{
public:
	virtual const char *getName(void) const { return "Root"; }
	virtual void onRegisterCallbacks(oZ::Pipeline &pipeline) { ... }

	// Return the list of dependencies
	virtual Dependencies getDependencies(void) const noexcept { return { "Child" }; }

	// Find dependencies and store them before any process for performance reasons
	virtual void onRetreiveDependencies(oZ::Pipeline &pipeline) {
		_child = pipeline.findModule<ChildModule>();
	}

private:
	std::shared_ptr<ChildModule> _child;
};

HowTo: Configuration file

Let's see how simple it is to add a configuration file to setup a module. Please note that the configuration file language is totally up to you, in this example it is named MyCustomConfigLoader.

// Import your own config loader
#include "MyCustomConfigLoader.hpp"

class MyModule : public oZ::IModule
{
public:
	static const auto *FileName = "MyModule.conf";

	virtual const char *getName(void) const { return "My" };
	virtual void onRegisterCallbacks(oZ::Pipeline &pipeline) { ... }

	// Open configuration file and load some properties using MyCustomConfigLoader
	virtual void onLoadConfigurationFile(const std::string &directory) {
		auto config = MyCustomConfigLoader::Parse(directory + FileName);
		_x = config["x"].toInt();
		_y = config["y"].toInt();
	}

private:
	int _x = 0, _y = 0;
};