Codegen¶
URLab generates most of its MuJoCo component wrappers from MuJoCo's own headers and schema rather than hand-writing them. This page explains why that exists and how the pipeline fits together at a high level. For the contributor how-to (rules, markers, drift gate), see Codegen.
Why generate the wrappers¶
Every MJCF element, option field, enum, and sensor type maps to a UE
type somewhere under Source/URLab/Public/MuJoCo/. MuJoCo defines all
of that in its headers, and those definitions move with every release.
Mirroring them by hand means that a routine MuJoCo bump (say 3.6 to 3.7)
turns into days of editing dozens of files, with easy-to-miss omissions
when a new field or sensor type appears.
Codegen inverts that. A version bump becomes one command: rebuild the snapshots from the new headers, run the generator, ship. The generated files track MuJoCo's metadata automatically, and a drift diagnostic surfaces anything new that needs a URLab-side decision. Hand-written rules cover only those decisions that cannot be derived from MuJoCo's own metadata.
The snapshot-driven pipeline¶
The pipeline runs in two stages: capture MuJoCo's current shape into snapshots, then feed those snapshots to the generator.
flowchart LR
Headers["MuJoCo headers<br/>+ submodule source"]
subgraph Snaps["Three snapshots"]
S1["mjxmacro<br/>model/data array layouts"]
S2["mjcf_schema<br/>MJCF element + attribute schema"]
S3["introspect<br/>clang-AST: mjsX fields, mjt* enums"]
end
Gen["generate_ue_components.py"]
Out["Generated + marker-region<br/>C++ under Source/"]
Headers --> S1 & S2 & S3
S1 & S2 & S3 --> Gen
Gen --> Out
Scripts/codegen/regen_all.py orchestrates the whole run: it rebuilds
the three snapshot JSONs under Scripts/codegen/snapshots/, then runs
the generator against them. The snapshots are the decoupling point. The
generator never reads MuJoCo headers directly; it reads the captured
snapshots plus URLab's codegen_rules.json, which keeps generation
deterministic and reviewable.
Generated versus hand-written code¶
Generated output and hand-written logic coexist in the same files. The boundary is explicit. Codegen-owned regions are fenced with marker comments:
// --- CODEGEN_IMPORT_START ---
// (codegen-emitted, regenerated on every run)
// --- CODEGEN_IMPORT_END ---
Everything inside a // --- CODEGEN_*_START --- / // ---
CODEGEN_*_END --- pair is owned by the generator and replaced on each
regen; everything outside is hand-written and preserved. Some files are
emitted whole as banner-mode artifacts (for example
MjOptionGenerated.h). Editing inside a marked region without updating
the corresponding rule means the next regen reverts the change, which is
the most common codegen mistake.
The generator runs clang-format over everything it emits, so generated code matches a clang-format pass across the tree. That makes the output stable: the build gate can re-run the generator and compare, and an unformatted or stale emit fails the check before compile time.
Related¶
- Codegen: the contributor how-to, with the rules file, marker pairs, and the drift gate.
- Bumping MuJoCo: the end-to-end version-bump procedure.
- MJCF Support: the coverage this pipeline produces.