Skip to content

Module System

Max Roncace edited this page Oct 21, 2023 · 5 revisions

Module System

Argus is designed to be highly modular and as such is architected around a system of "modules," or discrete projects which contain code pertaining to a particular system of the engine. For instance, input is handled by a dedicated module, while resource management is handled by another.

Modules may declare dependencies at build time upon other modules, in which case they will have access to those modules' headers and therefore functionality. For static and dynamic modules, these dependency chains must be linear - circular dependencies are not allowed and will cause either a configuration error or a runtime error, depending on the type of module.

Types of Modules

Modules take one of four forms at the moment: static modules, engine libraries, dynamic modules, and executable modules. Modules are separated according to their type within the engine directory in the repository root.

Engine Libraries

Engine libraries are statically-linked support libraries which implement utility features used by other modules. They do not execute lifecycle events and as such cannot be disabled. Engine libraries may only depend on other engine libraries and are permitted to form circular dependency chains.

Static Modules

Static modules are statically-linked libraries which are "built into" the engine. These comprise modules implementing basic functionality such as input and resource management as mentioned above. They will always be present at runtime by virtue of being statically linked, but may be enabled or disabled at runtime. In this context, to be "enabled" is for a module to receive lifecycle events (which are required for any sort of bootstrapping). Thus, a "disabled" module will not execute any code over the lifespan of the engine. Modules are disabled by default and must be requested by the game client, either explicitly or transitively.

The dependency topology of static modules is validated at configure-time and a hard-coded load order is generated by the build script. If the topology is invalid (e.g. the modules contain cyclical dependencies), the configure will fail. Additionally, static modules may only depend on static modules and engine libraries. They may not depend on dynamic modules - in the event that a static module must enable a dynamic module, it must do so at runtime during the Load lifecycle stage.

Dynamic Modules

Dynamic modules are dynamically-linked libraries which exist in the filesystem alongside the engine and are loaded on-demand at runtime. They may be requested by the client via the engine configuration, or they may be requested by other modules during the Load lifecycle stage. These modules are intended for two purposes: first, to allow compile-time linking with libraries which may or may not be available on the target system, and second, to allow the client to extend the engine's functionality with C++ code rather than simple scripts.

The dependency topology of dynamic modules is validated at runtime. If a dynamic module which is part of a cyclical dependency chain is requested, the engine initialization routine will panic and halt. Dynamic modules may depend on all types of engine modules.

Executable Modules

Executable modules are, as their name suggests, built as executables and linked against the base Argus library. They currently have access to all includes (including internal includes) from all modules. They emit standalone artifacts to the bin and dist/bin directories in the build directory and do not affect other modules in any way.

Filesystem Structure

The root build script expects modules to utilize a specific directory structure, which is specified here:

module_root
 |_ include
 |   |_ [C/C++ headers go here]
 |_ res
 |   |_ [Root resource filesystem goes here]
 |_ src
 |   |_ [C/C++ sources go here]
 |_ module.cmake
 |_ module.properties

Module Configuration

Modules are configured by the build system according to a module.properties file provided in the root module directory. This file specifies a number of parameters which will be used to compile and link the module sources.

Additionally, a module.cmake file may be provided to enable more complex configuration and build steps. For example, the render_opengl module provides a module.cmake which invokes Aglet, the script responsible for generating the OpenGL headers used by the module. The module.cmake file will be included by the root CMake script after the module project() call has been emitted.

Module Parameters (.properties)

Parameters may include CMake variables such as ${SOMELIB_INCLUDE_DIR} which will be expanded at configure time.

Bold text indicates a required parameter. Parameters with a list type must be a comma-separated list (e.g. list_key = item1,item2).

Name Type Description
name string The name of the module. Must be alphanumeric and unique.
type string The type of the module. Must be one of {static, dynamic, library}.
languages list The languages the module is written in as specified by CMake.
engine_module_deps list The engine modules this module is dependent on. Must follow the dependency rules outlined above.
engine_library_deps list The engine libraries this module is dependent on.
include_dirs list Directories which should be included when compiling this module. This is intended exclusively for external libraries, as include directories from dependency modules are implicitly included.
linker_deps list Libraries which should be linked with this module. Like include_dirs, this is intended exclusively for external libraries.
linker_deps_win32 list Libraries which should be linked only when building for Win32 platforms.
linker_deps_linux list Libraries which should be linked only when building for Linux-based platforms.
linker_deps_macos list Libraries which should be linked only when building for macOS platforms.
extra_source_dirs list Additional directories which should be scanned for compile units. This is mainly intended for generated sources.
required_packages list Libraries which should be located via CMake's find_package at configure time. The build will fail if they cannot be located.
optional_packages list Similar to required_packages, but for optional packages that will not fail the build if not found.

Module Variables (.cmake)

A number of variables are provided to module.cmake scripts when they are invoked. These should not be modified.

Additionally, the script is run in the context of a project specific to the module, and as such the usual project variables will be available as well (e.g. PROJECT_SOURCE_DIR).

Name Description
MODULE_NAME The name of the module (as specified in modules.properties).
INCLUDE_DIR_NAME The name to use for directories added to a compilation unit's include path.
SOURCE_DIR_NAME The name to use for directories which are to be scanned for source files.
MODULE_PROJECT_DIR The root directory of the module (equivalent to PROJECT_SOURCE_DIR).
ROOT_PROJECT_DIR The directory of the root project (the one responsible for building all the modules and linking them together).
MODULE_GENERATED_DIR The directory to emit generated headers and sources to (files should be written to ${MODULE_GENERATED_DIR}/${INCLUDE_DIR_NAME} and ${MODULE_GENERATED_DIR}/${SOURCE_DIR_NAME} respectively).

Module Resources

Modules may include internal resources by means of the res directory. If this directory exists in a module's root directory, its contents will be packed into an ARP package and embedded into the module binary during compilation. The module's source files may then include internal/<module name>/resources.h, which will contain the binary content of the generated package. This content can then be passed directly to the resource manager via ResourceManager::instance().add_memory_package to make its contents available for use.