Skip to content

Moduler systems using macros

Jaibeer S Dugal edited this page Oct 4, 2023 · 3 revisions

We are now trying to design a modular system for well... modules.

  1. Module: This is the interface that defines the contract for all modules. It contains pure virtual functions that must be implemented by any module. It ensures that all modules adhere to a common interface, making it easier to manage them in a centralized way.

  2. AModule: This is an abstract class that implements IModule and provides default implementations for some or all of the methods defined in IModule. This class can implement common behavior that is shared across all modules, reducing code duplication.

  3. WindowModule, InputModule, etc.: These are concrete classes that inherit from AModule and implement the specific behavior for each module. They can override methods from AModule to provide module-specific behavior.

  4. REGISTER_MODULE Macro: This macro helps in registering a module along with its dependencies in the ModuleRegistry. It makes the registration process easier and less error-prone by automating the creation of registrar objects and instances of the modules.

  5. ModuleRegistry: This is a singleton class that manages all the modules in the system. It keeps track of all registered modules and their dependencies, and provides methods to initialize, update, and clean up modules in the correct order based on their dependencies.

classDiagram
    class IModule {
        +virtual InitializeModule() : void = 0
        +virtual UpdateModule() : void = 0
        +virtual CleanUpModule() : void = 0
    }
    
    class AModule {
        +InitializeModule() : void
        +UpdateModule() : void
        +CleanUpModule() : void
    }
    
    class WindowModule {
        +InitializeModule() : void
        +UpdateModule() : void
        +CleanUpModule() : void
    }
    
    class InputModule {
        +InitializeModule() : void
        +UpdateModule() : void
        +CleanUpModule() : void
    }
    
    class ModuleRegistry {
        -modules : map<string, IModule*>
        +Instance() : ModuleRegistry&
        +GetModule<T>() : T*
        +RegisterModule<T>(T*) : void
        +InitializeModules() : void
        +UpdateModules() : void
        +CleanUpModules() : void
    }
    
    IModule <|-- AModule 
    AModule <|-- WindowModule 
    AModule <|-- InputModule 
    ModuleRegistry "1" o-- "*" IModule : manages

Loading

Module Registry

All right using Macros for this makes no sense in this case cause it does requrie to make it so that we can easily create a namespced instance and try and create a module.

first, let's break something important down. namespace area that will create a struct # operator this is the Stringifier operator. using the methadoly of

	#define REGISTER_MODULE(ModuleClass, ModuleName, ...)                   \  
namespace { \  
	// Creating a on stack struct registere
    struct ModuleClass##Registrar { \  
	    // creating a constructor for it \
        ModuleClass##Registrar() { \  
	        // initializing the input for the class we are trying to create \
            static std::shared_ptr<ModuleClass> ModuleName = std::make_shared<ModuleClass>(); \  
            // registering it 
            ModuleRegistry::Instance().RegisterModule(ModuleName, {__VA_ARGS__}); \  
        } \  
    }; \  
    // then creating an instance of it to actually call the constructor.
    static ModuleClass##Registrar ModuleClass##registrar; \  
}

The problems with this approach.

  1. Static Initialization Order: Be cautious when relying on static initialization order, especially when you have multiple translation units (source files) using this technique. The order of initialization of these static variables is not guaranteed and can lead to subtle bugs. It may work in simple cases but become problematic as your project grows.

  2. Multiple Declarations: Each translation unit (source file) that includes this macro will generate a separate instance of the ModuleClass##Registrar. If you include this macro in multiple source files, you'll end up with multiple instances of the registrar, which could be unexpected behaviour. // can be fixed with inline static? SOl . By adding the inline keyword, you ensure that the ModuleClass##registrar variable is defined only once across all translation units. This will prevent the multiple-declaration issue and ensure that the registration happens once.

Keep in mind that the use of inline for variables has a different meaning compared to functions. In this context, it ensures that the variable is defined only once, and the linker will merge all instances into a single one.

  1. Namespace Pollution: This approach creates anonymous namespaces for each module, which may lead to unnecessary namespace pollution in your codebase.

  2. Clarity: The approach may not be immediately obvious to someone reading your code, so it's essential to document it clearly for anyone working on the project.

  3. Error Handling: This approach doesn't provide a straightforward way to handle errors during module registration. If module creation or registration fails, there's no mechanism to report or handle the error gracefully.

Clone this wiki locally