Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions docs/K1_MUJOCO_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ sim/soccer (docker container, NUClear)
| Display | Optional. Only needed for the GLFW viewer window; `--headless` (or `K1_HEADLESS=1`, see below) skips it entirely — useful for CI or a bare server. |

Nothing else to install for the **sim** itself: `mujoco/docker/k1sim.sh` builds the image (Ubuntu 22.04 +
pinned MuJoCo/Fast-DDS/NUClear/GLFW versions — see `mujoco/tools/install_deps.sh`) the first time it's
needed. The host-side `./b` formatters use [uv](https://docs.astral.sh/uv/) — see
[Host tooling & dependencies](#host-tooling--dependencies-uv) below.
pinned MuJoCo/Fast-DDS/fastddsgen/NUClear/GLFW versions, plus the JRE fastddsgen needs — see
`mujoco/tools/install_deps.sh`) the first time it's needed. The host-side `./b` formatters use
[uv](https://docs.astral.sh/uv/) — see [Host tooling & dependencies](#host-tooling--dependencies-uv) below.

## 2. Quick start

Expand Down Expand Up @@ -166,7 +166,7 @@ There are **two separate environments** — keep them straight:

| Environment | Manages | Lives in | Used by |
| --- | --- | --- | --- |
| **docker image** | C++ sim toolchain: cmake/ninja, the **MuJoCo C library**, Fast-DDS, NUClear (`tools/install_deps.sh`) | the `k1sim` image | `./b configure` / `build` / `run` |
| **docker image** | C++ sim toolchain: cmake/ninja, the **MuJoCo C library**, Fast-DDS, fastddsgen + a JRE, NUClear (`tools/install_deps.sh`) | the `k1sim` image | `./b configure` / `build` / `run` |
| **host uv venv** | Python: the `./b` formatters | repo-root `.venv` (`pyproject.toml` + `uv.lock`) | formatters |

The Python dependency manager is [uv](https://docs.astral.sh/uv/) (as in NUbots). From the repo root:
Expand All @@ -179,6 +179,20 @@ The C++ deploy MuJoCo version is pinned in `cmake/MuJoCoTarget.cmake`, `docker/D
`tools/install_deps.sh`. Keep the training side (the mujoco_playground fork, §7) on the same MuJoCo
version to avoid a sim2sim gap; bumping one means bumping **all** and rebuilding the image (`./b image`).

`tools/install_deps.sh` **always** installs fastddsgen (pinned v3.2.1); the old
`--with-fastddsgen` flag is still accepted but does nothing. It is part of the standard set now
because the Fast-DDS type support is **generated during the build** from `mujoco/idl/**/*.idl` into
`build-docker/idl_gen/` instead of being committed — the same arrangement NUbots uses for protobuf.
fastddsgen is a Java program, so a **Java 11+ runtime is a hard requirement** for a native
(non-docker) build; the image already installs `default-jre-headless`, so the `./b` workflow needs
nothing extra.

Adding a message is therefore one step: write the `.idl` under `mujoco/idl/<package>/msg/` and
build. No regeneration script to run by hand, no generated files to commit, no CMake edit — CMake
picks a new `.idl` up on its own and re-runs the generator when one changes. The generator flags
are wire-format-critical and live in `mujoco/idl/regenerate.sh`: read
`mujoco/module/SdkBridge/PROTOCOL.md` §5 before changing them.

### Extra `./b` commands

- **`./b image`** — (re)build the docker toolchain image. `./b build` only builds it when it's *missing*, so
Expand Down
77 changes: 77 additions & 0 deletions mujoco/cmake/IdlGen.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Generates the Fast-DDS type support from mujoco/idl/**/*.idl at build time.
#
# The generated code is a build artifact (${CMAKE_BINARY_DIR}/idl_gen), never committed —
# the same arrangement NUbots uses for protobuf (nuclear/message/Neutron.cmake runs protoc
# over shared/message/**.proto into the build dir). mujoco/idl/**/*.idl is the source of
# truth; see module/SdkBridge/PROTOCOL.md for where those layouts came from.
#
# Include this from the directory that defines the k1sim_idl target, so the GENERATED
# source properties and the target share a directory scope.
#
# Exports:
# k1sim_idl_files the .idl inputs
# k1sim_idl_outputs every generated file (the library's source list)
# k1sim_idl_packages one generated include directory per IDL package

find_program(
FASTDDSGEN_EXECUTABLE fastddsgen
HINTS $ENV{K1SIM_DEPS_PREFIX}/bin ${PROJECT_SOURCE_DIR}/.deps/install/bin /opt/k1sim-deps/bin
DOC "eProsima Fast-DDS-Gen IDL compiler (installed by tools/install_deps.sh)"
)
if(NOT FASTDDSGEN_EXECUTABLE)
message(
FATAL_ERROR
"fastddsgen not found. It generates the DDS types at build time — run "
"tools/install_deps.sh (needs a Java 11+ runtime), or build in the docker image "
"(docker/k1sim.sh), which bakes it into /opt/k1sim-deps/bin."
)
endif()

set(K1SIM_IDL_DIR ${PROJECT_SOURCE_DIR}/idl)
set(K1SIM_IDL_OUT ${CMAKE_BINARY_DIR}/idl_gen)

# CONFIGURE_DEPENDS: adding a new .idl re-runs cmake on the next build, so a new message
# needs no edit here at all.
file(GLOB k1sim_idl_files CONFIGURE_DEPENDS "${K1SIM_IDL_DIR}/*/msg/*.idl")
if(NOT k1sim_idl_files)
message(FATAL_ERROR "No .idl files found under ${K1SIM_IDL_DIR}/*/msg/")
endif()

# fastddsgen emits exactly these six files per type, into idl_gen/<package>/. idl/regenerate.sh
# asserts that invariant after every run (a type outside its own package directory, or any file
# at the output root, is a hard error there) — which is what makes this list safe to predict.
set(k1sim_idl_outputs "")
set(k1sim_idl_packages "")
foreach(idl ${k1sim_idl_files})
get_filename_component(type_name ${idl} NAME_WE)
get_filename_component(pkg_dir ${idl} DIRECTORY) # .../<pkg>/msg
get_filename_component(pkg_dir ${pkg_dir} DIRECTORY) # .../<pkg>
get_filename_component(pkg ${pkg_dir} NAME)
foreach(suffix ".cxx" ".h" "CdrAux.hpp" "CdrAux.ipp" "PubSubTypes.cxx" "PubSubTypes.h")
list(APPEND k1sim_idl_outputs "${K1SIM_IDL_OUT}/${pkg}/${type_name}${suffix}")
endforeach()
list(APPEND k1sim_idl_packages "${K1SIM_IDL_OUT}/${pkg}")
endforeach()
list(REMOVE_DUPLICATES k1sim_idl_packages)

# A manifest of the .idl set, rewritten at configure time only when that set changes.
# Depending the generator on it makes a REMOVED or renamed message re-run generation (which
# wipes OUT_DIR first); without it, ninja sees every remaining output still present and
# up to date, and the deleted type's headers linger in the build tree on the include path.
list(SORT k1sim_idl_files)
string(REPLACE ";" "\n" k1sim_idl_manifest "${k1sim_idl_files}")
file(CONFIGURE OUTPUT ${CMAKE_BINARY_DIR}/idl_manifest.txt CONTENT "${k1sim_idl_manifest}\n")

# One invocation for every .idl, not one per file: fastddsgen also generates the code for
# every type an .idl #includes (Odometry.idl pulls in Header, Time, PoseWithCovariance, ...),
# so per-file commands would have two rules claiming the same output file, which cmake rejects.
# That also means every .idl is a dependency of the single command — coarse, but a rebuild is
# one JVM start, and it needs no dependency scraping to stay correct.
add_custom_command(
OUTPUT ${k1sim_idl_outputs}
COMMAND ${CMAKE_COMMAND} -E env FASTDDSGEN=${FASTDDSGEN_EXECUTABLE} OUT_DIR=${K1SIM_IDL_OUT}
bash ${K1SIM_IDL_DIR}/regenerate.sh
DEPENDS ${k1sim_idl_files} ${K1SIM_IDL_DIR}/regenerate.sh ${CMAKE_BINARY_DIR}/idl_manifest.txt
COMMENT "Generating Fast-DDS types from mujoco/idl"
VERBATIM
)
13 changes: 13 additions & 0 deletions mujoco/idl/builtin_interfaces/msg/Time.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// builtin_interfaces::msg::dds_::Time_ — see PROTOCOL.md.
// Field layout verified against include/booster/idl/builtin_interfaces/Time.h.

module builtin_interfaces {
module msg {
module dds_ {
struct Time_ {
long sec;
unsigned long nanosec;
};
};
};
};
15 changes: 15 additions & 0 deletions mujoco/idl/geometry_msgs/msg/PoseWithCovariance.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// geometry_msgs::msg::dds_::PoseWithCovariance_ — see PROTOCOL.md.
// Field layout verified against include/booster/idl/geometry_msgs/PoseWithCovariance.h.

#include "Pose.idl"

module geometry_msgs {
module msg {
module dds_ {
struct PoseWithCovariance_ {
geometry_msgs::msg::dds_::Pose_ pose;
double covariance[36];
};
};
};
};
15 changes: 15 additions & 0 deletions mujoco/idl/geometry_msgs/msg/Twist.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// geometry_msgs::msg::dds_::Twist_ — see PROTOCOL.md.
// Field layout verified against include/booster/idl/geometry_msgs/Twist.h.

#include "Vector3.idl"

module geometry_msgs {
module msg {
module dds_ {
struct Twist_ {
geometry_msgs::msg::dds_::Vector3_ linear;
geometry_msgs::msg::dds_::Vector3_ angular;
};
};
};
};
15 changes: 15 additions & 0 deletions mujoco/idl/geometry_msgs/msg/TwistWithCovariance.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// geometry_msgs::msg::dds_::TwistWithCovariance_ — see PROTOCOL.md.
// Field layout verified against include/booster/idl/geometry_msgs/TwistWithCovariance.h.

#include "Twist.idl"

module geometry_msgs {
module msg {
module dds_ {
struct TwistWithCovariance_ {
geometry_msgs::msg::dds_::Twist_ twist;
double covariance[36];
};
};
};
};
14 changes: 14 additions & 0 deletions mujoco/idl/geometry_msgs/msg/Vector3.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// geometry_msgs::msg::dds_::Vector3_ — see PROTOCOL.md.
// Field layout verified against include/booster/idl/geometry_msgs/Vector3.h.

module geometry_msgs {
module msg {
module dds_ {
struct Vector3_ {
double x;
double y;
double z;
};
};
};
};
19 changes: 19 additions & 0 deletions mujoco/idl/nav_msgs/msg/Odometry.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// nav_msgs::msg::dds_::Odometry_ — see PROTOCOL.md.
// Field layout verified against include/booster/idl/nav_msgs/Odometry.h.

#include "Header.idl"
#include "PoseWithCovariance.idl"
#include "TwistWithCovariance.idl"

module nav_msgs {
module msg {
module dds_ {
struct Odometry_ {
std_msgs::msg::dds_::Header_ header;
string child_frame_id;
geometry_msgs::msg::dds_::PoseWithCovariance_ pose;
geometry_msgs::msg::dds_::TwistWithCovariance_ twist;
};
};
};
};
38 changes: 31 additions & 7 deletions mujoco/idl/regenerate.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
#!/usr/bin/env bash
# Regenerates mujoco/idl_gen/ from mujoco/idl/**/*.idl using the fastddsgen baked
# into the k1sim docker image (/opt/k1sim-deps/bin/fastddsgen). Run via e.g.:
# Generates the Fast-DDS type support from mujoco/idl/**/*.idl using the fastddsgen
# baked into the k1sim docker image (/opt/k1sim-deps/bin/fastddsgen).
#
# CMake is the normal caller: cmake/IdlGen.cmake wires this up as a build step and
# passes OUT_DIR=${CMAKE_BINARY_DIR}/idl_gen, so the generated code is a build
# artifact and is never committed (same arrangement as NUbots generating protobuf
# from shared/message/**.proto). Nothing downstream should ever read a generated
# file from the source tree.
#
# Run it by hand only to inspect the output, and point OUT_DIR somewhere scratch:
#
# docker run --rm -v $(pwd)/..:/workspace/NUSim \
# -w /workspace/NUSim/mujoco --user $(id -u):$(id -g) \
# k1sim:latest ./idl/regenerate.sh
# -e OUT_DIR=/tmp/idl_gen k1sim:latest ./idl/regenerate.sh
#
# NOTE: OUT_DIR is wiped (rm -rf) before generating. Never aim it at a source dir.
#
# fastddsgen version recorded at last run: "fastddsgen version 3.2.1" (OpenJDK 11.0.31)
# The generator version is asserted below, not just reported: the wire format is
# version- and flag-sensitive, and with the output no longer visible in a diff,
# this assertion is what catches a generator swap. See module/SdkBridge/PROTOCOL.md.
#
# Flags:
# -cdr v1 -de final Force classic CDR (no XCDR2 DELIMIT_CDR2/DHEADER framing),
Expand Down Expand Up @@ -37,12 +49,24 @@
set -euo pipefail

FASTDDSGEN=${FASTDDSGEN:-/opt/k1sim-deps/bin/fastddsgen}
FASTDDSGEN_VERSION=3.2.1 # must match FASTDDSGEN_TAG in tools/install_deps.sh
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # mujoco/
IDL_DIR="$ROOT/idl"
OUT_DIR="$ROOT/idl_gen"
# Default lands under build/ (gitignored), never in the source tree: a hand run must not
# be able to recreate a committed-looking idl_gen/. CMake always passes OUT_DIR explicitly.
OUT_DIR="${OUT_DIR:-$ROOT/build/idl_gen}"

echo "== fastddsgen version =="
"$FASTDDSGEN" -version
# Hard-fail on a generator other than the pinned one. fastddsgen 3.2.1 with the flags
# below emits classic CDR; other versions change their defaults (see the -cdr/-de note
# above), which would silently alter the wire format the robot decodes.
version_output="$("$FASTDDSGEN" -version 2>&1 | tr -d '\r')"
echo "== $(echo "$version_output" | grep -i '^fastddsgen' || echo "$version_output")"
if ! grep -qiE "^fastddsgen version $FASTDDSGEN_VERSION\$" <<< "$version_output"; then
echo "error: expected fastddsgen $FASTDDSGEN_VERSION, got:" >&2
echo "$version_output" >&2
echo " reinstall with tools/install_deps.sh (FASTDDSGEN_TAG pins v$FASTDDSGEN_VERSION)." >&2
exit 1
fi

# Where fastddsgen runs: <package>/ -> idl/<package>/msg
VIEW="$(mktemp -d)"
Expand Down
15 changes: 15 additions & 0 deletions mujoco/idl/std_msgs/msg/Header.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// std_msgs::msg::dds_::Header_ — see PROTOCOL.md.
// Field layout verified against include/booster/idl/std_msgs/Header.h.

#include "Time.idl"

module std_msgs {
module msg {
module dds_ {
struct Header_ {
builtin_interfaces::msg::dds_::Time_ stamp;
string frame_id;
};
};
};
};
Loading