diff --git a/doc/changelog.rst b/doc/changelog.rst index e913c653..3b91f6db 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -30,6 +30,8 @@ Actuation - :ref:`mjsActuator` gained ``velrange`` and ``ffrange`` fields, changing its size and layout. The :ref:`mjtGain` and :ref:`mjtDyn` enums gained ``pid`` members, shifting the values of ``mjGAIN_USER`` and ``mjDYN_USER``. + - :ref:`mjResource` gained an ``args`` field (changing its size and layout), used to hold optional extra encoding and + decoding arguments formatted as URI query parameters (separated by ``&``). Engine ^^^^^^ diff --git a/doc/includes/references.h b/doc/includes/references.h index 0d07e4c3..6c8ebc11 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -1252,6 +1252,8 @@ typedef struct mjResource_ { mjVFS* vfs; // pointer to the VFS char timestamp[512]; // timestamp of the resource const struct mjpResourceProvider* provider; // pointer to the provider + const char* args; // resource arguments/hints, URI query format key=val&... + // (optional) } mjResource; typedef struct mjpResourceProvider { const char* prefix; // prefix for match against a resource name diff --git a/include/mujoco/mjplugin.h b/include/mujoco/mjplugin.h index 4907f274..9a13f6a8 100644 --- a/include/mujoco/mjplugin.h +++ b/include/mujoco/mjplugin.h @@ -30,6 +30,8 @@ typedef struct mjResource_ { mjVFS* vfs; // pointer to the VFS char timestamp[512]; // timestamp of the resource const struct mjpResourceProvider* provider; // pointer to the provider + const char* args; // resource arguments/hints, URI query format key=val&... + // (optional) } mjResource; // callback for opening a resource, returns zero on failure. diff --git a/test/plugin/decoder/decoder_test.cc b/test/plugin/decoder/decoder_test.cc index 02ca6513..1aed947c 100644 --- a/test/plugin/decoder/decoder_test.cc +++ b/test/plugin/decoder/decoder_test.cc @@ -16,6 +16,10 @@ #include +#include +#include +#include + #include #include #include @@ -118,5 +122,45 @@ TEST_F(DecoderPluginTest, CanDecode) { mj_deleteSpec(spec); } +TEST_F(DecoderPluginTest, DecodeWithResourceArgs) { + static auto decode_args_fn = + +[](mjResource* resource, const mjVFS* vfs) -> mjSpec* { + mjSpec* s = MakeSimpleSpec(); + if (resource && resource->args) { + std::string_view args_view(resource->args); + size_t pos = args_view.find("size="); + if (pos != std::string_view::npos) { + mjsElement* elem = mjs_firstElement(s, mjOBJ_GEOM); + mjsGeom* geom = mjs_asGeom(elem); + if (geom) { + geom->size[0] = std::atof(args_view.data() + pos + 5); + } + } + } + return s; + }; + + mjpDecoder decoder; + mjp_defaultDecoder(&decoder); + decoder.content_type = "model/argsformat"; + decoder.extension = ".argsformat"; + decoder.can_decode = +[](const mjResource* r) -> int { return 1; }; + decoder.decode = decode_args_fn; + mjp_registerDecoder(&decoder); + + mjResource resource; + std::memset(&resource, 0, sizeof(resource)); + resource.name = const_cast("test.argsformat"); + resource.args = "size=42.0&foo=bar"; + + mjSpec* spec = mju_decodeResource(&resource, "model/argsformat", nullptr); + ASSERT_THAT(spec, testing::NotNull()); + mjsElement* elem = mjs_firstElement(spec, mjOBJ_GEOM); + mjsGeom* geom = mjs_asGeom(elem); + ASSERT_THAT(geom, testing::NotNull()); + EXPECT_DOUBLE_EQ(geom->size[0], 42.0); + mj_deleteSpec(spec); +} + } // namespace } // namespace mujoco diff --git a/test/plugin/encoder/encoder_test.cc b/test/plugin/encoder/encoder_test.cc index fec30d99..45309689 100644 --- a/test/plugin/encoder/encoder_test.cc +++ b/test/plugin/encoder/encoder_test.cc @@ -130,5 +130,29 @@ TEST_F(EncoderPluginTest, EncodeModel) { mj_deleteSpec(spec); } +TEST_F(EncoderPluginTest, EncodeWithResourceArgs) { + mjSpec* spec = mj_makeSpec(); + mjModel* model = mj_compile(spec, nullptr); + ASSERT_THAT(model, testing::NotNull()); + + const mjpEncoder* found = mjp_findEncoder("output.fakeformat", nullptr); + ASSERT_THAT(found, testing::NotNull()); + + mjResource resource = {}; + resource.name = const_cast("output.fakeformat"); + resource.args = "format=binary&compression=9"; + + int result = found->encode(spec, model, nullptr, &resource); + EXPECT_GT(result, 0); + + auto* output = static_cast(resource.data); + ASSERT_THAT(output, testing::NotNull()); + EXPECT_STREQ(resource.args, "format=binary&compression=9"); + + found->close_resource(&resource); + mj_deleteModel(model); + mj_deleteSpec(spec); +} + } // namespace } // namespace mujoco