Skip to content

Latest commit

 

History

History
199 lines (147 loc) · 5.64 KB

8_cpp_modules.md

File metadata and controls

199 lines (147 loc) · 5.64 KB

C++ Morpheus Modules

Background

See Simple Python Module for an introduction to Morpheus modules.

The Passthrough Module

The following example will create a simple C++ module that passes through the input data without modification. This module will be written in C++ and would be compiled into the Morpheus core library.

Note: One thing that is different with respect to c++ modules, is that they are assumed to be stateless by default, meaning that the module itself can be released after the initialize function as been called. If you need a module whose state is persisted across the lifetime of the pipeline, you will also need to inherit from the PersistentModule class, which will cause the pipeline to hold a reference to the module until the pipeline is destroyed.

my_test_module.hpp

#pragma once
#include <mrc/modules/properties/persistent.hpp>
#include <mrc/modules/segment_modules.hpp>
#include <nlohmann/json.hpp>

namespace morpheus {
#pragma GCC visibility push(default)
class MyTestModule: public mrc::modules::SegmentModule, public mrc::modules::PersistentModule
{
    using type_t = MyTestModule;

  public:
    DataLoaderModule(std::string module_name);
    DataLoaderModule(std::string module_name, nlohmann::json _config);

  protected:
    void initialize(mrc::segment::Builder& builder) override;
    std::string module_type_name() const override;

  private:
    int my_persistent_value{0};
};
#pragma GCC visibility pop
}  // namespace morpheus

my_test_module.cpp

#include <mrc/modules/segment_modules.hpp>
#include <mrc/segment/builder.hpp>
#include <mrc/utils/type_utils.hpp>
#include <nlohmann/json.hpp>

#include <string>

namespace morpheus {
MyTestModule::MyTestModule(std::string module_name) : SegmentModule(module_name) {}

MyTestModule::MyTestModule(std::string module_name, nlohmann::json _config) :
  SegmentModule(std::move(module_name), std::move(_config))
{}

void MyTestModule::initialize(mrc::segment::Builder& builder) {
    auto passthrough_node = builder.make_node<std::shared_ptr<MyDataType>>("passthrough_node",
            rxcpp::operators::map([this](std::shared_ptr<MyDataType> data) {
                return data;
            }));

    register_input_port("input_0", passthrough_node);
    register_output_port("output_0", passthrough_node);
}

std::string MyTestModule::module_type_name() const
{
    return std::string(::mrc::boost_type_name<type_t>());
}
}

my_test_module_registration.cpp

#include "mrc/modules/module_registry.hpp"

#include "my_test_module.hpp"

int main(int argc, char** argv) {
    const std::vector<unsigned int> release_version = {1, 0, 0};

    auto module_constructor = [](std::string module_name, nlohmann::json config) {
        return std::make_shared<morpheus::MyTestModule>(module_name, config);
    };

    ModuleRegistry::register_module("my_test_module", "my_module_namespace", release_version, module_constructor);
}

Advanced Topics

Dynamic Module Creation and Loading

#include "mrc/version.hpp"
#include "mrc/modules/module_registry.hpp"

#include "my_test_module.hpp"

extern "C" {

const std::vector<unsigned int> TestModuleVersion{mrc_VERSION_MAJOR, mrc_VERSION_MINOR, mrc_VERSION_PATCH};

const char* MODULES[] = {"my_test_module::my_module_namespace"};

 bool MRC_MODULE_entrypoint_load()  // NOLINT
{
    using namespace mrc::modules;

    try
    {
        ModuleRegistry::register_module(
            "my_test_module",
            "my_module_namespace"
            TestModuleVersion,
            [](std::string module_name, nlohmann::json config) {
                return std::make_shared<mrc::modules::MyTestModule>(std::move(module_name), std::move(config));
            });
    } catch (...)
    {
        return false;
    }

    return true;
}

bool MRC_MODULE_entrypoint_unload()  // NOLINT
{
    using namespace mrc::modules;

    try
    {
        ModuleRegistry::unregister_module("my_test_module", "my_module_namespace");
    } catch (...)
    {
        return false;
    }

    return true;
}

unsigned int MRC_MODULE_entrypoint_list(const char** result)  // NOLINT
{
    *result = (const char*)(&MODULES);

    return 1; // Number of modules
}

The above code is an example of how to declare a shared module that can be loaded at runtime. If we assume this snippet is compile into my_test_module.so, we can load dynamically load the module at runtime using the following:

#include "mrc/modules/module_registry.hpp"

std::string get_modules_path() {
    return std::string{YOUR_MODULES_PATH};
}

int main(int argc, char** argv) {
    using namespace mrc::modules;

    auto plugin = PluginModule::create_or_acquire("my_test_module.so");
    plugin->set_library_directory(get_modules_path());
    plugin->load();

    std::string module_namespace{"my_module_namespace"};
    std::string module_name{"my_test_module"};

    ModuleRegistry::contains_namespace(module_namespace); // Should be true
    ModuleRegistry::contains(module_name, module_namespace); // Should be true
}