Introduce mjpEncoder plugin architecture

Add a new mjpEncoder plugin type mirroring the existing mjpDecoder pattern.
Encoders serialize an mjSpec + mjModel to an mjResource for a given format.

New API functions:
- mjp_registerEncoder: globally register an encoder
- mjp_defaultEncoder: zero-initialize an encoder struct
- mjp_findEncoder: look up an encoder by filename extension or content type

The mjfEncode callback takes (mjSpec*, mjModel*, mjVFS*, mjResource*) and
returns 0 on success. Writing to mjResource keeps symmetry with the decoder
reading from mjResource and leaves the door open for writable resource providers.

PiperOrigin-RevId: 889187898
Change-Id: I180771b2255b91dea188ac5e2cdc3a8f0fb85364
This commit is contained in:
Sam Haves
2026-03-25 05:34:48 -07:00
committed by Copybara-Service
parent 2d33b50243
commit f5d3ce3451
15 changed files with 617 additions and 0 deletions
+65
View File
@@ -1580,6 +1580,29 @@ used for opening and reading resources.
.. mujoco-include:: mjpResourceProvider
.. _mjpDecoder:
mjpDecoder
~~~~~~~~~~~~~~~~~~~
This data structure defines a decoder. It contains a set of callbacks used for decoding :ref:`mjResource`
into :ref:`mjSpec`.
.. mujoco-include:: mjpDecoder
.. _mjpEncoder:
mjpEncoder
~~~~~~~~~~~~~~~~~~~
This data structure defines an encoder. It contains a set of callbacks used for encoding of :ref:`mjSpec` and
:ref:`mjModel` into :ref:`mjResource`.
.. mujoco-include:: mjpEncoder
.. _tyFunction:
Function types
@@ -1757,6 +1780,48 @@ This callback is for checking if a resource was modified since it was last read.
Returns positive value if the resource was modified since last open, 0 if resource was not modified,
and negative value if inconclusive.
.. _mjfDecode:
mjfDecode
~~~~~~~~~
.. code-block:: C
typedef mjSpec* (*mjfDecode)(mjResource* resource, const mjVFS* vfs);
This callback is given an opened resource, and is responsible for decoding it into a :ref:`mjSpec`.
Ownership of the resource and the returned spec is responsibility of the caller.
When decoding fails, the callback should return NULL.
.. _mjfCanDecode:
mjfCanDecode
~~~~~~~~~~~~
.. code-block:: C
typedef int (*mjfCanDecode)(const mjResource* resource);
This callback is given an opened resource, and is responsible for returning true if the resource can
be decoded by the :ref:`mjpDecoder<mjpDecoder>`.
.. _mjfEncode:
mjfEncode
~~~~~~~~~
.. code-block:: C
typedef int (*mjfEncode)(const mjSpec* s, const mjModel* m, const mjVFS* vfs,
mjResource* resource);
This callback populates the :ref:`mjResource<mjResource>` `data` member with bytes representing the
given spec in the format associated with the owning plugin. This may be called with the associated
compiled :ref:`mjModel`.
.. _tyNotes:
+46
View File
@@ -59,6 +59,19 @@ Parse spec from a file.
*Nullable:* ``vfs``, ``error``
.. _mj_encode:
`mj_encode <#mj_encode>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mj_encode
Encode spec/model to a file using a registered encoder.
Returns the number of bytes written on success, -1 on failure.
*Nullable:* ``m``, ``vfs``, ``error``
.. _mj_compile:
`mj_compile <#mj_compile>`__
@@ -3255,6 +3268,39 @@ Return the resource provider with the prefix that matches against the resource n
If no match, return NULL.
.. _mjp_registerEncoder:
`mjp_registerEncoder <#mjp_registerEncoder>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjp_registerEncoder
Globally register an encoder. This function is thread-safe.
If an identical mjpEncoder is already registered, this function does nothing.
If a non-identical mjpEncoder with the same name is already registered, an mju_error is raised.
.. _mjp_defaultEncoder:
`mjp_defaultEncoder <#mjp_defaultEncoder>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjp_defaultEncoder
Set default resource encoder definition.
.. _mjp_findEncoder:
`mjp_findEncoder <#mjp_findEncoder>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mjp_findEncoder
Return the encoder that matches against the content type or filename extension.
If no match, return NULL.
.. _Thread:
Threads
+4
View File
@@ -25,6 +25,10 @@ General
The polynomial order is defined by the new constant :ref:`mjNPOLY<glNumericSizes>`. A future breaking C-API change
may unify the linear and higher-order coefficients into a single array.
- Introduced :ref:`mjpEncoder`, the counterpart to :ref:`mjpDecoder` for encoding of :ref:`mjSpec` and :ref:`mjModel` into :ref:`mjResource`.
- Added :ref:`mj_encode`, :ref:`mjp_registerEncoder`, :ref:`mjp_defaultEncoder`, and :ref:`mjp_findEncoder`.
.. admonition:: Breaking API changes
:class: attention
+13
View File
@@ -1686,6 +1686,13 @@ struct mjpDecoder {
// for cleaning it up
};
typedef struct mjpDecoder mjpDecoder;
struct mjpEncoder {
const char* content_type;
const char* extension;
mjfEncode encode;
mjfCloseResource close_resource;
};
typedef struct mjpEncoder mjpEncoder;
typedef enum mjtPluginCapabilityBit_ {
mjPLUGIN_ACTUATOR = 1<<0, // actuator forces
mjPLUGIN_SENSOR = 1<<1, // sensor measurements
@@ -3155,6 +3162,9 @@ mjSpec* mj_parseXML(const char* filename, const mjVFS* vfs, char* error, int err
mjSpec* mj_parseXMLString(const char* xml, const mjVFS* vfs, char* error, int error_sz);
mjSpec* mj_parse(const char* filename, const char* content_type,
const mjVFS* vfs, char* error, int error_sz);
int mj_encode(const mjSpec* s, const mjModel* m, const char* filename,
const char* content_type, const mjVFS* vfs, char* error,
int error_sz);
mjModel* mj_compile(mjSpec* s, const mjVFS* vfs);
int mj_copyBack(mjSpec* s, const mjModel* m);
int mj_recompile(mjSpec* s, const mjVFS* vfs, mjModel* m, mjData* d);
@@ -3591,6 +3601,9 @@ const mjpResourceProvider* mjp_getResourceProviderAtSlot(int slot);
void mjp_registerDecoder(const mjpDecoder* decoder);
void mjp_defaultDecoder(mjpDecoder* decoder);
const mjpDecoder* mjp_findDecoder(const mjResource* resource, const char* content_type);
void mjp_registerEncoder(const mjpEncoder* encoder);
void mjp_defaultEncoder(mjpEncoder* encoder);
const mjpEncoder* mjp_findEncoder(const char* filename, const char* content_type);
mjResource* mju_openResource(const char* dir, const char* name,
const mjVFS* vfs, char* error, size_t nerror);
void mju_closeResource(mjResource* resource);