-
Notifications
You must be signed in to change notification settings - Fork 0
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.
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 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 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 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 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.
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
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.
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. |
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). |
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.