From 95a4b1c551cdb29dd2fda4627720093eb95ee96c Mon Sep 17 00:00:00 2001 From: Levi Burner Date: Tue, 4 Oct 2022 14:37:06 -0400 Subject: [PATCH] simulate python: fix cmake, glfw usage, update Python version of physics loop --- python/mujoco/CMakeLists.txt | 44 +++++++++--------- python/mujoco/constants.cc | 4 -- python/mujoco/raw.h | 4 -- python/mujoco/simulate.cc | 70 +++++++++++++--------------- python/mujoco/simulate.py | 81 +++++++++++++++++++++----------- python/setup.py | 9 ++-- simulate/CMakeLists.txt | 44 +++++++++++++----- simulate/glfw_dispatch.cc | 5 +- simulate/glfw_dispatch.h | 1 + simulate/main.cc | 12 +++-- simulate/simulate.cc | 90 ++++++++++++++++++------------------ simulate/simulate.h | 29 +++++++----- 12 files changed, 219 insertions(+), 174 deletions(-) diff --git a/python/mujoco/CMakeLists.txt b/python/mujoco/CMakeLists.txt index dbe7b1fc..07757aec 100644 --- a/python/mujoco/CMakeLists.txt +++ b/python/mujoco/CMakeLists.txt @@ -62,7 +62,6 @@ add_compile_options("${MUJOCO_HARDEN_COMPILE_OPTIONS}") add_link_options("${MUJOCO_HARDEN_LINK_OPTIONS}") find_package(Python3 COMPONENTS Interpreter Development) -find_package(glfw3 3.3 REQUIRED) include(FindOrFetch) @@ -116,37 +115,37 @@ if(NOT TARGET mujoco) endif() endif() -# ==================== MJSIMULATE LIBRARY ========================================== -if(NOT TARGET mjsimulate) - find_library(MJSIMULATE_LIBRARY mjsimulate mjsimulate HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED) - find_path(MJSIMULATE_INCLUDE mujoco/simulate.h mujoco/uitools.h mujoco/array_safety.h HINTS ${MUJOCO_INCLUDE_DIR} REQUIRED) - message("MuJoCo Simulate is at ${MJSIMULATE_LIBRARY}") - message("MuJoCo Simulate headers are at ${MJSIMULATE_INCLUDE}") - add_library(mjsimulate SHARED IMPORTED) +# ==================== LIBSIMULATE LIBRARY ========================================== +if(NOT TARGET libsimulate) + find_library(LIBSIMULATE_LIBRARY libsimulate libsimulate HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED) + find_path(LIBSIMULATE_INCLUDE mujoco/simulate.h mujoco/uitools.h mujoco/array_safety.h HINTS ${MUJOCO_INCLUDE_DIR} REQUIRED) + message("MuJoCo Simulate is at ${LIBSIMULATE_LIBRARY}") + message("MuJoCo Simulate headers are at ${LIBSIMULATE_INCLUDE}") + add_library(libsimulate SHARED IMPORTED) if(WIN32) - set_target_properties(mjsimulate PROPERTIES IMPORTED_IMPLIB "${MJSIMULATE_LIBRARY}") + set_target_properties(libsimulate PROPERTIES IMPORTED_IMPLIB "${LIBSIMULATE_LIBRARY}") else() - set_target_properties(mjsimulate PROPERTIES IMPORTED_LOCATION "${MJSIMULATE_LIBRARY}") + set_target_properties(libsimulate PROPERTIES IMPORTED_LOCATION "${LIBSIMULATE_LIBRARY}") endif() - target_include_directories(mjsimulate INTERFACE "${MJSIMULATE_INCLUDE}") + target_include_directories(libsimulate INTERFACE "${LIBSIMULATE_INCLUDE}") if(APPLE) execute_process( - COMMAND otool -XD ${MJSIMULATE_LIBRARY} + COMMAND otool -XD ${LIBSIMULATE_LIBRARY} COMMAND head -n 1 COMMAND xargs dirname COMMAND xargs echo -n - OUTPUT_VARIABLE MJSIMULATE_INSTALL_NAME_DIR + OUTPUT_VARIABLE LIBSIMULATE_INSTALL_NAME_DIR ) - set_target_properties(mjsimulate PROPERTIES INSTALL_NAME_DIR "${MJSIMULATE_INSTALL_NAME_DIR}") + set_target_properties(libsimulate PROPERTIES INSTALL_NAME_DIR "${LIBSIMULATE_INSTALL_NAME_DIR}") elseif(UNIX) execute_process( - COMMAND objdump -p ${MJSIMULATE_LIBRARY} + COMMAND objdump -p ${LIBSIMULATE_LIBRARY} COMMAND grep SONAME COMMAND grep -Po [^\\s]+$ COMMAND xargs echo -n - OUTPUT_VARIABLE MJSIMULATE_SONAME + OUTPUT_VARIABLE LIBSIMULATE_SONAME ) - set_target_properties(mjsimulate PROPERTIES IMPORTED_SONAME "${MJSIMULATE_SONAME}") + set_target_properties(libsimulate PROPERTIES IMPORTED_SONAME "${LIBSIMULATE_SONAME}") endif() endif() @@ -266,7 +265,7 @@ target_link_libraries(errors_header INTERFACE crossplatform func_wrap mujoco) add_library(raw INTERFACE) target_sources(raw INTERFACE raw.h) set_target_properties(raw PROPERTIES PUBLIC_HEADER raw.h) -target_link_libraries(raw INTERFACE mujoco mjsimulate) +target_link_libraries(raw INTERFACE mujoco) add_library(structs_header INTERFACE) target_sources( @@ -344,7 +343,7 @@ target_link_libraries( ) mujoco_pybind11_module(_constants constants.cc) -target_link_libraries(_constants PRIVATE mujoco mjsimulate) +target_link_libraries(_constants PRIVATE mujoco) mujoco_pybind11_module(_enums enums.cc) target_link_libraries( @@ -408,10 +407,9 @@ target_link_libraries( mujoco_pybind11_module(_simulate simulate.cc) target_link_libraries( _simulate - PRIVATE mjsimulate + PRIVATE libsimulate mujoco raw - glfw structs_header) target_link_options(_simulate PRIVATE -Wl,-no-as-needed) @@ -426,7 +424,7 @@ set(LIBRARIES_FOR_WHEEL "$" "$" "$" - "$" + "$" ) if(MUJOCO_PYTHON_MAKE_WHEEL) @@ -455,6 +453,6 @@ if(MUJOCO_PYTHON_MAKE_WHEEL) _simulate _structs mujoco - mjsimulate + libsimulate ) endif() diff --git a/python/mujoco/constants.cc b/python/mujoco/constants.cc index 3fcbd279..2ffe73bb 100644 --- a/python/mujoco/constants.cc +++ b/python/mujoco/constants.cc @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -79,9 +78,6 @@ PYBIND11_MODULE(_constants, pymodule) { // from mujoco.h X(mjVERSION_HEADER); - // from simulate.h - X(mujoco::Simulate::kMaxFilenameLength); - #undef X pymodule.attr("mjDISABLESTRING") = MakeTuple(mjDISABLESTRING); pymodule.attr("mjENABLESTRING") = MakeTuple(mjENABLESTRING); diff --git a/python/mujoco/raw.h b/python/mujoco/raw.h index 3399540a..5aa146f4 100644 --- a/python/mujoco/raw.h +++ b/python/mujoco/raw.h @@ -19,7 +19,6 @@ #include #include #include -#include // Type aliases for MuJoCo C structs to allow us refer to consistently refer // to them under the "raw" namespace. @@ -56,9 +55,6 @@ using MjvOption = ::mjvOption; using MjvScene = ::mjvScene; using MjvFigure = ::mjvFigure; -// From simulate.h -using Simulate = ::mujoco::Simulate; - } // namespace mujoco::raw #endif // MUJOCO_PYTHON_RAW_H_ diff --git a/python/mujoco/simulate.cc b/python/mujoco/simulate.cc index 46540e8b..28182ea3 100644 --- a/python/mujoco/simulate.cc +++ b/python/mujoco/simulate.cc @@ -34,107 +34,101 @@ namespace { namespace py = ::pybind11; -const auto simulate_doc = R"( -Python wrapper for the Simulate class -)"; - -// We define SimulateWrapper here instead of in structs because -// we do not want to make _structs dependent on gflw - PYBIND11_MODULE(_simulate, pymodule) { namespace py = ::pybind11; - py::class_(pymodule, "Simulate") + py::class_(pymodule, "Simulate") .def(py::init<>()) .def("renderloop", - [](mujoco::raw::Simulate& simulate) { + [](mujoco::Simulate& simulate) { simulate.renderloop(); }, py::call_guard()) .def("load", - [](mujoco::raw::Simulate& simulate, std::string filename, const MjModelWrapper& m, MjDataWrapper& d, bool delete_old_m_d) { + [](mujoco::Simulate& simulate, std::string filename, const MjModelWrapper& m, MjDataWrapper& d) { const raw::MjModel* m_ptr = m.get(); raw::MjData* d_ptr = d.get(); - simulate.load(filename.c_str(), (mjModel*)m_ptr, d_ptr, delete_old_m_d); + simulate.load(filename.c_str(), (mjModel*)m_ptr, d_ptr); }, py::call_guard()) - .def("applyposepertubations", &mujoco::raw::Simulate::applyposepertubations) - .def("applyforceperturbations", &mujoco::raw::Simulate::applyforceperturbations) + .def("applyposepertubations", &mujoco::Simulate::applyposepertubations) + .def("applyforceperturbations", &mujoco::Simulate::applyforceperturbations) .def("lock", // TODO wrap mutex properly as as seperate pybind11 object? - [](mujoco::raw::Simulate& simulate) { + [](mujoco::Simulate& simulate) { simulate.mtx.lock(); }, py::call_guard()) .def("unlock", - [](mujoco::raw::Simulate& simulate) { + [](mujoco::Simulate& simulate) { simulate.mtx.unlock(); }, py::call_guard()) - .def_readwrite("ctrlnoisestd", &mujoco::raw::Simulate::ctrlnoisestd) - .def_readwrite("ctrlnoiserate", &mujoco::raw::Simulate::ctrlnoiserate) - .def_readwrite("slow_down", &mujoco::raw::Simulate::slow_down) - .def_readwrite("speed_changed", &mujoco::raw::Simulate::speed_changed) - .def("getrefreshRate", - [](mujoco::raw::Simulate& simulate) { - return simulate.vmode.refreshRate; - }) + .def_readwrite("ctrlnoisestd", &mujoco::Simulate::ctrlnoisestd) + .def_readwrite("ctrlnoiserate", &mujoco::Simulate::ctrlnoiserate) + + .def_readwrite("realtimeindex", &mujoco::Simulate::realTimeIndex) + .def_readwrite("speedchanged", &mujoco::Simulate::speedChanged) + .def_readwrite("measuredslowdown", &mujoco::Simulate::measuredSlowdown) + .def_readwrite("refreshrate", &mujoco::Simulate::refreshRate) + + .def_readwrite("busywait", &mujoco::Simulate::busywait) + .def_readwrite("run", &mujoco::Simulate::run) - .def_readwrite("busywait", &mujoco::raw::Simulate::busywait) - .def_readwrite("run", &mujoco::raw::Simulate::run) - //.def_readwrite("exitrequest", &mujoco::raw::Simulate::exitrequest) .def("getexitrequest", - [](mujoco::raw::Simulate& simulate) { + [](mujoco::Simulate& simulate) { return simulate.exitrequest.load(); } ) .def("setexitrequest", - [](mujoco::raw::Simulate& simulate, bool exitrequest) { + [](mujoco::Simulate& simulate, bool exitrequest) { simulate.exitrequest.store(exitrequest); } ) - // .def_readwrite("uiloadrequest", &mujoco::raw::Simulate::uiloadrequest) + .def("getuiloadrequest", - [](mujoco::raw::Simulate& simulate) { + [](mujoco::Simulate& simulate) { return simulate.uiloadrequest.load(); } ) .def("setuiloadrequest", - [](mujoco::raw::Simulate& simulate, int uiloadrequest) { + [](mujoco::Simulate& simulate, int uiloadrequest) { simulate.uiloadrequest.store(uiloadrequest); } ) .def("uiloadrequest_fetch_sub", - [](mujoco::raw::Simulate& simulate, int arg) { + [](mujoco::Simulate& simulate, int arg) { simulate.uiloadrequest.fetch_sub(arg); } ) - // .def_readwrite("droploadrequest", &mujoco::raw::Simulate::droploadrequest) + .def("getdroploadrequest", - [](mujoco::raw::Simulate& simulate) { + [](mujoco::Simulate& simulate) { return simulate.droploadrequest.load(); } ) .def("setdroploadrequest", - [](mujoco::raw::Simulate& simulate, bool droploadrequest) { + [](mujoco::Simulate& simulate, bool droploadrequest) { simulate.droploadrequest.store(droploadrequest); } ) .def("getdropfilename", - [](mujoco::raw::Simulate& simulate) { + [](mujoco::Simulate& simulate) { return (char*)simulate.dropfilename; } ) .def("getfilename", - [](mujoco::raw::Simulate& simulate) { + [](mujoco::Simulate& simulate) { return (char*)simulate.filename; } ) .def("setloadError", - [](mujoco::raw::Simulate& simulate, std::string& loadError) { + [](mujoco::Simulate& simulate, std::string& loadError) { strncpy(simulate.loadError, loadError.c_str(), simulate.kMaxFilenameLength); } ); + + pymodule.def("setglfwdlhandle", [](std::uintptr_t dlhandle) { mujoco::setglfwdlhandle(reinterpret_cast(dlhandle)); }); } } // namespace diff --git a/python/mujoco/simulate.py b/python/mujoco/simulate.py index d4c37f14..42142b3d 100644 --- a/python/mujoco/simulate.py +++ b/python/mujoco/simulate.py @@ -18,6 +18,9 @@ import mujoco from mujoco import _simulate import glfw +from glfw import _glfw +_simulate.setglfwdlhandle(_glfw._handle) + import numpy as np import ctypes @@ -29,6 +32,14 @@ class Simulate(_simulate.Simulate): def __init__(self): super().__init__() + # logarithmically spaced realtime slow-down coefficients (percent) + self.percentrealtime = [ + 100, 80, 66, 50, 40, 33, 25, 20, 16, 13, + 10, 8, 6.6, 5.0, 4, 3.3, 2.5, 2, 1.6, 1.3, + 1, .8, .66, .5, .4, .33, .25, .2, .16, .13, + .1 + ] + def _get_refreshRate(self): return self.getrefreshRate() @@ -107,14 +118,14 @@ def load_and_step_model(m, d, simulate, filename, preload_callback=None, load_ca if mnew is not None: dnew = mujoco.MjData(mnew) - simulate.load(filename, mnew, dnew, False) + simulate.load(filename, mnew, dnew) mujoco.mj_forward(mnew, dnew) else: mnew = None dnew = None if load_callback is not None: - load_callback(mnew, dnew, loadError) + load_callback(mnew, dnew) return mnew, dnew, loadError @@ -132,11 +143,11 @@ def run_physics_loop(simulate, preload_callback=None, load_callback=None, file=N # constants syncmisalign = 0.1 # maximum time mis-alignment before re-sync - refreshfactor = 0.7 # fraction of refresh available for simulation + simrefreshfraction = 0.7 # fraction of refresh available for simulation # cpu-sim synchronization point - cpusync = 0.0 - simsync = 0.0 + synccpu = 0.0 + syncsim = 0.0 # run until asked to exit while not simulate.exitrequest: @@ -144,7 +155,6 @@ def run_physics_loop(simulate, preload_callback=None, load_callback=None, file=N simulate.droploadrequest = 0 m_, d_, loadError = load_and_step_model(m, d, simulate, simulate.dropfilename, preload_callback, load_callback) - if m_ is not None: m = m_ d = d_ @@ -178,28 +188,36 @@ def run_physics_loop(simulate, preload_callback=None, load_callback=None, file=N # running if simulate.run != 0: # record cpu time at start of iteration - tmstart = glfw.get_time() + startcpu = glfw.get_time() + + elapsedcpu = startcpu - synccpu + elapsedsim = d.time - syncsim # inject noise if simulate.ctrlnoisestd != 0.0: - # convert rate and scale to discrete time given current timestep + # convert rate and scale to discrete time (Ornstein–Uhlenbeck) rate = math.exp(-m.opt.timestep / simulate.ctrlnoiserate) scale = simulate.ctrlnoisestd * math.sqrt(1-rate*rate) for i in range(m.nu): # update noise ctrlnoise[i] = rate * ctrlnoise[i] + scale * mujoco.mju_standardNormal(None) + # apply noise d.ctrl[i] = ctrlnoise[i] - # out-of-sync (for any reason) - offset = abs((d.time*simulate.slow_down - simsync) - (tmstart - cpusync)) - if (d.time*simulate.slow_down < simsync or tmstart < cpusync or cpusync == 0.0 or - offset > syncmisalign*simulate.slow_down or simulate.speed_changed): + # requested slow-down factor + slowdown = 100 / simulate.percentrealtime[simulate.realtimeindex] + + # misalignment condition: distance from target sim time is bigger than syncmisalign + misaligned = abs(elapsedcpu/slowdown - elapsedsim) > syncmisalign + + # out-of-sync (for any reason): reset sync times, step + if elapsedsim < 0 or elapsedcpu < 0 or synccpu == 0 or misaligned or simulate.speedchanged: # re-sync - cpusync = tmstart - simsync = d.time*simulate.slow_down - simulate.speed_changed = False + synccpu = startcpu + syncsim = d.time + simulate.speedchanged = False # clear old perturbations, apply new d.xfrc_applied[:, :] = 0 @@ -209,21 +227,30 @@ def run_physics_loop(simulate, preload_callback=None, load_callback=None, file=N # run single step, let next iteration deal with timing mujoco.mj_step(m, d) - # in-sync + # in-sync: step until ahead of cpu else: - while ((d.time*simulate.slow_down - simsync) < (glfw.get_time() - cpusync) and - (glfw.get_time() - tmstart) < (refreshfactor/simulate.refreshRate)): + measured = False + prevsim = d.time + refreshtime = simrefreshfraction/simulate.refreshrate; + + # step while sim lags behind cpu and within refreshtime + while (((d.time - syncsim)*slowdown < (glfw.get_time() - synccpu)) + and ((glfw.get_time() - startcpu) < refreshtime)): + # measure slowdown before first step + if not measured and elapsedsim: + simulate.measuredslowdown = elapsedcpu / elapsedsim + measured = True + # clear old perturbations, apply new d.xfrc_applied[:, :] = 0 simulate.applyposepertubations(0) # move mocap bodies only simulate.applyforceperturbations() - # run mj_step - prevtm = d.time*simulate.slow_down + # call mj_step mujoco.mj_step(m, d) - # break on reset - if d.time*simulate.slow_down < prevtm: + # break if reset + if d.time < prevsim: break # paused @@ -237,13 +264,14 @@ def run_physics_loop(simulate, preload_callback=None, load_callback=None, file=N # end exclusive access simulate.unlock() -def run_simulate_and_physics(file=None, preload_callback=None, load_callback=None): +def run_simulate_and_physics(file=None, preload_callback=None, load_callback=None, init=True, terminate=True): # simulate object encapsulates the UI simulate = Simulate() # init GLFW - if not glfw.init(): - raise mujoco.FatalError('could not initialize GLFW') + if init: + if not glfw.init(): + raise mujoco.FatalError('could not initialize GLFW') # if m is not None: physics_thread = threading.Thread(target=lambda: run_physics_loop(simulate, preload_callback=preload_callback, load_callback=load_callback, file=file)) @@ -253,4 +281,5 @@ def run_simulate_and_physics(file=None, preload_callback=None, load_callback=Non simulate.renderloop() physics_thread.join() - glfw.terminate() + if terminate: + glfw.terminate() diff --git a/python/setup.py b/python/setup.py index e0380108..9223208d 100644 --- a/python/setup.py +++ b/python/setup.py @@ -63,11 +63,11 @@ def get_mujoco_lib_pattern(): def get_external_lib_patterns(): if platform.system() == 'Windows': - return ['mujoco.dll', 'mjsimulate.dll'] + return ['mujoco.dll', 'libsimulate.dll'] elif platform.system() == 'Darwin': - return ['libmujoco.*.dylib', 'libmjsimulate.*.dylib'] + return ['libmujoco.*.dylib', 'liblibsimulate.*.dylib'] else: - return ['libmujoco.so.*', 'libmjsimulate.so*'] + return ['libmujoco.so.*', 'liblibsimulate.so*'] def start_and_end(iterable): it = iter(iterable) @@ -318,7 +318,8 @@ setup( 'libmujoco*.so.*', 'mujoco.dll', 'include/mujoco/*.h', - 'libmjsimulate*.so*', + 'liblibsimulate.*.dylib', + 'liblibsimulate*.so*', ]), }, ) diff --git a/simulate/CMakeLists.txt b/simulate/CMakeLists.txt index 9a74fd77..70ddb5b3 100644 --- a/simulate/CMakeLists.txt +++ b/simulate/CMakeLists.txt @@ -97,8 +97,8 @@ if(NOT TARGET lodepng) endif() endif() -# Simulate library -add_library(libsimulate STATIC) +# Simulate shared library +add_library(libsimulate SHARED simulate.cc uitools.cc glfw_dispatch.cc) add_library(mujoco::libsimulate ALIAS libsimulate) target_sources( @@ -106,19 +106,16 @@ target_sources( PUBLIC simulate.h array_safety.h glfw_dispatch.h - glfw_dispatch.cc - simulate.cc uitools.h - uitools.cc ) target_include_directories(libsimulate PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_compile_definitions(libsimulate PUBLIC MJSIMULATE_STATIC) +target_compile_definitions(libsimulate PUBLIC LIBSIMULATE_DLL_EXPORTS mjGLFW_DYNAMIC_SYMBOLS) target_compile_options(libsimulate PUBLIC ${MUJOCO_SIMULATE_COMPILE_OPTIONS}) -target_link_libraries(libsimulate PUBLIC glfw lodepng mujoco::mujoco) +target_link_libraries(libsimulate PUBLIC lodepng glfw mujoco::mujoco) target_link_options(libsimulate PRIVATE ${MUJOCO_SIMULATE_LINK_OPTIONS}) set_target_properties( - mjsimulate PROPERTIES VERSION "${mujoco_VERSION}" PUBLIC_HEADER "simulate.h" + libsimulate PROPERTIES VERSION "${mujoco_VERSION}" PUBLIC_HEADER "simulate.h" ) if(APPLE) @@ -126,6 +123,31 @@ if(APPLE) target_link_libraries(libsimulate PUBLIC "-framework Cocoa") endif() +# Simulate static library +add_library(libsimulatestatic STATIC simulate.cc uitools.cc glfw_dispatch.cc) +add_library(mujoco::libsimulatestatic ALIAS libsimulatestatic) +target_sources( + libsimulatestatic + PUBLIC simulate.h + array_safety.h + glfw_dispatch.h + uitools.h +) +target_include_directories(libsimulatestatic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_definitions(libsimulatestatic PUBLIC LIBSIMULATE_STATIC) +target_compile_options(libsimulatestatic PUBLIC ${MUJOCO_SIMULATE_COMPILE_OPTIONS}) +target_link_libraries(libsimulatestatic PUBLIC lodepng glfw mujoco::mujoco) +target_link_options(libsimulatestatic PRIVATE ${MUJOCO_SIMULATE_LINK_OPTIONS}) + +set_target_properties( + libsimulatestatic PROPERTIES VERSION "${mujoco_VERSION}" PUBLIC_HEADER "simulate.h" +) + +if(APPLE) + target_sources(libsimulatestatic PRIVATE macos_save.mm) + target_link_libraries(libsimulatestatic PUBLIC "-framework Cocoa") +endif() + # Build simulate executable if(APPLE) set(SIMULATE_RESOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../dist/mujoco.icns) @@ -150,7 +172,7 @@ endif() target_link_libraries( simulate - libsimulate + libsimulatestatic mujoco::mujoco glfw Threads::Threads @@ -225,7 +247,7 @@ if(_INSTALL_SIMULATE) target_add_rpath( TARGETS - mjsimulate + libsimulate INSTALL_DIRECTORY "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}" LIB_DIRS @@ -245,7 +267,7 @@ if(_INSTALL_SIMULATE) ) install( - TARGETS mjsimulate + TARGETS libsimulate EXPORT ${PROJECT_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT simulate LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT simulate diff --git a/simulate/glfw_dispatch.cc b/simulate/glfw_dispatch.cc index 2d6e7bcd..c6a1d753 100644 --- a/simulate/glfw_dispatch.cc +++ b/simulate/glfw_dispatch.cc @@ -80,6 +80,7 @@ const struct Glfw& Glfw(void* dlhandle) { // go/keep-sorted start mjGLFW_INITIALIZE_SYMBOL(glfwCreateWindow); + mjGLFW_INITIALIZE_SYMBOL(glfwDestroyWindow); mjGLFW_INITIALIZE_SYMBOL(glfwGetCursorPos); mjGLFW_INITIALIZE_SYMBOL(glfwGetFramebufferSize); mjGLFW_INITIALIZE_SYMBOL(glfwGetKey); @@ -115,10 +116,6 @@ const struct Glfw& Glfw(void* dlhandle) { #undef mjGLFW_INITIALIZE_SYMBOL -#if defined(mjGLFW_DYNAMIC_SYMBOLS) && !defined(_MSC_VER) - dlclose(dlhandle); -#endif - return glfw; }(); return glfw; diff --git a/simulate/glfw_dispatch.h b/simulate/glfw_dispatch.h index 5dfc1d98..3a44ead2 100644 --- a/simulate/glfw_dispatch.h +++ b/simulate/glfw_dispatch.h @@ -25,6 +25,7 @@ struct Glfw { #define mjGLFW_DECLARE_SYMBOL(func) decltype(&::func) func // go/keep-sorted start mjGLFW_DECLARE_SYMBOL(glfwCreateWindow); + mjGLFW_DECLARE_SYMBOL(glfwDestroyWindow); mjGLFW_DECLARE_SYMBOL(glfwGetCursorPos); mjGLFW_DECLARE_SYMBOL(glfwGetFramebufferSize); mjGLFW_DECLARE_SYMBOL(glfwGetKey); diff --git a/simulate/main.cc b/simulate/main.cc index 266430e3..926ba732 100644 --- a/simulate/main.cc +++ b/simulate/main.cc @@ -337,7 +337,10 @@ void PhysicsLoop(mj::Simulate& sim) { mjData* dnew = nullptr; if (mnew) dnew = mj_makeData(mnew); if (dnew) { - sim.load(sim.dropfilename, mnew, dnew, true); + sim.load(sim.dropfilename, mnew, dnew); + + if (d) mj_deleteData(d); + if (m) mj_deleteModel(m); m = mnew; d = dnew; @@ -356,7 +359,10 @@ void PhysicsLoop(mj::Simulate& sim) { mjData* dnew = nullptr; if (mnew) dnew = mj_makeData(mnew); if (dnew) { - sim.load(sim.filename, mnew, dnew, true); + sim.load(sim.filename, mnew, dnew); + + if (d) mj_deleteData(d); + if (m) mj_deleteModel(m); m = mnew; d = dnew; @@ -482,7 +488,7 @@ void PhysicsThread(mj::Simulate* sim, const char* filename) { m = LoadModel(filename, *sim); if (m) d = mj_makeData(m); if (d) { - sim->load(filename, m, d, true); + sim->load(filename, m, d); mj_forward(m, d); // allocate ctrlnoise diff --git a/simulate/simulate.cc b/simulate/simulate.cc index 5f13406e..941f5de5 100644 --- a/simulate/simulate.cc +++ b/simulate/simulate.cc @@ -934,7 +934,7 @@ void copykey(mj::Simulate* sim) { mju::strcat_arr(clipboard, "'/>"); // copy to clipboard - Glfw().glfwSetClipboardString(sim->window, clipboard); + Glfw().glfwSetClipboardString(reinterpret_cast(sim->window), clipboard); } // millisecond timer, for MuJoCo built-in profiler @@ -974,7 +974,7 @@ void copycamera(mj::Simulate* sim) { camera[0].up[0], camera[0].up[1], camera[0].up[2]); // copy spec into clipboard - Glfw().glfwSetClipboardString(sim->window, clipboard); + Glfw().glfwSetClipboardString(reinterpret_cast(sim->window), clipboard); } // update UI 0 when MuJoCo structures change (except for joint sliders) @@ -1035,7 +1035,7 @@ void uiLayout(mjuiState* state) { // rect 0: entire framebuffer rect[0].left = 0; rect[0].bottom = 0; - Glfw().glfwGetFramebufferSize(sim->window, &rect[0].width, &rect[0].height); + Glfw().glfwGetFramebufferSize(reinterpret_cast(sim->window), &rect[0].width, &rect[0].height); // rect 1: UI 0 rect[1].left = 0; @@ -1128,21 +1128,24 @@ void uiEvent(mjuiState* state) { break; case 9: // Full screen - if (Glfw().glfwGetWindowMonitor(sim->window)) { + if (Glfw().glfwGetWindowMonitor(reinterpret_cast(sim->window))) { // restore window from saved data - Glfw().glfwSetWindowMonitor(sim->window, nullptr, sim->windowpos[0], sim->windowpos[1], + Glfw().glfwSetWindowMonitor(reinterpret_cast(sim->window), + nullptr, sim->windowpos[0], sim->windowpos[1], sim->windowsize[0], sim->windowsize[1], 0); } // currently windowed: switch to full screen else { // save window data - Glfw().glfwGetWindowPos(sim->window, sim->windowpos, sim->windowpos+1); - Glfw().glfwGetWindowSize(sim->window, sim->windowsize, sim->windowsize+1); + Glfw().glfwGetWindowPos(reinterpret_cast(sim->window), sim->windowpos, sim->windowpos+1); + Glfw().glfwGetWindowSize(reinterpret_cast(sim->window), sim->windowsize, sim->windowsize+1); // switch - Glfw().glfwSetWindowMonitor(sim->window, Glfw().glfwGetPrimaryMonitor(), 0, 0, - sim->vmode.width, sim->vmode.height, sim->vmode.refreshRate); + Glfw().glfwSetWindowMonitor(reinterpret_cast(sim->window), Glfw().glfwGetPrimaryMonitor(), 0, 0, + reinterpret_cast(sim->vmode)->width, + reinterpret_cast(sim->vmode)->height, + reinterpret_cast(sim->vmode)->refreshRate); } // reinstante vsync, just in case @@ -1155,8 +1158,8 @@ void uiEvent(mjuiState* state) { } // modify UI - uiModify(sim->window, &sim->ui0, state, &sim->con); - uiModify(sim->window, &sim->ui1, state, &sim->con); + uiModify(reinterpret_cast(sim->window), &sim->ui0, state, &sim->con); + uiModify(reinterpret_cast(sim->window), &sim->ui1, state, &sim->con); } // simulation section @@ -1263,7 +1266,7 @@ void uiEvent(mjuiState* state) { sim->ui1.nsect = SECT_JOINT; makejoint(sim, sim->ui1.sect[SECT_JOINT].state); sim->ui1.nsect = NSECT1; - uiModify(sim->window, &sim->ui1, state, &sim->con); + uiModify(reinterpret_cast(sim->window), &sim->ui1, state, &sim->con); } // remake control section if actuator group changed @@ -1271,7 +1274,7 @@ void uiEvent(mjuiState* state) { sim->ui1.nsect = SECT_CONTROL; makecontrol(sim, sim->ui1.sect[SECT_CONTROL].state); sim->ui1.nsect = NSECT1; - uiModify(sim->window, &sim->ui1, state, &sim->con); + uiModify(reinterpret_cast(sim->window), &sim->ui1, state, &sim->con); } } @@ -1574,11 +1577,9 @@ void Simulate::applyforceperturbations() { //------------------------- Tell the render thread to load a file and wait ------------------------- void Simulate::load(const char* file, mjModel* mnew, - mjData* dnew, - bool delete_old_m_d) { + mjData* dnew) { this->mnew = mnew; this->dnew = dnew; - this->delete_old_m_d = delete_old_m_d; mju::strcpy_arr(this->filename, file); { @@ -1594,16 +1595,6 @@ void Simulate::load(const char* file, //------------------------------------- load mjb or xml model -------------------------------------- void Simulate::loadmodel() { - if (this->delete_old_m_d) { - // delete old model if requested - if (this->d) { - mj_deleteData(d); - } - if (this->m) { - mj_deleteModel(m); - } - } - this->m = this->mnew; this->d = this->dnew; @@ -1626,10 +1617,10 @@ void Simulate::loadmodel() { mjv_updateScene(this->m, this->d, &this->vopt, &this->pert, &this->cam, mjCAT_ALL, &this->scn); // set window title to model name - if (this->window && this->m->names) { + if (reinterpret_cast(this->window) && this->m->names) { char title[200] = "Simulate : "; mju::strcat_arr(title, this->m->names); - Glfw().glfwSetWindowTitle(this->window, title); + Glfw().glfwSetWindowTitle(reinterpret_cast(this->window), title); } // set keyframe range and divisions @@ -1641,8 +1632,8 @@ void Simulate::loadmodel() { makesections(this); // full ui update - uiModify(this->window, &this->ui0, &this->uistate, &this->con); - uiModify(this->window, &this->ui1, &this->uistate, &this->con); + uiModify(reinterpret_cast(this->window), &this->ui0, &this->uistate, &this->con); + uiModify(reinterpret_cast(this->window), &this->ui1, &this->uistate, &this->con); updatesettings(this); // clear request @@ -1758,7 +1749,7 @@ void Simulate::render() { } // finalize - Glfw().glfwSwapBuffers(this->window); + Glfw().glfwSwapBuffers(reinterpret_cast(this->window)); return; } @@ -1865,13 +1856,13 @@ void Simulate::render() { } // finalize - Glfw().glfwSwapBuffers(this->window); + Glfw().glfwSwapBuffers(reinterpret_cast(this->window)); } // clear callbacks registered in external structures void Simulate::clearcallback() { - uiClearCallback(this->window); + uiClearCallback(reinterpret_cast(this->window)); } void Simulate::renderloop() { @@ -1883,25 +1874,27 @@ void Simulate::renderloop() { Glfw().glfwWindowHint(GLFW_VISIBLE, 1); // get videomode and save - this->vmode = *Glfw().glfwGetVideoMode(Glfw().glfwGetPrimaryMonitor()); + this->vmode = Glfw().glfwGetVideoMode(Glfw().glfwGetPrimaryMonitor()); // use videomode refreshrate if nonzero - if (this->vmode.refreshRate) this->refreshRate = this->vmode.refreshRate; + if (reinterpret_cast(this->vmode)->refreshRate) + this->refreshRate = reinterpret_cast(this->vmode)->refreshRate; // create window - this->window = Glfw().glfwCreateWindow((2*this->vmode.width)/3, (2*this->vmode.height)/3, + this->window = Glfw().glfwCreateWindow((2*reinterpret_cast(this->vmode)->width)/3, + (2*reinterpret_cast(this->vmode)->height)/3, "Simulate", nullptr, nullptr); - if (!this->window) { + if (!reinterpret_cast(this->window)) { Glfw().glfwTerminate(); mju_error("could not create window"); } // save window position and size - Glfw().glfwGetWindowPos(this->window, this->windowpos, this->windowpos+1); - Glfw().glfwGetWindowSize(this->window, this->windowsize, this->windowsize+1); + Glfw().glfwGetWindowPos(reinterpret_cast(this->window), this->windowpos, this->windowpos+1); + Glfw().glfwGetWindowSize(reinterpret_cast(this->window), this->windowsize, this->windowsize+1); // make context current, set v-sync - Glfw().glfwMakeContextCurrent(this->window); + Glfw().glfwMakeContextCurrent(reinterpret_cast(this->window)); Glfw().glfwSwapInterval(this->vsync); // init abstract visualization @@ -1915,7 +1908,7 @@ void Simulate::renderloop() { mjv_makeScene(nullptr, &this->scn, maxgeom); // select default font - int fontscale = uiFontScale(this->window); + int fontscale = uiFontScale(reinterpret_cast(this->window)); this->font = fontscale/50 - 1; // make empty context @@ -1939,7 +1932,7 @@ void Simulate::renderloop() { // set GLFW callbacks this->uistate.userdata = this; - uiSetCallback(this->window, &this->uistate, uiEvent, uiLayout, uiRender, uiDrop); + uiSetCallback(reinterpret_cast(this->window), &this->uistate, uiEvent, uiLayout, uiRender, uiDrop); // populate uis with standard sections this->ui0.userdata = this; @@ -1948,11 +1941,11 @@ void Simulate::renderloop() { mjui_add(&this->ui0, this->defOption); mjui_add(&this->ui0, this->defSimulation); mjui_add(&this->ui0, this->defWatch); - uiModify(this->window, &this->ui0, &this->uistate, &this->con); - uiModify(this->window, &this->ui1, &this->uistate, &this->con); + uiModify(reinterpret_cast(this->window), &this->ui0, &this->uistate, &this->con); + uiModify(reinterpret_cast(this->window), &this->ui1, &this->uistate, &this->con); // run event loop - while (!Glfw().glfwWindowShouldClose(this->window) && !this->exitrequest.load()) { + while (!Glfw().glfwWindowShouldClose(reinterpret_cast(this->window)) && !this->exitrequest.load()) { { const std::lock_guard lock(this->mtx); @@ -1979,6 +1972,13 @@ void Simulate::renderloop() { this->clearcallback(); mjv_freeScene(&this->scn); mjr_freeContext(&this->con); + + Glfw().glfwDestroyWindow(reinterpret_cast(this->window)); +} + +//------------------------------------ setup the glfw dispatch table ------------------------------- +void setglfwdlhandle(void* dlhandle) { + Glfw(dlhandle); } } // namespace mujoco diff --git a/simulate/simulate.h b/simulate/simulate.h index 4091ba6e..f61a3021 100644 --- a/simulate/simulate.h +++ b/simulate/simulate.h @@ -23,17 +23,17 @@ #include #include -#ifdef MJSIMULATE_STATIC +#ifdef LIBSIMULATE_STATIC // static library - #define MJSIMULATEAPI - #define MJSIMULATELOCAL + #define LIBSIMULATEAPI + #define LIBSIMULATELOCAL #else - #ifdef MJSIMULATE_DLL_EXPORTS - #define MJSIMULATEAPI MUJOCO_HELPER_DLL_EXPORT + #ifdef LIBSIMULATE_DLL_EXPORTS + #define LIBSIMULATEAPI MUJOCO_HELPER_DLL_EXPORT #else - #define MJSIMULATEAPI MUJOCO_HELPER_DLL_IMPORT + #define LIBSIMULATEAPI MUJOCO_HELPER_DLL_IMPORT #endif - #define MJSIMULATELOCAL MUJOCO_HELPER_DLL_LOCAL + #define LIBSIMULATELOCAL MUJOCO_HELPER_DLL_LOCAL #endif namespace mujoco { @@ -41,7 +41,7 @@ namespace mujoco { //-------------------------------- global ----------------------------------------------- // Simulate states not contained in MuJoCo structures -class MJSIMULATEAPI Simulate { +class LIBSIMULATEAPI Simulate { public: // create object and initialize the simulate ui Simulate() = default; @@ -54,7 +54,7 @@ class MJSIMULATEAPI Simulate { // Request that the Simulate UI thread render a new model // optionally delete the old model and data when done - void load(const char* file, mjModel* m, mjData* d, bool delete_old_m_d); + void load(const char* file, mjModel* m, mjData* d); // functions below are used by the renderthread // load mjb or xml model that has been requested by load() @@ -79,7 +79,6 @@ class MJSIMULATEAPI Simulate { // model and data to be visualized mjModel* mnew = nullptr; mjData* dnew = nullptr; - bool delete_old_m_d = false; mjModel* m = nullptr; mjData* d = nullptr; @@ -163,12 +162,14 @@ class MJSIMULATEAPI Simulate { mjvFigure figsensor = {}; // OpenGL rendering and UI - GLFWvidmode vmode = {}; + // Use void* for GLFW objects to avoid requiring users of the shared library + // from needing GLFW headers (in particular Python should not need them) + const void* vmode = {}; // const GLFWvidmode* int refreshRate = 60; int windowpos[2] = {0}; int windowsize[2] = {0}; mjrContext con = {}; - GLFWwindow* window = nullptr; + void* window = nullptr; // GLFWwindow* mjuiState uistate = {}; mjUI ui0 = {}; mjUI ui1 = {}; @@ -228,6 +229,10 @@ class MJSIMULATEAPI Simulate { char info_content[Simulate::kMaxFilenameLength] = {0}; }; +// setup the glfw dispatch table +// if set, must be called prior to other Simulate functions +LIBSIMULATEAPI void setglfwdlhandle(void* dlhandle); + } // namespace mujoco #endif