in mujoco_py/builder.py [0:0]
def build_callback_fn(function_string, userdata_names=[]):
'''
Builds a C callback function and returns a function pointer int.
function_string : str
This is a string of the C function to be compiled
userdata_names : list or tuple
This is an optional list to defince convenience names
We compile and link and load the function, and return a function pointer.
See `MjSim.set_substep_callback()` for an example use of these callbacks.
The callback function should match the signature:
void fun(const mjModel *m, mjData *d);
Here's an example function_string:
```
"""
#include <stdio.h>
void fun(const mjModel* m, mjData* d) {
printf("hello");
}
"""
```
Input and output for the function pass through userdata in the data struct:
```
"""
void fun(const mjModel* m, mjData* d) {
d->userdata[0] += 1;
}
"""
```
`userdata_names` is expected to match the model where the callback is used.
These can bet set on a model with:
`model.set_userdata_names([...])`
If `userdata_names` is supplied, convenience `#define`s are added for each.
For example:
`userdata_names = ['my_sum']`
Will get gerenerated into the extra line:
`#define my_sum d->userdata[0]`
And prepended to the top of the function before compilation.
Here's an example that takes advantage of this:
```
"""
void fun(const mjModel* m, mjData* d) {
for (int i = 0; i < m->nu; i++) {
my_sum += d->ctrl[i];
}
}
"""
```
Note these are just C `#define`s and are limited in how they can be used.
After compilation, the built library containing the function is loaded
into memory and all of the files (including the library) are deleted.
To retain these for debugging set the `MUJOCO_PY_DEBUG_FN_BUILDER` envvar.
To save time compiling, these function pointers may be re-used by many
different consumers. They are thread-safe and don't acquire the GIL.
See the file `tests/test_substep.py` for additional examples,
including an example which iterates over contacts to compute penetrations.
'''
assert isinstance(userdata_names, (list, tuple)), \
'invalid userdata_names: {}'.format(userdata_names)
ffibuilder = FFI()
ffibuilder.cdef('extern uintptr_t __fun;')
name = '_fn_' + ''.join(choice(ascii_lowercase) for _ in range(15))
source_string = '#include <mujoco.h>\n'
# Add defines for each userdata to make setting them easier
for i, data_name in enumerate(userdata_names):
source_string += '#define {} d->userdata[{}]\n'.format(data_name, i)
source_string += function_string
source_string += '\nuintptr_t __fun = (uintptr_t) fun;'
# Link against mujoco so we can call mujoco functions from within callback
ffibuilder.set_source(name, source_string,
include_dirs=[join(mujoco_path, 'include')],
library_dirs=[join(mujoco_path, 'bin')],
libraries=['mujoco210'])
# Catch compilation exceptions so we can cleanup partial files in that case
try:
library_path = ffibuilder.compile(verbose=True)
except Exception as e:
build_fn_cleanup(name)
raise e
# On Mac the MuJoCo library is linked strangely, so we have to fix it here
if sys.platform == 'darwin':
fixed_library_path = manually_link_libraries(mujoco_path, library_path)
move(fixed_library_path, library_path) # Overwrite with fixed library
module = load_dynamic_ext(name, library_path)
# Now that the module is loaded into memory, we can actually delete it
build_fn_cleanup(name)
return module.lib.__fun