[ntcore] Add API to get and set user data on NT_Topic (#8962)

Theres many uses where we need to attach data to a topic, and doing it
this way can make it really easy and efficient.
This commit is contained in:
Thad House
2026-06-08 19:24:15 -07:00
committed by GitHub
parent 111130d8bb
commit 72d85be0c2
9 changed files with 161 additions and 0 deletions

View File

@@ -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) { NT_Type GetTopicType(NT_Topic topicHandle) {
std::scoped_lock lock{m_mutex}; std::scoped_lock lock{m_mutex};
if (auto topic = m_impl.GetTopicByHandle(topicHandle)) { if (auto topic = m_impl.GetTopicByHandle(topicHandle)) {

View File

@@ -100,6 +100,8 @@ struct LocalTopic {
VectorSet<LocalEntry*> entries; VectorSet<LocalEntry*> entries;
VectorSet<NT_Listener> listeners; VectorSet<NT_Listener> listeners;
void* userData{nullptr};
private: private:
// update flags from properties // update flags from properties
void RefreshFlags(); void RefreshFlags();

View File

@@ -274,6 +274,14 @@ void NT_GetTopicName(NT_Topic topic, struct WPI_String* name) {
wpi::nt::ConvertToC(wpi::nt::GetTopicName(topic), 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) { NT_Type NT_GetTopicType(NT_Topic topic) {
return wpi::nt::GetTopicType(topic); return wpi::nt::GetTopicType(topic);
} }

View File

@@ -217,6 +217,19 @@ std::string GetTopicName(NT_Topic topic) {
return {}; 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) { NT_Type GetTopicType(NT_Topic topic) {
if (auto ii = InstanceImpl::GetTyped(topic, Handle::TOPIC)) { if (auto ii = InstanceImpl::GetTyped(topic, Handle::TOPIC)) {

View File

@@ -60,6 +60,23 @@ class Topic {
*/ */
std::string GetName() const { return ::wpi::nt::GetTopicName(m_handle); } 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. * Gets the type of the topic.
* *

View File

@@ -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); 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. * Gets the type for the specified topic, or unassigned if non existent.
* *

View File

@@ -647,6 +647,28 @@ NT_Topic GetTopic(NT_Inst inst, std::string_view name);
*/ */
std::string GetTopicName(NT_Topic topic); 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. * Gets the type for the specified topic, or unassigned if non existent.
* *

View File

@@ -15,6 +15,10 @@ classes:
ignore: true ignore: true
GetInstance: GetInstance:
GetName: GetName:
GetUserData:
ignore: true
SetUserData:
ignore: true
GetType: GetType:
GetTypeString: GetTypeString:
SetPersistent: SetPersistent:

View File

@@ -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 <gtest/gtest.h>
#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());
}