#pragma once
#include <stdint.h>
#include <atomic>
#include <functional>
#include <memory>
#include <ranges>
#include <span>
#include <tuple>
#include <utility>
#include <vector>
#include <wpi/SmallVector.h>
#include <wpi/json_fwd.h>
#include <wpi/mutex.h>
#include <wpi/struct/Struct.h>
#include "networktables/NetworkTableInstance.h"
#include "networktables/Topic.h"
#include "ntcore_cpp.h"
namespace nt {
template <typename T, typename... I>
requires wpi::StructSerializable<T, I...>
class StructArrayTopic;
template <typename T, typename... I>
requires wpi::StructSerializable<T, I...>
class StructArraySubscriber : public Subscriber {
using S = wpi::Struct<T, I...>;
public:
using TopicType = StructArrayTopic<T, I...>;
using ValueType = std::vector<T>;
using ParamType = std::span<const T>;
using TimestampedValueType = Timestamped<ValueType>;
StructArraySubscriber() = default;
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
StructArraySubscriber(NT_Subscriber handle, U&& defaultValue, I... info)
: Subscriber{handle},
m_defaultValue{defaultValue.begin(), defaultValue.end()},
m_info{std::move(info)...} {
}
ValueType Get() const { return Get(m_defaultValue); }
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
ValueType Get(U&& defaultValue) const {
return GetAtomic(std::forward<U>(defaultValue)).value;
}
ValueType Get(std::span<const T> defaultValue) const {
return GetAtomic(defaultValue).value;
}
TimestampedValueType GetAtomic() const { return GetAtomic(m_defaultValue); }
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
TimestampedValueType GetAtomic(U&& defaultValue) const {
wpi::SmallVector<uint8_t, 128> buf;
size_t size = std::apply(S::GetSize, m_info);
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
if (view.value.size() == 0 || (view.value.size() % size) != 0) {
return {0, 0, std::forward<U>(defaultValue)};
}
TimestampedValueType rv{view.time, view.serverTime, {}};
rv.value.reserve(view.value.size() / size);
for (auto in = view.value.begin(), end = view.value.end(); in != end;
in += size) {
std::apply(
[&](const I&... info) {
rv.value.emplace_back(S::Unpack(
std::span<const uint8_t>{std::to_address(in), size}, info...));
},
m_info);
}
return rv;
}
TimestampedValueType GetAtomic(std::span<const T> defaultValue) const {
wpi::SmallVector<uint8_t, 128> buf;
size_t size = std::apply(S::GetSize, m_info);
TimestampedRawView view = ::nt::GetAtomicRaw(m_subHandle, buf, {});
if (view.value.size() == 0 || (view.value.size() % size) != 0) {
return {0, 0, {defaultValue.begin(), defaultValue.end()}};
}
TimestampedValueType rv{view.time, view.serverTime, {}};
rv.value.reserve(view.value.size() / size);
for (auto in = view.value.begin(), end = view.value.end(); in != end;
in += size) {
std::apply(
[&](const I&... info) {
rv.value.emplace_back(S::Unpack(
std::span<const uint8_t>{std::to_address(in), size}, info...));
},
m_info);
}
return rv;
}
std::vector<TimestampedValueType> ReadQueue() {
auto raw = ::nt::ReadQueueRaw(m_subHandle);
std::vector<TimestampedValueType> rv;
rv.reserve(raw.size());
size_t size = std::apply(S::GetSize, m_info);
for (auto&& r : raw) {
if (r.value.size() == 0 || (r.value.size() % size) != 0) {
continue;
}
std::vector<T> values;
values.reserve(r.value.size() / size);
for (auto in = r.value.begin(), end = r.value.end(); in != end;
in += size) {
std::apply(
[&](const I&... info) {
values.emplace_back(
S::Unpack(std::span<const uint8_t>{std::to_address(in), size},
info...));
},
m_info);
}
rv.emplace_back(r.time, r.serverTime, std::move(values));
}
return rv;
}
TopicType GetTopic() const {
return std::apply(
[&](const I&... info) {
return StructArrayTopic<T, I...>{
::nt::GetTopicFromHandle(m_subHandle), info...};
},
m_info);
}
private:
ValueType m_defaultValue;
[[no_unique_address]]
std::tuple<I...> m_info;
};
template <typename T, typename... I>
requires wpi::StructSerializable<T, I...>
class StructArrayPublisher : public Publisher {
using S = wpi::Struct<T, I...>;
public:
using TopicType = StructArrayTopic<T, I...>;
using ValueType = std::vector<T>;
using ParamType = std::span<const T>;
using TimestampedValueType = Timestamped<ValueType>;
StructArrayPublisher() = default;
explicit StructArrayPublisher(NT_Publisher handle, I... info)
: Publisher{handle}, m_info{std::move(info)...} {}
StructArrayPublisher(const StructArrayPublisher&) = delete;
StructArrayPublisher& operator=(const StructArrayPublisher&) = delete;
StructArrayPublisher(StructArrayPublisher&& rhs)
: Publisher{std::move(rhs)},
m_buf{std::move(rhs.m_buf)},
m_schemaPublished{
rhs.m_schemaPublished.load(std::memory_order_relaxed)},
m_info{std::move(rhs.m_info)} {}
StructArrayPublisher& operator=(StructArrayPublisher&& rhs) {
Publisher::operator=(std::move(rhs));
m_buf = std::move(rhs.m_buf);
m_schemaPublished.store(
rhs.m_schemaPublished.load(std::memory_order_relaxed),
std::memory_order_relaxed);
m_info = std::move(rhs.m_info);
return *this;
}
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
void Set(U&& value, int64_t time = 0) {
std::apply(
[&](const I&... info) {
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
GetTopic().GetInstance().template AddStructSchema<T>(info...);
}
m_buf.Write(
std::forward<U>(value),
[&](auto bytes) { ::nt::SetRaw(m_pubHandle, bytes, time); },
info...);
},
m_info);
}
void Set(std::span<const T> value, int64_t time = 0) {
std::apply(
[&](const I&... info) {
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
GetTopic().GetInstance().template AddStructSchema<T>(info...);
}
m_buf.Write(
value,
[&](auto bytes) { ::nt::SetRaw(m_pubHandle, bytes, time); },
info...);
},
m_info);
}
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
void SetDefault(U&& value) {
std::apply(
[&](const I&... info) {
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
GetTopic().GetInstance().template AddStructSchema<T>(info...);
}
m_buf.Write(
std::forward<U>(value),
[&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); },
info...);
},
m_info);
}
void SetDefault(std::span<const T> value) {
std::apply(
[&](const I&... info) {
if (!m_schemaPublished.exchange(true, std::memory_order_relaxed)) {
GetTopic().GetInstance().template AddStructSchema<T>(info...);
}
m_buf.Write(
value,
[&](auto bytes) { ::nt::SetDefaultRaw(m_pubHandle, bytes); },
info...);
},
m_info);
}
TopicType GetTopic() const {
return std::apply(
[&](const I&... info) {
return StructArrayTopic<T, I...>{
::nt::GetTopicFromHandle(m_pubHandle), info...};
},
m_info);
}
private:
wpi::StructArrayBuffer<T, I...> m_buf;
std::atomic_bool m_schemaPublished{false};
[[no_unique_address]]
std::tuple<I...> m_info;
};
template <typename T, typename... I>
requires wpi::StructSerializable<T, I...>
class StructArrayEntry final : public StructArraySubscriber<T, I...>,
public StructArrayPublisher<T, I...> {
public:
using SubscriberType = StructArraySubscriber<T, I...>;
using PublisherType = StructArrayPublisher<T, I...>;
using TopicType = StructArrayTopic<T, I...>;
using ValueType = std::vector<T>;
using ParamType = std::span<const T>;
using TimestampedValueType = Timestamped<ValueType>;
StructArrayEntry() = default;
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
StructArrayEntry(NT_Entry handle, U&& defaultValue, const I&... info)
: StructArraySubscriber<T, I...>{handle, defaultValue, info...},
StructArrayPublisher<T, I...>{handle, info...} {
}
explicit operator bool() const { return this->m_subHandle != 0; }
NT_Entry GetHandle() const { return this->m_subHandle; }
TopicType GetTopic() const {
return StructArraySubscriber<T, I...>::GetTopic();
}
void Unpublish() { ::nt::Unpublish(this->m_pubHandle); }
};
template <typename T, typename... I>
requires wpi::StructSerializable<T, I...>
class StructArrayTopic final : public Topic {
public:
using SubscriberType = StructArraySubscriber<T, I...>;
using PublisherType = StructArrayPublisher<T, I...>;
using EntryType = StructArrayEntry<T, I...>;
using ValueType = std::vector<T>;
using ParamType = std::span<const T>;
using TimestampedValueType = Timestamped<ValueType>;
StructArrayTopic() = default;
explicit StructArrayTopic(NT_Topic handle, I... info)
: Topic{handle}, m_info{std::move(info)...} {}
explicit StructArrayTopic(Topic topic, I... info)
: Topic{topic}, m_info{std::move(info)...} {}
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
[[nodiscard]]
SubscriberType Subscribe(
U&& defaultValue, const PubSubOptions& options = kDefaultPubSubOptions) {
return std::apply(
[&](const I&... info) {
return StructArraySubscriber<T, I...>{
::nt::Subscribe(
m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
info...),
options),
defaultValue, info...};
},
m_info);
}
[[nodiscard]]
SubscriberType Subscribe(
std::span<const T> defaultValue,
const PubSubOptions& options = kDefaultPubSubOptions) {
return std::apply(
[&](const I&... info) {
return StructArraySubscriber<T, I...>{
::nt::Subscribe(
m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
info...),
options),
defaultValue, info...};
},
m_info);
}
[[nodiscard]]
PublisherType Publish(const PubSubOptions& options = kDefaultPubSubOptions) {
return std::apply(
[&](const I&... info) {
return StructArrayPublisher<T, I...>{
::nt::Publish(
m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
info...),
options),
info...};
},
m_info);
}
[[nodiscard]]
PublisherType PublishEx(
const wpi::json& properties,
const PubSubOptions& options = kDefaultPubSubOptions) {
return std::apply(
[&](const I&... info) {
return StructArrayPublisher<T, I...>{
::nt::PublishEx(
m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
info...),
properties, options),
info...};
},
m_info);
}
template <typename U>
#if __cpp_lib_ranges >= 201911L
requires std::ranges::range<U> &&
std::convertible_to<std::ranges::range_value_t<U>, T>
#endif
[[nodiscard]]
EntryType GetEntry(U&& defaultValue,
const PubSubOptions& options = kDefaultPubSubOptions) {
return std::apply(
[&](const I&... info) {
return StructArrayEntry<T, I...>{
::nt::GetEntry(
m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
info...),
options),
defaultValue, info...};
},
m_info);
}
[[nodiscard]]
EntryType GetEntry(std::span<const T> defaultValue,
const PubSubOptions& options = kDefaultPubSubOptions) {
return std::apply(
[&](const I&... info) {
return StructArrayEntry<T, I...>{
::nt::GetEntry(
m_handle, NT_RAW,
wpi::MakeStructArrayTypeString<T, std::dynamic_extent>(
info...),
options),
defaultValue, info...};
},
m_info);
}
private:
[[no_unique_address]]
std::tuple<I...> m_info;
};
}