[ntcore] Unify listeners (#4536)

This combines all 4 NT listener APIs (topic, value, connection, and
logging) into a single unified listener API.
This commit is contained in:
Peter Johnson
2022-10-31 21:52:14 -07:00
committed by GitHub
parent 32fbfb7da6
commit b114006543
71 changed files with 3613 additions and 5786 deletions

View File

@@ -4,191 +4,16 @@
#include "ConnectionList.h"
#include <atomic>
#include <optional>
#include <wpi/DataLog.h>
#include <wpi/DenseMap.h>
#include <wpi/SafeThread.h>
#include <wpi/Synchronization.h>
#include <wpi/UidVector.h>
#include <wpi/SmallVector.h>
#include <wpi/json_serializer.h>
#include <wpi/raw_ostream.h>
#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<NT_ConnectionListenerPoller> handle;
std::vector<ConnectionNotification> queue;
};
struct ListenerData {
static constexpr auto kType = Handle::kConnectionListener;
ListenerData(NT_ConnectionListener handle, PollerData* poller)
: handle{handle}, poller{poller} {}
wpi::SignalObject<NT_ConnectionListener> 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<NT_ConnectionListener,
std::function<void(const ConnectionNotification& event)>>
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<std::optional<ConnectionInfo>, 8> m_connections;
HandleMap<PollerData, 8> m_pollers;
HandleMap<ListenerData, 8> m_listeners;
HandleMap<DataLoggerData, 8> m_dataloggers;
wpi::SafeThreadOwner<ListenerThread> m_listenerThread;
NT_ConnectionListener AddListener(
std::function<void(const ConnectionNotification& event)> 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<void(const ConnectionNotification& event)> 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<NT_ConnectionListener, 16> 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<Impl>(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<ConnectionInfo> ConnectionList::GetConnections() const {
std::scoped_lock lock{m_mutex};
std::vector<ConnectionInfo> 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<void(const ConnectionNotification& event)> 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<const ConnectionInfo*, 16> 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<ConnectionNotification> ConnectionList::ReadListenerQueue(
NT_ConnectionListenerPoller pollerHandle) {
std::scoped_lock lock{m_mutex};
if (auto poller = m_impl->m_pollers.Get(pollerHandle)) {
std::vector<ConnectionNotification> 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());
}
}

View File

@@ -4,21 +4,31 @@
#pragma once
#include <stdint.h>
#include <atomic>
#include <functional>
#include <memory>
#include <optional>
#include <string_view>
#include <vector>
#include <wpi/DataLog.h>
#include <wpi/UidVector.h>
#include <wpi/mutex.h>
#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<ConnectionInfo> GetConnections() const final;
bool IsConnected() const final;
NT_ConnectionListener AddListener(
bool immediateNotify,
std::function<void(const ConnectionNotification& event)> callback);
bool WaitForListenerQueue(double timeout);
NT_ConnectionListenerPoller CreateListenerPoller();
void DestroyListenerPoller(NT_ConnectionListenerPoller pollerHandle);
NT_ConnectionListener AddPolledListener(
NT_ConnectionListenerPoller pollerHandle, bool immediateNotify);
std::vector<ConnectionNotification> 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<Impl> 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<std::optional<ConnectionInfo>, 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<DataLoggerData, 8> m_dataloggers;
};
} // namespace nt

View File

@@ -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);

View File

@@ -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 <functional>
#include <memory>
#include <span>
#include <string_view>
#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<bool(unsigned int mask, Event* event)>;
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<const NT_Listener> handles, unsigned int flags,
std::span<ConnectionInfo const* const> infos) = 0;
virtual void Notify(std::span<const NT_Listener> handles, unsigned int flags,
std::span<const TopicInfo> infos) = 0;
virtual void Notify(std::span<const NT_Listener> 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<const NT_Listener> handles, unsigned int flags,
const ConnectionInfo* info) {
Notify(handles, flags, {&info, 1});
}
void Notify(std::span<const NT_Listener> handles, unsigned int flags,
const TopicInfo& info) {
Notify(handles, flags, {&info, 1});
}
};
} // namespace nt

View File

@@ -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());
}

View File

@@ -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<NetworkServer> GetServer();
std::shared_ptr<INetworkClient> GetClient();
ListenerStorage listenerStorage;
LoggerImpl logger_impl;
wpi::Logger logger;
ConnectionList connectionList;

View File

@@ -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 <algorithm>
#include <wpi/DenseMap.h>
#include <wpi/SmallVector.h>
#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<NT_Listener, ListenerCallback> 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<const NT_Listener> handles,
unsigned int flags,
std::span<ConnectionInfo const* const> 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<const NT_Listener> handles,
unsigned int flags,
std::span<const TopicInfo> 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<const NT_Listener> 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<std::pair<NT_Listener, unsigned int>>
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<NT_Listener, 16> toRemove;
for (auto&& listener : m_listeners) {
if (listener->poller == poller.get()) {
toRemove.emplace_back(listener->handle);
}
}
return DoRemoveListeners(toRemove);
} else {
return {};
}
}
std::vector<Event> ListenerStorage::ReadListenerQueue(
NT_ListenerPoller pollerHandle) {
std::scoped_lock lock{m_mutex};
if (auto poller = m_pollers.Get(pollerHandle)) {
std::vector<Event> rv;
rv.swap(poller->queue);
return rv;
} else {
return {};
}
}
std::vector<std::pair<NT_Listener, unsigned int>>
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<std::pair<NT_Listener, unsigned int>>
ListenerStorage::DoRemoveListeners(std::span<const NT_Listener> handles) {
std::vector<std::pair<NT_Listener, unsigned int>> 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;
}

View File

@@ -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 <functional>
#include <memory>
#include <span>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/SafeThread.h>
#include <wpi/SmallVector.h>
#include <wpi/Synchronization.h>
#include <wpi/mutex.h>
#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<const NT_Listener> handles, unsigned int flags,
std::span<ConnectionInfo const* const> infos) final;
void Notify(std::span<const NT_Listener> handles, unsigned int flags,
std::span<const TopicInfo> infos) final;
void Notify(std::span<const NT_Listener> 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<std::pair<NT_Listener, unsigned int>>
DestroyListenerPoller(NT_ListenerPoller pollerHandle);
std::vector<Event> ReadListenerQueue(NT_ListenerPoller pollerHandle);
// returns listener handle and mask for each listener that was destroyed
[[nodiscard]] std::vector<std::pair<NT_Listener, unsigned int>>
RemoveListener(NT_Listener listenerHandle);
bool WaitForListenerQueue(double timeout);
private:
// these assume the mutex is already held
NT_Listener DoAddListener(NT_ListenerPoller pollerHandle);
std::vector<std::pair<NT_Listener, unsigned int>> DoRemoveListeners(
std::span<const NT_Listener> 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<NT_ListenerPoller> handle;
std::vector<Event> queue;
};
HandleMap<PollerData, 8> m_pollers;
struct ListenerData {
static constexpr auto kType = Handle::kListener;
ListenerData(NT_Listener handle, PollerData* poller)
: handle{handle}, poller{poller} {}
wpi::SignalObject<NT_Listener> handle;
PollerData* poller;
wpi::SmallVector<std::pair<FinishEventFunc, unsigned int>, 2> sources;
unsigned int eventMask{0};
};
HandleMap<ListenerData, 8> m_listeners;
// Utility wrapper for making a set-like vector
template <typename T>
class VectorSet : public std::vector<T> {
public:
void Add(T value) { this->push_back(value); }
void Remove(T value) { std::erase(*this, value); }
};
VectorSet<ListenerData*> m_connListeners;
VectorSet<ListenerData*> m_topicListeners;
VectorSet<ListenerData*> m_valueListeners;
VectorSet<ListenerData*> m_logListeners;
class Thread;
wpi::SafeThreadOwner<Thread> m_thread;
};
} // namespace nt

File diff suppressed because it is too large Load Diff

View File

@@ -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<const std::string_view> prefixes, unsigned int mask,
std::function<void(const TopicNotification&)> callback);
NT_TopicListener AddTopicListener(
NT_Handle handle, unsigned int mask,
std::function<void(const TopicNotification&)> callback);
bool WaitForTopicListenerQueue(double timeout);
void AddListener(NT_Listener listener,
std::span<const std::string_view> 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<const std::string_view> prefixes,
unsigned int mask);
NT_TopicListener AddPolledTopicListener(NT_TopicListenerPoller poller,
NT_Handle handle, unsigned int mask);
std::vector<TopicNotification> ReadTopicListenerQueue(
NT_TopicListenerPoller poller);
void RemoveTopicListener(NT_TopicListener listener);
//
// Value listener functions
//
NT_ValueListener AddValueListener(
NT_Handle subentry, unsigned int mask,
std::function<void(const ValueNotification&)> 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<ValueNotification> ReadValueListenerQueue(
NT_ValueListenerPoller poller);
void RemoveValueListener(NT_ValueListener listener);
void RemoveListener(NT_Listener listener, unsigned int mask);
//
// Data log functions

View File

@@ -5,24 +5,27 @@
#include "LoggerImpl.h"
#include <fmt/format.h>
#include <wpi/DenseMap.h>
#include <wpi/Logger.h>
#include <wpi/SmallVector.h>
#include <wpi/fs.h>
#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<NT_Logger, std::function<void(const LogMessage& msg)>>
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<void(const LogMessage& msg)> 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<LogMessage> LoggerImpl::ReadQueue(NT_LoggerPoller pollerHandle) {
std::scoped_lock lock{m_mutex};
if (auto poller = m_pollers.Get(pollerHandle)) {
std::vector<LogMessage> 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);
}
}

View File

@@ -4,34 +4,29 @@
#pragma once
#include <utility>
#include <atomic>
#include <vector>
#include <wpi/SafeThread.h>
#include <wpi/Synchronization.h>
#include <wpi/mutex.h>
#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<void(const LogMessage& msg)> 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<LogMessage> 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<NT_LoggerPoller> handle;
std::vector<LogMessage> queue;
};
HandleMap<PollerData, 8> 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<NT_Logger> handle;
PollerData* poller;
NT_Listener listener;
unsigned int minLevel;
unsigned int maxLevel;
};
HandleMap<ListenerData, 8> m_listeners;
class Thread;
wpi::SafeThreadOwner<Thread> m_thread;
std::vector<ListenerLevels> m_listenerLevels;
};
} // namespace nt

View File

@@ -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<jint>(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, "<init>",
"(Ledu/wpi/first/networktables/NetworkTableInstance;IZLedu/wpi/first/"
"networktables/ConnectionInfo;)V");
JLocal<jobject> conn{env, MakeJObject(env, notification.conn)};
return env->NewObject(connectionNotificationCls, constructor, inst,
static_cast<jint>(notification.listener),
static_cast<jboolean>(notification.connected),
conn.obj());
}
static jobject MakeJObject(JNIEnv* env, jobject inst,
const nt::LogMessage& msg) {
static jmethodID constructor = env->GetMethodID(
logMessageCls, "<init>",
"(Ledu/wpi/first/networktables/NetworkTableInstance;IILjava/lang/"
"String;ILjava/lang/String;)V");
logMessageCls, "<init>", "(ILjava/lang/String;ILjava/lang/String;)V");
JLocal<jstring> filename{env, MakeJString(env, msg.filename)};
JLocal<jstring> message{env, MakeJString(env, msg.message)};
return env->NewObject(logMessageCls, constructor, inst,
static_cast<jint>(msg.logger),
return env->NewObject(logMessageCls, constructor,
static_cast<jint>(msg.level), filename.obj(),
static_cast<jint>(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, "<init>",
"(ILedu/wpi/first/networktables/TopicInfo;I)V");
JLocal<jobject> info{env, MakeJObject(env, inst, notification.info)};
return env->NewObject(topicNotificationCls, constructor,
static_cast<jint>(notification.listener), info.obj(),
static_cast<jint>(notification.flags));
env->GetMethodID(valueEventDataCls, "<init>",
"(Ledu/wpi/first/networktables/NetworkTableInstance;II"
"Ledu/wpi/first/networktables/NetworkTableValue;)V");
JLocal<jobject> value{env, MakeJValue(env, data.value)};
return env->NewObject(valueEventDataCls, constructor, inst,
static_cast<jint>(data.topic),
static_cast<jint>(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, "<init>",
"(Ledu/wpi/first/networktables/NetworkTableInstance;III"
"Ledu/wpi/first/networktables/NetworkTableValue;I)V");
JLocal<jobject> value{env, MakeJValue(env, notification.value)};
return env->NewObject(valueNotificationCls, constructor, inst,
static_cast<jint>(notification.listener),
static_cast<jint>(notification.topic),
static_cast<jint>(notification.subentry), value.obj(),
static_cast<jint>(notification.flags));
env->GetMethodID(eventCls, "<init>",
"(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<jobject> connInfo{env, nullptr};
JLocal<jobject> topicInfo{env, nullptr};
JLocal<jobject> valueData{env, nullptr};
JLocal<jobject> logMessage{env, nullptr};
if (auto v = event.GetConnectionInfo()) {
connInfo = JLocal<jobject>{env, MakeJObject(env, *v)};
} else if (auto v = event.GetTopicInfo()) {
topicInfo = JLocal<jobject>{env, MakeJObject(env, inst, *v)};
} else if (auto v = event.GetValueEventData()) {
valueData = JLocal<jobject>{env, MakeJObject(env, inst, *v)};
} else if (auto v = event.GetLogMessage()) {
logMessage = JLocal<jobject>{env, MakeJObject(env, *v)};
}
return env->NewObject(eventCls, constructor, inst,
static_cast<jint>(event.listener),
static_cast<jint>(event.flags), connInfo.obj(),
topicInfo.obj(), valueData.obj(), logMessage.obj());
}
static jobjectArray MakeJObject(JNIEnv* env, std::span<const nt::Value> arr) {
@@ -293,52 +287,9 @@ static jobjectArray MakeJObject(JNIEnv* env, std::span<const nt::Value> arr) {
return jarr;
}
static jobjectArray MakeJObject(
JNIEnv* env, jobject inst,
std::span<const nt::ConnectionNotification> arr) {
jobjectArray jarr =
env->NewObjectArray(arr.size(), connectionNotificationCls, nullptr);
if (!jarr) {
return nullptr;
}
for (size_t i = 0; i < arr.size(); ++i) {
JLocal<jobject> elem{env, MakeJObject(env, inst, arr[i])};
env->SetObjectArrayElement(jarr, i, elem.obj());
}
return jarr;
}
static jobjectArray MakeJObject(JNIEnv* env, jobject inst,
std::span<const nt::LogMessage> arr) {
jobjectArray jarr = env->NewObjectArray(arr.size(), logMessageCls, nullptr);
if (!jarr) {
return nullptr;
}
for (size_t i = 0; i < arr.size(); ++i) {
JLocal<jobject> elem{env, MakeJObject(env, inst, arr[i])};
env->SetObjectArrayElement(jarr, i, elem.obj());
}
return jarr;
}
static jobjectArray MakeJObject(JNIEnv* env, jobject inst,
std::span<const nt::TopicNotification> arr) {
jobjectArray jarr =
env->NewObjectArray(arr.size(), topicNotificationCls, nullptr);
if (!jarr) {
return nullptr;
}
for (size_t i = 0; i < arr.size(); ++i) {
JLocal<jobject> elem{env, MakeJObject(env, inst, arr[i])};
env->SetObjectArrayElement(jarr, i, elem.obj());
}
return jarr;
}
static jobjectArray MakeJObject(JNIEnv* env, jobject inst,
std::span<const nt::ValueNotification> arr) {
jobjectArray jarr =
env->NewObjectArray(arr.size(), valueNotificationCls, nullptr);
std::span<const nt::Event> 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"

View File

@@ -101,16 +101,9 @@ void NetworkTableInstance::SetServer(std::span<const std::string_view> servers,
SetServer(serversArr);
}
NT_TopicListener NetworkTableInstance::AddTopicListener(
MultiSubscriber& subscriber, int eventMask,
std::function<void(const TopicNotification&)> listener) {
return ::nt::AddTopicListener(subscriber.GetHandle(), eventMask,
std::move(listener));
}
NT_ValueListener NetworkTableInstance::AddValueListener(
MultiSubscriber& subscriber, unsigned int eventMask,
std::function<void(const ValueNotification&)> 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));
}

View File

@@ -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<NT_Event>(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<std::string_view, 8> 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<std::string_view, 8> 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<NT_TopicNotification>(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<NT_ValueNotification>(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<NT_ConnectionNotification>(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<NT_LogMessage>(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 */

View File

@@ -9,6 +9,7 @@
#include <cstdio>
#include <cstdlib>
#include <fmt/format.h>
#include <wpi/json.h>
#include <wpi/timestamp.h>
@@ -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<int64_t> 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<const std::string_view> prefixes, unsigned int mask,
std::function<void(const TopicNotification&)> callback) {
static void CleanupListeners(
InstanceImpl& ii,
std::span<const std::pair<NT_Listener, unsigned int>> 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<void(const TopicNotification&)> 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<Event> 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<const std::string_view> 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<const std::string_view> 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<TopicNotification> 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<void(const ValueNotification&)> 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<const std::string_view> 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<ValueNotification> 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<void(const ConnectionNotification& event)> 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<ConnectionNotification> 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<void(const LogMessage& msg)> 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<LogMessage> 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

View File

@@ -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 <functional>
#include <vector>
#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<void(const ConnectionNotification&)> 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<ConnectionNotification> ReadQueue();
private:
NT_ConnectionListenerPoller m_handle{0};
};
} // namespace nt
#include "ConnectionListener.inc"

View File

@@ -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 <utility>
#include <vector>
#include "networktables/ConnectionListener.h"
#include "networktables/NetworkTableInstance.h"
#include "ntcore_cpp.h"
namespace nt {
inline ConnectionListener::ConnectionListener(
NetworkTableInstance inst, bool immediateNotify,
std::function<void(const ConnectionNotification&)> 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<ConnectionNotification>
ConnectionListenerPoller::ReadQueue() {
return nt::ReadConnectionListenerQueue(m_handle);
}
} // namespace nt

View File

@@ -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<void(const ConnectionNotification& event)> 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<void(const TopicNotification&)> 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<void(const TopicNotification&)> 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<void(const TopicNotification&)> 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<void(const TopicNotification&)> 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<const std::string_view> prefixes, int eventMask,
std::function<void(const TopicNotification&)> 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<void(const ValueNotification&)> 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<void(const ValueNotification&)> 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<void(const ValueNotification&)> 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<const std::string_view> 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<void(const LogMessage& msg)> 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);
/** @} */

View File

@@ -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<void(const ConnectionNotification& event)> 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<void(const TopicNotification&)> 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<void(const TopicNotification&)> 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<void(const TopicNotification&)> 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<const std::string_view> prefixes, int eventMask,
std::function<void(const TopicNotification&)> 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<void(const ValueNotification&)> listener) {
return ::nt::AddValueListener(subscriber.GetHandle(), eventMask,
std::move(listener));
}
inline NT_ValueListener NetworkTableInstance::AddValueListener(
NetworkTableEntry& entry, int eventMask,
std::function<void(const ValueNotification&)> 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<void(const LogMessage& msg)> 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

View File

@@ -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 <functional>
#include <span>
#include <string_view>
#include <vector>
#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<const std::string_view> 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<const std::string_view> 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<Event> ReadQueue();
private:
NT_ListenerPoller m_handle{0};
};
} // namespace nt
#include "NetworkTableListener.inc"

View File

@@ -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 <span>
#include <string_view>
#include <utility>
#include <vector>
#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<const std::string_view> 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<const std::string_view> 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<Event> NetworkTableListenerPoller::ReadQueue() {
return ::nt::ReadListenerQueue(m_handle);
}
} // namespace nt

View File

@@ -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 <functional>
#include <span>
#include <string_view>
#include <vector>
#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<const std::string_view> prefixes, unsigned int mask,
std::function<void(const TopicNotification&)> 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<void(const TopicNotification&)> 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<void(const TopicNotification&)> 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<void(const TopicNotification&)> 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<void(const TopicNotification&)> 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<const std::string_view> 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<TopicNotification> ReadQueue();
private:
NT_TopicListenerPoller m_handle{0};
};
} // namespace nt
#include "TopicListener.inc"

View File

@@ -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 <span>
#include <string_view>
#include <utility>
#include <vector>
#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<const std::string_view> prefixes,
unsigned int mask, std::function<void(const TopicNotification&)> listener)
: m_handle{AddTopicListener(inst.GetHandle(), prefixes, mask, listener)} {}
inline TopicListener::TopicListener(
Topic topic, unsigned int mask,
std::function<void(const TopicNotification&)> listener)
: m_handle{AddTopicListener(topic.GetHandle(), mask, listener)} {}
inline TopicListener::TopicListener(
Subscriber& subscriber, unsigned int mask,
std::function<void(const TopicNotification&)> listener)
: m_handle{AddTopicListener(subscriber.GetHandle(), mask, listener)} {}
inline TopicListener::TopicListener(
MultiSubscriber& subscriber, unsigned int mask,
std::function<void(const TopicNotification&)> listener)
: m_handle{AddTopicListener(subscriber.GetHandle(), mask, listener)} {}
inline TopicListener::TopicListener(
NetworkTableEntry& entry, unsigned int mask,
std::function<void(const TopicNotification&)> 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<const std::string_view> 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<TopicNotification> TopicListenerPoller::ReadQueue() {
return nt::ReadTopicListenerQueue(m_handle);
}
} // namespace nt

View File

@@ -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 <functional>
#include <vector>
#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<void(const ValueNotification&)> 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<void(const ValueNotification&)> 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<void(const ValueNotification&)> 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<ValueNotification> ReadQueue();
private:
NT_ValueListenerPoller m_handle{0};
};
} // namespace nt
#include "ValueListener.inc"

View File

@@ -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 <utility>
#include <vector>
#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<void(const ValueNotification&)> listener)
: m_handle{AddValueListener(subscriber.GetHandle(), mask, listener)} {}
inline ValueListener::ValueListener(
MultiSubscriber& subscriber, unsigned int mask,
std::function<void(const ValueNotification&)> listener)
: m_handle{AddValueListener(subscriber.GetHandle(), mask, listener)} {}
inline ValueListener::ValueListener(
NetworkTableEntry& entry, unsigned int mask,
std::function<void(const ValueNotification&)> 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<ValueNotification> ValueListenerPoller::ReadQueue() {
return nt::ReadValueListenerQueue(m_handle);
}
} // namespace nt

View File

@@ -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);
/** @} */

View File

@@ -13,6 +13,7 @@
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
#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<ConnectionInfo, TopicInfo, ValueEventData, LogMessage> data;
const ConnectionInfo* GetConnectionInfo() const {
return std::get_if<nt::ConnectionInfo>(&data);
}
ConnectionInfo* GetConnectionInfo() {
return std::get_if<nt::ConnectionInfo>(&data);
}
const TopicInfo* GetTopicInfo() const {
return std::get_if<nt::TopicInfo>(&data);
}
TopicInfo* GetTopicInfo() { return std::get_if<nt::TopicInfo>(&data); }
const ValueEventData* GetValueEventData() const {
return std::get_if<nt::ValueEventData>(&data);
}
ValueEventData* GetValueEventData() {
return std::get_if<nt::ValueEventData>(&data);
}
const LogMessage* GetLogMessage() const {
return std::get_if<nt::LogMessage>(&data);
}
LogMessage* GetLogMessage() { return std::get_if<nt::LogMessage>(&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<void(const Event&)>;
/**
* 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<Event> 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<const std::string_view> prefixes, unsigned int mask,
std::function<void(const TopicNotification&)> callback);
NT_Listener AddListener(NT_Inst inst,
std::span<const std::string_view> 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<void(const TopicNotification&)> 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<TopicNotification> 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<const std::string_view> prefixes,
unsigned int mask);
NT_Listener AddPolledListener(NT_ListenerPoller poller,
std::span<const std::string_view> 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<void(const ValueNotification&)> 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<ValueNotification> 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<void(const ConnectionNotification& event)> 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<ConnectionNotification> 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<void(const LogMessage& msg)> 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<LogMessage> 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);
/** @} */
/** @} */