diff --git a/ntcore/src/main/native/cpp/LocalStorage.hpp b/ntcore/src/main/native/cpp/LocalStorage.hpp index e7a1ad1b5a..cdb32a4d91 100644 --- a/ntcore/src/main/native/cpp/LocalStorage.hpp +++ b/ntcore/src/main/native/cpp/LocalStorage.hpp @@ -106,6 +106,22 @@ class LocalStorage final : public net::ILocalStorage { } } + void* GetTopicUserData(NT_Topic topicHandle) { + std::scoped_lock lock{m_mutex}; + if (auto topic = m_impl.GetTopicByHandle(topicHandle)) { + return topic->userData; + } else { + return nullptr; + } + } + + void SetTopicUserData(NT_Topic topicHandle, void* userData) { + std::scoped_lock lock{m_mutex}; + if (auto topic = m_impl.GetTopicByHandle(topicHandle)) { + topic->userData = userData; + } + } + NT_Type GetTopicType(NT_Topic topicHandle) { std::scoped_lock lock{m_mutex}; if (auto topic = m_impl.GetTopicByHandle(topicHandle)) { diff --git a/ntcore/src/main/native/cpp/local/LocalTopic.hpp b/ntcore/src/main/native/cpp/local/LocalTopic.hpp index 913218b1fd..ed728ceccc 100644 --- a/ntcore/src/main/native/cpp/local/LocalTopic.hpp +++ b/ntcore/src/main/native/cpp/local/LocalTopic.hpp @@ -100,6 +100,8 @@ struct LocalTopic { VectorSet entries; VectorSet listeners; + void* userData{nullptr}; + private: // update flags from properties void RefreshFlags(); diff --git a/ntcore/src/main/native/cpp/ntcore_c.cpp b/ntcore/src/main/native/cpp/ntcore_c.cpp index efdf555473..c906aa4b66 100644 --- a/ntcore/src/main/native/cpp/ntcore_c.cpp +++ b/ntcore/src/main/native/cpp/ntcore_c.cpp @@ -274,6 +274,14 @@ void NT_GetTopicName(NT_Topic topic, struct WPI_String* name) { wpi::nt::ConvertToC(wpi::nt::GetTopicName(topic), name); } +void* NT_GetTopicUserData(NT_Topic topic) { + return wpi::nt::GetTopicUserData(topic); +} + +void NT_SetTopicUserData(NT_Topic topic, void* userData) { + wpi::nt::SetTopicUserData(topic, userData); +} + NT_Type NT_GetTopicType(NT_Topic topic) { return wpi::nt::GetTopicType(topic); } diff --git a/ntcore/src/main/native/cpp/ntcore_cpp.cpp b/ntcore/src/main/native/cpp/ntcore_cpp.cpp index c2467260c3..43fb69f010 100644 --- a/ntcore/src/main/native/cpp/ntcore_cpp.cpp +++ b/ntcore/src/main/native/cpp/ntcore_cpp.cpp @@ -217,6 +217,19 @@ std::string GetTopicName(NT_Topic topic) { return {}; } } +void* GetTopicUserData(NT_Topic topic) { + if (auto ii = InstanceImpl::GetTyped(topic, Handle::TOPIC)) { + return ii->localStorage.GetTopicUserData(topic); + } else { + return nullptr; + } +} + +void SetTopicUserData(NT_Topic topic, void* userData) { + if (auto ii = InstanceImpl::GetTyped(topic, Handle::TOPIC)) { + ii->localStorage.SetTopicUserData(topic, userData); + } +} NT_Type GetTopicType(NT_Topic topic) { if (auto ii = InstanceImpl::GetTyped(topic, Handle::TOPIC)) { diff --git a/ntcore/src/main/native/include/wpi/nt/Topic.hpp b/ntcore/src/main/native/include/wpi/nt/Topic.hpp index 7fe5b0e046..e424f7e8d0 100644 --- a/ntcore/src/main/native/include/wpi/nt/Topic.hpp +++ b/ntcore/src/main/native/include/wpi/nt/Topic.hpp @@ -60,6 +60,23 @@ class Topic { */ std::string GetName() const { return ::wpi::nt::GetTopicName(m_handle); } + /** + * Gets the user data associated with the topic. + * + * @return User data pointer, or nullptr if no user data is associated. + */ + void* GetUserData() const { return ::wpi::nt::GetTopicUserData(m_handle); } + + /** + * Sets the user data associated with the topic. User data is not used by + * ntcore and is for the user's convenience. It is not automatically freed. + * + * @param userData User data pointer to associate with the topic. + */ + void SetUserData(void* userData) { + ::wpi::nt::SetTopicUserData(m_handle, userData); + } + /** * Gets the type of the topic. * diff --git a/ntcore/src/main/native/include/wpi/nt/ntcore_c.h b/ntcore/src/main/native/include/wpi/nt/ntcore_c.h index c7d28bf186..643c8d8347 100644 --- a/ntcore/src/main/native/include/wpi/nt/ntcore_c.h +++ b/ntcore/src/main/native/include/wpi/nt/ntcore_c.h @@ -654,6 +654,25 @@ NT_Topic NT_GetTopic(NT_Inst inst, const struct WPI_String* name); */ void NT_GetTopicName(NT_Topic topic, struct WPI_String* name); +/** + * Gets the user data pointer for the specified topic. + * Returns nullptr if the handle is invalid or no user data is associated. + * The user data pointer is not used by ntcore and is for the caller's use. + * + * @param topic topic handle + * @return User data pointer + */ +void* NT_GetTopicUserData(NT_Topic topic); + +/** + * Sets the user data pointer for the specified topic. The user data pointer + * is not used by ntcore and is for the caller's use. + * + * @param topic topic handle + * @param userData user data pointer to associate with the topic + */ +void NT_SetTopicUserData(NT_Topic topic, void* userData); + /** * Gets the type for the specified topic, or unassigned if non existent. * diff --git a/ntcore/src/main/native/include/wpi/nt/ntcore_cpp.hpp b/ntcore/src/main/native/include/wpi/nt/ntcore_cpp.hpp index 5d80bfca53..4610259f3f 100644 --- a/ntcore/src/main/native/include/wpi/nt/ntcore_cpp.hpp +++ b/ntcore/src/main/native/include/wpi/nt/ntcore_cpp.hpp @@ -647,6 +647,28 @@ NT_Topic GetTopic(NT_Inst inst, std::string_view name); */ std::string GetTopicName(NT_Topic topic); +/** + * Gets the user data associated with the specified topic. + * Returns nullptr if the handle is invalid or no user data is associated. + * User data is not used by ntcore and is for the user's convenience. It is not + * inherited by subscribers/publishers of the topic, and is not automatically + * freed. + * + * @param topic topic handle + * @return User data pointer + */ +void* GetTopicUserData(NT_Topic topic); + +/** + * Sets the user data associated with the specified topic. User data is not + * used by ntcore and is for the user's convenience. It is not inherited by + * subscribers/publishers of the topic, and is not automatically freed. + * + * @param topic topic handle + * @param userData User data pointer to associate with the topic + */ +void SetTopicUserData(NT_Topic topic, void* userData); + /** * Gets the type for the specified topic, or unassigned if non existent. * diff --git a/ntcore/src/main/python/semiwrap/Topic.yml b/ntcore/src/main/python/semiwrap/Topic.yml index bb52df81e4..ff7eaf7abf 100644 --- a/ntcore/src/main/python/semiwrap/Topic.yml +++ b/ntcore/src/main/python/semiwrap/Topic.yml @@ -15,6 +15,10 @@ classes: ignore: true GetInstance: GetName: + GetUserData: + ignore: true + SetUserData: + ignore: true GetType: GetTypeString: SetPersistent: diff --git a/ntcore/src/test/native/cpp/TopicTest.cpp b/ntcore/src/test/native/cpp/TopicTest.cpp new file mode 100644 index 0000000000..20911c87a0 --- /dev/null +++ b/ntcore/src/test/native/cpp/TopicTest.cpp @@ -0,0 +1,60 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "wpi/nt/Topic.hpp" + +#include + +#include "wpi/nt/NetworkTableInstance.hpp" + +class TopicTest : public ::testing::Test { + public: + TopicTest() : m_inst{wpi::nt::NetworkTableInstance::Create()} {} + ~TopicTest() override { wpi::nt::NetworkTableInstance::Destroy(m_inst); } + + protected: + wpi::nt::NetworkTableInstance m_inst; +}; + +TEST_F(TopicTest, UserDataDefaultsToNull) { + auto topic = m_inst.GetTopic("foo"); + + EXPECT_EQ(nullptr, topic.GetUserData()); +} + +TEST_F(TopicTest, UserDataRoundTrip) { + auto topic = m_inst.GetTopic("foo"); + int data = 5; + + topic.SetUserData(&data); + + EXPECT_EQ(&data, topic.GetUserData()); +} + +TEST_F(TopicTest, UserDataCanBeReplacedAndCleared) { + auto topic = m_inst.GetTopic("foo"); + auto sameTopic = m_inst.GetTopic("foo"); + int data1 = 5; + int data2 = 10; + + topic.SetUserData(&data1); + EXPECT_EQ(&data1, sameTopic.GetUserData()); + + sameTopic.SetUserData(&data2); + EXPECT_EQ(&data2, topic.GetUserData()); + + topic.SetUserData(nullptr); + EXPECT_EQ(nullptr, sameTopic.GetUserData()); +} + +TEST_F(TopicTest, UserDataInvalidTopic) { + wpi::nt::Topic topic; + int data = 5; + + EXPECT_EQ(nullptr, topic.GetUserData()); + + topic.SetUserData(&data); + + EXPECT_EQ(nullptr, topic.GetUserData()); +}