SCINE Module System =================== Introduction ------------ SCINE consists of multiple smaller projects that each seek to solve a particular problem. However, these projects must be integrated in a fashion that allows more general problems to be addressed. Although we need to ensure that components work according to their stated code contracts, we simultaneously need to keep coupling to a minimum to avoid interface regressions affecting too many downstream projects. To this end, we have created a Core library. It contains interfaces that define the functionality that module writers wish to make available to others through the module system. Furthermore, it contains definitions on how module writers can provide instances of such interfaces to others. Lastly, `core` enables us to load modules and exchange instances of interface-implementing instances at runtime without coupling the individual modules at compile-time through its `ModuleManager` class. Furthermore, there is the Utilities library. It is a library collecting low-level functionality that is widely available. As a developer, you may hard-link against the Utilities library. Depending on whether types defined in the Utilities form part of your API, your link should be `PUBLIC` or `PRIVATE` at CMake level. Writing Your Own Module ----------------------- We have written a number of functions and template trickery to make the process as easy as possible for you. Copy the `SampleModule.h` and `SampleModule.cpp` files from Core library's `Tests` directory and follow the instructions therein. Technical Documentation ----------------------- We chose a "double-blind" module interface to get the most decoupling possible between modules that wish to make use of each other's functionality through a core interface. Structural Overview ------------------- - `Module` base class (Provided in Core) - is abstract - has no dependency on any interface - defines only a single `list`/`has`/`get` triplet for all interfaces, abstracting via a two-string interface - Derived module classes (functionality provider responsibility) - have no public dependencies on interfaces or models - have private dependencies on only those models and interfaces they provide - define the available models of their concepts through a single typedef and helper functions from Core - The implementations of all `Module` classes are identical using the helper functions - `ModuleManager` (consumer-facing class in Core) - Has no public interface dependencies - Provides - `list()` and `list(interf.-str)` - `has(model-string)` and `has(interf.-str, model-str)` - `get(model-string)` and `get(interf.-str, model-str)` - desired `Interface` class definition must be included by the consumer - a list of all possible interface strings - Interfaces - Must provide a `constexpr const char* interface` member with a unique string across all interfaces - Models (classes derived from Interfaces) - Must provide a `constexpr const char* model` member with an interface-wide unique string Tasks When Adding a New Interface --------------------------------- - Add the new interface to `Core/Interfaces` - The interface and your models must fulfill the structural requirements listed above - In your derived `Module` class: - Add your interface name and its models to the typedef you use to implement all class members through the helper functions Tasks When Adding a New Interface Model --------------------------------------- - Add the new model to the corresponding interface's model list in the typedef used to implement your derived `Module` class Consequences ------------ This way, neither `Module` or `ModuleManager`'s interfaces are affected when you add a new Interface to Core, and there are very few public interface dependencies. It is necessary, however, to use `std::shared_ptr` instead of `std::unique_ptr` for model pointers, since `boost::any` requires copyable value types. Additionally, for the mechanism to work properly, both the interface identifying string and the model identifying string have to match, which places a heavier burden on implementation correctness, since some errors can only be found at runtime. It does **not** introduce undefined behavior if type mismatches occur between the module implementing `get` and the consumer expectation type due to `boost::any`'s semantics.