Move mjSpec/mjModel/mjData management into a separate class.

PiperOrigin-RevId: 866327584
Change-Id: I72f6ec656e193f67dee22214fe01c517432f87e5
This commit is contained in:
Haroon Qureshi
2026-02-06 00:50:18 -08:00
committed by Copybara-Service
parent eb7aaa2868
commit 3e8a81383a
5 changed files with 251 additions and 124 deletions
+22 -111
View File
@@ -37,12 +37,12 @@
#include "experimental/platform/helpers.h"
#include "experimental/platform/imgui_widgets.h"
#include "experimental/platform/interaction.h"
#include "experimental/platform/model_holder.h"
#include "experimental/platform/picture_gui.h"
#include "experimental/platform/plugin.h"
#include "experimental/platform/renderer.h"
#include "experimental/platform/step_control.h"
#include "experimental/platform/window.h"
#include "user/user_resource.h"
namespace mujoco::studio {
@@ -120,20 +120,8 @@ App::App(Config config)
}
void App::ClearModel() {
if (model_) {
mj_deleteData(data_);
data_ = nullptr;
mj_deleteModel(model_);
model_ = nullptr;
if (spec_) {
mj_deleteSpec(spec_);
spec_ = nullptr;
}
}
model_holder_.reset();
window_->SetTitle("MuJoCo Studio");
step_control_.SetSpeed(100.f);
profiler_.Clear();
tmp_ = UiTempState();
@@ -152,138 +140,61 @@ void App::RequestModelReload() {
}
void App::InitEmptyModel() {
mjSpec* spec = mj_makeSpec();
mjModel* model = mj_compile(spec, nullptr);
InitModel(model, spec, nullptr, "", kEmptyModel);
model_holder_ = platform::ModelHolder::FromSpec(mj_makeSpec());
OnModelLoaded("", kEmptyModel);
}
void App::LoadModelFromFile(const std::string& filepath) {
mjModel* model = nullptr;
mjSpec* spec = nullptr;
mjVFS vfs;
mj_defaultVFS(&vfs);
const std::string resolved_file =
platform::ResolveFile(filepath, search_paths_);
if (resolved_file.empty()) {
SetLoadError("File not found: " + filepath);
return;
}
char err[1000] = "";
if (resolved_file.ends_with(".mjb")) {
model = mj_loadModel(resolved_file.c_str(), &vfs);
model_holder_ = platform::ModelHolder::FromFile(resolved_file);
if (model_holder_->ok()) {
OnModelLoaded(filepath, kModelFromFile);
UpdateFilePaths(resolved_file);
window_->SetTitle("MuJoCo Studio : " + filepath);
} else {
spec = mj_parse(resolved_file.c_str(), nullptr, &vfs, err, sizeof(err));
SetLoadError(std::string(model_holder_->error()));
}
if (err[0]) {
SetLoadError(err);
return;
}
InitModel(model, spec, &vfs, filepath, kModelFromFile);
UpdateFilePaths(resolved_file);
window_->SetTitle("MuJoCo Studio : " + filepath);
mj_deleteVFS(&vfs);
}
struct BufferProvider : public mjpResourceProvider {
BufferProvider(std::span<const std::byte> buffer) : buffer(buffer) {
mjp_defaultResourceProvider(this);
open = [](mjResource* resource) {
return 1;
};
read = [](mjResource* resource, const void** buffer) {
BufferProvider* self = (BufferProvider*)resource->provider;
*buffer = self->buffer.data();
return static_cast<int>(self->buffer.size());
};
close = [](mjResource* resource) {};
}
std::span<const std::byte> buffer;
};
void App::LoadModelFromBuffer(std::span<const std::byte> buffer,
std::string_view content_type,
std::string_view filename) {
mjModel* model = nullptr;
mjSpec* spec = nullptr;
mjVFS vfs;
mj_defaultVFS(&vfs);
char err[1000] = "";
if (content_type == "text/xml") {
const char* ptr = reinterpret_cast<const char*>(buffer.data());
spec = mj_parseXMLString(ptr, nullptr, err, sizeof(err));
} else if (content_type == "application/mjb") {
model = mj_loadModelBuffer(buffer.data(), buffer.size());
} else if (content_type == "application/zip") {
BufferProvider provider(buffer);
mjResource resource;
memset(&resource, 0, sizeof(mjResource));
resource.vfs = &vfs;
resource.provider = &provider;
resource.name = (char*)filename.data();
spec = mju_decodeResource(&resource, content_type.data(), &vfs);
model_holder_ =
platform::ModelHolder::FromBuffer(buffer, content_type, filename);
if (model_holder_->ok()) {
OnModelLoaded(std::string(filename), kModelFromFile);
} else {
SetLoadError("Unknown content type; expected text/xml or application/mjb");
return;
SetLoadError(std::string(model_holder_->error()));
}
if (err[0]) {
SetLoadError(err);
return;
}
InitModel(model, spec, &vfs, std::string(filename), kModelFromBuffer);
mj_deleteVFS(&vfs);
}
void App::InitModel(mjModel* model, mjSpec* spec, mjVFS* vfs,
std::string filename, ModelKind model_kind) {
void App::OnModelLoaded(std::string filename, ModelKind model_kind) {
model_path_ = std::move(filename);
if (model_kind_ == kEmptyModel) {
step_control_.Unpause();
}
ClearModel();
model_path_ = std::move(filename);
model_kind_ = model_kind;
if (model_kind_ == kEmptyModel) {
step_control_.Pause();
}
spec_ = spec;
model_ = model;
// If we have a spec but not a model, we need to compile the model from the spec.
if (spec_ && !model_) {
model_ = mj_compile(spec_, vfs);
if (!model_) {
SetLoadError("Error compiling model from spec.");
return;
}
}
if (!model_) {
mju_error("Error making data for model: %s", model_path_.c_str());
}
data_ = mj_makeData(model_);
if (!data_) {
mju_error("Error making data for model: %s", model_path_.c_str());
}
// Reset/reinitialize everything that depends on the new mjModel.
renderer_->Init(model_);
const int state_size = mj_stateSize(model_, mjSTATE_INTEGRATION);
mjModel* model = model_holder_->model();
renderer_->Init(model);
const int state_size = mj_stateSize(model, mjSTATE_INTEGRATION);
history_.Init(state_size);
// Initialize the speed based on the model's default real-time setting.
float min_error = FLT_MAX;
const float desired = mju_log(100 * model_->vis.global.realtime);
const float desired = mju_log(100 * model->vis.global.realtime);
for (int i = 0; i < kPercentRealTime.size(); ++i) {
const float speed = std::stof(kPercentRealTime[i]);
const float error = mju_abs(mju_log(speed) - desired);
+10 -13
View File
@@ -27,6 +27,7 @@
#include <mujoco/mujoco.h>
#include "experimental/platform/gui.h"
#include "experimental/platform/interaction.h"
#include "experimental/platform/model_holder.h"
#include "experimental/platform/picture_gui.h"
#include "experimental/platform/renderer.h"
#include "experimental/platform/sim_history.h"
@@ -168,8 +169,7 @@ class App {
// Updates the currently loaded model to the given model. If model is null,
// then compile the spec to a model.
void InitModel(mjModel* model, mjSpec* spec, mjVFS* vfs, std::string filename,
ModelKind model_kind);
void OnModelLoaded(std::string filename, ModelKind model_kind);
void SetLoadError(std::string error);
void UpdateFilePaths(const std::string& resolved_path);
@@ -207,12 +207,13 @@ class App {
float GetExpectedLabelWidth();
std::vector<const char*> GetCameraNames();
mjSpec* spec() { return spec_; }
mjModel* model() { return model_; }
mjData* data() { return data_; }
bool has_spec() const { return spec_ != nullptr; }
bool has_model() const { return model_ != nullptr; }
bool has_data() const { return data_ != nullptr; }
mjSpec* spec() { return model_holder_->spec(); }
mjModel* model() { return model_holder_->model(); }
mjData* data() { return model_holder_->data(); }
bool has_spec() const { return model_holder_ && model_holder_->spec(); }
bool has_model() const { return model_holder_ && model_holder_->model(); }
bool has_data() const { return model_holder_ && model_holder_->data(); }
std::string ini_path_;
std::string model_name_; // Used if model_kind_ is kModelFromBuffer.
@@ -224,15 +225,11 @@ class App {
std::unique_ptr<platform::Window> window_;
std::unique_ptr<platform::Renderer> renderer_;
std::unique_ptr<platform::ModelHolder> model_holder_;
platform::StepControl step_control_;
platform::SimProfiler profiler_;
platform::SimHistory history_;
std::vector<std::string> search_paths_;
mjSpec* spec_ = nullptr;
mjModel* model_ = nullptr;
mjData* data_ = nullptr;
std::vector<std::byte> pixels_;
mjvCamera camera_;