diff --git a/glass/src/app/native/cpp/main.cpp b/glass/src/app/native/cpp/main.cpp index c5cc8b1b87..a20ff8bf54 100644 --- a/glass/src/app/native/cpp/main.cpp +++ b/glass/src/app/native/cpp/main.cpp @@ -70,44 +70,41 @@ static void RemapEnterKeyCallback(GLFWwindow* window, int key, int scancode, } static void NtInitialize() { - // update window title when connection status changes auto inst = nt::GetDefaultInstance(); - auto poller = nt::CreateConnectionListenerPoller(inst); - nt::AddPolledConnectionListener(poller, true); + auto poller = nt::CreateListenerPoller(inst); + nt::AddPolledListener( + poller, inst, + NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE | NT_EVENT_LOGMESSAGE); gui::AddEarlyExecute([poller] { auto win = gui::GetSystemWindow(); if (!win) { return; } - for (auto&& event : nt::ReadConnectionListenerQueue(poller)) { - if (event.connected) { - glfwSetWindowTitle( - win, fmt::format("Glass - Connected ({})", event.conn.remote_ip) - .c_str()); - } else { - glfwSetWindowTitle(win, "Glass - DISCONNECTED"); + for (auto&& event : nt::ReadListenerQueue(poller)) { + if (auto connInfo = event.GetConnectionInfo()) { + // update window title when connection status changes + if ((event.flags & NT_EVENT_CONNECTED) != 0) { + glfwSetWindowTitle( + win, fmt::format("Glass - Connected ({})", connInfo->remote_ip) + .c_str()); + } else { + glfwSetWindowTitle(win, "Glass - DISCONNECTED"); + } + } else if (auto msg = event.GetLogMessage()) { + const char* level = ""; + if (msg->level >= NT_LOG_CRITICAL) { + level = "CRITICAL: "; + } else if (msg->level >= NT_LOG_ERROR) { + level = "ERROR: "; + } else if (msg->level >= NT_LOG_WARNING) { + level = "WARNING: "; + } + gNetworkTablesLog.Append(fmt::format( + "{}{} ({}:{})\n", level, msg->message, msg->filename, msg->line)); } } }); - // handle NetworkTables log messages - auto logPoller = nt::CreateLoggerPoller(inst); - nt::AddPolledLogger(logPoller, NT_LOG_INFO, 100); - gui::AddEarlyExecute([logPoller] { - for (auto&& msg : nt::ReadLoggerQueue(logPoller)) { - const char* level = ""; - if (msg.level >= NT_LOG_CRITICAL) { - level = "CRITICAL: "; - } else if (msg.level >= NT_LOG_ERROR) { - level = "ERROR: "; - } else if (msg.level >= NT_LOG_WARNING) { - level = "WARNING: "; - } - gNetworkTablesLog.Append(fmt::format("{}{} ({}:{})\n", level, msg.message, - msg.filename, msg.line)); - } - }); - gNetworkTablesLogWindow = std::make_unique( glass::GetStorageRoot().GetChild("NetworkTables Log"), "NetworkTables Log", glass::Window::kHide); diff --git a/glass/src/libnt/native/cpp/NTField2D.cpp b/glass/src/libnt/native/cpp/NTField2D.cpp index 60e9759773..243f81e09c 100644 --- a/glass/src/libnt/native/cpp/NTField2D.cpp +++ b/glass/src/libnt/native/cpp/NTField2D.cpp @@ -117,58 +117,55 @@ NTField2DModel::NTField2DModel(nt::NetworkTableInstance inst, {{nt::PubSubOption::SendAll(true), nt::PubSubOption::Periodic(0.05)}}}, m_nameTopic{inst.GetTopic(fmt::format("{}/.name", path))}, - m_topicListener{inst}, - m_valueListener{inst} { - m_topicListener.Add(m_tableSub, NT_TOPIC_NOTIFY_PUBLISH | - NT_TOPIC_NOTIFY_UNPUBLISH | - NT_TOPIC_NOTIFY_IMMEDIATE); - m_valueListener.Add(m_tableSub, - NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL); + m_poller{inst} { + m_poller.AddListener(m_tableSub, nt::EventFlags::kTopic | + nt::EventFlags::kValueAll | + nt::EventFlags::kImmediate); } NTField2DModel::~NTField2DModel() = default; void NTField2DModel::Update() { - // handle publish/unpublish - for (auto&& event : m_topicListener.ReadQueue()) { - auto name = wpi::drop_front(event.info.name, m_path.size()); - if (name.empty() || name[0] == '.') { - continue; - } - auto [it, match] = Find(event.info.name); - if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) { - if (match) { - m_objects.erase(it); + for (auto&& event : m_poller.ReadQueue()) { + if (auto info = event.GetTopicInfo()) { + // handle publish/unpublish + auto name = wpi::drop_front(info->name, m_path.size()); + if (name.empty() || name[0] == '.') { + continue; } - continue; - } else if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) { - if (!match) { - it = m_objects.emplace( - it, std::make_unique( - event.info.name, nt::DoubleArrayTopic{event.info.topic})); + auto [it, match] = Find(info->name); + if (event.flags & nt::EventFlags::kUnpublish) { + if (match) { + m_objects.erase(it); + } + continue; + } else if (event.flags & nt::EventFlags::kPublish) { + if (!match) { + it = m_objects.emplace( + it, std::make_unique( + info->name, nt::DoubleArrayTopic{info->topic})); + } + } else if (!match) { + continue; + } + } else if (auto valueData = event.GetValueEventData()) { + // update values + // .name + if (valueData->topic == m_nameTopic.GetHandle()) { + if (valueData->value && valueData->value.IsString()) { + m_nameValue = valueData->value.GetString(); + } + continue; } - } else if (!match) { - continue; - } - } - // update values - for (auto&& event : m_valueListener.ReadQueue()) { - // .name - if (event.topic == m_nameTopic.GetHandle()) { - if (event.value && event.value.IsString()) { - m_nameValue = event.value.GetString(); + auto it = + std::find_if(m_objects.begin(), m_objects.end(), [&](const auto& e) { + return e->GetTopic().GetHandle() == valueData->topic; + }); + if (it != m_objects.end()) { + (*it)->NTUpdate(valueData->value); + continue; } - continue; - } - - auto it = - std::find_if(m_objects.begin(), m_objects.end(), [&](const auto& e) { - return e->GetTopic().GetHandle() == event.topic; - }); - if (it != m_objects.end()) { - (*it)->NTUpdate(event.value); - continue; } } } diff --git a/glass/src/libnt/native/cpp/NTMechanism2D.cpp b/glass/src/libnt/native/cpp/NTMechanism2D.cpp index 54838d92b9..fa97de1b89 100644 --- a/glass/src/libnt/native/cpp/NTMechanism2D.cpp +++ b/glass/src/libnt/native/cpp/NTMechanism2D.cpp @@ -41,8 +41,7 @@ class NTMechanismGroupImpl final { const char* GetName() const { return m_name.c_str(); } void ForEachObject(wpi::function_ref func); - void NTUpdate(const nt::TopicNotification& event, std::string_view name); - void NTUpdate(const nt::ValueNotification& event, std::string_view name); + void NTUpdate(const nt::Event& event, std::string_view name); protected: nt::NetworkTableInstance m_inst; @@ -74,8 +73,7 @@ class NTMechanismObjectModel final : public MechanismObjectModel { frc::Rotation2d GetAngle() const final { return m_angleValue; } units::meter_t GetLength() const final { return m_lengthValue; } - bool NTUpdate(const nt::TopicNotification& event, std::string_view name); - void NTUpdate(const nt::ValueNotification& event, std::string_view name); + bool NTUpdate(const nt::Event& event, std::string_view name); private: NTMechanismGroupImpl m_group; @@ -102,7 +100,7 @@ void NTMechanismGroupImpl::ForEachObject( } } -void NTMechanismGroupImpl::NTUpdate(const nt::TopicNotification& event, +void NTMechanismGroupImpl::NTUpdate(const nt::Event& event, std::string_view name) { if (name.empty()) { return; @@ -118,83 +116,69 @@ void NTMechanismGroupImpl::NTUpdate(const nt::TopicNotification& event, [](const auto& e, std::string_view name) { return e->GetName() < name; }); bool match = it != m_objects.end() && (*it)->GetName() == name; - if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) { - if (!match) { - it = m_objects.emplace( - it, std::make_unique( - m_inst, fmt::format("{}/{}", m_path, name), name)); - match = true; + if (event.GetTopicInfo()) { + if (event.flags & nt::EventFlags::kPublish) { + if (!match) { + it = m_objects.emplace( + it, std::make_unique( + m_inst, fmt::format("{}/{}", m_path, name), name)); + match = true; + } } - } - if (match) { - if ((*it)->NTUpdate(event, childName)) { - m_objects.erase(it); + if (match) { + if ((*it)->NTUpdate(event, childName)) { + m_objects.erase(it); + } + } + } else if (event.GetValueEventData()) { + if (match) { + (*it)->NTUpdate(event, childName); } } } -void NTMechanismGroupImpl::NTUpdate(const nt::ValueNotification& event, - std::string_view name) { - if (name.empty()) { - return; - } - std::string_view childName; - std::tie(name, childName) = wpi::split(name, '/'); - if (childName.empty()) { - return; - } - - auto it = std::lower_bound( - m_objects.begin(), m_objects.end(), name, - [](const auto& e, std::string_view name) { return e->GetName() < name; }); - if (it != m_objects.end() && (*it)->GetName() == name) { - (*it)->NTUpdate(event, childName); - } -} - -bool NTMechanismObjectModel::NTUpdate(const nt::TopicNotification& event, +bool NTMechanismObjectModel::NTUpdate(const nt::Event& event, std::string_view childName) { - if (event.info.topic == m_typeTopic.GetHandle()) { - if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) { - return true; + if (auto info = event.GetTopicInfo()) { + if (info->topic == m_typeTopic.GetHandle()) { + if (event.flags & nt::EventFlags::kUnpublish) { + return true; + } + } else if (info->topic != m_colorTopic.GetHandle() && + info->topic != m_weightTopic.GetHandle() && + info->topic != m_angleTopic.GetHandle() && + info->topic != m_lengthTopic.GetHandle()) { + m_group.NTUpdate(event, childName); + } + } else if (auto valueData = event.GetValueEventData()) { + if (valueData->topic == m_typeTopic.GetHandle()) { + if (valueData->value && valueData->value.IsString()) { + m_typeValue = valueData->value.GetString(); + } + } else if (valueData->topic == m_colorTopic.GetHandle()) { + if (valueData->value && valueData->value.IsString()) { + ConvertColor(valueData->value.GetString(), &m_colorValue); + } + } else if (valueData->topic == m_weightTopic.GetHandle()) { + if (valueData->value && valueData->value.IsDouble()) { + m_weightValue = valueData->value.GetDouble(); + } + } else if (valueData->topic == m_angleTopic.GetHandle()) { + if (valueData->value && valueData->value.IsDouble()) { + m_angleValue = units::degree_t{valueData->value.GetDouble()}; + } + } else if (valueData->topic == m_lengthTopic.GetHandle()) { + if (valueData->value && valueData->value.IsDouble()) { + m_lengthValue = units::meter_t{valueData->value.GetDouble()}; + } + } else { + m_group.NTUpdate(event, childName); } - } else if (event.info.topic != m_colorTopic.GetHandle() && - event.info.topic != m_weightTopic.GetHandle() && - event.info.topic != m_angleTopic.GetHandle() && - event.info.topic != m_lengthTopic.GetHandle()) { - m_group.NTUpdate(event, childName); } return false; } -void NTMechanismObjectModel::NTUpdate(const nt::ValueNotification& event, - std::string_view childName) { - if (event.topic == m_typeTopic.GetHandle()) { - if (event.value && event.value.IsString()) { - m_typeValue = event.value.GetString(); - } - } else if (event.topic == m_colorTopic.GetHandle()) { - if (event.value && event.value.IsString()) { - ConvertColor(event.value.GetString(), &m_colorValue); - } - } else if (event.topic == m_weightTopic.GetHandle()) { - if (event.value && event.value.IsDouble()) { - m_weightValue = event.value.GetDouble(); - } - } else if (event.topic == m_angleTopic.GetHandle()) { - if (event.value && event.value.IsDouble()) { - m_angleValue = units::degree_t{event.value.GetDouble()}; - } - } else if (event.topic == m_lengthTopic.GetHandle()) { - if (event.value && event.value.IsDouble()) { - m_lengthValue = units::meter_t{event.value.GetDouble()}; - } - } else { - m_group.NTUpdate(event, childName); - } -} - class NTMechanism2DModel::RootModel final : public MechanismRootModel { public: RootModel(nt::NetworkTableInstance inst, std::string_view path, @@ -209,8 +193,7 @@ class NTMechanism2DModel::RootModel final : public MechanismRootModel { m_group.ForEachObject(func); } - bool NTUpdate(const nt::TopicNotification& event, std::string_view childName); - void NTUpdate(const nt::ValueNotification& event, std::string_view childName); + bool NTUpdate(const nt::Event& event, std::string_view childName); frc::Translation2d GetPosition() const override { return m_pos; }; @@ -221,36 +204,35 @@ class NTMechanism2DModel::RootModel final : public MechanismRootModel { frc::Translation2d m_pos; }; -bool NTMechanism2DModel::RootModel::NTUpdate(const nt::TopicNotification& event, +bool NTMechanism2DModel::RootModel::NTUpdate(const nt::Event& event, std::string_view childName) { - if (event.info.topic == m_xTopic.GetHandle() || - event.info.topic == m_yTopic.GetHandle()) { - if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) { - return true; + if (auto info = event.GetTopicInfo()) { + if (info->topic == m_xTopic.GetHandle() || + info->topic == m_yTopic.GetHandle()) { + if (event.flags & nt::EventFlags::kUnpublish) { + return true; + } + } else { + m_group.NTUpdate(event, childName); + } + } else if (auto valueData = event.GetValueEventData()) { + if (valueData->topic == m_xTopic.GetHandle()) { + if (valueData->value && valueData->value.IsDouble()) { + m_pos = frc::Translation2d{units::meter_t{valueData->value.GetDouble()}, + m_pos.Y()}; + } + } else if (valueData->topic == m_yTopic.GetHandle()) { + if (valueData->value && valueData->value.IsDouble()) { + m_pos = frc::Translation2d{ + m_pos.X(), units::meter_t{valueData->value.GetDouble()}}; + } + } else { + m_group.NTUpdate(event, childName); } - } else { - m_group.NTUpdate(event, childName); } return false; } -void NTMechanism2DModel::RootModel::NTUpdate(const nt::ValueNotification& event, - std::string_view childName) { - if (event.topic == m_xTopic.GetHandle()) { - if (event.value && event.value.IsDouble()) { - m_pos = frc::Translation2d{units::meter_t{event.value.GetDouble()}, - m_pos.Y()}; - } - } else if (event.topic == m_yTopic.GetHandle()) { - if (event.value && event.value.IsDouble()) { - m_pos = frc::Translation2d{m_pos.X(), - units::meter_t{event.value.GetDouble()}}; - } - } else { - m_group.NTUpdate(event, childName); - } -} - NTMechanism2DModel::NTMechanism2DModel(std::string_view path) : NTMechanism2DModel{nt::NetworkTableInstance::GetDefault(), path} {} @@ -265,75 +247,72 @@ NTMechanism2DModel::NTMechanism2DModel(nt::NetworkTableInstance inst, m_nameTopic{m_inst.GetTopic(fmt::format("{}/.name", path))}, m_dimensionsTopic{m_inst.GetTopic(fmt::format("{}/dims", path))}, m_bgColorTopic{m_inst.GetTopic(fmt::format("{}/backgroundColor", path))}, - m_topicListener{m_inst}, - m_valueListener{m_inst}, + m_poller{m_inst}, m_dimensionsValue{1_m, 1_m} { - m_topicListener.Add(m_tableSub, NT_TOPIC_NOTIFY_PUBLISH | - NT_TOPIC_NOTIFY_UNPUBLISH | - NT_TOPIC_NOTIFY_IMMEDIATE); - m_valueListener.Add(m_tableSub, - NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL); + m_poller.AddListener(m_tableSub, nt::EventFlags::kTopic | + nt::EventFlags::kValueAll | + nt::EventFlags::kImmediate); } NTMechanism2DModel::~NTMechanism2DModel() = default; void NTMechanism2DModel::Update() { - for (auto&& event : m_topicListener.ReadQueue()) { - auto name = wpi::drop_front(event.info.name, m_path.size()); - if (name.empty() || name[0] == '.') { - continue; - } - std::string_view childName; - std::tie(name, childName) = wpi::split(name, '/'); - if (childName.empty()) { - continue; - } - - auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name, - [](const auto& e, std::string_view name) { - return e->GetName() < name; - }); - bool match = it != m_roots.end() && (*it)->GetName() == name; - - if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) { - if (!match) { - it = m_roots.emplace( - it, std::make_unique( - m_inst, fmt::format("{}{}", m_path, name), name)); - match = true; + for (auto&& event : m_poller.ReadQueue()) { + if (auto info = event.GetTopicInfo()) { + auto name = wpi::drop_front(info->name, m_path.size()); + if (name.empty() || name[0] == '.') { + continue; } - } - if (match) { - if ((*it)->NTUpdate(event, childName)) { - m_roots.erase(it); + std::string_view childName; + std::tie(name, childName) = wpi::split(name, '/'); + if (childName.empty()) { + continue; } - } - } - for (auto&& event : m_valueListener.ReadQueue()) { - // .name - if (event.topic == m_nameTopic.GetHandle()) { - if (event.value && event.value.IsString()) { - m_nameValue = event.value.GetString(); - } - continue; - } + auto it = std::lower_bound(m_roots.begin(), m_roots.end(), name, + [](const auto& e, std::string_view name) { + return e->GetName() < name; + }); + bool match = it != m_roots.end() && (*it)->GetName() == name; - // dims - if (event.topic == m_dimensionsTopic.GetHandle()) { - if (event.value && event.value.IsDoubleArray()) { - auto arr = event.value.GetDoubleArray(); - if (arr.size() == 2) { - m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]}, - units::meter_t{arr[1]}}; + if (event.flags & nt::EventFlags::kPublish) { + if (!match) { + it = m_roots.emplace( + it, std::make_unique( + m_inst, fmt::format("{}{}", m_path, name), name)); + match = true; } } - } + if (match) { + if ((*it)->NTUpdate(event, childName)) { + m_roots.erase(it); + } + } + } else if (auto valueData = event.GetValueEventData()) { + // .name + if (valueData->topic == m_nameTopic.GetHandle()) { + if (valueData->value && valueData->value.IsString()) { + m_nameValue = valueData->value.GetString(); + } + continue; + } - // backgroundColor - if (event.topic == m_bgColorTopic.GetHandle()) { - if (event.value && event.value.IsString()) { - ConvertColor(event.value.GetString(), &m_bgColorValue); + // dims + if (valueData->topic == m_dimensionsTopic.GetHandle()) { + if (valueData->value && valueData->value.IsDoubleArray()) { + auto arr = valueData->value.GetDoubleArray(); + if (arr.size() == 2) { + m_dimensionsValue = frc::Translation2d{units::meter_t{arr[0]}, + units::meter_t{arr[1]}}; + } + } + } + + // backgroundColor + if (valueData->topic == m_bgColorTopic.GetHandle()) { + if (valueData->value && valueData->value.IsString()) { + ConvertColor(valueData->value.GetString(), &m_bgColorValue); + } } } } diff --git a/glass/src/libnt/native/cpp/NetworkTables.cpp b/glass/src/libnt/native/cpp/NetworkTables.cpp index 246662f7b5..9a60fe490d 100644 --- a/glass/src/libnt/native/cpp/NetworkTables.cpp +++ b/glass/src/libnt/native/cpp/NetworkTables.cpp @@ -110,15 +110,10 @@ NetworkTablesModel::NetworkTablesModel() : NetworkTablesModel{nt::NetworkTableInstance::GetDefault()} {} NetworkTablesModel::NetworkTablesModel(nt::NetworkTableInstance inst) - : m_inst{inst}, - m_subscriber{inst, {{"", "$"}}}, - m_topicPoller{inst}, - m_valuePoller{inst} { - m_topicPoller.Add(m_subscriber, - NT_TOPIC_NOTIFY_IMMEDIATE | NT_TOPIC_NOTIFY_PROPERTIES | - NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_UNPUBLISH); - m_valuePoller.Add(m_subscriber, - NT_VALUE_NOTIFY_IMMEDIATE | NT_VALUE_NOTIFY_LOCAL); + : m_inst{inst}, m_poller{inst} { + m_poller.AddListener({{"", "$"}}, nt::EventFlags::kTopic | + nt::EventFlags::kValueAll | + nt::EventFlags::kImmediate); } NetworkTablesModel::Entry::~Entry() { @@ -426,56 +421,57 @@ void NetworkTablesModel::ValueSource::UpdateFromValue( void NetworkTablesModel::Update() { bool updateTree = false; - for (auto&& event : m_topicPoller.ReadQueue()) { - auto& entry = m_entries[event.info.topic]; - if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) { - if (!entry) { - entry = std::make_unique(); - m_sortedEntries.emplace_back(entry.get()); + for (auto&& event : m_poller.ReadQueue()) { + if (auto info = event.GetTopicInfo()) { + auto& entry = m_entries[info->topic]; + if (event.flags & nt::EventFlags::kPublish) { + if (!entry) { + entry = std::make_unique(); + m_sortedEntries.emplace_back(entry.get()); + updateTree = true; + } + } + if (event.flags & nt::EventFlags::kUnpublish) { + auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(), + entry.get()); + // will be removed completely below + if (it != m_sortedEntries.end()) { + *it = nullptr; + } + m_entries.erase(info->topic); + updateTree = true; + continue; + } + if (event.flags & nt::EventFlags::kProperties) { updateTree = true; } - } - if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) { - auto it = std::find(m_sortedEntries.begin(), m_sortedEntries.end(), - entry.get()); - // will be removed completely below - if (it != m_sortedEntries.end()) { - *it = nullptr; + if (entry) { + entry->UpdateTopic(std::move(event)); } - m_entries.erase(event.info.topic); - updateTree = true; - continue; - } - if (event.flags & NT_TOPIC_NOTIFY_PROPERTIES) { - updateTree = true; - } - if (entry) { - entry->UpdateTopic(std::move(event)); - } - } - for (auto&& event : m_valuePoller.ReadQueue()) { - auto& entry = m_entries[event.topic]; - if (entry) { - entry->UpdateFromValue(std::move(event.value), entry->info.name, - entry->info.type_str); - if (wpi::starts_with(entry->info.name, '$') && entry->value.IsRaw() && - entry->info.type_str == "msgpack") { - // meta topic handling - if (entry->info.name == "$clients") { - UpdateClients(entry->value.GetRaw()); - } else if (entry->info.name == "$serverpub") { - m_server.UpdatePublishers(entry->value.GetRaw()); - } else if (entry->info.name == "$serversub") { - m_server.UpdateSubscribers(entry->value.GetRaw()); - } else if (wpi::starts_with(entry->info.name, "$clientpub$")) { - auto it = m_clients.find(wpi::drop_front(entry->info.name, 11)); - if (it != m_clients.end()) { - it->second.UpdatePublishers(entry->value.GetRaw()); - } - } else if (wpi::starts_with(entry->info.name, "$clientsub$")) { - auto it = m_clients.find(wpi::drop_front(entry->info.name, 11)); - if (it != m_clients.end()) { - it->second.UpdateSubscribers(entry->value.GetRaw()); + } else if (auto valueData = event.GetValueEventData()) { + auto& entry = m_entries[valueData->topic]; + if (entry) { + entry->UpdateFromValue(std::move(valueData->value), entry->info.name, + entry->info.type_str); + if (wpi::starts_with(entry->info.name, '$') && entry->value.IsRaw() && + entry->info.type_str == "msgpack") { + // meta topic handling + if (entry->info.name == "$clients") { + UpdateClients(entry->value.GetRaw()); + } else if (entry->info.name == "$serverpub") { + m_server.UpdatePublishers(entry->value.GetRaw()); + } else if (entry->info.name == "$serversub") { + m_server.UpdateSubscribers(entry->value.GetRaw()); + } else if (wpi::starts_with(entry->info.name, "$clientpub$")) { + auto it = m_clients.find(wpi::drop_front(entry->info.name, 11)); + if (it != m_clients.end()) { + it->second.UpdatePublishers(entry->value.GetRaw()); + } + } else if (wpi::starts_with(entry->info.name, "$clientsub$")) { + auto it = m_clients.find(wpi::drop_front(entry->info.name, 11)); + if (it != m_clients.end()) { + it->second.UpdateSubscribers(entry->value.GetRaw()); + } } } } diff --git a/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp b/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp index 5120177581..413899ee4d 100644 --- a/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp +++ b/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp @@ -23,13 +23,10 @@ NetworkTablesProvider::NetworkTablesProvider(Storage& storage, nt::NetworkTableInstance inst) : Provider{storage.GetChild("windows")}, m_inst{inst}, - m_topicPoller{inst}, - m_valuePoller{inst}, + m_poller{inst}, m_typeCache{storage.GetChild("types")} { storage.SetCustomApply([this] { - m_topicListener = m_topicPoller.Add({{""}}, NT_TOPIC_NOTIFY_PUBLISH | - NT_TOPIC_NOTIFY_UNPUBLISH | - NT_TOPIC_NOTIFY_IMMEDIATE); + m_listener = m_poller.AddListener({{""}}, nt::EventFlags::kTopic); for (auto&& childIt : m_storage.GetChildren()) { auto id = childIt.key(); auto typePtr = m_typeCache.FindValue(id); @@ -51,8 +48,8 @@ NetworkTablesProvider::NetworkTablesProvider(Storage& storage, } }); storage.SetCustomClear([this, &storage] { - m_topicPoller.Remove(m_topicListener); - m_topicListener = 0; + m_poller.RemoveListener(m_listener); + m_listener = 0; for (auto&& modelEntry : m_modelEntries) { modelEntry->model.reset(); } @@ -102,59 +99,60 @@ void NetworkTablesProvider::DisplayMenu() { void NetworkTablesProvider::Update() { Provider::Update(); - // add/remove entries from NT changes - for (auto&& event : m_topicPoller.ReadQueue()) { - // look for .type fields - if (!wpi::ends_with(event.info.name, "/.type") || - event.info.type != NT_STRING || event.info.type_str != "string") { - continue; - } - - if (event.flags & NT_TOPIC_NOTIFY_UNPUBLISH) { - auto it = m_topicMap.find(event.info.topic); - if (it != m_topicMap.end()) { - m_valuePoller.Remove(it->second.listener); - m_topicMap.erase(it); + for (auto&& event : m_poller.ReadQueue()) { + if (auto info = event.GetTopicInfo()) { + // add/remove entries from NT changes + // look for .type fields + if (!wpi::ends_with(info->name, "/.type") || info->type != NT_STRING || + info->type_str != "string") { + continue; } - auto it2 = std::find_if( - m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) { - return static_cast(elem->modelEntry) - ->typeTopic.GetHandle() == event.info.topic; - }); - if (it2 != m_viewEntries.end()) { - m_viewEntries.erase(it2); + if (event.flags & nt::EventFlags::kUnpublish) { + auto it = m_topicMap.find(info->topic); + if (it != m_topicMap.end()) { + m_poller.RemoveListener(it->second.listener); + m_topicMap.erase(it); + } + + auto it2 = std::find_if( + m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) { + return static_cast(elem->modelEntry) + ->typeTopic.GetHandle() == info->topic; + }); + if (it2 != m_viewEntries.end()) { + m_viewEntries.erase(it2); + } + } else if (event.flags & nt::EventFlags::kPublish) { + // subscribe to it; use a subscriber so we only get string values + SubListener sublistener; + sublistener.subscriber = nt::StringTopic{info->topic}.Subscribe(""); + sublistener.listener = m_poller.AddListener( + sublistener.subscriber, + nt::EventFlags::kValueAll | nt::EventFlags::kImmediate); + m_topicMap.try_emplace(info->topic, std::move(sublistener)); } - } else if (event.flags & NT_TOPIC_NOTIFY_PUBLISH) { - // subscribe to it - SubListener sublistener; - sublistener.subscriber = nt::StringTopic{event.info.topic}.Subscribe(""); - sublistener.listener = - m_valuePoller.Add(sublistener.subscriber, - NT_VALUE_NOTIFY_LOCAL | NT_VALUE_NOTIFY_IMMEDIATE); - m_topicMap.try_emplace(event.info.topic, std::move(sublistener)); + } else if (auto valueData = event.GetValueEventData()) { + // handle actual .type strings + if (!valueData->value.IsString()) { + continue; + } + + // only handle ones where we have a builder + auto builderIt = m_typeMap.find(valueData->value.GetString()); + if (builderIt == m_typeMap.end()) { + continue; + } + + auto topicName = nt::GetTopicName(valueData->topic); + auto tableName = wpi::drop_back(topicName, 6); + + GetOrCreateView(builderIt->second, nt::Topic{valueData->topic}, + tableName); + // cache the type + m_typeCache.SetString(tableName, valueData->value.GetString()); } } - - // handle actual .type strings - for (auto&& event : m_valuePoller.ReadQueue()) { - if (!event.value.IsString()) { - continue; - } - - // only handle ones where we have a builder - auto builderIt = m_typeMap.find(event.value.GetString()); - if (builderIt == m_typeMap.end()) { - continue; - } - - auto topicName = nt::GetTopicName(event.topic); - auto tableName = wpi::drop_back(topicName, 6); - - GetOrCreateView(builderIt->second, nt::Topic{event.topic}, tableName); - // cache the type - m_typeCache.SetString(tableName, event.value.GetString()); - } } void NetworkTablesProvider::Register(std::string_view typeName, diff --git a/glass/src/libnt/native/include/glass/networktables/NTField2D.h b/glass/src/libnt/native/include/glass/networktables/NTField2D.h index 257bbca6ec..40265edd57 100644 --- a/glass/src/libnt/native/include/glass/networktables/NTField2D.h +++ b/glass/src/libnt/native/include/glass/networktables/NTField2D.h @@ -12,9 +12,8 @@ #include #include +#include #include -#include -#include #include #include "glass/other/Field2D.h" @@ -48,8 +47,7 @@ class NTField2DModel : public Field2DModel { nt::NetworkTableInstance m_inst; nt::MultiSubscriber m_tableSub; nt::StringTopic m_nameTopic; - nt::TopicListenerPoller m_topicListener; - nt::ValueListenerPoller m_valueListener; + nt::NetworkTableListenerPoller m_poller; std::string m_nameValue; class ObjectModel; diff --git a/glass/src/libnt/native/include/glass/networktables/NTMechanism2D.h b/glass/src/libnt/native/include/glass/networktables/NTMechanism2D.h index 859a1e2390..e8ffa9d7ee 100644 --- a/glass/src/libnt/native/include/glass/networktables/NTMechanism2D.h +++ b/glass/src/libnt/native/include/glass/networktables/NTMechanism2D.h @@ -13,8 +13,7 @@ #include #include #include -#include -#include +#include #include "glass/other/Mechanism2D.h" @@ -50,8 +49,7 @@ class NTMechanism2DModel : public Mechanism2DModel { nt::Topic m_nameTopic; nt::Topic m_dimensionsTopic; nt::Topic m_bgColorTopic; - nt::TopicListenerPoller m_topicListener; - nt::ValueListenerPoller m_valueListener; + nt::NetworkTableListenerPoller m_poller; std::string m_nameValue; frc::Translation2d m_dimensionsValue; diff --git a/glass/src/libnt/native/include/glass/networktables/NetworkTables.h b/glass/src/libnt/native/include/glass/networktables/NetworkTables.h index b34c034397..66f5fceb14 100644 --- a/glass/src/libnt/native/include/glass/networktables/NetworkTables.h +++ b/glass/src/libnt/native/include/glass/networktables/NetworkTables.h @@ -13,10 +13,8 @@ #include #include -#include #include -#include -#include +#include #include #include #include @@ -85,8 +83,10 @@ class NetworkTablesModel : public Model { Entry& operator=(const Entry&) = delete; ~Entry(); - void UpdateTopic(nt::TopicNotification&& event) { - UpdateInfo(std::move(event.info)); + void UpdateTopic(nt::Event&& event) { + if (std::holds_alternative(event.data)) { + UpdateInfo(std::get(std::move(event.data))); + } } void UpdateInfo(nt::TopicInfo&& info_); @@ -180,9 +180,7 @@ class NetworkTablesModel : public Model { void UpdateClients(std::span data); nt::NetworkTableInstance m_inst; - nt::MultiSubscriber m_subscriber; - nt::TopicListenerPoller m_topicPoller; - nt::ValueListenerPoller m_valuePoller; + nt::NetworkTableListenerPoller m_poller; wpi::DenseMap> m_entries; // sorted by name diff --git a/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h b/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h index da02b10c4a..cba34cc669 100644 --- a/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h +++ b/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h @@ -10,10 +10,9 @@ #include #include +#include #include #include -#include -#include #include #include @@ -79,9 +78,8 @@ class NetworkTablesProvider : private Provider { void Update() override; nt::NetworkTableInstance m_inst; - nt::TopicListenerPoller m_topicPoller; - NT_TopicListener m_topicListener{0}; - nt::ValueListenerPoller m_valuePoller; + nt::NetworkTableListenerPoller m_poller; + NT_Listener m_listener{0}; // cached mapping from table name to type string Storage& m_typeCache; @@ -96,7 +94,7 @@ class NetworkTablesProvider : private Provider { struct SubListener { nt::StringSubscriber subscriber; - NT_ValueListener listener; + NT_Listener listener; }; // mapping from .type topic to subscriber/listener diff --git a/ntcore/src/generate/java/NetworkTableInstance.java.jinja b/ntcore/src/generate/java/NetworkTableInstance.java.jinja index 60223480a5..4c4da68a44 100644 --- a/ntcore/src/generate/java/NetworkTableInstance.java.jinja +++ b/ntcore/src/generate/java/NetworkTableInstance.java.jinja @@ -66,9 +66,7 @@ public final class NetworkTableInstance implements AutoCloseable { @Override public synchronized void close() { if (m_owned && m_handle != 0) { - m_connectionListener.close(); - m_topicListener.close(); - m_valueListener.close(); + m_listeners.close(); NetworkTablesJNI.destroyInstance(m_handle); } } @@ -357,33 +355,84 @@ public final class NetworkTableInstance implements AutoCloseable { * Callback Creation Functions */ - private abstract static class ListenerBase implements AutoCloseable { - protected final ReentrantLock m_lock = new ReentrantLock(); - protected final Map> m_listeners = new HashMap<>(); + private static class ListenerStorage implements AutoCloseable { + private final ReentrantLock m_lock = new ReentrantLock(); + private final Map> m_listeners = new HashMap<>(); private Thread m_thread; - protected int m_poller; + private int m_poller; private boolean m_waitQueue; private final Event m_waitQueueEvent = new Event(); private final Condition m_waitQueueCond = m_lock.newCondition(); - protected final NetworkTableInstance m_inst; + private final NetworkTableInstance m_inst; - ListenerBase(NetworkTableInstance inst) { + ListenerStorage(NetworkTableInstance inst) { m_inst = inst; } + int add(String[] prefixes, int mask, Consumer listener) { + m_lock.lock(); + try { + if (m_poller == 0) { + m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); + startThread(); + } + int h = NetworkTablesJNI.addListener(m_poller, prefixes, mask); + m_listeners.put(h, listener); + return h; + } finally { + m_lock.unlock(); + } + } + + int add(int handle, int mask, Consumer listener) { + m_lock.lock(); + try { + if (m_poller == 0) { + m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); + startThread(); + } + int h = NetworkTablesJNI.addListener(m_poller, handle, mask); + m_listeners.put(h, listener); + return h; + } finally { + m_lock.unlock(); + } + } + + int addLogger(int minLevel, int maxLevel, Consumer listener) { + m_lock.lock(); + try { + if (m_poller == 0) { + m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); + startThread(); + } + int h = NetworkTablesJNI.addLogger(m_poller, minLevel, maxLevel); + m_listeners.put(h, listener); + return h; + } finally { + m_lock.unlock(); + } + } + + void remove(int listener) { + m_lock.lock(); + try { + m_listeners.remove(listener); + } finally { + m_lock.unlock(); + } + NetworkTablesJNI.removeListener(listener); + } + @Override public void close() { if (m_poller != 0) { - destroyPoller(); + NetworkTablesJNI.destroyListenerPoller(m_poller); } m_poller = 0; } - protected abstract T[] readQueue(); - - protected abstract void destroyPoller(); - - protected void startThread(String name) { + private void startThread() { m_thread = new Thread( () -> { @@ -407,8 +456,9 @@ public final class NetworkTableInstance implements AutoCloseable { wasInterrupted = true; break; } - for (T event : readQueue()) { - Consumer listener; + for (NetworkTableEvent event : + NetworkTablesJNI.readListenerQueue(m_inst, m_poller)) { + Consumer listener; m_lock.lock(); try { listener = m_listeners.get(event.listener); @@ -439,14 +489,14 @@ public final class NetworkTableInstance implements AutoCloseable { m_lock.lock(); try { if (!wasInterrupted) { - destroyPoller(); + NetworkTablesJNI.destroyListenerPoller(m_poller); } m_poller = 0; } finally { m_lock.unlock(); } }, - name); + "NTListener"); m_thread.setDaemon(true); m_thread.start(); } @@ -477,48 +527,29 @@ public final class NetworkTableInstance implements AutoCloseable { } } - private static final class ConnectionListener extends ListenerBase { - ConnectionListener(NetworkTableInstance inst) { - super(inst); - } + private ListenerStorage m_listeners = new ListenerStorage(this); - int add(boolean immediateNotify, Consumer listener) { - m_lock.lock(); - try { - if (m_poller == 0) { - m_poller = NetworkTablesJNI.createConnectionListenerPoller(m_inst.getHandle()); - startThread("NTConnectionListener"); - } - int h = NetworkTablesJNI.addPolledConnectionListener(m_poller, immediateNotify); - m_listeners.put(h, listener); - return h; - } finally { - m_lock.unlock(); - } - } - - void remove(int listener) { - m_lock.lock(); - try { - m_listeners.remove(listener); - } finally { - m_lock.unlock(); - } - NetworkTablesJNI.removeConnectionListener(listener); - } - - @Override - protected ConnectionNotification[] readQueue() { - return NetworkTablesJNI.readConnectionListenerQueue(m_inst, m_poller); - } - - @Override - protected void destroyPoller() { - NetworkTablesJNI.destroyConnectionListenerPoller(m_poller); - } + /** + * Remove a connection listener. + * + * @param listener Listener handle to remove + */ + public void removeListener(int listener) { + m_listeners.remove(listener); } - private ConnectionListener m_connectionListener = new ConnectionListener(this); + /** + * Wait for the listener queue to be empty. This is primarily useful for deterministic + * testing. This blocks until either the listener queue is empty (e.g. there are no + * more events that need to be passed along to callbacks or poll queues) or the timeout expires. + * + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to + * block indefinitely + * @return False if timed out, otherwise true. + */ + public boolean waitForListenerQueue(double timeout) { + return m_listeners.waitForQueue(timeout); + } /** * Add a connection listener. The callback function is called asynchronously on a separate @@ -530,110 +561,35 @@ public final class NetworkTableInstance implements AutoCloseable { * @return Listener handle */ public int addConnectionListener( - boolean immediateNotify, Consumer listener) { - return m_connectionListener.add(immediateNotify, listener); + boolean immediateNotify, Consumer listener) { + return m_listeners.add(m_handle, + NetworkTableEvent.kConnection | (immediateNotify ? NetworkTableEvent.kImmediate : 0), + listener); } /** - * Remove a connection listener. - * - * @param listener Listener handle to remove - */ - public void removeConnectionListener(int listener) { - m_connectionListener.remove(listener); - } - - /** - * Wait for the connection listener queue to be empty. This is primarily useful for deterministic - * testing. This blocks until either the connection listener queue is empty (e.g. there are no - * more events that need to be passed along to callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to - * block indefinitely - * @return False if timed out, otherwise true. - */ - public boolean waitForConnectionListenerQueue(double timeout) { - return m_connectionListener.waitForQueue(timeout); - } - - private static final class TopicListener extends ListenerBase { - TopicListener(NetworkTableInstance inst) { - super(inst); - } - - int add(int handle, int eventMask, Consumer listener) { - m_lock.lock(); - try { - if (m_poller == 0) { - m_poller = NetworkTablesJNI.createTopicListenerPoller(m_inst.getHandle()); - startThread("NTTopicListener"); - } - int h = NetworkTablesJNI.addPolledTopicListener(m_poller, handle, eventMask); - m_listeners.put(h, listener); - return h; - } finally { - m_lock.unlock(); - } - } - - int add(String[] prefixes, int eventMask, Consumer listener) { - m_lock.lock(); - try { - if (m_poller == 0) { - m_poller = NetworkTablesJNI.createTopicListenerPoller(m_inst.getHandle()); - startThread("NTTopicListener"); - } - int h = NetworkTablesJNI.addPolledTopicListener(m_poller, prefixes, eventMask); - m_listeners.put(h, listener); - return h; - } finally { - m_lock.unlock(); - } - } - - void remove(int listener) { - m_lock.lock(); - try { - m_listeners.remove(listener); - } finally { - m_lock.unlock(); - } - NetworkTablesJNI.removeTopicListener(listener); - } - - @Override - protected TopicNotification[] readQueue() { - return NetworkTablesJNI.readTopicListenerQueue(m_inst, m_poller); - } - - @Override - protected void destroyPoller() { - NetworkTablesJNI.destroyTopicListenerPoller(m_poller); - } - } - - private TopicListener m_topicListener = new TopicListener(this); - - /** - * Add a topic listener for changes on a particular topic. The callback function is called + * Add a listener for changes on a particular topic. The callback function is called * asynchronously on a separate thread, so it's important to use synchronization or atomics when * accessing any shared state from the callback function. * + *

This creates a corresponding internal subscriber with the lifetime of the + * listener. + * * @param topic Topic * @param eventMask Bitmask of TopicListenerFlags values * @param listener Listener function * @return Listener handle */ - public int addTopicListener( - Topic topic, int eventMask, Consumer listener) { + public int addListener( + Topic topic, int eventMask, Consumer listener) { if (topic.getInstance().getHandle() != m_handle) { throw new IllegalArgumentException("topic is not from this instance"); } - return m_topicListener.add(topic.getHandle(), eventMask, listener); + return m_listeners.add(topic.getHandle(), eventMask, listener); } /** - * Add a topic listener for topic changes on a subscriber. The callback function is called + * Add a listener for changes on a subscriber. The callback function is called * asynchronously on a separate thread, so it's important to use synchronization or atomics when * accessing any shared state from the callback function. This does NOT keep the subscriber * active. @@ -643,16 +599,16 @@ public final class NetworkTableInstance implements AutoCloseable { * @param listener Listener function * @return Listener handle */ - public int addTopicListener( - Subscriber subscriber, int eventMask, Consumer listener) { + public int addListener( + Subscriber subscriber, int eventMask, Consumer listener) { if (subscriber.getTopic().getInstance().getHandle() != m_handle) { throw new IllegalArgumentException("subscriber is not from this instance"); } - return m_topicListener.add(subscriber.getHandle(), eventMask, listener); + return m_listeners.add(subscriber.getHandle(), eventMask, listener); } /** - * Add a topic listener for topic changes on a subscriber. The callback function is called + * Add a listener for changes on a subscriber. The callback function is called * asynchronously on a separate thread, so it's important to use synchronization or atomics when * accessing any shared state from the callback function. This does NOT keep the subscriber * active. @@ -662,16 +618,16 @@ public final class NetworkTableInstance implements AutoCloseable { * @param listener Listener function * @return Listener handle */ - public int addTopicListener( - MultiSubscriber subscriber, int eventMask, Consumer listener) { + public int addListener( + MultiSubscriber subscriber, int eventMask, Consumer listener) { if (subscriber.getInstance().getHandle() != m_handle) { throw new IllegalArgumentException("subscriber is not from this instance"); } - return m_topicListener.add(subscriber.getHandle(), eventMask, listener); + return m_listeners.add(subscriber.getHandle(), eventMask, listener); } /** - * Add a topic listener for topic changes on an entry. The callback function is called + * Add a listener for changes on an entry. The callback function is called * asynchronously on a separate thread, so it's important to use synchronization or atomics when * accessing any shared state from the callback function. * @@ -680,173 +636,33 @@ public final class NetworkTableInstance implements AutoCloseable { * @param listener Listener function * @return Listener handle */ - public int addTopicListener( - NetworkTableEntry entry, int eventMask, Consumer listener) { + public int addListener( + NetworkTableEntry entry, int eventMask, Consumer listener) { if (entry.getTopic().getInstance().getHandle() != m_handle) { throw new IllegalArgumentException("entry is not from this instance"); } - return m_topicListener.add(entry.getHandle(), eventMask, listener); + return m_listeners.add(entry.getHandle(), eventMask, listener); } /** - * Add a topic listener for changes to topics with names that start with any of the given + * Add a listener for changes to topics with names that start with any of the given * prefixes. The callback function is called asynchronously on a separate thread, so it's * important to use synchronization or atomics when accessing any shared state from the callback * function. * + *

This creates a corresponding internal subscriber with the lifetime of the + * listener. + * * @param prefixes Topic name string prefixes * @param eventMask Bitmask of TopicListenerFlags values * @param listener Listener function * @return Listener handle */ - public int addTopicListener( + public int addListener( String[] prefixes, int eventMask, - Consumer listener) { - return m_topicListener.add(prefixes, eventMask, listener); - } - - /** - * Remove a topic listener. - * - * @param listener Listener handle to remove - */ - public void removeTopicListener(int listener) { - m_topicListener.remove(listener); - } - - /** - * Wait for the topic listener queue to be empty. This is primarily useful for deterministic - * testing. This blocks until either the topic listener queue is empty (e.g. there are no - * more events that need to be passed along to callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to - * block indefinitely - * @return False if timed out, otherwise true. - */ - public boolean waitForTopicListenerQueue(double timeout) { - return m_topicListener.waitForQueue(timeout); - } - - private static final class ValueListener extends ListenerBase { - ValueListener(NetworkTableInstance inst) { - super(inst); - } - - int add(int handle, int eventMask, Consumer listener) { - m_lock.lock(); - try { - if (m_poller == 0) { - m_poller = NetworkTablesJNI.createValueListenerPoller(m_inst.getHandle()); - startThread("NTValueListener"); - } - int h = NetworkTablesJNI.addPolledValueListener(m_poller, handle, eventMask); - m_listeners.put(h, listener); - return h; - } finally { - m_lock.unlock(); - } - } - - void remove(int listener) { - m_lock.lock(); - try { - m_listeners.remove(listener); - } finally { - m_lock.unlock(); - } - NetworkTablesJNI.removeValueListener(listener); - } - - @Override - protected ValueNotification[] readQueue() { - return NetworkTablesJNI.readValueListenerQueue(m_inst, m_poller); - } - - @Override - protected void destroyPoller() { - NetworkTablesJNI.destroyValueListenerPoller(m_poller); - } - } - - private ValueListener m_valueListener = new ValueListener(this); - - /** - * Add a value listener for value changes on a subscriber. The callback function is called - * asynchronously on a separate thread, so it's important to use synchronization or atomics when - * accessing any shared state from the callback function. This does NOT keep the subscriber - * active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of ValueListenerFlags values - * @param listener Listener function - * @return Listener handle - */ - public int addValueListener( - Subscriber subscriber, int eventMask, Consumer listener) { - if (subscriber.getTopic().getInstance().getHandle() != m_handle) { - throw new IllegalArgumentException("subscriber is not from this instance"); - } - return m_valueListener.add(subscriber.getHandle(), eventMask, listener); - } - - /** - * Add a value listener for value changes on a subscriber. The callback function is called - * asynchronously on a separate thread, so it's important to use synchronization or atomics when - * accessing any shared state from the callback function. This does NOT keep the subscriber - * active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of ValueListenerFlags values - * @param listener Listener function - * @return Listener handle - */ - public int addValueListener( - MultiSubscriber subscriber, int eventMask, Consumer listener) { - if (subscriber.getInstance().getHandle() != m_handle) { - throw new IllegalArgumentException("subscriber is not from this instance"); - } - return m_valueListener.add(subscriber.getHandle(), eventMask, listener); - } - - /** - * Add a value listener for value changes on an entry. The callback function is called - * asynchronously on a separate thread, so it's important to use synchronization or atomics when - * accessing any shared state from the callback function. - * - * @param entry Entry - * @param eventMask Bitmask of ValueListenerFlags values - * @param listener Listener function - * @return Listener handle - */ - public int addValueListener( - NetworkTableEntry entry, int eventMask, Consumer listener) { - if (entry.getTopic().getInstance().getHandle() != m_handle) { - throw new IllegalArgumentException("entry is not from this instance"); - } - return m_valueListener.add(entry.getHandle(), eventMask, listener); - } - - /** - * Remove a value listener. - * - * @param listener Listener handle to remove - */ - public void removeValueListener(int listener) { - m_valueListener.remove(listener); - } - - /** - * Wait for the value listener queue to be empty. This is primarily useful for deterministic - * testing. This blocks until either the value listener queue is empty (e.g. there are no - * more events that need to be passed along to callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to - * block indefinitely - * @return False if timed out, otherwise true. - */ - public boolean waitForValueListenerQueue(double timeout) { - return m_valueListener.waitForQueue(timeout); + Consumer listener) { + return m_listeners.add(prefixes, eventMask, listener); } /* @@ -871,8 +687,8 @@ public final class NetworkTableInstance implements AutoCloseable { } /** - * Stops local-only operation. startServer or startClient can be called after this call to start a - * server or client. + * Stops local-only operation. startServer or startClient can be called after this call to start + * a server or client. */ public void stopLocal() { NetworkTablesJNI.stopLocal(m_handle); @@ -1003,8 +819,8 @@ public final class NetworkTableInstance implements AutoCloseable { } /** - * Sets server addresses and ports for client (without restarting client). The client will attempt - * to connect to each server in round robin fashion. + * Sets server addresses and ports for client (without restarting client). The client will + * attempt to connect to each server in round robin fashion. * * @param serverNames array of server names * @param ports array of port numbers (0=default) @@ -1067,8 +883,8 @@ public final class NetworkTableInstance implements AutoCloseable { /** * Flushes all updated values immediately to the network. Note: This is rate-limited to protect - * the network from flooding. This is primarily useful for synchronizing network updates with user - * code. + * the network from flooding. This is primarily useful for synchronizing network updates with + * user code. */ public void flush() { NetworkTablesJNI.flush(m_handle); @@ -1137,98 +953,19 @@ public final class NetworkTableInstance implements AutoCloseable { NetworkTablesJNI.stopConnectionDataLog(logger); } - private final ReentrantLock m_loggerLock = new ReentrantLock(); - private final Map> m_loggers = new HashMap<>(); - private int m_loggerPoller; - - @SuppressWarnings("PMD.AvoidCatchingThrowable") - private void startLogThread() { - var loggerThread = - new Thread( - () -> { - boolean wasInterrupted = false; - while (!Thread.interrupted()) { - try { - WPIUtilJNI.waitForObject(m_loggerPoller); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - // don't try to destroy poller, as its handle is likely no longer valid - wasInterrupted = true; - break; - } - LogMessage[] events = NetworkTablesJNI.readLoggerQueue(this, m_loggerPoller); - for (LogMessage event : events) { - Consumer logger; - m_loggerLock.lock(); - try { - logger = m_loggers.get(event.logger); - } finally { - m_loggerLock.unlock(); - } - if (logger != null) { - try { - logger.accept(event); - } catch (Throwable throwable) { - System.err.println( - "Unhandled exception during logger callback: " + throwable.toString()); - throwable.printStackTrace(); - } - } - } - } - m_loggerLock.lock(); - try { - if (!wasInterrupted) { - NetworkTablesJNI.destroyLoggerPoller(m_loggerPoller); - } - } finally { - m_loggerLock.unlock(); - } - }, - "NTLogger"); - loggerThread.setDaemon(true); - loggerThread.start(); - } - /** * Add logger callback function. By default, log messages are sent to stderr; this function sends * log messages with the specified levels to the provided callback function instead. The callback * function will only be called for log messages with level greater than or equal to minLevel and * less than or equal to maxLevel; messages outside this range will be silently ignored. * - * @param func log callback function * @param minLevel minimum log level * @param maxLevel maximum log level - * @return Logger handle + * @param func callback function + * @return Listener handle */ - public int addLogger(Consumer func, int minLevel, int maxLevel) { - m_loggerLock.lock(); - try { - if (m_loggerPoller == 0) { - m_loggerPoller = NetworkTablesJNI.createLoggerPoller(m_handle); - startLogThread(); - } - int handle = NetworkTablesJNI.addPolledLogger(m_loggerPoller, minLevel, maxLevel); - m_loggers.put(handle, func); - return handle; - } finally { - m_loggerLock.unlock(); - } - } - - /** - * Remove a logger. - * - * @param logger Logger handle to remove - */ - public void removeLogger(int logger) { - m_loggerLock.lock(); - try { - m_loggers.remove(logger); - } finally { - m_loggerLock.unlock(); - } - NetworkTablesJNI.removeLogger(logger); + public int addLogger(int minLevel, int maxLevel, Consumer func) { + return m_listeners.addLogger(minLevel, maxLevel, func); } @Override diff --git a/ntcore/src/generate/java/NetworkTablesJNI.java.jinja b/ntcore/src/generate/java/NetworkTablesJNI.java.jinja index ebb3f97fd6..682951573d 100644 --- a/ntcore/src/generate/java/NetworkTablesJNI.java.jinja +++ b/ntcore/src/generate/java/NetworkTablesJNI.java.jinja @@ -199,40 +199,18 @@ public final class NetworkTablesJNI { public static native TopicInfo getTopicInfo(NetworkTableInstance inst, int topic); - public static native int createTopicListenerPoller(int inst); + public static native int createListenerPoller(int inst); - public static native void destroyTopicListenerPoller(int poller); + public static native void destroyListenerPoller(int poller); - public static native int addPolledTopicListener(int poller, String[] prefixes, int flags); + public static native int addListener(int poller, String[] prefixes, int mask); - public static native int addPolledTopicListener(int poller, int handle, int flags); + public static native int addListener(int poller, int handle, int mask); - public static native TopicNotification[] readTopicListenerQueue( + public static native NetworkTableEvent[] readListenerQueue( NetworkTableInstance inst, int poller); - public static native void removeTopicListener(int topicListener); - - public static native int createValueListenerPoller(int inst); - - public static native void destroyValueListenerPoller(int poller); - - public static native int addPolledValueListener(int poller, int subentry, int flags); - - public static native ValueNotification[] readValueListenerQueue( - NetworkTableInstance inst, int poller); - - public static native void removeValueListener(int valueListener); - - public static native int createConnectionListenerPoller(int inst); - - public static native void destroyConnectionListenerPoller(int poller); - - public static native int addPolledConnectionListener(int poller, boolean immediateNotify); - - public static native ConnectionNotification[] readConnectionListenerQueue( - NetworkTableInstance inst, int poller); - - public static native void removeConnectionListener(int connListener); + public static native void removeListener(int listener); public static native int getNetworkMode(int inst); @@ -287,13 +265,5 @@ public final class NetworkTablesJNI { public static native void stopConnectionDataLog(int logger); - public static native int createLoggerPoller(int inst); - - public static native void destroyLoggerPoller(int poller); - - public static native int addPolledLogger(int poller, int minLevel, int maxLevel); - - public static native LogMessage[] readLoggerQueue(NetworkTableInstance inst, int poller); - - public static native void removeLogger(int logger); + public static native int addLogger(int poller, int minLevel, int maxLevel); } diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionListener.java b/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionListener.java deleted file mode 100644 index c8996ffd6d..0000000000 --- a/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionListener.java +++ /dev/null @@ -1,71 +0,0 @@ -// 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. - -package edu.wpi.first.networktables; - -import java.util.function.Consumer; - -/** - * Connection listener. This calls back to a callback function when a connection change occurs. The - * callback function is called asynchronously on a separate thread, so it's important to use - * synchronization or atomics when accessing any shared state from the callback function. - */ -public final class ConnectionListener implements AutoCloseable { - /** - * Create a listener for connection changes. - * - * @param inst Instance - * @param immediateNotify if notification should be immediately created for existing connections - * @param listener Listener function - */ - public ConnectionListener( - NetworkTableInstance inst, - boolean immediateNotify, - Consumer listener) { - m_inst = inst; - m_handle = inst.addConnectionListener(immediateNotify, listener); - } - - @Override - public synchronized void close() { - if (m_handle != 0) { - m_inst.removeConnectionListener(m_handle); - m_handle = 0; - } - } - - /** - * Determines if the native handle is valid. - * - * @return True if the native handle is valid, false otherwise. - */ - public boolean isValid() { - return m_handle != 0; - } - - /** - * Gets the native handle. - * - * @return Native handle - */ - public int getHandle() { - return m_handle; - } - - /** - * Wait for the connection listener queue to be empty. This is primarily useful for deterministic - * testing. This blocks until either the connection listener queue is empty (e.g. there are no - * more events that need to be passed along to callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to - * block indefinitely - * @return False if timed out, otherwise true. - */ - public boolean waitForQueue(double timeout) { - return m_inst.waitForConnectionListenerQueue(timeout); - } - - private final NetworkTableInstance m_inst; - private int m_handle; -} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionListenerPoller.java b/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionListenerPoller.java deleted file mode 100644 index 88121cd0ad..0000000000 --- a/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionListenerPoller.java +++ /dev/null @@ -1,78 +0,0 @@ -// 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. - -package edu.wpi.first.networktables; - -/** - * A connection listener. This queues connection notifications. Code using the listener must - * periodically call readQueue() to read the notifications. - */ -public final class ConnectionListenerPoller implements AutoCloseable { - /** - * Construct a connection listener poller. - * - * @param inst Instance - */ - public ConnectionListenerPoller(NetworkTableInstance inst) { - m_inst = inst; - m_handle = NetworkTablesJNI.createConnectionListenerPoller(inst.getHandle()); - } - - /** - * Create a connection listener. - * - * @param immediateNotify if notification should be immediately created for existing connections - * @return Listener handle - */ - public int add(boolean immediateNotify) { - return NetworkTablesJNI.addPolledConnectionListener(m_handle, immediateNotify); - } - - /** - * Remove a connection listener. - * - * @param listener Listener handle - */ - public void remove(int listener) { - NetworkTablesJNI.removeConnectionListener(listener); - } - - /** - * Read connection notifications. - * - * @return Connection notifications since the previous call to readQueue() - */ - public ConnectionNotification[] readQueue() { - return NetworkTablesJNI.readConnectionListenerQueue(m_inst, m_handle); - } - - @Override - public synchronized void close() { - if (m_handle != 0) { - NetworkTablesJNI.destroyConnectionListenerPoller(m_handle); - } - m_handle = 0; - } - - /** - * Determines if the native handle is valid. - * - * @return True if the native handle is valid, false otherwise. - */ - public boolean isValid() { - return m_handle != 0; - } - - /** - * Gets the native handle. - * - * @return Handle - */ - public int getHandle() { - return m_handle; - } - - private final NetworkTableInstance m_inst; - private int m_handle; -} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionNotification.java b/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionNotification.java deleted file mode 100644 index b20c17a84a..0000000000 --- a/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionNotification.java +++ /dev/null @@ -1,30 +0,0 @@ -// 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. - -package edu.wpi.first.networktables; - -/** NetworkTables Connection notification. */ -@SuppressWarnings("MemberName") -public final class ConnectionNotification extends NotificationBase { - /** True if event is due to connection being established. */ - public final boolean connected; - - /** Connection information. */ - public final ConnectionInfo conn; - - /** - * Constructor. This should generally only be used internally to NetworkTables. - * - * @param inst Instance - * @param listener Listener that was triggered - * @param connected Connected if true - * @param conn Connection information - */ - public ConnectionNotification( - NetworkTableInstance inst, int listener, boolean connected, ConnectionInfo conn) { - super(inst, listener); - this.connected = connected; - this.conn = conn; - } -} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/LogMessage.java b/ntcore/src/main/java/edu/wpi/first/networktables/LogMessage.java index 8ed653dcb9..fd090afff0 100644 --- a/ntcore/src/main/java/edu/wpi/first/networktables/LogMessage.java +++ b/ntcore/src/main/java/edu/wpi/first/networktables/LogMessage.java @@ -19,9 +19,6 @@ public final class LogMessage { public static final int kDebug3 = 7; public static final int kDebug4 = 6; - /** The logger that generated the message. */ - public final int logger; - /** Log level of the message. */ public final int level; @@ -37,26 +34,15 @@ public final class LogMessage { /** * Constructor. This should generally only be used internally to NetworkTables. * - * @param inst Instance - * @param logger Logger * @param level Log level * @param filename Filename * @param line Line number * @param message Message */ - public LogMessage( - NetworkTableInstance inst, int logger, int level, String filename, int line, String message) { - this.m_inst = inst; - this.logger = logger; + public LogMessage(int level, String filename, int line, String message) { this.level = level; this.filename = filename; this.line = line; this.message = message; } - - private final NetworkTableInstance m_inst; - - NetworkTableInstance getInstance() { - return m_inst; - } } diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableEvent.java b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableEvent.java new file mode 100644 index 0000000000..24fc52027a --- /dev/null +++ b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableEvent.java @@ -0,0 +1,127 @@ +// 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. + +package edu.wpi.first.networktables; + +/** + * NetworkTables event. + * + *

Events have flags. The flags are a bitmask and must be OR'ed together when listening to an + * event to indicate the combination of events desired to be received. + */ +@SuppressWarnings("MemberName") +public final class NetworkTableEvent { + /** No flags. */ + public static final int kNone = 0; + + /** + * Initial listener addition. Set this flag to receive immediate notification of matches to the + * flag criteria. + */ + public static final int kImmediate = 0x01; + + /** Client connected (on server, any client connected). */ + public static final int kConnected = 0x02; + + /** Client disconnected (on server, any client disconnected). */ + public static final int kDisconnected = 0x04; + + /** Any connection event (connect or disconnect). */ + public static final int kConnection = kConnected | kDisconnected; + + /** New topic published. */ + public static final int kPublish = 0x08; + + /** Topic unpublished. */ + public static final int kUnpublish = 0x10; + + /** Topic properties changed. */ + public static final int kProperties = 0x20; + + /** Any topic event (publish, unpublish, or properties changed). */ + public static final int kTopic = kPublish | kUnpublish | kProperties; + + /** Topic value updated (via network). */ + public static final int kValueRemote = 0x40; + + /** Topic value updated (local). */ + public static final int kValueLocal = 0x80; + + /** Topic value updated (network or local). */ + public static final int kValueAll = kValueRemote | kValueLocal; + + /** Log message. */ + public static final int kLogMessage = 0x100; + + /** + * Handle of listener that was triggered. The value returned when adding the listener can be used + * to map this to a specific added listener. + */ + public final int listener; + + /** + * Event flags. For example, kPublish if the topic was not previously published. Also indicates + * the data included with the event: + * + *

    + *
  • kConnected or kDisconnected: connInfo + *
  • kPublish, kUnpublish, or kProperties: topicInfo + *
  • kValueRemote, kValueLocal: valueData + *
  • kLogMessage: logMessage + *
+ */ + public final int flags; + + /** Connection information (for connection events). */ + public final ConnectionInfo connInfo; + + /** Topic information (for topic events). */ + public final TopicInfo topicInfo; + + /** Value data (for value events). */ + public final ValueEventData valueData; + + /** Log message (for log message events). */ + public final LogMessage logMessage; + + /** + * Constructor. This should generally only be used internally to NetworkTables. + * + * @param inst Instance + * @param listener Listener that was triggered + * @param flags Event flags + * @param connInfo Connection information + * @param topicInfo Topic information + * @param valueData Value data + * @param logMessage Log message + */ + public NetworkTableEvent( + NetworkTableInstance inst, + int listener, + int flags, + ConnectionInfo connInfo, + TopicInfo topicInfo, + ValueEventData valueData, + LogMessage logMessage) { + this.m_inst = inst; + this.listener = listener; + this.flags = flags; + this.connInfo = connInfo; + this.topicInfo = topicInfo; + this.valueData = valueData; + this.logMessage = logMessage; + } + + /* Network table instance. */ + private final NetworkTableInstance m_inst; + + /** + * Gets the instance associated with this event. + * + * @return Instance + */ + public NetworkTableInstance getInstance() { + return m_inst; + } +} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableListener.java b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableListener.java new file mode 100644 index 0000000000..a06d871450 --- /dev/null +++ b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableListener.java @@ -0,0 +1,167 @@ +// 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. + +package edu.wpi.first.networktables; + +import java.util.function.Consumer; + +/** + * Event listener. This calls back to a callback function when an event matching the specified mask + * occurs. The callback function is called asynchronously on a separate thread, so it's important to + * use synchronization or atomics when accessing any shared state from the callback function. + */ +public final class NetworkTableListener implements AutoCloseable { + /** + * Create a listener for changes to topics with names that start with any of the given prefixes. + * This creates a corresponding internal subscriber with the lifetime of the listener. + * + * @param inst Instance + * @param prefixes Topic name string prefixes + * @param eventMask Bitmask of NetworkTableEvent flags values + * @param listener Listener function + * @return Listener + */ + public static NetworkTableListener createListener( + NetworkTableInstance inst, + String[] prefixes, + int eventMask, + Consumer listener) { + return new NetworkTableListener(inst, inst.addListener(prefixes, eventMask, listener)); + } + + /** + * Create a listener for changes on a particular topic. This creates a corresponding internal + * subscriber with the lifetime of the listener. + * + * @param topic Topic + * @param eventMask Bitmask of NetworkTableEvent flags values + * @param listener Listener function + * @return Listener + */ + public static NetworkTableListener createListener( + Topic topic, int eventMask, Consumer listener) { + NetworkTableInstance inst = topic.getInstance(); + return new NetworkTableListener(inst, inst.addListener(topic, eventMask, listener)); + } + + /** + * Create a listener for topic changes on a subscriber. This does NOT keep the subscriber active. + * + * @param subscriber Subscriber + * @param eventMask Bitmask of NetworkTableEvent flags values + * @param listener Listener function + * @return Listener + */ + public static NetworkTableListener createListener( + Subscriber subscriber, int eventMask, Consumer listener) { + NetworkTableInstance inst = subscriber.getTopic().getInstance(); + return new NetworkTableListener(inst, inst.addListener(subscriber, eventMask, listener)); + } + + /** + * Create a listener for topic changes on a subscriber. This does NOT keep the subscriber active. + * + * @param subscriber Subscriber + * @param eventMask Bitmask of NetworkTableEvent flags values + * @param listener Listener function + * @return Listener + */ + public static NetworkTableListener createListener( + MultiSubscriber subscriber, int eventMask, Consumer listener) { + NetworkTableInstance inst = subscriber.getInstance(); + return new NetworkTableListener(inst, inst.addListener(subscriber, eventMask, listener)); + } + + /** + * Create a listener for topic changes on an entry. + * + * @param entry Entry + * @param eventMask Bitmask of NetworkTableEvent flags values + * @param listener Listener function + * @return Listener + */ + public static NetworkTableListener createListener( + NetworkTableEntry entry, int eventMask, Consumer listener) { + NetworkTableInstance inst = entry.getInstance(); + return new NetworkTableListener(inst, inst.addListener(entry, eventMask, listener)); + } + + /** + * Create a connection listener. + * + * @param inst instance + * @param immediateNotify notify listener of all existing connections + * @param listener listener function + * @return Listener + */ + public static NetworkTableListener createConnectionListener( + NetworkTableInstance inst, boolean immediateNotify, Consumer listener) { + return new NetworkTableListener(inst, inst.addConnectionListener(immediateNotify, listener)); + } + + /** + * Create a listener for log messages. By default, log messages are sent to stderr; this function + * sends log messages with the specified levels to the provided callback function instead. The + * callback function will only be called for log messages with level greater than or equal to + * minLevel and less than or equal to maxLevel; messages outside this range will be silently + * ignored. + * + * @param inst instance + * @param minLevel minimum log level + * @param maxLevel maximum log level + * @param listener listener function + * @return Listener + */ + public static NetworkTableListener createLogger( + NetworkTableInstance inst, int minLevel, int maxLevel, Consumer listener) { + return new NetworkTableListener(inst, inst.addLogger(minLevel, maxLevel, listener)); + } + + @Override + public synchronized void close() { + if (m_handle != 0) { + m_inst.removeListener(m_handle); + m_handle = 0; + } + } + + /** + * Determines if the native handle is valid. + * + * @return True if the native handle is valid, false otherwise. + */ + public boolean isValid() { + return m_handle != 0; + } + + /** + * Gets the native handle. + * + * @return Native handle + */ + public int getHandle() { + return m_handle; + } + + /** + * Wait for the topic listener queue to be empty. This is primarily useful for deterministic + * testing. This blocks until either the topic listener queue is empty (e.g. there are no more + * events that need to be passed along to callbacks or poll queues) or the timeout expires. + * + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to + * block indefinitely + * @return False if timed out, otherwise true. + */ + public boolean waitForQueue(double timeout) { + return m_inst.waitForListenerQueue(timeout); + } + + private NetworkTableListener(NetworkTableInstance inst, int handle) { + m_inst = inst; + m_handle = handle; + } + + private final NetworkTableInstance m_inst; + private int m_handle; +} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableListenerPoller.java b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableListenerPoller.java new file mode 100644 index 0000000000..105ff77090 --- /dev/null +++ b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableListenerPoller.java @@ -0,0 +1,154 @@ +// 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. + +package edu.wpi.first.networktables; + +/** + * Topic change listener. This queues topic change events matching the specified mask. Code using + * the listener must periodically call readQueue() to read the events. + */ +public final class NetworkTableListenerPoller implements AutoCloseable { + /** + * Construct a topic listener poller. + * + * @param inst Instance + */ + public NetworkTableListenerPoller(NetworkTableInstance inst) { + m_inst = inst; + m_handle = NetworkTablesJNI.createListenerPoller(inst.getHandle()); + } + + /** + * Start listening to topic changes for topics with names that start with any of the given + * prefixes. This creates a corresponding internal subscriber with the lifetime of the listener. + * + * @param prefixes Topic name string prefixes + * @param eventMask Bitmask of TopicListenerFlags values + * @return Listener handle + */ + public int addListener(String[] prefixes, int eventMask) { + return NetworkTablesJNI.addListener(m_handle, prefixes, eventMask); + } + + /** + * Start listening to changes to a particular topic. This creates a corresponding internal + * subscriber with the lifetime of the listener. + * + * @param topic Topic + * @param eventMask Bitmask of TopicListenerFlags values + * @return Listener handle + */ + public int addListener(Topic topic, int eventMask) { + return NetworkTablesJNI.addListener(m_handle, topic.getHandle(), eventMask); + } + + /** + * Start listening to topic changes on a subscriber. This does NOT keep the subscriber active. + * + * @param subscriber Subscriber + * @param eventMask Bitmask of TopicListenerFlags values + * @return Listener handle + */ + public int addListener(Subscriber subscriber, int eventMask) { + return NetworkTablesJNI.addListener(m_handle, subscriber.getHandle(), eventMask); + } + + /** + * Start listening to topic changes on a subscriber. This does NOT keep the subscriber active. + * + * @param subscriber Subscriber + * @param eventMask Bitmask of TopicListenerFlags values + * @return Listener handle + */ + public int addListener(MultiSubscriber subscriber, int eventMask) { + return NetworkTablesJNI.addListener(m_handle, subscriber.getHandle(), eventMask); + } + + /** + * Start listening to topic changes on an entry. + * + * @param entry Entry + * @param eventMask Bitmask of TopicListenerFlags values + * @return Listener handle + */ + public int addListener(NetworkTableEntry entry, int eventMask) { + return NetworkTablesJNI.addListener(m_handle, entry.getHandle(), eventMask); + } + + /** + * Add a connection listener. The callback function is called asynchronously on a separate thread, + * so it's important to use synchronization or atomics when accessing any shared state from the + * callback function. + * + * @param immediateNotify notify listener of all existing connections + * @return Listener handle + */ + public int addConnectionListener(boolean immediateNotify) { + return NetworkTablesJNI.addListener( + m_handle, + m_inst.getHandle(), + NetworkTableEvent.kConnection | (immediateNotify ? NetworkTableEvent.kImmediate : 0)); + } + + /** + * Add logger callback function. By default, log messages are sent to stderr; this function sends + * log messages with the specified levels to the provided callback function instead. The callback + * function will only be called for log messages with level greater than or equal to minLevel and + * less than or equal to maxLevel; messages outside this range will be silently ignored. + * + * @param minLevel minimum log level + * @param maxLevel maximum log level + * @return Listener handle + */ + public int addLogger(int minLevel, int maxLevel) { + return NetworkTablesJNI.addLogger(m_handle, minLevel, maxLevel); + } + + /** + * Remove a listener. + * + * @param listener Listener handle + */ + public void removeListener(int listener) { + NetworkTablesJNI.removeListener(listener); + } + + /** + * Read topic notifications. + * + * @return Topic notifications since the previous call to readQueue() + */ + public NetworkTableEvent[] readQueue() { + return NetworkTablesJNI.readListenerQueue(m_inst, m_handle); + } + + @Override + public synchronized void close() { + if (m_handle != 0) { + NetworkTablesJNI.destroyListenerPoller(m_handle); + } + m_handle = 0; + } + + /** + * Determines if the native handle is valid. + * + * @return True if the native handle is valid, false otherwise. + */ + public boolean isValid() { + return m_handle != 0; + } + + /** + * Gets the native handle. + * + * @return Handle + */ + public int getHandle() { + return m_handle; + } + + private final NetworkTableInstance m_inst; + private int m_handle; +} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/NotificationBase.java b/ntcore/src/main/java/edu/wpi/first/networktables/NotificationBase.java deleted file mode 100644 index 5496692c64..0000000000 --- a/ntcore/src/main/java/edu/wpi/first/networktables/NotificationBase.java +++ /dev/null @@ -1,38 +0,0 @@ -// 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. - -package edu.wpi.first.networktables; - -/** Base class for NetworkTables notifications. */ -@SuppressWarnings("MemberName") -public class NotificationBase { - /** - * Handle of listener that was triggered. The value returned when adding the listener can be used - * to map this to a specific added listener. - */ - public final int listener; - - /** - * Constructor. This should generally only be used internally to NetworkTables. - * - * @param inst Instance - * @param listener Listener handle - */ - public NotificationBase(NetworkTableInstance inst, int listener) { - this.m_inst = inst; - this.listener = listener; - } - - /* Network table instance. */ - protected final NetworkTableInstance m_inst; - - /** - * Gets the instance associated with this notification. - * - * @return Instance - */ - public NetworkTableInstance getInstance() { - return m_inst; - } -} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/TopicListener.java b/ntcore/src/main/java/edu/wpi/first/networktables/TopicListener.java deleted file mode 100644 index f6e0b4c6d0..0000000000 --- a/ntcore/src/main/java/edu/wpi/first/networktables/TopicListener.java +++ /dev/null @@ -1,124 +0,0 @@ -// 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. - -package edu.wpi.first.networktables; - -import java.util.function.Consumer; - -/** - * Topic change listener. This calls back to a callback function when a topic change matching the - * specified mask occurs. The callback function is called asynchronously on a separate thread, so - * it's important to use synchronization or atomics when accessing any shared state from the - * callback function. - */ -public final class TopicListener implements AutoCloseable { - /** - * Create a listener for changes on a particular topic. - * - * @param topic Topic - * @param eventMask Bitmask of TopicListenerFlags values - * @param listener Listener function - */ - public TopicListener(Topic topic, int eventMask, Consumer listener) { - m_inst = topic.getInstance(); - m_handle = m_inst.addTopicListener(topic, eventMask, listener); - } - - /** - * Create a listener for topic changes on a subscriber. This does NOT keep the subscriber active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of TopicListenerFlags values - * @param listener Listener function - */ - public TopicListener(Subscriber subscriber, int eventMask, Consumer listener) { - m_inst = subscriber.getTopic().getInstance(); - m_handle = m_inst.addTopicListener(subscriber, eventMask, listener); - } - - /** - * Create a listener for topic changes on a subscriber. This does NOT keep the subscriber active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of TopicListenerFlags values - * @param listener Listener function - */ - public TopicListener( - MultiSubscriber subscriber, int eventMask, Consumer listener) { - m_inst = subscriber.getInstance(); - m_handle = m_inst.addTopicListener(subscriber, eventMask, listener); - } - - /** - * Create a listener for topic changes on an entry. - * - * @param entry Entry - * @param eventMask Bitmask of TopicListenerFlags values - * @param listener Listener function - */ - public TopicListener( - NetworkTableEntry entry, int eventMask, Consumer listener) { - m_inst = entry.getInstance(); - m_handle = m_inst.addTopicListener(entry, eventMask, listener); - } - - /** - * Create a listener for changes to topics with names that start with any of the given prefixes. - * - * @param inst Instance - * @param prefixes Topic name string prefixes - * @param eventMask Bitmask of TopicListenerFlags values - * @param listener Listener function - */ - public TopicListener( - NetworkTableInstance inst, - String[] prefixes, - int eventMask, - Consumer listener) { - m_inst = inst; - m_handle = inst.addTopicListener(prefixes, eventMask, listener); - } - - @Override - public synchronized void close() { - if (m_handle != 0) { - m_inst.removeTopicListener(m_handle); - m_handle = 0; - } - } - - /** - * Determines if the native handle is valid. - * - * @return True if the native handle is valid, false otherwise. - */ - public boolean isValid() { - return m_handle != 0; - } - - /** - * Gets the native handle. - * - * @return Native handle - */ - public int getHandle() { - return m_handle; - } - - /** - * Wait for the topic listener queue to be empty. This is primarily useful for deterministic - * testing. This blocks until either the topic listener queue is empty (e.g. there are no more - * events that need to be passed along to callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to - * block indefinitely - * @return False if timed out, otherwise true. - */ - public boolean waitForQueue(double timeout) { - return m_inst.waitForTopicListenerQueue(timeout); - } - - private final NetworkTableInstance m_inst; - private int m_handle; -} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/TopicListenerFlags.java b/ntcore/src/main/java/edu/wpi/first/networktables/TopicListenerFlags.java deleted file mode 100644 index 2592fc5de4..0000000000 --- a/ntcore/src/main/java/edu/wpi/first/networktables/TopicListenerFlags.java +++ /dev/null @@ -1,47 +0,0 @@ -// 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. - -package edu.wpi.first.networktables; - -/** - * Flag values for use with topic listeners. - * - *

The flags are a bitmask and must be OR'ed together to indicate the combination of events - * desired to be received. - * - *

The constants kPublish, kUnpublish, and kProperties represent different events that can occur - * to topics. - */ -public enum TopicListenerFlags { - ; // no enum values - - /** - * Initial listener addition. - * - *

Set this flag to receive immediate notification of topics matching the flag criteria - * (generally only useful when combined with kPublish). - */ - public static final int kImmediate = 0x01; - - /** - * Newly published topic. - * - *

Set this flag to receive a notification when a topic is initially published. - */ - public static final int kPublish = 0x02; - - /** - * Topic has no more publishers. - * - *

Set this flag to receive a notification when a topic has no more publishers. - */ - public static final int kUnpublish = 0x04; - - /** - * Topic's properties changed. - * - *

Set this flag to receive a notification when an topic's properties change. - */ - public static final int kProperties = 0x08; -} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/TopicListenerPoller.java b/ntcore/src/main/java/edu/wpi/first/networktables/TopicListenerPoller.java deleted file mode 100644 index 02fd87e3e1..0000000000 --- a/ntcore/src/main/java/edu/wpi/first/networktables/TopicListenerPoller.java +++ /dev/null @@ -1,124 +0,0 @@ -// 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. - -package edu.wpi.first.networktables; - -/** - * Topic change listener. This queues topic change events matching the specified mask. Code using - * the listener must periodically call readQueue() to read the events. - */ -public final class TopicListenerPoller implements AutoCloseable { - /** - * Construct a topic listener poller. - * - * @param inst Instance - */ - public TopicListenerPoller(NetworkTableInstance inst) { - m_inst = inst; - m_handle = NetworkTablesJNI.createTopicListenerPoller(inst.getHandle()); - } - - /** - * Start listening to changes to a particular topic. - * - * @param topic Topic - * @param eventMask Bitmask of TopicListenerFlags values - * @return Listener handle - */ - public int add(Topic topic, int eventMask) { - return NetworkTablesJNI.addPolledTopicListener(m_handle, topic.getHandle(), eventMask); - } - - /** - * Start listening to topic changes on a subscriber. This does NOT keep the subscriber active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of TopicListenerFlags values - * @return Listener handle - */ - public int add(Subscriber subscriber, int eventMask) { - return NetworkTablesJNI.addPolledTopicListener(m_handle, subscriber.getHandle(), eventMask); - } - - /** - * Start listening to topic changes on a subscriber. This does NOT keep the subscriber active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of TopicListenerFlags values - * @return Listener handle - */ - public int add(MultiSubscriber subscriber, int eventMask) { - return NetworkTablesJNI.addPolledTopicListener(m_handle, subscriber.getHandle(), eventMask); - } - - /** - * Start listening to topic changes on an entry. - * - * @param entry Entry - * @param eventMask Bitmask of TopicListenerFlags values - * @return Listener handle - */ - public int add(NetworkTableEntry entry, int eventMask) { - return NetworkTablesJNI.addPolledTopicListener(m_handle, entry.getHandle(), eventMask); - } - - /** - * Start listening to topic changes for topics with names that start with any of the given - * prefixes. - * - * @param prefixes Topic name string prefixes - * @param eventMask Bitmask of TopicListenerFlags values - * @return Listener handle - */ - public int add(String[] prefixes, int eventMask) { - return NetworkTablesJNI.addPolledTopicListener(m_handle, prefixes, eventMask); - } - - /** - * Remove a listener. - * - * @param listener Listener handle - */ - public void remove(int listener) { - NetworkTablesJNI.removeTopicListener(listener); - } - - /** - * Read topic notifications. - * - * @return Topic notifications since the previous call to readQueue() - */ - public TopicNotification[] readQueue() { - return NetworkTablesJNI.readTopicListenerQueue(m_inst, m_handle); - } - - @Override - public synchronized void close() { - if (m_handle != 0) { - NetworkTablesJNI.destroyTopicListenerPoller(m_handle); - } - m_handle = 0; - } - - /** - * Determines if the native handle is valid. - * - * @return True if the native handle is valid, false otherwise. - */ - public boolean isValid() { - return m_handle != 0; - } - - /** - * Gets the native handle. - * - * @return Handle - */ - public int getHandle() { - return m_handle; - } - - private final NetworkTableInstance m_inst; - private int m_handle; -} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/TopicNotification.java b/ntcore/src/main/java/edu/wpi/first/networktables/TopicNotification.java deleted file mode 100644 index e8d717446c..0000000000 --- a/ntcore/src/main/java/edu/wpi/first/networktables/TopicNotification.java +++ /dev/null @@ -1,31 +0,0 @@ -// 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. - -package edu.wpi.first.networktables; - -/** NetworkTables topic notification. */ -@SuppressWarnings("MemberName") -public final class TopicNotification extends NotificationBase { - /** Topic information. */ - public final TopicInfo info; - - /** - * Update flags. For example, {@link TopicListenerFlags#kPublish} if the topic was not previously - * published. - */ - public final int flags; - - /** - * Constructor. This should generally only be used internally to NetworkTables. - * - * @param listener Listener that was triggered - * @param info Topic information - * @param flags Update flags - */ - public TopicNotification(int listener, TopicInfo info, int flags) { - super(info.getInstance(), listener); - this.info = info; - this.flags = flags; - } -} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ValueNotification.java b/ntcore/src/main/java/edu/wpi/first/networktables/ValueEventData.java similarity index 72% rename from ntcore/src/main/java/edu/wpi/first/networktables/ValueNotification.java rename to ntcore/src/main/java/edu/wpi/first/networktables/ValueEventData.java index 067efd3fce..511bb669b0 100644 --- a/ntcore/src/main/java/edu/wpi/first/networktables/ValueNotification.java +++ b/ntcore/src/main/java/edu/wpi/first/networktables/ValueEventData.java @@ -4,9 +4,9 @@ package edu.wpi.first.networktables; -/** NetworkTables value notification. */ +/** NetworkTables value event data. */ @SuppressWarnings("MemberName") -public final class ValueNotification extends NotificationBase { +public final class ValueEventData { /** Topic handle. Topic.getHandle() can be used to map this to the corresponding Topic object. */ public final int topic; @@ -19,35 +19,26 @@ public final class ValueNotification extends NotificationBase { /** The new value. */ public final NetworkTableValue value; - /** Update flags. */ - public final int flags; - /** * Constructor. This should generally only be used internally to NetworkTables. * * @param inst Instance - * @param listener Listener that was triggered * @param topic Topic handle * @param subentry Subscriber/entry handle * @param value The new value - * @param flags Update flags */ - public ValueNotification( - NetworkTableInstance inst, - int listener, - int topic, - int subentry, - NetworkTableValue value, - int flags) { - super(inst, listener); + public ValueEventData( + NetworkTableInstance inst, int topic, int subentry, NetworkTableValue value) { + this.m_inst = inst; this.topic = topic; this.subentry = subentry; this.value = value; - this.flags = flags; } /* Cached topic object. */ - Topic m_topicObject; + private Topic m_topicObject; + + private final NetworkTableInstance m_inst; /** * Get the topic as an object. diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ValueListener.java b/ntcore/src/main/java/edu/wpi/first/networktables/ValueListener.java deleted file mode 100644 index c357cc58b1..0000000000 --- a/ntcore/src/main/java/edu/wpi/first/networktables/ValueListener.java +++ /dev/null @@ -1,95 +0,0 @@ -// 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. - -package edu.wpi.first.networktables; - -import java.util.function.Consumer; - -/** - * Value change listener. This calls back to a callback function when a value change matching the - * specified mask occurs. The callback function is called asynchronously on a separate thread, so - * it's important to use synchronization or atomics when accessing any shared state from the - * callback function. - */ -public final class ValueListener implements AutoCloseable { - /** - * Create a listener for value changes on a subscriber. This does NOT keep the subscriber active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of ValueListenerFlags values - * @param listener Listener function - */ - public ValueListener(Subscriber subscriber, int eventMask, Consumer listener) { - m_inst = subscriber.getTopic().getInstance(); - m_handle = m_inst.addValueListener(subscriber, eventMask, listener); - } - - /** - * Create a listener for value changes on a subscriber. This does NOT keep the subscriber active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of ValueListenerFlags values - * @param listener Listener function - */ - public ValueListener( - MultiSubscriber subscriber, int eventMask, Consumer listener) { - m_inst = subscriber.getInstance(); - m_handle = m_inst.addValueListener(subscriber, eventMask, listener); - } - - /** - * Create a listener for value changes on an entry. - * - * @param entry Entry - * @param eventMask Bitmask of ValueListenerFlags values - * @param listener Listener function - */ - public ValueListener( - NetworkTableEntry entry, int eventMask, Consumer listener) { - m_inst = entry.getInstance(); - m_handle = m_inst.addValueListener(entry, eventMask, listener); - } - - @Override - public synchronized void close() { - if (m_handle != 0) { - m_inst.removeValueListener(m_handle); - m_handle = 0; - } - } - - /** - * Determines if the native handle is valid. - * - * @return True if the native handle is valid, false otherwise. - */ - public boolean isValid() { - return m_handle != 0; - } - - /** - * Gets the native handle. - * - * @return Native handle - */ - public int getHandle() { - return m_handle; - } - - /** - * Wait for the value listener queue to be empty. This is primarily useful for deterministic - * testing. This blocks until either the value listener queue is empty (e.g. there are no more - * events that need to be passed along to callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to - * block indefinitely - * @return False if timed out, otherwise true. - */ - public boolean waitForQueue(double timeout) { - return m_inst.waitForValueListenerQueue(timeout); - } - - private final NetworkTableInstance m_inst; - private int m_handle; -} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ValueListenerFlags.java b/ntcore/src/main/java/edu/wpi/first/networktables/ValueListenerFlags.java deleted file mode 100644 index 9a303d6d75..0000000000 --- a/ntcore/src/main/java/edu/wpi/first/networktables/ValueListenerFlags.java +++ /dev/null @@ -1,34 +0,0 @@ -// 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. - -package edu.wpi.first.networktables; - -/** - * Flag values for use with value listeners. - * - *

The flags are a bitmask and must be OR'ed together to indicate the combination of events - * desired to be received. - * - *

By default, notifications are only generated for remote changes occurring after the listener - * is created. The constants kImmediate and kLocal are modifiers that cause notifications to be - * generated at other times. - */ -public enum ValueListenerFlags { - ; // no enum values - - /** - * Initial listener addition. - * - *

Set this flag to receive immediate notification of the current value. - */ - public static final int kImmediate = 0x01; - - /** - * Changed locally. - * - *

Set this flag to receive notification of both local changes and changes coming from remote - * nodes. By default, notifications are only generated for remote changes. - */ - public static final int kLocal = 0x02; -} diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ValueListenerPoller.java b/ntcore/src/main/java/edu/wpi/first/networktables/ValueListenerPoller.java deleted file mode 100644 index 167288a878..0000000000 --- a/ntcore/src/main/java/edu/wpi/first/networktables/ValueListenerPoller.java +++ /dev/null @@ -1,101 +0,0 @@ -// 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. - -package edu.wpi.first.networktables; - -/** - * Value change listener. This queues value change events matching the specified mask. Code using - * the listener must periodically call readQueue() to read the events. - */ -public final class ValueListenerPoller implements AutoCloseable { - /** - * Construct a value listener poller. - * - * @param inst Instance - */ - public ValueListenerPoller(NetworkTableInstance inst) { - m_inst = inst; - m_handle = NetworkTablesJNI.createValueListenerPoller(inst.getHandle()); - } - - /** - * Start listening to value changes on a subscriber. This does NOT keep the subscriber active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of ValueListenerFlags values - * @return Listener handle - */ - public int add(Subscriber subscriber, int eventMask) { - return NetworkTablesJNI.addPolledValueListener(m_handle, subscriber.getHandle(), eventMask); - } - - /** - * Start listening to value changes on a subscriber. This does NOT keep the subscriber active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of ValueListenerFlags values - * @return Listener handle - */ - public int add(MultiSubscriber subscriber, int eventMask) { - return NetworkTablesJNI.addPolledValueListener(m_handle, subscriber.getHandle(), eventMask); - } - - /** - * Start listening to value changes on an entry. - * - * @param entry Entry - * @param eventMask Bitmask of ValueListenerFlags values - * @return Listener handle - */ - public int add(NetworkTableEntry entry, int eventMask) { - return NetworkTablesJNI.addPolledValueListener(m_handle, entry.getHandle(), eventMask); - } - - /** - * Remove a listener. - * - * @param listener Listener handle - */ - public void remove(int listener) { - NetworkTablesJNI.removeValueListener(listener); - } - - /** - * Read value notifications. - * - * @return Value notifications since the previous call to readQueue() - */ - public ValueNotification[] readQueue() { - return NetworkTablesJNI.readValueListenerQueue(m_inst, m_handle); - } - - @Override - public synchronized void close() { - if (m_handle != 0) { - NetworkTablesJNI.destroyValueListenerPoller(m_handle); - } - m_handle = 0; - } - - /** - * Determines if the native handle is valid. - * - * @return True if the native handle is valid, false otherwise. - */ - public boolean isValid() { - return m_handle != 0; - } - - /** - * Gets the native handle. - * - * @return Handle - */ - public int getHandle() { - return m_handle; - } - - private final NetworkTableInstance m_inst; - private int m_handle; -} diff --git a/ntcore/src/main/native/cpp/ConnectionList.cpp b/ntcore/src/main/native/cpp/ConnectionList.cpp index 75e1072c26..4781f52ae4 100644 --- a/ntcore/src/main/native/cpp/ConnectionList.cpp +++ b/ntcore/src/main/native/cpp/ConnectionList.cpp @@ -4,191 +4,16 @@ #include "ConnectionList.h" -#include -#include - -#include -#include -#include -#include -#include +#include #include #include -#include "HandleMap.h" +#include "IListenerStorage.h" +#include "ntcore_c.h" #include "ntcore_cpp.h" using namespace nt; -namespace { - -struct PollerData { - static constexpr auto kType = Handle::kConnectionListenerPoller; - - explicit PollerData(NT_ConnectionListenerPoller handle) : handle{handle} {} - - wpi::SignalObject handle; - std::vector queue; -}; - -struct ListenerData { - static constexpr auto kType = Handle::kConnectionListener; - - ListenerData(NT_ConnectionListener handle, PollerData* poller) - : handle{handle}, poller{poller} {} - - wpi::SignalObject handle; - PollerData* poller; -}; - -struct DataLoggerData { - static constexpr auto kType = Handle::kConnectionDataLogger; - - DataLoggerData(NT_ConnectionDataLogger handle, wpi::log::DataLog& log, - std::string_view name, int64_t time) - : handle{handle}, - entry{log, name, "{\"schema\":\"NTConnectionInfo\",\"source\":\"NT\"}", - "json", time} {} - - NT_ConnectionDataLogger handle; - wpi::log::StringLogEntry entry; -}; - -class ListenerThread final : public wpi::SafeThreadEvent { - public: - explicit ListenerThread(NT_ConnectionListenerPoller poller) - : m_poller{poller} {} - - void Main() final; - - NT_ConnectionListenerPoller m_poller; - wpi::DenseMap> - m_callbacks; - wpi::Event m_waitQueueWakeup; - wpi::Event m_waitQueueWaiter; -}; - -class CLImpl { - public: - explicit CLImpl(int inst) : m_inst{inst} {} - - int m_inst; - - // shared with user (must be atomic or mutex-protected) - std::atomic_bool m_connected{false}; - wpi::UidVector, 8> m_connections; - - HandleMap m_pollers; - HandleMap m_listeners; - HandleMap m_dataloggers; - - wpi::SafeThreadOwner m_listenerThread; - - NT_ConnectionListener AddListener( - std::function callback, - bool immediateNotify); - PollerData* CreateListenerPoller() { return m_pollers.Add(m_inst); } - void DestroyListenerPoller(NT_ConnectionListenerPoller pollerHandle); - NT_ConnectionListener AddPolledListener( - NT_ConnectionListenerPoller pollerHandle, bool immediateNotify); - void RemoveListener(NT_ConnectionListener listenerHandle); -}; - -} // namespace - -void ListenerThread::Main() { - while (m_active) { - WPI_Handle signaledBuf[3]; - auto signaled = wpi::WaitForObjects( - {m_poller, m_stopEvent.GetHandle(), m_waitQueueWakeup.GetHandle()}, - signaledBuf); - if (signaled.empty() || !m_active) { - return; - } - // call all the way back out to the C++ API to ensure valid handle - auto events = nt::ReadConnectionListenerQueue(m_poller); - if (events.empty()) { - continue; - } - std::unique_lock lock{m_mutex}; - for (auto&& event : events) { - auto callbackIt = m_callbacks.find(event.listener); - if (callbackIt != m_callbacks.end()) { - auto callback = callbackIt->second; - lock.unlock(); - callback(event); - lock.lock(); - } - } - if (std::find(signaled.begin(), signaled.end(), - m_waitQueueWakeup.GetHandle()) != signaled.end()) { - m_waitQueueWaiter.Set(); - } - } -} - -NT_ConnectionListener CLImpl::AddListener( - std::function callback, - bool immediateNotify) { - if (!m_listenerThread) { - m_listenerThread.Start(CreateListenerPoller()->handle); - } - if (auto thr = m_listenerThread.GetThread()) { - auto listener = AddPolledListener(thr->m_poller, immediateNotify); - if (listener) { - thr->m_callbacks.try_emplace(listener, std::move(callback)); - } - return listener; - } else { - return {}; - } -} - -void CLImpl::DestroyListenerPoller(NT_ConnectionListenerPoller pollerHandle) { - if (auto poller = m_pollers.Remove(pollerHandle)) { - // ensure all listeners that use this poller are removed - wpi::SmallVector toRemove; - for (auto&& listener : m_listeners) { - if (listener->poller == poller.get()) { - toRemove.emplace_back(listener->handle); - } - } - for (auto handle : toRemove) { - RemoveListener(handle); - } - } -} - -NT_ConnectionListener CLImpl::AddPolledListener( - NT_ConnectionListenerPoller pollerHandle, bool immediateNotify) { - auto poller = m_pollers.Get(pollerHandle); - if (!poller) { - return {}; - } - - auto listener = m_listeners.Add(m_inst, poller); - if (immediateNotify && !m_connections.empty()) { - for (auto&& conn : m_connections) { - listener->poller->queue.emplace_back(listener->handle.GetHandle(), true, - *conn); - } - listener->poller->handle.Set(); - listener->handle.Set(); - } - return listener->handle; -} - -void CLImpl::RemoveListener(NT_ConnectionListener listenerHandle) { - if (auto listener = m_listeners.Remove(listenerHandle)) { - if (auto thr = m_listenerThread.GetThread()) { - if (thr->m_poller == listener->poller->handle) { - thr->m_callbacks.erase(listenerHandle); - } - } - } -} - static std::string ConnInfoToJson(bool connected, const ConnectionInfo& info) { std::string str; wpi::raw_string_ostream os{str}; @@ -207,50 +32,35 @@ static std::string ConnInfoToJson(bool connected, const ConnectionInfo& info) { return str; } -class ConnectionList::Impl : public CLImpl { - public: - explicit Impl(int inst) : CLImpl{inst} {} -}; - -ConnectionList::ConnectionList(int inst) - : m_impl{std::make_unique(inst)} {} +ConnectionList::ConnectionList(int inst, IListenerStorage& listenerStorage) + : m_inst{inst}, m_listenerStorage{listenerStorage} {} ConnectionList::~ConnectionList() = default; int ConnectionList::AddConnection(const ConnectionInfo& info) { std::scoped_lock lock{m_mutex}; - m_impl->m_connected = true; - for (auto&& listener : m_impl->m_listeners) { - listener->poller->queue.emplace_back(listener->handle.GetHandle(), true, - info); - listener->poller->handle.Set(); - listener->handle.Set(); - } - if (!m_impl->m_dataloggers.empty()) { + m_connected = true; + m_listenerStorage.Notify({}, NT_EVENT_CONNECTED, &info); + if (!m_dataloggers.empty()) { auto now = Now(); - for (auto&& datalogger : m_impl->m_dataloggers) { + for (auto&& datalogger : m_dataloggers) { datalogger->entry.Append(ConnInfoToJson(true, info), now); } } - return m_impl->m_connections.emplace_back(info); + return m_connections.emplace_back(info); } void ConnectionList::RemoveConnection(int handle) { std::scoped_lock lock{m_mutex}; - auto val = m_impl->m_connections.erase(handle); - if (m_impl->m_connections.empty()) { - m_impl->m_connected = false; + auto val = m_connections.erase(handle); + if (m_connections.empty()) { + m_connected = false; } if (val) { - for (auto&& listener : m_impl->m_listeners) { - listener->poller->queue.emplace_back(listener->handle.GetHandle(), false, - *val); - listener->poller->handle.Set(); - listener->handle.Set(); - } - if (!m_impl->m_dataloggers.empty()) { + m_listenerStorage.Notify({}, NT_EVENT_DISCONNECTED, &(*val)); + if (!m_dataloggers.empty()) { auto now = Now(); - for (auto&& datalogger : m_impl->m_dataloggers) { + for (auto&& datalogger : m_dataloggers) { datalogger->entry.Append(ConnInfoToJson(false, *val), now); } } @@ -259,92 +69,50 @@ void ConnectionList::RemoveConnection(int handle) { void ConnectionList::ClearConnections() { std::scoped_lock lock{m_mutex}; - m_impl->m_connected = false; - for (auto&& conn : m_impl->m_connections) { - for (auto&& listener : m_impl->m_listeners) { - listener->poller->queue.emplace_back(listener->handle.GetHandle(), false, - *conn); - listener->poller->handle.Set(); - listener->handle.Set(); - } + m_connected = false; + for (auto&& conn : m_connections) { + m_listenerStorage.Notify({}, NT_EVENT_DISCONNECTED, &(*conn)); } - m_impl->m_connections.clear(); + m_connections.clear(); } std::vector ConnectionList::GetConnections() const { std::scoped_lock lock{m_mutex}; std::vector info; - info.reserve(m_impl->m_connections.size()); - for (auto&& conn : m_impl->m_connections) { + info.reserve(m_connections.size()); + for (auto&& conn : m_connections) { info.emplace_back(*conn); } return info; } bool ConnectionList::IsConnected() const { - return m_impl->m_connected; + return m_connected; } -NT_ConnectionListener ConnectionList::AddListener( - bool immediateNotify, - std::function callback) { +void ConnectionList::AddListener(NT_Listener listener, unsigned int eventMask) { std::scoped_lock lock{m_mutex}; - return m_impl->AddListener(std::move(callback), immediateNotify); -} - -bool ConnectionList::WaitForListenerQueue(double timeout) { - std::scoped_lock lock{m_mutex}; - WPI_EventHandle h; - if (auto thr = m_impl->m_listenerThread.GetThread()) { - h = thr->m_waitQueueWaiter.GetHandle(); - thr->m_waitQueueWakeup.Set(); - } else { - return false; + eventMask &= (NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE); + m_listenerStorage.Activate(listener, eventMask); + if ((eventMask & (NT_EVENT_CONNECTED | NT_EVENT_IMMEDIATE)) == + (NT_EVENT_CONNECTED | NT_EVENT_IMMEDIATE) && + !m_connections.empty()) { + wpi::SmallVector infos; + infos.reserve(m_connections.size()); + for (auto&& conn : m_connections) { + infos.emplace_back(&(*conn)); + } + m_listenerStorage.Notify({&listener, 1}, + NT_EVENT_CONNECTED | NT_EVENT_IMMEDIATE, infos); } - bool timedOut; - return wpi::WaitForObject(h, timeout, &timedOut); -} - -NT_ConnectionListenerPoller ConnectionList::CreateListenerPoller() { - std::scoped_lock lock{m_mutex}; - return m_impl->CreateListenerPoller()->handle; -} - -void ConnectionList::DestroyListenerPoller( - NT_ConnectionListenerPoller pollerHandle) { - std::scoped_lock lock{m_mutex}; - m_impl->DestroyListenerPoller(pollerHandle); -} - -NT_ConnectionListener ConnectionList::AddPolledListener( - NT_ConnectionListenerPoller pollerHandle, bool immediateNotify) { - std::scoped_lock lock{m_mutex}; - return m_impl->AddPolledListener(pollerHandle, immediateNotify); -} - -std::vector ConnectionList::ReadListenerQueue( - NT_ConnectionListenerPoller pollerHandle) { - std::scoped_lock lock{m_mutex}; - if (auto poller = m_impl->m_pollers.Get(pollerHandle)) { - std::vector rv; - rv.swap(poller->queue); - return rv; - } else { - return {}; - } -} - -void ConnectionList::RemoveListener(NT_ConnectionListener listenerHandle) { - std::scoped_lock lock{m_mutex}; - m_impl->RemoveListener(listenerHandle); } NT_ConnectionDataLogger ConnectionList::StartDataLog(wpi::log::DataLog& log, std::string_view name) { std::scoped_lock lock{m_mutex}; auto now = Now(); - auto datalogger = m_impl->m_dataloggers.Add(m_impl->m_inst, log, name, now); - for (auto&& conn : m_impl->m_connections) { + auto datalogger = m_dataloggers.Add(m_inst, log, name, now); + for (auto&& conn : m_connections) { datalogger->entry.Append(ConnInfoToJson(true, *conn), now); } return datalogger->handle; @@ -352,7 +120,7 @@ NT_ConnectionDataLogger ConnectionList::StartDataLog(wpi::log::DataLog& log, void ConnectionList::StopDataLog(NT_ConnectionDataLogger logger) { std::scoped_lock lock{m_mutex}; - if (auto datalogger = m_impl->m_dataloggers.Remove(logger)) { + if (auto datalogger = m_dataloggers.Remove(logger)) { datalogger->entry.Finish(Now()); } } diff --git a/ntcore/src/main/native/cpp/ConnectionList.h b/ntcore/src/main/native/cpp/ConnectionList.h index b1602690cc..c46266d5c1 100644 --- a/ntcore/src/main/native/cpp/ConnectionList.h +++ b/ntcore/src/main/native/cpp/ConnectionList.h @@ -4,21 +4,31 @@ #pragma once +#include + +#include #include #include +#include #include #include +#include +#include #include +#include "Handle.h" +#include "HandleMap.h" #include "IConnectionList.h" #include "ntcore_cpp.h" namespace nt { +class IListenerStorage; + class ConnectionList final : public IConnectionList { public: - explicit ConnectionList(int inst); + ConnectionList(int inst, IListenerStorage& listenerStorage); ~ConnectionList() final; // IConnectionList interface @@ -30,28 +40,35 @@ class ConnectionList final : public IConnectionList { std::vector GetConnections() const final; bool IsConnected() const final; - NT_ConnectionListener AddListener( - bool immediateNotify, - std::function callback); - bool WaitForListenerQueue(double timeout); - - NT_ConnectionListenerPoller CreateListenerPoller(); - void DestroyListenerPoller(NT_ConnectionListenerPoller pollerHandle); - NT_ConnectionListener AddPolledListener( - NT_ConnectionListenerPoller pollerHandle, bool immediateNotify); - std::vector ReadListenerQueue( - NT_ConnectionListenerPoller pollerHandle); - void RemoveListener(NT_ConnectionListener listenerHandle); + void AddListener(NT_Listener listener, unsigned int eventMask); NT_ConnectionDataLogger StartDataLog(wpi::log::DataLog& log, std::string_view name); void StopDataLog(NT_ConnectionDataLogger logger); private: - class Impl; - std::unique_ptr m_impl; - + int m_inst; + IListenerStorage& m_listenerStorage; mutable wpi::mutex m_mutex; + + // shared with user (must be atomic or mutex-protected) + std::atomic_bool m_connected{false}; + wpi::UidVector, 8> m_connections; + + struct DataLoggerData { + static constexpr auto kType = Handle::kConnectionDataLogger; + + DataLoggerData(NT_ConnectionDataLogger handle, wpi::log::DataLog& log, + std::string_view name, int64_t time) + : handle{handle}, + entry{log, name, + "{\"schema\":\"NTConnectionInfo\",\"source\":\"NT\"}", "json", + time} {} + + NT_ConnectionDataLogger handle; + wpi::log::StringLogEntry entry; + }; + HandleMap m_dataloggers; }; } // namespace nt diff --git a/ntcore/src/main/native/cpp/Handle.h b/ntcore/src/main/native/cpp/Handle.h index ccd6f613b8..0eb9041735 100644 --- a/ntcore/src/main/native/cpp/Handle.h +++ b/ntcore/src/main/native/cpp/Handle.h @@ -18,24 +18,16 @@ namespace nt { class Handle { public: enum Type { - kConnectionListener = wpi::kHandleTypeNTBase, - kConnectionListenerPoller, + kListener = wpi::kHandleTypeNTBase, + kListenerPoller, kEntry, - kEntryListener, - kEntryListenerPoller, kInstance, - kLogger, - kLoggerPoller, kDataLogger, kConnectionDataLogger, kMultiSubscriber, kTopic, - kTopicListener, - kTopicListenerPoller, kSubscriber, kPublisher, - kValueListener, - kValueListenerPoller, kTypeMax }; static_assert(kTypeMax <= wpi::kHandleTypeHALBase); diff --git a/ntcore/src/main/native/cpp/IListenerStorage.h b/ntcore/src/main/native/cpp/IListenerStorage.h new file mode 100644 index 0000000000..71018206be --- /dev/null +++ b/ntcore/src/main/native/cpp/IListenerStorage.h @@ -0,0 +1,49 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "ntcore_cpp.h" + +namespace nt { + +class IListenerStorage { + public: + // Return false if event should not be issued (final check). + // This is called only during Notify() processing. + using FinishEventFunc = std::function; + + virtual ~IListenerStorage() = default; + + virtual void Activate(NT_Listener listener, unsigned int mask, + FinishEventFunc finishEvent = {}) = 0; + + // If handles is not empty, notifies ONLY those listeners + virtual void Notify(std::span handles, unsigned int flags, + std::span infos) = 0; + virtual void Notify(std::span handles, unsigned int flags, + std::span infos) = 0; + virtual void Notify(std::span handles, unsigned int flags, + NT_Topic topic, NT_Handle subentry, + const Value& value) = 0; + virtual void Notify(unsigned int flags, unsigned int level, + std::string_view filename, unsigned int line, + std::string_view message) = 0; + + void Notify(std::span handles, unsigned int flags, + const ConnectionInfo* info) { + Notify(handles, flags, {&info, 1}); + } + void Notify(std::span handles, unsigned int flags, + const TopicInfo& info) { + Notify(handles, flags, {&info, 1}); + } +}; + +} // namespace nt diff --git a/ntcore/src/main/native/cpp/InstanceImpl.cpp b/ntcore/src/main/native/cpp/InstanceImpl.cpp index c773a1dc15..bbeff6ff77 100644 --- a/ntcore/src/main/native/cpp/InstanceImpl.cpp +++ b/ntcore/src/main/native/cpp/InstanceImpl.cpp @@ -13,11 +13,12 @@ wpi::mutex InstanceImpl::s_mutex; using namespace std::placeholders; InstanceImpl::InstanceImpl(int inst) - : logger_impl(inst), - logger( - std::bind(&LoggerImpl::Log, &logger_impl, _1, _2, _3, _4)), // NOLINT - connectionList(inst), - localStorage(inst, logger), + : listenerStorage{inst}, + logger_impl{listenerStorage}, + logger{ + std::bind(&LoggerImpl::Log, &logger_impl, _1, _2, _3, _4)}, // NOLINT + connectionList{inst, listenerStorage}, + localStorage{inst, listenerStorage, logger}, m_inst{inst} { logger.set_min_level(logger_impl.GetMinLevel()); } diff --git a/ntcore/src/main/native/cpp/InstanceImpl.h b/ntcore/src/main/native/cpp/InstanceImpl.h index 7186146c9a..4b916690a8 100644 --- a/ntcore/src/main/native/cpp/InstanceImpl.h +++ b/ntcore/src/main/native/cpp/InstanceImpl.h @@ -15,6 +15,7 @@ #include "ConnectionList.h" #include "Handle.h" +#include "ListenerStorage.h" #include "LocalStorage.h" #include "Log.h" #include "LoggerImpl.h" @@ -55,6 +56,7 @@ class InstanceImpl { std::shared_ptr GetServer(); std::shared_ptr GetClient(); + ListenerStorage listenerStorage; LoggerImpl logger_impl; wpi::Logger logger; ConnectionList connectionList; diff --git a/ntcore/src/main/native/cpp/ListenerStorage.cpp b/ntcore/src/main/native/cpp/ListenerStorage.cpp new file mode 100644 index 0000000000..b19e871ba5 --- /dev/null +++ b/ntcore/src/main/native/cpp/ListenerStorage.cpp @@ -0,0 +1,351 @@ +// 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 "ListenerStorage.h" + +#include + +#include +#include + +#include "ntcore_c.h" + +using namespace nt; + +class ListenerStorage::Thread final : public wpi::SafeThreadEvent { + public: + explicit Thread(NT_ListenerPoller poller) : m_poller{poller} {} + + void Main() final; + + NT_ListenerPoller m_poller; + wpi::DenseMap m_callbacks; + wpi::Event m_waitQueueWakeup; + wpi::Event m_waitQueueWaiter; +}; + +void ListenerStorage::Thread::Main() { + while (m_active) { + WPI_Handle signaledBuf[3]; + auto signaled = wpi::WaitForObjects( + {m_poller, m_stopEvent.GetHandle(), m_waitQueueWakeup.GetHandle()}, + signaledBuf); + if (signaled.empty() || !m_active) { + return; + } + // call all the way back out to the C++ API to ensure valid handle + auto events = nt::ReadListenerQueue(m_poller); + if (events.empty()) { + continue; + } + std::unique_lock lock{m_mutex}; + for (auto&& event : events) { + auto callbackIt = m_callbacks.find(event.listener); + if (callbackIt != m_callbacks.end()) { + auto callback = callbackIt->second; + lock.unlock(); + callback(event); + lock.lock(); + } + } + if (std::find(signaled.begin(), signaled.end(), + m_waitQueueWakeup.GetHandle()) != signaled.end()) { + m_waitQueueWaiter.Set(); + } + } +} + +ListenerStorage::ListenerStorage(int inst) : m_inst{inst} {} + +ListenerStorage::~ListenerStorage() = default; + +void ListenerStorage::Activate(NT_Listener listenerHandle, unsigned int mask, + FinishEventFunc finishEvent) { + std::scoped_lock lock{m_mutex}; + if (auto listener = m_listeners.Get(listenerHandle)) { + listener->sources.emplace_back(std::move(finishEvent), mask); + unsigned int deltaMask = mask & (~listener->eventMask); + listener->eventMask |= mask; + + if ((deltaMask & NT_EVENT_CONNECTION) != 0) { + m_connListeners.Add(listener); + } + if ((deltaMask & NT_EVENT_TOPIC) != 0) { + m_topicListeners.Add(listener); + } + if ((deltaMask & NT_EVENT_VALUE_ALL) != 0) { + m_valueListeners.Add(listener); + } + // detect the higher log bits too; see LoggerImpl + if ((deltaMask & NT_EVENT_LOGMESSAGE) != 0 || + (deltaMask & 0x1ff0000) != 0) { + m_logListeners.Add(listener); + } + } +} + +void ListenerStorage::Notify(std::span handles, + unsigned int flags, + std::span infos) { + if (flags == 0) { + return; + } + std::scoped_lock lock{m_mutex}; + + auto doSignal = [&](ListenerData& listener) { + if ((flags & listener.eventMask) != 0) { + for (auto&& [finishEvent, mask] : listener.sources) { + if ((flags & mask) != 0) { + for (auto&& info : infos) { + listener.poller->queue.emplace_back(listener.handle, flags, *info); + // finishEvent is never set (see ConnectionList) + } + } + } + listener.handle.Set(); + listener.poller->handle.Set(); + } + }; + + if (!handles.empty()) { + for (auto handle : handles) { + if (auto listener = m_listeners.Get(handle)) { + doSignal(*listener); + } + } + } else { + for (auto&& listener : m_connListeners) { + doSignal(*listener); + } + } +} + +void ListenerStorage::Notify(std::span handles, + unsigned int flags, + std::span infos) { + if (flags == 0) { + return; + } + std::scoped_lock lock{m_mutex}; + + auto doSignal = [&](ListenerData& listener) { + if ((flags & listener.eventMask) != 0) { + int count = 0; + for (auto&& [finishEvent, mask] : listener.sources) { + if ((flags & mask) != 0) { + for (auto&& info : infos) { + listener.poller->queue.emplace_back(listener.handle, flags, info); + if (finishEvent && + !finishEvent(mask, &listener.poller->queue.back())) { + listener.poller->queue.pop_back(); + } else { + ++count; + } + } + } + } + if (count > 0) { + listener.handle.Set(); + listener.poller->handle.Set(); + } + } + }; + + if (!handles.empty()) { + for (auto handle : handles) { + if (auto listener = m_listeners.Get(handle)) { + doSignal(*listener); + } + } + } else { + for (auto&& listener : m_topicListeners) { + doSignal(*listener); + } + } +} + +void ListenerStorage::Notify(std::span handles, + unsigned int flags, NT_Topic topic, + NT_Handle subentry, const Value& value) { + if (flags == 0) { + return; + } + std::scoped_lock lock{m_mutex}; + + auto doSignal = [&](ListenerData& listener) { + if ((flags & listener.eventMask) != 0) { + int count = 0; + for (auto&& [finishEvent, mask] : listener.sources) { + if ((flags & mask) != 0) { + listener.poller->queue.emplace_back(listener.handle, flags, topic, + subentry, value); + if (finishEvent && + !finishEvent(mask, &listener.poller->queue.back())) { + listener.poller->queue.pop_back(); + } else { + ++count; + } + } + } + if (count > 0) { + listener.handle.Set(); + listener.poller->handle.Set(); + } + } + }; + + if (!handles.empty()) { + for (auto handle : handles) { + if (auto listener = m_listeners.Get(handle)) { + doSignal(*listener); + } + } + } else { + for (auto&& listener : m_valueListeners) { + doSignal(*listener); + } + } +} + +void ListenerStorage::Notify(unsigned int flags, unsigned int level, + std::string_view filename, unsigned int line, + std::string_view message) { + if (flags == 0) { + return; + } + std::scoped_lock lock{m_mutex}; + for (auto&& listener : m_logListeners) { + if ((flags & listener->eventMask) != 0) { + int count = 0; + for (auto&& [finishEvent, mask] : listener->sources) { + if ((flags & mask) != 0) { + listener->poller->queue.emplace_back(listener->handle, flags, level, + filename, line, message); + if (finishEvent && + !finishEvent(mask, &listener->poller->queue.back())) { + listener->poller->queue.pop_back(); + } else { + ++count; + } + } + } + if (count > 0) { + listener->handle.Set(); + listener->poller->handle.Set(); + } + } + } +} + +NT_Listener ListenerStorage::AddListener(ListenerCallback callback) { + std::scoped_lock lock{m_mutex}; + if (!m_thread) { + m_thread.Start(m_pollers.Add(m_inst)->handle); + } + if (auto thr = m_thread.GetThread()) { + auto listener = DoAddListener(thr->m_poller); + if (listener) { + thr->m_callbacks.try_emplace(listener, std::move(callback)); + } + return listener; + } else { + return {}; + } +} + +NT_Listener ListenerStorage::AddListener(NT_ListenerPoller pollerHandle) { + std::scoped_lock lock{m_mutex}; + return DoAddListener(pollerHandle); +} + +NT_Listener ListenerStorage::DoAddListener(NT_ListenerPoller pollerHandle) { + if (auto poller = m_pollers.Get(pollerHandle)) { + return m_listeners.Add(m_inst, poller)->handle; + } else { + return {}; + } +} + +NT_ListenerPoller ListenerStorage::CreateListenerPoller() { + std::scoped_lock lock{m_mutex}; + return m_pollers.Add(m_inst)->handle; +} + +std::vector> +ListenerStorage::DestroyListenerPoller(NT_ListenerPoller pollerHandle) { + std::scoped_lock lock{m_mutex}; + if (auto poller = m_pollers.Remove(pollerHandle)) { + // ensure all listeners that use this poller are removed + wpi::SmallVector toRemove; + for (auto&& listener : m_listeners) { + if (listener->poller == poller.get()) { + toRemove.emplace_back(listener->handle); + } + } + return DoRemoveListeners(toRemove); + } else { + return {}; + } +} + +std::vector ListenerStorage::ReadListenerQueue( + NT_ListenerPoller pollerHandle) { + std::scoped_lock lock{m_mutex}; + if (auto poller = m_pollers.Get(pollerHandle)) { + std::vector rv; + rv.swap(poller->queue); + return rv; + } else { + return {}; + } +} + +std::vector> +ListenerStorage::RemoveListener(NT_Listener listenerHandle) { + std::scoped_lock lock{m_mutex}; + return DoRemoveListeners({&listenerHandle, 1}); +} + +bool ListenerStorage::WaitForListenerQueue(double timeout) { + std::scoped_lock lock{m_mutex}; + WPI_EventHandle h; + if (auto thr = m_thread.GetThread()) { + h = thr->m_waitQueueWaiter.GetHandle(); + thr->m_waitQueueWakeup.Set(); + } else { + return false; + } + bool timedOut; + return wpi::WaitForObject(h, timeout, &timedOut); +} + +std::vector> +ListenerStorage::DoRemoveListeners(std::span handles) { + std::vector> rv; + auto thr = m_thread.GetThread(); + for (auto handle : handles) { + if (auto listener = m_listeners.Remove(handle)) { + rv.emplace_back(handle, listener->eventMask); + if (thr) { + if (thr->m_poller == listener->poller->handle) { + thr->m_callbacks.erase(handle); + } + } + if ((listener->eventMask & NT_EVENT_CONNECTION) != 0) { + m_connListeners.Remove(listener.get()); + } + if ((listener->eventMask & NT_EVENT_TOPIC) != 0) { + m_topicListeners.Remove(listener.get()); + } + if ((listener->eventMask & NT_EVENT_VALUE_ALL) != 0) { + m_valueListeners.Remove(listener.get()); + } + if ((listener->eventMask & NT_EVENT_LOGMESSAGE) != 0 || + (listener->eventMask & 0x1ff0000) != 0) { + m_logListeners.Remove(listener.get()); + } + } + } + return rv; +} diff --git a/ntcore/src/main/native/cpp/ListenerStorage.h b/ntcore/src/main/native/cpp/ListenerStorage.h new file mode 100644 index 0000000000..654d7fd5fe --- /dev/null +++ b/ntcore/src/main/native/cpp/ListenerStorage.h @@ -0,0 +1,111 @@ +// 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. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "Handle.h" +#include "HandleMap.h" +#include "IListenerStorage.h" +#include "ntcore_cpp.h" + +namespace nt { + +class ListenerStorage final : public IListenerStorage { + public: + explicit ListenerStorage(int inst); + ListenerStorage(const ListenerStorage&) = delete; + ListenerStorage& operator=(const ListenerStorage&) = delete; + ~ListenerStorage() final; + + // IListenerStorage interface + void Activate(NT_Listener listenerHandle, unsigned int mask, + FinishEventFunc finishEvent = {}) final; + void Notify(std::span handles, unsigned int flags, + std::span infos) final; + void Notify(std::span handles, unsigned int flags, + std::span infos) final; + void Notify(std::span handles, unsigned int flags, + NT_Topic topic, NT_Handle subentry, const Value& value) final; + void Notify(unsigned int flags, unsigned int level, std::string_view filename, + unsigned int line, std::string_view message) final; + + // user-facing functions + NT_Listener AddListener(ListenerCallback callback); + NT_Listener AddListener(NT_ListenerPoller pollerHandle); + NT_ListenerPoller CreateListenerPoller(); + + // returns listener handle and mask for each listener that was destroyed + [[nodiscard]] std::vector> + DestroyListenerPoller(NT_ListenerPoller pollerHandle); + + std::vector ReadListenerQueue(NT_ListenerPoller pollerHandle); + + // returns listener handle and mask for each listener that was destroyed + [[nodiscard]] std::vector> + RemoveListener(NT_Listener listenerHandle); + + bool WaitForListenerQueue(double timeout); + + private: + // these assume the mutex is already held + NT_Listener DoAddListener(NT_ListenerPoller pollerHandle); + std::vector> DoRemoveListeners( + std::span handles); + + int m_inst; + mutable wpi::mutex m_mutex; + + struct PollerData { + static constexpr auto kType = Handle::kListenerPoller; + + explicit PollerData(NT_ListenerPoller handle) : handle{handle} {} + + wpi::SignalObject handle; + std::vector queue; + }; + HandleMap m_pollers; + + struct ListenerData { + static constexpr auto kType = Handle::kListener; + + ListenerData(NT_Listener handle, PollerData* poller) + : handle{handle}, poller{poller} {} + + wpi::SignalObject handle; + PollerData* poller; + wpi::SmallVector, 2> sources; + unsigned int eventMask{0}; + }; + HandleMap m_listeners; + + // Utility wrapper for making a set-like vector + template + class VectorSet : public std::vector { + public: + void Add(T value) { this->push_back(value); } + void Remove(T value) { std::erase(*this, value); } + }; + + VectorSet m_connListeners; + VectorSet m_topicListeners; + VectorSet m_valueListeners; + VectorSet m_logListeners; + + class Thread; + wpi::SafeThreadOwner m_thread; +}; + +} // namespace nt diff --git a/ntcore/src/main/native/cpp/LocalStorage.cpp b/ntcore/src/main/native/cpp/LocalStorage.cpp index 0ca15bbcb5..9f015f8de7 100644 --- a/ntcore/src/main/native/cpp/LocalStorage.cpp +++ b/ntcore/src/main/native/cpp/LocalStorage.cpp @@ -7,8 +7,6 @@ #include #include -#include -#include #include #include #include @@ -18,6 +16,7 @@ #include "Handle.h" #include "HandleMap.h" +#include "IListenerStorage.h" #include "Log.h" #include "PubSubOptions.h" #include "Types_internal.h" @@ -33,19 +32,13 @@ template class VectorSet : public std::vector { public: void Add(T value) { this->push_back(value); } - void Remove(T value) { - this->erase(std::remove(this->begin(), this->end(), value), this->end()); - } + void Remove(T value) { std::erase(*this, value); } }; struct EntryData; struct PublisherData; struct SubscriberData; struct MultiSubscriberData; -struct TopicListenerPollerData; -struct TopicListenerData; -struct ValueListenerPollerData; -struct ValueListenerData; struct DataLoggerEntry { DataLoggerEntry(wpi::log::DataLog& log, int entry, NT_DataLogger logger) @@ -94,7 +87,7 @@ struct TopicData { VectorSet localSubscribers; VectorSet multiSubscribers; VectorSet entries; - VectorSet listeners; + VectorSet listeners; }; struct PubSubConfig : public PubSubOptions { @@ -147,7 +140,7 @@ struct SubscriberData { wpi::circular_buffer pollStorage; // value listeners - VectorSet valueListeners; + VectorSet valueListeners; }; struct EntryData { @@ -185,86 +178,28 @@ struct MultiSubscriberData { PubSubOptions options; // value listeners - VectorSet valueListeners; + VectorSet valueListeners; }; -struct TopicListenerPollerData { - static constexpr auto kType = Handle::kTopicListenerPoller; - - explicit TopicListenerPollerData(NT_TopicListenerPoller handle) - : handle{handle} {} - - wpi::SignalObject handle; - std::vector queue; -}; - -struct TopicListenerData { - static constexpr auto kType = Handle::kTopicListener; - - TopicListenerData(NT_TopicListener handle, TopicListenerPollerData* poller, - SubscriberData* subscriber, TopicData* topic, - unsigned int eventMask) +struct ListenerData { + ListenerData(NT_Listener handle, SubscriberData* subscriber, + unsigned int eventMask, bool subscriberOwned) : handle{handle}, - poller{poller}, - topic{topic}, - eventMask{eventMask & ~NT_TOPIC_NOTIFY_IMMEDIATE} {} - TopicListenerData(NT_TopicListener handle, TopicListenerPollerData* poller, - MultiSubscriberData* multiSubscriber, - std::span prefixes, - unsigned int eventMask) - : handle{handle}, - poller{poller}, - multiSubscriber{multiSubscriber}, - prefixes{prefixes.begin(), prefixes.end()}, - eventMask{eventMask & ~NT_TOPIC_NOTIFY_IMMEDIATE} {} - - wpi::SignalObject handle; - TopicListenerPollerData* poller; - SubscriberData* subscriber{nullptr}; - MultiSubscriberData* multiSubscriber{nullptr}; - TopicData* topic{nullptr}; - std::vector prefixes; - unsigned int eventMask; - bool subscriberOwned{false}; -}; - -struct ValueListenerPollerData { - static constexpr auto kType = Handle::kValueListenerPoller; - - explicit ValueListenerPollerData(NT_ValueListenerPoller handle) - : handle{handle} {} - - wpi::SignalObject handle; - std::vector queue; -}; - -struct ValueListenerData { - static constexpr auto kType = Handle::kValueListener; - - ValueListenerData(NT_ValueListener handle, ValueListenerPollerData* poller, - SubscriberData* subscriber, NT_Handle subentryHandle, - unsigned int eventMask) - : handle{handle}, - poller{poller}, + eventMask{eventMask}, subscriber{subscriber}, - subentryHandle{subentryHandle}, - eventMask{eventMask & ~NT_VALUE_NOTIFY_IMMEDIATE} {} - - ValueListenerData(NT_ValueListener handle, ValueListenerPollerData* poller, - MultiSubscriberData* subscriber, NT_Handle subentryHandle, - unsigned int eventMask) + subscriberOwned{subscriberOwned} {} + ListenerData(NT_Listener handle, MultiSubscriberData* subscriber, + unsigned int eventMask, bool subscriberOwned) : handle{handle}, - poller{poller}, + eventMask{eventMask}, multiSubscriber{subscriber}, - subentryHandle{subentryHandle}, - eventMask{eventMask & ~NT_VALUE_NOTIFY_IMMEDIATE} {} + subscriberOwned{subscriberOwned} {} - wpi::SignalObject handle; - ValueListenerPollerData* poller; + NT_Listener handle; + unsigned int eventMask; SubscriberData* subscriber{nullptr}; MultiSubscriberData* multiSubscriber{nullptr}; - NT_Handle subentryHandle; - unsigned int eventMask; + bool subscriberOwned; }; struct DataLoggerData { @@ -287,42 +222,12 @@ struct DataLoggerData { std::string logPrefix; }; -struct TopicListenerThread final : public wpi::SafeThreadEvent { - public: - explicit TopicListenerThread(TopicListenerPollerData* pollerData) - : m_pollerData{pollerData}, m_poller{pollerData->handle} {} - - void Main() final; - - TopicListenerPollerData* m_pollerData; - NT_TopicListenerPoller m_poller; - wpi::DenseMap> - m_callbacks; - wpi::Event m_waitQueueWakeup; - wpi::Event m_waitQueueWaiter; -}; - -struct ValueListenerThread final : public wpi::SafeThreadEvent { - public: - explicit ValueListenerThread(ValueListenerPollerData* pollerData) - : m_pollerData{pollerData}, m_poller{pollerData->handle} {} - - void Main() final; - - ValueListenerPollerData* m_pollerData; - NT_ValueListenerPoller m_poller; - wpi::DenseMap> - m_callbacks; - wpi::Event m_waitQueueWakeup; - wpi::Event m_waitQueueWaiter; -}; - struct LSImpl { - LSImpl(int inst, wpi::Logger& logger) : m_inst{inst}, m_logger{logger} {} + LSImpl(int inst, IListenerStorage& listenerStorage, wpi::Logger& logger) + : m_inst{inst}, m_listenerStorage{listenerStorage}, m_logger{logger} {} int m_inst; + IListenerStorage& m_listenerStorage; wpi::Logger& m_logger; net::NetworkInterface* m_network{nullptr}; @@ -332,33 +237,24 @@ struct LSImpl { HandleMap m_subscribers; HandleMap m_entries; HandleMap m_multiSubscribers; - HandleMap m_topicListenerPollers; - HandleMap m_topicListeners; - HandleMap m_valueListenerPollers; - HandleMap m_valueListeners; HandleMap m_dataloggers; // name mappings wpi::StringMap m_nameTopics; - // string-based listeners - VectorSet m_topicPrefixListeners; + // listeners + wpi::DenseMap> m_listeners; - // callback listener threads - wpi::SafeThreadOwner m_topicListenerThread; - wpi::SafeThreadOwner m_valueListenerThread; + // string-based listeners + VectorSet m_topicPrefixListeners; // topic functions void NotifyTopic(TopicData* topic, unsigned int eventFlags); - void NotifyTopicListener(TopicListenerData* listener, TopicData* topic, - unsigned int eventFlags); void CheckReset(TopicData* topic); bool SetValue(TopicData* topic, const Value& value, unsigned int eventFlags); void NotifyValue(TopicData* topic, unsigned int eventFlags); - void NotifyValueListener(ValueListenerData* listener, TopicData* topic, - unsigned int eventFlags); void SetFlags(TopicData* topic, unsigned int flags); void SetPersistent(TopicData* topic, bool value); @@ -395,53 +291,24 @@ struct LSImpl { std::unique_ptr RemoveMultiSubscriber( NT_MultiSubscriber subHandle); - TopicListenerPollerData* AddTopicListenerPoller() { - return m_topicListenerPollers.Add(m_inst); - } - TopicListenerData* AddTopicListenerImpl(TopicListenerPollerData* poller, - TopicData* topic, - unsigned int eventMask); - TopicListenerData* AddTopicListenerImpl(TopicListenerPollerData* poller, - SubscriberData* topic, - unsigned int eventMask); - TopicListenerData* AddTopicListenerImpl(TopicListenerPollerData* poller, - MultiSubscriberData* topic, - unsigned int eventMask); - TopicListenerData* AddTopicListenerImpl( - TopicListenerPollerData* poller, - std::span prefixes, unsigned int eventMask); - NT_TopicListener AddTopicListener( - std::span prefixes, unsigned int mask, - std::function callback); - NT_TopicListener AddTopicListenerHandle(TopicListenerPollerData* poller, - NT_Handle handle, unsigned int mask); - NT_TopicListener AddTopicListener( - NT_Handle handle, unsigned int mask, - std::function callback); - void DestroyTopicListenerPoller(NT_TopicListenerPoller pollerHandle); - std::unique_ptr RemoveTopicListener( - NT_TopicListener listenerHandle); + void AddListenerImpl(NT_Listener listenerHandle, TopicData* topic, + unsigned int eventMask); + void AddListenerImpl(NT_Listener listenerHandle, SubscriberData* subscriber, + unsigned int eventMask, NT_Handle subentryHandle, + bool subscriberOwned); + void AddListenerImpl(NT_Listener listenerHandle, + MultiSubscriberData* subscriber, unsigned int eventMask, + bool subscriberOwned); + void AddListenerImpl(NT_Listener listenerHandle, + std::span prefixes, + unsigned int eventMask); - ValueListenerPollerData* AddValueListenerPoller() { - return m_valueListenerPollers.Add(m_inst); - } - ValueListenerData* AddValueListenerImpl(ValueListenerPollerData* poller, - SubscriberData* subscriber, - NT_Handle subentryHandle, - unsigned int eventMask); - ValueListenerData* AddValueListenerImpl(ValueListenerPollerData* poller, - MultiSubscriberData* subscriber, - NT_Handle subentryHandle, - unsigned int eventMask); - NT_ValueListener AddValueListenerHandle(ValueListenerPollerData* poller, - NT_Handle subentryHandle, - unsigned int mask); - NT_ValueListener AddValueListener( - NT_Handle subentry, unsigned int mask, - std::function callback); - void DestroyValueListenerPoller(NT_ValueListenerPoller pollerHandle); - std::unique_ptr RemoveValueListener( - NT_ValueListener listenerHandle); + void AddListener(NT_Listener listenerHandle, + std::span prefixes, + unsigned int mask); + void AddListener(NT_Listener listenerHandle, NT_Handle handle, + unsigned int mask); + void RemoveListener(NT_Listener listenerHandle, unsigned int mask); TopicData* GetOrCreateTopic(std::string_view name); TopicData* GetTopic(NT_Handle handle); @@ -534,83 +401,27 @@ void SubscriberData::UpdateActive() { (NT_INTEGER_ARRAY | NT_FLOAT_ARRAY | NT_DOUBLE_ARRAY))); } -void TopicListenerThread::Main() { - while (m_active) { - WPI_Handle signaledBuf[3]; - auto signaled = wpi::WaitForObjects( - {m_poller, m_stopEvent.GetHandle(), m_waitQueueWakeup.GetHandle()}, - signaledBuf); - if (signaled.empty() || !m_active) { - break; - } - // call all the way back out to the C++ API to ensure valid handle - auto events = nt::ReadTopicListenerQueue(m_poller); - if (events.empty()) { - continue; - } - std::unique_lock lock{m_mutex}; - for (auto&& event : events) { - auto callbackIt = m_callbacks.find(event.listener); - if (callbackIt != m_callbacks.end()) { - auto callback = callbackIt->second; - lock.unlock(); - callback(event); - lock.lock(); - } - } - if (std::find(signaled.begin(), signaled.end(), - m_waitQueueWakeup.GetHandle()) != signaled.end()) { - m_waitQueueWaiter.Set(); - } - } -} - -void ValueListenerThread::Main() { - while (m_active) { - WPI_Handle signaledBuf[3]; - auto signaled = wpi::WaitForObjects( - {m_poller, m_stopEvent.GetHandle(), m_waitQueueWakeup.GetHandle()}, - signaledBuf); - if (signaled.empty() || !m_active) { - break; - } - // call all the way back out to the C++ API to ensure valid handle - auto events = nt::ReadValueListenerQueue(m_poller); - if (events.empty()) { - continue; - } - std::unique_lock lock{m_mutex}; - for (auto&& event : events) { - auto callbackIt = m_callbacks.find(event.listener); - if (callbackIt != m_callbacks.end()) { - auto callback = callbackIt->second; - lock.unlock(); - callback(event); - lock.lock(); - } - } - if (std::find(signaled.begin(), signaled.end(), - m_waitQueueWakeup.GetHandle()) != signaled.end()) { - m_waitQueueWaiter.Set(); - } - } -} - void LSImpl::NotifyTopic(TopicData* topic, unsigned int eventFlags) { - for (auto&& listener : topic->listeners) { - NotifyTopicListener(listener, topic, eventFlags); + auto topicInfo = topic->GetTopicInfo(); + if (!topic->listeners.empty()) { + m_listenerStorage.Notify(topic->listeners, eventFlags, topicInfo); } + wpi::SmallVector listeners; for (auto listener : m_topicPrefixListeners) { - for (auto&& prefix : listener->prefixes) { - if (wpi::starts_with(topic->name, prefix)) { - NotifyTopicListener(listener, topic, eventFlags); + if (listener->multiSubscriber) { + for (auto&& prefix : listener->multiSubscriber->prefixes) { + if (wpi::starts_with(topic->name, prefix)) { + listeners.emplace_back(listener->handle); + } } } } + if (!listeners.empty()) { + m_listenerStorage.Notify(listeners, eventFlags, topicInfo); + } - if ((eventFlags & (NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_UNPUBLISH)) != - 0) { + if ((eventFlags & (NT_EVENT_PUBLISH | NT_EVENT_UNPUBLISH)) != 0) { if (!m_dataloggers.empty()) { auto now = Now(); for (auto&& datalogger : m_dataloggers) { @@ -619,13 +430,13 @@ void LSImpl::NotifyTopic(TopicData* topic, unsigned int eventFlags) { [&](const auto& elem) { return elem.logger == datalogger->handle; }); - if ((eventFlags & NT_TOPIC_NOTIFY_PUBLISH) != 0 && + if ((eventFlags & NT_EVENT_PUBLISH) != 0 && it == topic->datalogs.end()) { topic->datalogs.emplace_back(datalogger->log, datalogger->Start(topic, now), datalogger->handle); topic->datalogType = topic->type; - } else if ((eventFlags & NT_TOPIC_NOTIFY_UNPUBLISH) != 0 && + } else if ((eventFlags & NT_EVENT_UNPUBLISH) != 0 && it != topic->datalogs.end()) { it->log->Finish(it->entry, now); topic->datalogType = NT_UNASSIGNED; @@ -634,7 +445,7 @@ void LSImpl::NotifyTopic(TopicData* topic, unsigned int eventFlags) { } } } - } else if ((eventFlags & NT_TOPIC_NOTIFY_PROPERTIES) != 0) { + } else if ((eventFlags & NT_EVENT_PROPERTIES) != 0) { if (!topic->datalogs.empty()) { auto metadata = DataLoggerEntry::MakeMetadata(topic->propertiesStr); for (auto&& datalog : topic->datalogs) { @@ -644,19 +455,6 @@ void LSImpl::NotifyTopic(TopicData* topic, unsigned int eventFlags) { } } -void LSImpl::NotifyTopicListener(TopicListenerData* listener, TopicData* topic, - unsigned int eventFlags) { - // filter by event mask - if ((eventFlags & listener->eventMask) == 0) { - return; - } - - listener->poller->queue.emplace_back(listener->handle, topic->GetTopicInfo(), - eventFlags); - listener->handle.Set(); - listener->poller->handle.Set(); -} - void LSImpl::CheckReset(TopicData* topic) { if (topic->Exists()) { return; @@ -675,7 +473,7 @@ bool LSImpl::SetValue(TopicData* topic, const Value& value, if (topic->type != NT_UNASSIGNED && topic->type != value.type()) { return false; } - bool isNetwork = (eventFlags & NT_VALUE_NOTIFY_LOCAL) == 0; + bool isNetwork = (eventFlags & NT_EVENT_VALUE_REMOTE) != 0; if (!topic->lastValue || topic->lastValueNetwork == isNetwork || value.time() >= topic->lastValue.time()) { // TODO: notify option even if older value @@ -697,36 +495,18 @@ void LSImpl::NotifyValue(TopicData* topic, unsigned int eventFlags) { if (subscriber->active) { subscriber->pollStorage.emplace_back(topic->lastValue); subscriber->handle.Set(); - for (auto&& listener : subscriber->valueListeners) { - NotifyValueListener(listener, topic, eventFlags); - } + m_listenerStorage.Notify(subscriber->valueListeners, eventFlags, + topic->handle, 0, topic->lastValue); } } for (auto&& subscriber : topic->multiSubscribers) { subscriber->handle.Set(); - for (auto&& listener : subscriber->valueListeners) { - NotifyValueListener(listener, topic, eventFlags); - } + m_listenerStorage.Notify(subscriber->valueListeners, eventFlags, + topic->handle, 0, topic->lastValue); } } -void LSImpl::NotifyValueListener(ValueListenerData* listener, TopicData* topic, - unsigned int eventFlags) { - // only notify listener for local events if it wants local events - bool listenLocal = (listener->eventMask & NT_VALUE_NOTIFY_LOCAL) != 0; - bool eventLocal = (eventFlags & NT_VALUE_NOTIFY_LOCAL) != 0; - if (eventLocal && !listenLocal) { - return; - } - - listener->poller->queue.emplace_back(listener->handle, topic->handle, - listener->subentryHandle, - topic->lastValue, eventFlags); - listener->handle.Set(); - listener->poller->handle.Set(); -} - void LSImpl::SetFlags(TopicData* topic, unsigned int flags) { wpi::json update = wpi::json::object(); if ((flags & NT_PERSISTENT) != 0) { @@ -745,7 +525,7 @@ void LSImpl::SetFlags(TopicData* topic, unsigned int flags) { } topic->flags = flags; if (!update.empty()) { - PropertiesUpdated(topic, update, NT_TOPIC_NOTIFY_NONE, true, false); + PropertiesUpdated(topic, update, NT_EVENT_NONE, true, false); } } @@ -760,7 +540,7 @@ void LSImpl::SetPersistent(TopicData* topic, bool value) { topic->properties.erase("persistent"); update["persistent"] = wpi::json(); } - PropertiesUpdated(topic, update, NT_TOPIC_NOTIFY_NONE, true, false); + PropertiesUpdated(topic, update, NT_EVENT_NONE, true, false); } void LSImpl::SetRetained(TopicData* topic, bool value) { @@ -774,7 +554,7 @@ void LSImpl::SetRetained(TopicData* topic, bool value) { topic->properties.erase("retained"); update["retained"] = wpi::json(); } - PropertiesUpdated(topic, update, NT_TOPIC_NOTIFY_NONE, true, false); + PropertiesUpdated(topic, update, NT_EVENT_NONE, true, false); } void LSImpl::SetProperties(TopicData* topic, const wpi::json& update, @@ -790,7 +570,7 @@ void LSImpl::SetProperties(TopicData* topic, const wpi::json& update, topic->properties[change.key()] = change.value(); } } - PropertiesUpdated(topic, update, NT_TOPIC_NOTIFY_NONE, sendNetwork); + PropertiesUpdated(topic, update, NT_EVENT_NONE, sendNetwork); } void LSImpl::PropertiesUpdated(TopicData* topic, const wpi::json& update, @@ -823,7 +603,7 @@ void LSImpl::PropertiesUpdated(TopicData* topic, const wpi::json& update, } topic->propertiesStr = topic->properties.dump(); - NotifyTopic(topic, eventFlags | NT_TOPIC_NOTIFY_PROPERTIES); + NotifyTopic(topic, eventFlags | NT_EVENT_PROPERTIES); // check local flag so we don't echo back received properties changes if (m_network && sendNetwork) { m_network->SetProperties(topic->handle, topic->name, update); @@ -848,7 +628,7 @@ void LSImpl::NetworkAnnounce(TopicData* topic, std::string_view typeStr, return; // ack of our publish; ignore } - unsigned int notify = NT_TOPIC_NOTIFY_NONE; + unsigned int event = NT_EVENT_NONE; // fresh non-local publish; the network publish always sets the type even // if it was locally published, but output a diagnostic for this case bool didExist = topic->Exists(); @@ -866,7 +646,7 @@ void LSImpl::NetworkAnnounce(TopicData* topic, std::string_view typeStr, RefreshPubSubActive(topic); } if (!didExist) { - notify |= NT_TOPIC_NOTIFY_PUBLISH; + event |= NT_EVENT_PUBLISH; } // may be properties update, but need to compare to see if it actually @@ -887,9 +667,9 @@ void LSImpl::NetworkAnnounce(TopicData* topic, std::string_view typeStr, } if (!update.empty()) { topic->properties = properties; - PropertiesUpdated(topic, update, notify, false); - } else if (notify != NT_TOPIC_NOTIFY_NONE) { - NotifyTopic(topic, notify); + PropertiesUpdated(topic, update, event, false); + } else if (event != NT_EVENT_NONE) { + NotifyTopic(topic, event); } } @@ -901,7 +681,7 @@ void LSImpl::RemoveNetworkPublisher(TopicData* topic) { if (didExist && !topic->Exists()) { DEBUG4("Unpublished {}", topic->name); CheckReset(topic); - NotifyTopic(topic, NT_TOPIC_NOTIFY_UNPUBLISH); + NotifyTopic(topic, NT_EVENT_UNPUBLISH); } if (!topic->localPublishers.empty()) { @@ -957,10 +737,9 @@ PublisherData* LSImpl::AddLocalPublisher(TopicData* topic, } if (topic->properties.empty()) { - NotifyTopic(topic, NT_TOPIC_NOTIFY_PUBLISH); + NotifyTopic(topic, NT_EVENT_PUBLISH); } else { - PropertiesUpdated(topic, topic->properties, NT_TOPIC_NOTIFY_PUBLISH, - false); + PropertiesUpdated(topic, topic->properties, NT_EVENT_PUBLISH, false); } } else { // only need to update just this publisher @@ -990,7 +769,7 @@ std::unique_ptr LSImpl::RemoveLocalPublisher( topic->localPublishers.Remove(publisher.get()); if (didExist && !topic->Exists()) { CheckReset(topic); - NotifyTopic(topic, NT_TOPIC_NOTIFY_UNPUBLISH); + NotifyTopic(topic, NT_EVENT_UNPUBLISH); } if (publisher->active && m_network) { @@ -1044,13 +823,9 @@ std::unique_ptr LSImpl::RemoveLocalSubscriber( if (subscriber) { auto topic = subscriber->topic; topic->localSubscribers.Remove(subscriber.get()); - for (auto listener : subscriber->valueListeners) { - listener->subscriber = nullptr; - } - // shouldn't be necessary, but do it anyway - for (auto&& listener : m_topicListeners) { - if (listener->subscriber == subscriber.get()) { - listener->subscriber = nullptr; + for (auto&& listener : m_listeners) { + if (listener.getSecond()->subscriber == subscriber.get()) { + listener.getSecond()->subscriber = nullptr; } } if (m_network) { @@ -1100,13 +875,9 @@ std::unique_ptr LSImpl::RemoveMultiSubscriber( for (auto&& topic : m_topics) { topic->multiSubscribers.Remove(subscriber.get()); } - for (auto listener : subscriber->valueListeners) { - listener->multiSubscriber = nullptr; - } - // shouldn't be necessary, but do it anyway - for (auto&& listener : m_topicListeners) { - if (listener->multiSubscriber == subscriber.get()) { - listener->multiSubscriber = nullptr; + for (auto&& listener : m_listeners) { + if (listener.getSecond()->multiSubscriber == subscriber.get()) { + listener.getSecond()->multiSubscriber = nullptr; } } if (m_network) { @@ -1116,280 +887,184 @@ std::unique_ptr LSImpl::RemoveMultiSubscriber( return subscriber; } -TopicListenerData* LSImpl::AddTopicListenerImpl(TopicListenerPollerData* poller, - TopicData* topic, - unsigned int eventMask) { +void LSImpl::AddListenerImpl(NT_Listener listenerHandle, TopicData* topic, + unsigned int eventMask) { // subscribe to make sure topic updates are received PubSubConfig config; - config.topicsOnly = true; - auto subscriber = AddLocalSubscriber(topic, config); - auto listener = AddTopicListenerImpl(poller, subscriber, eventMask); - listener->subscriberOwned = true; - return listener; + config.topicsOnly = (eventMask & NT_EVENT_VALUE_ALL) == 0; + auto sub = AddLocalSubscriber(topic, config); + AddListenerImpl(listenerHandle, sub, eventMask, sub->handle, true); } -TopicListenerData* LSImpl::AddTopicListenerImpl(TopicListenerPollerData* poller, - SubscriberData* subscriber, - unsigned int eventMask) { - auto listener = m_topicListeners.Add(m_inst, poller, subscriber, - subscriber->topic, eventMask); - subscriber->topic->listeners.Add(listener); +void LSImpl::AddListenerImpl(NT_Listener listenerHandle, + SubscriberData* subscriber, unsigned int eventMask, + NT_Handle subentryHandle, bool subscriberOwned) { + m_listeners.try_emplace(listenerHandle, std::make_unique( + listenerHandle, subscriber, + eventMask, subscriberOwned)); - // handle immediate - if ((eventMask & (NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_IMMEDIATE)) == - (NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_IMMEDIATE) && - listener->topic->Exists()) { - listener->poller->queue.emplace_back( - listener->handle, subscriber->topic->GetTopicInfo(), - NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_IMMEDIATE); - listener->handle.Set(); - listener->poller->handle.Set(); - } - - return listener; -} - -TopicListenerData* LSImpl::AddTopicListenerImpl(TopicListenerPollerData* poller, - MultiSubscriberData* subscriber, - unsigned int eventMask) { - auto listener = m_topicListeners.Add(m_inst, poller, subscriber, - subscriber->prefixes, eventMask); - m_topicPrefixListeners.Add(listener); - - // handle immediate - if ((eventMask & (NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_IMMEDIATE)) == - (NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_IMMEDIATE)) { - bool any = false; - for (auto&& topic : m_topics) { - for (auto&& prefix : listener->multiSubscriber->prefixes) { - if (wpi::starts_with(topic->name, prefix) && topic->Exists()) { - listener->poller->queue.emplace_back( - listener->handle, topic->GetTopicInfo(), - NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_IMMEDIATE); - any = true; - } - } - } - if (any) { - listener->handle.Set(); - listener->poller->handle.Set(); - } - } - - return listener; -} - -TopicListenerData* LSImpl::AddTopicListenerImpl( - TopicListenerPollerData* poller, std::span prefixes, - unsigned int eventMask) { - // subscribe to make sure topic updates are received - PubSubOptions options; - options.topicsOnly = true; - options.prefixMatch = true; - auto subscriber = AddMultiSubscriber(prefixes, options); - auto listener = AddTopicListenerImpl(poller, subscriber, eventMask); - listener->subscriberOwned = true; - return listener; -} - -NT_TopicListener LSImpl::AddTopicListener( - std::span prefixes, unsigned int mask, - std::function callback) { - if (!m_topicListenerThread) { - m_topicListenerThread.Start(AddTopicListenerPoller()); - } - if (auto thr = m_topicListenerThread.GetThread()) { - NT_TopicListener listener = - AddTopicListenerImpl(thr->m_pollerData, prefixes, mask)->handle; - if (listener) { - thr->m_callbacks.try_emplace(listener, std::move(callback)); - } - return listener; - } else { - return {}; - } -} - -NT_TopicListener LSImpl::AddTopicListenerHandle(TopicListenerPollerData* poller, - NT_Handle handle, - unsigned int mask) { - if (auto topic = m_topics.Get(handle)) { - return AddTopicListenerImpl(poller, topic, mask)->handle; - } else if (auto sub = m_multiSubscribers.Get(handle)) { - return AddTopicListenerImpl(poller, sub, mask)->handle; - } else if (auto sub = m_subscribers.Get(handle)) { - return AddTopicListenerImpl(poller, sub, mask)->handle; - } else if (auto entry = m_entries.Get(handle)) { - return AddTopicListenerImpl(poller, entry->subscriber, mask)->handle; - } else { - return {}; - } -} - -NT_TopicListener LSImpl::AddTopicListener( - NT_Handle handle, unsigned int mask, - std::function callback) { - if (!m_topicListenerThread) { - m_topicListenerThread.Start(AddTopicListenerPoller()); - } - if (auto thr = m_topicListenerThread.GetThread()) { - NT_TopicListener listener = - AddTopicListenerHandle(thr->m_pollerData, handle, mask); - if (listener) { - thr->m_callbacks.try_emplace(listener, std::move(callback)); - } - return listener; - } else { - return {}; - } -} - -void LSImpl::DestroyTopicListenerPoller(NT_TopicListenerPoller pollerHandle) { - if (auto poller = m_topicListenerPollers.Remove(pollerHandle)) { - // ensure all listeners that use this poller are removed - wpi::SmallVector toRemove; - for (auto&& listener : m_topicListeners) { - if (listener->poller == poller.get()) { - toRemove.emplace_back(listener->handle); - } - } - for (auto handle : toRemove) { - RemoveTopicListener(handle); - } - } -} - -std::unique_ptr LSImpl::RemoveTopicListener( - NT_TopicListener listenerHandle) { - auto listener = m_topicListeners.Remove(listenerHandle); - if (listener) { - if (listener->subscriberOwned) { - if (listener->subscriber) { - RemoveLocalSubscriber(listener->subscriber->handle); - } - if (listener->multiSubscriber) { - RemoveMultiSubscriber(listener->multiSubscriber->handle); - } - } - if (listener->topic) { - listener->topic->listeners.Remove(listener.get()); - } else { - m_topicPrefixListeners.Remove(listener.get()); - } - } - return listener; -} - -ValueListenerData* LSImpl::AddValueListenerImpl(ValueListenerPollerData* poller, - SubscriberData* subscriber, - NT_Handle subentryHandle, - unsigned int eventMask) { - auto listener = m_valueListeners.Add(m_inst, poller, subscriber, - subentryHandle, eventMask); - listener->subscriber->valueListeners.Add(listener); auto topic = subscriber->topic; - // handle immediate - if ((eventMask & NT_VALUE_NOTIFY_IMMEDIATE) != 0 && topic->lastValue) { - listener->poller->queue.emplace_back(listener->handle, topic->handle, - subentryHandle, topic->lastValue, - NT_VALUE_NOTIFY_IMMEDIATE); - listener->handle.Set(); - listener->poller->handle.Set(); + if ((eventMask & NT_EVENT_TOPIC) != 0) { + m_listenerStorage.Activate( + listenerHandle, eventMask & (NT_EVENT_TOPIC | NT_EVENT_IMMEDIATE)); + + topic->listeners.Add(listenerHandle); + + // handle immediate publish + if ((eventMask & (NT_EVENT_PUBLISH | NT_EVENT_IMMEDIATE)) == + (NT_EVENT_PUBLISH | NT_EVENT_IMMEDIATE) && + topic->Exists()) { + m_listenerStorage.Notify({&listenerHandle, 1}, + NT_EVENT_PUBLISH | NT_EVENT_IMMEDIATE, + topic->GetTopicInfo()); + } } - return listener; + if ((eventMask & NT_EVENT_VALUE_ALL) != 0) { + m_listenerStorage.Activate( + listenerHandle, eventMask & (NT_EVENT_VALUE_ALL | NT_EVENT_IMMEDIATE), + [subentryHandle](unsigned int mask, Event* event) { + if (auto valueData = event->GetValueEventData()) { + valueData->subentry = subentryHandle; + } + return true; + }); + + subscriber->valueListeners.Add(listenerHandle); + + // handle immediate value + if ((eventMask & NT_EVENT_VALUE_ALL) != 0 && + (eventMask & NT_EVENT_IMMEDIATE) != 0 && topic->lastValue) { + m_listenerStorage.Notify({&listenerHandle, 1}, + NT_EVENT_IMMEDIATE | NT_EVENT_VALUE_ALL, + topic->handle, subentryHandle, topic->lastValue); + } + } } -ValueListenerData* LSImpl::AddValueListenerImpl(ValueListenerPollerData* poller, - MultiSubscriberData* subscriber, - NT_Handle subentryHandle, - unsigned int eventMask) { - auto listener = m_valueListeners.Add(m_inst, poller, subscriber, - subentryHandle, eventMask); - listener->multiSubscriber->valueListeners.Add(listener); +void LSImpl::AddListenerImpl(NT_Listener listenerHandle, + MultiSubscriberData* subscriber, + unsigned int eventMask, bool subscriberOwned) { + auto listener = + m_listeners + .try_emplace(listenerHandle, std::make_unique( + listenerHandle, subscriber, + eventMask, subscriberOwned)) + .first->getSecond() + .get(); - // handle immediate - if ((eventMask & NT_VALUE_NOTIFY_IMMEDIATE) != 0) { - bool any = false; + // if we're doing anything immediate, get the list of matching topics + wpi::SmallVector topics; + if ((eventMask & NT_EVENT_IMMEDIATE) != 0 && + (eventMask & (NT_EVENT_PUBLISH | NT_EVENT_VALUE_ALL)) != 0) { for (auto&& topic : m_topics) { - for (auto&& prefix : listener->multiSubscriber->prefixes) { - if (wpi::starts_with(topic->name, prefix) && topic->lastValue) { - listener->poller->queue.emplace_back(listener->handle, topic->handle, - subentryHandle, topic->lastValue, - NT_VALUE_NOTIFY_IMMEDIATE); - any = true; + for (auto&& prefix : subscriber->prefixes) { + if (wpi::starts_with(topic->name, prefix) && topic->Exists()) { + topics.emplace_back(topic.get()); } } } - if (any) { - listener->handle.Set(); - listener->poller->handle.Set(); - } } - return listener; -} + if ((eventMask & NT_EVENT_TOPIC) != 0) { + m_listenerStorage.Activate( + listenerHandle, eventMask & (NT_EVENT_TOPIC | NT_EVENT_IMMEDIATE)); -NT_ValueListener LSImpl::AddValueListenerHandle(ValueListenerPollerData* poller, - NT_Handle subentryHandle, - unsigned int mask) { - if (auto sub = m_subscribers.Get(subentryHandle)) { - return AddValueListenerImpl(poller, sub, subentryHandle, mask)->handle; - } else if (auto entry = m_entries.Get(subentryHandle)) { - return AddValueListenerImpl(poller, entry->subscriber, subentryHandle, mask) - ->handle; - } else if (auto sub = m_multiSubscribers.Get(subentryHandle)) { - return AddValueListenerImpl(poller, sub, subentryHandle, mask)->handle; - } else { - return {}; - } -} + m_topicPrefixListeners.Add(listener); -NT_ValueListener LSImpl::AddValueListener( - NT_Handle subentry, unsigned int mask, - std::function callback) { - if (!m_valueListenerThread) { - m_valueListenerThread.Start(AddValueListenerPoller()); - } - if (auto thr = m_valueListenerThread.GetThread()) { - auto listener = AddValueListenerHandle(thr->m_pollerData, subentry, mask); - if (listener) { - thr->m_callbacks.try_emplace(listener, std::move(callback)); - } - return listener; - } else { - return {}; - } -} - -void LSImpl::DestroyValueListenerPoller(NT_ValueListenerPoller pollerHandle) { - if (auto poller = m_valueListenerPollers.Remove(pollerHandle)) { - // ensure all listeners that use this poller are removed - wpi::SmallVector toRemove; - for (auto&& listener : m_valueListeners) { - if (listener->poller == poller.get()) { - toRemove.emplace_back(listener->handle); + // handle immediate publish + if ((eventMask & (NT_EVENT_PUBLISH | NT_EVENT_IMMEDIATE)) == + (NT_EVENT_PUBLISH | NT_EVENT_IMMEDIATE)) { + std::vector topicInfos; + for (auto&& topic : topics) { + topicInfos.emplace_back(topic->GetTopicInfo()); + } + if (!topicInfos.empty()) { + m_listenerStorage.Notify({&listenerHandle, 1}, + NT_EVENT_PUBLISH | NT_EVENT_IMMEDIATE, + topicInfos); } } - for (auto handle : toRemove) { - RemoveValueListener(handle); + } + + if ((eventMask & NT_EVENT_VALUE_ALL) != 0) { + m_listenerStorage.Activate( + listenerHandle, eventMask & (NT_EVENT_VALUE_ALL | NT_EVENT_IMMEDIATE), + [subentryHandle = subscriber->handle.GetHandle()](unsigned int mask, + Event* event) { + if (auto valueData = event->GetValueEventData()) { + valueData->subentry = subentryHandle; + } + return true; + }); + + subscriber->valueListeners.Add(listenerHandle); + + // handle immediate value + if ((eventMask & NT_EVENT_VALUE_ALL) != 0 && + (eventMask & NT_EVENT_IMMEDIATE) != 0) { + for (auto&& topic : topics) { + if (topic->lastValue) { + m_listenerStorage.Notify( + {&listenerHandle, 1}, NT_EVENT_VALUE_ALL | NT_EVENT_IMMEDIATE, + topic->handle, subscriber->handle, topic->lastValue); + } + } } } } -std::unique_ptr LSImpl::RemoveValueListener( - NT_ValueListener listenerHandle) { - auto listener = m_valueListeners.Remove(listenerHandle); - if (listener) { - if (listener->subscriber) { - listener->subscriber->valueListeners.Remove(listener.get()); - } - if (listener->multiSubscriber) { - listener->multiSubscriber->valueListeners.Remove(listener.get()); +void LSImpl::AddListener(NT_Listener listenerHandle, + std::span prefixes, + unsigned int eventMask) { + // subscribe to make sure topic updates are received + PubSubOptions options; + options.topicsOnly = (eventMask & NT_EVENT_VALUE_ALL) == 0; + options.prefixMatch = true; + auto sub = AddMultiSubscriber(prefixes, options); + AddListenerImpl(listenerHandle, sub, eventMask, true); +} + +void LSImpl::AddListener(NT_Listener listenerHandle, NT_Handle handle, + unsigned int mask) { + if (auto topic = m_topics.Get(handle)) { + AddListenerImpl(listenerHandle, topic, mask); + } else if (auto sub = m_multiSubscribers.Get(handle)) { + AddListenerImpl(listenerHandle, sub, mask, false); + } else if (auto sub = m_subscribers.Get(handle)) { + AddListenerImpl(listenerHandle, sub, mask, sub->handle, false); + } else if (auto entry = m_entries.Get(handle)) { + AddListenerImpl(listenerHandle, entry->subscriber, mask, entry->handle, + false); + } +} + +void LSImpl::RemoveListener(NT_Listener listenerHandle, unsigned int mask) { + auto listenerIt = m_listeners.find(listenerHandle); + if (listenerIt == m_listeners.end()) { + return; + } + auto listener = std::move(listenerIt->getSecond()); + m_listeners.erase(listenerIt); + if (!listener) { + return; + } + + m_topicPrefixListeners.Remove(listener.get()); + if (listener->subscriber) { + listener->subscriber->valueListeners.Remove(listenerHandle); + listener->subscriber->topic->listeners.Remove(listenerHandle); + if (listener->subscriberOwned) { + RemoveLocalSubscriber(listener->subscriber->handle); + } + } + if (listener->multiSubscriber) { + listener->multiSubscriber->valueListeners.Remove(listenerHandle); + if (listener->subscriberOwned) { + RemoveMultiSubscriber(listener->multiSubscriber->handle); } } - return listener; } TopicData* LSImpl::GetOrCreateTopic(std::string_view name) { @@ -1489,7 +1164,7 @@ bool LSImpl::PublishLocalValue(PublisherData* publisher, const Value& value) { if (m_network) { m_network->SetValue(publisher->handle, value); } - return SetValue(publisher->topic, value, NT_VALUE_NOTIFY_LOCAL); + return SetValue(publisher->topic, value, NT_EVENT_VALUE_LOCAL); } else { return false; } @@ -1560,11 +1235,13 @@ void LSImpl::RemoveSubEntry(NT_Handle subentryHandle) { class LocalStorage::Impl : public LSImpl { public: - Impl(int inst, wpi::Logger& logger) : LSImpl{inst, logger} {} + Impl(int inst, IListenerStorage& listenerStorage, wpi::Logger& logger) + : LSImpl{inst, listenerStorage, logger} {} }; -LocalStorage::LocalStorage(int inst, wpi::Logger& logger) - : m_impl{std::make_unique(inst, logger)} {} +LocalStorage::LocalStorage(int inst, IListenerStorage& listenerStorage, + wpi::Logger& logger) + : m_impl{std::make_unique(inst, listenerStorage, logger)} {} LocalStorage::~LocalStorage() = default; @@ -1596,7 +1273,7 @@ void LocalStorage::NetworkPropertiesUpdate(std::string_view name, void LocalStorage::NetworkSetValue(NT_Topic topicHandle, const Value& value) { std::scoped_lock lock{m_mutex}; if (auto topic = m_impl->m_topics.Get(topicHandle)) { - m_impl->SetValue(topic, value, NT_VALUE_NOTIFY_NONE); + m_impl->SetValue(topic, value, NT_EVENT_VALUE_REMOTE); } } @@ -1813,7 +1490,7 @@ void LocalStorage::SetTopicProperty(NT_Topic topicHandle, std::string_view name, } wpi::json update = wpi::json::object(); update[name] = value; - m_impl->PropertiesUpdated(topic, update, NT_TOPIC_NOTIFY_NONE, true); + m_impl->PropertiesUpdated(topic, update, NT_EVENT_NONE, true); } } @@ -1824,7 +1501,7 @@ void LocalStorage::DeleteTopicProperty(NT_Topic topicHandle, topic->properties.erase(name); wpi::json update = wpi::json::object(); update[name] = wpi::json(); - m_impl->PropertiesUpdated(topic, update, NT_TOPIC_NOTIFY_NONE, true); + m_impl->PropertiesUpdated(topic, update, NT_EVENT_NONE, true); } } @@ -2370,142 +2047,24 @@ int64_t LocalStorage::GetEntryLastChange(NT_Handle subentryHandle) { } } -NT_TopicListener LocalStorage::AddTopicListener( - std::span prefixes, unsigned int mask, - std::function callback) { +void LocalStorage::AddListener(NT_Listener listener, + std::span prefixes, + unsigned int mask) { + mask &= (NT_EVENT_TOPIC | NT_EVENT_VALUE_ALL | NT_EVENT_IMMEDIATE); std::scoped_lock lock{m_mutex}; - return m_impl->AddTopicListener(prefixes, mask, std::move(callback)); + m_impl->AddListener(listener, prefixes, mask); } -NT_TopicListener LocalStorage::AddTopicListener( - NT_Topic topicHandle, unsigned int mask, - std::function callback) { +void LocalStorage::AddListener(NT_Listener listener, NT_Handle handle, + unsigned int mask) { + mask &= (NT_EVENT_TOPIC | NT_EVENT_VALUE_ALL | NT_EVENT_IMMEDIATE); std::scoped_lock lock{m_mutex}; - return m_impl->AddTopicListener(topicHandle, mask, std::move(callback)); + m_impl->AddListener(listener, handle, mask); } -bool LocalStorage::WaitForTopicListenerQueue(double timeout) { +void LocalStorage::RemoveListener(NT_Listener listener, unsigned int mask) { std::scoped_lock lock{m_mutex}; - WPI_EventHandle h; - if (auto thr = m_impl->m_topicListenerThread.GetThread()) { - h = thr->m_waitQueueWaiter.GetHandle(); - thr->m_waitQueueWakeup.Set(); - } else { - return false; - } - bool timedOut; - return wpi::WaitForObject(h, timeout, &timedOut); -} - -NT_TopicListenerPoller LocalStorage::CreateTopicListenerPoller() { - std::scoped_lock lock{m_mutex}; - return m_impl->AddTopicListenerPoller()->handle; -} - -void LocalStorage::DestroyTopicListenerPoller( - NT_TopicListenerPoller pollerHandle) { - std::scoped_lock lock{m_mutex}; - m_impl->DestroyTopicListenerPoller(pollerHandle); -} - -NT_TopicListener LocalStorage::AddPolledTopicListener( - NT_TopicListenerPoller pollerHandle, - std::span prefixes, unsigned int mask) { - std::scoped_lock lock{m_mutex}; - if (auto poller = m_impl->m_topicListenerPollers.Get(pollerHandle)) { - return m_impl->AddTopicListenerImpl(poller, prefixes, mask)->handle; - } else { - return {}; - } -} - -NT_TopicListener LocalStorage::AddPolledTopicListener( - NT_TopicListenerPoller pollerHandle, NT_Handle handle, unsigned int mask) { - std::scoped_lock lock{m_mutex}; - - auto poller = m_impl->m_topicListenerPollers.Get(pollerHandle); - if (!poller) { - return {}; - } - - return m_impl->AddTopicListenerHandle(poller, handle, mask); -} - -std::vector LocalStorage::ReadTopicListenerQueue( - NT_TopicListenerPoller pollerHandle) { - std::scoped_lock lock{m_mutex}; - if (auto poller = m_impl->m_topicListenerPollers.Get(pollerHandle)) { - std::vector rv; - rv.swap(poller->queue); - return rv; - } else { - return {}; - } -} - -void LocalStorage::RemoveTopicListener(NT_TopicListener listenerHandle) { - std::scoped_lock lock{m_mutex}; - m_impl->RemoveTopicListener(listenerHandle); -} - -NT_ValueListener LocalStorage::AddValueListener( - NT_Handle subentry, unsigned int mask, - std::function callback) { - std::scoped_lock lock{m_mutex}; - return m_impl->AddValueListener(subentry, mask, std::move(callback)); -} - -bool LocalStorage::WaitForValueListenerQueue(double timeout) { - std::scoped_lock lock{m_mutex}; - WPI_EventHandle h; - if (auto thr = m_impl->m_valueListenerThread.GetThread()) { - h = thr->m_waitQueueWaiter.GetHandle(); - thr->m_waitQueueWakeup.Set(); - } else { - return false; - } - bool timedOut; - return wpi::WaitForObject(h, timeout, &timedOut); -} - -NT_ValueListenerPoller LocalStorage::CreateValueListenerPoller() { - std::scoped_lock lock{m_mutex}; - return m_impl->AddValueListenerPoller()->handle; -} - -void LocalStorage::DestroyValueListenerPoller( - NT_ValueListenerPoller pollerHandle) { - std::scoped_lock lock{m_mutex}; - m_impl->DestroyValueListenerPoller(pollerHandle); -} - -NT_ValueListener LocalStorage::AddPolledValueListener( - NT_ValueListenerPoller pollerHandle, NT_Handle subentryHandle, - unsigned int mask) { - std::scoped_lock lock{m_mutex}; - - auto poller = m_impl->m_valueListenerPollers.Get(pollerHandle); - if (!poller) { - return {}; - } - return m_impl->AddValueListenerHandle(poller, subentryHandle, mask); -} - -std::vector LocalStorage::ReadValueListenerQueue( - NT_ValueListenerPoller pollerHandle) { - std::scoped_lock lock{m_mutex}; - if (auto poller = m_impl->m_valueListenerPollers.Get(pollerHandle)) { - std::vector rv; - rv.swap(poller->queue); - return rv; - } else { - return {}; - } -} - -void LocalStorage::RemoveValueListener(NT_ValueListener listenerHandle) { - std::scoped_lock lock{m_mutex}; - m_impl->RemoveValueListener(listenerHandle); + m_impl->RemoveListener(listener, mask); } NT_DataLogger LocalStorage::StartDataLog(wpi::log::DataLog& log, diff --git a/ntcore/src/main/native/cpp/LocalStorage.h b/ntcore/src/main/native/cpp/LocalStorage.h index 0c8e0a678a..e2b770a1fe 100644 --- a/ntcore/src/main/native/cpp/LocalStorage.h +++ b/ntcore/src/main/native/cpp/LocalStorage.h @@ -25,9 +25,12 @@ class Logger; namespace nt { +class IListenerStorage; + class LocalStorage final : public net::ILocalStorage { public: - LocalStorage(int inst, wpi::Logger& logger); + LocalStorage(int inst, IListenerStorage& listenerStorage, + wpi::Logger& logger); LocalStorage(const LocalStorage&) = delete; LocalStorage& operator=(const LocalStorage&) = delete; ~LocalStorage() final; @@ -189,51 +192,15 @@ class LocalStorage final : public net::ILocalStorage { int64_t GetEntryLastChange(NT_Entry entry); // - // Topic listener functions + // Listener functions // - NT_TopicListener AddTopicListener( - std::span prefixes, unsigned int mask, - std::function callback); - NT_TopicListener AddTopicListener( - NT_Handle handle, unsigned int mask, - std::function callback); - bool WaitForTopicListenerQueue(double timeout); + void AddListener(NT_Listener listener, + std::span prefixes, + unsigned int mask); + void AddListener(NT_Listener listener, NT_Handle handle, unsigned int mask); - NT_TopicListenerPoller CreateTopicListenerPoller(); - void DestroyTopicListenerPoller(NT_TopicListenerPoller poller); - - NT_TopicListener AddPolledTopicListener( - NT_TopicListenerPoller poller, std::span prefixes, - unsigned int mask); - NT_TopicListener AddPolledTopicListener(NT_TopicListenerPoller poller, - NT_Handle handle, unsigned int mask); - - std::vector ReadTopicListenerQueue( - NT_TopicListenerPoller poller); - - void RemoveTopicListener(NT_TopicListener listener); - - // - // Value listener functions - // - - NT_ValueListener AddValueListener( - NT_Handle subentry, unsigned int mask, - std::function callback); - bool WaitForValueListenerQueue(double timeout); - - NT_ValueListenerPoller CreateValueListenerPoller(); - void DestroyValueListenerPoller(NT_ValueListenerPoller poller); - - NT_ValueListener AddPolledValueListener(NT_ValueListenerPoller poller, - NT_Handle subentry, - unsigned int mask); - - std::vector ReadValueListenerQueue( - NT_ValueListenerPoller poller); - - void RemoveValueListener(NT_ValueListener listener); + void RemoveListener(NT_Listener listener, unsigned int mask); // // Data log functions diff --git a/ntcore/src/main/native/cpp/LoggerImpl.cpp b/ntcore/src/main/native/cpp/LoggerImpl.cpp index 2e3e24f3ba..f5742d5d98 100644 --- a/ntcore/src/main/native/cpp/LoggerImpl.cpp +++ b/ntcore/src/main/native/cpp/LoggerImpl.cpp @@ -5,24 +5,27 @@ #include "LoggerImpl.h" #include -#include +#include +#include #include +#include "IListenerStorage.h" + using namespace nt; static void DefaultLogger(unsigned int level, const char* file, unsigned int line, const char* msg) { - if (level == 20) { + if (level == wpi::WPI_LOG_INFO) { fmt::print(stderr, "NT: {}\n", msg); return; } std::string_view levelmsg; - if (level >= 50) { + if (level >= wpi::WPI_LOG_CRITICAL) { levelmsg = "CRITICAL"; - } else if (level >= 40) { + } else if (level >= wpi::WPI_LOG_ERROR) { levelmsg = "ERROR"; - } else if (level >= 30) { + } else if (level >= wpi::WPI_LOG_WARNING) { levelmsg = "WARNING"; } else { return; @@ -30,108 +33,107 @@ static void DefaultLogger(unsigned int level, const char* file, fmt::print(stderr, "NT: {}: {} ({}:{})\n", levelmsg, msg, file, line); } -class LoggerImpl::Thread final : public wpi::SafeThreadEvent { - public: - explicit Thread(NT_LoggerPoller poller) : m_poller{poller} {} +static constexpr unsigned int kFlagCritical = 1u << 16; +static constexpr unsigned int kFlagError = 1u << 17; +static constexpr unsigned int kFlagWarning = 1u << 18; +static constexpr unsigned int kFlagInfo = 1u << 19; +static constexpr unsigned int kFlagDebug = 1u << 20; +static constexpr unsigned int kFlagDebug1 = 1u << 21; +static constexpr unsigned int kFlagDebug2 = 1u << 22; +static constexpr unsigned int kFlagDebug3 = 1u << 23; +static constexpr unsigned int kFlagDebug4 = 1u << 24; - void Main() final; - - NT_LoggerPoller m_poller; - wpi::DenseMap> - m_callbacks; -}; - -void LoggerImpl::Thread::Main() { - while (m_active) { - WPI_Handle signaledBuf[2]; - auto signaled = - wpi::WaitForObjects({m_poller, m_stopEvent.GetHandle()}, signaledBuf); - if (signaled.empty() || !m_active) { - return; - } - // call all the way back out to the C++ API to ensure valid handle - auto events = nt::ReadLoggerQueue(m_poller); - if (events.empty()) { - continue; - } - std::unique_lock lock{m_mutex}; - for (auto&& event : events) { - auto callbackIt = m_callbacks.find(event.logger); - if (callbackIt != m_callbacks.end()) { - auto callback = callbackIt->second; - lock.unlock(); - callback(event); - lock.lock(); - } - } +static unsigned int LevelToFlag(unsigned int level) { + if (level >= wpi::WPI_LOG_CRITICAL) { + return EventFlags::kLogMessage | kFlagCritical; + } else if (level >= wpi::WPI_LOG_ERROR) { + return EventFlags::kLogMessage | kFlagError; + } else if (level >= wpi::WPI_LOG_WARNING) { + return EventFlags::kLogMessage | kFlagWarning; + } else if (level >= wpi::WPI_LOG_INFO) { + return EventFlags::kLogMessage | kFlagInfo; + } else if (level >= wpi::WPI_LOG_DEBUG) { + return EventFlags::kLogMessage | kFlagDebug; + } else if (level >= wpi::WPI_LOG_DEBUG1) { + return EventFlags::kLogMessage | kFlagDebug1; + } else if (level >= wpi::WPI_LOG_DEBUG2) { + return EventFlags::kLogMessage | kFlagDebug2; + } else if (level >= wpi::WPI_LOG_DEBUG3) { + return EventFlags::kLogMessage | kFlagDebug3; + } else if (level >= wpi::WPI_LOG_DEBUG4) { + return EventFlags::kLogMessage | kFlagDebug4; + } else { + return EventFlags::kLogMessage; } } -LoggerImpl::LoggerImpl(int inst) : m_inst{inst} {} +static unsigned int LevelsToEventMask(unsigned int minLevel, + unsigned int maxLevel) { + unsigned int mask = 0; + if (minLevel <= wpi::WPI_LOG_CRITICAL && maxLevel >= wpi::WPI_LOG_CRITICAL) { + mask |= kFlagCritical; + } + if (minLevel <= wpi::WPI_LOG_ERROR && maxLevel >= wpi::WPI_LOG_ERROR) { + mask |= kFlagError; + } + if (minLevel <= wpi::WPI_LOG_WARNING && maxLevel >= wpi::WPI_LOG_WARNING) { + mask |= kFlagWarning; + } + if (minLevel <= wpi::WPI_LOG_INFO && maxLevel >= wpi::WPI_LOG_INFO) { + mask |= kFlagInfo; + } + if (minLevel <= wpi::WPI_LOG_DEBUG && maxLevel >= wpi::WPI_LOG_DEBUG) { + mask |= kFlagDebug; + } + if (minLevel <= wpi::WPI_LOG_DEBUG1 && maxLevel >= wpi::WPI_LOG_DEBUG1) { + mask |= kFlagDebug1; + } + if (minLevel <= wpi::WPI_LOG_DEBUG2 && maxLevel >= wpi::WPI_LOG_DEBUG2) { + mask |= kFlagDebug2; + } + if (minLevel <= wpi::WPI_LOG_DEBUG3 && maxLevel >= wpi::WPI_LOG_DEBUG3) { + mask |= kFlagDebug3; + } + if (minLevel <= wpi::WPI_LOG_DEBUG4 && maxLevel >= wpi::WPI_LOG_DEBUG4) { + mask |= kFlagDebug4; + } + if (mask == 0) { + mask = EventFlags::kLogMessage; + } + return mask; +} + +LoggerImpl::LoggerImpl(IListenerStorage& listenerStorage) + : m_listenerStorage{listenerStorage} {} LoggerImpl::~LoggerImpl() = default; -NT_Logger LoggerImpl::Add(std::function callback, - unsigned int minLevel, unsigned int maxLevel) { - if (!m_thread) { - m_thread.Start(CreatePoller()); - } - if (auto thr = m_thread.GetThread()) { - auto listener = AddPolled(thr->m_poller, minLevel, maxLevel); - if (listener) { - thr->m_callbacks.try_emplace(listener, std::move(callback)); - } - return listener; - } else { - return {}; - } +void LoggerImpl::AddListener(NT_Listener listener, unsigned int minLevel, + unsigned int maxLevel) { + ++m_listenerCount; + std::scoped_lock lock{m_mutex}; + m_listenerLevels.emplace_back(listener, minLevel, maxLevel); + m_listenerStorage.Activate(listener, LevelsToEventMask(minLevel, maxLevel), + [](unsigned int mask, Event* event) { + event->flags = NT_EVENT_LOGMESSAGE; + return true; + }); } -NT_LoggerPoller LoggerImpl::CreatePoller() { +void LoggerImpl::RemoveListener(NT_Listener listener) { + --m_listenerCount; std::scoped_lock lock{m_mutex}; - return m_pollers.Add(m_inst)->handle; -} - -void LoggerImpl::DestroyPoller(NT_LoggerPoller pollerHandle) { - std::scoped_lock lock{m_mutex}; - m_pollers.Remove(pollerHandle); -} - -NT_Logger LoggerImpl::AddPolled(NT_LoggerPoller pollerHandle, - unsigned int minLevel, unsigned int maxLevel) { - std::scoped_lock lock{m_mutex}; - if (auto poller = m_pollers.Get(pollerHandle)) { - return m_listeners.Add(m_inst, poller, minLevel, maxLevel)->handle; - } else { - return {}; - } -} - -std::vector LoggerImpl::ReadQueue(NT_LoggerPoller pollerHandle) { - std::scoped_lock lock{m_mutex}; - if (auto poller = m_pollers.Get(pollerHandle)) { - std::vector rv; - rv.swap(poller->queue); - return rv; - } else { - return {}; - } -} - -void LoggerImpl::Remove(NT_Logger listenerHandle) { - std::scoped_lock lock{m_mutex}; - m_listeners.Remove(listenerHandle); - if (auto thr = m_thread.GetThread()) { - thr->m_callbacks.erase(listenerHandle); - } + std::erase_if(m_listenerLevels, + [&](auto& v) { return v.listener == listener; }); } unsigned int LoggerImpl::GetMinLevel() { // return 0; + std::scoped_lock lock{m_mutex}; unsigned int level = NT_LOG_INFO; - for (auto&& listener : m_listeners) { - if (listener && listener->minLevel < level) { - level = listener->minLevel; + for (auto&& listenerLevel : m_listenerLevels) { + if (listenerLevel.minLevel < level) { + level = listenerLevel.minLevel; } } return level; @@ -140,19 +142,10 @@ unsigned int LoggerImpl::GetMinLevel() { void LoggerImpl::Log(unsigned int level, const char* file, unsigned int line, const char* msg) { auto filename = fs::path{file}.filename(); - { - std::scoped_lock lock{m_mutex}; - if (m_listeners.empty()) { - DefaultLogger(level, filename.string().c_str(), line, msg); - } else { - for (auto&& listener : m_listeners) { - if (level >= listener->minLevel && level <= listener->maxLevel) { - listener->poller->queue.emplace_back(listener->handle.GetHandle(), - level, file, line, msg); - listener->poller->handle.Set(); - listener->handle.Set(); - } - } - } + if (m_listenerCount == 0) { + DefaultLogger(level, filename.string().c_str(), line, msg); + } else { + m_listenerStorage.Notify(LevelToFlag(level), level, filename.string(), line, + msg); } } diff --git a/ntcore/src/main/native/cpp/LoggerImpl.h b/ntcore/src/main/native/cpp/LoggerImpl.h index 141e0c8629..13a1b80fb5 100644 --- a/ntcore/src/main/native/cpp/LoggerImpl.h +++ b/ntcore/src/main/native/cpp/LoggerImpl.h @@ -4,34 +4,29 @@ #pragma once -#include +#include #include -#include -#include #include -#include "Handle.h" -#include "HandleMap.h" +#include "IListenerStorage.h" #include "ntcore_c.h" #include "ntcore_cpp.h" namespace nt { +class IListenerStorage; + class LoggerImpl { public: - explicit LoggerImpl(int inst); + explicit LoggerImpl(IListenerStorage& listenerStorage); + LoggerImpl(const LoggerImpl&) = delete; + LoggerImpl& operator=(const LoggerImpl&) = delete; ~LoggerImpl(); - NT_Logger Add(std::function callback, - unsigned int minLevel, unsigned int maxLevel); - - NT_LoggerPoller CreatePoller(); - void DestroyPoller(NT_LoggerPoller pollerHandle); - NT_Logger AddPolled(NT_LoggerPoller pollerHandle, unsigned int minLevel, - unsigned int maxLevel); - std::vector ReadQueue(NT_LoggerPoller pollerHandle); - void Remove(NT_Logger listenerHandle); + void AddListener(NT_Listener listener, unsigned int minLevel, + unsigned int maxLevel); + void RemoveListener(NT_Listener listener); unsigned int GetMinLevel(); @@ -39,38 +34,20 @@ class LoggerImpl { const char* msg); private: - int m_inst; - mutable wpi::mutex m_mutex; + IListenerStorage& m_listenerStorage; + std::atomic_int m_listenerCount{0}; + wpi::mutex m_mutex; - struct PollerData { - static constexpr auto kType = Handle::kLoggerPoller; + struct ListenerLevels { + ListenerLevels(NT_Listener listener, unsigned int minLevel, + unsigned int maxLevel) + : listener{listener}, minLevel{minLevel}, maxLevel{maxLevel} {} - explicit PollerData(NT_LoggerPoller handle) : handle{handle} {} - - wpi::SignalObject handle; - std::vector queue; - }; - HandleMap m_pollers; - - struct ListenerData { - static constexpr auto kType = Handle::kLogger; - - ListenerData(NT_Logger handle, PollerData* poller, unsigned int minLevel, - unsigned int maxLevel) - : handle{handle}, - poller{poller}, - minLevel{minLevel}, - maxLevel{maxLevel} {} - - wpi::SignalObject handle; - PollerData* poller; + NT_Listener listener; unsigned int minLevel; unsigned int maxLevel; }; - HandleMap m_listeners; - - class Thread; - wpi::SafeThreadOwner m_thread; + std::vector m_listenerLevels; }; } // namespace nt diff --git a/ntcore/src/main/native/cpp/jni/NetworkTablesJNI.cpp b/ntcore/src/main/native/cpp/jni/NetworkTablesJNI.cpp index 78ba3d8003..66f7dbb953 100644 --- a/ntcore/src/main/native/cpp/jni/NetworkTablesJNI.cpp +++ b/ntcore/src/main/native/cpp/jni/NetworkTablesJNI.cpp @@ -34,15 +34,14 @@ void JNI_UnloadTypes(JNIEnv* env); static JavaVM* jvm = nullptr; static JClass booleanCls; static JClass connectionInfoCls; -static JClass connectionNotificationCls; static JClass doubleCls; +static JClass eventCls; static JClass floatCls; static JClass logMessageCls; static JClass longCls; static JClass topicInfoCls; -static JClass topicNotificationCls; static JClass valueCls; -static JClass valueNotificationCls; +static JClass valueEventDataCls; static JException illegalArgEx; static JException interruptedEx; static JException nullPointerEx; @@ -50,16 +49,14 @@ static JException nullPointerEx; static const JClassInit classes[] = { {"java/lang/Boolean", &booleanCls}, {"edu/wpi/first/networktables/ConnectionInfo", &connectionInfoCls}, - {"edu/wpi/first/networktables/ConnectionNotification", - &connectionNotificationCls}, {"java/lang/Double", &doubleCls}, + {"edu/wpi/first/networktables/NetworkTableEvent", &eventCls}, {"java/lang/Float", &floatCls}, {"edu/wpi/first/networktables/LogMessage", &logMessageCls}, {"java/lang/Long", &longCls}, {"edu/wpi/first/networktables/TopicInfo", &topicInfoCls}, - {"edu/wpi/first/networktables/TopicNotification", &topicNotificationCls}, {"edu/wpi/first/networktables/NetworkTableValue", &valueCls}, - {"edu/wpi/first/networktables/ValueNotification", &valueNotificationCls}}; + {"edu/wpi/first/networktables/ValueEventData", &valueEventDataCls}}; static const JExceptionInit exceptions[] = { {"java/lang/IllegalArgumentException", &illegalArgEx}, @@ -216,29 +213,12 @@ static jobject MakeJObject(JNIEnv* env, const nt::ConnectionInfo& info) { static_cast(info.protocol_version)); } -static jobject MakeJObject(JNIEnv* env, jobject inst, - const nt::ConnectionNotification& notification) { +static jobject MakeJObject(JNIEnv* env, const nt::LogMessage& msg) { static jmethodID constructor = env->GetMethodID( - connectionNotificationCls, "", - "(Ledu/wpi/first/networktables/NetworkTableInstance;IZLedu/wpi/first/" - "networktables/ConnectionInfo;)V"); - JLocal conn{env, MakeJObject(env, notification.conn)}; - return env->NewObject(connectionNotificationCls, constructor, inst, - static_cast(notification.listener), - static_cast(notification.connected), - conn.obj()); -} - -static jobject MakeJObject(JNIEnv* env, jobject inst, - const nt::LogMessage& msg) { - static jmethodID constructor = env->GetMethodID( - logMessageCls, "", - "(Ledu/wpi/first/networktables/NetworkTableInstance;IILjava/lang/" - "String;ILjava/lang/String;)V"); + logMessageCls, "", "(ILjava/lang/String;ILjava/lang/String;)V"); JLocal filename{env, MakeJString(env, msg.filename)}; JLocal message{env, MakeJString(env, msg.message)}; - return env->NewObject(logMessageCls, constructor, inst, - static_cast(msg.logger), + return env->NewObject(logMessageCls, constructor, static_cast(msg.level), filename.obj(), static_cast(msg.line), message.obj()); } @@ -257,28 +237,42 @@ static jobject MakeJObject(JNIEnv* env, jobject inst, } static jobject MakeJObject(JNIEnv* env, jobject inst, - const nt::TopicNotification& notification) { + const nt::ValueEventData& data) { static jmethodID constructor = - env->GetMethodID(topicNotificationCls, "", - "(ILedu/wpi/first/networktables/TopicInfo;I)V"); - JLocal info{env, MakeJObject(env, inst, notification.info)}; - return env->NewObject(topicNotificationCls, constructor, - static_cast(notification.listener), info.obj(), - static_cast(notification.flags)); + env->GetMethodID(valueEventDataCls, "", + "(Ledu/wpi/first/networktables/NetworkTableInstance;II" + "Ledu/wpi/first/networktables/NetworkTableValue;)V"); + JLocal value{env, MakeJValue(env, data.value)}; + return env->NewObject(valueEventDataCls, constructor, inst, + static_cast(data.topic), + static_cast(data.subentry), value.obj()); } -static jobject MakeJObject(JNIEnv* env, jobject inst, - const nt::ValueNotification& notification) { +static jobject MakeJObject(JNIEnv* env, jobject inst, const nt::Event& event) { static jmethodID constructor = - env->GetMethodID(valueNotificationCls, "", - "(Ledu/wpi/first/networktables/NetworkTableInstance;III" - "Ledu/wpi/first/networktables/NetworkTableValue;I)V"); - JLocal value{env, MakeJValue(env, notification.value)}; - return env->NewObject(valueNotificationCls, constructor, inst, - static_cast(notification.listener), - static_cast(notification.topic), - static_cast(notification.subentry), value.obj(), - static_cast(notification.flags)); + env->GetMethodID(eventCls, "", + "(Ledu/wpi/first/networktables/NetworkTableInstance;II" + "Ledu/wpi/first/networktables/ConnectionInfo;" + "Ledu/wpi/first/networktables/TopicInfo;" + "Ledu/wpi/first/networktables/ValueEventData;" + "Ledu/wpi/first/networktables/LogMessage;)V"); + JLocal connInfo{env, nullptr}; + JLocal topicInfo{env, nullptr}; + JLocal valueData{env, nullptr}; + JLocal logMessage{env, nullptr}; + if (auto v = event.GetConnectionInfo()) { + connInfo = JLocal{env, MakeJObject(env, *v)}; + } else if (auto v = event.GetTopicInfo()) { + topicInfo = JLocal{env, MakeJObject(env, inst, *v)}; + } else if (auto v = event.GetValueEventData()) { + valueData = JLocal{env, MakeJObject(env, inst, *v)}; + } else if (auto v = event.GetLogMessage()) { + logMessage = JLocal{env, MakeJObject(env, *v)}; + } + return env->NewObject(eventCls, constructor, inst, + static_cast(event.listener), + static_cast(event.flags), connInfo.obj(), + topicInfo.obj(), valueData.obj(), logMessage.obj()); } static jobjectArray MakeJObject(JNIEnv* env, std::span arr) { @@ -293,52 +287,9 @@ static jobjectArray MakeJObject(JNIEnv* env, std::span arr) { return jarr; } -static jobjectArray MakeJObject( - JNIEnv* env, jobject inst, - std::span arr) { - jobjectArray jarr = - env->NewObjectArray(arr.size(), connectionNotificationCls, nullptr); - if (!jarr) { - return nullptr; - } - for (size_t i = 0; i < arr.size(); ++i) { - JLocal elem{env, MakeJObject(env, inst, arr[i])}; - env->SetObjectArrayElement(jarr, i, elem.obj()); - } - return jarr; -} - static jobjectArray MakeJObject(JNIEnv* env, jobject inst, - std::span arr) { - jobjectArray jarr = env->NewObjectArray(arr.size(), logMessageCls, nullptr); - if (!jarr) { - return nullptr; - } - for (size_t i = 0; i < arr.size(); ++i) { - JLocal elem{env, MakeJObject(env, inst, arr[i])}; - env->SetObjectArrayElement(jarr, i, elem.obj()); - } - return jarr; -} - -static jobjectArray MakeJObject(JNIEnv* env, jobject inst, - std::span arr) { - jobjectArray jarr = - env->NewObjectArray(arr.size(), topicNotificationCls, nullptr); - if (!jarr) { - return nullptr; - } - for (size_t i = 0; i < arr.size(); ++i) { - JLocal elem{env, MakeJObject(env, inst, arr[i])}; - env->SetObjectArrayElement(jarr, i, elem.obj()); - } - return jarr; -} - -static jobjectArray MakeJObject(JNIEnv* env, jobject inst, - std::span arr) { - jobjectArray jarr = - env->NewObjectArray(arr.size(), valueNotificationCls, nullptr); + std::span arr) { + jobjectArray jarr = env->NewObjectArray(arr.size(), eventCls, nullptr); if (!jarr) { return nullptr; } @@ -1013,35 +964,35 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_getTopicInfo /* * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: createTopicListenerPoller + * Method: createListenerPoller * Signature: (I)I */ JNIEXPORT jint JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_createTopicListenerPoller +Java_edu_wpi_first_networktables_NetworkTablesJNI_createListenerPoller (JNIEnv*, jclass, jint inst) { - return nt::CreateTopicListenerPoller(inst); + return nt::CreateListenerPoller(inst); } /* * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: destroyTopicListenerPoller + * Method: destroyListenerPoller * Signature: (I)V */ JNIEXPORT void JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyTopicListenerPoller +Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyListenerPoller (JNIEnv*, jclass, jint poller) { - nt::DestroyTopicListenerPoller(poller); + nt::DestroyListenerPoller(poller); } /* * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: addPolledTopicListener + * Method: addListener * Signature: (I[Ljava/lang/Object;I)I */ JNIEXPORT jint JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledTopicListener__I_3Ljava_lang_String_2I +Java_edu_wpi_first_networktables_NetworkTablesJNI_addListener__I_3Ljava_lang_String_2I (JNIEnv* env, jclass, jint poller, jobjectArray prefixes, jint flags) { if (!prefixes) { @@ -1066,163 +1017,43 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledTopicListener__I_3Lja arrview.emplace_back(arr.back()); } - return nt::AddPolledTopicListener(poller, arrview, flags); + return nt::AddPolledListener(poller, arrview, flags); } /* * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: addPolledTopicListener + * Method: addListener * Signature: (III)I */ JNIEXPORT jint JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledTopicListener__III +Java_edu_wpi_first_networktables_NetworkTablesJNI_addListener__III (JNIEnv* env, jclass, jint poller, jint handle, jint flags) { - return nt::AddPolledTopicListener(poller, handle, flags); + return nt::AddPolledListener(poller, handle, flags); } /* * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: readTopicListenerQueue + * Method: readListenerQueue * Signature: (Ljava/lang/Object;I)[Ljava/lang/Object; */ JNIEXPORT jobjectArray JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_readTopicListenerQueue +Java_edu_wpi_first_networktables_NetworkTablesJNI_readListenerQueue (JNIEnv* env, jclass, jobject inst, jint poller) { - return MakeJObject(env, inst, nt::ReadTopicListenerQueue(poller)); + return MakeJObject(env, inst, nt::ReadListenerQueue(poller)); } /* * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: removeTopicListener + * Method: removeListener * Signature: (I)V */ JNIEXPORT void JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_removeTopicListener +Java_edu_wpi_first_networktables_NetworkTablesJNI_removeListener (JNIEnv*, jclass, jint topicListener) { - nt::RemoveTopicListener(topicListener); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: createValueListenerPoller - * Signature: (I)I - */ -JNIEXPORT jint JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_createValueListenerPoller - (JNIEnv*, jclass, jint inst) -{ - return nt::CreateValueListenerPoller(inst); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: destroyValueListenerPoller - * Signature: (I)V - */ -JNIEXPORT void JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyValueListenerPoller - (JNIEnv*, jclass, jint poller) -{ - nt::DestroyValueListenerPoller(poller); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: addPolledValueListener - * Signature: (III)I - */ -JNIEXPORT jint JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledValueListener - (JNIEnv* env, jclass, jint poller, jint topic, jint flags) -{ - return nt::AddPolledValueListener(poller, topic, flags); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: readValueListenerQueue - * Signature: (Ljava/lang/Object;I)[Ljava/lang/Object; - */ -JNIEXPORT jobjectArray JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_readValueListenerQueue - (JNIEnv* env, jclass, jobject inst, jint poller) -{ - return MakeJObject(env, inst, nt::ReadValueListenerQueue(poller)); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: removeValueListener - * Signature: (I)V - */ -JNIEXPORT void JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_removeValueListener - (JNIEnv*, jclass, jint topicListener) -{ - nt::RemoveValueListener(topicListener); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: createConnectionListenerPoller - * Signature: (I)I - */ -JNIEXPORT jint JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_createConnectionListenerPoller - (JNIEnv*, jclass, jint inst) -{ - return nt::CreateConnectionListenerPoller(inst); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: destroyConnectionListenerPoller - * Signature: (I)V - */ -JNIEXPORT void JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyConnectionListenerPoller - (JNIEnv*, jclass, jint poller) -{ - nt::DestroyConnectionListenerPoller(poller); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: addPolledConnectionListener - * Signature: (IZ)I - */ -JNIEXPORT jint JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledConnectionListener - (JNIEnv* env, jclass, jint poller, jboolean immediateNotify) -{ - return nt::AddPolledConnectionListener(poller, immediateNotify); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: readConnectionListenerQueue - * Signature: (Ljava/lang/Object;I)[Ljava/lang/Object; - */ -JNIEXPORT jobjectArray JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_readConnectionListenerQueue - (JNIEnv* env, jclass, jobject inst, jint poller) -{ - return MakeJObject(env, inst, nt::ReadConnectionListenerQueue(poller)); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: removeConnectionListener - * Signature: (I)V - */ -JNIEXPORT void JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_removeConnectionListener - (JNIEnv*, jclass, jint connListenerUid) -{ - nt::RemoveConnectionListener(connListenerUid); + nt::RemoveListener(topicListener); } /* @@ -1561,62 +1392,14 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_stopConnectionDataLog /* * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: createLoggerPoller - * Signature: (I)I - */ -JNIEXPORT jint JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_createLoggerPoller - (JNIEnv*, jclass, jint inst) -{ - return nt::CreateLoggerPoller(inst); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: destroyLoggerPoller - * Signature: (I)V - */ -JNIEXPORT void JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyLoggerPoller - (JNIEnv*, jclass, jint poller) -{ - nt::DestroyLoggerPoller(poller); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: addPolledLogger + * Method: addLogger * Signature: (III)I */ JNIEXPORT jint JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledLogger +Java_edu_wpi_first_networktables_NetworkTablesJNI_addLogger (JNIEnv*, jclass, jint poller, jint minLevel, jint maxLevel) { return nt::AddPolledLogger(poller, minLevel, maxLevel); } -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: readLoggerQueue - * Signature: (Ljava/lang/Object;I)[Ljava/lang/Object; - */ -JNIEXPORT jobjectArray JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_readLoggerQueue - (JNIEnv* env, jclass, jobject inst, jint poller) -{ - return MakeJObject(env, inst, nt::ReadLoggerQueue(poller)); -} - -/* - * Class: edu_wpi_first_networktables_NetworkTablesJNI - * Method: removeLogger - * Signature: (I)V - */ -JNIEXPORT void JNICALL -Java_edu_wpi_first_networktables_NetworkTablesJNI_removeLogger - (JNIEnv*, jclass, jint logger) -{ - nt::RemoveLogger(logger); -} - } // extern "C" diff --git a/ntcore/src/main/native/cpp/networktables/NetworkTableInstance.cpp b/ntcore/src/main/native/cpp/networktables/NetworkTableInstance.cpp index 79fb4d6540..47203b2084 100644 --- a/ntcore/src/main/native/cpp/networktables/NetworkTableInstance.cpp +++ b/ntcore/src/main/native/cpp/networktables/NetworkTableInstance.cpp @@ -101,16 +101,9 @@ void NetworkTableInstance::SetServer(std::span servers, SetServer(serversArr); } -NT_TopicListener NetworkTableInstance::AddTopicListener( - MultiSubscriber& subscriber, int eventMask, - std::function listener) { - return ::nt::AddTopicListener(subscriber.GetHandle(), eventMask, - std::move(listener)); -} - -NT_ValueListener NetworkTableInstance::AddValueListener( - MultiSubscriber& subscriber, unsigned int eventMask, - std::function listener) { - return ::nt::AddValueListener(subscriber.GetHandle(), eventMask, - std::move(listener)); +NT_Listener NetworkTableInstance::AddListener(MultiSubscriber& subscriber, + int eventMask, + ListenerCallback listener) { + return ::nt::AddListener(subscriber.GetHandle(), eventMask, + std::move(listener)); } diff --git a/ntcore/src/main/native/cpp/ntcore_c.cpp b/ntcore/src/main/native/cpp/ntcore_c.cpp index 7f28758332..e6d15300b8 100644 --- a/ntcore/src/main/native/cpp/ntcore_c.cpp +++ b/ntcore/src/main/native/cpp/ntcore_c.cpp @@ -37,35 +37,42 @@ static void ConvertToC(const ConnectionInfo& in, NT_ConnectionInfo* out) { out->protocol_version = in.protocol_version; } -static void ConvertToC(const TopicNotification& in, NT_TopicNotification* out) { - out->listener = in.listener; - ConvertToC(in.info, &out->info); - out->flags = in.flags; -} - -static void ConvertToC(const ValueNotification& in, NT_ValueNotification* out) { - out->listener = in.listener; +static void ConvertToC(const ValueEventData& in, NT_ValueEventData* out) { out->topic = in.topic; out->subentry = in.subentry; ConvertToC(in.value, &out->value); - out->flags = in.flags; -} - -static void ConvertToC(const ConnectionNotification& in, - NT_ConnectionNotification* out) { - out->listener = in.listener; - out->connected = in.connected; - ConvertToC(in.conn, &out->conn); } static void ConvertToC(const LogMessage& in, NT_LogMessage* out) { - out->logger = in.logger; out->level = in.level; ConvertToC(in.filename, &out->filename); out->line = in.line; ConvertToC(in.message, &out->message); } +static void ConvertToC(const Event& in, NT_Event* out) { + out->listener = in.listener; + out->flags = in.flags; + if ((in.flags & NT_EVENT_VALUE_ALL) != 0) { + if (auto v = in.GetValueEventData()) { + return ConvertToC(*v, &out->data.valueData); + } + } else if ((in.flags & NT_EVENT_TOPIC) != 0) { + if (auto v = in.GetTopicInfo()) { + return ConvertToC(*v, &out->data.topicInfo); + } + } else if ((in.flags & NT_EVENT_CONNECTION) != 0) { + if (auto v = in.GetConnectionInfo()) { + return ConvertToC(*v, &out->data.connInfo); + } + } else if ((in.flags & NT_EVENT_LOGMESSAGE) != 0) { + if (auto v = in.GetLogMessage()) { + return ConvertToC(*v, &out->data.logMessage); + } + } + out->flags = NT_EVENT_NONE; // sanity to make sure we don't dispose +} + static void DisposeConnectionInfo(NT_ConnectionInfo* info) { std::free(info->remote_id.str); std::free(info->remote_ip.str); @@ -77,16 +84,21 @@ static void DisposeTopicInfo(NT_TopicInfo* info) { std::free(info->properties.str); } -static void DisposeTopicNotification(NT_TopicNotification* info) { - DisposeTopicInfo(&info->info); +static void DisposeLogMessage(NT_LogMessage* msg) { + std::free(msg->filename); + std::free(msg->message); } -static void DisposeValueNotification(NT_ValueNotification* info) { - NT_DisposeValue(&info->value); -} - -static void DisposeConnectionNotification(NT_ConnectionNotification* info) { - DisposeConnectionInfo(&info->conn); +static void DisposeEvent(NT_Event* event) { + if ((event->flags & NT_EVENT_VALUE_ALL) != 0) { + NT_DisposeValue(&event->data.valueData.value); + } else if ((event->flags & NT_EVENT_TOPIC) != 0) { + DisposeTopicInfo(&event->data.topicInfo); + } else if ((event->flags & NT_EVENT_CONNECTION) != 0) { + DisposeConnectionInfo(&event->data.connInfo); + } else if ((event->flags & NT_EVENT_LOGMESSAGE) != 0) { + DisposeLogMessage(&event->data.logMessage); + } } extern "C" { @@ -383,169 +395,87 @@ NT_Topic NT_GetTopicFromHandle(NT_Handle pubsubentry) { * Callback Creation Functions */ -NT_TopicListener NT_AddTopicListener(NT_Inst inst, const char* prefix, - size_t prefix_len, unsigned int mask, - void* data, - NT_TopicListenerCallback callback) { +NT_ListenerPoller NT_CreateListenerPoller(NT_Inst inst) { + return nt::CreateListenerPoller(inst); +} + +void NT_DestroyListenerPoller(NT_ListenerPoller poller) { + nt::DestroyListenerPoller(poller); +} + +struct NT_Event* NT_ReadListenerQueue(NT_ListenerPoller poller, size_t* len) { + auto arr_cpp = nt::ReadListenerQueue(poller); + return ConvertToC(arr_cpp, len); +} + +void NT_RemoveListener(NT_Listener listener) { + nt::RemoveListener(listener); +} + +NT_Bool NT_WaitForListenerQueue(NT_Handle handle, double timeout) { + return nt::WaitForListenerQueue(handle, timeout); +} + +NT_Listener NT_AddListenerSingle(NT_Inst inst, const char* prefix, + size_t prefix_len, unsigned int mask, + void* data, NT_ListenerCallback callback) { std::string_view p{prefix, prefix_len}; - return nt::AddTopicListener(inst, {{p}}, mask, [=](auto& event) { - NT_TopicNotification event_c; + return nt::AddListener(inst, {{p}}, mask, [=](auto& event) { + NT_Event event_c; ConvertToC(event, &event_c); callback(data, &event_c); - DisposeTopicNotification(&event_c); + DisposeEvent(&event_c); }); } -NT_TopicListener NT_AddTopicListenerMultiple( - NT_Inst inst, const NT_String* prefixes, size_t prefixes_len, - unsigned int mask, void* data, NT_TopicListenerCallback callback) { +NT_Listener NT_AddListenerMultiple(NT_Inst inst, const NT_String* prefixes, + size_t prefixes_len, unsigned int mask, + void* data, NT_ListenerCallback callback) { wpi::SmallVector p; p.reserve(prefixes_len); for (size_t i = 0; i < prefixes_len; ++i) { p.emplace_back(prefixes[i].str, prefixes[i].len); } - return nt::AddTopicListener(inst, p, mask, [=](auto& event) { - NT_TopicNotification event_c; + return nt::AddListener(inst, p, mask, [=](auto& event) { + NT_Event event_c; ConvertToC(event, &event_c); callback(data, &event_c); - DisposeTopicNotification(&event_c); + DisposeEvent(&event_c); }); } -NT_TopicListener NT_AddTopicListenerSingle(NT_Topic topic, unsigned int mask, - void* data, - NT_TopicListenerCallback callback) { - return nt::AddTopicListener(topic, mask, [=](auto& event) { - NT_TopicNotification event_c; +NT_Listener NT_AddListener(NT_Topic topic, unsigned int mask, void* data, + NT_ListenerCallback callback) { + return nt::AddListener(topic, mask, [=](auto& event) { + NT_Event event_c; ConvertToC(event, &event_c); callback(data, &event_c); - DisposeTopicNotification(&event_c); + DisposeEvent(&event_c); }); } -NT_Bool NT_WaitForTopicListenerQueue(NT_Handle handle, double timeout) { - return nt::WaitForTopicListenerQueue(handle, timeout); -} - -NT_TopicListenerPoller NT_CreateTopicListenerPoller(NT_Inst inst) { - return nt::CreateTopicListenerPoller(inst); -} - -void NT_DestroyTopicListenerPoller(NT_TopicListenerPoller poller) { - nt::DestroyTopicListenerPoller(poller); -} - -NT_TopicListener NT_AddPolledTopicListener(NT_TopicListenerPoller poller, - const char* prefix, - size_t prefix_len, - unsigned int mask) { +NT_Listener NT_AddPolledListenerSingle(NT_ListenerPoller poller, + const char* prefix, size_t prefix_len, + unsigned int mask) { std::string_view p{prefix, prefix_len}; - return nt::AddPolledTopicListener(poller, {{p}}, mask); + return nt::AddPolledListener(poller, {{p}}, mask); } -NT_TopicListener NT_AddPolledTopicListenerMultiple( - NT_TopicListenerPoller poller, const NT_String* prefixes, - size_t prefixes_len, unsigned int mask) { +NT_Listener NT_AddPolledListenerMultiple(NT_ListenerPoller poller, + const NT_String* prefixes, + size_t prefixes_len, + unsigned int mask) { wpi::SmallVector p; p.reserve(prefixes_len); for (size_t i = 0; i < prefixes_len; ++i) { p.emplace_back(prefixes[i].str, prefixes[i].len); } - return nt::AddPolledTopicListener(poller, p, mask); + return nt::AddPolledListener(poller, p, mask); } -NT_TopicListener NT_AddPolledTopicListenerSingle(NT_TopicListenerPoller poller, - NT_Topic topic, - unsigned int mask) { - return nt::AddPolledTopicListener(poller, topic, mask); -} - -struct NT_TopicNotification* NT_ReadTopicListenerQueue( - NT_TopicListenerPoller poller, size_t* len) { - auto arr_cpp = nt::ReadTopicListenerQueue(poller); - return ConvertToC(arr_cpp, len); -} - -void NT_RemoveTopicListener(NT_TopicListener topic_listener) { - nt::RemoveTopicListener(topic_listener); -} - -NT_ValueListener NT_AddValueListener(NT_Handle subentry, unsigned int mask, - void* data, - NT_ValueListenerCallback callback) { - return nt::AddValueListener(subentry, mask, [=](auto& event) { - NT_ValueNotification event_c; - ConvertToC(event, &event_c); - callback(data, &event_c); - DisposeValueNotification(&event_c); - }); -} - -NT_Bool NT_WaitForValueListenerQueue(NT_Handle handle, double timeout) { - return nt::WaitForValueListenerQueue(handle, timeout); -} - -NT_ValueListenerPoller NT_CreateValueListenerPoller(NT_Inst inst) { - return nt::CreateValueListenerPoller(inst); -} - -void NT_DestroyValueListenerPoller(NT_ValueListenerPoller poller) { - nt::DestroyValueListenerPoller(poller); -} - -NT_ValueListener NT_AddPolledValueListener(NT_ValueListenerPoller poller, - NT_Handle subentry, - unsigned int mask) { - return nt::AddPolledValueListener(poller, subentry, mask); -} - -struct NT_ValueNotification* NT_ReadValueListenerQueue( - NT_ValueListenerPoller poller, size_t* len) { - auto arr_cpp = nt::ReadValueListenerQueue(poller); - return ConvertToC(arr_cpp, len); -} - -void NT_RemoveValueListener(NT_ValueListener value_listener) { - nt::RemoveValueListener(value_listener); -} - -NT_ConnectionListener NT_AddConnectionListener( - NT_Inst inst, NT_Bool immediate_notify, void* data, - NT_ConnectionListenerCallback callback) { - return nt::AddConnectionListener(inst, immediate_notify != 0, - [=](const ConnectionNotification& event) { - NT_ConnectionNotification event_c; - ConvertToC(event, &event_c); - callback(data, &event_c); - DisposeConnectionNotification(&event_c); - }); -} - -NT_Bool NT_WaitForConnectionListenerQueue(NT_Handle handle, double timeout) { - return nt::WaitForConnectionListenerQueue(handle, timeout); -} - -NT_ConnectionListenerPoller NT_CreateConnectionListenerPoller(NT_Inst inst) { - return nt::CreateConnectionListenerPoller(inst); -} - -void NT_DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller) { - nt::DestroyConnectionListenerPoller(poller); -} - -NT_ConnectionListener NT_AddPolledConnectionListener( - NT_ConnectionListenerPoller poller, NT_Bool immediate_notify) { - return nt::AddPolledConnectionListener(poller, immediate_notify); -} - -struct NT_ConnectionNotification* NT_ReadConnectionListenerQueue( - NT_ConnectionListenerPoller poller, size_t* len) { - auto arr_cpp = nt::ReadConnectionListenerQueue(poller); - return ConvertToC(arr_cpp, len); -} - -void NT_RemoveConnectionListener(NT_ConnectionListener conn_listener) { - nt::RemoveConnectionListener(conn_listener); +NT_Listener NT_AddPolledListener(NT_ListenerPoller poller, NT_Topic topic, + unsigned int mask) { + return nt::AddPolledListener(poller, topic, mask); } /* @@ -641,41 +571,22 @@ void NT_SetNow(uint64_t timestamp) { nt::SetNow(timestamp); } -NT_Logger NT_AddLogger(NT_Inst inst, void* data, NT_LogFunc func, - unsigned int min_level, unsigned int max_level) { - return nt::AddLogger( - inst, - [=](const LogMessage& msg) { - NT_LogMessage msg_c; - ConvertToC(msg, &msg_c); - func(data, &msg_c); - NT_DisposeLogMessage(&msg_c); - }, - min_level, max_level); +NT_Listener NT_AddLogger(NT_Inst inst, unsigned int min_level, + unsigned int max_level, void* data, + NT_ListenerCallback func) { + return nt::AddLogger(inst, min_level, max_level, [=](auto& event) { + NT_Event event_c; + ConvertToC(event, &event_c); + func(data, &event_c); + NT_DisposeEvent(&event_c); + }); } -NT_LoggerPoller NT_CreateLoggerPoller(NT_Inst inst) { - return nt::CreateLoggerPoller(inst); -} - -void NT_DestroyLoggerPoller(NT_LoggerPoller poller) { - nt::DestroyLoggerPoller(poller); -} - -NT_Logger NT_AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level, - unsigned int max_level) { +NT_Listener NT_AddPolledLogger(NT_ListenerPoller poller, unsigned int min_level, + unsigned int max_level) { return nt::AddPolledLogger(poller, min_level, max_level); } -struct NT_LogMessage* NT_ReadLoggerQueue(NT_LoggerPoller poller, size_t* len) { - auto arr_cpp = nt::ReadLoggerQueue(poller); - return ConvertToC(arr_cpp, len); -} - -void NT_RemoveLogger(NT_Logger logger) { - nt::RemoveLogger(logger); -} - void NT_DisposeValue(NT_Value* value) { switch (value->type) { case NT_UNASSIGNED: @@ -759,50 +670,15 @@ void NT_DisposeTopicInfo(NT_TopicInfo* info) { DisposeTopicInfo(info); } -void NT_DisposeTopicNotificationArray(NT_TopicNotification* arr, size_t count) { +void NT_DisposeEventArray(NT_Event* arr, size_t count) { for (size_t i = 0; i < count; i++) { - DisposeTopicNotification(&arr[i]); + DisposeEvent(&arr[i]); } std::free(arr); } -void NT_DisposeTopicNotification(NT_TopicNotification* info) { - DisposeTopicNotification(info); -} - -void NT_DisposeValueNotificationArray(NT_ValueNotification* arr, size_t count) { - for (size_t i = 0; i < count; i++) { - DisposeValueNotification(&arr[i]); - } - std::free(arr); -} - -void NT_DisposeValueNotification(NT_ValueNotification* info) { - DisposeValueNotification(info); -} - -void NT_DisposeConnectionNotificationArray(NT_ConnectionNotification* arr, - size_t count) { - for (size_t i = 0; i < count; i++) { - DisposeConnectionNotification(&arr[i]); - } - std::free(arr); -} - -void NT_DisposeConnectionNotification(NT_ConnectionNotification* info) { - DisposeConnectionNotification(info); -} - -void NT_DisposeLogMessageArray(NT_LogMessage* arr, size_t count) { - for (size_t i = 0; i < count; i++) { - NT_DisposeLogMessage(&arr[i]); - } - std::free(arr); -} - -void NT_DisposeLogMessage(NT_LogMessage* info) { - std::free(info->filename); - std::free(info->message); +void NT_DisposeEvent(NT_Event* event) { + DisposeEvent(event); } /* Interop Utility Functions */ diff --git a/ntcore/src/main/native/cpp/ntcore_cpp.cpp b/ntcore/src/main/native/cpp/ntcore_cpp.cpp index 5f12f4269b..b14862eda8 100644 --- a/ntcore/src/main/native/cpp/ntcore_cpp.cpp +++ b/ntcore/src/main/native/cpp/ntcore_cpp.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -17,6 +18,7 @@ #include "Log.h" #include "Types_internal.h" #include "ntcore.h" +#include "ntcore_c.h" static std::atomic_bool gNowSet{false}; static std::atomic gNowTime; @@ -54,7 +56,7 @@ void DestroyInstance(NT_Inst inst) { NT_Inst GetInstanceFromHandle(NT_Handle handle) { Handle h{handle}; auto type = h.GetType(); - if (type >= Handle::kConnectionListener && type < Handle::kTypeMax) { + if (type >= Handle::kListener && type < Handle::kTypeMax) { return Handle(h.GetInst(), 0, Handle::kInstance); } @@ -386,200 +388,123 @@ void UnsubscribeMultiple(NT_MultiSubscriber sub) { * Callback Creation Functions */ -NT_TopicListener AddTopicListener( - NT_Inst inst, std::span prefixes, unsigned int mask, - std::function callback) { +static void CleanupListeners( + InstanceImpl& ii, + std::span> listeners) { + bool updateMinLevel = false; + for (auto&& [listener, mask] : listeners) { + // connection doesn't need removal notification + if ((mask & (NT_EVENT_TOPIC | NT_EVENT_VALUE_ALL)) != 0) { + ii.localStorage.RemoveListener(listener, mask); + } + if ((mask & NT_EVENT_LOGMESSAGE) != 0) { + ii.logger_impl.RemoveListener(listener); + updateMinLevel = true; + } + } + if (updateMinLevel) { + ii.logger.set_min_level(ii.logger_impl.GetMinLevel()); + } +} + +static void DoAddListener(InstanceImpl& ii, NT_Listener listener, + NT_Handle handle, unsigned int mask) { + if (Handle{handle}.IsType(Handle::kInstance)) { + if ((mask & NT_EVENT_CONNECTION) != 0) { + ii.connectionList.AddListener(listener, mask); + } + if ((mask & NT_EVENT_LOGMESSAGE) != 0) { + ii.logger_impl.AddListener(listener, NT_LOG_INFO, UINT_MAX); + } + } else if ((mask & (NT_EVENT_TOPIC | NT_EVENT_VALUE_ALL)) != 0) { + ii.localStorage.AddListener(listener, handle, mask); + } +} + +NT_ListenerPoller CreateListenerPoller(NT_Inst inst) { if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) { - return ii->localStorage.AddTopicListener(prefixes, mask, - std::move(callback)); + return ii->listenerStorage.CreateListenerPoller(); } else { return {}; } } -NT_TopicListener AddTopicListener( - NT_Handle handle, unsigned int mask, - std::function callback) { - if (auto ii = InstanceImpl::GetTyped(handle, Handle::kTopic)) { - return ii->localStorage.AddTopicListener(handle, mask, std::move(callback)); +void DestroyListenerPoller(NT_ListenerPoller poller) { + if (auto ii = InstanceImpl::GetTyped(poller, Handle::kListenerPoller)) { + CleanupListeners(*ii, ii->listenerStorage.DestroyListenerPoller(poller)); + } +} + +std::vector ReadListenerQueue(NT_ListenerPoller poller) { + if (auto ii = InstanceImpl::GetTyped(poller, Handle::kListenerPoller)) { + return ii->listenerStorage.ReadListenerQueue(poller); } else { return {}; } } -bool WaitForTopicListenerQueue(NT_Handle handle, double timeout) { +void RemoveListener(NT_Listener listener) { + if (auto ii = InstanceImpl::GetTyped(listener, Handle::kListener)) { + CleanupListeners(*ii, ii->listenerStorage.RemoveListener(listener)); + } +} + +bool WaitForListenerQueue(NT_Handle handle, double timeout) { if (auto ii = InstanceImpl::GetHandle(handle)) { - return ii->localStorage.WaitForTopicListenerQueue(timeout); + return ii->listenerStorage.WaitForListenerQueue(timeout); } else { return {}; } } -NT_TopicListenerPoller CreateTopicListenerPoller(NT_Inst inst) { +NT_Listener AddListener(NT_Inst inst, + std::span prefixes, + unsigned int mask, ListenerCallback callback) { if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) { - return ii->localStorage.CreateTopicListenerPoller(); - } else { - return {}; + if ((mask & (NT_EVENT_TOPIC | NT_EVENT_VALUE_ALL)) != 0) { + auto listener = ii->listenerStorage.AddListener(std::move(callback)); + ii->localStorage.AddListener(listener, prefixes, mask); + return listener; + } } + return {}; } -void DestroyTopicListenerPoller(NT_TopicListenerPoller poller) { - if (auto ii = InstanceImpl::GetTyped(poller, Handle::kTopicListenerPoller)) { - ii->localStorage.DestroyTopicListenerPoller(poller); - } -} - -NT_TopicListener AddPolledTopicListener( - NT_TopicListenerPoller poller, std::span prefixes, - unsigned int mask) { - if (auto ii = InstanceImpl::GetTyped(poller, Handle::kTopicListenerPoller)) { - return ii->localStorage.AddPolledTopicListener(poller, prefixes, mask); - } else { - return {}; - } -} - -NT_TopicListener AddPolledTopicListener(NT_TopicListenerPoller poller, - NT_Handle handle, unsigned int mask) { - if (auto ii = InstanceImpl::GetTyped(poller, Handle::kTopicListenerPoller)) { - return ii->localStorage.AddPolledTopicListener(poller, handle, mask); - } else { - return {}; - } -} - -std::vector ReadTopicListenerQueue( - NT_TopicListenerPoller poller) { - if (auto ii = InstanceImpl::GetTyped(poller, Handle::kTopicListenerPoller)) { - return ii->localStorage.ReadTopicListenerQueue(poller); - } else { - return {}; - } -} - -void RemoveTopicListener(NT_TopicListener listener) { - if (auto ii = InstanceImpl::GetTyped(listener, Handle::kTopicListener)) { - return ii->localStorage.RemoveTopicListener(listener); - } -} - -NT_ValueListener AddValueListener( - NT_Handle subentry, unsigned int mask, - std::function callback) { - if (auto ii = InstanceImpl::GetHandle(subentry)) { - return ii->localStorage.AddValueListener(subentry, mask, - std::move(callback)); - } else { - return {}; - } -} - -bool WaitForValueListenerQueue(NT_Handle handle, double timeout) { +NT_Listener AddListener(NT_Handle handle, unsigned int mask, + ListenerCallback callback) { if (auto ii = InstanceImpl::GetHandle(handle)) { - return ii->localStorage.WaitForValueListenerQueue(timeout); + auto listener = ii->listenerStorage.AddListener(std::move(callback)); + DoAddListener(*ii, listener, handle, mask); + return listener; } else { return {}; } } -NT_ValueListenerPoller CreateValueListenerPoller(NT_Inst inst) { - if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) { - return ii->localStorage.CreateValueListenerPoller(); +NT_Listener AddPolledListener(NT_ListenerPoller poller, + std::span prefixes, + unsigned int mask) { + if (auto ii = InstanceImpl::GetTyped(poller, Handle::kListenerPoller)) { + if ((mask & (NT_EVENT_TOPIC | NT_EVENT_VALUE_ALL)) != 0) { + auto listener = ii->listenerStorage.AddListener(poller); + ii->localStorage.AddListener(listener, prefixes, mask); + return listener; + } + } + return {}; +} + +NT_Listener AddPolledListener(NT_ListenerPoller poller, NT_Handle handle, + unsigned int mask) { + if (auto ii = InstanceImpl::GetTyped(poller, Handle::kListenerPoller)) { + auto listener = ii->listenerStorage.AddListener(poller); + DoAddListener(*ii, listener, handle, mask); + return listener; } else { return {}; } } -void DestroyValueListenerPoller(NT_ValueListenerPoller poller) { - if (auto ii = InstanceImpl::GetTyped(poller, Handle::kValueListenerPoller)) { - ii->localStorage.DestroyValueListenerPoller(poller); - } -} - -NT_ValueListener AddPolledValueListener(NT_ValueListenerPoller poller, - NT_Handle subentry, unsigned int mask) { - if (auto ii = InstanceImpl::GetTyped(poller, Handle::kValueListenerPoller)) { - return ii->localStorage.AddPolledValueListener(poller, subentry, mask); - } else { - return {}; - } -} - -std::vector ReadValueListenerQueue( - NT_ValueListenerPoller poller) { - if (auto ii = InstanceImpl::GetTyped(poller, Handle::kValueListenerPoller)) { - return ii->localStorage.ReadValueListenerQueue(poller); - } else { - return {}; - } -} - -void RemoveValueListener(NT_ValueListener listener) { - if (auto ii = InstanceImpl::GetTyped(listener, Handle::kValueListener)) { - return ii->localStorage.RemoveValueListener(listener); - } -} - -NT_ConnectionListener AddConnectionListener( - NT_Inst inst, bool immediate_notify, - std::function callback) { - if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) { - return ii->connectionList.AddListener(immediate_notify, - std::move(callback)); - } else { - return {}; - } -} - -bool WaitForConnectionListenerQueue(NT_Handle handle, double timeout) { - if (auto ii = InstanceImpl::GetHandle(handle)) { - return ii->connectionList.WaitForListenerQueue(timeout); - } else { - return {}; - } -} - -NT_ConnectionListenerPoller CreateConnectionListenerPoller(NT_Inst inst) { - if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) { - return ii->connectionList.CreateListenerPoller(); - } else { - return {}; - } -} - -void DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller) { - if (auto ii = - InstanceImpl::GetTyped(poller, Handle::kConnectionListenerPoller)) { - return ii->connectionList.DestroyListenerPoller(poller); - } -} - -NT_ConnectionListener AddPolledConnectionListener( - NT_ConnectionListenerPoller poller, bool immediate_notify) { - if (auto ii = - InstanceImpl::GetTyped(poller, Handle::kConnectionListenerPoller)) { - return ii->connectionList.AddPolledListener(poller, immediate_notify); - } else { - return {}; - } -} - -std::vector ReadConnectionListenerQueue( - NT_ConnectionListenerPoller poller) { - if (auto ii = - InstanceImpl::GetTyped(poller, Handle::kConnectionListenerPoller)) { - return ii->connectionList.ReadListenerQueue(poller); - } else { - return {}; - } -} - -void RemoveConnectionListener(NT_ConnectionListener listener) { - if (auto ii = InstanceImpl::GetTyped(listener, Handle::kConnectionListener)) { - return ii->connectionList.RemoveListener(listener); - } -} - int64_t Now() { if (gNowSet) { return gNowTime; @@ -796,58 +721,32 @@ bool IsConnected(NT_Inst inst) { } } -NT_Logger AddLogger(NT_Inst inst, - std::function func, - unsigned int minLevel, unsigned int maxLevel) { +NT_Listener AddLogger(NT_Inst inst, unsigned int minLevel, + unsigned int maxLevel, ListenerCallback func) { if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) { if (minLevel < ii->logger.min_level()) { ii->logger.set_min_level(minLevel); } - return ii->logger_impl.Add(std::move(func), minLevel, maxLevel); + auto listener = ii->listenerStorage.AddListener(std::move(func)); + ii->logger_impl.AddListener(listener, minLevel, maxLevel); + return listener; } else { return {}; } } -NT_LoggerPoller CreateLoggerPoller(NT_Inst inst) { - if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) { - return ii->logger_impl.CreatePoller(); - } else { - return {}; - } -} - -void DestroyLoggerPoller(NT_LoggerPoller poller) { - if (auto ii = InstanceImpl::GetTyped(poller, Handle::kLoggerPoller)) { - ii->logger_impl.DestroyPoller(poller); - } -} - -NT_Logger AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level, - unsigned int max_level) { - if (auto ii = InstanceImpl::GetTyped(poller, Handle::kLoggerPoller)) { - if (min_level < ii->logger.min_level()) { - ii->logger.set_min_level(min_level); +NT_Listener AddPolledLogger(NT_ListenerPoller poller, unsigned int minLevel, + unsigned int maxLevel) { + if (auto ii = InstanceImpl::GetTyped(poller, Handle::kListenerPoller)) { + if (minLevel < ii->logger.min_level()) { + ii->logger.set_min_level(minLevel); } - return ii->logger_impl.AddPolled(poller, min_level, max_level); + auto listener = ii->listenerStorage.AddListener(poller); + ii->logger_impl.AddListener(listener, minLevel, maxLevel); + return listener; } else { return {}; } } -std::vector ReadLoggerQueue(NT_LoggerPoller poller) { - if (auto ii = InstanceImpl::GetTyped(poller, Handle::kLoggerPoller)) { - return ii->logger_impl.ReadQueue(poller); - } else { - return {}; - } -} - -void RemoveLogger(NT_Logger logger) { - if (auto ii = InstanceImpl::GetTyped(logger, Handle::kLogger)) { - ii->logger_impl.Remove(logger); - ii->logger.set_min_level(ii->logger_impl.GetMinLevel()); - } -} - } // namespace nt diff --git a/ntcore/src/main/native/include/networktables/ConnectionListener.h b/ntcore/src/main/native/include/networktables/ConnectionListener.h deleted file mode 100644 index 9f0144ef23..0000000000 --- a/ntcore/src/main/native/include/networktables/ConnectionListener.h +++ /dev/null @@ -1,128 +0,0 @@ -// 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. - -#pragma once - -#include -#include - -#include "ntcore_cpp.h" - -namespace nt { - -class NetworkTableInstance; - -/** - * Connection listener. This calls back to a callback function when a connection - * change occurs. The callback function is called asynchronously on a separate - * thread, so it's important to use synchronization or atomics when accessing - * any shared state from the callback function. - */ -class ConnectionListener final { - public: - ConnectionListener() = default; - - /** - * Create a listener for connection changes. - * - * @param inst Instance - * @param immediateNotify if notification should be immediately created for - * existing connections - * @param listener Listener function - */ - ConnectionListener( - NetworkTableInstance inst, bool immediateNotify, - std::function listener); - - ConnectionListener(const ConnectionListener&) = delete; - ConnectionListener& operator=(const ConnectionListener&) = delete; - ConnectionListener(ConnectionListener&& rhs); - ConnectionListener& operator=(ConnectionListener&& rhs); - ~ConnectionListener(); - - explicit operator bool() const { return m_handle != 0; } - - /** - * Gets the native handle. - * - * @return Handle - */ - NT_ConnectionListener GetHandle() const { return m_handle; } - - /** - * Wait for the connection listener queue to be empty. This is primarily - * useful for deterministic testing. This blocks until either the connection - * listener queue is empty (e.g. there are no more events that need to be - * passed along to callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or - * a negative value to block indefinitely - * @return False if timed out, otherwise true. - */ - bool WaitForQueue(double timeout); - - private: - NT_ConnectionListener m_handle{0}; -}; - -/** - * A connection listener. This queues connection notifications. Code using - * the listener must periodically call readQueue() to read the notifications. - */ -class ConnectionListenerPoller final { - public: - ConnectionListenerPoller() = default; - - /** - * Construct a connection listener poller. - * - * @param inst Instance - */ - explicit ConnectionListenerPoller(NetworkTableInstance inst); - - ConnectionListenerPoller(const ConnectionListenerPoller&) = delete; - ConnectionListenerPoller& operator=(const ConnectionListenerPoller&) = delete; - ConnectionListenerPoller(ConnectionListenerPoller&& rhs); - ConnectionListenerPoller& operator=(ConnectionListenerPoller&& rhs); - ~ConnectionListenerPoller(); - - explicit operator bool() const { return m_handle != 0; } - - /** - * Gets the native handle. - * - * @return Handle - */ - NT_ConnectionListenerPoller GetHandle() const { return m_handle; } - - /** - * Create a connection listener. - * - * @param immediateNotify if notification should be immediately created for - * existing connections - * @return Listener handle - */ - NT_ConnectionListener Add(bool immediateNotify); - - /** - * Remove a connection listener. - * - * @param listener Listener handle - */ - void Remove(NT_ConnectionListener listener); - - /** - * Read connection notifications. - * - * @return Connection notifications since the previous call to readQueue() - */ - std::vector ReadQueue(); - - private: - NT_ConnectionListenerPoller m_handle{0}; -}; - -} // namespace nt - -#include "ConnectionListener.inc" diff --git a/ntcore/src/main/native/include/networktables/ConnectionListener.inc b/ntcore/src/main/native/include/networktables/ConnectionListener.inc deleted file mode 100644 index 1e7395ce01..0000000000 --- a/ntcore/src/main/native/include/networktables/ConnectionListener.inc +++ /dev/null @@ -1,83 +0,0 @@ -// 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. - -#pragma once - -#include -#include - -#include "networktables/ConnectionListener.h" -#include "networktables/NetworkTableInstance.h" -#include "ntcore_cpp.h" - -namespace nt { - -inline ConnectionListener::ConnectionListener( - NetworkTableInstance inst, bool immediateNotify, - std::function listener) - : m_handle{ - AddConnectionListener(inst.GetHandle(), immediateNotify, listener)} {} - -inline ConnectionListener::ConnectionListener(ConnectionListener&& rhs) - : m_handle(rhs.m_handle) { - rhs.m_handle = 0; -} - -inline ConnectionListener& ConnectionListener::operator=( - ConnectionListener&& rhs) { - std::swap(m_handle, rhs.m_handle); - return *this; -} - -inline ConnectionListener::~ConnectionListener() { - if (m_handle != 0) { - nt::RemoveConnectionListener(m_handle); - } -} - -inline bool ConnectionListener::WaitForQueue(double timeout) { - if (m_handle != 0) { - return nt::WaitForConnectionListenerQueue(m_handle, timeout); - } else { - return false; - } -} - -inline ConnectionListenerPoller::ConnectionListenerPoller( - NetworkTableInstance inst) - : m_handle(nt::CreateConnectionListenerPoller(inst.GetHandle())) {} - -inline ConnectionListenerPoller::ConnectionListenerPoller( - ConnectionListenerPoller&& rhs) - : m_handle(rhs.m_handle) { - rhs.m_handle = 0; -} - -inline ConnectionListenerPoller& ConnectionListenerPoller::operator=( - ConnectionListenerPoller&& rhs) { - std::swap(m_handle, rhs.m_handle); - return *this; -} - -inline ConnectionListenerPoller::~ConnectionListenerPoller() { - if (m_handle != 0) { - nt::DestroyConnectionListenerPoller(m_handle); - } -} - -inline NT_ConnectionListener ConnectionListenerPoller::Add( - bool immediateNotify) { - return nt::AddPolledConnectionListener(m_handle, immediateNotify); -} - -inline void ConnectionListenerPoller::Remove(NT_ConnectionListener listener) { - nt::RemoveConnectionListener(listener); -} - -inline std::vector -ConnectionListenerPoller::ReadQueue() { - return nt::ReadConnectionListenerQueue(m_handle); -} - -} // namespace nt diff --git a/ntcore/src/main/native/include/networktables/NetworkTableInstance.h b/ntcore/src/main/native/include/networktables/NetworkTableInstance.h index 83bd4c0923..7c9ed6e810 100644 --- a/ntcore/src/main/native/include/networktables/NetworkTableInstance.h +++ b/ntcore/src/main/native/include/networktables/NetworkTableInstance.h @@ -357,9 +357,28 @@ class NetworkTableInstance final { /** * @{ - * @name Connection Listener Functions + * @name Listener Functions */ + /** + * Remove a listener. + * + * @param listener Listener handle to remove + */ + static void RemoveListener(NT_Listener listener); + + /** + * Wait for the listener queue to be empty. This is primarily + * useful for deterministic testing. This blocks until either the + * listener queue is empty (e.g. there are no more events that need to be + * passed along to callbacks or poll queues) or the timeout expires. + * + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or + * a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ + bool WaitForListenerQueue(double timeout); + /** * Add a connection listener. The callback function is called asynchronously * on a separate thread, so it's important to use synchronization or atomics @@ -369,200 +388,84 @@ class NetworkTableInstance final { * @param callback listener to add * @return Listener handle */ - NT_ConnectionListener AddConnectionListener( - bool immediate_notify, - std::function callback) const; + NT_Listener AddConnectionListener(bool immediate_notify, + ListenerCallback callback) const; /** - * Remove a connection listener. - * - * @param conn_listener Listener handle to remove - */ - static void RemoveConnectionListener(NT_ConnectionListener conn_listener); - - /** - * Wait for the connection listener queue to be empty. This is primarily - * useful for deterministic testing. This blocks until either the connection - * listener queue is empty (e.g. there are no more events that need to be - * passed along to callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or - * a negative value to block indefinitely - * @return False if timed out, otherwise true. - */ - bool WaitForConnectionListenerQueue(double timeout); - - /** @} */ - - /** - * @{ - * @name Topic Listener Functions - */ - - /** - * Add a topic listener for changes on a particular topic. The callback + * Add a listener for changes on a particular topic. The callback * function is called asynchronously on a separate thread, so it's important * to use synchronization or atomics when accessing any shared state from the * callback function. * + * This creates a corresponding internal subscriber with the lifetime of the + * listener. + * * @param topic Topic - * @param eventMask Bitmask of TopicListenerFlags values + * @param eventMask Bitmask of EventFlags values * @param listener Listener function * @return Listener handle */ - NT_TopicListener AddTopicListener( - Topic topic, unsigned int eventMask, - std::function listener); + NT_Listener AddListener(Topic topic, unsigned int eventMask, + ListenerCallback listener); /** - * Add a topic listener for topic changes on a subscriber. The callback + * Add a listener for changes on a subscriber. The callback * function is called asynchronously on a separate thread, so it's important * to use synchronization or atomics when accessing any shared state from the * callback function. This does NOT keep the subscriber active. * * @param subscriber Subscriber - * @param eventMask Bitmask of TopicListenerFlags values + * @param eventMask Bitmask of EventFlags values * @param listener Listener function * @return Listener handle */ - NT_TopicListener AddTopicListener( - Subscriber& subscriber, unsigned int eventMask, - std::function listener); + NT_Listener AddListener(Subscriber& subscriber, unsigned int eventMask, + ListenerCallback listener); /** - * Add a topic listener for topic changes on a subscriber. The callback + * Add a listener for changes on a subscriber. The callback * function is called asynchronously on a separate thread, so it's important * to use synchronization or atomics when accessing any shared state from the * callback function. This does NOT keep the subscriber active. * * @param subscriber Subscriber - * @param eventMask Bitmask of TopicListenerFlags values + * @param eventMask Bitmask of EventFlags values * @param listener Listener function * @return Listener handle */ - NT_TopicListener AddTopicListener( - MultiSubscriber& subscriber, int eventMask, - std::function listener); + NT_Listener AddListener(MultiSubscriber& subscriber, int eventMask, + ListenerCallback listener); /** - * Add a topic listener for topic changes on an entry. The callback function + * Add a listener for changes on an entry. The callback function * is called asynchronously on a separate thread, so it's important to use * synchronization or atomics when accessing any shared state from the * callback function. * * @param entry Entry - * @param eventMask Bitmask of TopicListenerFlags values + * @param eventMask Bitmask of EventFlags values * @param listener Listener function * @return Listener handle */ - NT_TopicListener AddTopicListener( - NetworkTableEntry& entry, int eventMask, - std::function listener); + NT_Listener AddListener(NetworkTableEntry& entry, int eventMask, + ListenerCallback listener); /** - * Add a topic listener for changes to topics with names that start with any + * Add a listener for changes to topics with names that start with any * of the given prefixes. The callback function is called asynchronously on a * separate thread, so it's important to use synchronization or atomics when * accessing any shared state from the callback function. * + * This creates a corresponding internal subscriber with the lifetime of the + * listener. + * * @param prefixes Topic name string prefixes - * @param eventMask Bitmask of TopicListenerFlags values + * @param eventMask Bitmask of EventFlags values * @param listener Listener function * @return Listener handle */ - NT_TopicListener AddTopicListener( - std::span prefixes, int eventMask, - std::function listener); - - /** - * Remove a topic listener. - * - * @param listener Listener handle to remove - */ - static void RemoveTopicListener(NT_TopicListener listener); - - /** - * Wait for the topic listener queue to be empty. This is primarily useful for - * deterministic testing. This blocks until either the topic listener queue is - * empty (e.g. there are no more events that need to be passed along to - * callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or - * a negative value to block indefinitely - * @return False if timed out, otherwise true. - */ - bool WaitForTopicListenerQueue(double timeout); - - /** @} */ - - /** - * @{ - * @name Value Listener Functions - */ - - /** - * Add a value listener for value changes on a subscriber. The callback - * function is called asynchronously on a separate thread, so it's important - * to use synchronization or atomics when accessing any shared state from the - * callback function. This does NOT keep the subscriber active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of ValueListenerFlags values - * @param listener Listener function - * @return Listener handle - */ - NT_ValueListener AddValueListener( - Subscriber& subscriber, unsigned int eventMask, - std::function listener); - - /** - * Add a value listener for value changes on a subscriber. The callback - * function is called asynchronously on a separate thread, so it's important - * to use synchronization or atomics when accessing any shared state from the - * callback function. This does NOT keep the subscriber active. - * - * @param subscriber Subscriber - * @param eventMask Bitmask of ValueListenerFlags values - * @param listener Listener function - * @return Listener handle - */ - NT_ValueListener AddValueListener( - MultiSubscriber& subscriber, unsigned int eventMask, - std::function listener); - - /** - * Add a value listener for value changes on an entry. The callback function - * is called asynchronously on a separate thread, so it's important to use - * synchronization or atomics when accessing any shared state from the - * callback function. - * - * @param entry Entry - * @param eventMask Bitmask of ValueListenerFlags values - * @param listener Listener function - * @return Listener handle - */ - NT_ValueListener AddValueListener( - NetworkTableEntry& entry, int eventMask, - std::function listener); - - /** - * Remove a value listener. - * - * @param listener Listener handle to remove - */ - static void RemoveValueListener(NT_ValueListener listener); - - /** - * Wait for the value listener queue to be empty. This is primarily useful for - * deterministic testing. This blocks until either the value listener queue is - * empty (e.g. there are no more events that need to be passed along to - * callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or - * a negative value to block indefinitely - * @return False if timed out, otherwise true. - */ - bool WaitForValueListenerQueue(double timeout); + NT_Listener AddListener(std::span prefixes, + int eventMask, ListenerCallback listener); /** @} */ @@ -771,20 +674,13 @@ class NetworkTableInstance final { * log messages with level greater than or equal to minLevel and less than or * equal to maxLevel; messages outside this range will be silently ignored. * - * @param func log callback function * @param minLevel minimum log level * @param maxLevel maximum log level - * @return Logger handle + * @param func callback function + * @return Listener handle */ - NT_Logger AddLogger(std::function func, - unsigned int minLevel, unsigned int maxLevel); - - /** - * Remove a logger. - * - * @param logger Logger handle to remove - */ - static void RemoveLogger(NT_Logger logger); + NT_Listener AddLogger(unsigned int minLevel, unsigned int maxLevel, + ListenerCallback func); /** @} */ diff --git a/ntcore/src/main/native/include/networktables/NetworkTableInstance.inc b/ntcore/src/main/native/include/networktables/NetworkTableInstance.inc index 7485da89ff..361b8eee97 100644 --- a/ntcore/src/main/native/include/networktables/NetworkTableInstance.inc +++ b/ntcore/src/main/native/include/networktables/NetworkTableInstance.inc @@ -10,6 +10,7 @@ #include "networktables/NetworkTableInstance.h" #include "networktables/Topic.h" +#include "ntcore_c.h" namespace nt { @@ -82,81 +83,42 @@ inline NetworkTableEntry NetworkTableInstance::GetEntry(std::string_view name) { return NetworkTableEntry{::nt::GetEntry(m_handle, name)}; } -inline NT_ConnectionListener NetworkTableInstance::AddConnectionListener( - bool immediate_notify, - std::function callback) const { - return ::nt::AddConnectionListener(m_handle, immediate_notify, - std::move(callback)); +inline bool NetworkTableInstance::WaitForListenerQueue(double timeout) { + return ::nt::WaitForListenerQueue(m_handle, timeout); } -inline bool NetworkTableInstance::WaitForConnectionListenerQueue( - double timeout) { - return ::nt::WaitForConnectionListenerQueue(m_handle, timeout); +inline void NetworkTableInstance::RemoveListener(NT_Listener listener) { + ::nt::RemoveListener(listener); } -inline void NetworkTableInstance::RemoveConnectionListener( - NT_ConnectionListener conn_listener) { - ::nt::RemoveConnectionListener(conn_listener); +inline NT_Listener NetworkTableInstance::AddConnectionListener( + bool immediate_notify, ListenerCallback callback) const { + return ::nt::AddListener( + m_handle, + NT_EVENT_CONNECTION | (immediate_notify ? NT_EVENT_IMMEDIATE : 0), + std::move(callback)); } -inline NT_TopicListener NetworkTableInstance::AddTopicListener( - Topic topic, unsigned int eventMask, - std::function listener) { - return ::nt::AddTopicListener(topic.GetHandle(), eventMask, - std::move(listener)); +inline NT_Listener NetworkTableInstance::AddListener( + Topic topic, unsigned int eventMask, ListenerCallback listener) { + return ::nt::AddListener(topic.GetHandle(), eventMask, std::move(listener)); } -inline NT_TopicListener NetworkTableInstance::AddTopicListener( - Subscriber& subscriber, unsigned int eventMask, - std::function listener) { - return ::nt::AddTopicListener(subscriber.GetHandle(), eventMask, - std::move(listener)); +inline NT_Listener NetworkTableInstance::AddListener( + Subscriber& subscriber, unsigned int eventMask, ListenerCallback listener) { + return ::nt::AddListener(subscriber.GetHandle(), eventMask, + std::move(listener)); } -inline NT_TopicListener NetworkTableInstance::AddTopicListener( - NetworkTableEntry& entry, int eventMask, - std::function listener) { - return ::nt::AddTopicListener(entry.GetHandle(), eventMask, - std::move(listener)); +inline NT_Listener NetworkTableInstance::AddListener( + NetworkTableEntry& entry, int eventMask, ListenerCallback listener) { + return ::nt::AddListener(entry.GetHandle(), eventMask, std::move(listener)); } -inline NT_TopicListener NetworkTableInstance::AddTopicListener( +inline NT_Listener NetworkTableInstance::AddListener( std::span prefixes, int eventMask, - std::function listener) { - return ::nt::AddTopicListener(m_handle, prefixes, eventMask, - std::move(listener)); -} - -inline void NetworkTableInstance::RemoveTopicListener( - NT_TopicListener listener) { - return ::nt::RemoveTopicListener(listener); -} - -inline bool NetworkTableInstance::WaitForTopicListenerQueue(double timeout) { - return ::nt::WaitForTopicListenerQueue(m_handle, timeout); -} - -inline NT_ValueListener NetworkTableInstance::AddValueListener( - Subscriber& subscriber, unsigned int eventMask, - std::function listener) { - return ::nt::AddValueListener(subscriber.GetHandle(), eventMask, - std::move(listener)); -} - -inline NT_ValueListener NetworkTableInstance::AddValueListener( - NetworkTableEntry& entry, int eventMask, - std::function listener) { - return ::nt::AddValueListener(entry.GetHandle(), eventMask, - std::move(listener)); -} - -inline void NetworkTableInstance::RemoveValueListener( - NT_ValueListener listener) { - ::nt::RemoveValueListener(listener); -} - -inline bool NetworkTableInstance::WaitForValueListenerQueue(double timeout) { - return ::nt::WaitForValueListenerQueue(m_handle, timeout); + ListenerCallback listener) { + return ::nt::AddListener(m_handle, prefixes, eventMask, std::move(listener)); } inline unsigned int NetworkTableInstance::GetNetworkMode() const { @@ -254,14 +216,10 @@ inline void NetworkTableInstance::StopConnectionDataLog( ::nt::StopConnectionDataLog(logger); } -inline NT_Logger NetworkTableInstance::AddLogger( - std::function func, unsigned int min_level, - unsigned int max_level) { - return ::nt::AddLogger(m_handle, func, min_level, max_level); -} - -inline void NetworkTableInstance::RemoveLogger(NT_Logger logger) { - ::nt::RemoveLogger(logger); +inline NT_Listener NetworkTableInstance::AddLogger(unsigned int min_level, + unsigned int max_level, + ListenerCallback func) { + return ::nt::AddLogger(m_handle, min_level, max_level, std::move(func)); } } // namespace nt diff --git a/ntcore/src/main/native/include/networktables/NetworkTableListener.h b/ntcore/src/main/native/include/networktables/NetworkTableListener.h new file mode 100644 index 0000000000..3b51bda309 --- /dev/null +++ b/ntcore/src/main/native/include/networktables/NetworkTableListener.h @@ -0,0 +1,287 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "ntcore_cpp.h" + +namespace nt { + +class MultiSubscriber; +class NetworkTableEntry; +class NetworkTableInstance; +class Subscriber; +class Topic; + +/** + * Event listener. This calls back to a callback function when an event + * matching the specified mask occurs. The callback function is called + * asynchronously on a separate thread, so it's important to use synchronization + * or atomics when accessing any shared state from the callback function. + */ +class NetworkTableListener final { + public: + NetworkTableListener() = default; + + /** + * Create a listener for changes to topics with names that start with any of + * the given prefixes. This creates a corresponding internal subscriber with + * the lifetime of the listener. + * + * @param inst Instance + * @param prefixes Topic name string prefixes + * @param mask Bitmask of EventFlags values + * @param listener Listener function + * @return Listener + */ + static NetworkTableListener CreateListener( + NetworkTableInstance inst, std::span prefixes, + unsigned int mask, ListenerCallback listener); + + /** + * Create a listener for changes on a particular topic. This creates a + * corresponding internal subscriber with the lifetime of the listener. + * + * @param topic Topic + * @param mask Bitmask of EventFlags values + * @param listener Listener function + * @return Listener + */ + static NetworkTableListener CreateListener(Topic topic, unsigned int mask, + ListenerCallback listener); + + /** + * Create a listener for topic changes on a subscriber. This does NOT keep the + * subscriber active. + * + * @param subscriber Subscriber + * @param mask Bitmask of EventFlags values + * @param listener Listener function + * @return Listener + */ + static NetworkTableListener CreateListener(Subscriber& subscriber, + unsigned int mask, + ListenerCallback listener); + + /** + * Create a listener for topic changes on a subscriber. This does NOT keep the + * subscriber active. + * + * @param subscriber Subscriber + * @param mask Bitmask of EventFlags values + * @param listener Listener function + * @return Listener + */ + static NetworkTableListener CreateListener(MultiSubscriber& subscriber, + unsigned int mask, + ListenerCallback listener); + + /** + * Create a listener for topic changes on an entry. + * + * @param entry Entry + * @param mask Bitmask of EventFlags values + * @param listener Listener function + * @return Listener + */ + static NetworkTableListener CreateListener(NetworkTableEntry& entry, + unsigned int mask, + ListenerCallback listener); + + /** + * Create a connection listener. + * + * @param inst instance + * @param immediate_notify notify listener of all existing connections + * @param listener listener function + * @return Listener + */ + static NetworkTableListener CreateConnectionListener( + NetworkTableInstance inst, bool immediate_notify, + ListenerCallback listener); + + /** + * Create a listener for log messages. By default, log messages are sent to + * stderr; this function sends log messages with the specified levels to the + * provided callback function instead. The callback function will only be + * called for log messages with level greater than or equal to minLevel and + * less than or equal to maxLevel; messages outside this range will be + * silently ignored. + * + * @param inst instance + * @param minLevel minimum log level + * @param maxLevel maximum log level + * @param listener listener function + * @return Listener + */ + static NetworkTableListener CreateLogger(NetworkTableInstance inst, + unsigned int minLevel, + unsigned int maxLevel, + ListenerCallback listener); + + NetworkTableListener(const NetworkTableListener&) = delete; + NetworkTableListener& operator=(const NetworkTableListener&) = delete; + NetworkTableListener(NetworkTableListener&& rhs); + NetworkTableListener& operator=(NetworkTableListener&& rhs); + ~NetworkTableListener(); + + explicit operator bool() const { return m_handle != 0; } + + /** + * Gets the native handle. + * + * @return Handle + */ + NT_Listener GetHandle() const { return m_handle; } + + /** + * Wait for the listener queue to be empty. This is primarily useful for + * deterministic testing. This blocks until either the listener queue is + * empty (e.g. there are no more events that need to be passed along to + * callbacks or poll queues) or the timeout expires. + * + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or + * a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ + bool WaitForQueue(double timeout); + + private: + explicit NetworkTableListener(NT_Listener handle) : m_handle{handle} {} + + NT_Listener m_handle{0}; +}; + +/** + * Event polled listener. This queues events matching the specified mask. Code + * using the listener must periodically call ReadQueue() to read the + * events. + */ +class NetworkTableListenerPoller final { + public: + NetworkTableListenerPoller() = default; + + /** + * Construct a listener poller. + * + * @param inst Instance + */ + explicit NetworkTableListenerPoller(NetworkTableInstance inst); + + NetworkTableListenerPoller(const NetworkTableListenerPoller&) = delete; + NetworkTableListenerPoller& operator=(const NetworkTableListenerPoller&) = + delete; + NetworkTableListenerPoller(NetworkTableListenerPoller&& rhs); + NetworkTableListenerPoller& operator=(NetworkTableListenerPoller&& rhs); + ~NetworkTableListenerPoller(); + + explicit operator bool() const { return m_handle != 0; } + + /** + * Gets the native handle. + * + * @return Handle + */ + NT_ListenerPoller GetHandle() const { return m_handle; } + + /** + * Start listening to topic changes for topics with names that start with any + * of the given prefixes. This creates a corresponding internal subscriber + * with the lifetime of the listener. + * + * @param prefixes Topic name string prefixes + * @param mask Bitmask of EventFlags values + * @return Listener handle + */ + NT_Listener AddListener(std::span prefixes, + unsigned int mask); + + /** + * Start listening to changes to a particular topic. This creates a + * corresponding internal subscriber with the lifetime of the listener. + * + * @param topic Topic + * @param mask Bitmask of EventFlags values + * @return Listener handle + */ + NT_Listener AddListener(Topic topic, unsigned int mask); + + /** + * Start listening to topic changes on a subscriber. This does NOT keep the + * subscriber active. + * + * @param subscriber Subscriber + * @param mask Bitmask of EventFlags values + * @return Listener handle + */ + NT_Listener AddListener(Subscriber& subscriber, unsigned int mask); + + /** + * Start listening to topic changes on a subscriber. This does NOT keep the + * subscriber active. + * + * @param subscriber Subscriber + * @param mask Bitmask of EventFlags values + * @return Listener handle + */ + NT_Listener AddListener(MultiSubscriber& subscriber, unsigned int mask); + + /** + * Start listening to topic changes on an entry. + * + * @param entry Entry + * @param mask Bitmask of EventFlags values + * @return Listener handle + */ + NT_Listener AddListener(NetworkTableEntry& entry, unsigned int mask); + + /** + * Add a connection listener. The callback function is called asynchronously + * on a separate thread, so it's important to use synchronization or atomics + * when accessing any shared state from the callback function. + * + * @param immediate_notify notify listener of all existing connections + * @return Listener handle + */ + NT_Listener AddConnectionListener(bool immediate_notify); + + /** + * Add logger callback function. By default, log messages are sent to stderr; + * this function sends log messages with the specified levels to the provided + * callback function instead. The callback function will only be called for + * log messages with level greater than or equal to minLevel and less than or + * equal to maxLevel; messages outside this range will be silently ignored. + * + * @param minLevel minimum log level + * @param maxLevel maximum log level + * @return Listener handle + */ + NT_Listener AddLogger(unsigned int minLevel, unsigned int maxLevel); + + /** + * Remove a listener. + * + * @param listener Listener handle + */ + void RemoveListener(NT_Listener listener); + + /** + * Read events. + * + * @return Events since the previous call to ReadQueue() + */ + std::vector ReadQueue(); + + private: + NT_ListenerPoller m_handle{0}; +}; + +} // namespace nt + +#include "NetworkTableListener.inc" diff --git a/ntcore/src/main/native/include/networktables/NetworkTableListener.inc b/ntcore/src/main/native/include/networktables/NetworkTableListener.inc new file mode 100644 index 0000000000..3595f20651 --- /dev/null +++ b/ntcore/src/main/native/include/networktables/NetworkTableListener.inc @@ -0,0 +1,160 @@ +// 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. + +#pragma once + +#include +#include +#include +#include + +#include "networktables/MultiSubscriber.h" +#include "networktables/NetworkTableEntry.h" +#include "networktables/NetworkTableInstance.h" +#include "networktables/NetworkTableListener.h" +#include "networktables/Topic.h" +#include "ntcore_cpp.h" + +namespace nt { + +inline NetworkTableListener NetworkTableListener::CreateListener( + NetworkTableInstance inst, std::span prefixes, + unsigned int mask, ListenerCallback listener) { + return NetworkTableListener{ + ::nt::AddListener(inst.GetHandle(), prefixes, mask, std::move(listener))}; +} + +inline NetworkTableListener NetworkTableListener::CreateListener( + Topic topic, unsigned int mask, ListenerCallback listener) { + return NetworkTableListener{ + nt::AddListener(topic.GetHandle(), mask, std::move(listener))}; +} + +inline NetworkTableListener NetworkTableListener::CreateListener( + Subscriber& subscriber, unsigned int mask, ListenerCallback listener) { + return NetworkTableListener{ + ::nt::AddListener(subscriber.GetHandle(), mask, std::move(listener))}; +} + +inline NetworkTableListener NetworkTableListener::CreateListener( + MultiSubscriber& subscriber, unsigned int mask, ListenerCallback listener) { + return NetworkTableListener{ + ::nt::AddListener(subscriber.GetHandle(), mask, std::move(listener))}; +} + +inline NetworkTableListener NetworkTableListener::CreateListener( + NetworkTableEntry& entry, unsigned int mask, ListenerCallback listener) { + return NetworkTableListener{ + ::nt::AddListener(entry.GetHandle(), mask, std::move(listener))}; +} + +inline NetworkTableListener NetworkTableListener::CreateConnectionListener( + NetworkTableInstance inst, bool immediate_notify, + ListenerCallback listener) { + return NetworkTableListener{::nt::AddListener( + inst.GetHandle(), + NT_EVENT_CONNECTION | (immediate_notify ? NT_EVENT_IMMEDIATE : 0), + std::move(listener))}; +} + +inline NetworkTableListener NetworkTableListener::CreateLogger( + NetworkTableInstance inst, unsigned int minLevel, unsigned int maxLevel, + ListenerCallback listener) { + return NetworkTableListener{::nt::AddLogger(inst.GetHandle(), minLevel, + maxLevel, std::move(listener))}; +} + +inline NetworkTableListener::NetworkTableListener(NetworkTableListener&& rhs) + : m_handle(rhs.m_handle) { + rhs.m_handle = 0; +} + +inline NetworkTableListener& NetworkTableListener::operator=( + NetworkTableListener&& rhs) { + std::swap(m_handle, rhs.m_handle); + return *this; +} + +inline NetworkTableListener::~NetworkTableListener() { + if (m_handle != 0) { + nt::RemoveListener(m_handle); + } +} + +inline bool NetworkTableListener::WaitForQueue(double timeout) { + if (m_handle != 0) { + return nt::WaitForListenerQueue(m_handle, timeout); + } else { + return false; + } +} + +inline NetworkTableListenerPoller::NetworkTableListenerPoller( + NetworkTableInstance inst) + : m_handle(nt::CreateListenerPoller(inst.GetHandle())) {} + +inline NetworkTableListenerPoller::NetworkTableListenerPoller( + NetworkTableListenerPoller&& rhs) + : m_handle(rhs.m_handle) { + rhs.m_handle = 0; +} + +inline NetworkTableListenerPoller& NetworkTableListenerPoller::operator=( + NetworkTableListenerPoller&& rhs) { + std::swap(m_handle, rhs.m_handle); + return *this; +} + +inline NetworkTableListenerPoller::~NetworkTableListenerPoller() { + if (m_handle != 0) { + nt::DestroyListenerPoller(m_handle); + } +} + +inline NT_Listener NetworkTableListenerPoller::AddListener( + std::span prefixes, unsigned int mask) { + return nt::AddPolledListener(m_handle, prefixes, mask); +} + +inline NT_Listener NetworkTableListenerPoller::AddListener(Topic topic, + unsigned int mask) { + return ::nt::AddPolledListener(m_handle, topic.GetHandle(), mask); +} + +inline NT_Listener NetworkTableListenerPoller::AddListener( + Subscriber& subscriber, unsigned int mask) { + return ::nt::AddPolledListener(m_handle, subscriber.GetHandle(), mask); +} + +inline NT_Listener NetworkTableListenerPoller::AddListener( + MultiSubscriber& subscriber, unsigned int mask) { + return ::nt::AddPolledListener(m_handle, subscriber.GetHandle(), mask); +} + +inline NT_Listener NetworkTableListenerPoller::AddListener( + NetworkTableEntry& entry, unsigned int mask) { + return ::nt::AddPolledListener(m_handle, entry.GetHandle(), mask); +} + +inline NT_Listener NetworkTableListenerPoller::AddConnectionListener( + bool immediate_notify) { + return ::nt::AddPolledListener( + m_handle, ::nt::GetInstanceFromHandle(m_handle), + NT_EVENT_CONNECTION | (immediate_notify ? NT_EVENT_IMMEDIATE : 0)); +} + +inline NT_Listener NetworkTableListenerPoller::AddLogger( + unsigned int minLevel, unsigned int maxLevel) { + return ::nt::AddPolledLogger(m_handle, minLevel, maxLevel); +} + +inline void NetworkTableListenerPoller::RemoveListener(NT_Listener listener) { + ::nt::RemoveListener(listener); +} + +inline std::vector NetworkTableListenerPoller::ReadQueue() { + return ::nt::ReadListenerQueue(m_handle); +} + +} // namespace nt diff --git a/ntcore/src/main/native/include/networktables/TopicListener.h b/ntcore/src/main/native/include/networktables/TopicListener.h deleted file mode 100644 index 5cfc105b75..0000000000 --- a/ntcore/src/main/native/include/networktables/TopicListener.h +++ /dev/null @@ -1,263 +0,0 @@ -// 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. - -#pragma once - -#include -#include -#include -#include - -#include "ntcore_cpp.h" - -namespace nt { - -class MultiSubscriber; -class NetworkTableEntry; -class NetworkTableInstance; -class Subscriber; -class Topic; - -/** - * Flag values for use with topic listeners. - * - * The flags are a bitmask and must be OR'ed together to indicate the - * combination of events desired to be received. - * - * The constants kPublish, kUnpublish, and kProperties represent different - * events that can occur to entries. - * - * @ingroup ntcore_cpp_api - */ -struct TopicListenerFlags { - TopicListenerFlags() = delete; - - /** - * Initial listener addition. - * Set this flag to receive immediate notification of entries matching the - * flag criteria (generally only useful when combined with kPublish). - */ - static constexpr unsigned int kImmediate = NT_TOPIC_NOTIFY_IMMEDIATE; - - /** - * Newly published topic. - * - * Set this flag to receive a notification when a topic is initially - * published. - */ - static constexpr unsigned int kPublish = NT_TOPIC_NOTIFY_PUBLISH; - - /** - * Topic has no more publishers. - * - * Set this flag to receive a notification when a topic has no more - * publishers. - */ - static constexpr unsigned int kUnpublish = NT_TOPIC_NOTIFY_UNPUBLISH; - - /** - * Topic's properties changed. - * - * Set this flag to receive a notification when an topic's properties change. - */ - static constexpr unsigned int kProperties = NT_TOPIC_NOTIFY_PROPERTIES; -}; - -/** - * Topic change listener. This calls back to a callback function when a topic - * change matching the specified mask occurs. The callback function is called - * asynchronously on a separate thread, so it's important to use synchronization - * or atomics when accessing any shared state from the callback function. - */ -class TopicListener final { - public: - TopicListener() = default; - - /** - * Create a listener for changes to topics with names that start with any of - * the given prefixes. - * - * @param inst Instance - * @param prefixes Topic name string prefixes - * @param mask Bitmask of TopicListenerFlags values - * @param listener Listener function - */ - TopicListener(NetworkTableInstance inst, - std::span prefixes, unsigned int mask, - std::function listener); - - /** - * Create a listener for changes on a particular topic. - * - * @param topic Topic - * @param mask Bitmask of TopicListenerFlags values - * @param listener Listener function - */ - TopicListener(Topic topic, unsigned int mask, - std::function listener); - - /** - * Create a listener for topic changes on a subscriber. This does NOT keep the - * subscriber active. - * - * @param subscriber Subscriber - * @param mask Bitmask of TopicListenerFlags values - * @param listener Listener function - */ - TopicListener(Subscriber& subscriber, unsigned int mask, - std::function listener); - - /** - * Create a listener for topic changes on a subscriber. This does NOT keep the - * subscriber active. - * - * @param subscriber Subscriber - * @param mask Bitmask of TopicListenerFlags values - * @param listener Listener function - */ - TopicListener(MultiSubscriber& subscriber, unsigned int mask, - std::function listener); - - /** - * Create a listener for topic changes on an entry. - * - * @param entry Entry - * @param mask Bitmask of TopicListenerFlags values - * @param listener Listener function - */ - TopicListener(NetworkTableEntry& entry, unsigned int mask, - std::function listener); - - TopicListener(const TopicListener&) = delete; - TopicListener& operator=(const TopicListener&) = delete; - TopicListener(TopicListener&& rhs); - TopicListener& operator=(TopicListener&& rhs); - ~TopicListener(); - - explicit operator bool() const { return m_handle != 0; } - - /** - * Gets the native handle. - * - * @return Handle - */ - NT_TopicListener GetHandle() const { return m_handle; } - - /** - * Wait for the topic listener queue to be empty. This is primarily useful for - * deterministic testing. This blocks until either the topic listener queue is - * empty (e.g. there are no more events that need to be passed along to - * callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or - * a negative value to block indefinitely - * @return False if timed out, otherwise true. - */ - bool WaitForQueue(double timeout); - - private: - NT_TopicListener m_handle{0}; -}; - -/** - * Topic change listener. This queues topic change events matching the specified - * mask. Code using the listener must periodically call readQueue() to read the - * events. - */ -class TopicListenerPoller final { - public: - TopicListenerPoller() = default; - - /** - * Construct a topic listener poller. - * - * @param inst Instance - */ - explicit TopicListenerPoller(NetworkTableInstance inst); - - TopicListenerPoller(const TopicListenerPoller&) = delete; - TopicListenerPoller& operator=(const TopicListenerPoller&) = delete; - TopicListenerPoller(TopicListenerPoller&& rhs); - TopicListenerPoller& operator=(TopicListenerPoller&& rhs); - ~TopicListenerPoller(); - - explicit operator bool() const { return m_handle != 0; } - - /** - * Gets the native handle. - * - * @return Handle - */ - NT_TopicListenerPoller GetHandle() const { return m_handle; } - - /** - * Start listening to changes to a particular topic. - * - * @param prefixes Topic name string prefixes - * @param mask Bitmask of TopicListenerFlags values - * @return Listener handle - */ - NT_TopicListener Add(std::span prefixes, - unsigned int mask); - - /** - * Start listening to topic changes for topics with names that start with any - * of the given prefixes. - * - * @param topic Topic - * @param mask Bitmask of TopicListenerFlags values - * @return Listener handle - */ - NT_TopicListener Add(Topic topic, unsigned int mask); - - /** - * Start listening to topic changes on a subscriber. This does NOT keep the - * subscriber active. - * - * @param subscriber Subscriber - * @param mask Bitmask of TopicListenerFlags values - * @return Listener handle - */ - NT_TopicListener Add(Subscriber& subscriber, unsigned int mask); - - /** - * Start listening to topic changes on a subscriber. This does NOT keep the - * subscriber active. - * - * @param subscriber Subscriber - * @param mask Bitmask of TopicListenerFlags values - * @return Listener handle - */ - NT_TopicListener Add(MultiSubscriber& subscriber, unsigned int mask); - - /** - * Start listening to topic changes on an entry. - * - * @param entry Entry - * @param mask Bitmask of TopicListenerFlags values - * @return Listener handle - */ - NT_TopicListener Add(NetworkTableEntry& entry, unsigned int mask); - - /** - * Remove a listener. - * - * @param listener Listener handle - */ - void Remove(NT_TopicListener listener); - - /** - * Read topic notifications. - * - * @return Topic notifications since the previous call to readQueue() - */ - std::vector ReadQueue(); - - private: - NT_TopicListenerPoller m_handle{0}; -}; - -} // namespace nt - -#include "TopicListener.inc" diff --git a/ntcore/src/main/native/include/networktables/TopicListener.inc b/ntcore/src/main/native/include/networktables/TopicListener.inc deleted file mode 100644 index adb9e17851..0000000000 --- a/ntcore/src/main/native/include/networktables/TopicListener.inc +++ /dev/null @@ -1,123 +0,0 @@ -// 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. - -#pragma once - -#include -#include -#include -#include - -#include "networktables/MultiSubscriber.h" -#include "networktables/NetworkTableEntry.h" -#include "networktables/NetworkTableInstance.h" -#include "networktables/Topic.h" -#include "networktables/TopicListener.h" -#include "ntcore_cpp.h" - -namespace nt { - -inline TopicListener::TopicListener( - NetworkTableInstance inst, std::span prefixes, - unsigned int mask, std::function listener) - : m_handle{AddTopicListener(inst.GetHandle(), prefixes, mask, listener)} {} - -inline TopicListener::TopicListener( - Topic topic, unsigned int mask, - std::function listener) - : m_handle{AddTopicListener(topic.GetHandle(), mask, listener)} {} - -inline TopicListener::TopicListener( - Subscriber& subscriber, unsigned int mask, - std::function listener) - : m_handle{AddTopicListener(subscriber.GetHandle(), mask, listener)} {} - -inline TopicListener::TopicListener( - MultiSubscriber& subscriber, unsigned int mask, - std::function listener) - : m_handle{AddTopicListener(subscriber.GetHandle(), mask, listener)} {} - -inline TopicListener::TopicListener( - NetworkTableEntry& entry, unsigned int mask, - std::function listener) - : m_handle{AddTopicListener(entry.GetHandle(), mask, listener)} {} - -inline TopicListener::TopicListener(TopicListener&& rhs) - : m_handle(rhs.m_handle) { - rhs.m_handle = 0; -} - -inline TopicListener& TopicListener::operator=(TopicListener&& rhs) { - std::swap(m_handle, rhs.m_handle); - return *this; -} - -inline TopicListener::~TopicListener() { - if (m_handle != 0) { - nt::RemoveTopicListener(m_handle); - } -} - -inline bool TopicListener::WaitForQueue(double timeout) { - if (m_handle != 0) { - return nt::WaitForTopicListenerQueue(m_handle, timeout); - } else { - return false; - } -} - -inline TopicListenerPoller::TopicListenerPoller(NetworkTableInstance inst) - : m_handle(nt::CreateTopicListenerPoller(inst.GetHandle())) {} - -inline TopicListenerPoller::TopicListenerPoller(TopicListenerPoller&& rhs) - : m_handle(rhs.m_handle) { - rhs.m_handle = 0; -} - -inline TopicListenerPoller& TopicListenerPoller::operator=( - TopicListenerPoller&& rhs) { - std::swap(m_handle, rhs.m_handle); - return *this; -} - -inline TopicListenerPoller::~TopicListenerPoller() { - if (m_handle != 0) { - nt::DestroyTopicListenerPoller(m_handle); - } -} - -inline NT_TopicListener TopicListenerPoller::Add( - std::span prefixes, unsigned int mask) { - return nt::AddPolledTopicListener(m_handle, prefixes, mask); -} - -inline NT_TopicListener TopicListenerPoller::Add(Topic topic, - unsigned int mask) { - return nt::AddPolledTopicListener(m_handle, topic.GetHandle(), mask); -} - -inline NT_TopicListener TopicListenerPoller::Add(Subscriber& subscriber, - unsigned int mask) { - return nt::AddPolledTopicListener(m_handle, subscriber.GetHandle(), mask); -} - -inline NT_TopicListener TopicListenerPoller::Add(MultiSubscriber& subscriber, - unsigned int mask) { - return nt::AddPolledTopicListener(m_handle, subscriber.GetHandle(), mask); -} - -inline NT_TopicListener TopicListenerPoller::Add(NetworkTableEntry& entry, - unsigned int mask) { - return nt::AddPolledTopicListener(m_handle, entry.GetHandle(), mask); -} - -inline void TopicListenerPoller::Remove(NT_TopicListener listener) { - nt::RemoveTopicListener(listener); -} - -inline std::vector TopicListenerPoller::ReadQueue() { - return nt::ReadTopicListenerQueue(m_handle); -} - -} // namespace nt diff --git a/ntcore/src/main/native/include/networktables/ValueListener.h b/ntcore/src/main/native/include/networktables/ValueListener.h deleted file mode 100644 index 9c85bc7e0d..0000000000 --- a/ntcore/src/main/native/include/networktables/ValueListener.h +++ /dev/null @@ -1,203 +0,0 @@ -// 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. - -#pragma once - -#include -#include - -#include "ntcore_cpp.h" - -namespace nt { - -class MultiSubscriber; -class NetworkTableEntry; -class NetworkTableInstance; -class Subscriber; - -/** - * Flag values for use with value listeners. - * - * The flags are a bitmask and must be OR'ed together to indicate the - * combination of events desired to be received. - * - * By default, notifications are only generated for remote changes occurring - * after the listener is created. The constants kImmediate and kLocal are - * modifiers that cause notifications to be generated at other times. - */ -struct ValueListenerFlags { - ValueListenerFlags() = delete; - - /** - * Initial listener addition. - * - * Set this flag to receive immediate notification of the current value. - */ - static constexpr unsigned int kImmediate = NT_VALUE_NOTIFY_IMMEDIATE; - - /** - * Changed locally. - * - * Set this flag to receive notification of both local changes and changes - * coming from remote nodes. By default, notifications are only generated for - * remote changes. - */ - static constexpr unsigned int kLocal = NT_VALUE_NOTIFY_LOCAL; -}; - -/** - * Value change listener. This calls back to a callback function when a value - * change matching the specified mask occurs. The callback function is called - * asynchronously on a separate thread, so it's important to use synchronization - * or atomics when accessing any shared state from the callback function. - */ -class ValueListener final { - public: - ValueListener() = default; - - /** - * Create a listener for value changes on a subscriber. This does NOT keep the - * subscriber active. - * - * @param subscriber Subscriber - * @param mask Bitmask of ValueListenerFlags values - * @param listener Listener function - */ - ValueListener(Subscriber& subscriber, unsigned int mask, - std::function listener); - - /** - * Create a listener for value changes on a subscriber. This does NOT keep the - * subscriber active. - * - * @param subscriber Subscriber - * @param mask Bitmask of ValueListenerFlags values - * @param listener Listener function - */ - ValueListener(MultiSubscriber& subscriber, unsigned int mask, - std::function listener); - - /** - * Create a listener for value changes on an entry. - * - * @param entry Entry - * @param mask Bitmask of ValueListenerFlags values - * @param listener Listener function - */ - ValueListener(NetworkTableEntry& entry, unsigned int mask, - std::function listener); - - ValueListener(const ValueListener&) = delete; - ValueListener& operator=(const ValueListener&) = delete; - ValueListener(ValueListener&& rhs); - ValueListener& operator=(ValueListener&& rhs); - ~ValueListener(); - - explicit operator bool() const { return m_handle != 0; } - - /** - * Gets the native handle. - * - * @return Handle - */ - NT_ValueListener GetHandle() const { return m_handle; } - - /** - * Wait for the value listener queue to be empty. This is primarily useful for - * deterministic testing. This blocks until either the value listener queue is - * empty (e.g. there are no more events that need to be passed along to - * callbacks or poll queues) or the timeout expires. - * - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or - * a negative value to block indefinitely - * @return False if timed out, otherwise true. - */ - bool WaitForQueue(double timeout); - - private: - NT_ValueListener m_handle{0}; -}; - -/** - * Value change listener. This queues value change events matching the specified - * mask. Code using the listener must periodically call readQueue() to read the - * events. - */ -class ValueListenerPoller final { - public: - ValueListenerPoller() = default; - - /** - * Construct a value listener poller. - * - * @param inst Instance - */ - explicit ValueListenerPoller(NetworkTableInstance inst); - - ValueListenerPoller(const ValueListenerPoller&) = delete; - ValueListenerPoller& operator=(const ValueListenerPoller&) = delete; - ValueListenerPoller(ValueListenerPoller&& rhs); - ValueListenerPoller& operator=(ValueListenerPoller&& rhs); - ~ValueListenerPoller(); - - explicit operator bool() const { return m_handle != 0; } - - /** - * Gets the native handle. - * - * @return Handle - */ - NT_ValueListenerPoller GetHandle() const { return m_handle; } - - /** - * Start listening to value changes on a subscriber. This does NOT keep the - * subscriber active. - * - * @param subscriber Subscriber - * @param mask Bitmask of ValueListenerFlags values - * @return Listener handle - */ - NT_ValueListener Add(Subscriber& subscriber, unsigned int mask); - - /** - * Start listening to value changes on a subscriber. This does NOT keep the - * subscriber active. - * - * @param subscriber Subscriber - * @param mask Bitmask of ValueListenerFlags values - * @return Listener handle - */ - NT_ValueListener Add(MultiSubscriber& subscriber, unsigned int mask); - - /** - * Start listening to value changes on an entry. - * - * @param entry Entry - * @param mask Bitmask of ValueListenerFlags values - * @return Listener handle - */ - NT_ValueListener Add(NetworkTableEntry& entry, unsigned int mask); - - /** - * Remove a listener. - * - * @param listener Listener handle - */ - void Remove(NT_ValueListener listener); - - /** - * Reads value listener queue (all value changes since last call). - * - * @param poller poller handle - * @return Array of value notifications. - */ - std::vector ReadQueue(); - - private: - NT_ValueListenerPoller m_handle{0}; -}; - -} // namespace nt - -#include "ValueListener.inc" diff --git a/ntcore/src/main/native/include/networktables/ValueListener.inc b/ntcore/src/main/native/include/networktables/ValueListener.inc deleted file mode 100644 index 4950ba1c26..0000000000 --- a/ntcore/src/main/native/include/networktables/ValueListener.inc +++ /dev/null @@ -1,101 +0,0 @@ -// 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. - -#pragma once - -#include -#include - -#include "networktables/MultiSubscriber.h" -#include "networktables/NetworkTableEntry.h" -#include "networktables/NetworkTableInstance.h" -#include "networktables/Topic.h" -#include "networktables/ValueListener.h" -#include "ntcore_cpp.h" - -namespace nt { - -inline ValueListener::ValueListener( - Subscriber& subscriber, unsigned int mask, - std::function listener) - : m_handle{AddValueListener(subscriber.GetHandle(), mask, listener)} {} - -inline ValueListener::ValueListener( - MultiSubscriber& subscriber, unsigned int mask, - std::function listener) - : m_handle{AddValueListener(subscriber.GetHandle(), mask, listener)} {} - -inline ValueListener::ValueListener( - NetworkTableEntry& entry, unsigned int mask, - std::function listener) - : m_handle{AddValueListener(entry.GetHandle(), mask, listener)} {} - -inline ValueListener::ValueListener(ValueListener&& rhs) - : m_handle(rhs.m_handle) { - rhs.m_handle = 0; -} - -inline ValueListener& ValueListener::operator=(ValueListener&& rhs) { - std::swap(m_handle, rhs.m_handle); - return *this; -} - -inline ValueListener::~ValueListener() { - if (m_handle != 0) { - nt::RemoveValueListener(m_handle); - } -} - -inline bool ValueListener::WaitForQueue(double timeout) { - if (m_handle != 0) { - return nt::WaitForValueListenerQueue(m_handle, timeout); - } else { - return false; - } -} - -inline ValueListenerPoller::ValueListenerPoller(NetworkTableInstance inst) - : m_handle(nt::CreateValueListenerPoller(inst.GetHandle())) {} - -inline ValueListenerPoller::ValueListenerPoller(ValueListenerPoller&& rhs) - : m_handle(rhs.m_handle) { - rhs.m_handle = 0; -} - -inline ValueListenerPoller& ValueListenerPoller::operator=( - ValueListenerPoller&& rhs) { - std::swap(m_handle, rhs.m_handle); - return *this; -} - -inline ValueListenerPoller::~ValueListenerPoller() { - if (m_handle != 0) { - nt::DestroyValueListenerPoller(m_handle); - } -} - -inline NT_ValueListener ValueListenerPoller::Add(Subscriber& subscriber, - unsigned int mask) { - return nt::AddPolledValueListener(m_handle, subscriber.GetHandle(), mask); -} - -inline NT_ValueListener ValueListenerPoller::Add(MultiSubscriber& subscriber, - unsigned int mask) { - return nt::AddPolledValueListener(m_handle, subscriber.GetHandle(), mask); -} - -inline NT_ValueListener ValueListenerPoller::Add(NetworkTableEntry& entry, - unsigned int mask) { - return nt::AddPolledValueListener(m_handle, entry.GetHandle(), mask); -} - -inline void ValueListenerPoller::Remove(NT_ValueListener listener) { - nt::RemoveValueListener(listener); -} - -inline std::vector ValueListenerPoller::ReadQueue() { - return nt::ReadValueListenerQueue(m_handle); -} - -} // namespace nt diff --git a/ntcore/src/main/native/include/ntcore_c.h b/ntcore/src/main/native/include/ntcore_c.h index 91e83c2262..a40349ee00 100644 --- a/ntcore/src/main/native/include/ntcore_c.h +++ b/ntcore/src/main/native/include/ntcore_c.h @@ -29,21 +29,15 @@ typedef int NT_Bool; typedef unsigned int NT_Handle; typedef NT_Handle NT_ConnectionDataLogger; -typedef NT_Handle NT_ConnectionListener; -typedef NT_Handle NT_ConnectionListenerPoller; typedef NT_Handle NT_DataLogger; typedef NT_Handle NT_Entry; typedef NT_Handle NT_Inst; -typedef NT_Handle NT_Logger; -typedef NT_Handle NT_LoggerPoller; +typedef NT_Handle NT_Listener; +typedef NT_Handle NT_ListenerPoller; typedef NT_Handle NT_MultiSubscriber; typedef NT_Handle NT_Topic; -typedef NT_Handle NT_TopicListener; -typedef NT_Handle NT_TopicListenerPoller; typedef NT_Handle NT_Subscriber; typedef NT_Handle NT_Publisher; -typedef NT_Handle NT_ValueListener; -typedef NT_Handle NT_ValueListenerPoller; /** Default network tables port number (NT3) */ #define NT_DEFAULT_PORT3 1735 @@ -103,20 +97,33 @@ enum NT_PubSubOptionType { NT_PUBSUB_KEEPDUPLICATES, /* preserve duplicate values */ }; -/** Topic notification flags. */ -enum NT_TopicNotifyKind { - NT_TOPIC_NOTIFY_NONE = 0, - NT_TOPIC_NOTIFY_IMMEDIATE = 0x01, /* initial listener addition */ - NT_TOPIC_NOTIFY_PUBLISH = 0x02, /* initially published */ - NT_TOPIC_NOTIFY_UNPUBLISH = 0x04, /* no more publishers */ - NT_TOPIC_NOTIFY_PROPERTIES = 0x08, /* properties changed */ -}; - -/** Value notification flags. */ -enum NT_ValueNotifyKind { - NT_VALUE_NOTIFY_NONE = 0, - NT_VALUE_NOTIFY_IMMEDIATE = 0x01, /* initial listener addition */ - NT_VALUE_NOTIFY_LOCAL = 0x02, /* changed locally */ +/** Event notification flags. */ +enum NT_EventFlags { + NT_EVENT_NONE = 0, + /** Initial listener addition. */ + NT_EVENT_IMMEDIATE = 0x01, + /** Client connected (on server, any client connected). */ + NT_EVENT_CONNECTED = 0x02, + /** Client disconnected (on server, any client disconnected). */ + NT_EVENT_DISCONNECTED = 0x04, + /** Any connection event (connect or disconnect). */ + NT_EVENT_CONNECTION = NT_EVENT_CONNECTED | NT_EVENT_DISCONNECTED, + /** New topic published. */ + NT_EVENT_PUBLISH = 0x08, + /** Topic unpublished. */ + NT_EVENT_UNPUBLISH = 0x10, + /** Topic properties changed. */ + NT_EVENT_PROPERTIES = 0x20, + /** Any topic event (publish, unpublish, or properties changed). */ + NT_EVENT_TOPIC = NT_EVENT_PUBLISH | NT_EVENT_UNPUBLISH | NT_EVENT_PROPERTIES, + /** Topic value updated (via network). */ + NT_EVENT_VALUE_REMOTE = 0x40, + /** Topic value updated (local). */ + NT_EVENT_VALUE_LOCAL = 0x80, + /** Topic value updated (network or local). */ + NT_EVENT_VALUE_ALL = NT_EVENT_VALUE_REMOTE | NT_EVENT_VALUE_LOCAL, + /** Log message. */ + NT_EVENT_LOGMESSAGE = 0x100, }; /* @@ -178,6 +185,7 @@ struct NT_Value { } data; }; +/** NetworkTables Topic Information */ struct NT_TopicInfo { /** Topic handle */ NT_Topic topic; @@ -221,23 +229,8 @@ struct NT_ConnectionInfo { unsigned int protocol_version; }; -/** NetworkTables Topic Notification */ -struct NT_TopicNotification { - /** Listener that was triggered. */ - NT_TopicListener listener; - - /** Topic info. */ - struct NT_TopicInfo info; - - /** Update flags. */ - unsigned int flags; -}; - -/** NetworkTables Value Notification */ -struct NT_ValueNotification { - /** Listener that was triggered. */ - NT_ValueListener listener; - +/** NetworkTables value event data. */ +struct NT_ValueEventData { /** Topic handle. */ NT_Topic topic; @@ -246,31 +239,10 @@ struct NT_ValueNotification { /** The new value. */ struct NT_Value value; - - /** - * Update flags. For example, NT_NOTIFY_NEW if the key did not previously - * exist. - */ - unsigned int flags; -}; - -/** NetworkTables Connection Notification */ -struct NT_ConnectionNotification { - /** Listener that was triggered. */ - NT_ConnectionListener listener; - - /** True if event is due to connection being established. */ - NT_Bool connected; - - /** Connection info. */ - struct NT_ConnectionInfo conn; }; /** NetworkTables log message. */ struct NT_LogMessage { - /** The logger that generated the message. */ - NT_Logger logger; - /** Log level of the message. See NT_LogLevel. */ unsigned int level; @@ -284,6 +256,30 @@ struct NT_LogMessage { char* message; }; +/** NetworkTables event */ +struct NT_Event { + /** Listener that triggered this event. */ + NT_Handle listener; + + /** + * Event flags (NT_EventFlags). Also indicates the data included with the + * event: + * - NT_EVENT_CONNECTED or NT_EVENT_DISCONNECTED: connInfo + * - NT_EVENT_PUBLISH, NT_EVENT_UNPUBLISH, or NT_EVENT_PROPERTIES: topicInfo + * - NT_EVENT_VALUE_REMOTE, NT_NOTIFY_VALUE_LOCAL: valueData + * - NT_EVENT_LOGMESSAGE: logMessage + */ + unsigned int flags; + + /** Event data; content depends on flags. */ + union { + struct NT_ConnectionInfo connInfo; + struct NT_TopicInfo topicInfo; + struct NT_ValueEventData valueData; + struct NT_LogMessage logMessage; + } data; +}; + /** NetworkTables publish/subscribe option. */ struct NT_PubSubOption { /** Option type. */ @@ -804,346 +800,184 @@ void NT_UnsubscribeMultiple(NT_MultiSubscriber sub); /** @} */ /** - * @defgroup ntcore_topiclistener_cfunc Topic Listener Functions + * @defgroup ntcore_listener_cfunc Listener Functions * @{ */ /** - * Topic listener callback function. + * Event listener callback function. * * @param data data pointer provided to callback creation function * @param event event info */ -typedef void (*NT_TopicListenerCallback)( - void* data, const struct NT_TopicNotification* event); +typedef void (*NT_ListenerCallback)(void* data, const struct NT_Event* event); + +/** + * Creates a listener poller. + * + * A poller provides a single queue of poll events. Events linked to this + * poller (using NT_AddPolledXListener()) will be stored in the queue and + * must be collected by calling NT_ReadListenerQueue(). + * The returned handle must be destroyed with NT_DestroyListenerPoller(). + * + * @param inst instance handle + * @return poller handle + */ +NT_ListenerPoller NT_CreateListenerPoller(NT_Inst inst); + +/** + * Destroys a listener poller. This will abort any blocked polling + * call and prevent additional events from being generated for this poller. + * + * @param poller poller handle + */ +void NT_DestroyListenerPoller(NT_ListenerPoller poller); + +/** + * Read notifications. + * + * @param poller poller handle + * @param len length of returned array (output) + * @return Array of events. Returns NULL and len=0 if no events since last + * call. + */ +struct NT_Event* NT_ReadListenerQueue(NT_ListenerPoller poller, size_t* len); + +/** + * Removes a listener. + * + * @param listener Listener handle to remove + */ +void NT_RemoveListener(NT_Listener listener); + +/** + * Wait for the listener queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the listener + * queue is empty (e.g. there are no more events that need to be passed along to + * callbacks or poll queues) or the timeout expires. + * + * @param handle handle + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a + * negative value to block indefinitely + * @return False if timed out, otherwise true. + */ +NT_Bool NT_WaitForListenerQueue(NT_Handle handle, double timeout); /** * Create a listener for changes to topics with names that start with - * the given prefix. + * the given prefix. This creates a corresponding internal subscriber with the + * lifetime of the listener. * * @param inst Instance handle * @param prefix Topic name string prefix * @param prefix_len Length of topic name string prefix - * @param mask Bitmask of NT_TopicListenerFlags values + * @param mask Bitmask of NT_EventFlags values (only topic and value events will + * be generated) * @param data Data passed to callback function * @param callback Listener function + * @return Listener handle */ -NT_TopicListener NT_AddTopicListener(NT_Inst inst, const char* prefix, - size_t prefix_len, unsigned int mask, - void* data, - NT_TopicListenerCallback callback); +NT_Listener NT_AddListenerSingle(NT_Inst inst, const char* prefix, + size_t prefix_len, unsigned int mask, + void* data, NT_ListenerCallback callback); /** * Create a listener for changes to topics with names that start with any of - * the given prefixes. + * the given prefixes. This creates a corresponding internal subscriber with the + * lifetime of the listener. * * @param inst Instance handle * @param prefixes Topic name string prefixes * @param prefixes_len Number of elements in prefixes array - * @param mask Bitmask of NT_TopicListenerFlags values + * @param mask Bitmask of NT_EventFlags values (only topic and value events will + * be generated) * @param data Data passed to callback function * @param callback Listener function + * @return Listener handle */ -NT_TopicListener NT_AddTopicListenerMultiple(NT_Inst inst, - const struct NT_String* prefixes, - size_t prefixes_len, - unsigned int mask, void* data, - NT_TopicListenerCallback callback); +NT_Listener NT_AddListenerMultiple(NT_Inst inst, + const struct NT_String* prefixes, + size_t prefixes_len, unsigned int mask, + void* data, NT_ListenerCallback callback); /** - * Create a listener for changes on a particular topic. + * Create a listener. * - * @param topic Topic handle - * @param mask Bitmask of NT_TopicListenerFlags values + * Some combinations of handle and mask have no effect: + * - connection and log message events are only generated on instances + * - topic and value events are only generated on non-instances + * + * Adding value and topic events on a topic will create a corresponding internal + * subscriber with the lifetime of the listener. + * + * Adding a log message listener through this function will only result in + * events at NT_LOG_INFO or higher; for more customized settings, use + * NT_AddLogger(). + * + * @param handle Handle + * @param mask Bitmask of NT_EventFlags values * @param data Data passed to callback function * @param callback Listener function + * @return Listener handle */ -NT_TopicListener NT_AddTopicListenerSingle(NT_Topic topic, unsigned int mask, - void* data, - NT_TopicListenerCallback callback); +NT_Listener NT_AddListener(NT_Handle handle, unsigned int mask, void* data, + NT_ListenerCallback callback); /** - * Wait for the topic listener queue to be empty. This is primarily useful - * for deterministic testing. This blocks until either the topic listener - * queue is empty (e.g. there are no more events that need to be passed along to - * callbacks or poll queues) or the timeout expires. - * - * @param handle handle - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a - * negative value to block indefinitely - * @return False if timed out, otherwise true. - */ -NT_Bool NT_WaitForTopicListenerQueue(NT_Handle handle, double timeout); - -/** - * Creates a topic listener poller. - * - * A poller provides a single queue of poll events. Events linked to this - * poller (using NT_AddPolledTopicListener()) will be stored in the queue and - * must be collected by calling NT_ReadTopicListenerQueue(). - * The returned handle must be destroyed with NT_DestroyTopicListenerPoller(). - * - * @param inst instance handle - * @return poller handle - */ -NT_TopicListenerPoller NT_CreateTopicListenerPoller(NT_Inst inst); - -/** - * Destroys a topic listener poller. This will abort any blocked polling - * call and prevent additional events from being generated for this poller. - * - * @param poller poller handle - */ -void NT_DestroyTopicListenerPoller(NT_TopicListenerPoller poller); - -/** - * Read topic notifications. - * - * @param poller poller handle - * @param len length of returned array (output) - * @return Array of topic notifications. Returns NULL and len=0 if no - * notifications since last call. - */ -struct NT_TopicNotification* NT_ReadTopicListenerQueue( - NT_TopicListenerPoller poller, size_t* len); - -/** - * Creates a polled topic listener. - * The caller is responsible for calling NT_ReadTopicListenerQueue() to poll. + * Creates a polled topic listener. This creates a corresponding internal + * subscriber with the lifetime of the listener. + * The caller is responsible for calling NT_ReadListenerQueue() to poll. * * @param poller poller handle * @param prefix UTF-8 string prefix * @param prefix_len Length of UTF-8 string prefix - * @param mask NT_NotifyKind bitmask + * @param mask NT_EventFlags bitmask (only topic and value events + * will be generated) * @return Listener handle */ -NT_TopicListener NT_AddPolledTopicListener(NT_TopicListenerPoller poller, - const char* prefix, - size_t prefix_len, - unsigned int mask); +NT_Listener NT_AddPolledListenerSingle(NT_ListenerPoller poller, + const char* prefix, size_t prefix_len, + unsigned int mask); /** - * Creates a polled topic listener. - * The caller is responsible for calling NT_ReadTopicListenerQueue() to poll. + * Creates a polled topic listener. This creates a corresponding internal + * subscriber with the lifetime of the listener. + * The caller is responsible for calling NT_ReadListenerQueue() to poll. * * @param poller poller handle * @param prefixes array of UTF-8 string prefixes * @param prefixes_len Length of prefixes array + * @param mask NT_EventFlags bitmask (only topic and value events + * will be generated) + * @return Listener handle + */ +NT_Listener NT_AddPolledListenerMultiple(NT_ListenerPoller poller, + const struct NT_String* prefixes, + size_t prefixes_len, + unsigned int mask); + +/** + * Creates a polled listener. + * The caller is responsible for calling NT_ReadListenerQueue() to poll. + * + * Some combinations of handle and mask have no effect: + * - connection and log message events are only generated on instances + * - topic and value events are only generated on non-instances + * + * Adding value and topic events on a topic will create a corresponding internal + * subscriber with the lifetime of the listener. + * + * Adding a log message listener through this function will only result in + * events at NT_LOG_INFO or higher; for more customized settings, use + * NT_AddPolledLogger(). + * + * @param poller poller handle + * @param handle handle * @param mask NT_NotifyKind bitmask * @return Listener handle */ -NT_TopicListener NT_AddPolledTopicListenerMultiple( - NT_TopicListenerPoller poller, const struct NT_String* prefixes, - size_t prefixes_len, unsigned int mask); - -/** - * Creates a polled topic listener. - * The caller is responsible for calling NT_ReadTopicListenerQueue() to poll. - * - * @param poller poller handle - * @param topic topic - * @param mask NT_NotifyKind bitmask - * @return Listener handle - */ -NT_TopicListener NT_AddPolledTopicListenerSingle(NT_TopicListenerPoller poller, - NT_Topic topic, - unsigned int mask); - -/** - * Removes a topic listener. - * - * @param topic_listener Listener handle to remove - */ -void NT_RemoveTopicListener(NT_TopicListener topic_listener); - -/** @} */ - -/** - * @defgroup ntcore_valuelistener_cfunc Value Listener Functions - * @{ - */ - -/** - * Value listener callback function. - * - * @param data data pointer provided to callback creation function - * @param event event info - */ -typedef void (*NT_ValueListenerCallback)( - void* data, const struct NT_ValueNotification* event); - -/** - * Create a listener for value changes on a subscriber. - * - * @param subentry Subscriber/entry - * @param mask Bitmask of NT_ValueListenerFlags values - * @param data Data passed to listener function - * @param callback Listener function - */ -NT_ValueListener NT_AddValueListener(NT_Handle subentry, unsigned int mask, - void* data, - NT_ValueListenerCallback callback); - -/** - * Wait for the value listener queue to be empty. This is primarily useful - * for deterministic testing. This blocks until either the value listener - * queue is empty (e.g. there are no more events that need to be passed along to - * callbacks or poll queues) or the timeout expires. - * - * @param handle handle - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a - * negative value to block indefinitely - * @return False if timed out, otherwise true. - */ -NT_Bool NT_WaitForValueListenerQueue(NT_Handle handle, double timeout); - -/** - * Create a value listener poller. - * - * A poller provides a single queue of poll events. Events linked to this - * poller (using NT_AddPolledValueListener()) will be stored in the queue and - * must be collected by calling NT_ReadValueListenerQueue(). - * The returned handle must be destroyed with NT_DestroyValueListenerPoller(). - * - * @param inst instance handle - * @return poller handle - */ -NT_ValueListenerPoller NT_CreateValueListenerPoller(NT_Inst inst); - -/** - * Destroy a value listener poller. This will abort any blocked polling - * call and prevent additional events from being generated for this poller. - * - * @param poller poller handle - */ -void NT_DestroyValueListenerPoller(NT_ValueListenerPoller poller); - -/** - * Reads value listener queue (all value changes since last call). - * - * @param poller poller handle - * @param len length of returned array (output) - * @return Array of value notifications. Returns NULL and len=0 if no - * notifications since last call. - */ -struct NT_ValueNotification* NT_ReadValueListenerQueue( - NT_ValueListenerPoller poller, size_t* len); - -/** - * Create a polled value listener. - * The caller is responsible for calling NT_ReadValueListenerQueue() to poll. - * - * @param poller poller handle - * @param subentry subscriber or entry handle - * @param flags NT_NotifyKind bitmask - * @return Listener handle - */ -NT_ValueListener NT_AddPolledValueListener(NT_ValueListenerPoller poller, - NT_Handle subentry, - unsigned int flags); - -/** - * Remove a value listener. - * - * @param value_listener Listener handle to remove - */ -void NT_RemoveValueListener(NT_ValueListener value_listener); - -/** @} */ - -/** - * @defgroup ntcore_connectionlistener_cfunc Connection Listener Functions - * @{ - */ - -/** - * Connection listener callback function. - * Called when a network connection is made or lost. - * - * @param data data pointer provided to callback creation function - * @param event event info - */ -typedef void (*NT_ConnectionListenerCallback)( - void* data, const struct NT_ConnectionNotification* event); - -/** - * Add a connection listener. - * - * @param inst instance handle - * @param data data pointer to pass to callback - * @param callback listener to add - * @param immediate_notify notify listener of all existing connections - * @return Listener handle - */ -NT_ConnectionListener NT_AddConnectionListener( - NT_Inst inst, NT_Bool immediate_notify, void* data, - NT_ConnectionListenerCallback callback); - -/** - * Wait for the connection listener queue to be empty. This is primarily useful - * for deterministic testing. This blocks until either the connection listener - * queue is empty (e.g. there are no more events that need to be passed along to - * callbacks or poll queues) or the timeout expires. - * - * @param handle handle - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a - * negative value to block indefinitely - * @return False if timed out, otherwise true. - */ -NT_Bool NT_WaitForConnectionListenerQueue(NT_Handle handle, double timeout); - -/** - * Create a connection listener poller. - * A poller provides a single queue of poll events. Events linked to this - * poller (using NT_AddPolledConnectionListener()) will be stored in the queue - * and must be collected by calling NT_PollConnectionListener(). - * The returned handle must be destroyed with - * NT_DestroyConnectionListenerPoller(). - * - * @param inst instance handle - * @return poller handle - */ -NT_ConnectionListenerPoller NT_CreateConnectionListenerPoller(NT_Inst inst); - -/** - * Destroy a connection listener poller. This will abort any blocked polling - * call and prevent additional events from being generated for this poller. - * - * @param poller poller handle - */ -void NT_DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller); - -/** - * Create a polled connection listener. - * The caller is responsible for calling NT_PollConnectionListener() to poll. - * - * @param poller poller handle - * @param immediate_notify notify listener of all existing connections - */ -NT_ConnectionListener NT_AddPolledConnectionListener( - NT_ConnectionListenerPoller poller, NT_Bool immediate_notify); - -/** - * Get the next connection event. This blocks until the next connect or - * disconnect occurs. This is intended to be used with - * NT_AddPolledConnectionListener(); connection listeners created using - * NT_AddConnectionListener() will not be serviced through this function. - * - * @param poller poller handle - * @param len length of returned array (output) - * @return Array of information on the connection events. Only returns NULL - * if an error occurred (e.g. the instance was invalid or is shutting - * down). - */ -struct NT_ConnectionNotification* NT_ReadConnectionListenerQueue( - NT_ConnectionListenerPoller poller, size_t* len); - -/** - * Remove a connection listener. - * - * @param conn_listener Listener handle to remove - */ -void NT_RemoveConnectionListener(NT_ConnectionListener conn_listener); +NT_Listener NT_AddPolledListener(NT_ListenerPoller poller, NT_Handle handle, + unsigned int mask); /** @} */ @@ -1390,67 +1224,19 @@ void NT_DisposeTopicInfoArray(struct NT_TopicInfo* arr, size_t count); void NT_DisposeTopicInfo(struct NT_TopicInfo* info); /** - * Disposes an topic notification array. + * Disposes an event array. * * @param arr pointer to the array to dispose * @param count number of elements in the array */ -void NT_DisposeTopicNotificationArray(struct NT_TopicNotification* arr, - size_t count); +void NT_DisposeEventArray(struct NT_Event* arr, size_t count); /** - * Disposes a single topic notification. + * Disposes a single event. * - * @param info pointer to the info to dispose + * @param event pointer to the event to dispose */ -void NT_DisposeTopicNotification(struct NT_TopicNotification* info); - -/** - * Disposes an value notification array. - * - * @param arr pointer to the array to dispose - * @param count number of elements in the array - */ -void NT_DisposeValueNotificationArray(struct NT_ValueNotification* arr, - size_t count); - -/** - * Disposes a single value notification. - * - * @param info pointer to the info to dispose - */ -void NT_DisposeValueNotification(struct NT_ValueNotification* info); - -/** - * Disposes a connection notification array. - * - * @param arr pointer to the array to dispose - * @param count number of elements in the array - */ -void NT_DisposeConnectionNotificationArray( - struct NT_ConnectionNotification* arr, size_t count); - -/** - * Disposes a single connection notification. - * - * @param info pointer to the info to dispose - */ -void NT_DisposeConnectionNotification(struct NT_ConnectionNotification* info); - -/** - * Disposes a log message array. - * - * @param arr pointer to the array to dispose - * @param count number of elements in the array - */ -void NT_DisposeLogMessageArray(struct NT_LogMessage* arr, size_t count); - -/** - * Disposes a single log message. - * - * @param info pointer to the info to dispose - */ -void NT_DisposeLogMessage(struct NT_LogMessage* info); +void NT_DisposeEvent(struct NT_Event* event); /** * Returns monotonic current time in 1 us increments. @@ -1481,14 +1267,6 @@ void NT_SetNow(uint64_t timestamp); * @{ */ -/** - * Log function. - * - * @param data data pointer passed to NT_AddLogger() - * @param msg message information - */ -typedef void (*NT_LogFunc)(void* data, const struct NT_LogMessage* msg); - /** * Add logger callback function. By default, log messages are sent to stderr; * this function sends log messages to the provided callback function instead. @@ -1497,61 +1275,28 @@ typedef void (*NT_LogFunc)(void* data, const struct NT_LogMessage* msg); * messages outside this range will be silently ignored. * * @param inst instance handle - * @param data data pointer to pass to func - * @param func log callback function * @param min_level minimum log level * @param max_level maximum log level - * @return Logger handle + * @param data data pointer to pass to func + * @param func listener callback function + * @return Listener handle */ -NT_Logger NT_AddLogger(NT_Inst inst, void* data, NT_LogFunc func, - unsigned int min_level, unsigned int max_level); +NT_Listener NT_AddLogger(NT_Inst inst, unsigned int min_level, + unsigned int max_level, void* data, + NT_ListenerCallback func); /** - * Create a log poller. A poller provides a single queue of poll events. - * The returned handle must be destroyed with NT_DestroyLoggerPoller(). - * - * @param inst instance handle - * @return poller handle - */ -NT_LoggerPoller NT_CreateLoggerPoller(NT_Inst inst); - -/** - * Destroy a log poller. This will abort any blocked polling call and prevent - * additional events from being generated for this poller. - * - * @param poller poller handle - */ -void NT_DestroyLoggerPoller(NT_LoggerPoller poller); - -/** - * Set the log level for a log poller. Events will only be generated for + * Set the log level for a listener poller. Events will only be generated for * log messages with level greater than or equal to min_level and less than or * equal to max_level; messages outside this range will be silently ignored. * * @param poller poller handle * @param min_level minimum log level * @param max_level maximum log level - * @return Logger handle + * @return Listener handle */ -NT_Logger NT_AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level, - unsigned int max_level); - -/** - * Get the next log event. This blocks until the next log occurs. - * - * @param poller poller handle - * @param len length of returned array (output) - * @return Array of information on the log events. Only returns NULL if an - * error occurred (e.g. the instance was invalid or is shutting down). - */ -struct NT_LogMessage* NT_ReadLoggerQueue(NT_LoggerPoller poller, size_t* len); - -/** - * Remove a logger. - * - * @param logger Logger handle to remove - */ -void NT_RemoveLogger(NT_Logger logger); +NT_Listener NT_AddPolledLogger(NT_ListenerPoller poller, unsigned int min_level, + unsigned int max_level); /** @} */ diff --git a/ntcore/src/main/native/include/ntcore_cpp.h b/ntcore/src/main/native/include/ntcore_cpp.h index c5df2243f4..8c51d60037 100644 --- a/ntcore/src/main/native/include/ntcore_cpp.h +++ b/ntcore/src/main/native/include/ntcore_cpp.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "networktables/NetworkTableValue.h" @@ -40,6 +41,47 @@ namespace nt { * @{ */ +/** + * Event notification flags. + * + * The flags are a bitmask and must be OR'ed together to indicate the + * combination of events desired to be received. + * + */ +struct EventFlags { + EventFlags() = delete; + + static constexpr unsigned int kNone = NT_EVENT_NONE; + /** + * Initial listener addition. + * Set this flag to receive immediate notification of matches to the + * flag criteria. + */ + static constexpr unsigned int kImmediate = NT_EVENT_IMMEDIATE; + /** Client connected (on server, any client connected). */ + static constexpr unsigned int kConnected = NT_EVENT_CONNECTED; + /** Client disconnected (on server, any client disconnected). */ + static constexpr unsigned int kDisconnected = NT_EVENT_DISCONNECTED; + /** Any connection event (connect or disconnect). */ + static constexpr unsigned int kConnection = kConnected | kDisconnected; + /** New topic published. */ + static constexpr unsigned int kPublish = NT_EVENT_PUBLISH; + /** Topic unpublished. */ + static constexpr unsigned int kUnpublish = NT_EVENT_UNPUBLISH; + /** Topic properties changed. */ + static constexpr unsigned int kProperties = NT_EVENT_PROPERTIES; + /** Any topic event (publish, unpublish, or properties changed). */ + static constexpr unsigned int kTopic = kPublish | kUnpublish | kProperties; + /** Topic value updated (via network). */ + static constexpr unsigned int kValueRemote = NT_EVENT_VALUE_REMOTE; + /** Topic value updated (local). */ + static constexpr unsigned int kValueLocal = NT_EVENT_VALUE_LOCAL; + /** Topic value updated (network or local). */ + static constexpr unsigned int kValueAll = kValueRemote | kValueLocal; + /** Log message. */ + static constexpr unsigned int kLogMessage = NT_EVENT_LOGMESSAGE; +}; + /** NetworkTables Topic Information */ struct TopicInfo { /** Topic handle */ @@ -106,47 +148,12 @@ struct ConnectionInfo { } }; -/** NetworkTables Topic Notification */ -class TopicNotification { +/** NetworkTables Value Event Data */ +class ValueEventData { public: - TopicNotification() = default; - TopicNotification(NT_TopicListener listener_, TopicInfo info_, - unsigned int flags_) - : listener(listener_), info(std::move(info_)), flags(flags_) {} - - /** Listener that was triggered. */ - NT_TopicListener listener{0}; - - /** Topic info. */ - TopicInfo info; - - /** - * Notification flags. - */ - unsigned int flags{0}; - - friend void swap(TopicNotification& first, TopicNotification& second) { - using std::swap; - swap(first.listener, second.listener); - swap(first.info, second.info); - swap(first.flags, second.flags); - } -}; - -/** NetworkTables Value Notification */ -class ValueNotification { - public: - ValueNotification() = default; - ValueNotification(NT_ValueListener listener_, NT_Topic topic_, - NT_Handle subentry_, Value value_, unsigned int flags_) - : listener(listener_), - topic(topic_), - subentry(subentry_), - value(std::move(value_)), - flags(flags_) {} - - /** Listener that was triggered. */ - NT_ValueListener listener{0}; + ValueEventData() = default; + ValueEventData(NT_Topic topic, NT_Handle subentry, Value value) + : topic{topic}, subentry{subentry}, value{std::move(value)} {} /** Topic handle. */ NT_Topic topic{0}; @@ -156,63 +163,15 @@ class ValueNotification { /** The new value. */ Value value; - - /** - * Update flags. For example, NT_NOTIFY_NEW if the key did not previously - * exist. - */ - unsigned int flags{0}; - - friend void swap(ValueNotification& first, ValueNotification& second) { - using std::swap; - swap(first.listener, second.listener); - swap(first.topic, second.topic); - swap(first.subentry, second.subentry); - swap(first.value, second.value); - swap(first.flags, second.flags); - } -}; - -/** NetworkTables Connection Notification */ -class ConnectionNotification { - public: - ConnectionNotification() = default; - ConnectionNotification(NT_ConnectionListener listener_, bool connected_, - ConnectionInfo conn_) - : listener(listener_), connected(connected_), conn(std::move(conn_)) {} - - /** Listener that was triggered. */ - NT_ConnectionListener listener{0}; - - /** True if event is due to connection being established. */ - bool connected = false; - - /** Connection info. */ - ConnectionInfo conn; - - friend void swap(ConnectionNotification& first, - ConnectionNotification& second) { - using std::swap; - swap(first.listener, second.listener); - swap(first.connected, second.connected); - swap(first.conn, second.conn); - } }; /** NetworkTables log message. */ class LogMessage { public: LogMessage() = default; - LogMessage(NT_Logger logger_, unsigned int level_, std::string_view filename_, - unsigned int line_, std::string_view message_) - : logger(logger_), - level(level_), - filename(filename_), - line(line_), - message(message_) {} - - /** The logger that generated the message. */ - NT_Logger logger{0}; + LogMessage(unsigned int level, std::string_view filename, unsigned int line, + std::string_view message) + : level{level}, filename{filename}, line{line}, message{message} {} /** Log level of the message. See NT_LogLevel. */ unsigned int level{0}; @@ -225,15 +184,71 @@ class LogMessage { /** The message. */ std::string message; +}; - friend void swap(LogMessage& first, LogMessage& second) { - using std::swap; - swap(first.logger, second.logger); - swap(first.level, second.level); - swap(first.filename, second.filename); - swap(first.line, second.line); - swap(first.message, second.message); +/** NetworkTables event */ +class Event { + public: + Event() = default; + Event(NT_Listener listener, unsigned int flags, ConnectionInfo info) + : listener{listener}, flags{flags}, data{std::move(info)} {} + Event(NT_Listener listener, unsigned int flags, TopicInfo info) + : listener{listener}, flags{flags}, data{std::move(info)} {} + Event(NT_Listener listener, unsigned int flags, ValueEventData data) + : listener{listener}, flags{flags}, data{std::move(data)} {} + Event(NT_Listener listener, unsigned int flags, LogMessage msg) + : listener{listener}, flags{flags}, data{std::move(msg)} {} + Event(NT_Listener listener, unsigned int flags, NT_Topic topic, + NT_Handle subentry, Value value) + : listener{listener}, + flags{flags}, + data{ValueEventData{topic, subentry, std::move(value)}} {} + Event(NT_Listener listener, unsigned int flags, unsigned int level, + std::string_view filename, unsigned int line, std::string_view message) + : listener{listener}, + flags{flags}, + data{LogMessage{level, filename, line, message}} {} + + /** Listener that triggered this event. */ + NT_Listener listener{0}; + + /** + * Event flags (NT_EventFlags). Also indicates the data included with the + * event: + * - NT_NOTIFY_CONNECTED or NT_NOTIFY_DISCONNECTED: GetConnectionInfo() + * - NT_NOTIFY_PUBLISH, NT_NOTIFY_UNPUBLISH, or NT_NOTIFY_PROPERTIES: + * GetTopicInfo() + * - NT_NOTIFY_VALUE, NT_NOTIFY_VALUE_LOCAL: GetValueData() + * - NT_NOTIFY_LOGMESSAGE: GetLogMessage() + */ + unsigned int flags{0}; + + /** Event data; content depends on flags. */ + std::variant data; + + const ConnectionInfo* GetConnectionInfo() const { + return std::get_if(&data); } + ConnectionInfo* GetConnectionInfo() { + return std::get_if(&data); + } + + const TopicInfo* GetTopicInfo() const { + return std::get_if(&data); + } + TopicInfo* GetTopicInfo() { return std::get_if(&data); } + + const ValueEventData* GetValueEventData() const { + return std::get_if(&data); + } + ValueEventData* GetValueEventData() { + return std::get_if(&data); + } + + const LogMessage* GetLogMessage() const { + return std::get_if(&data); + } + LogMessage* GetLogMessage() { return std::get_if(&data); } }; /** NetworkTables publish/subscribe option. */ @@ -771,272 +786,134 @@ void UnsubscribeMultiple(NT_MultiSubscriber sub); /** @} */ /** - * @defgroup ntcore_topiclistener_func Topic Listener Functions + * @defgroup ntcore_listener_func Listener Functions * @{ */ +using ListenerCallback = std::function; + +/** + * Creates a listener poller. + * + * A poller provides a single queue of poll events. Events linked to this + * poller (using AddPolledListener()) will be stored in the queue and + * must be collected by calling ReadListenerQueue(). + * The returned handle must be destroyed with DestroyListenerPoller(). + * + * @param inst instance handle + * @return poller handle + */ +NT_ListenerPoller CreateListenerPoller(NT_Inst inst); + +/** + * Destroys a listener poller. This will abort any blocked polling + * call and prevent additional events from being generated for this poller. + * + * @param poller poller handle + */ +void DestroyListenerPoller(NT_ListenerPoller poller); + +/** + * Read notifications. + * + * @param poller poller handle + * @return Array of events. Returns empty array if no events since last call. + */ +std::vector ReadListenerQueue(NT_ListenerPoller poller); + +/** + * Removes a listener. + * + * @param listener Listener handle to remove + */ +void RemoveListener(NT_Listener listener); + +/** + * Wait for the listener queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the listener + * queue is empty (e.g. there are no more events that need to be passed along to + * callbacks or poll queues) or the timeout expires. + * + * @param handle handle + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a + * negative value to block indefinitely + * @return False if timed out, otherwise true. + */ +bool WaitForListenerQueue(NT_Handle handle, double timeout); + /** * Create a listener for changes to topics with names that start with any of - * the given prefixes. + * the given prefixes. This creates a corresponding internal subscriber with the + * lifetime of the listener. * * @param inst Instance handle * @param prefixes Topic name string prefixes - * @param mask Bitmask of NT_TopicListenerFlags values + * @param mask Bitmask of NT_EventFlags values (only topic and value events will + * be generated) * @param callback Listener function */ -NT_TopicListener AddTopicListener( - NT_Inst inst, std::span prefixes, unsigned int mask, - std::function callback); +NT_Listener AddListener(NT_Inst inst, + std::span prefixes, + unsigned int mask, ListenerCallback callback); /** - * Create a listener for changes on a particular topic. + * Create a listener. * - * @param handle Topic, subscriber, multi-subscriber, or entry handle - * @param mask Bitmask of NT_TopicListenerFlags values + * Some combinations of handle and mask have no effect: + * - connection and log message events are only generated on instances + * - topic and value events are only generated on non-instances + * + * Adding value and topic events on a topic will create a corresponding internal + * subscriber with the lifetime of the listener. + * + * Adding a log message listener through this function will only result in + * events at NT_LOG_INFO or higher; for more customized settings, use + * AddLogger(). + * + * @param handle Instance, topic, subscriber, multi-subscriber, or entry handle + * @param mask Bitmask of NT_EventFlags values * @param callback Listener function */ -NT_TopicListener AddTopicListener( - NT_Handle handle, unsigned int mask, - std::function callback); +NT_Listener AddListener(NT_Handle handle, unsigned int mask, + ListenerCallback callback); /** - * Wait for the topic listener queue to be empty. This is primarily useful - * for deterministic testing. This blocks until either the topic listener - * queue is empty (e.g. there are no more events that need to be passed along to - * callbacks or poll queues) or the timeout expires. + * Creates a polled listener. This creates a corresponding internal subscriber + * with the lifetime of the listener. + * The caller is responsible for calling ReadListenerQueue() to poll. * - * @param handle handle - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a - * negative value to block indefinitely - * @return False if timed out, otherwise true. - */ -bool WaitForTopicListenerQueue(NT_Handle handle, double timeout); - -/** - * Creates a topic listener poller. - * - * A poller provides a single queue of poll events. Events linked to this - * poller (using AddPolledTopicListener()) will be stored in the queue and - * must be collected by calling ReadTopicListenerQueue(). - * The returned handle must be destroyed with DestroyTopicListenerPoller(). - * - * @param inst instance handle - * @return poller handle - */ -NT_TopicListenerPoller CreateTopicListenerPoller(NT_Inst inst); - -/** - * Destroys a topic listener poller. This will abort any blocked polling - * call and prevent additional events from being generated for this poller. - * - * @param poller poller handle - */ -void DestroyTopicListenerPoller(NT_TopicListenerPoller poller); - -/** - * Read topic notifications. - * - * @param poller poller handle - * @return Array of topic notifications. Returns empty array if no - * notifications since last call. - */ -std::vector ReadTopicListenerQueue( - NT_TopicListenerPoller poller); - -/** - * Creates a polled topic listener. - * The caller is responsible for calling ReadTopicListenerQueue() to poll. - * - * @param poller poller handle - * @param prefixes array of UTF-8 string prefixes - * @param mask NT_NotifyKind bitmask + * @param poller poller handle + * @param prefixes array of UTF-8 string prefixes + * @param mask Bitmask of NT_EventFlags values (only topic and value events will + * be generated) * @return Listener handle */ -NT_TopicListener AddPolledTopicListener( - NT_TopicListenerPoller poller, std::span prefixes, - unsigned int mask); +NT_Listener AddPolledListener(NT_ListenerPoller poller, + std::span prefixes, + unsigned int mask); /** - * Creates a polled topic listener. - * The caller is responsible for calling ReadTopicListenerQueue() to poll. + * Creates a polled listener. + * The caller is responsible for calling ReadListenerQueue() to poll. * - * @param poller poller handle - * @param handle topic, subscriber, multi-subscriber, or entry handle - * @param mask NT_NotifyKind bitmask + * Some combinations of handle and mask have no effect: + * - connection and log message events are only generated on instances + * - topic and value events are only generated on non-instances + * + * Adding value and topic events on a topic will create a corresponding internal + * subscriber with the lifetime of the listener. + * + * Adding a log message listener through this function will only result in + * events at NT_LOG_INFO or higher; for more customized settings, use + * AddPolledLogger(). + * + * @param poller poller handle + * @param handle instance, topic, subscriber, multi-subscriber, or entry handle + * @param mask NT_EventFlags bitmask * @return Listener handle */ -NT_TopicListener AddPolledTopicListener(NT_TopicListenerPoller poller, - NT_Handle handle, unsigned int mask); - -/** - * Removes a topic listener. - * - * @param listener Listener handle to remove - */ -void RemoveTopicListener(NT_TopicListener listener); - -/** @} */ - -/** - * @defgroup ntcore_valuelistener_func Value Listener Functions - * @{ - */ - -/** - * Create a listener for value changes on a subscriber. - * - * @param subentry Subscriber/entry - * @param mask Bitmask of NT_ValueListenerFlags values - * @param callback Listener function - */ -NT_ValueListener AddValueListener( - NT_Handle subentry, unsigned int mask, - std::function callback); - -/** - * Wait for the value listener queue to be empty. This is primarily useful - * for deterministic testing. This blocks until either the value listener - * queue is empty (e.g. there are no more events that need to be passed along to - * callbacks or poll queues) or the timeout expires. - * - * @param handle handle - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a - * negative value to block indefinitely - * @return False if timed out, otherwise true. - */ -bool WaitForValueListenerQueue(NT_Handle handle, double timeout); - -/** - * Create a value listener poller. - * - * A poller provides a single queue of poll events. Events linked to this - * poller (using AddPolledValueListener()) will be stored in the queue and - * must be collected by calling ReadValueListenerQueue(). - * The returned handle must be destroyed with DestroyValueListenerPoller(). - * - * @param inst instance handle - * @return poller handle - */ -NT_ValueListenerPoller CreateValueListenerPoller(NT_Inst inst); - -/** - * Destroy a value listener poller. This will abort any blocked polling - * call and prevent additional events from being generated for this poller. - * - * @param poller poller handle - */ -void DestroyValueListenerPoller(NT_ValueListenerPoller poller); - -/** - * Reads value listener queue (all value changes since last call). - * - * @param poller poller handle - * @return Array of value notifications. - */ -std::vector ReadValueListenerQueue( - NT_ValueListenerPoller poller); - -/** - * Create a polled value listener. - * The caller is responsible for calling ReadValueListenerQueue() to poll. - * - * @param poller poller handle - * @param subentry subscriber or entry handle - * @param mask NT_NotifyKind bitmask - * @return Listener handle - */ -NT_ValueListener AddPolledValueListener(NT_ValueListenerPoller poller, - NT_Handle subentry, unsigned int mask); - -/** - * Remove a value listener. - * - * @param listener Listener handle to remove - */ -void RemoveValueListener(NT_ValueListener listener); - -/** @} */ - -/** - * @defgroup ntcore_connectionlistener_func Connection Listener Functions - * @{ - */ - -/** - * Add a connection listener. - * - * @param inst instance handle - * @param callback listener to add - * @param immediate_notify notify listener of all existing connections - * @return Listener handle - */ -NT_ConnectionListener AddConnectionListener( - NT_Inst inst, bool immediate_notify, - std::function callback); - -/** - * Wait for the connection listener queue to be empty. This is primarily useful - * for deterministic testing. This blocks until either the connection listener - * queue is empty (e.g. there are no more events that need to be passed along to - * callbacks or poll queues) or the timeout expires. - * - * @param handle handle - * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a - * negative value to block indefinitely - * @return False if timed out, otherwise true. - */ -bool WaitForConnectionListenerQueue(NT_Handle handle, double timeout); - -/** - * Create a connection listener poller. - * - * A poller provides a single queue of poll events. Events linked to this - * poller (using AddPolledConnectionListener()) will be stored in the queue and - * must be collected by calling PollConnectionListener(). - * The returned handle must be destroyed with DestroyConnectionListenerPoller(). - * - * @param inst instance handle - * @return poller handle - */ -NT_ConnectionListenerPoller CreateConnectionListenerPoller(NT_Inst inst); - -/** - * Destroy a connection listener poller. This will abort any blocked polling - * call and prevent additional events from being generated for this poller. - * - * @param poller poller handle - */ -void DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller); - -/** - * Create a polled connection listener. - * The caller is responsible for calling PollConnectionListener() to poll. - * - * @param poller poller handle - * @param immediate_notify notify listener of all existing connections - */ -NT_ConnectionListener AddPolledConnectionListener( - NT_ConnectionListenerPoller poller, bool immediate_notify); - -/** - * Get the next connection event. This blocks until the next connect or - * disconnect occurs. This is intended to be used with - * AddPolledConnectionListener(); connection listeners created using - * AddConnectionListener() will not be serviced through this function. - * - * @param poller poller handle - * @return Information on the connection events. Only returns empty if an - * error occurred (e.g. the instance was invalid or is shutting down). - */ -std::vector ReadConnectionListenerQueue( - NT_ConnectionListenerPoller poller); - -/** - * Remove a connection listener. - * - * @param conn_listener Listener handle to remove - */ -void RemoveConnectionListener(NT_ConnectionListener conn_listener); +NT_Listener AddPolledListener(NT_ListenerPoller poller, NT_Handle handle, + unsigned int mask); /** @} */ @@ -1313,31 +1190,13 @@ void StopConnectionDataLog(NT_ConnectionDataLogger logger); * messages outside this range will be silently ignored. * * @param inst instance handle - * @param func log callback function * @param min_level minimum log level * @param max_level maximum log level - * @return Logger handle + * @param func listener callback function + * @return Listener handle */ -NT_Logger AddLogger(NT_Inst inst, - std::function func, - unsigned int min_level, unsigned int max_level); - -/** - * Create a log poller. A poller provides a single queue of poll events. - * The returned handle must be destroyed with DestroyLoggerPoller(). - * - * @param inst instance handle - * @return poller handle - */ -NT_LoggerPoller CreateLoggerPoller(NT_Inst inst); - -/** - * Destroy a log poller. This will abort any blocked polling call and prevent - * additional events from being generated for this poller. - * - * @param poller poller handle - */ -void DestroyLoggerPoller(NT_LoggerPoller poller); +NT_Listener AddLogger(NT_Inst inst, unsigned int min_level, + unsigned int max_level, ListenerCallback func); /** * Set the log level for a log poller. Events will only be generated for @@ -1349,24 +1208,8 @@ void DestroyLoggerPoller(NT_LoggerPoller poller); * @param max_level maximum log level * @return Logger handle */ -NT_Logger AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level, - unsigned int max_level); - -/** - * Get the next log event. This blocks until the next log occurs. - * - * @param poller poller handle - * @return Information on the log events. Only returns empty if an error - * occurred (e.g. the instance was invalid or is shutting down). - */ -std::vector ReadLoggerQueue(NT_LoggerPoller poller); - -/** - * Remove a logger. - * - * @param logger Logger handle to remove - */ -void RemoveLogger(NT_Logger logger); +NT_Listener AddPolledLogger(NT_ListenerPoller poller, unsigned int min_level, + unsigned int max_level); /** @} */ /** @} */ diff --git a/ntcore/src/test/java/edu/wpi/first/networktables/ConnectionListenerTest.java b/ntcore/src/test/java/edu/wpi/first/networktables/ConnectionListenerTest.java index b3ce5909c3..434e9279d5 100644 --- a/ntcore/src/test/java/edu/wpi/first/networktables/ConnectionListenerTest.java +++ b/ntcore/src/test/java/edu/wpi/first/networktables/ConnectionListenerTest.java @@ -8,7 +8,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import edu.wpi.first.util.WPIUtilJNI; @@ -61,9 +60,11 @@ class ConnectionListenerTest { @Test void testJNI() { // set up the poller - int poller = NetworkTablesJNI.createConnectionListenerPoller(m_serverInst.getHandle()); + int poller = NetworkTablesJNI.createListenerPoller(m_serverInst.getHandle()); assertNotSame(poller, 0, "bad poller handle"); - int handle = NetworkTablesJNI.addPolledConnectionListener(poller, false); + int handle = + NetworkTablesJNI.addListener( + poller, m_serverInst.getHandle(), NetworkTableEvent.kConnection); assertNotSame(handle, 0, "bad listener handle"); // trigger a connect event @@ -75,13 +76,13 @@ class ConnectionListenerTest { } catch (InterruptedException ex) { fail("interrupted while waiting for queue"); } - ConnectionNotification[] events = - NetworkTablesJNI.readConnectionListenerQueue(m_serverInst, poller); + NetworkTableEvent[] events = NetworkTablesJNI.readListenerQueue(m_serverInst, poller); assertNotNull(events); assertEquals(1, events.length); assertEquals(handle, events[0].listener); - assertTrue(events[0].connected); + assertNotNull(events[0].connInfo); + assertEquals(events[0].flags, NetworkTableEvent.kConnected); // trigger a disconnect event m_clientInst.stopClient(); @@ -97,12 +98,12 @@ class ConnectionListenerTest { } catch (InterruptedException ex) { fail("interrupted while waiting for queue"); } - events = NetworkTablesJNI.readConnectionListenerQueue(m_serverInst, poller); + events = NetworkTablesJNI.readListenerQueue(m_serverInst, poller); assertNotNull(events); assertEquals(1, events.length); assertEquals(handle, events[0].listener); - assertFalse(events[0].connected); + assertEquals(events[0].flags, NetworkTableEvent.kDisconnected); } private static int threadedPort = 10001; @@ -111,7 +112,7 @@ class ConnectionListenerTest { @ValueSource(strings = {"127.0.0.1", "127.0.0.1 ", " 127.0.0.1 "}) void testThreaded(String address) { m_serverInst.startServer("connectionlistenertest.json", address, 0, threadedPort); - List events = new ArrayList<>(); + List events = new ArrayList<>(); final int handle = m_serverInst.addConnectionListener( false, @@ -147,13 +148,14 @@ class ConnectionListenerTest { } // wait for thread - m_serverInst.waitForConnectionListenerQueue(1.0); + m_serverInst.waitForListenerQueue(1.0); // get the event synchronized (events) { assertEquals(1, events.size()); assertEquals(handle, events.get(0).listener); - assertTrue(events.get(0).connected); + assertNotNull(events.get(0).connInfo); + assertEquals(events.get(0).flags, NetworkTableEvent.kConnected); events.clear(); } @@ -166,7 +168,7 @@ class ConnectionListenerTest { } // wait for thread - m_serverInst.waitForConnectionListenerQueue(1.0); + m_serverInst.waitForListenerQueue(1.0); // get the event try { @@ -177,7 +179,8 @@ class ConnectionListenerTest { synchronized (events) { assertEquals(1, events.size()); assertEquals(handle, events.get(0).listener); - assertFalse(events.get(0).connected); + assertNotNull(events.get(0).connInfo); + assertEquals(events.get(0).flags, NetworkTableEvent.kDisconnected); } } } diff --git a/ntcore/src/test/java/edu/wpi/first/networktables/LoggerTest.java b/ntcore/src/test/java/edu/wpi/first/networktables/LoggerTest.java index 6a5a489ae5..17e8d43455 100644 --- a/ntcore/src/test/java/edu/wpi/first/networktables/LoggerTest.java +++ b/ntcore/src/test/java/edu/wpi/first/networktables/LoggerTest.java @@ -28,8 +28,8 @@ class LoggerTest { @Test void addMessageTest() { - List msgs = new ArrayList<>(); - m_clientInst.addLogger(msgs::add, LogMessage.kInfo, 100); + List msgs = new ArrayList<>(); + m_clientInst.addLogger(LogMessage.kInfo, 100, msgs::add); m_clientInst.startClient4("client"); m_clientInst.setServer("127.0.0.1", 10000); diff --git a/ntcore/src/test/java/edu/wpi/first/networktables/TopicListenerTest.java b/ntcore/src/test/java/edu/wpi/first/networktables/TopicListenerTest.java index 17fb6c3d51..130628d65a 100644 --- a/ntcore/src/test/java/edu/wpi/first/networktables/TopicListenerTest.java +++ b/ntcore/src/test/java/edu/wpi/first/networktables/TopicListenerTest.java @@ -5,6 +5,7 @@ package edu.wpi.first.networktables; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; import edu.wpi.first.util.WPIUtilJNI; @@ -35,8 +36,8 @@ class TopicListenerTest { m_clientInst.setServer("127.0.0.1", 10010); // Use connection listener to ensure we've connected - int poller = NetworkTablesJNI.createConnectionListenerPoller(m_clientInst.getHandle()); - NetworkTablesJNI.addPolledConnectionListener(poller, false); + int poller = NetworkTablesJNI.createListenerPoller(m_clientInst.getHandle()); + NetworkTablesJNI.addListener(poller, m_clientInst.getHandle(), NetworkTableEvent.kConnected); try { if (WPIUtilJNI.waitForObjectTimeout(poller, 1.0)) { fail("client didn't connect to server"); @@ -52,10 +53,9 @@ class TopicListenerTest { @Test void testPrefixNewRemote() { connect(); - final int poller = NetworkTablesJNI.createTopicListenerPoller(m_serverInst.getHandle()); + final int poller = NetworkTablesJNI.createListenerPoller(m_serverInst.getHandle()); final int handle = - NetworkTablesJNI.addPolledTopicListener( - poller, new String[] {"/foo"}, TopicListenerFlags.kPublish); + NetworkTablesJNI.addListener(poller, new String[] {"/foo"}, NetworkTableEvent.kPublish); // Trigger an event m_clientInst.getEntry("/foo/bar").setDouble(1.0); @@ -75,13 +75,14 @@ class TopicListenerTest { Thread.currentThread().interrupt(); fail("interrupted while waiting for signal"); } - TopicNotification[] events = NetworkTablesJNI.readTopicListenerQueue(m_serverInst, poller); + NetworkTableEvent[] events = NetworkTablesJNI.readListenerQueue(m_serverInst, poller); // Check the event assertEquals(1, events.length); assertEquals(handle, events[0].listener); - assertEquals(m_serverInst.getTopic("/foo/bar"), events[0].info.getTopic()); - assertEquals("/foo/bar", events[0].info.name); - assertEquals(TopicListenerFlags.kPublish, events[0].flags); + assertNotNull(events[0].topicInfo); + assertEquals(m_serverInst.getTopic("/foo/bar"), events[0].topicInfo.getTopic()); + assertEquals("/foo/bar", events[0].topicInfo.name); + assertEquals(NetworkTableEvent.kPublish, events[0].flags); } } diff --git a/ntcore/src/test/native/cpp/ConnectionListenerTest.cpp b/ntcore/src/test/native/cpp/ConnectionListenerTest.cpp index 4c3624bbef..15612773b7 100644 --- a/ntcore/src/test/native/cpp/ConnectionListenerTest.cpp +++ b/ntcore/src/test/native/cpp/ConnectionListenerTest.cpp @@ -49,10 +49,10 @@ void ConnectionListenerTest::Connect(const char* address, unsigned int port3, TEST_F(ConnectionListenerTest, Polled) { // set up the poller - NT_ConnectionListenerPoller poller = - nt::CreateConnectionListenerPoller(server_inst); + NT_ListenerPoller poller = nt::CreateListenerPoller(server_inst); ASSERT_NE(poller, 0u); - NT_ConnectionListener handle = nt::AddPolledConnectionListener(poller, false); + NT_Listener handle = + nt::AddPolledListener(poller, server_inst, nt::EventFlags::kConnection); ASSERT_NE(handle, 0u); // trigger a connect event @@ -62,10 +62,11 @@ TEST_F(ConnectionListenerTest, Polled) { bool timed_out = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timed_out)); ASSERT_FALSE(timed_out); - auto result = nt::ReadConnectionListenerQueue(poller); + auto result = nt::ReadListenerQueue(poller); ASSERT_EQ(result.size(), 1u); EXPECT_EQ(handle, result[0].listener); - EXPECT_TRUE(result[0].connected); + EXPECT_TRUE(result[0].GetConnectionInfo()); + EXPECT_EQ(result[0].flags, nt::EventFlags::kConnected); // trigger a disconnect event nt::StopClient(client_inst); @@ -75,10 +76,11 @@ TEST_F(ConnectionListenerTest, Polled) { timed_out = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timed_out)); ASSERT_FALSE(timed_out); - result = nt::ReadConnectionListenerQueue(poller); + result = nt::ReadListenerQueue(poller); ASSERT_EQ(result.size(), 1u); EXPECT_EQ(handle, result[0].listener); - EXPECT_FALSE(result[0].connected); + EXPECT_TRUE(result[0].GetConnectionInfo()); + EXPECT_EQ(result[0].flags, nt::EventFlags::kDisconnected); } class ConnectionListenerVariantTest @@ -87,12 +89,12 @@ class ConnectionListenerVariantTest TEST_P(ConnectionListenerVariantTest, Threaded) { wpi::mutex m; - std::vector result; - auto handle = nt::AddConnectionListener( - server_inst, false, [&](const nt::ConnectionNotification& event) { - std::scoped_lock lock{m}; - result.push_back(event); - }); + std::vector result; + auto handle = nt::AddListener(server_inst, nt::EventFlags::kConnection, + [&](auto& event) { + std::scoped_lock lock{m}; + result.push_back(event); + }); // trigger a connect event Connect(GetParam().first, 0, 20001 + GetParam().second); @@ -106,7 +108,8 @@ TEST_P(ConnectionListenerVariantTest, Threaded) { std::scoped_lock lock{m}; ASSERT_EQ(result.size(), 1u); EXPECT_EQ(handle, result[0].listener); - EXPECT_TRUE(result[0].connected); + EXPECT_TRUE(result[0].GetConnectionInfo()); + EXPECT_EQ(result[0].flags, nt::EventFlags::kConnected); result.clear(); } @@ -115,14 +118,15 @@ TEST_P(ConnectionListenerVariantTest, Threaded) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); // wait for thread - nt::WaitForConnectionListenerQueue(server_inst, 1.0); + nt::WaitForListenerQueue(server_inst, 1.0); // get the event { std::scoped_lock lock{m}; ASSERT_EQ(result.size(), 1u); EXPECT_EQ(handle, result[0].listener); - EXPECT_FALSE(result[0].connected); + EXPECT_TRUE(result[0].GetConnectionInfo()); + EXPECT_EQ(result[0].flags, nt::EventFlags::kDisconnected); } } diff --git a/ntcore/src/test/native/cpp/LocalStorageTest.cpp b/ntcore/src/test/native/cpp/LocalStorageTest.cpp index 96a0ee76c4..83386465df 100644 --- a/ntcore/src/test/native/cpp/LocalStorageTest.cpp +++ b/ntcore/src/test/native/cpp/LocalStorageTest.cpp @@ -3,6 +3,7 @@ // the WPILib BSD license file in the root directory of this project. #include "LocalStorage.h" +#include "MockListenerStorage.h" #include "MockLogger.h" #include "PubSubOptionsMatcher.h" #include "SpanMatcher.h" @@ -36,7 +37,8 @@ class LocalStorageTest : public ::testing::Test { ::testing::StrictMock startup; ::testing::StrictMock network; wpi::MockLogger logger; - LocalStorage storage{0, logger}; + MockListenerStorage listenerStorage; + LocalStorage storage{0, listenerStorage, logger}; NT_Topic fooTopic{storage.GetTopic("foo")}; NT_Topic barTopic{storage.GetTopic("bar")}; NT_Topic bazTopic{storage.GetTopic("baz")}; diff --git a/ntcore/src/test/native/cpp/LoggerTest.cpp b/ntcore/src/test/native/cpp/LoggerTest.cpp new file mode 100644 index 0000000000..a9f499c7ac --- /dev/null +++ b/ntcore/src/test/native/cpp/LoggerTest.cpp @@ -0,0 +1,96 @@ +// 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 + +#include "Handle.h" +#include "TestPrinters.h" +#include "gtest/gtest.h" +#include "ntcore_cpp.h" + +class LoggerTest : public ::testing::Test { + public: + LoggerTest() : m_inst(nt::CreateInstance()) {} + + ~LoggerTest() override { nt::DestroyInstance(m_inst); } + + void Generate(); + void Check(const std::vector& events, NT_Listener handle, + bool infoMsg, bool errMsg); + + protected: + NT_Inst m_inst; +}; + +void LoggerTest::Generate() { + // generate info message + nt::StartClient4(m_inst, ""); + + // generate error message + nt::Publish(nt::Handle(nt::Handle{m_inst}.GetInst(), 5, nt::Handle::kTopic), + NT_DOUBLE, ""); +} + +void LoggerTest::Check(const std::vector& events, NT_Listener handle, + bool infoMsg, bool errMsg) { + size_t count = (infoMsg ? 1u : 0u) + (errMsg ? 1u : 0u); + ASSERT_EQ(events.size(), count); + for (size_t i = 0; i < count; ++i) { + ASSERT_EQ(events[i].listener, handle); + ASSERT_EQ(events[i].flags & nt::EventFlags::kLogMessage, + nt::EventFlags::kLogMessage); + auto log = events[i].GetLogMessage(); + ASSERT_TRUE(log); + if (infoMsg) { + ASSERT_EQ(log->message, "starting network client"); + ASSERT_EQ(log->level, NT_LOG_INFO); + infoMsg = false; + } else if (errMsg) { + ASSERT_EQ(log->message, + "trying to publish invalid topic handle (386924549)"); + ASSERT_EQ(log->level, NT_LOG_ERROR); + errMsg = false; + } + } +} + +TEST_F(LoggerTest, DefaultLogRange) { + auto poller = nt::CreateListenerPoller(m_inst); + auto handle = + nt::AddPolledListener(poller, m_inst, nt::EventFlags::kLogMessage); + + Generate(); + + bool timedOut = false; + ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); + auto events = nt::ReadListenerQueue(poller); + + Check(events, handle, true, true); +} + +TEST_F(LoggerTest, InfoOnly) { + auto poller = nt::CreateListenerPoller(m_inst); + auto handle = nt::AddPolledLogger(poller, NT_LOG_INFO, NT_LOG_INFO); + + Generate(); + + bool timedOut = false; + ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); + auto events = nt::ReadListenerQueue(poller); + + Check(events, handle, true, false); +} + +TEST_F(LoggerTest, Error) { + auto poller = nt::CreateListenerPoller(m_inst); + auto handle = nt::AddPolledLogger(poller, NT_LOG_ERROR, 100); + + Generate(); + + bool timedOut = false; + ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); + auto events = nt::ReadListenerQueue(poller); + + Check(events, handle, false, true); +} diff --git a/ntcore/src/test/native/cpp/MockListenerStorage.h b/ntcore/src/test/native/cpp/MockListenerStorage.h new file mode 100644 index 0000000000..e02d21f53e --- /dev/null +++ b/ntcore/src/test/native/cpp/MockListenerStorage.h @@ -0,0 +1,41 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "IListenerStorage.h" +#include "gmock/gmock.h" + +namespace nt { + +class MockListenerStorage : public IListenerStorage { + public: + MOCK_METHOD(void, Activate, + (NT_Listener listenerHandle, unsigned int mask, + FinishEventFunc finishEvent), + (override)); + MOCK_METHOD(void, Notify, + (std::span handles, unsigned int flags, + std::span infos), + (override)); + MOCK_METHOD(void, Notify, + (std::span handles, unsigned int flags, + std::span infos), + (override)); + MOCK_METHOD(void, Notify, + (std::span handles, unsigned int flags, + NT_Topic topic, NT_Handle subentry, const Value& value), + (override)); + MOCK_METHOD(void, Notify, + (unsigned int flags, unsigned int level, + std::string_view filename, unsigned int line, + std::string_view message), + (override)); +}; + +} // namespace nt diff --git a/ntcore/src/test/native/cpp/TestPrinters.cpp b/ntcore/src/test/native/cpp/TestPrinters.cpp index 8213468ff8..34afe4cd45 100644 --- a/ntcore/src/test/native/cpp/TestPrinters.cpp +++ b/ntcore/src/test/native/cpp/TestPrinters.cpp @@ -20,44 +20,32 @@ void PrintTo(const json& val, ::std::ostream* os) { } // namespace wpi namespace nt { -#if 0 -void PrintTo(const EntryNotification& event, std::ostream* os) { - *os << "EntryNotification{listener="; + +void PrintTo(const Event& event, std::ostream* os) { + *os << "Event{listener="; PrintTo(Handle{event.listener}, os); - *os << ", entry="; - PrintTo(Handle{event.entry}, os); - *os << ", name=\"" << event.name << "\", flags=" << event.flags << ", value="; - PrintTo(event.value, os); + *os << ", flags=" << event.flags; + // *os << ", name=\"" << event.name << "\", flags=" << event.flags + // << "value="; + // PrintTo(event.value, os); *os << '}'; } -#endif + void PrintTo(const Handle& handle, std::ostream* os) { *os << "Handle{"; switch (handle.GetType()) { - case Handle::kConnectionListener: - *os << "kConnectionListener"; + case Handle::kListener: + *os << "kListener"; break; - case Handle::kConnectionListenerPoller: - *os << "kConnectionListenerPoller"; + case Handle::kListenerPoller: + *os << "kListenerPoller"; break; case Handle::kEntry: *os << "kEntry"; break; - case Handle::kEntryListener: - *os << "kEntryListener"; - break; - case Handle::kEntryListenerPoller: - *os << "kEntryListenerPoller"; - break; case Handle::kInstance: *os << "kInstance"; break; - case Handle::kLogger: - *os << "kLogger"; - break; - case Handle::kLoggerPoller: - *os << "kLoggerPoller"; - break; case Handle::kTopic: *os << "kTopic"; break; diff --git a/ntcore/src/test/native/cpp/TestPrinters.h b/ntcore/src/test/native/cpp/TestPrinters.h index c7e6e9edc4..c9c7e0e191 100644 --- a/ntcore/src/test/native/cpp/TestPrinters.h +++ b/ntcore/src/test/native/cpp/TestPrinters.h @@ -49,12 +49,12 @@ struct ClientMessage; struct ServerMessage; } // namespace net -// class EntryNotification; +class Event; class Handle; class PubSubOptions; class Value; -// void PrintTo(const EntryNotification& event, std::ostream* os); +void PrintTo(const Event& event, std::ostream* os); void PrintTo(const Handle& handle, std::ostream* os); void PrintTo(const net3::Message3& msg, std::ostream* os); void PrintTo(const net::ClientMessage& msg, std::ostream* os); diff --git a/ntcore/src/test/native/cpp/TopicListenerTest.cpp b/ntcore/src/test/native/cpp/TopicListenerTest.cpp index 3be750b712..f0a002a49c 100644 --- a/ntcore/src/test/native/cpp/TopicListenerTest.cpp +++ b/ntcore/src/test/native/cpp/TopicListenerTest.cpp @@ -19,16 +19,16 @@ class TopicListenerTest : public ::testing::Test { TopicListenerTest() : m_serverInst(nt::CreateInstance()), m_clientInst(nt::CreateInstance()) { #if 0 - nt::AddLogger(server_inst, - [](const nt::LogMessage& msg) { - std::fprintf(stderr, "SERVER: %s\n", msg.message.c_str()); - }, - 0, UINT_MAX); - nt::AddLogger(client_inst, - [](const nt::LogMessage& msg) { - std::fprintf(stderr, "CLIENT: %s\n", msg.message.c_str()); - }, - 0, UINT_MAX); + nt::AddLogger(m_serverInst, 0, UINT_MAX, [](auto& event) { + if (auto msg = event.GetLogMessage()) { + std::fprintf(stderr, "SERVER: %s\n", msg->message.c_str()); + } + }); + nt::AddLogger(m_clientInst, 0, UINT_MAX, [](auto& event) { + if (auto msg = event.GetLogMessage()) { + std::fprintf(stderr, "CLIENT: %s\n", msg.message.c_str()); + } + }); #endif } @@ -39,9 +39,8 @@ class TopicListenerTest : public ::testing::Test { void Connect(unsigned int port); static void PublishTopics(NT_Inst inst); - void CheckEvents(const std::vector& events, - NT_TopicListener handle, unsigned int flags, - std::string_view topicName = "/foo/bar"); + void CheckEvents(const std::vector& events, NT_Listener handle, + unsigned int flags, std::string_view topicName = "/foo/bar"); protected: NT_Inst m_serverInst; @@ -54,9 +53,8 @@ void TopicListenerTest::Connect(unsigned int port) { nt::SetServer(m_clientInst, "127.0.0.1", port); // Use connection listener to ensure we've connected - NT_ConnectionListenerPoller poller = - nt::CreateConnectionListenerPoller(m_clientInst); - nt::AddPolledConnectionListener(poller, false); + NT_ListenerPoller poller = nt::CreateListenerPoller(m_clientInst); + nt::AddPolledListener(poller, m_clientInst, nt::EventFlags::kConnected); bool timedOut = false; if (!wpi::WaitForObject(poller, 1.0, &timedOut)) { FAIL() << "client didn't connect to server"; @@ -69,27 +67,29 @@ void TopicListenerTest::PublishTopics(NT_Inst inst) { nt::Publish(nt::GetTopic(inst, "/baz"), NT_DOUBLE, "double"); } -void TopicListenerTest::CheckEvents( - const std::vector& events, NT_TopicListener handle, - unsigned int flags, std::string_view topicName) { +void TopicListenerTest::CheckEvents(const std::vector& events, + NT_Listener handle, unsigned int flags, + std::string_view topicName) { ASSERT_EQ(events.size(), 1u); ASSERT_EQ(events[0].listener, handle); - ASSERT_EQ(events[0].info.topic, nt::GetTopic(m_serverInst, topicName)); - ASSERT_EQ(events[0].info.name, topicName); ASSERT_EQ(events[0].flags, flags); + auto topicInfo = events[0].GetTopicInfo(); + ASSERT_TRUE(topicInfo); + ASSERT_EQ(topicInfo->topic, nt::GetTopic(m_serverInst, topicName)); + ASSERT_EQ(topicInfo->name, topicName); } TEST_F(TopicListenerTest, TopicNewLocal) { - auto poller = nt::CreateTopicListenerPoller(m_serverInst); - auto handle = nt::AddPolledTopicListener( - poller, nt::GetTopic(m_serverInst, "/foo"), NT_TOPIC_NOTIFY_PUBLISH); + auto poller = nt::CreateListenerPoller(m_serverInst); + auto handle = nt::AddPolledListener( + poller, nt::GetTopic(m_serverInst, "/foo"), nt::EventFlags::kPublish); PublishTopics(m_serverInst); bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); - CheckEvents(events, handle, NT_TOPIC_NOTIFY_PUBLISH, "/foo"); + auto events = nt::ReadListenerQueue(poller); + CheckEvents(events, handle, nt::EventFlags::kPublish, "/foo"); } TEST_F(TopicListenerTest, DISABLED_TopicNewRemote) { @@ -97,9 +97,9 @@ TEST_F(TopicListenerTest, DISABLED_TopicNewRemote) { if (HasFatalFailure()) { return; } - auto poller = nt::CreateTopicListenerPoller(m_serverInst); - auto handle = nt::AddPolledTopicListener( - poller, nt::GetTopic(m_serverInst, "/foo"), NT_TOPIC_NOTIFY_PUBLISH); + auto poller = nt::CreateListenerPoller(m_serverInst); + auto handle = nt::AddPolledListener( + poller, nt::GetTopic(m_serverInst, "/foo"), nt::EventFlags::kPublish); PublishTopics(m_clientInst); @@ -108,54 +108,54 @@ TEST_F(TopicListenerTest, DISABLED_TopicNewRemote) { bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); - CheckEvents(events, handle, NT_TOPIC_NOTIFY_PUBLISH, "/foo"); + auto events = nt::ReadListenerQueue(poller); + CheckEvents(events, handle, nt::EventFlags::kPublish, "/foo"); } TEST_F(TopicListenerTest, TopicPublishImm) { PublishTopics(m_serverInst); - auto poller = nt::CreateTopicListenerPoller(m_serverInst); - auto handle = nt::AddPolledTopicListener( + auto poller = nt::CreateListenerPoller(m_serverInst); + auto handle = nt::AddPolledListener( poller, nt::GetTopic(m_serverInst, "/foo"), - NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_IMMEDIATE); + nt::EventFlags::kPublish | nt::EventFlags::kImmediate); bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); + auto events = nt::ReadListenerQueue(poller); CheckEvents(events, handle, - NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_IMMEDIATE, "/foo"); + nt::EventFlags::kPublish | nt::EventFlags::kImmediate, "/foo"); } TEST_F(TopicListenerTest, TopicUnpublishPropsImm) { PublishTopics(m_serverInst); - auto poller = nt::CreateTopicListenerPoller(m_serverInst); - nt::AddPolledTopicListener(poller, nt::GetTopic(m_serverInst, "/foo"), - NT_TOPIC_NOTIFY_UNPUBLISH | - NT_TOPIC_NOTIFY_PROPERTIES | - NT_TOPIC_NOTIFY_IMMEDIATE); + auto poller = nt::CreateListenerPoller(m_serverInst); + nt::AddPolledListener(poller, nt::GetTopic(m_serverInst, "/foo"), + nt::EventFlags::kUnpublish | + nt::EventFlags::kProperties | + nt::EventFlags::kImmediate); bool timedOut = false; ASSERT_FALSE(wpi::WaitForObject(poller, 0.02, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); + auto events = nt::ReadListenerQueue(poller); ASSERT_TRUE(events.empty()); } TEST_F(TopicListenerTest, TopicUnpublishLocal) { auto topic = nt::GetTopic(m_serverInst, "/foo"); - auto poller = nt::CreateTopicListenerPoller(m_serverInst); + auto poller = nt::CreateListenerPoller(m_serverInst); auto handle = - nt::AddPolledTopicListener(poller, topic, NT_TOPIC_NOTIFY_UNPUBLISH); + nt::AddPolledListener(poller, topic, nt::EventFlags::kUnpublish); auto pub = nt::Publish(topic, NT_DOUBLE, "double"); nt::Unpublish(pub); bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); - CheckEvents(events, handle, NT_TOPIC_NOTIFY_UNPUBLISH, "/foo"); + auto events = nt::ReadListenerQueue(poller); + CheckEvents(events, handle, nt::EventFlags::kUnpublish, "/foo"); } TEST_F(TopicListenerTest, DISABLED_TopicUnpublishRemote) { @@ -163,9 +163,9 @@ TEST_F(TopicListenerTest, DISABLED_TopicUnpublishRemote) { if (HasFatalFailure()) { return; } - auto poller = nt::CreateTopicListenerPoller(m_serverInst); - auto handle = nt::AddPolledTopicListener( - poller, nt::GetTopic(m_serverInst, "/foo"), NT_TOPIC_NOTIFY_UNPUBLISH); + auto poller = nt::CreateListenerPoller(m_serverInst); + auto handle = nt::AddPolledListener( + poller, nt::GetTopic(m_serverInst, "/foo"), nt::EventFlags::kUnpublish); auto pub = nt::Publish(nt::GetTopic(m_clientInst, "/foo"), NT_DOUBLE, "double"); @@ -179,23 +179,23 @@ TEST_F(TopicListenerTest, DISABLED_TopicUnpublishRemote) { bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); - CheckEvents(events, handle, NT_TOPIC_NOTIFY_UNPUBLISH, "/foo"); + auto events = nt::ReadListenerQueue(poller); + CheckEvents(events, handle, nt::EventFlags::kUnpublish, "/foo"); } TEST_F(TopicListenerTest, TopicPropertiesLocal) { auto topic = nt::GetTopic(m_serverInst, "/foo"); - auto poller = nt::CreateTopicListenerPoller(m_serverInst); + auto poller = nt::CreateListenerPoller(m_serverInst); auto handle = - nt::AddPolledTopicListener(poller, topic, NT_TOPIC_NOTIFY_PROPERTIES); + nt::AddPolledListener(poller, topic, nt::EventFlags::kProperties); nt::SetTopicProperty(topic, "foo", 5); bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); - CheckEvents(events, handle, NT_TOPIC_NOTIFY_PROPERTIES, "/foo"); + auto events = nt::ReadListenerQueue(poller); + CheckEvents(events, handle, nt::EventFlags::kProperties, "/foo"); } TEST_F(TopicListenerTest, DISABLED_TopicPropertiesRemote) { @@ -206,9 +206,9 @@ TEST_F(TopicListenerTest, DISABLED_TopicPropertiesRemote) { // the topic needs to actually exist nt::Publish(nt::GetTopic(m_serverInst, "/foo"), NT_BOOLEAN, "boolean"); - auto poller = nt::CreateTopicListenerPoller(m_serverInst); - auto handle = nt::AddPolledTopicListener( - poller, nt::GetTopic(m_serverInst, "/foo"), NT_TOPIC_NOTIFY_PROPERTIES); + auto poller = nt::CreateListenerPoller(m_serverInst); + auto handle = nt::AddPolledListener( + poller, nt::GetTopic(m_serverInst, "/foo"), nt::EventFlags::kProperties); nt::FlushLocal(m_serverInst); nt::SetTopicProperty(nt::GetTopic(m_clientInst, "/foo"), "foo", 5); @@ -217,21 +217,21 @@ TEST_F(TopicListenerTest, DISABLED_TopicPropertiesRemote) { bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); - CheckEvents(events, handle, NT_TOPIC_NOTIFY_PROPERTIES, "/foo"); + auto events = nt::ReadListenerQueue(poller); + CheckEvents(events, handle, nt::EventFlags::kProperties, "/foo"); } TEST_F(TopicListenerTest, PrefixPublishLocal) { - auto poller = nt::CreateTopicListenerPoller(m_serverInst); + auto poller = nt::CreateListenerPoller(m_serverInst); auto handle = - nt::AddPolledTopicListener(poller, {{"/foo/"}}, NT_TOPIC_NOTIFY_PUBLISH); + nt::AddPolledListener(poller, {{"/foo/"}}, nt::EventFlags::kPublish); PublishTopics(m_serverInst); bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); - CheckEvents(events, handle, NT_TOPIC_NOTIFY_PUBLISH); + auto events = nt::ReadListenerQueue(poller); + CheckEvents(events, handle, nt::EventFlags::kPublish); } TEST_F(TopicListenerTest, DISABLED_PrefixPublishRemote) { @@ -239,9 +239,9 @@ TEST_F(TopicListenerTest, DISABLED_PrefixPublishRemote) { if (HasFatalFailure()) { return; } - auto poller = nt::CreateTopicListenerPoller(m_serverInst); + auto poller = nt::CreateListenerPoller(m_serverInst); auto handle = - nt::AddPolledTopicListener(poller, {{"/foo/"}}, NT_TOPIC_NOTIFY_PUBLISH); + nt::AddPolledListener(poller, {{"/foo/"}}, nt::EventFlags::kPublish); PublishTopics(m_clientInst); @@ -250,35 +250,36 @@ TEST_F(TopicListenerTest, DISABLED_PrefixPublishRemote) { bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); - CheckEvents(events, handle, NT_TOPIC_NOTIFY_PUBLISH); + auto events = nt::ReadListenerQueue(poller); + CheckEvents(events, handle, nt::EventFlags::kPublish); } TEST_F(TopicListenerTest, PrefixPublishImm) { PublishTopics(m_serverInst); - auto poller = nt::CreateTopicListenerPoller(m_serverInst); - auto handle = nt::AddPolledTopicListener( - poller, {{"/foo/"}}, NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_IMMEDIATE); + auto poller = nt::CreateListenerPoller(m_serverInst); + auto handle = nt::AddPolledListener( + poller, {{"/foo/"}}, + nt::EventFlags::kPublish | nt::EventFlags::kImmediate); bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); + auto events = nt::ReadListenerQueue(poller); CheckEvents(events, handle, - NT_TOPIC_NOTIFY_PUBLISH | NT_TOPIC_NOTIFY_IMMEDIATE); + nt::EventFlags::kPublish | nt::EventFlags::kImmediate); } TEST_F(TopicListenerTest, PrefixUnpublishPropsImm) { PublishTopics(m_serverInst); - auto poller = nt::CreateTopicListenerPoller(m_serverInst); - nt::AddPolledTopicListener(poller, {{"/foo/"}}, - NT_TOPIC_NOTIFY_UNPUBLISH | - NT_TOPIC_NOTIFY_PROPERTIES | - NT_TOPIC_NOTIFY_IMMEDIATE); + auto poller = nt::CreateListenerPoller(m_serverInst); + nt::AddPolledListener(poller, {{"/foo/"}}, + nt::EventFlags::kUnpublish | + nt::EventFlags::kProperties | + nt::EventFlags::kImmediate); bool timedOut = false; ASSERT_FALSE(wpi::WaitForObject(poller, 0.02, &timedOut)); - auto events = nt::ReadTopicListenerQueue(poller); + auto events = nt::ReadListenerQueue(poller); ASSERT_TRUE(events.empty()); } diff --git a/ntcore/src/test/native/cpp/ValueListenerTest.cpp b/ntcore/src/test/native/cpp/ValueListenerTest.cpp index cca8606f6e..7b10476f35 100644 --- a/ntcore/src/test/native/cpp/ValueListenerTest.cpp +++ b/ntcore/src/test/native/cpp/ValueListenerTest.cpp @@ -34,12 +34,12 @@ TEST_F(ValueListenerTest, MultiPollSub) { auto pub = nt::Publish(topic, NT_DOUBLE, "double"); auto sub = nt::Subscribe(topic, NT_DOUBLE, "double"); - auto poller1 = nt::CreateValueListenerPoller(m_inst); - auto poller2 = nt::CreateValueListenerPoller(m_inst); - auto poller3 = nt::CreateValueListenerPoller(m_inst); - auto h1 = nt::AddPolledValueListener(poller1, sub, NT_VALUE_NOTIFY_LOCAL); - auto h2 = nt::AddPolledValueListener(poller2, sub, NT_VALUE_NOTIFY_LOCAL); - auto h3 = nt::AddPolledValueListener(poller3, sub, NT_VALUE_NOTIFY_LOCAL); + auto poller1 = nt::CreateListenerPoller(m_inst); + auto poller2 = nt::CreateListenerPoller(m_inst); + auto poller3 = nt::CreateListenerPoller(m_inst); + auto h1 = nt::AddPolledListener(poller1, sub, nt::EventFlags::kValueLocal); + auto h2 = nt::AddPolledListener(poller2, sub, nt::EventFlags::kValueLocal); + auto h3 = nt::AddPolledListener(poller3, sub, nt::EventFlags::kValueLocal); nt::SetDouble(pub, 0); @@ -50,30 +50,36 @@ TEST_F(ValueListenerTest, MultiPollSub) { ASSERT_FALSE(timedOut); ASSERT_TRUE(wpi::WaitForObject(poller3, 1.0, &timedOut)); ASSERT_FALSE(timedOut); - auto results1 = nt::ReadValueListenerQueue(poller1); - auto results2 = nt::ReadValueListenerQueue(poller2); - auto results3 = nt::ReadValueListenerQueue(poller3); + auto results1 = nt::ReadListenerQueue(poller1); + auto results2 = nt::ReadListenerQueue(poller2); + auto results3 = nt::ReadListenerQueue(poller3); ASSERT_EQ(results1.size(), 1u); - EXPECT_EQ(results1[0].flags, NT_VALUE_NOTIFY_LOCAL); + EXPECT_EQ(results1[0].flags, nt::EventFlags::kValueLocal); EXPECT_EQ(results1[0].listener, h1); - EXPECT_EQ(results1[0].subentry, sub); - EXPECT_EQ(results1[0].topic, topic); - EXPECT_EQ(results1[0].value, nt::Value::MakeDouble(0.0)); + auto valueData = results1[0].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, sub); + EXPECT_EQ(valueData->topic, topic); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); ASSERT_EQ(results2.size(), 1u); - EXPECT_EQ(results2[0].flags, NT_VALUE_NOTIFY_LOCAL); + EXPECT_EQ(results2[0].flags, nt::EventFlags::kValueLocal); EXPECT_EQ(results2[0].listener, h2); - EXPECT_EQ(results2[0].subentry, sub); - EXPECT_EQ(results2[0].topic, topic); - EXPECT_EQ(results2[0].value, nt::Value::MakeDouble(0.0)); + valueData = results2[0].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, sub); + EXPECT_EQ(valueData->topic, topic); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); ASSERT_EQ(results3.size(), 1u); - EXPECT_EQ(results3[0].flags, NT_VALUE_NOTIFY_LOCAL); + EXPECT_EQ(results3[0].flags, nt::EventFlags::kValueLocal); EXPECT_EQ(results3[0].listener, h3); - EXPECT_EQ(results3[0].subentry, sub); - EXPECT_EQ(results3[0].topic, topic); - EXPECT_EQ(results3[0].value, nt::Value::MakeDouble(0.0)); + valueData = results3[0].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, sub); + EXPECT_EQ(valueData->topic, topic); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); } TEST_F(ValueListenerTest, PollMultiSub) { @@ -82,29 +88,33 @@ TEST_F(ValueListenerTest, PollMultiSub) { auto sub1 = nt::Subscribe(topic, NT_DOUBLE, "double"); auto sub2 = nt::Subscribe(topic, NT_DOUBLE, "double"); - auto poller = nt::CreateValueListenerPoller(m_inst); - auto h1 = nt::AddPolledValueListener(poller, sub1, NT_VALUE_NOTIFY_LOCAL); - auto h2 = nt::AddPolledValueListener(poller, sub2, NT_VALUE_NOTIFY_LOCAL); + auto poller = nt::CreateListenerPoller(m_inst); + auto h1 = nt::AddPolledListener(poller, sub1, nt::EventFlags::kValueLocal); + auto h2 = nt::AddPolledListener(poller, sub2, nt::EventFlags::kValueLocal); nt::SetDouble(pub, 0); bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); ASSERT_FALSE(timedOut); - auto results = nt::ReadValueListenerQueue(poller); + auto results = nt::ReadListenerQueue(poller); ASSERT_EQ(results.size(), 2u); - EXPECT_EQ(results[0].flags, NT_VALUE_NOTIFY_LOCAL); + EXPECT_EQ(results[0].flags, nt::EventFlags::kValueLocal); EXPECT_EQ(results[0].listener, h1); - EXPECT_EQ(results[0].subentry, sub1); - EXPECT_EQ(results[0].topic, topic); - EXPECT_EQ(results[0].value, nt::Value::MakeDouble(0.0)); + auto valueData = results[0].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, sub1); + EXPECT_EQ(valueData->topic, topic); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); - EXPECT_EQ(results[1].flags, NT_VALUE_NOTIFY_LOCAL); + EXPECT_EQ(results[1].flags, nt::EventFlags::kValueLocal); EXPECT_EQ(results[1].listener, h2); - EXPECT_EQ(results[1].subentry, sub2); - EXPECT_EQ(results[1].topic, topic); - EXPECT_EQ(results[1].value, nt::Value::MakeDouble(0.0)); + valueData = results[1].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, sub2); + EXPECT_EQ(valueData->topic, topic); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); } TEST_F(ValueListenerTest, PollMultiSubTopic) { @@ -115,9 +125,9 @@ TEST_F(ValueListenerTest, PollMultiSubTopic) { auto sub1 = nt::Subscribe(topic1, NT_DOUBLE, "double"); auto sub2 = nt::Subscribe(topic2, NT_DOUBLE, "double"); - auto poller = nt::CreateValueListenerPoller(m_inst); - auto h1 = nt::AddPolledValueListener(poller, sub1, NT_VALUE_NOTIFY_LOCAL); - auto h2 = nt::AddPolledValueListener(poller, sub2, NT_VALUE_NOTIFY_LOCAL); + auto poller = nt::CreateListenerPoller(m_inst); + auto h1 = nt::AddPolledListener(poller, sub1, nt::EventFlags::kValueLocal); + auto h2 = nt::AddPolledListener(poller, sub2, nt::EventFlags::kValueLocal); nt::SetDouble(pub1, 0); nt::SetDouble(pub2, 1); @@ -125,20 +135,24 @@ TEST_F(ValueListenerTest, PollMultiSubTopic) { bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); ASSERT_FALSE(timedOut); - auto results = nt::ReadValueListenerQueue(poller); + auto results = nt::ReadListenerQueue(poller); ASSERT_EQ(results.size(), 2u); - EXPECT_EQ(results[0].flags, NT_VALUE_NOTIFY_LOCAL); + EXPECT_EQ(results[0].flags, nt::EventFlags::kValueLocal); EXPECT_EQ(results[0].listener, h1); - EXPECT_EQ(results[0].subentry, sub1); - EXPECT_EQ(results[0].topic, topic1); - EXPECT_EQ(results[0].value, nt::Value::MakeDouble(0.0)); + auto valueData = results[0].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, sub1); + EXPECT_EQ(valueData->topic, topic1); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); - EXPECT_EQ(results[1].flags, NT_VALUE_NOTIFY_LOCAL); + EXPECT_EQ(results[1].flags, nt::EventFlags::kValueLocal); EXPECT_EQ(results[1].listener, h2); - EXPECT_EQ(results[1].subentry, sub2); - EXPECT_EQ(results[1].topic, topic2); - EXPECT_EQ(results[1].value, nt::Value::MakeDouble(1.0)); + valueData = results[1].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, sub2); + EXPECT_EQ(valueData->topic, topic2); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(1.0)); } TEST_F(ValueListenerTest, PollSubMultiple) { @@ -148,8 +162,8 @@ TEST_F(ValueListenerTest, PollSubMultiple) { auto pub2 = nt::Publish(topic2, NT_DOUBLE, "double"); auto sub = nt::SubscribeMultiple(m_inst, {{"foo"}}); - auto poller = nt::CreateValueListenerPoller(m_inst); - auto h = nt::AddPolledValueListener(poller, sub, NT_VALUE_NOTIFY_LOCAL); + auto poller = nt::CreateListenerPoller(m_inst); + auto h = nt::AddPolledListener(poller, sub, nt::EventFlags::kValueLocal); nt::SetDouble(pub1, 0); nt::SetDouble(pub2, 1); @@ -157,90 +171,139 @@ TEST_F(ValueListenerTest, PollSubMultiple) { bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); ASSERT_FALSE(timedOut); - auto results = nt::ReadValueListenerQueue(poller); + auto results = nt::ReadListenerQueue(poller); ASSERT_EQ(results.size(), 2u); - EXPECT_EQ(results[0].flags, NT_VALUE_NOTIFY_LOCAL); + EXPECT_EQ(results[0].flags, nt::EventFlags::kValueLocal); EXPECT_EQ(results[0].listener, h); - EXPECT_EQ(results[0].subentry, sub); - EXPECT_EQ(results[0].topic, topic1); - EXPECT_EQ(results[0].value, nt::Value::MakeDouble(0.0)); + auto valueData = results[0].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, sub); + EXPECT_EQ(valueData->topic, topic1); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); - EXPECT_EQ(results[1].flags, NT_VALUE_NOTIFY_LOCAL); + EXPECT_EQ(results[1].flags, nt::EventFlags::kValueLocal); EXPECT_EQ(results[1].listener, h); - EXPECT_EQ(results[1].subentry, sub); - EXPECT_EQ(results[1].topic, topic2); - EXPECT_EQ(results[1].value, nt::Value::MakeDouble(1.0)); + valueData = results[1].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, sub); + EXPECT_EQ(valueData->topic, topic2); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(1.0)); +} + +TEST_F(ValueListenerTest, PollSubPrefixCreated) { + auto poller = nt::CreateListenerPoller(m_inst); + auto h = + nt::AddPolledListener(poller, {{"foo"}}, nt::EventFlags::kValueLocal); + + auto topic1 = nt::GetTopic(m_inst, "foo/1"); + auto topic2 = nt::GetTopic(m_inst, "foo/2"); + auto topic3 = nt::GetTopic(m_inst, "bar/3"); + auto pub1 = nt::Publish(topic1, NT_DOUBLE, "double"); + auto pub2 = nt::Publish(topic2, NT_DOUBLE, "double"); + auto pub3 = nt::Publish(topic3, NT_DOUBLE, "double"); + + nt::SetDouble(pub1, 0); + nt::SetDouble(pub2, 1); + nt::SetDouble(pub3, 1); + + bool timedOut = false; + ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); + ASSERT_FALSE(timedOut); + auto results = nt::ReadListenerQueue(poller); + + ASSERT_EQ(results.size(), 2u); + EXPECT_EQ(results[0].flags, nt::EventFlags::kValueLocal); + EXPECT_EQ(results[0].listener, h); + auto valueData = results[0].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->topic, topic1); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); + + EXPECT_EQ(results[1].flags, nt::EventFlags::kValueLocal); + EXPECT_EQ(results[1].listener, h); + valueData = results[1].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->topic, topic2); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(1.0)); } TEST_F(ValueListenerTest, PollEntry) { auto entry = nt::GetEntry(m_inst, "foo"); - auto poller = nt::CreateValueListenerPoller(m_inst); - auto h = nt::AddPolledValueListener(poller, entry, NT_VALUE_NOTIFY_LOCAL); + auto poller = nt::CreateListenerPoller(m_inst); + auto h = nt::AddPolledListener(poller, entry, nt::EventFlags::kValueLocal); ASSERT_TRUE(nt::SetDouble(entry, 0)); bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); ASSERT_FALSE(timedOut); - auto results = nt::ReadValueListenerQueue(poller); + auto results = nt::ReadListenerQueue(poller); ASSERT_EQ(results.size(), 1u); - EXPECT_EQ(results[0].flags, NT_VALUE_NOTIFY_LOCAL); + EXPECT_EQ(results[0].flags, nt::EventFlags::kValueLocal); EXPECT_EQ(results[0].listener, h); - EXPECT_EQ(results[0].subentry, entry); - EXPECT_EQ(results[0].topic, nt::GetTopic(m_inst, "foo")); - EXPECT_EQ(results[0].value, nt::Value::MakeDouble(0.0)); + auto valueData = results[0].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, entry); + EXPECT_EQ(valueData->topic, nt::GetTopic(m_inst, "foo")); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); } TEST_F(ValueListenerTest, PollImmediate) { auto entry = nt::GetEntry(m_inst, "foo"); ASSERT_TRUE(nt::SetDouble(entry, 0)); - auto poller = nt::CreateValueListenerPoller(m_inst); - auto h = nt::AddPolledValueListener( - poller, entry, NT_VALUE_NOTIFY_LOCAL | NT_VALUE_NOTIFY_IMMEDIATE); + auto poller = nt::CreateListenerPoller(m_inst); + auto h = nt::AddPolledListener( + poller, entry, nt::EventFlags::kValueLocal | nt::EventFlags::kImmediate); bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); ASSERT_FALSE(timedOut); - auto results = nt::ReadValueListenerQueue(poller); + auto results = nt::ReadListenerQueue(poller); ASSERT_EQ(results.size(), 1u); - EXPECT_EQ(results[0].flags, NT_VALUE_NOTIFY_IMMEDIATE); + EXPECT_EQ(results[0].flags & + (nt::EventFlags::kValueLocal | nt::EventFlags::kImmediate), + nt::EventFlags::kValueLocal | nt::EventFlags::kImmediate); EXPECT_EQ(results[0].listener, h); - EXPECT_EQ(results[0].subentry, entry); - EXPECT_EQ(results[0].topic, nt::GetTopic(m_inst, "foo")); - EXPECT_EQ(results[0].value, nt::Value::MakeDouble(0.0)); + auto valueData = results[0].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, entry); + EXPECT_EQ(valueData->topic, nt::GetTopic(m_inst, "foo")); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); } TEST_F(ValueListenerTest, PollImmediateNoValue) { auto entry = nt::GetEntry(m_inst, "foo"); - auto poller = nt::CreateValueListenerPoller(m_inst); - auto h = nt::AddPolledValueListener( - poller, entry, NT_VALUE_NOTIFY_LOCAL | NT_VALUE_NOTIFY_IMMEDIATE); + auto poller = nt::CreateListenerPoller(m_inst); + auto h = nt::AddPolledListener( + poller, entry, nt::EventFlags::kValueLocal | nt::EventFlags::kImmediate); bool timedOut = false; ASSERT_FALSE(wpi::WaitForObject(poller, 0.02, &timedOut)); ASSERT_TRUE(timedOut); - auto results = nt::ReadValueListenerQueue(poller); + auto results = nt::ReadListenerQueue(poller); ASSERT_TRUE(results.empty()); // now set a value ASSERT_TRUE(nt::SetDouble(entry, 0)); ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); - results = nt::ReadValueListenerQueue(poller); + results = nt::ReadListenerQueue(poller); ASSERT_FALSE(timedOut); ASSERT_EQ(results.size(), 1u); - EXPECT_EQ(results[0].flags, NT_VALUE_NOTIFY_LOCAL); + EXPECT_EQ(results[0].flags, nt::EventFlags::kValueLocal); EXPECT_EQ(results[0].listener, h); - EXPECT_EQ(results[0].subentry, entry); - EXPECT_EQ(results[0].topic, nt::GetTopic(m_inst, "foo")); - EXPECT_EQ(results[0].value, nt::Value::MakeDouble(0.0)); + auto valueData = results[0].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, entry); + EXPECT_EQ(valueData->topic, nt::GetTopic(m_inst, "foo")); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); } TEST_F(ValueListenerTest, PollImmediateSubMultiple) { @@ -252,27 +315,35 @@ TEST_F(ValueListenerTest, PollImmediateSubMultiple) { nt::SetDouble(pub1, 0); nt::SetDouble(pub2, 1); - auto poller = nt::CreateValueListenerPoller(m_inst); - auto h = nt::AddPolledValueListener( - poller, sub, NT_VALUE_NOTIFY_LOCAL | NT_VALUE_NOTIFY_IMMEDIATE); + auto poller = nt::CreateListenerPoller(m_inst); + auto h = nt::AddPolledListener( + poller, sub, nt::EventFlags::kValueLocal | nt::EventFlags::kImmediate); bool timedOut = false; ASSERT_TRUE(wpi::WaitForObject(poller, 1.0, &timedOut)); ASSERT_FALSE(timedOut); - auto results = nt::ReadValueListenerQueue(poller); + auto results = nt::ReadListenerQueue(poller); ASSERT_EQ(results.size(), 2u); - EXPECT_EQ(results[0].flags, NT_VALUE_NOTIFY_IMMEDIATE); + EXPECT_EQ(results[0].flags & + (nt::EventFlags::kValueLocal | nt::EventFlags::kImmediate), + nt::EventFlags::kValueLocal | nt::EventFlags::kImmediate); EXPECT_EQ(results[0].listener, h); - EXPECT_EQ(results[0].subentry, sub); - EXPECT_EQ(results[0].topic, topic1); - EXPECT_EQ(results[0].value, nt::Value::MakeDouble(0.0)); + auto valueData = results[0].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, sub); + EXPECT_EQ(valueData->topic, topic1); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(0.0)); - EXPECT_EQ(results[1].flags, NT_VALUE_NOTIFY_IMMEDIATE); + EXPECT_EQ(results[1].flags & + (nt::EventFlags::kValueLocal | nt::EventFlags::kImmediate), + nt::EventFlags::kValueLocal | nt::EventFlags::kImmediate); EXPECT_EQ(results[1].listener, h); - EXPECT_EQ(results[1].subentry, sub); - EXPECT_EQ(results[1].topic, topic2); - EXPECT_EQ(results[1].value, nt::Value::MakeDouble(1.0)); + valueData = results[1].GetValueEventData(); + ASSERT_TRUE(valueData); + EXPECT_EQ(valueData->subentry, sub); + EXPECT_EQ(valueData->topic, topic2); + EXPECT_EQ(valueData->value, nt::Value::MakeDouble(1.0)); } } // namespace nt diff --git a/ntcore/src/test/native/cpp/main.cpp b/ntcore/src/test/native/cpp/main.cpp index caf7318925..6d37027c53 100644 --- a/ntcore/src/test/native/cpp/main.cpp +++ b/ntcore/src/test/native/cpp/main.cpp @@ -8,13 +8,12 @@ #include "ntcore.h" int main(int argc, char** argv) { - nt::AddLogger( - nt::GetDefaultInstance(), - [](const nt::LogMessage& msg) { - std::fputs(msg.message.c_str(), stderr); - std::fputc('\n', stderr); - }, - 0, UINT_MAX); + nt::AddLogger(nt::GetDefaultInstance(), 0, UINT_MAX, [](auto& event) { + if (auto msg = event.GetLogMessage()) { + std::fputs(msg->message.c_str(), stderr); + std::fputc('\n', stderr); + } + }); ::testing::InitGoogleMock(&argc, argv); int ret = RUN_ALL_TESTS(); return ret; diff --git a/ntcoreffi/src/main/native/symbols.txt b/ntcoreffi/src/main/native/symbols.txt index 90f05f5c93..f9c951a553 100644 --- a/ntcoreffi/src/main/native/symbols.txt +++ b/ntcoreffi/src/main/native/symbols.txt @@ -1,37 +1,25 @@ -NT_AddConnectionListener +NT_AddListener +NT_AddListenerMultiple +NT_AddListenerSingle NT_AddLogger -NT_AddPolledConnectionListener +NT_AddPolledListener +NT_AddPolledListenerMultiple +NT_AddPolledListenerSingle NT_AddPolledLogger -NT_AddPolledTopicListener -NT_AddPolledTopicListenerMultiple -NT_AddPolledTopicListenerSingle -NT_AddPolledValueListener -NT_AddTopicListener -NT_AddTopicListenerMultiple -NT_AddTopicListenerSingle -NT_AddValueListener NT_AllocateBooleanArray NT_AllocateCharArray NT_AllocateDoubleArray NT_AllocateFloatArray NT_AllocateIntegerArray NT_AllocateStringArray -NT_CreateConnectionListenerPoller NT_CreateInstance -NT_CreateLoggerPoller -NT_CreateTopicListenerPoller -NT_CreateValueListenerPoller +NT_CreateListenerPoller NT_DeleteTopicProperty -NT_DestroyConnectionListenerPoller NT_DestroyInstance -NT_DestroyLoggerPoller -NT_DestroyTopicListenerPoller -NT_DestroyValueListenerPoller +NT_DestroyListenerPoller NT_DisposeConnectionInfoArray -NT_DisposeConnectionNotification -NT_DisposeConnectionNotificationArray -NT_DisposeLogMessage -NT_DisposeLogMessageArray +NT_DisposeEvent +NT_DisposeEventArray NT_DisposeString NT_DisposeTimestampedBoolean NT_DisposeTimestampedBooleanArray @@ -46,12 +34,8 @@ NT_DisposeTimestampedString NT_DisposeTimestampedStringArray NT_DisposeTopicInfo NT_DisposeTopicInfoArray -NT_DisposeTopicNotification -NT_DisposeTopicNotificationArray NT_DisposeValue NT_DisposeValueArray -NT_DisposeValueNotification -NT_DisposeValueNotificationArray NT_Flush NT_FlushLocal NT_FreeBooleanArray @@ -149,8 +133,7 @@ NT_IsConnected NT_Now NT_Publish NT_PublishEx -NT_ReadConnectionListenerQueue -NT_ReadLoggerQueue +NT_ReadListenerQueue NT_ReadQueueBoolean NT_ReadQueueBooleanArray NT_ReadQueueDouble @@ -167,14 +150,9 @@ NT_ReadQueueValuesBoolean NT_ReadQueueValuesDouble NT_ReadQueueValuesFloat NT_ReadQueueValuesInteger -NT_ReadTopicListenerQueue -NT_ReadValueListenerQueue NT_Release NT_ReleaseEntry -NT_RemoveConnectionListener -NT_RemoveLogger -NT_RemoveTopicListener -NT_RemoveValueListener +NT_RemoveListener NT_SetBoolean NT_SetBooleanArray NT_SetDefaultBoolean @@ -220,9 +198,7 @@ NT_StopServer NT_Subscribe NT_Unpublish NT_Unsubscribe -NT_WaitForConnectionListenerQueue -NT_WaitForTopicListenerQueue -NT_WaitForValueListenerQueue +NT_WaitForListenerQueue WPI_CreateEvent WPI_CreateSemaphore WPI_CreateSignalObject diff --git a/outlineviewer/src/main/native/cpp/main.cpp b/outlineviewer/src/main/native/cpp/main.cpp index b368cd3f3d..20f4dcc12e 100644 --- a/outlineviewer/src/main/native/cpp/main.cpp +++ b/outlineviewer/src/main/native/cpp/main.cpp @@ -39,50 +39,48 @@ static glass::NetworkTablesFlagsSettings gFlagsSettings; static glass::MainMenuBar gMainMenu; static void NtInitialize() { - // update window title when connection status changes auto inst = nt::GetDefaultInstance(); - auto poller = nt::CreateConnectionListenerPoller(inst); - nt::AddPolledConnectionListener(poller, true); + auto poller = nt::CreateListenerPoller(inst); + nt::AddPolledListener( + poller, inst, + NT_EVENT_CONNECTION | NT_EVENT_IMMEDIATE | NT_EVENT_LOGMESSAGE); gui::AddEarlyExecute([inst, poller] { auto win = gui::GetSystemWindow(); if (!win) { return; } - for (auto&& event : nt::ReadConnectionListenerQueue(poller)) { - if ((nt::GetNetworkMode(inst) & NT_NET_MODE_SERVER) != 0) { - // for server mode, just print number of clients connected - glfwSetWindowTitle(win, - fmt::format("OutlineViewer - {} Clients Connected", - nt::GetConnections(inst).size()) - .c_str()); - } else if (event.connected) { - glfwSetWindowTitle(win, fmt::format("OutlineViewer - Connected ({})", - event.conn.remote_ip) - .c_str()); - } else { - glfwSetWindowTitle(win, "OutlineViewer - DISCONNECTED"); + for (auto&& event : nt::ReadListenerQueue(poller)) { + if (auto connInfo = event.GetConnectionInfo()) { + // update window title when connection status changes + if ((nt::GetNetworkMode(inst) & NT_NET_MODE_SERVER) != 0) { + // for server mode, just print number of clients connected + glfwSetWindowTitle(win, + fmt::format("OutlineViewer - {} Clients Connected", + nt::GetConnections(inst).size()) + .c_str()); + } else if ((event.flags & NT_EVENT_CONNECTED) != 0) { + glfwSetWindowTitle(win, fmt::format("OutlineViewer - Connected ({})", + connInfo->remote_ip) + .c_str()); + } else { + glfwSetWindowTitle(win, "OutlineViewer - DISCONNECTED"); + } + } else if (auto msg = event.GetLogMessage()) { + // handle NetworkTables log messages + const char* level = ""; + if (msg->level >= NT_LOG_CRITICAL) { + level = "CRITICAL: "; + } else if (msg->level >= NT_LOG_ERROR) { + level = "ERROR: "; + } else if (msg->level >= NT_LOG_WARNING) { + level = "WARNING: "; + } + gLog.Append(fmt::format("{}{} ({}:{})\n", level, msg->message, + msg->filename, msg->line)); } } }); - // handle NetworkTables log messages - auto logPoller = nt::CreateLoggerPoller(inst); - nt::AddPolledLogger(logPoller, NT_LOG_INFO, 100); - gui::AddEarlyExecute([logPoller] { - for (auto&& msg : nt::ReadLoggerQueue(logPoller)) { - const char* level = ""; - if (msg.level >= NT_LOG_CRITICAL) { - level = "CRITICAL: "; - } else if (msg.level >= NT_LOG_ERROR) { - level = "ERROR: "; - } else if (msg.level >= NT_LOG_WARNING) { - level = "WARNING: "; - } - gLog.Append(fmt::format("{}{} ({}:{})\n", level, msg.message, - msg.filename, msg.line)); - } - }); - // NetworkTables table window gModel = std::make_unique(); gui::AddEarlyExecute([] { gModel->Update(); }); diff --git a/wpilibc/src/main/native/cpp/Preferences.cpp b/wpilibc/src/main/native/cpp/Preferences.cpp index 91b8cffb77..cfb5964516 100644 --- a/wpilibc/src/main/native/cpp/Preferences.cpp +++ b/wpilibc/src/main/native/cpp/Preferences.cpp @@ -11,8 +11,8 @@ #include #include #include +#include #include -#include using namespace frc; @@ -28,7 +28,7 @@ struct Instance { nt::StringPublisher typePublisher{table->GetStringTopic(".type").Publish()}; nt::MultiSubscriber tableSubscriber{nt::NetworkTableInstance::GetDefault(), {{fmt::format("{}/", table->GetPath())}}}; - nt::TopicListener listener; + nt::NetworkTableListener listener; }; } // namespace @@ -166,13 +166,14 @@ void Preferences::RemoveAll() { Instance::Instance() { typePublisher.Set("RobotPreferences"); - listener = nt::TopicListener{ - tableSubscriber, NT_TOPIC_NOTIFY_IMMEDIATE | NT_TOPIC_NOTIFY_PUBLISH, - [typeTopic = typePublisher.GetTopic().GetHandle()]( - const nt::TopicNotification& event) { - if (event.info.topic != typeTopic) { - nt::SetTopicPersistent(event.info.topic, true); + listener = nt::NetworkTableListener::CreateListener( + tableSubscriber, NT_EVENT_PUBLISH | NT_EVENT_IMMEDIATE, + [typeTopic = typePublisher.GetTopic().GetHandle()](auto& event) { + if (auto topicInfo = event.GetTopicInfo()) { + if (topicInfo->topic != typeTopic) { + nt::SetTopicPersistent(topicInfo->topic, true); + } } - }}; + }); HAL_Report(HALUsageReporting::kResourceType_Preferences, 0); } diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/Preferences.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/Preferences.java index f4f667431f..8bae3ad4f9 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/Preferences.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/Preferences.java @@ -11,11 +11,11 @@ import edu.wpi.first.hal.HAL; import edu.wpi.first.networktables.MultiSubscriber; import edu.wpi.first.networktables.NetworkTable; import edu.wpi.first.networktables.NetworkTableEntry; +import edu.wpi.first.networktables.NetworkTableEvent; import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.networktables.NetworkTableListener; import edu.wpi.first.networktables.StringPublisher; import edu.wpi.first.networktables.Topic; -import edu.wpi.first.networktables.TopicListener; -import edu.wpi.first.networktables.TopicListenerFlags; import java.util.Collection; /** @@ -39,7 +39,7 @@ public final class Preferences { private static StringPublisher m_typePublisher; private static MultiSubscriber m_tableSubscriber; - private static TopicListener m_listener; + private static NetworkTableListener m_listener; /** Creates a preference class. */ private Preferences() {} @@ -75,13 +75,15 @@ public final class Preferences { m_listener.close(); } m_listener = - new TopicListener( + NetworkTableListener.createListener( m_tableSubscriber, - TopicListenerFlags.kImmediate | TopicListenerFlags.kPublish, + NetworkTableEvent.kImmediate | NetworkTableEvent.kPublish, event -> { - Topic topic = event.info.getTopic(); - if (!topic.equals(m_typePublisher.getTopic())) { - event.info.getTopic().setPersistent(true); + if (event.topicInfo != null) { + Topic topic = event.topicInfo.getTopic(); + if (!topic.equals(m_typePublisher.getTopic())) { + event.topicInfo.getTopic().setPersistent(true); + } } }); }