MMgr v1.0.0
Zero-heap memory manager in C11. One borrowed buffer, carved by asserted offset.
Loading...
Searching...
No Matches
Contributing

Build and test

All five environments are one build. cmake/MMgrModule.cmake emits mmgr_<module>_<env> for every entry in MMGR_ENVIRONMENTS. A single configure builds host, word32, word16, idx16 and checks together, and a single ctest run covers all five.

cmake -S . -B build -DMMGR_BUILD_TESTS=ON
cmake --build build --parallel
ctest --test-dir build --output-on-failure

A clean run is 160 CTest targets. test_memoriam_praetereo and test_memoria_externa are not among them unless MMGR_ENABLE_DMA or MMGR_ENABLE_EXTRAM is on. They are skipped loudly, through MMGR_SUITES_SKIPPED and a CMake status message, because a silently dropped suite leaves a passing run that tested less than it looks like.

A capability gates the whole suite, never a case inside one. A suite that compiles half its cases away still reports as passing.

Formatting

Three formatters, one per language, each owning its own files, alone. All three wrap at 120 columns. A Python tool and the C it rewrites line up in a side-by-side diff.

find src test -name '*.c' -o -name '*.h' | grep -v '^test/vendor/' | xargs clang-format -i
black tools
npm run format

CI checks and never rewrites. A formatter that rewrites on CI produces commits nobody reviewed and races the author's own push; the fix belongs in the working tree.

Comments

Public headers carry Doxygen comments. Implementation files carry a comment only where the code cannot say it for itself - a bound that is not obvious, a cast that is essential, a failure mode you would see at runtime. A comment that restates the line is worse than none.

Every header opens with an SPDX line and a @file block. The group is mod_<stem>, the stem column of tools/dev_env/names.tsv, and docs/groups.dox already declares it - do not add a @defgroup to a header.

/* MMgr - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Commercial OR LicenseRef-Educational
*/
/**
* @file spatium.h
* @ingroup mod_spat
* @brief A buffer, its capacity and a cursor, carried as one object.
*
* What the module is for, and what a caller has to know before using it. Warnings about what is
* not checked go here and not in every entry.
*/

A config struct documents its members with @param in the struct's own block. Do not repeat them on the entries that read it - they are aggregate-initialized members, not function parameters, and listing them as parameters of a one-argument function reads as a lie:

/**
* @brief The one argument every entry in this module takes.
*
* @param buf Caller-owned storage [BORROWS]. The span does not release it.
* @param cap Size of the storage in bytes.
*/
typedef struct
{
uint8_t *const buf;
const size_t cap;
/**
* @brief Builds a span over `buf`.
*
* @param c Reads `buf` and `cap`.
* @return The span, by value.
*
* @position{0}
* @warning Does not check that `buf` is non-NULL.
*/
mmgr_span mmgr_spat_from(const SpatiumCfg *args)
Builds a fill span over args->buf, with pos at 0 and overflow clear.
Arguments for every spat call, where each call reads only the members it needs.
Definition spatium.h:80
A buffer being filled: its bytes, how far the writer has gone, and whether it ran out.
Definition spatium.h:47

@position{n} is the entry's position in the dispatch table, @par Namespace\ilinebr name` names the table, and [BORROWS], [TAKES OWNERSHIP] and [RETURNS OWNERSHIP] mark who owns a pointer. Everything is a borrow here, and so [BORROWS]` is what you will write.

Doxygen takes one branch of a #if. Put the doc block on the branch it takes, not above the #if, or the entity comes out undocumented - docs/Doxyfile sets PREDEFINED and that setting decides which branch that is.

Run doxygen docs/Doxyfile before committing. WARN_IF_UNDOCUMENTED is on. Anything you added and did not document shows up in docs/doxygen-warnings.log.

Adding a module

  1. A directory under src/.
  2. A three-line CMakeLists.txt calling mmgr_add_module(). Nothing central lists the modules, and so there is no registry to keep in step.
  3. One line in src/CMakeLists.txt.
  4. One @defgroup in docs/groups.dox, placed where it belongs in the data path.
  5. One guide in docs/modules/.
  6. A row in tools/dev_env/names.tsv.

Documentation

Anything in docs/ that can be derived from the tree is derived from the tree and lives inside a generated region. Regenerate before committing:

python -m tools.ci_tooling.ci gen
python -m tools.ci_tooling.ci check

Never hand-edit between the BEGIN GENERATED and END GENERATED markers. They are HTML comments, and each one names the generator that owns that region.