Portable component system. More...
Classes | |
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... | |
Enumerations | |
enum | Types { Class } |
Types. | |
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.
Interface
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()); LM_INTERFACE_CLASS_END(); };
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_CLASS(B, A); LM_INTERFACE_F(Func2, void()); LM_INTERFACE_CLASS_END(); };
Implementation
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 { ... } }; LM_COMPONENT_REGISTER_IMPL(A_Impl, "a::impl");
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
#define LM_COMPONENT_REGISTER_IMPL | ( | ImplType, | |
Key | |||
) |
#define LM_DEFINE_CLASS_TYPE | ( | ClassType, | |
BaseClassType | |||
) |
#define LM_IMPL_CLASS | ( | Impl, | |
Base | |||
) |
#define LM_IMPL_F | ( | Name | ) |
#define LM_INTERFACE_CLASS | ( | Current, | |
Base, | |||
N | |||
) |
#define LM_INTERFACE_F | ( | ID, | |
Name, | |||
Signature | |||
) |