Current section
Files
Jump to
Current section
Files
docs/plan.md
# The plan
Where this package is going, and why it is going in this order. The short version: the
physics first, because that is what Monty needs and what a NIF is good at; the scene second,
because building a model at run time is a data-structure problem; rendering last, because a GL
context is a platform problem.
## v0.1 — physics (this release)
What is here:
* a model compiled from MJCF, from a path or from memory;
* a data block, stepping, `mj_forward`, and the integration state as a binary;
* every field of `mjModel` and `mjData`, one function each, generated from the headers;
* every MuJoCo enum, resolved from the headers;
* the C functions in `c_src/manifest.exs`, bound 1:1 (`ExMujoco.Api`), plus a dozen
hand-written ones where a call needs a decision;
* MuJoCo's fatal error caught and re-raised, so a bad model cannot abort the VM.
What is deliberately not here: `mjSpec`, rendering. Both are below.
### What "later replaces the Python wrapper" needs
`ex.tbp.monty/priv/monty_py/world.py` imports `MjData, MjSpec, Renderer, mj_forward,
mjtGeom, mjtJoint, mjtLightType, mjtTexture, mjtTextureRole`. Three of those four are covered
here. The fourth — and the one that builds the scene — is `mjSpec`.
## v0.2 — `mjSpec`: a model you build, not only one you read
The object graph MuJoCo compiles from: `mjSpec`, `mjsBody`, `mjsGeom`, `mjsJoint`,
`mjsLight`, `mjsCamera`, `mjsSite`, `mjsTexture`, `mjsMesh`, plus `mj_compile`.
The shape of the work:
* `mjsBody* body = mjs_addBody(parent, spec)` returns a pointer into a tree owned by the spec,
so the resource is the `mjSpec` and the Elixir side holds a *path* to an element, not a
pointer — an element handle that outlives a re-parent is a use-after-free waiting to be
written. The alternative, one resource per element, holds the spec alive per element and is
easier to reason about; that is the decision to make first.
* The `mjsXxx` structs are plain C, so their fields generate the same way `mjModel`'s do.
* `mj_compile` returns an `mjModel` that the spec still owns; `mj_recompile` is the loop that
matters for editing a scene between episodes.
Monty's world builder needs one more thing: **meshes from memory**. `mjs_addMesh` takes a name
and fills `vert`/`face`; MuJoCo also has `mj_addBufferVFS` for assets that are not files. That
is the piece that replaces `mesh_data` handling in the Python wrapper.
## v0.3 — rendering
`mjrContext`, `mjrRender`, `mjvScene`, `mjvCamera`, `mjvUpdateGeom`, and an offscreen frame to
`depth` / `rgb` / `segmentation`.
Why it is last: a GL context belongs to a thread and to a platform. Linux wants EGL or
OSMesa, macOS wants CGL, Windows wants WGL, and MuJoCo's own `Renderer` picks between them
with `mjr_resolveGLContext`. Two consequences:
* **one NIF per frame and a context pinned to one scheduler thread**, or a dirty-IO NIF with
its own thread; the Python wrapper already had to solve this (`_on_one_thread`), and the
reason it did is worth reading before writing the Elixir one;
* the context must be created and destroyed by the same thread, so the resource holds the
thread, not just the context.
Until then, `depth` and `segmentation` come from Python, or from whatever else already renders.
## Beyond
* **`mj_step` on a worker.** A step is one call into MuJoCo, which is the point of the NIF — but
on a big model it is milliseconds, and milliseconds inside a normal NIF block a scheduler.
`step/1` is documented as such; a `step_async/1` backed by a dirty-IO NIF or a
`Task`-per-episode pool is the next thing a caller will want.
* **`Nx`.** `ExMujoco.Tensor` is `{type, shape, binary}`, which is `Nx.from_binary/2` plus a
shape. A dependency on Nx in a physics package would be a dependency on a numerical
ecosystem the caller may not have, so the conversion is theirs to make and one line long.
* **Batched rollouts.** `mjbatch` exists in MuJoCo 4 and is a genuine match for a BEAM process
per environment; it is not in v0.1 because it is a large API and wants `mjSpec` first.
* **A `MuJoCo` plugin surface.** `mj_loadPluginLibrary` and the decoder plugins
(`libstl_decoder`, `libobj_decoder`) ship with the release; loading them from a path in
`priv/` is a small addition once someone needs a format the built-in decoder does not read.