diff --git a/src/main/java/com/eprosima/fastcdr/idl/templates/TypesHeader.stg b/src/main/java/com/eprosima/fastcdr/idl/templates/TypesHeader.stg index 572862c3..ddac0b03 100644 --- a/src/main/java/com/eprosima/fastcdr/idl/templates/TypesHeader.stg +++ b/src/main/java/com/eprosima/fastcdr/idl/templates/TypesHeader.stg @@ -190,10 +190,12 @@ private: interface(ctx, parent, interface, export_list) ::= << +$if(interface.withOutputParameters)$ namespace detail { $interface.operations : { op | $if(op.outputparam)$$operation_out_struct_fwd_decl(interface, op)$$endif$}$ -} +} // namespace detail +$endif$ /*! * @brief This class represents the interface $interface.name$ defined by the user in the IDL file. @@ -207,10 +209,12 @@ public: $export_list$ }; +$if(interface.withOutputParameters)$ namespace detail { $interface.operations : { op | $if(op.outputparam)$$operation_out_struct(interface, op)$$"\n"$$endif$}$ -} +} // namespace detail +$endif$ >> operation(ctx, parent, operation, param_list, operation_type) ::= << diff --git a/src/main/java/com/eprosima/fastcdr/idl/templates/TypesSwigInterface.stg b/src/main/java/com/eprosima/fastcdr/idl/templates/TypesSwigInterface.stg index 49fd4092..4714f46a 100644 --- a/src/main/java/com/eprosima/fastcdr/idl/templates/TypesSwigInterface.stg +++ b/src/main/java/com/eprosima/fastcdr/idl/templates/TypesSwigInterface.stg @@ -15,11 +15,20 @@ group TypesSwigInterface; import "com/eprosima/fastdds/idl/templates/eprosima.stg" +import "FastCdrCommon.stg" main(ctx, definitions) ::= << $fileHeader(ctx=ctx, file=[ctx.filename, ".i"], description=["This header file contains the SWIG interface of the described types in the IDL file."])$ -%module(moduleimport="if __import__('os').name == 'nt': import win32api; win32api.LoadLibrary('$ctx.filename$.dll')\nif __package__ or '.' in __name__:\n from . import _$ctx.filename$Wrapper\nelse:\n import _$ctx.filename$Wrapper") $ctx.filename$ +%module($if(ctx.thereIsInterface)$threads="1",directors="1",$endif$moduleimport="if __import__('os').name == 'nt': import win32api; win32api.LoadLibrary('$ctx.filename$.dll')\nif __package__ or '.' in __name__:\n from . import _$ctx.filename$Wrapper\nelse:\n import _$ctx.filename$Wrapper") $ctx.filename$ + +$if(ctx.thereIsInterface)$ +// We have enabled threads because the RPC server directors will call the target language environment +// from the C++ server threads, but we don't want the calls from the target language to release their +// locks (e.g. Python GIL) when calling the C++ methods. +// See a very nice explanation at https://github.com/swig/swig/issues/927#issuecomment-289279243 +%feature("nothreadallow"); +$endif$ // If using windows in debug, it would try to use python_d, which would not be found. %begin %{ @@ -30,9 +39,16 @@ $fileHeader(ctx=ctx, file=[ctx.filename, ".i"], description=["This header file c %} // SWIG helper modules +$if(ctx.thereIsInterface)$ +%include "exception.i" +$endif$ %include "stdint.i" %include "std_array.i" %include "std_map.i" +$if(ctx.thereIsInterface)$ +%include "std_pair.i" +%include "std_shared_ptr.i" +$endif$ %include "std_string.i" %include "std_vector.i" %include "typemaps.i" @@ -47,6 +63,11 @@ $ctx.directIncludeDependencies : {include | %include "$include$.i"}; separator=" %{ #include "$ctx.filename$.hpp" +$if(ctx.thereIsInterface)$ +#include "$ctx.filename$Client.hpp" +#include "$ctx.filename$Server.hpp" +#include "$ctx.filename$ServerImpl.hpp" +$endif$ #include %} @@ -62,6 +83,118 @@ $endif$ %import(module="fastdds") "fastdds/dds/core/LoanableTypedCollection.hpp" %import(module="fastdds") "fastdds/dds/core/LoanableSequence.hpp" +$if(ctx.thereIsInterface)$ +%import(module="fastdds") "fastdds/dds/rpc/exceptions/RpcException.hpp" +%import(module="fastdds") "fastdds/dds/rpc/exceptions/RpcOperationError.hpp" + +%exception { + try + { + \$action + } + catch (const eprosima::fastdds::dds::rpc::RpcException& ex) + { + SWIG_exception(SWIG_RuntimeError, ex.what()); + } + catch (const std::exception& ex) + { + SWIG_exception(SWIG_RuntimeError, ex.what()); + } + catch (...) + { + SWIG_exception(SWIG_RuntimeError,"Unknown exception"); + } +} + +$if(ctx.thereIsOutputFeed)$ +%import(module="fastdds") "fastdds/dds/rpc/interfaces/RpcServerWriter.hpp" +%ignore eprosima::fastdds::dds::rpc::RpcClientReader::read(T&); +%ignore eprosima::fastdds::dds::rpc::RpcClientReader::read(T&,eprosima::fastdds::dds::Duration_t&); +%import(module="fastdds") "fastdds/dds/rpc/interfaces/RpcClientReader.hpp" +%extend eprosima::fastdds::dds::rpc::RpcClientReader { + std::pair read( + const eprosima::fastdds::dds::Duration_t& timeout = eprosima::fastdds::dds::c_TimeInfinite) + { + std::pair ret_val{}; + if (eprosima::fastdds::dds::c_TimeInfinite == timeout) + { + ret_val.first = self->read(ret_val.second); + } + else + { + ret_val.first = self->read(ret_val.second, timeout); + } + return ret_val; + } +} + +$ctx.outputFeedTypes : {feed_type | $output_feed(feed_type)$}; separator="\n\n"$ +$endif$ + +$if(ctx.thereIsNonFeedOperation)$ +// Code for std::future taken from https://github.com/swig/swig/issues/1828#issuecomment-648449092 +namespace eprosima::fastdds::dds::rpc +{ +template +class RpcFuture { + public: + RpcFuture() noexcept; + RpcFuture(RpcFuture &&) noexcept; + RpcFuture(const RpcFuture& rhs) = delete; + ~RpcFuture(); + RpcFuture& operator=(const RpcFuture& rhs) = delete; + RpcFuture& operator=(RpcFuture&&) noexcept; + + // retrieving the value + R get(); + + // functions to check state + bool valid() const noexcept; + void wait() const; + +/* + template + future_status wait_for(const chrono::duration& rel_time) const; + template + future_status wait_until(const chrono::time_point& abs_time) const; +*/ +}; + +} + +$ctx.outputNonFeedTypes : {non_feed_type | $output_non_feed(non_feed_type)$}; separator="\n\n"$ +$endif$ + +$if(ctx.thereIsInputFeed)$ +%import(module="fastdds") "fastdds/dds/rpc/interfaces/RpcClientWriter.hpp" +%import(module="fastdds") "fastdds/dds/rpc/interfaces/RpcStatusCode.hpp" + +%ignore eprosima::fastdds::dds::rpc::RpcServerReader::read(T&); +%ignore eprosima::fastdds::dds::rpc::RpcServerReader::read(T&,eprosima::fastdds::dds::Duration_t&); +%import(module="fastdds") "fastdds/dds/rpc/interfaces/RpcServerReader.hpp" +%extend eprosima::fastdds::dds::rpc::RpcServerReader { + std::pair read( + const eprosima::fastdds::dds::Duration_t& timeout = eprosima::fastdds::dds::c_TimeInfinite) + { + std::pair ret_val{}; + if (eprosima::fastdds::dds::c_TimeInfinite == timeout) + { + ret_val.first = self->read(ret_val.second); + } + else + { + ret_val.first = self->read(ret_val.second, timeout); + } + return ret_val; + } +} + +$ctx.inputFeedTypes : {feed_type | $input_feed(feed_type)$}; separator="\n\n"$ +$endif$ + +%exception; +$endif$ + %define %traits_penumn(Type...) %fragment(SWIG_Traits_frag(Type),"header", fragment="StdTraits") { @@ -78,6 +211,11 @@ $definitions; separator="\n"$ // Include the class interfaces %include "$ctx.filename$.hpp" +$if(ctx.thereIsInterface)$ +%include "$ctx.filename$Client.hpp" +%include "$ctx.filename$Server.hpp" +%include "$ctx.filename$ServerImpl.hpp" +$endif$ // Include the corresponding TopicDataType %include "$ctx.filename$PubSubTypes.i" @@ -241,3 +379,65 @@ bitset_type(ctx, parent, bitset, extensions) ::= << enum_type(ctx, parent, enum) ::= << %traits_penumn(enum $enum.cppTypename$); >> + +interface(ctx, parent, interface, export_list) ::= << + +$export_list$ + +%shared_ptr($interface.scopedname$); +$if(!interface.annotatedAsNested)$ +%shared_ptr($interface.scopedname$Server); +%extend $interface.scopedname$Server +{ + void run() + { + Py_BEGIN_ALLOW_THREADS + self->run(); + Py_END_ALLOW_THREADS + } +} + +%shared_ptr($interface.scopedname$Server_IServerImplementation); +%shared_ptr($interface.scopedname$ServerImplementation); +%feature("director") $interface.scopedname$ServerImplementation; +$endif$ +>> + +output_non_feed(type) ::= << +%shared_ptr(eprosima::fastdds::dds::rpc::RpcFuture<$type.cppTypename$>); +%template($type.formatedCppTypename$_rpc_future) eprosima::fastdds::dds::rpc::RpcFuture<$type.cppTypename$>; + +$! +// Combine the typemap from shared_ptr +// https://github.com/swig/swig/blob/b96b955ca15a01f0425fb26c234528530923202a/Lib/python/boost_shared_ptr.i#L41-L44 +// with the use of the 'optimal' attribute to avoid the need for a copy constructor, inspired by +// https://github.com/swig/swig/issues/1828#issuecomment-648449092 +!$ +%typemap(out, optimal="1") eprosima::fastdds::dds::rpc::RpcFuture<$type.cppTypename$> { + std::shared_ptr<\$1_ltype> *smartresult = new std::shared_ptr<\$1_ltype>(new \$1_ltype(\$1)); + \$result = SWIG_NewPointerObj(SWIG_as_voidptr(smartresult), \$descriptor(std::shared_ptr< eprosima::fastdds::dds::rpc::RpcFuture<$type.cppTypename$\>> *), SWIG_POINTER_OWN); +} +>> + +output_feed(type) ::= << +%shared_ptr(eprosima::fastdds::dds::rpc::RpcClientReader<$type.cppTypename$>); +%template($type.formatedCppTypename$_client_reader_result) std::pair; +%template($type.formatedCppTypename$_client_reader) eprosima::fastdds::dds::rpc::RpcClientReader<$type.cppTypename$>; + +%template($type.formatedCppTypename$_server_writer) eprosima::fastdds::dds::rpc::RpcServerWriter<$type.cppTypename$>; +>> + +input_feed(type) ::= << +%template($type.formatedCppTypename$_server_reader_result) std::pair; +%template($type.formatedCppTypename$_server_reader) eprosima::fastdds::dds::rpc::RpcServerReader<$type.cppTypename$>; + +%shared_ptr(eprosima::fastdds::dds::rpc::RpcClientWriter<$type.cppTypename$>); +%template($type.formatedCppTypename$_rpc_client_writer) eprosima::fastdds::dds::rpc::RpcClientWriter<$type.cppTypename$>; +%typemap(in,numinputs=0) std::shared_ptr>& %{ + \$1 = new std::shared_ptr>(); +%} +%typemap(argout) std::shared_ptr>& (PyObject* tmp) %{ + tmp = SWIG_NewPointerObj(\$1, \$1_descriptor, SWIG_POINTER_OWN); + \$result = SWIG_Python_AppendOutput(\$result, tmp); +%} +>> diff --git a/src/main/java/com/eprosima/fastdds/idl/grammar/Context.java b/src/main/java/com/eprosima/fastdds/idl/grammar/Context.java index 53337fa7..d158b5c4 100644 --- a/src/main/java/com/eprosima/fastdds/idl/grammar/Context.java +++ b/src/main/java/com/eprosima/fastdds/idl/grammar/Context.java @@ -14,6 +14,8 @@ package com.eprosima.fastdds.idl.grammar; +import com.eprosima.fastdds.idl.grammar.Operation; +import com.eprosima.fastdds.idl.grammar.Param; import com.eprosima.fastdds.idl.parser.typecode.AliasTypeCode; import com.eprosima.fastdds.idl.parser.typecode.ArrayTypeCode; import com.eprosima.fastdds.idl.parser.typecode.BitmaskTypeCode; @@ -47,14 +49,35 @@ import java.util.Map.Entry; import java.util.AbstractMap; import java.util.AbstractMap.SimpleEntry; +import java.util.HashMap; import java.util.Stack; import java.util.stream.Collectors; import java.util.stream.Stream; import org.antlr.v4.runtime.Token; - public class Context extends com.eprosima.idl.context.Context implements com.eprosima.fastcdr.idl.context.Context { + + public class TypeNamePair + { + public TypeNamePair(TypeCode type) + { + if (null != type) + { + this.cppTypename = type.getCppTypename(); + this.formatedCppTypename = type.getFormatedCppTypename(); + } + else + { + this.cppTypename = "void"; + this.formatedCppTypename = "void"; + } + } + + public String cppTypename; + public String formatedCppTypename; + } + // TODO Remove middleware parameter. It is temporal while cdr and rest don't have async functions. public Context( TemplateManager tmanager, @@ -489,37 +512,76 @@ public boolean isThereIsInterface() return there_is_at_least_one_interface; } - public void setThereIsInputFeed( - boolean value) + public void inputFeedAdded( + Param p) { - there_is_at_least_one_input_feed = value; + TypeNamePair type_pair = new TypeNamePair(p.getTypecode()); + m_input_feed_types.putIfAbsent(type_pair.cppTypename, type_pair); } - public boolean isThereIsInputFeed() + /*! + * @ingroup api_for_stg + * @brief This function returns the list of types used in input feeds. + */ + public ArrayList getInputFeedTypes() { - return there_is_at_least_one_input_feed; + return new ArrayList(m_input_feed_types.values()); } - public boolean setThereIsOutputFeed( - boolean value) + public boolean isThereIsInputFeed() { - return there_is_at_least_one_output_feed = value; + return !m_input_feed_types.isEmpty(); } public boolean isThereIsOutputFeed() { - return there_is_at_least_one_output_feed; + return !m_output_feed_types.isEmpty(); } - public boolean setThereIsNonFeedOperation( - boolean value) + public boolean isThereIsNonFeedOperation() { - return there_is_at_least_one_non_feed_operation = value; + return !m_output_non_feed_types.isEmpty(); } - public boolean isThereIsNonFeedOperation() + public void operationAdded( + Operation op) + { + TypeNamePair type_pair; + if (op.getOutputparam().size() > 0) + { + type_pair = new TypeNamePair(op.getOutTypeCode()); + } + else + { + type_pair = new TypeNamePair(op.getRettype()); + } + + if (op.isAnnotationFeed()) + { + m_output_feed_types.putIfAbsent(type_pair.cppTypename, type_pair); + } + else + { + m_output_non_feed_types.putIfAbsent(type_pair.cppTypename, type_pair); + } + } + + /*! + * @ingroup api_for_stg + * @brief This function returns the list of types used in output feeds. + */ + public ArrayList getOutputFeedTypes() + { + return new ArrayList(m_output_feed_types.values()); + } + + /*! + * @ingroup api_for_stg + * @brief This function returns the list of types used as operation return types. + */ + public ArrayList getOutputNonFeedTypes() { - return there_is_at_least_one_non_feed_operation; + return new ArrayList(m_output_non_feed_types.values()); } /*** Functions inherited from FastCDR Context ***/ @@ -854,11 +916,11 @@ public Param createParam( private boolean there_is_at_least_one_exception = false; - private boolean there_is_at_least_one_input_feed = false; + private Map m_input_feed_types = new HashMap(); - private boolean there_is_at_least_one_output_feed = false; + private Map m_output_feed_types = new HashMap(); - private boolean there_is_at_least_one_non_feed_operation = false; + private Map m_output_non_feed_types = new HashMap(); private boolean there_is_at_least_one_interface = false; } diff --git a/src/main/java/com/eprosima/fastdds/idl/grammar/Interface.java b/src/main/java/com/eprosima/fastdds/idl/grammar/Interface.java index 3bacaa39..75a6faa8 100644 --- a/src/main/java/com/eprosima/fastdds/idl/grammar/Interface.java +++ b/src/main/java/com/eprosima/fastdds/idl/grammar/Interface.java @@ -33,21 +33,23 @@ public Interface(Context ctx, String scopeFile, boolean isInScope, String scope, @Override public void add(com.eprosima.idl.parser.tree.Export exp) { + super.add(exp); + if (exp instanceof Operation) { Operation op = (Operation)exp; if (op.isAnnotationFeed()) { m_hasOutputFeeds = true; - m_context.setThereIsOutputFeed(true); } - else + + if (op.getOutputparam().size() > 0) { - m_context.setThereIsNonFeedOperation(true); + m_has_operations_with_output_arguments = true; } - } - super.add(exp); + m_context.operationAdded(op); + } } /*! @@ -60,6 +62,18 @@ public boolean isWithOutputFeeds() return m_hasOutputFeeds; } + /*! + * @ingroup api_for_stg + * + * @brief This function is used to check if the interface has operations with output arguments. + * + * @return True if the interface has operations with output arguments, false otherwise. + */ + public boolean isWithOutputParameters() + { + return m_has_operations_with_output_arguments; + } + /*! * @brief This function is used in stringtemplates to generate the typesupport code for the interface. * @@ -164,6 +178,7 @@ private EnumTypeCode get_remoteExceptionCode_t_type() private Context m_context = null; private boolean m_hasOutputFeeds = false; + private boolean m_has_operations_with_output_arguments = false; private StructTypeCode m_request_type = null; private StructTypeCode m_reply_type = null; static private EnumTypeCode m_remoteExceptionCode_t_type = null; diff --git a/src/main/java/com/eprosima/fastdds/idl/grammar/Operation.java b/src/main/java/com/eprosima/fastdds/idl/grammar/Operation.java index ffd031ea..ccc7a882 100644 --- a/src/main/java/com/eprosima/fastdds/idl/grammar/Operation.java +++ b/src/main/java/com/eprosima/fastdds/idl/grammar/Operation.java @@ -85,7 +85,7 @@ public void add(com.eprosima.idl.parser.tree.Param param) else { // Take note that there is at least one input feed - m_context.setThereIsInputFeed(true); + m_context.inputFeedAdded(p); m_hasInputFeeds = true; } } diff --git a/src/main/java/com/eprosima/fastdds/idl/templates/ClientSource.stg b/src/main/java/com/eprosima/fastdds/idl/templates/ClientSource.stg index e4f0d134..871e4167 100644 --- a/src/main/java/com/eprosima/fastdds/idl/templates/ClientSource.stg +++ b/src/main/java/com/eprosima/fastdds/idl/templates/ClientSource.stg @@ -477,7 +477,7 @@ $if(operation.annotationFeed)$ } void set_exception( - std::exception_ptr exception) + std::exception_ptr exception) override { std::lock_guard _(mtx_); if (!finished_) diff --git a/src/main/java/com/eprosima/fastdds/idl/templates/ServerSource.stg b/src/main/java/com/eprosima/fastdds/idl/templates/ServerSource.stg index db9cf0f7..ab51748c 100644 --- a/src/main/java/com/eprosima/fastdds/idl/templates/ServerSource.stg +++ b/src/main/java/com/eprosima/fastdds/idl/templates/ServerSource.stg @@ -194,7 +194,7 @@ public: private: //{ Output feed helpers - + struct IOutputFeedCancellator { virtual ~IOutputFeedCancellator() = default; @@ -770,7 +770,7 @@ $endif$ $endif$ } $if(op.annotationFeed)$ - catch (const frpc::RpcFeedCancelledException& ex) + catch (const frpc::RpcFeedCancelledException& /*ex*/) { ReplyType reply{}; reply.$op.name$ = $op.resultTypeCode.scopedname${}; diff --git a/src/main/java/com/eprosima/fastdds/idl/templates/SwigCMake.stg b/src/main/java/com/eprosima/fastdds/idl/templates/SwigCMake.stg index ccb62e84..8c621982 100644 --- a/src/main/java/com/eprosima/fastdds/idl/templates/SwigCMake.stg +++ b/src/main/java/com/eprosima/fastdds/idl/templates/SwigCMake.stg @@ -16,6 +16,20 @@ group SwigCMake; swig_cmake(solution) ::= << +# Copyright 2025 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cmake_minimum_required(VERSION 3.20) # SWIG: use standard target name.