diff --git a/rosidl_generator_py/cmake/custom_command.cmake b/rosidl_generator_py/cmake/custom_command.cmake index 203747a6..6543deea 100644 --- a/rosidl_generator_py/cmake/custom_command.cmake +++ b/rosidl_generator_py/cmake/custom_command.cmake @@ -19,7 +19,7 @@ # a different CMake subdirectory, and this command is invoked after an # add_subdirectory() call. add_custom_command( - OUTPUT ${_generated_extension_files} ${_generated_py_files} ${_generated_c_files} + OUTPUT ${_generated_extension_files} ${_generated_py_files} ${_generated_c_files} ${_generated_c_base_files} # This assumes that python_cmake_module was found, which is always the case since this is only # called from rosidl_generator_py_generate_interfaces.cmake COMMAND Python3::Interpreter ${rosidl_generator_py_BIN} @@ -39,5 +39,6 @@ else() ${_generated_extension_files} ${_generated_py_files} ${_generated_c_files} + ${_generated_c_base_files} ) endif() diff --git a/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake b/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake index de6a6c65..afe36883 100644 --- a/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake +++ b/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake @@ -48,6 +48,7 @@ set(_output_path set(_generated_extension_files "") set(_generated_py_files "") set(_generated_c_files "") +set(_generated_c_base_files "") foreach(_typesupport_impl ${_typesupport_impls}) set(_generated_extension_${_typesupport_impl}_files "") @@ -63,6 +64,13 @@ foreach(_abs_idl_file ${rosidl_generate_interfaces_ABS_IDL_FILES}) list(APPEND _generated_c_files "${_output_path}/${_parent_folder}/_${_module_name}_s.c") endforeach() +list(APPEND _generated_c_base_files + "${_output_path}/_${PROJECT_NAME}_bases.c") +if(NOT _generated_c_files STREQUAL "") + list(APPEND _generated_c_files + "${_output_path}/_${PROJECT_NAME}_decl.h" + "${_output_path}/_${PROJECT_NAME}_import.c") +endif() file(MAKE_DIRECTORY "${_output_path}") file(WRITE "${_output_path}/__init__.py" "") @@ -106,9 +114,13 @@ set(target_dependencies ${rosidl_generator_py_GENERATOR_FILES} "${rosidl_generator_py_TEMPLATE_DIR}/_action_pkg_typesupport_entry_point.c.em" "${rosidl_generator_py_TEMPLATE_DIR}/_action.py.em" + "${rosidl_generator_py_TEMPLATE_DIR}/_idl_pkg_bases.c.em" + "${rosidl_generator_py_TEMPLATE_DIR}/_idl_pkg_decl.h.em" + "${rosidl_generator_py_TEMPLATE_DIR}/_idl_pkg_import.c.em" "${rosidl_generator_py_TEMPLATE_DIR}/_idl_pkg_typesupport_entry_point.c.em" "${rosidl_generator_py_TEMPLATE_DIR}/_idl_support.c.em" "${rosidl_generator_py_TEMPLATE_DIR}/_idl.py.em" + "${rosidl_generator_py_TEMPLATE_DIR}/_msg_base.c.em" "${rosidl_generator_py_TEMPLATE_DIR}/_msg_pkg_typesupport_entry_point.c.em" "${rosidl_generator_py_TEMPLATE_DIR}/_msg_support.c.em" "${rosidl_generator_py_TEMPLATE_DIR}/_msg.py.em" @@ -147,7 +159,7 @@ file(WRITE "${_subdir}/CMakeLists.txt" "${_custom_command}") add_subdirectory("${_subdir}" ${rosidl_generate_interfaces_TARGET}${_target_suffix}) set_property( SOURCE - ${_generated_extension_files} ${_generated_py_files} ${_generated_c_files} + ${_generated_extension_files} ${_generated_py_files} ${_generated_c_files} ${_generated_c_base_files} PROPERTY GENERATED 1) set(_target_name_lib "${rosidl_generate_interfaces_TARGET}__rosidl_generator_py") @@ -179,6 +191,37 @@ endif() rosidl_get_typesupport_target(c_typesupport_target "${rosidl_generate_interfaces_TARGET}" "rosidl_typesupport_c") target_link_libraries(${_target_name_lib} PRIVATE ${c_typesupport_target}) +set(_target_name_bases_lib "${rosidl_generate_interfaces_TARGET}__bases") +add_library(${_target_name_bases_lib} SHARED ${_generated_c_base_files}) +add_dependencies( + ${_target_name_bases_lib} + ${rosidl_generate_interfaces_TARGET}${_target_suffix}) +target_link_libraries( + ${_target_name_bases_lib} + Python3::NumPy + Python3::Python + ${PythonExtra_LIBRARIES} +) +target_include_directories( + ${_target_name_bases_lib} PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_c + ${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_py + ${PythonExtra_INCLUDE_DIRS} +) + +set_target_properties(${_target_name_bases_lib} PROPERTIES + COMPILE_OPTIONS "${_extension_compile_flags}" + PREFIX "" + LIBRARY_OUTPUT_DIRECTORY${_build_type} ${_output_path} + RUNTIME_OUTPUT_DIRECTORY${_build_type} ${_output_path} + OUTPUT_NAME "_${PROJECT_NAME}_bases${PythonExtra_EXTENSION_SUFFIX}" +) +if(NOT rosidl_generate_interfaces_SKIP_INSTALL) + install(TARGETS ${_target_name_bases_lib} + DESTINATION "${PYTHON_INSTALL_DIR}/${PROJECT_NAME}") +endif() + + foreach(_typesupport_impl ${_typesupport_impls}) find_package(${_typesupport_impl} REQUIRED) # a typesupport package might not be able to generated a target anymore diff --git a/rosidl_generator_py/resource/_idl_pkg_bases.c.em b/rosidl_generator_py/resource/_idl_pkg_bases.c.em new file mode 100644 index 00000000..5d154479 --- /dev/null +++ b/rosidl_generator_py/resource/_idl_pkg_bases.c.em @@ -0,0 +1,190 @@ +// generated from rosidl_generator_py/resource/_idl_pkg_bases.c.em +// generated code does not contain a copyright notice +@ +@####################################################################### +@# EmPy template for generating __bases.c files +@# +@# Context: +@# - package_name (string) +@# - content (IdlContent, list of elements, e.g. Messages or Services) +@####################################################################### +@ +@####################################################################### +@# Handle messages +@####################################################################### +@{ +from rosidl_parser.definition import Message +from rosidl_pycommon import convert_camel_case_to_lower_case_underscore + +include_directives = set() +register_functions = [] + +}@ +@[for message in content.get_elements_of_type(Message)]@ +@{ + +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=message, include_directives=include_directives, + register_functions=register_functions) +}@ +@[end for]@ +@ +@####################################################################### +@# Handle services +@####################################################################### +@{ +from rosidl_parser.definition import Service +}@ +@[for service in content.get_elements_of_type(Service)]@ +@{ + +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=service.request_message, include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=service.response_message, include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=service.event_message, include_directives=include_directives, + register_functions=register_functions) +}@ +@[end for]@ +@ +@####################################################################### +@# Handle actions +@####################################################################### +@{ +from rosidl_parser.definition import Action +}@ +@[for action in content.get_elements_of_type(Action)]@ +@{ + +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=action.goal, include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=action.result, include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=action.feedback, include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=action.send_goal_service.request_message, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=action.send_goal_service.response_message, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=action.send_goal_service.event_message, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=action.get_result_service.request_message, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=action.get_result_service.response_message, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=action.get_result_service.event_message, + include_directives=include_directives, + register_functions=register_functions) +}@ + +@{ +TEMPLATE( + '_msg_base.c.em', + package_name=package_name, + message=action.feedback_message, include_directives=include_directives, + register_functions=register_functions) +}@ +@[end for]@ + + +static PyModuleDef _module = { + PyModuleDef_HEAD_INIT, + .m_name = "_@(package_name)_bases", + .m_doc = "Extention module for @(package_name) messages", + .m_size = -1, +}; + + +PyMODINIT_FUNC +PyInit__@(package_name)_bases(void) +{ + PyObject * pymodule = NULL; + pymodule = PyModule_Create(&_module); + if (!pymodule) { + return NULL; + } + int8_t err; +@[for register_function in register_functions]@ + + err = @(register_function)(pymodule); + if (err) { + Py_XDECREF(pymodule); + return NULL; + } +@[end for]@ + + return pymodule; +} diff --git a/rosidl_generator_py/resource/_idl_pkg_decl.h.em b/rosidl_generator_py/resource/_idl_pkg_decl.h.em new file mode 100644 index 00000000..2c5049ab --- /dev/null +++ b/rosidl_generator_py/resource/_idl_pkg_decl.h.em @@ -0,0 +1,179 @@ +// generated from rosidl_generator_py/resource/_idl_pkg_decl.c.em +// generated code does not contain a copyright notice +#pragma once +@ +@####################################################################### +@# EmPy template for generating __decl.h files +@# +@# Context: +@# - package_name (string) +@# - content (IdlContent, list of elements, e.g. Messages or Services) +@####################################################################### +@ +@####################################################################### +@# Handle messages +@####################################################################### +@{ +from rosidl_parser.definition import Message +from rosidl_pycommon import convert_camel_case_to_lower_case_underscore + +include_directives = set() +required_py_modules = [] + +}@ +@[for message in content.get_elements_of_type(Message)]@ +@{ + +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=message, include_directives=include_directives, + required_py_modules=required_py_modules) +}@ +@[end for]@ +@ +@####################################################################### +@# Handle services +@####################################################################### +@{ +from rosidl_parser.definition import Service +}@ +@[for service in content.get_elements_of_type(Service)]@ +@{ + +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=service.request_message, include_directives=include_directives, + required_py_modules=required_py_modules) +}@ + +@{ +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=service.response_message, include_directives=include_directives, + required_py_modules=required_py_modules) +}@ + +@{ +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=service.event_message, include_directives=include_directives, + required_py_modules=required_py_modules) +}@ +@[end for]@ +@ +@####################################################################### +@# Handle actions +@####################################################################### +@{ +from rosidl_parser.definition import Action +}@ +@[for action in content.get_elements_of_type(Action)]@ +@{ + +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=action.goal, include_directives=include_directives, + required_py_modules=required_py_modules) +}@ + +@{ +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=action.result, include_directives=include_directives, + required_py_modules=required_py_modules) +}@ + +@{ +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=action.feedback, include_directives=include_directives, + required_py_modules=required_py_modules) +}@ + +@{ +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=action.send_goal_service.request_message, + include_directives=include_directives, + required_py_modules=required_py_modules) +}@ + +@{ +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=action.send_goal_service.response_message, + include_directives=include_directives, + required_py_modules=required_py_modules) +}@ + +@{ +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=action.send_goal_service.event_message, + include_directives=include_directives, + required_py_modules=required_py_modules) +}@ + +@{ +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=action.get_result_service.request_message, + include_directives=include_directives, + required_py_modules=required_py_modules) +}@ + +@{ +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=action.get_result_service.response_message, + include_directives=include_directives, + required_py_modules=required_py_modules) +}@ + +@{ +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=action.get_result_service.event_message, + include_directives=include_directives, + required_py_modules=required_py_modules) +}@ + +@{ +TEMPLATE( + '_msg_decl.h.em', + package_name=package_name, + message=action.feedback_message, include_directives=include_directives, + required_py_modules=required_py_modules) +}@ +@[end for]@ + +#define MAX__IMPORT_INDEX @(len(required_py_modules)) + +typedef struct lazy_import_state +{ + PyObject * cached_imports[MAX__IMPORT_INDEX]; +} lazy_import_state; + +int @(package_name)__lazy_import_initialize(lazy_import_state * state); +int @(package_name)__lazy_import_finalize(lazy_import_state * state); +PyObject * @(package_name)__lazy_import(lazy_import_state * state, size_t index); + +// Since lazy cache global state is shared between multiple typesupport implementations, +// its lifetime should be managed carefully. Extention module can call `__lazy_import` +// function only between `__lazy_import_acquire` and `__lazy_import_release` calls. +// This functions should be removed when cache state becomes module specific. +int @(package_name)__lazy_import_acquire(); +int @(package_name)__lazy_import_release(); diff --git a/rosidl_generator_py/resource/_idl_pkg_import.c.em b/rosidl_generator_py/resource/_idl_pkg_import.c.em new file mode 100644 index 00000000..4d101c61 --- /dev/null +++ b/rosidl_generator_py/resource/_idl_pkg_import.c.em @@ -0,0 +1,204 @@ +// generated from rosidl_generator_py/resource/_idl_pkg_import.c.em +// generated code does not contain a copyright notice +@ +@####################################################################### +@# EmPy template for generating __import.c files +@# +@# Context: +@# - package_name (string) +@# - content (IdlContent, list of elements, e.g. Messages or Services) +@####################################################################### +@ +@####################################################################### +@# Handle messages +@####################################################################### +@{ +from collections import OrderedDict +from rosidl_parser.definition import Action +from rosidl_parser.definition import Message +from rosidl_parser.definition import Service +from rosidl_pycommon import convert_camel_case_to_lower_case_underscore + + +def collect_py_deps(dst, message: Message): + from rosidl_generator_py.generate_py_impl import SPECIAL_NESTED_BASIC_TYPES + from rosidl_parser.definition import ACTION_FEEDBACK_SUFFIX + from rosidl_parser.definition import ACTION_GOAL_SUFFIX + from rosidl_parser.definition import ACTION_RESULT_SUFFIX + from rosidl_parser.definition import ACTION_GOAL_SERVICE_SUFFIX + from rosidl_parser.definition import ACTION_RESULT_SERVICE_SUFFIX + from rosidl_parser.definition import ACTION_FEEDBACK_MESSAGE_SUFFIX + from rosidl_parser.definition import SERVICE_EVENT_MESSAGE_SUFFIX + from rosidl_parser.definition import SERVICE_REQUEST_MESSAGE_SUFFIX + from rosidl_parser.definition import SERVICE_RESPONSE_MESSAGE_SUFFIX + from rosidl_parser.definition import AbstractNestedType + from rosidl_parser.definition import AbstractSequence + from rosidl_parser.definition import Array + from rosidl_parser.definition import BasicType + from rosidl_parser.definition import NamespacedType + from rosidl_pycommon import convert_camel_case_to_lower_case_underscore + + def cut_service_or_action_suffix(name): + for suffix in ( + SERVICE_EVENT_MESSAGE_SUFFIX, SERVICE_REQUEST_MESSAGE_SUFFIX, SERVICE_RESPONSE_MESSAGE_SUFFIX, + ACTION_GOAL_SERVICE_SUFFIX, ACTION_RESULT_SERVICE_SUFFIX, ACTION_FEEDBACK_MESSAGE_SUFFIX, + ACTION_FEEDBACK_SUFFIX, ACTION_GOAL_SUFFIX, ACTION_RESULT_SUFFIX, + ): + if name.endswith(suffix): + name = name[:-len(suffix)] + return name + + for member in message.structure.members: + if isinstance(member.type, AbstractNestedType) and isinstance(member.type.value_type, BasicType) and member.type.value_type.typename in SPECIAL_NESTED_BASIC_TYPES: + if isinstance(member.type, Array): + if 'numpy.ndarray' not in dst: + dst['numpy.ndarray'] = ('numpy', 'ndarray', False) + dtype = SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype'] + if dtype not in dst: + module, name = dtype.rsplit('.', maxsplit=1) + dst[dtype] = (module, name, False) + elif isinstance(member.type, AbstractSequence) and 'array.array' not in dst: + dst['array.array'] = ('array', 'array', False) + + interface_name = cut_service_or_action_suffix(message.structure.namespaced_type.name) + module_name = '_' + convert_camel_case_to_lower_case_underscore(interface_name) + full_module_name = ".".join((*message.structure.namespaced_type.namespaces, module_name)) + object_name = message.structure.namespaced_type.name + type_name = ".".join(message.structure.namespaced_type.namespaced_name()) + + assert type_name not in dst + # For example_msgs.srv.MyService_Request from example_msgs/srv/MyService.srv added + # ["example_msgs.srv.MyService_Request"] = ("example_msgs.msg._my_service", "MyService_Request", True) + dst[type_name] = (full_module_name, object_name, True) + + +required_py_objects = OrderedDict() + +for message in content.get_elements_of_type(Message): + collect_py_deps(required_py_objects, message) + +for service in content.get_elements_of_type(Service): + collect_py_deps(required_py_objects, service.request_message) + collect_py_deps(required_py_objects, service.response_message) + collect_py_deps(required_py_objects, service.event_message) + +for action in content.get_elements_of_type(Action): + collect_py_deps(required_py_objects, action.goal) + collect_py_deps(required_py_objects, action.result) + collect_py_deps(required_py_objects, action.feedback) + collect_py_deps(required_py_objects, action.send_goal_service.request_message) + collect_py_deps(required_py_objects, action.send_goal_service.response_message) + collect_py_deps(required_py_objects, action.send_goal_service.event_message) + collect_py_deps(required_py_objects, action.get_result_service.request_message) + collect_py_deps(required_py_objects, action.get_result_service.response_message) + collect_py_deps(required_py_objects, action.get_result_service.event_message) + collect_py_deps(required_py_objects, action.feedback_message) + +}@ +#include +#include +#include "./_@(package_name)_decl.h" + + +typedef struct +{ + const char * module_name; + const char * object_name; + bool ensure_is_type; +} lazy_import_info; + + +static lazy_import_info import_infos[MAX__IMPORT_INDEX] = { +@[for type_name, (module_name, object_name, ensure) in required_py_objects.items()]@ +@{ parts = type_name.split('.')}@ +@{ const_name = '__'.join(parts[:-1] + [convert_camel_case_to_lower_case_underscore(parts[-1])]).upper()}@ +@{ ensure_is_type = "true" if ensure else "false"}@ + // @(type_name) + [@(const_name)__IMPORT_INDEX] = {"@(module_name)", "@(object_name)", @(ensure_is_type)}, +@[end for]@ +}; + +int @(package_name)__lazy_import_initialize(lazy_import_state * state) +{ + assert(state != NULL); + for (size_t i = 0; i < MAX__IMPORT_INDEX; ++i) { + state->cached_imports[i] = NULL; + } + return 0; +} + +int @(package_name)__lazy_import_finalize(lazy_import_state * state) +{ + assert(state != NULL); + for (size_t i = 0; i < MAX__IMPORT_INDEX; ++i) { + Py_CLEAR(state->cached_imports[i]); + } + return 0; +} + + +static PyObject * state_storage = NULL; + +PyObject * @(package_name)__lazy_import(lazy_import_state * state, size_t index) +{ + if (state == NULL) { + if (state_storage == NULL) { + PyErr_SetString(PyExc_RuntimeError, "Internal error. Module not initilized properly"); + return NULL; + } + state = (lazy_import_state *)PyCapsule_GetPointer(state_storage, NULL); + if (state == NULL) { + return NULL; + } + } + + assert(index < MAX__IMPORT_INDEX); + PyObject * py_type = state->cached_imports[index]; + if (py_type != NULL) { + return py_type; + } + + const lazy_import_info * info = &import_infos[index]; + PyObject * module = PyImport_ImportModule(info->module_name); + if (!module) { + return NULL; + } + py_type = PyObject_GetAttrString(module, info->object_name); + Py_DECREF(module); + if (!py_type) { + return NULL; + } + if (info->ensure_is_type && !PyType_Check(py_type)) { + PyErr_SetString(PyExc_RuntimeError, "Imported object is not a python type"); + return NULL; + } + + state->cached_imports[index] = py_type; + return py_type; +} + +static void clear_storage(PyObject * capsule) +{ + (void)capsule; + state_storage = NULL; +} + +int @(package_name)__lazy_import_acquire() +{ + if (state_storage == NULL) { + lazy_import_state * state = PyMem_Malloc(sizeof(lazy_import_state)); + if (@(package_name)__lazy_import_initialize(state) != 0) { + return 1; + } + state_storage = PyCapsule_New(state, NULL, clear_storage); + } else { + Py_INCREF(state_storage); + } + return 0; +} + +int @(package_name)__lazy_import_release() +{ + Py_DECREF(state_storage); + return 0; +} diff --git a/rosidl_generator_py/resource/_idl_pkg_typesupport_entry_point.c.em b/rosidl_generator_py/resource/_idl_pkg_typesupport_entry_point.c.em index 0a73dd2d..f715dd40 100644 --- a/rosidl_generator_py/resource/_idl_pkg_typesupport_entry_point.c.em +++ b/rosidl_generator_py/resource/_idl_pkg_typesupport_entry_point.c.em @@ -21,22 +21,6 @@ include_directives = set() register_functions = [] }@ #include - -static PyMethodDef @(package_name)__methods[] = { - {NULL, NULL, 0, NULL} /* sentinel */ -}; - -static struct PyModuleDef @(package_name)__module = { - PyModuleDef_HEAD_INIT, - "_@(package_name)_support", - "_@(package_name)_doc", - -1, /* -1 means that the module keeps state in global variables */ - @(package_name)__methods, - NULL, - NULL, - NULL, - NULL, -}; @ @[for message in content.get_elements_of_type(Message)]@ @@ -85,6 +69,40 @@ TEMPLATE( }@ @[end for]@ +#include "./_@(package_name)_decl.h" + +static PyMethodDef @(package_name)__methods[] = { + {NULL, NULL, 0, NULL} /* sentinel */ +}; + +static int +_@(package_name)_clear(PyObject * module) +{ + (void)module; + @(package_name)__lazy_import_release(); + return 0; +} + +static void +_@(package_name)_free(void * module) +{ + _@(package_name)_clear((PyObject *)module); +} + + +static struct PyModuleDef @(package_name)__module = { + PyModuleDef_HEAD_INIT, + "_@(package_name)_support", + "_@(package_name)_doc", + -1, /* -1 means that the module keeps state in global variables */ + @(package_name)__methods, + NULL, + NULL, + _@(package_name)_clear, + _@(package_name)_free, +}; + + PyMODINIT_FUNC PyInit_@(package_name)_s__@(typesupport_impl)(void) { @@ -103,5 +121,6 @@ PyInit_@(package_name)_s__@(typesupport_impl)(void) } @[end for]@ + @(package_name)__lazy_import_acquire(); return pymodule; } diff --git a/rosidl_generator_py/resource/_idl_support.c.em b/rosidl_generator_py/resource/_idl_support.c.em index d46c0228..7774c9c6 100644 --- a/rosidl_generator_py/resource/_idl_support.c.em +++ b/rosidl_generator_py/resource/_idl_support.c.em @@ -18,6 +18,7 @@ from rosidl_parser.definition import Message include_directives = set() + }@ @[for message in content.get_elements_of_type(Message)]@ @{ diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index 61640c52..f3c643bb 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -61,6 +61,8 @@ for member in message.structure.members: else: assert False member_names.append(member.name) +imports.setdefault( + f'import {package_name}._{package_name}_bases as _bases', []) # used for base type }@ @#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @ @@ -103,6 +105,30 @@ class Metaclass_@(message.structure.namespaced_type.name)(type): @[end for]@ } + def __new__(cls, *args): + new_type = super().__new__(cls, *args) + + # Ugly hack here. + # There are two purposes for __slots__ field of message class: + # 1) __slots__ field is used in ROS as a list of message members. + # Number of ROS packages rely on its value. + # 2) defining __slots__ in Python class prevents adding __dict__ + # to class instances. This behvior is desirable. + # If #1 defined in message class body, then base class member descriptors are + # overriden and no longer can be used. On the other side, any __slots__ value + # should be defined to guaratee #2. + # So we define empty list in message class body and modify it after type object + # constructed. This is neither prohibited, nor encouraged by CPython documentation. + new_type.__slots__ += [ +@[for member in message.structure.members]@ +@[ if len(message.structure.members) == 1 and member.name == EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@ +@[ continue]@ +@[ end if]@ + '_@(member.name)', +@[end for]@ + ] + return new_type + @@classmethod def __import_type_support__(cls): try: @@ -188,7 +214,7 @@ for member in message.structure.members: @[end for]@ -class @(message.structure.namespaced_type.name)(metaclass=Metaclass_@(message.structure.namespaced_type.name)): +class @(message.structure.namespaced_type.name)(_bases.@(message.structure.namespaced_type.name)Base, metaclass=Metaclass_@(message.structure.namespaced_type.name)): @[if not message.constants]@ """Message class '@(message.structure.namespaced_type.name)'.""" @[else]@ @@ -202,15 +228,9 @@ class @(message.structure.namespaced_type.name)(metaclass=Metaclass_@(message.st """ @[end if]@ - __slots__ = [ -@[for member in message.structure.members]@ -@[ if len(message.structure.members) == 1 and member.name == EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@ -@[ continue]@ -@[ end if]@ - '_@(member.name)', -@[end for]@ - '_check_fields', - ] + # This field is modified after class creation. + # See the comment to Metaclass_@(message.structure.namespaced_type.name).__new__ + __slots__ = ['_check_fields'] _fields_and_field_types = { @[for member in message.structure.members]@ @@ -433,7 +453,11 @@ if member.name in dict(inspect.getmembers(builtins)).keys(): @@builtins.property@(noqa_string) def @(member.name)(self):@(noqa_string) """Message field '@(member.name)'.""" +@[if isinstance(member.type, BasicType) and member.type.typename == 'octet']@ + return bytes([self._@(member.name)]) +@[else]@ return self._@(member.name) +@[end if]@ @@@(member.name).setter@(noqa_string) def @(member.name)(self, value):@(noqa_string) @@ -603,6 +627,8 @@ bound = 1.7976931348623157e+308 @[ elif isinstance(member.type, AbstractSequence)]@ self._@(member.name) = array.array('@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])', value) @[ end if]@ +@[ elif isinstance(member.type, BasicType) and member.type.typename == 'octet']@ + self._@(member.name) = value[0] @[ else]@ self._@(member.name) = value @[ end if]@ diff --git a/rosidl_generator_py/resource/_msg_base.c.em b/rosidl_generator_py/resource/_msg_base.c.em new file mode 100644 index 00000000..04dc6bdd --- /dev/null +++ b/rosidl_generator_py/resource/_msg_base.c.em @@ -0,0 +1,131 @@ +@# Included from rosidl_generator_py/resource/_idl_pkg_bases.c.em +@{ +from rosidl_generator_py.generate_py_impl import SPECIAL_NESTED_BASIC_TYPES +from rosidl_parser.definition import AbstractNestedType +from rosidl_parser.definition import AbstractSequence +from rosidl_parser.definition import AbstractString +from rosidl_parser.definition import AbstractWString +from rosidl_parser.definition import Array +from rosidl_parser.definition import BasicType +from rosidl_parser.definition import EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME +from rosidl_parser.definition import NamespacedType +from rosidl_pycommon import convert_camel_case_to_lower_case_underscore + + +def primitive_msg_type_to_c(type_): + from rosidl_generator_c import BASIC_IDL_TYPES_TO_C + from rosidl_parser.definition import AbstractString + from rosidl_parser.definition import AbstractWString + from rosidl_parser.definition import BasicType + if isinstance(type_, AbstractString): + return 'rosidl_runtime_c__String' + if isinstance(type_, AbstractWString): + return 'rosidl_runtime_c__U16String' + assert isinstance(type_, BasicType) + return BASIC_IDL_TYPES_TO_C[type_.typename] + + +def primitive_msg_type_to_pymember_type(type_): + BASIC_IDL_TYPES_TO_PYMEMBER_TYPE = { + 'float': 'T_DOUBLE', + 'double': 'T_DOUBLE', + 'long double': 'T_DOUBLE', + 'char': 'T_CHAR', + 'wchar': 'T_USHORT', + 'boolean': 'T_BOOL', + 'octet': 'T_UBYTE', + 'uint8': 'T_UBYTE', + 'int8': 'T_BYTE', + 'uint16': 'T_USHORT', + 'int16': 'T_SHORT', + 'uint32': 'T_UINT', + 'int32': 'T_INT', + 'uint64': 'T_ULONGLONG', + 'int64': 'T_LONGLONG', + } + return BASIC_IDL_TYPES_TO_PYMEMBER_TYPE[type_.typename] + + +header_files = [ + 'Python.h', + 'stdbool.h', + 'structmember.h', + './_%s_decl.h' % package_name +] +}@ +@[for header_file in header_files]@ +@{ +repeated_header_file = header_file in include_directives +}@ +@[ if repeated_header_file]@ +// already included above +// @ +@[ else]@ +@{include_directives.add(header_file)}@ +@[ end if]@ +@[ if '/' not in header_file]@ +#include <@(header_file)> +@[ else]@ +#include "@(header_file)" +@[ end if]@ +@[end for]@ + + +static struct PyMemberDef @(message.structure.namespaced_type.name)Base_members[] = { +@[for member in message.structure.members]@ +@[ if len(message.structure.members) == 1 and member.name == EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@ +@[ continue]@ +@[ end if]@ + {"_@(member.name)", @ +@[ if isinstance(member.type, BasicType)]@ +@(primitive_msg_type_to_pymember_type(member.type)), @ +@[ else isinstance(member.type, AbstractGenericString)]@ +T_OBJECT, @ +@[ end if]@ +offsetof(@(message.structure.namespaced_type.name)Base, _@(member.name)), 0, NULL}, +@[end for]@ + {NULL} /* Sentinel */ +}; + +static void @(message.structure.namespaced_type.name)Base_dealloc(@(message.structure.namespaced_type.name)Base * self) +{ +@[for member in message.structure.members]@ +@[ if not isinstance(member.type, BasicType)]@ + Py_XDECREF(self->_@(member.name)); +@[ end if]@ +@[end for]@ + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +static PyTypeObject @(message.structure.namespaced_type.name)BaseType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "@(message.structure.namespaced_type.name)Base", + .tp_doc = "Base for @(message.structure.namespaced_type.name) python class", + .tp_basicsize = sizeof(@(message.structure.namespaced_type.name)Base), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = PyType_GenericNew, + .tp_dealloc = (destructor)@(message.structure.namespaced_type.name)Base_dealloc, + .tp_members = @(message.structure.namespaced_type.name)Base_members, +}; + + +@{ +type_name = convert_camel_case_to_lower_case_underscore(message.structure.namespaced_type.name) +register_function = '_register_base_msg_type__' + '__'.join(message.structure.namespaced_type.namespaces[1:] + [type_name]) +register_functions.append(register_function) +}@ +static int8_t @(register_function)(PyObject * module) +{ + if (PyType_Ready(&@(message.structure.namespaced_type.name)BaseType) < 0) { + return 1; + } + + Py_INCREF(&@(message.structure.namespaced_type.name)BaseType); + if (PyModule_AddObject(module, "@(message.structure.namespaced_type.name)Base", (PyObject *) &@(message.structure.namespaced_type.name)BaseType) < 0) { + Py_DECREF(&@(message.structure.namespaced_type.name)BaseType); + return 1; + } + return 0; +} diff --git a/rosidl_generator_py/resource/_msg_decl.h.em b/rosidl_generator_py/resource/_msg_decl.h.em new file mode 100644 index 00000000..1cddeaff --- /dev/null +++ b/rosidl_generator_py/resource/_msg_decl.h.em @@ -0,0 +1,116 @@ +@# Included from rosidl_generator_py/resource/_idl_pkg_decl.h.em +@{ +from rosidl_generator_py.generate_py_impl import SPECIAL_NESTED_BASIC_TYPES +from rosidl_parser.definition import AbstractNestedType +from rosidl_parser.definition import AbstractSequence +from rosidl_parser.definition import AbstractString +from rosidl_parser.definition import AbstractWString +from rosidl_parser.definition import Array +from rosidl_parser.definition import BasicType +from rosidl_parser.definition import EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME +from rosidl_parser.definition import NamespacedType +from rosidl_pycommon import convert_camel_case_to_lower_case_underscore + + +def primitive_msg_type_to_c(type_): + from rosidl_generator_c import BASIC_IDL_TYPES_TO_C + from rosidl_parser.definition import AbstractString + from rosidl_parser.definition import AbstractWString + from rosidl_parser.definition import BasicType + if isinstance(type_, AbstractString): + return 'rosidl_runtime_c__String' + if isinstance(type_, AbstractWString): + return 'rosidl_runtime_c__U16String' + assert isinstance(type_, BasicType) + return BASIC_IDL_TYPES_TO_C[type_.typename] + + +header_files = [ + 'Python.h', + 'stdbool.h', + 'structmember.h' +] +}@ +@[for header_file in header_files]@ +@{ +repeated_header_file = header_file in include_directives +}@ +@[ if repeated_header_file]@ +// already included above +// @ +@[ else]@ +@{include_directives.add(header_file)}@ +@[ end if]@ +@[ if '/' not in header_file]@ +#include <@(header_file)> +@[ else]@ +#include "@(header_file)" +@[ end if]@ +@[end for]@ + + +typedef struct +{ + PyObject_HEAD + /* Type-specific fields go here. */ +@[for member in message.structure.members]@ +@[ if len(message.structure.members) == 1 and member.name == EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@ +@[ continue]@ +@[ end if]@ +@[ if isinstance(member.type, BasicType)]@ +@[ if member.type.typename == 'float']@ + double _@(member.name); +@[ else]@ + @(primitive_msg_type_to_c(member.type)) _@(member.name); +@[ end if]@ +@[ else isinstance(member.type, AbstractGenericString)]@ + PyObject * _@(member.name); +@[ end if]@ +@[end for]@ +} @(message.structure.namespaced_type.name)Base; + +// Import-support constants for @(message.structure.namespaced_type.name)Base type +@{ +locally_declared = set() +}@ +@[for member in message.structure.members]@ +@[ if isinstance(member.type, AbstractNestedType) and isinstance(member.type.value_type, BasicType) and member.type.value_type.typename in SPECIAL_NESTED_BASIC_TYPES]@ +@[ if isinstance(member.type, Array)]@ +@[ if 'numpy.ndarray' not in required_py_modules]@ +#define NUMPY__NDARRAY__IMPORT_INDEX @(len(required_py_modules)) +@{ required_py_modules.append('numpy.ndarray')}@ +@[ elif 'numpy.ndarray' not in locally_declared]@ +// NUMPY__NDARRAY__IMPORT_INDEX defined above +@[ end if]@ +@{ locally_declared.add('numpy.ndarray')}@ +@{ dtype = SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype']}@ +@{ const_name = dtype.replace('.', '__').upper()}@ +@[ if dtype not in required_py_modules]@ +#define @(const_name)__IMPORT_INDEX @(len(required_py_modules)) +@{ required_py_modules.append(dtype)}@ +@[ elif dtype not in locally_declared]@ +// @(const_name)__IMPORT_INDEX defined above +@[ end if]@ +@{ locally_declared.add(dtype)}@ +@[ elif isinstance(member.type, AbstractSequence)]@ +@[ if 'array.array' not in required_py_modules]@ +#define ARRAY__ARRAY__IMPORT_INDEX @(len(required_py_modules)) +@{ required_py_modules.append('array.array')}@ +@[ elif 'array.array' not in locally_declared]@ +// ARRAY__ARRAY__IMPORT_INDEX defined above +@[ end if]@ +@{ locally_declared.add('array.array')}@ +@[ end if]@ +@[ end if]@ +@[end for]@ +@ +@{ +type_ = message.structure.namespaced_type +const_name = '__'.join(type_.namespaces + [convert_camel_case_to_lower_case_underscore(type_.name)]).upper() +# const_name = '__'.join(type_.namespaced_name()).upper() +assert const_name not in required_py_modules, "Const name collision" +const_value = len(required_py_modules) +required_py_modules.append(const_name) +}@ +#define @(const_name)__IMPORT_INDEX @(const_value) + diff --git a/rosidl_generator_py/resource/_msg_support.c.em b/rosidl_generator_py/resource/_msg_support.c.em index 36a29454..70360982 100644 --- a/rosidl_generator_py/resource/_msg_support.c.em +++ b/rosidl_generator_py/resource/_msg_support.c.em @@ -39,6 +39,7 @@ header_files = [ 'rosidl_runtime_c/visibility_control.h', include_base + '__struct.h', include_base + '__functions.h', + '../_%s_decl.h' % package_name, ] }@ @[for header_file in header_files]@ @@ -158,41 +159,25 @@ PyObject * @('__'.join(type_.namespaces + [convert_camel_case_to_lower_case_unde @{ module_name = '_' + convert_camel_case_to_lower_case_underscore(interface_path.stem) -class_module = '%s.%s' % ('.'.join(message.structure.namespaced_type.namespaces), module_name) -namespaced_type = message.structure.namespaced_type.name +import_index = '__'.join(message.structure.namespaced_type.namespaces + [convert_camel_case_to_lower_case_underscore(message.structure.namespaced_type.name)]).upper() +no_fields = len(message.structure.members) == 1 and message.structure.members[0].name == EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME }@ ROSIDL_GENERATOR_C_EXPORT bool @('__'.join(message.structure.namespaced_type.namespaces + [convert_camel_case_to_lower_case_underscore(message.structure.namespaced_type.name)]))__convert_from_py(PyObject * _pymsg, void * _ros_message) { // check that the passed message is of the expected Python class { - PyObject * class_attr = PyObject_GetAttrString(_pymsg, "__class__"); - if (class_attr == NULL) { - return false; - } - PyObject * name_attr = PyObject_GetAttrString(class_attr, "__name__"); - if (name_attr == NULL) { - Py_DECREF(class_attr); - return false; - } - PyObject * module_attr = PyObject_GetAttrString(class_attr, "__module__"); - if (module_attr == NULL) { - Py_DECREF(name_attr); - Py_DECREF(class_attr); - return false; - } - - // PyUnicode_1BYTE_DATA is just a cast - assert(strncmp("@(class_module)", (char *)PyUnicode_1BYTE_DATA(module_attr), @(len(class_module))) == 0); - assert(strncmp("@(namespaced_type)", (char *)PyUnicode_1BYTE_DATA(name_attr), @(len(namespaced_type))) == 0); - - Py_DECREF(module_attr); - Py_DECREF(name_attr); - Py_DECREF(class_attr); + PyTypeObject * py_type = (PyTypeObject *)@(package_name)__lazy_import(NULL, @(import_index)__IMPORT_INDEX); + assert(Py_TYPE(_pymsg) == py_type); } @(msg_typename) * ros_message = _ros_message; +@[if no_fields]@ + ros_message->@(member.name) = 0; +@[else] + @(message.structure.namespaced_type.name)Base * base_msg = (@(message.structure.namespaced_type.name)Base *)_pymsg; +@[end if]@ @[for member in message.structure.members]@ -@[ if len(message.structure.members) == 1 and member.name == EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@ +@[ if no_fields]@ ros_message->@(member.name) = 0; @[ continue]@ @[ end if]@ @@ -202,10 +187,12 @@ if isinstance(type_, AbstractNestedType): type_ = type_.value_type }@ { // @(member.name) - PyObject * field = PyObject_GetAttrString(_pymsg, "@(member.name)"); +@[ if not isinstance(member.type, BasicType)]@ + PyObject * field = base_msg->_@(member.name); if (!field) { return false; } +@[ end if]@ @[ if isinstance(type_, NamespacedType)]@ @{ nested_type = '__'.join(type_.namespaced_name()) @@ -213,20 +200,17 @@ nested_type = '__'.join(type_.namespaced_name()) @[ if isinstance(member.type, AbstractNestedType)]@ PyObject * seq_field = PySequence_Fast(field, "expected a sequence in '@(member.name)'"); if (!seq_field) { - Py_DECREF(field); return false; } @[ if isinstance(member.type, AbstractSequence)]@ Py_ssize_t size = PySequence_Size(field); if (-1 == size) { Py_DECREF(seq_field); - Py_DECREF(field); return false; } if (!@(nested_type)__Sequence__init(&(ros_message->@(member.name)), size)) { PyErr_SetString(PyExc_RuntimeError, "unable to create @(nested_type)__Sequence ros_message"); Py_DECREF(seq_field); - Py_DECREF(field); return false; } @(nested_type) * dest = ros_message->@(member.name).data; @@ -237,14 +221,12 @@ nested_type = '__'.join(type_.namespaced_name()) for (Py_ssize_t i = 0; i < size; ++i) { if (!@('__'.join(type_.namespaces + [convert_camel_case_to_lower_case_underscore(type_.name)]))__convert_from_py(PySequence_Fast_GET_ITEM(seq_field, i), &dest[i])) { Py_DECREF(seq_field); - Py_DECREF(field); return false; } } Py_DECREF(seq_field); @[ else]@ if (!@('__'.join(type_.namespaces + [convert_camel_case_to_lower_case_underscore(type_.name)]))__convert_from_py(field, &ros_message->@(member.name))) { - Py_DECREF(field); return false; } @[ end if]@ @@ -255,21 +237,18 @@ nested_type = '__'.join(type_.namespaced_name()) Py_buffer view; int rc = PyObject_GetBuffer(field, &view, PyBUF_SIMPLE); if (rc < 0) { - Py_DECREF(field); return false; } Py_ssize_t size = view.len / sizeof(@primitive_msg_type_to_c(member.type.value_type)); if (!rosidl_runtime_c__@(member.type.value_type.typename)__Sequence__init(&(ros_message->@(member.name)), size)) { PyErr_SetString(PyExc_RuntimeError, "unable to create @(member.type.value_type.typename)__Sequence ros_message"); PyBuffer_Release(&view); - Py_DECREF(field); return false; } @primitive_msg_type_to_c(member.type.value_type) * dest = ros_message->@(member.name).data; rc = PyBuffer_ToContiguous(dest, &view, view.len, 'C'); if (rc < 0) { PyBuffer_Release(&view); - Py_DECREF(field); return false; } PyBuffer_Release(&view); @@ -289,7 +268,6 @@ nested_type = '__'.join(type_.namespaced_name()) @[ else]@ PyObject * seq_field = PySequence_Fast(field, "expected a sequence in '@(member.name)'"); if (!seq_field) { - Py_DECREF(field); return false; } @[ end if]@ @@ -297,28 +275,24 @@ nested_type = '__'.join(type_.namespaced_name()) Py_ssize_t size = PySequence_Size(field); if (-1 == size) { Py_DECREF(seq_field); - Py_DECREF(field); return false; } @[ if isinstance(member.type.value_type, AbstractString)]@ if (!rosidl_runtime_c__String__Sequence__init(&(ros_message->@(member.name)), size)) { PyErr_SetString(PyExc_RuntimeError, "unable to create String__Sequence ros_message"); Py_DECREF(seq_field); - Py_DECREF(field); return false; } @[ elif isinstance(member.type.value_type, AbstractWString)]@ if (!rosidl_runtime_c__U16String__Sequence__init(&(ros_message->@(member.name)), size)) { PyErr_SetString(PyExc_RuntimeError, "unable to create U16String__Sequence ros_message"); Py_DECREF(seq_field); - Py_DECREF(field); return false; } @[ else]@ if (!rosidl_runtime_c__@(member.type.value_type.typename)__Sequence__init(&(ros_message->@(member.name)), size)) { PyErr_SetString(PyExc_RuntimeError, "unable to create @(member.type.value_type.typename)__Sequence ros_message"); Py_DECREF(seq_field); - Py_DECREF(field); return false; } @[ end if]@ @@ -332,7 +306,6 @@ nested_type = '__'.join(type_.namespaced_name()) PyObject * item = PySequence_Fast_GET_ITEM(seq_field, i); if (!item) { Py_DECREF(seq_field); - Py_DECREF(field); return false; } @[ end if]@ @@ -356,7 +329,6 @@ nested_type = '__'.join(type_.namespaced_name()) PyObject * encoded_item = PyUnicode_AsUTF8String(item); if (!encoded_item) { Py_DECREF(seq_field); - Py_DECREF(field); return false; } rosidl_runtime_c__String__assign(&dest[i], PyBytes_AS_STRING(encoded_item)); @@ -367,7 +339,6 @@ nested_type = '__'.join(type_.namespaced_name()) PyObject * encoded_item = PyUnicode_AsUTF16String(item); if (!encoded_item) { Py_DECREF(seq_field); - Py_DECREF(field); return false; } char * buffer; @@ -376,7 +347,6 @@ nested_type = '__'.join(type_.namespaced_name()) if (rc) { Py_DECREF(encoded_item); Py_DECREF(seq_field); - Py_DECREF(field); return false; } // use offset of 2 to skip BOM mark @@ -384,7 +354,6 @@ nested_type = '__'.join(type_.namespaced_name()) Py_DECREF(encoded_item); if (!succeeded) { Py_DECREF(seq_field); - Py_DECREF(field); return false; } @[ elif isinstance(member.type.value_type, BasicType) and member.type.value_type.typename == 'boolean']@ @@ -428,18 +397,6 @@ nested_type = '__'.join(type_.namespaced_name()) } Py_DECREF(seq_field); } -@[ elif isinstance(member.type, BasicType) and member.type.typename == 'char']@ - assert(PyUnicode_Check(field)); - PyObject * encoded_field = PyUnicode_AsUTF8String(field); - if (!encoded_field) { - Py_DECREF(field); - return false; - } - ros_message->@(member.name) = PyBytes_AS_STRING(encoded_field)[0]; - Py_DECREF(encoded_field); -@[ elif isinstance(member.type, BasicType) and member.type.typename == 'octet']@ - assert(PyBytes_Check(field)); - ros_message->@(member.name) = PyBytes_AS_STRING(field)[0]; @[ elif isinstance(member.type, AbstractString)]@ assert(PyUnicode_Check(field)); PyObject * encoded_field = PyUnicode_AsUTF8String(field); @@ -454,7 +411,6 @@ nested_type = '__'.join(type_.namespaced_name()) // the returned string starts with a BOM mark and uses native byte order PyObject * encoded_field = PyUnicode_AsUTF16String(field); if (!encoded_field) { - Py_DECREF(field); return false; } char * buffer; @@ -462,7 +418,6 @@ nested_type = '__'.join(type_.namespaced_name()) int rc = PyBytes_AsStringAndSize(encoded_field, &buffer, &length); if (rc) { Py_DECREF(encoded_field); - Py_DECREF(field); return false; } // use offset of 2 to skip BOM mark @@ -470,48 +425,18 @@ nested_type = '__'.join(type_.namespaced_name()) bool succeeded = rosidl_runtime_c__U16String__assignn_from_char(&ros_message->@(member.name), buffer + 2, length - 2); Py_DECREF(encoded_field); if (!succeeded) { - Py_DECREF(field); return false; } } -@[ elif isinstance(member.type, BasicType) and member.type.typename == 'boolean']@ - assert(PyBool_Check(field)); - ros_message->@(member.name) = (Py_True == field); -@[ elif isinstance(member.type, BasicType) and member.type.typename in ('float', 'double')]@ - assert(PyFloat_Check(field)); +@[ elif isinstance(member.type, BasicType)]@ @[ if member.type.typename == 'float']@ - ros_message->@(member.name) = (float)PyFloat_AS_DOUBLE(field); + ros_message->@(member.name) = (float)base_msg->_@(member.name); @[ else]@ - ros_message->@(member.name) = PyFloat_AS_DOUBLE(field); + ros_message->@(member.name) = base_msg->_@(member.name); @[ end if]@ -@[ elif isinstance(member.type, BasicType) and member.type.typename in ( - 'int8', - 'int16', - 'int32', - )]@ - assert(PyLong_Check(field)); - ros_message->@(member.name) = (@(primitive_msg_type_to_c(member.type)))PyLong_AsLong(field); -@[ elif isinstance(member.type, BasicType) and member.type.typename in ( - 'uint8', - 'uint16', - 'uint32', - )]@ - assert(PyLong_Check(field)); -@[ if member.type.typename == 'uint32']@ - ros_message->@(member.name) = PyLong_AsUnsignedLong(field); -@[ else]@ - ros_message->@(member.name) = (@(primitive_msg_type_to_c(member.type)))PyLong_AsUnsignedLong(field); -@[ end if]@ -@[ elif isinstance(member.type, BasicType) and member.type.typename == 'int64']@ - assert(PyLong_Check(field)); - ros_message->@(member.name) = PyLong_AsLongLong(field); -@[ elif isinstance(member.type, BasicType) and member.type.typename == 'uint64']@ - assert(PyLong_Check(field)); - ros_message->@(member.name) = PyLong_AsUnsignedLongLong(field); @[ else]@ assert(false); @[ end if]@ - Py_DECREF(field); } @[end for]@ @@ -522,26 +447,32 @@ ROSIDL_GENERATOR_C_EXPORT PyObject * @('__'.join(message.structure.namespaced_type.namespaces + [convert_camel_case_to_lower_case_underscore(message.structure.namespaced_type.name)]))__convert_to_py(void * raw_ros_message) { /* NOTE(esteve): Call constructor of @(message.structure.namespaced_type.name) */ - PyObject * _pymessage = NULL; + @(message.structure.namespaced_type.name)Base * _pymessage = NULL; { - PyObject * pymessage_module = PyImport_ImportModule("@('.'.join(message.structure.namespaced_type.namespaces)).@(module_name)"); - assert(pymessage_module); - PyObject * pymessage_class = PyObject_GetAttrString(pymessage_module, "@(message.structure.namespaced_type.name)"); - assert(pymessage_class); - Py_DECREF(pymessage_module); - _pymessage = PyObject_CallObject(pymessage_class, NULL); - Py_DECREF(pymessage_class); + PyTypeObject * type = (PyTypeObject *)@(package_name)__lazy_import(NULL, @(import_index)__IMPORT_INDEX); + if (!type) { + return NULL; + } + assert(type->tp_new); + + PyObject * empty_tuple = PyTuple_New(0); + if (!empty_tuple) { + return NULL; + } + + _pymessage = (@(message.structure.namespaced_type.name)Base *)type->tp_new(type, empty_tuple, NULL); + Py_DECREF(empty_tuple); if (!_pymessage) { return NULL; } } -@[if len(message.structure.members) == 1 and member.name == EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@ +@[if no_fields]@ (void)raw_ros_message; @[else]@ @(msg_typename) * ros_message = (@(msg_typename) *)raw_ros_message; @[end if]@ @[for member in message.structure.members]@ -@[ if len(message.structure.members) == 1 and member.name == EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@ +@[ if no_fields]@ @[ continue]@ @[ end if]@ @{ @@ -550,15 +481,23 @@ if isinstance(type_, AbstractNestedType): type_ = type_.value_type }@ { // @(member.name) +@[ if not isinstance(member.type, BasicType)]@ PyObject * field = NULL; +@[ end if]@ @[ if isinstance(member.type, AbstractNestedType) and isinstance(member.type.value_type, BasicType) and member.type.value_type.typename in SPECIAL_NESTED_BASIC_TYPES]@ @[ if isinstance(member.type, Array)]@ - field = PyObject_GetAttrString(_pymessage, "@(member.name)"); - if (!field) { +@{ dtype = SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype']} + PyObject * array_type = @(package_name)__lazy_import(NULL, NUMPY__NDARRAY__IMPORT_INDEX); + if (!array_type) { return NULL; } - assert(field->ob_type != NULL); - assert(field->ob_type->tp_name != NULL); + PyObject * element_type = @(package_name)__lazy_import(NULL, @(dtype.replace('.', '__').upper())__IMPORT_INDEX); + if (!element_type) { + return NULL; + } + PyObject * dims = Py_BuildValue("(i)", @(member.type.size)); + field = PyObject_CallFunctionObjArgs(array_type, dims, element_type, NULL); + Py_DECREF(dims); assert(strcmp(field->ob_type->tp_name, "numpy.ndarray") == 0); PyArrayObject * seq_field = (PyArrayObject *)field; assert(PyArray_NDIM(seq_field) == 1); @@ -567,11 +506,20 @@ if isinstance(type_, AbstractNestedType): @(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype'].replace('numpy.', 'npy_')) * dst = (@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['dtype'].replace('numpy.', 'npy_')) *)PyArray_GETPTR1(seq_field, 0); @primitive_msg_type_to_c(member.type.value_type) * src = &(ros_message->@(member.name)[0]); memcpy(dst, src, @(member.type.size) * sizeof(@primitive_msg_type_to_c(member.type.value_type))); - Py_DECREF(field); @[ elif isinstance(member.type, AbstractSequence)]@ - field = PyObject_GetAttrString(_pymessage, "@(member.name)"); - if (!field) { - return NULL; + { + PyObject * array_type = @(package_name)__lazy_import(NULL, ARRAY__ARRAY__IMPORT_INDEX); + if (!array_type) { + return NULL; + } + PyObject * type_code = PyUnicode_FromOrdinal('@(SPECIAL_NESTED_BASIC_TYPES[member.type.value_type.typename]['type_code'])'); + assert(type_code); + + field = PyObject_CallFunctionObjArgs(array_type, type_code, NULL); + Py_DECREF(type_code); + if (!field) { + return NULL; + } } assert(field->ob_type != NULL); assert(field->ob_type->tp_name != NULL); @@ -622,7 +570,6 @@ if isinstance(type_, AbstractNestedType): } Py_DECREF(ret); } - Py_DECREF(field); @[ end if]@ @[ else]@ @[ if isinstance(type_, NamespacedType)]@ @@ -736,16 +683,6 @@ nested_type = '__'.join(type_.namespaced_name()) @[ end if]@ } assert(PySequence_Check(field)); -@[ elif isinstance(member.type, BasicType) and member.type.typename == 'char']@ - field = Py_BuildValue("C", ros_message->@(member.name)); - if (!field) { - return NULL; - } -@[ elif isinstance(member.type, BasicType) and member.type.typename == 'octet']@ - field = PyBytes_FromStringAndSize((const char *)&ros_message->@(member.name), 1); - if (!field) { - return NULL; - } @[ elif isinstance(member.type, AbstractString)]@ field = PyUnicode_DecodeUTF8( ros_message->@(member.name).data, @@ -763,41 +700,22 @@ nested_type = '__'.join(type_.namespaced_name()) if (!field) { return NULL; } -@[ elif isinstance(member.type, BasicType) and member.type.typename == 'boolean']@ -@# using PyBool_FromLong allows treating the variable uniformly by calling Py_DECREF on it later - field = PyBool_FromLong(ros_message->@(member.name) ? 1 : 0); -@[ elif isinstance(member.type, BasicType) and member.type.typename in ('float', 'double')]@ - field = PyFloat_FromDouble(ros_message->@(member.name)); -@[ elif isinstance(member.type, BasicType) and member.type.typename in ( - 'int8', - 'int16', - 'int32', - )]@ - field = PyLong_FromLong(ros_message->@(member.name)); -@[ elif isinstance(member.type, BasicType) and member.type.typename in ( - 'uint8', - 'uint16', - 'uint32', - )]@ - field = PyLong_FromUnsignedLong(ros_message->@(member.name)); -@[ elif isinstance(member.type, BasicType) and member.type.typename == 'int64']@ - field = PyLong_FromLongLong(ros_message->@(member.name)); -@[ elif isinstance(member.type, BasicType) and member.type.typename == 'uint64']@ - field = PyLong_FromUnsignedLongLong(ros_message->@(member.name)); +@[ elif isinstance(member.type, BasicType)]@ +@# Basic types are copied directly @[ else]@ assert(false); @[ end if]@ - { - int rc = PyObject_SetAttrString(_pymessage, "@(member.name)", field); - Py_DECREF(field); - if (rc) { - return NULL; - } - } +@[ end if]@ +@ +@[ if isinstance(member.type, BasicType)]@ + _pymessage->_@(member.name) = ros_message->@(member.name); +@[ else]@ + // reference is transfered to object + _pymessage->_@(member.name) = field; @[ end if]@ } @[end for]@ // ownership of _pymessage is transferred to the caller - return _pymessage; + return (PyObject *)_pymessage; } diff --git a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py index 0cbaaa10..3cd99e57 100644 --- a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py +++ b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py @@ -192,6 +192,23 @@ def print_warning_if_reserved_keyword(member_name, interface_type, interface_nam minimum_timestamp=latest_target_timestamp) generated_files.append(generated_file) + # expand templates which are common for all typesupport implementation + for template_file, output_file in ( + ('_idl_pkg_bases.c.em', '_%s_bases.c' % package_name), + ('_idl_pkg_import.c.em', '_%s_import.c' % package_name), + ('_idl_pkg_decl.h.em', '_%s_decl.h' % package_name), + ): + template_file = os.path.join(template_dir, template_file) + generated_base_file = os.path.join(args['output_dir'], output_file) + data = { + 'package_name': args['package_name'], + 'content': idl_content, + } + expand_template( + template_file, data, generated_base_file, + minimum_timestamp=latest_target_timestamp) + generated_files.append(generated_file) + return generated_files