Component system

Portable component system. More...


class  Component
 Base class for all component classes. More...
class  Clonable
 Clonable component. More...
class  ComponentFactory
class  ImplEntry< ImplType >
struct  Portable< T >
struct  Portable< void >
struct  Portable< T & >
struct  Portable< std::vector< ContainerT > >
struct  Portable< std::string >
struct  Portable< const std::string & >
struct  TypeInfo
 Implements simple run-time reflection. More...


#define LM_INTERFACE_CLASS(Current, Base, N)
#define LM_INTERFACE_F(ID, Name, Signature)
#define LM_IMPL_CLASS(Impl, Base)
#define LM_IMPL_F(Name)
#define LM_DEFINE_CLASS_TYPE(ClassType, BaseClassType)


enum  Types { Class }

Detailed Description

Portable component system.

One of the major objective of this project is to create highly extensive framework for the renderer development. In order to achieve this goal, the user of the framework wants to have flexibility to extend the framework with low cost.

The overall design of the Lightmetrica follows the design pattern known as the dependency injection (DI), which make is possible to decouple the dependencies between classes. In particular, the all extensibule classes and its functions are accessed via interfaces. In C++, the class with all member functions are pure virtual member functions with =0. All instantiation of interfaces are controlled via ComponentFactory factory class.

The distinguishable feature of our system is to retain binary portability. That is, we can combine various plugins compiled with different compilers. This is benefitical because it does not restrict the users build environment. The users try to extend the framework can choose any compilers irrespective to the compiler utilized for builing the core library and the application. For instance, we can even combine the core library compiled with MSVC and plugins compiled with Mingw.

The basic idea is to implement vtable without relying on the language features (see chapter 8 in Imperfect C++ for details). We offer the wrapper macros for implementing interfaces and implementations similar to the way we usually create virtual functions. Similar idea is observed in cppcomponent, but our implementation is simpler than the existing implementation.


The interface class for our comopnent classes is the class (or struct) that is defined by some combination of macros.

struct A : public Component
    LM_INTERFACE_CLASS(A, Component);
    LM_INTERFACE_F(Func, void());

The interface class must begin with LM_INTERFACE_CLASS macro supplying the class type (e.g., A) and the base class type (e.g., Component). Also the class must be finished with LM_INTERFACE_CLASS_END macro.

The member functions are defined by LM_INTERFACE_F macro with the name of the function and the function signature. We can use some non-portable types (e.g., std::string, std::vector) as an argument of the function signature. Currently we don't support overloading and const member functions.

Also we can create the interface with inheritances.

struct B : public A
    LM_INTERFACE_F(Func2, void());


The implementation of the interface must also be defined by the combination of predefined macros:

struct A_Impl : public A
    LM_IMPL_CLASS(A_Impl, A);
    LM_IMPL_F(Func) = [this]() -> void { ... }


The class must begin with LM_IMPL_CLASS macro. The implementation of the interface functions must be defined LM_IMPL_F macro assigning the actual implemetation as a lambda function.

Finally we need to register the implementation to the factory class with LM_COMPONENT_REGISTER_IMPL macro with a key string (in this example, a::impl). The key is later utilized as a key to create an instance of the implementation.

Creating instances

Once we create the interface class and its implementation and assumes the interface A is defined in the header a.h. Then we can create an instance of A_Impl with the factory function ComponentFactory::Create:

#include "a.h"
const auto a = ComponentFactory::Create<A>("a::impl");

The factory function returns unique_ptr of the interface type. Note that we need careful handling of memory allocation/deallocation between boundaries of dynamic libraries. The function automatically registers proper deleter function for the instance created in the different libraries.

Macro Definition Documentation

namespace { \
template <typename T> \
class ImplEntry_Init; \
template <> \
class ImplEntry_Init<ImplType> { static const ImplEntry<ImplType>& reg; }; \
const ImplEntry<ImplType>& ImplEntry_Init<ImplType>::reg = ImplEntry<ImplType>::Instance(Key); \
Definition: component.h:556
#define LM_DEFINE_CLASS_TYPE (   ClassType,
static TypeInfo Type_() { \
TypeInfo t; \
t.type = Types::Class; \ = #ClassType; \
t.classT.base = #BaseClassType; \
return t; \
Implements simple run-time reflection.
Definition: reflection.h:43
#define LM_IMPL_CLASS (   Impl,
using ImplType = Impl; \
using BaseType = Base; \
const struct Impl ## _Init_ { \
Impl ## _Init_(ImplType* p) { p->implName = ImplType::Type_().name; } \
} Impl ## _Init_Inst_{this}
#define LM_IMPL_F (   Name)
struct Name ## _Init_ { \
Name ## _Init_(ImplType* p) { \
p->vt_[Name ## _ID_].f = (void*)(ImplFunctionGenerator<decltype(BaseType::Name)::Type>::Get()); \
p->vt_[Name ## _ID_].implf = (void*)(&p->Name ## _Impl_); \
} \
} Name ## _Init_Inst_{this}; \
friend struct Name ## _Init_; \
const std::function<decltype(BaseType::Name)::Type> Name ## _Impl_
#define LM_INTERFACE_CLASS (   Current,
LM_DEFINE_CLASS_TYPE(Current, Base); \
using BaseType = Base; \
using InterfaceType = Current; \
using UniquePtr = std::unique_ptr<InterfaceType, void(*)(Component*)>; \
static constexpr int NumInterfaces = BaseType::NumInterfaces + N
#define LM_INTERFACE_F (   ID,
static constexpr int Name ## _ID_ = BaseType::NumInterfaces + ID; \
using Name ## _G_ = VirtualFunctionGenerator<Name ## _ID_, InterfaceType, Signature>; \
const VirtualFunction<Name ## _ID_, InterfaceType, Signature> Name = Name ## _G_::Get(this, #Name)