#pragma once #include "ddkit/access/DdsWrap.hpp" #include "ddkit/access/Topic.hpp" #include "ddkit/access/Listener.hpp" #include "ddkit/access/ConnectState.hpp" #include "ddkit/utils/Logger.hpp" namespace ddkit { namespace access { template class Writer final { public: explicit Writer(std::shared_ptr> topic, dds::pub::qos::DataWriterQos dataWriterQos, dds::pub::qos::PublisherQos pubQos) : m_connectState(std::make_shared(topic->topicName(), topic->topicNameModule(), topic->topicNameTopic())) , m_listener(std::make_shared >(m_connectState)) , m_writer(dataWriter(topic, m_listener.get(), dataWriterQos, pubQos)) { DDKIT_LOG_TRACE; } ~Writer() { DDKIT_LOG_TRACE; // ddkit::log::info() << "closing writer!"; m_writer.close(); } bool isConnected() { return m_connectState->isConnected(); } void waitUntilConnected() { m_connectState->waitUntilConnected(); } IConnectCheckPtr getConnectCheck() { return m_connectState; } /// @brief isOkUnconnected == true -> do not warn about write while not connected /// - one could set this on a late-join-topic to avoid the warnings /// isThrowUnconnected == true -> do throw momentarily /// - one could set this on a 'checked' write ('Cmd') void send(T const& sample, bool isOkUnconnected, bool isThrowUnconnected) { ddkit::log::debug() << "send: " << DDKIT_LOG_VAR_VALUE(sample); bool isConnected = m_connectState->isConnected(); // return immediately, if Service for command is not yet connected (and told to throw) if (!isConnected && isThrowUnconnected) { throw std::runtime_error("Service for Cmd is unavailable yet - try later."); } // todo: as monitor-sessions are not distinguished so far, this (direct) exception will not be thrown, // if a monitor is running and connected // warn about probably lost samples (if not told otherwise) if (!isConnected && !isOkUnconnected) { ddkit::log::info() << "Writer not connected while sending (" << dds::topic::topic_type_name::value() << ")."; } // do the write m_writer.write(sample); } void dispose(T const& sample) { DDKIT_LOG_TRACE; ddkit::log::debug() << "dispose: " << DDKIT_LOG_VAR_VALUE(sample); // Get handle of instance auto handle = m_writer.lookup_instance(sample); // Instance has to be known if (handle.is_nil()) { ddkit::log::error() << "Tried to remove object \"" << sample << "\", which does not exist in dds."; return; } // We have to do both `dispose_instance` and `unregister_instance` // to fully free the instance. If we would just dispose the instance // the instance would remain stored by the reader and writer since it // remains `active`. Only after the `unregister_instance` the instance // has no associated writer and can be freed. m_writer.dispose_instance(handle); m_writer.unregister_instance(handle); } private: ConnectStatePtr m_connectState; std::shared_ptr> m_listener; dds::pub::DataWriter m_writer; /// Note on writer-listeners: /// - listeners have to be set in the constructor of the writer, because /// an event could be missing, when the listener is set afterwards /// - all depending on how fast discovery etc is, see also here: /// https://github.com/eclipse-cyclonedds/cyclonedds-cxx/issues/378#issuecomment-1463626212 static dds::pub::DataWriter dataWriter( std::shared_ptr> topic, ListenerWrite* listener, dds::pub::qos::DataWriterQos dataWriterQos, dds::pub::qos::PublisherQos pubQos) { DDKIT_LOG_TRACE; dds::pub::DataWriter ddsWriter = dds::pub::DataWriter( dds::pub::Publisher(topic->domainParticipant(), pubQos), topic->ddsTopic(), dataWriterQos, listener, dds::core::status::StatusMask::all()); return ddsWriter; } // no copying Writer(Writer const&) = delete; Writer(Writer&&) = delete; void operator=(Writer const&) = delete; }; } // namespace access } // namespace ddkit