Get/set external data buffer for DynamicData

1 post / 0 new
Offline
Last seen: 1 week 6 hours ago
Joined: 04/03/2021
Posts: 1
Get/set external data buffer for DynamicData

Hello,

I generate a topic during runtime from a user description (rti dds 6.0.1) with C++ 17, just like in the tutorial and I want to memcpy the contiguous data that I have in a buffer to the dynamic data (underlying) memory buffer in order to avoid looping. I don't want to shuffle the data from the buffer to individual sections of the dynamic data manually.

<!--break-->

Moreover, I think there's no way of knowning how many elements a dynamic data has. Please, see the [*] in the code snippet.

My class owns the memory where another part of the code would write data to, then that part of the code also calls this class'  publish() method. Then, I would use the data from the  data_buffer member variable (that this class owns) to publish it with DDS, hopefully without memcpy, but one memcpy from this class' buffer to the underlying dynamicdata buffer is acceptable.

Following is a code snippet with all the relevant parts for the discussion below.

const dds::core::xtypes::DynamicType& get_type(const type_t& type)
{
    using namespace dds::core::xtypes;
    switch (type) {
        case uint8_type:
            return primitive_type<uint8_t>();
        case uint16_type:
            return primitive_type<uint16_t>();
        case uint32_type:
            return primitive_type<uint32_t>();
        case int16_type:
            return primitive_type<int16_t>();
        case int32_type:
            return primitive_type<int32_t>();
        case float_type:
            return primitive_type<float>();
        case double_type:
            return primitive_type<double>();
        default:
            throw std::runtime_error("No such type");
            // rti does not support these
            // case uint64_type: return primitive_type<uint64_t>(); // This should be supported according to the documentation
            // case int8_type: return primitive_type<int8_t>();
            // case int64_type: return primitive_type<int64_t>(); // This should be supported according to the documentation
    }
}

// Generated struct type in this case
// @extensible
// struct my_struct_gen {
//@key unsigned long counter;
//     double x[3];
//     double y[1];
// };
// In this case y just happened to be of size 1, it should be an array anyways.
dds::core::xtypes::StructType create_type(const std::string& name, const std::vector<member_t>& members)
{
// Taken from the DynamicData and Dynamic Use cases tutorial
    using namespace dds::core::xtypes;
    StructType tmp(name);
    std::cout << "Struct :" << name << "\n";
    tmp.add_member(Member("counter", primitive_type<uint32_t>()).key(true));
    for (const auto& member : members) {
        std::cout << "Added member: " << member.name << "\n";
        tmp.add_member(Member(member.name, ArrayType(get_type(member.the_type), member.array_size)));
    }
    rti::core::xtypes::print_idl(tmp);
    std::cout << "Created dynamic type\n";
    return tmp;
}

std::shared_ptr<uint8_t> init_middleware()
{
    my_struct = dds::core::xtypes::StructType{create_type("my_struct", members_vect)};
    topic     = dds::topic::Topic<dds::core::xtypes::DynamicData>(participant, name, my_struct);
    // publisher initialized in constructor init list as: publisher{participant}
    writer = dds::pub::DataWriter<dds::core::xtypes::DynamicData>(publisher, topic);
    sample = dds::core::xtypes::DynamicData(my_struct);

    data_buffer = std::make_shared<uint8_t>(static_cast<uint8_t*>(std::malloc(dynamic_data_size), free));
    // or somehow get the underlying buffer addres to a contiguous chunk of memory from the DynamicData sample

    return {data_buffer};  // Pointer to memory allocated for the dynamic data
    // This should point to a contiguos memory: x[0] | x[1] | x[1] | y[0], etc.
}

void publish()
{
    sample.value("counter", counter++);

    // This should not be hard-coded [*]
    rti::core::xtypes::LoanedDynamicData x = sample.loan_value("x");
    x.get().value((uint32_t)1, 1.2); 
    // I can also put data from the member var data_buffer, this is not the point.
    // x.get().value((uint32_t)1, &data_buffer[5]);
    x.get().value((uint32_t)2, 2.4);
    x.get().value((uint32_t)3, 5.7);
    x.return_loan();

    // This should not be hard-coded [*]
    rti::core::xtypes::LoanedDynamicData y = sample.loan_value("y");
    y.get().value((uint32_t)1, 7.8);

    writer.write(sample);
}

The middleware (dds) initializes correctly and I can get data in the rtiadminconsole. The major issues I would like to solve with DynamicData are:

1) How to get access to the underlying memory buffer for the generated topic in order to avoid unnecesary copies?

 - I thought about creating a buffer myself, but I think that  .estimated_max_buffer_size()  here is not the right approach, for the example struct this returns 120. Apart from that I don't know the offset where the actual members reside, and where I should memcpy to.

 - I also looked into  .set_buffer . But when compiling I'm asked for at least 88 bytes. So, it's the same issue, I wouldn't know at which offset the actual data should go. Maybe I'm using them wrongly, or I understood the docs wrong.

2) Would the @external annotation be of any help here? I tried it for the example above. And as the documentation describes it will define the data as pointers

@extensible
struct my_struct_gen {
    @key unsigned long counter;
    double * x[3];
    double * y[1];
};

The code runs without modification. And if I try to set the pointer like this:

sample.value("x", &buffer[4]);
// or like this
// rti::core::xtypes::LoanedDynamicData x = sample.loan_value("x");
// x.get().value((uint32_t)1, &buffer[4]);

It doesn't work, the error is as follows.

undefined reference to `void rti::core::xtypes::DynamicDataImpl::value<unsigned char*>(unsigned int, unsigned char* const&)'

 

Minor issues that I also would like to know about:

3) According to the supported types table here, the types uint64_t, int64_t should be supported, but when I use them I get the error.

 ndds/hpp/rti/core/xtypes/PrimitiveTypesImpl.hpp:140:75: error: ‘value’ is not a member of ‘rti::core::xtypes::native_primitive_type_code<long unsigned int>’
     PrimitiveType() : DynamicTypeImpl(native_primitive_type_code<T>::value())
                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~

I just wanted to know, if perhaps the table in the documentation is wrong. If it's correct, then how can I use those types?

 

Please, let me know if I'm thinking wrong about this. The middleware just passes bytes from one process to another, there should be a way to do this cleanly with one memcpy.

Also, I don't want to use obscure, internal-only, undocumented functions that might be unsupported in later versions if you know what I mean ;) ;) a.k.a. struct DDS_DynamicDataBuffer * DDS_DynamicData_get_buffer(DDS_DynamicData * dynamic_data);

 

Thank you in advanced for your input!!

Cheers,
tg