Chapter 9 The Module Monorepo
CNA is one repository, but it is not one library with global src/ and include/ trees. Its physical ownership unit is the module. This matters to an application author because include spelling, link closure, optional features, and even which tests compile are all derived from those module boundaries.
9.1 Location is ownership
The normal framework layout is:
modules/<owner>/ CMakeLists.txt include/ src/ tests/ examples/
The repository root deliberately has no legacy production src/ or include/ directory. A configure-time guard fails if either returns. A second source-partition validator recursively inventories *.cpp and Objective-C++ *.mm files and accepts a translation unit only below a declared framework module or renderer family’s src, tests, or examples area.
The rule is stronger than tidiness. Moving an implementation file changes the component that owns its symbols, dependency declarations, test registration, and include reachability. The validator turns a physical move into an architectural change that must be reconciled explicitly.
9.2 Fifteen framework modules and one optional ABI module
The declared framework set contains fifteen physical modules. Their responsibilities can be read in four clusters:
| Cluster | Modules and ownership |
|---|---|
| Foundation and host | core owns CNA project vocabulary and leaf infrastructure; math owns the Microsoft::Xna::Framework value and geometry types; and platform owns CNA::Platform::IPlatform and the selected implementation. |
| Runtime and graphics | runtime, graphics, input, audio, and media own the game loop, renderer-independent graphics surface, device state, mixer, and media playback. |
| Assets and persistence | content owns native, CNJ, XNB, and glTF loading; storage owns Microsoft::Xna::Framework::Storage. |
| Optional/service surface | devices, devices-ext, graphics-ext, gamer-services, and net separate faithful APIs, project extensions, local services, and ENet-backed networking. |
The concrete aliases are correspondingly narrow. Foundation and runtime code use CNA::Core, CNA::Math, and CNA::Runtime. Framework services use CNA::GraphicsCore, CNA::Input, CNA::Audio, CNA::Media, CNA::Content, and CNA::Storage. Device and extension code uses CNA::Devices, CNA::DevicesExt, and CNA::GraphicsExt. Finally, CNA::GamerServices and CNA::Net are added only when networking is enabled. Appendix D maps every module to its pinned role without turning that inventory into an inferred dependency graph.
With CNA_BUILD_C_API=ON, modules/c-api defines a separate consumer surface as the shared cna_c_api / CNA::CApi target. It wraps the canonical C++ modules and exports C17 headers; it is not another framework implementation. The alpha.1 target is present in the module graph but compile-blocked by its stale renderer-identity map, as Appendix I demonstrates.
9.3 The module factory and one public include root
Most framework modules are created by a small root-level CMake function:
Every module exports exactly its own include/ directory. No module exports src/. A header required across modules must therefore live under a public include root, usually beneath CNA/Internal/ when it is an engine seam rather than application API. Renderer-local shader headers are the narrow exception: they remain in their own source directory and are included only relative to files in that same renderer.
Consumer spelling remains stable because the module root is removed by the include path. A game still writes, for example:
It should not embed modules/runtime/include/ or any other repository-relative prefix. That physical path identifies the owner for auditors; it is not the public include spelling.
9.4 Two deliberate construction exceptions
gamer-services and net hand-build targets named CNA_GamerServices and CNA_Net instead of going through cna_add_module(). Their aliases remain regular, but the concrete target naming differs from the twelve cna_<name> targets. Build tooling that guesses concrete names from directory names will therefore be wrong; use exported aliases or read the module’s own CMake file.
The second exception is headers-only access to the core include tree. cna_core_headers / CNA::CoreHeaders exposes leaf declarations without linking cna_core. Math and Storage use it for items such as the CNAEXT marker, PlayerIndex, and path-containment helpers. This keeps their link closures small while preserving the same include spelling.
9.5 Renderer families are physical modules too
modules/renderers/ declares 46 implementation families plus shared infrastructure. Only the selected family is normally entered. In an opt-in multi-renderer build, each distinct family required by CNA_GRAPHICS_RENDERERS is entered and receives a concrete target named cna_renderer_<family>. One family may carry more than one public identity: EasyGL owns five GL-profile identities, which is why the public registry contains 50 identities but the source partition contains 46 families.
The common setup function gives a renderer its own include root, build flags, sharp-runtime closure, and reverse links to graphics/core/math. The renderer’s source glob is deliberately non-recursive. A nested translation unit does not silently join the target merely because it was created on disk.
There are explicit composition exceptions:
-
•
Metal and Glide expose header-interface targets even when another renderer is selected, because their policy suites compile as part of the shared tests.
-
•
renderers/common/d3d is entered only for Direct3D 11 and 12; Direct3D 9 and 10 have independent implementations.
-
•
GDI reuses eight software-renderer 2D translation units and a software header target. Physical ownership remains with software; GDI lists the borrowed source set explicitly.
These are declared source relationships, not permission for arbitrary cross-family includes. Chapter 20 explains how the generated registry maps the compiled identity set to these physical targets and latches one active family.
9.6 Examples moved to their owners
Framework examples live beside the subsystem they demonstrate; renderer-specific examples live beside the family they exercise; CNAEXT examples live under graphics-ext. The graphics module owns shared renderer-agnostic fixtures such as the 2D demo and common pixel-test helpers. The selected renderer reaches them through CNA_GRAPHICS_EXAMPLES_DIR.
The repository-level examples/ directory now retains only examples/golden/, the cross-renderer image-oracle corpus. That exception is intentional: a golden image compared by many families has no single renderer owner. Executables are still emitted at the build root for compatibility, so output location is not evidence of source ownership.
9.7 The migration was checked as a no-loss transformation
The physical-module campaign moved 1,776 files in seven pure-rename commits before rewriting the build. Its reconciliation classified 1,357 production files before and after: 1,287 were byte-identical moves, 70 changed only ownership/build directives, and none disappeared. It also compared CTest names for representative HEADLESS and OPENGLES configurations and recorded no removals or renames.
Two later changes are easy to conflate with that move. Module-local example registration removed the old central example manifests, and renderer terminology later replaced backend-named build files. A path from before those follow-up changes may therefore be historically real yet wrong at the pinned revision.
9.8 How to read a source path in this book
A path such as modules/content/src/Xna/ContentManager.cpp carries three claims: Content owns the translation unit, its implementation is private to the module, and consumers reach declarations through Content’s public include root. It does not by itself prove which umbrella links the module, which features compiled that source, or which runtime path called it.
For any architecture claim, read four artifacts together:
-
1.
the file’s physical module;
-
2.
that module’s CMakeLists.txt;
-
3.
the root module registry and source-partition rules;
-
4.
a link or execution artifact for the selected configuration.
The next chapter applies that method to cycles, umbrellas, and the gates intended to keep module boundaries honest.