mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-01 02:41:48 +00:00
[ntcore] Allow numeric-compatible value sets (#4620)
Also fix entry publishing behavior to allow numerically compatible set default publish following a subscribe.
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include "Log.h"
|
||||
#include "PubSubOptions.h"
|
||||
#include "Types_internal.h"
|
||||
#include "Value_internal.h"
|
||||
#include "networktables/NetworkTableValue.h"
|
||||
#include "ntcore_c.h"
|
||||
|
||||
@@ -271,7 +272,7 @@ struct LSImpl {
|
||||
unsigned int eventFlags, bool sendNetwork,
|
||||
bool updateFlags = true);
|
||||
|
||||
void RefreshPubSubActive(TopicData* topic);
|
||||
void RefreshPubSubActive(TopicData* topic, bool warnOnSubMismatch);
|
||||
|
||||
void NetworkAnnounce(TopicData* topic, std::string_view typeStr,
|
||||
const wpi::json& properties, NT_Publisher pubHandle);
|
||||
@@ -394,17 +395,9 @@ void PublisherData::UpdateActive() {
|
||||
void SubscriberData::UpdateActive() {
|
||||
// for subscribers, unassigned is a wildcard
|
||||
// also allow numerically compatible subscribers
|
||||
active =
|
||||
config.type == NT_UNASSIGNED ||
|
||||
(config.type == topic->type && config.typeStr == topic->typeStr) ||
|
||||
((config.type & (NT_INTEGER | NT_FLOAT | NT_DOUBLE)) != 0 &&
|
||||
(config.type & (NT_INTEGER | NT_FLOAT | NT_DOUBLE)) ==
|
||||
(topic->type & (NT_INTEGER | NT_FLOAT | NT_DOUBLE))) ||
|
||||
((config.type & (NT_INTEGER_ARRAY | NT_FLOAT_ARRAY | NT_DOUBLE_ARRAY)) !=
|
||||
0 &&
|
||||
(config.type & (NT_INTEGER_ARRAY | NT_FLOAT_ARRAY | NT_DOUBLE_ARRAY)) ==
|
||||
(topic->type &
|
||||
(NT_INTEGER_ARRAY | NT_FLOAT_ARRAY | NT_DOUBLE_ARRAY)));
|
||||
active = config.type == NT_UNASSIGNED ||
|
||||
(config.type == topic->type && config.typeStr == topic->typeStr) ||
|
||||
IsNumericCompatible(config.type, topic->type);
|
||||
}
|
||||
|
||||
void LSImpl::NotifyTopic(TopicData* topic, unsigned int eventFlags) {
|
||||
@@ -620,12 +613,19 @@ void LSImpl::PropertiesUpdated(TopicData* topic, const wpi::json& update,
|
||||
}
|
||||
}
|
||||
|
||||
void LSImpl::RefreshPubSubActive(TopicData* topic) {
|
||||
void LSImpl::RefreshPubSubActive(TopicData* topic, bool warnOnSubMismatch) {
|
||||
for (auto&& publisher : topic->localPublishers) {
|
||||
publisher->UpdateActive();
|
||||
}
|
||||
for (auto&& subscriber : topic->localSubscribers) {
|
||||
subscriber->UpdateActive();
|
||||
if (warnOnSubMismatch && topic->Exists() && !subscriber->active) {
|
||||
// warn on type mismatch
|
||||
INFO(
|
||||
"local subscribe to '{}' disabled due to type mismatch (wanted '{}', "
|
||||
"published as '{}')",
|
||||
topic->name, subscriber->config.typeStr, topic->typeStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,7 +653,7 @@ void LSImpl::NetworkAnnounce(TopicData* topic, std::string_view typeStr,
|
||||
}
|
||||
topic->type = type;
|
||||
topic->typeStr = typeStr;
|
||||
RefreshPubSubActive(topic);
|
||||
RefreshPubSubActive(topic, true);
|
||||
}
|
||||
if (!didExist) {
|
||||
event |= NT_EVENT_PUBLISH;
|
||||
@@ -702,7 +702,7 @@ void LSImpl::RemoveNetworkPublisher(TopicData* topic) {
|
||||
nextPub->config.typeStr != topic->typeStr) {
|
||||
topic->type = nextPub->config.type;
|
||||
topic->typeStr = nextPub->config.typeStr;
|
||||
RefreshPubSubActive(topic);
|
||||
RefreshPubSubActive(topic, false);
|
||||
// this may result in a duplicate publish warning on the server side,
|
||||
// but send one anyway in this case just to be sure
|
||||
if (nextPub->active && m_network) {
|
||||
@@ -730,19 +730,20 @@ PublisherData* LSImpl::AddLocalPublisher(TopicData* topic,
|
||||
topic->localPublishers.Add(publisher);
|
||||
|
||||
if (!didExist) {
|
||||
DEBUG4("AddLocalPublisher: setting {} type {} typestr {}", topic->name,
|
||||
static_cast<int>(config.type), config.typeStr);
|
||||
// set the type to the published type
|
||||
topic->type = config.type;
|
||||
topic->typeStr = config.typeStr;
|
||||
RefreshPubSubActive(topic);
|
||||
RefreshPubSubActive(topic, true);
|
||||
|
||||
if (properties.is_null()) {
|
||||
topic->properties = wpi::json::object();
|
||||
} else if (properties.is_object()) {
|
||||
topic->properties = properties;
|
||||
} else {
|
||||
WPI_WARNING(m_logger,
|
||||
"ignoring non-object properties when publishing '{}'",
|
||||
topic->name);
|
||||
WARNING("ignoring non-object properties when publishing '{}'",
|
||||
topic->name);
|
||||
topic->properties = wpi::json::object();
|
||||
}
|
||||
|
||||
@@ -794,7 +795,7 @@ std::unique_ptr<PublisherData> LSImpl::RemoveLocalPublisher(
|
||||
nextPub->config.typeStr != topic->typeStr) {
|
||||
topic->type = nextPub->config.type;
|
||||
topic->typeStr = nextPub->config.typeStr;
|
||||
RefreshPubSubActive(topic);
|
||||
RefreshPubSubActive(topic, false);
|
||||
if (nextPub->active && m_network) {
|
||||
m_network->Publish(nextPub->handle, topic->handle, topic->name,
|
||||
topic->typeStr, topic->properties,
|
||||
@@ -817,7 +818,7 @@ SubscriberData* LSImpl::AddLocalSubscriber(TopicData* topic,
|
||||
// warn on type mismatch
|
||||
INFO(
|
||||
"local subscribe to '{}' disabled due to type mismatch (wanted '{}', "
|
||||
"currently '{}')",
|
||||
"published as '{}')",
|
||||
topic->name, config.typeStr, topic->typeStr);
|
||||
}
|
||||
if (m_network) {
|
||||
@@ -1176,8 +1177,12 @@ PublisherData* LSImpl::PublishEntry(EntryData* entry, NT_Type type) {
|
||||
entry->subscriber->config.typeStr = typeStr;
|
||||
} else if (entry->subscriber->config.type != type ||
|
||||
entry->subscriber->config.typeStr != typeStr) {
|
||||
// don't allow dynamically changing the type of an entry
|
||||
return nullptr;
|
||||
if (!IsNumericCompatible(type, entry->subscriber->config.type)) {
|
||||
// don't allow dynamically changing the type of an entry
|
||||
ERROR("cannot publish entry {} as type {}, previously subscribed as {}",
|
||||
entry->topic->name, typeStr, entry->subscriber->config.typeStr);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
// create publisher
|
||||
entry->publisher = AddLocalPublisher(entry->topic, wpi::json::object(),
|
||||
@@ -1199,6 +1204,10 @@ bool LSImpl::PublishLocalValue(PublisherData* publisher, const Value& value) {
|
||||
}
|
||||
if (publisher->topic->type != NT_UNASSIGNED &&
|
||||
publisher->topic->type != value.type()) {
|
||||
if (IsNumericCompatible(publisher->topic->type, value.type())) {
|
||||
return PublishLocalValue(
|
||||
publisher, ConvertNumericValue(value, publisher->topic->type));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (publisher->active) {
|
||||
@@ -1229,27 +1238,39 @@ bool LSImpl::SetEntryValue(NT_Handle pubentryHandle, const Value& value) {
|
||||
|
||||
bool LSImpl::SetDefaultEntryValue(NT_Handle pubsubentryHandle,
|
||||
const Value& value) {
|
||||
DEBUG4("SetDefaultEntryValue({}, {})", pubsubentryHandle,
|
||||
static_cast<int>(value.type()));
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
if (auto topic = GetTopic(pubsubentryHandle)) {
|
||||
if (topic->type == NT_UNASSIGNED || topic->type == value.type()) {
|
||||
// force value timestamps to 0
|
||||
topic->type = value.type();
|
||||
topic->lastValue = value;
|
||||
topic->lastValue.SetTime(0);
|
||||
topic->lastValue.SetServerTime(0);
|
||||
|
||||
if (!topic->lastValue &&
|
||||
(topic->type == NT_UNASSIGNED || topic->type == value.type() ||
|
||||
IsNumericCompatible(topic->type, value.type()))) {
|
||||
// publish if we haven't yet
|
||||
auto publisher = m_publishers.Get(pubsubentryHandle);
|
||||
if (!publisher) {
|
||||
if (auto entry = m_entries.Get(pubsubentryHandle)) {
|
||||
publisher = PublishEntry(entry, value.type());
|
||||
}
|
||||
if (!publisher) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
PublishLocalValue(publisher, topic->lastValue);
|
||||
|
||||
// force value timestamps to 0
|
||||
if (topic->type == NT_UNASSIGNED) {
|
||||
topic->type = value.type();
|
||||
}
|
||||
if (topic->type == value.type()) {
|
||||
topic->lastValue = value;
|
||||
} else if (IsNumericCompatible(topic->type, value.type())) {
|
||||
topic->lastValue = ConvertNumericValue(value, topic->type);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
topic->lastValue.SetTime(0);
|
||||
topic->lastValue.SetServerTime(0);
|
||||
if (publisher) {
|
||||
PublishLocalValue(publisher, topic->lastValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2045,10 +2066,17 @@ READ_QUEUE_NUMBER(Double)
|
||||
Value LocalStorage::GetEntryValue(NT_Handle subentryHandle) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (auto subscriber = m_impl->GetSubEntry(subentryHandle)) {
|
||||
return subscriber->topic->lastValue;
|
||||
} else {
|
||||
return {};
|
||||
if (subscriber->config.type == NT_UNASSIGNED ||
|
||||
!subscriber->topic->lastValue ||
|
||||
subscriber->config.type == subscriber->topic->lastValue.type()) {
|
||||
return subscriber->topic->lastValue;
|
||||
} else if (IsNumericCompatible(subscriber->config.type,
|
||||
subscriber->topic->lastValue.type())) {
|
||||
return ConvertNumericValue(subscriber->topic->lastValue,
|
||||
subscriber->config.type);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void LocalStorage::SetEntryFlags(NT_Entry entryHandle, unsigned int flags) {
|
||||
|
||||
@@ -14,4 +14,17 @@ std::string_view TypeToString(NT_Type type);
|
||||
NT_Type StringToType(std::string_view typeStr);
|
||||
NT_Type StringToType3(std::string_view typeStr);
|
||||
|
||||
constexpr bool IsNumeric(NT_Type type) {
|
||||
return (type & (NT_INTEGER | NT_FLOAT | NT_DOUBLE)) != 0;
|
||||
}
|
||||
|
||||
constexpr bool IsNumericArray(NT_Type type) {
|
||||
return (type & (NT_INTEGER_ARRAY | NT_FLOAT_ARRAY | NT_DOUBLE_ARRAY)) != 0;
|
||||
}
|
||||
|
||||
constexpr bool IsNumericCompatible(NT_Type a, NT_Type b) {
|
||||
return (IsNumeric(a) && IsNumeric(b)) ||
|
||||
(IsNumericArray(a) && IsNumericArray(b));
|
||||
}
|
||||
|
||||
} // namespace nt
|
||||
|
||||
@@ -89,6 +89,15 @@ Value Value::MakeBooleanArray(std::span<const int> value, int64_t time) {
|
||||
return val;
|
||||
}
|
||||
|
||||
Value Value::MakeBooleanArray(std::vector<int>&& value, int64_t time) {
|
||||
Value val{NT_BOOLEAN_ARRAY, time, private_init{}};
|
||||
auto data = std::make_shared<std::vector<int>>(std::move(value));
|
||||
val.m_val.data.arr_boolean.arr = data->data();
|
||||
val.m_val.data.arr_boolean.size = data->size();
|
||||
val.m_storage = std::move(data);
|
||||
return val;
|
||||
}
|
||||
|
||||
Value Value::MakeIntegerArray(std::span<const int64_t> value, int64_t time) {
|
||||
Value val{NT_INTEGER_ARRAY, time, private_init{}};
|
||||
auto data =
|
||||
@@ -99,6 +108,15 @@ Value Value::MakeIntegerArray(std::span<const int64_t> value, int64_t time) {
|
||||
return val;
|
||||
}
|
||||
|
||||
Value Value::MakeIntegerArray(std::vector<int64_t>&& value, int64_t time) {
|
||||
Value val{NT_INTEGER_ARRAY, time, private_init{}};
|
||||
auto data = std::make_shared<std::vector<int64_t>>(std::move(value));
|
||||
val.m_val.data.arr_int.arr = data->data();
|
||||
val.m_val.data.arr_int.size = data->size();
|
||||
val.m_storage = std::move(data);
|
||||
return val;
|
||||
}
|
||||
|
||||
Value Value::MakeFloatArray(std::span<const float> value, int64_t time) {
|
||||
Value val{NT_FLOAT_ARRAY, time, private_init{}};
|
||||
auto data = std::make_shared<std::vector<float>>(value.begin(), value.end());
|
||||
@@ -108,6 +126,15 @@ Value Value::MakeFloatArray(std::span<const float> value, int64_t time) {
|
||||
return val;
|
||||
}
|
||||
|
||||
Value Value::MakeFloatArray(std::vector<float>&& value, int64_t time) {
|
||||
Value val{NT_FLOAT_ARRAY, time, private_init{}};
|
||||
auto data = std::make_shared<std::vector<float>>(std::move(value));
|
||||
val.m_val.data.arr_float.arr = data->data();
|
||||
val.m_val.data.arr_float.size = data->size();
|
||||
val.m_storage = std::move(data);
|
||||
return val;
|
||||
}
|
||||
|
||||
Value Value::MakeDoubleArray(std::span<const double> value, int64_t time) {
|
||||
Value val{NT_DOUBLE_ARRAY, time, private_init{}};
|
||||
auto data = std::make_shared<std::vector<double>>(value.begin(), value.end());
|
||||
@@ -117,6 +144,15 @@ Value Value::MakeDoubleArray(std::span<const double> value, int64_t time) {
|
||||
return val;
|
||||
}
|
||||
|
||||
Value Value::MakeDoubleArray(std::vector<double>&& value, int64_t time) {
|
||||
Value val{NT_DOUBLE_ARRAY, time, private_init{}};
|
||||
auto data = std::make_shared<std::vector<double>>(std::move(value));
|
||||
val.m_val.data.arr_double.arr = data->data();
|
||||
val.m_val.data.arr_double.size = data->size();
|
||||
val.m_storage = std::move(data);
|
||||
return val;
|
||||
}
|
||||
|
||||
Value Value::MakeStringArray(std::span<const std::string> value, int64_t time) {
|
||||
Value val{NT_STRING_ARRAY, time, private_init{}};
|
||||
auto data = std::make_shared<StringArrayStorage>(value);
|
||||
|
||||
49
ntcore/src/main/native/cpp/Value_internal.cpp
Normal file
49
ntcore/src/main/native/cpp/Value_internal.cpp
Normal 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.
|
||||
|
||||
#include "Value_internal.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
Value nt::ConvertNumericValue(const Value& value, NT_Type type) {
|
||||
switch (type) {
|
||||
case NT_INTEGER: {
|
||||
Value newval =
|
||||
Value::MakeInteger(GetNumericAs<int64_t>(value), value.time());
|
||||
newval.SetServerTime(value.server_time());
|
||||
return newval;
|
||||
}
|
||||
case NT_FLOAT: {
|
||||
Value newval = Value::MakeFloat(GetNumericAs<float>(value), value.time());
|
||||
newval.SetServerTime(value.server_time());
|
||||
return newval;
|
||||
}
|
||||
case NT_DOUBLE: {
|
||||
Value newval =
|
||||
Value::MakeDouble(GetNumericAs<double>(value), value.time());
|
||||
newval.SetServerTime(value.server_time());
|
||||
return newval;
|
||||
}
|
||||
case NT_INTEGER_ARRAY: {
|
||||
Value newval = Value::MakeIntegerArray(GetNumericArrayAs<int64_t>(value),
|
||||
value.time());
|
||||
newval.SetServerTime(value.server_time());
|
||||
return newval;
|
||||
}
|
||||
case NT_FLOAT_ARRAY: {
|
||||
Value newval =
|
||||
Value::MakeFloatArray(GetNumericArrayAs<float>(value), value.time());
|
||||
newval.SetServerTime(value.server_time());
|
||||
return newval;
|
||||
}
|
||||
case NT_DOUBLE_ARRAY: {
|
||||
Value newval = Value::MakeDoubleArray(GetNumericArrayAs<double>(value),
|
||||
value.time());
|
||||
newval.SetServerTime(value.server_time());
|
||||
return newval;
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <wpi/MemAlloc.h>
|
||||
|
||||
#include "networktables/NetworkTableValue.h"
|
||||
#include "ntcore_c.h"
|
||||
|
||||
namespace nt {
|
||||
@@ -56,4 +57,35 @@ O* ConvertToC(const std::basic_string<I>& in, size_t* out_len) {
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T GetNumericAs(const Value& value) {
|
||||
if (value.IsInteger()) {
|
||||
return static_cast<T>(value.GetInteger());
|
||||
} else if (value.IsFloat()) {
|
||||
return static_cast<T>(value.GetFloat());
|
||||
} else if (value.IsDouble()) {
|
||||
return static_cast<T>(value.GetDouble());
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> GetNumericArrayAs(const Value& value) {
|
||||
if (value.IsIntegerArray()) {
|
||||
auto arr = value.GetIntegerArray();
|
||||
return {arr.begin(), arr.end()};
|
||||
} else if (value.IsFloatArray()) {
|
||||
auto arr = value.GetFloatArray();
|
||||
return {arr.begin(), arr.end()};
|
||||
} else if (value.IsDoubleArray()) {
|
||||
auto arr = value.GetDoubleArray();
|
||||
return {arr.begin(), arr.end()};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Value ConvertNumericValue(const Value& value, NT_Type type);
|
||||
|
||||
} // namespace nt
|
||||
|
||||
@@ -471,6 +471,18 @@ class Value final {
|
||||
return MakeBooleanArray(std::span(value.begin(), value.end()), time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a boolean array entry value.
|
||||
*
|
||||
* @param value the value
|
||||
* @param time if nonzero, the creation time to use (instead of the current
|
||||
* time)
|
||||
* @return The entry value
|
||||
*
|
||||
* @note This function moves the values out of the vector.
|
||||
*/
|
||||
static Value MakeBooleanArray(std::vector<int>&& value, int64_t time = 0);
|
||||
|
||||
/**
|
||||
* Creates an integer array entry value.
|
||||
*
|
||||
@@ -495,6 +507,18 @@ class Value final {
|
||||
return MakeIntegerArray(std::span(value.begin(), value.end()), time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an integer array entry value.
|
||||
*
|
||||
* @param value the value
|
||||
* @param time if nonzero, the creation time to use (instead of the current
|
||||
* time)
|
||||
* @return The entry value
|
||||
*
|
||||
* @note This function moves the values out of the vector.
|
||||
*/
|
||||
static Value MakeIntegerArray(std::vector<int64_t>&& value, int64_t time = 0);
|
||||
|
||||
/**
|
||||
* Creates a float array entry value.
|
||||
*
|
||||
@@ -518,6 +542,18 @@ class Value final {
|
||||
return MakeFloatArray(std::span(value.begin(), value.end()), time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a float array entry value.
|
||||
*
|
||||
* @param value the value
|
||||
* @param time if nonzero, the creation time to use (instead of the current
|
||||
* time)
|
||||
* @return The entry value
|
||||
*
|
||||
* @note This function moves the values out of the vector.
|
||||
*/
|
||||
static Value MakeFloatArray(std::vector<float>&& value, int64_t time = 0);
|
||||
|
||||
/**
|
||||
* Creates a double array entry value.
|
||||
*
|
||||
@@ -541,6 +577,18 @@ class Value final {
|
||||
return MakeDoubleArray(std::span(value.begin(), value.end()), time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a double array entry value.
|
||||
*
|
||||
* @param value the value
|
||||
* @param time if nonzero, the creation time to use (instead of the current
|
||||
* time)
|
||||
* @return The entry value
|
||||
*
|
||||
* @note This function moves the values out of the vector.
|
||||
*/
|
||||
static Value MakeDoubleArray(std::vector<double>&& value, int64_t time = 0);
|
||||
|
||||
/**
|
||||
* Creates a string array entry value.
|
||||
*
|
||||
|
||||
@@ -15,19 +15,23 @@
|
||||
#include "ntcore_c.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::AllOf;
|
||||
using ::testing::ElementsAre;
|
||||
using ::testing::Field;
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::Property;
|
||||
using ::testing::Return;
|
||||
|
||||
namespace nt {
|
||||
|
||||
::testing::Matcher<const PubSubOptions&> IsPubSubOptions(
|
||||
const PubSubOptions& good) {
|
||||
return ::testing::AllOf(
|
||||
::testing::Field("periodic", &PubSubOptions::periodic, good.periodic),
|
||||
::testing::Field("pollStorageSize", &PubSubOptions::pollStorageSize,
|
||||
good.pollStorageSize),
|
||||
::testing::Field("logging", &PubSubOptions::sendAll, good.sendAll),
|
||||
::testing::Field("keepDuplicates", &PubSubOptions::keepDuplicates,
|
||||
good.keepDuplicates));
|
||||
return AllOf(Field("periodic", &PubSubOptions::periodic, good.periodic),
|
||||
Field("pollStorageSize", &PubSubOptions::pollStorageSize,
|
||||
good.pollStorageSize),
|
||||
Field("logging", &PubSubOptions::sendAll, good.sendAll),
|
||||
Field("keepDuplicates", &PubSubOptions::keepDuplicates,
|
||||
good.keepDuplicates));
|
||||
}
|
||||
|
||||
class LocalStorageTest : public ::testing::Test {
|
||||
@@ -247,6 +251,9 @@ TEST_F(LocalStorageTest, PubUnpubPub) {
|
||||
EXPECT_CALL(network, Publish(_, fooTopic, std::string_view{"foo"},
|
||||
std::string_view{"boolean"}, wpi::json::object(),
|
||||
IsPubSubOptions({})));
|
||||
EXPECT_CALL(logger, Call(NT_LOG_INFO, _, _,
|
||||
"local subscribe to 'foo' disabled due to type "
|
||||
"mismatch (wanted 'int', published as 'boolean')"));
|
||||
auto pub = storage.Publish(fooTopic, NT_BOOLEAN, "boolean", {}, {});
|
||||
|
||||
auto val = Value::MakeBoolean(true, 5);
|
||||
@@ -329,7 +336,7 @@ TEST_F(LocalStorageTest, LocalSubConflict) {
|
||||
IsPubSubOptions({})));
|
||||
EXPECT_CALL(logger, Call(NT_LOG_INFO, _, _,
|
||||
"local subscribe to 'foo' disabled due to type "
|
||||
"mismatch (wanted 'int', currently 'boolean')"));
|
||||
"mismatch (wanted 'int', published as 'boolean')"));
|
||||
storage.Subscribe(fooTopic, NT_INTEGER, "int", {});
|
||||
}
|
||||
|
||||
@@ -466,6 +473,12 @@ TEST_F(LocalStorageTest, SetValueEmptyUntypedEntry) {
|
||||
}
|
||||
|
||||
TEST_F(LocalStorageTest, PublishUntyped) {
|
||||
EXPECT_CALL(
|
||||
logger,
|
||||
Call(
|
||||
NT_LOG_ERROR, _, _,
|
||||
"cannot publish 'foo' with an unassigned type or empty type string"));
|
||||
|
||||
EXPECT_EQ(storage.Publish(fooTopic, NT_UNASSIGNED, "", {}, {}), 0u);
|
||||
}
|
||||
|
||||
@@ -473,4 +486,205 @@ TEST_F(LocalStorageTest, SetValueInvalidHandle) {
|
||||
EXPECT_FALSE(storage.SetEntryValue(0u, {}));
|
||||
}
|
||||
|
||||
class LocalStorageNumberVariantsTest : public LocalStorageTest {
|
||||
public:
|
||||
void CreateSubscriber(NT_Handle* handle, std::string_view name, NT_Type type,
|
||||
std::string_view typeStr);
|
||||
void CreateSubscribers();
|
||||
void CreateSubscribersArray();
|
||||
|
||||
NT_Subscriber sub1, sub2, sub3, sub4;
|
||||
NT_Entry entry;
|
||||
|
||||
struct SubEntry {
|
||||
SubEntry(NT_Handle subentry, NT_Type type, std::string_view name)
|
||||
: subentry{subentry}, type{type}, name{name} {}
|
||||
NT_Handle subentry;
|
||||
NT_Type type;
|
||||
std::string name;
|
||||
};
|
||||
std::vector<SubEntry> subentries;
|
||||
};
|
||||
|
||||
void LocalStorageNumberVariantsTest::CreateSubscriber(
|
||||
NT_Handle* handle, std::string_view name, NT_Type type,
|
||||
std::string_view typeStr) {
|
||||
*handle = storage.Subscribe(fooTopic, type, typeStr, {});
|
||||
subentries.emplace_back(*handle, type, name);
|
||||
}
|
||||
|
||||
void LocalStorageNumberVariantsTest::CreateSubscribers() {
|
||||
EXPECT_CALL(logger,
|
||||
Call(NT_LOG_INFO, _, _,
|
||||
"local subscribe to 'foo' disabled due to type "
|
||||
"mismatch (wanted 'boolean', published as 'double')"));
|
||||
CreateSubscriber(&sub1, "subDouble", NT_DOUBLE, "double");
|
||||
CreateSubscriber(&sub2, "subInteger", NT_INTEGER, "int");
|
||||
CreateSubscriber(&sub3, "subFloat", NT_FLOAT, "float");
|
||||
CreateSubscriber(&sub4, "subBoolean", NT_BOOLEAN, "boolean");
|
||||
entry = storage.GetEntry("foo");
|
||||
subentries.emplace_back(entry, NT_UNASSIGNED, "entry");
|
||||
}
|
||||
|
||||
void LocalStorageNumberVariantsTest::CreateSubscribersArray() {
|
||||
EXPECT_CALL(logger,
|
||||
Call(NT_LOG_INFO, _, _,
|
||||
"local subscribe to 'foo' disabled due to type "
|
||||
"mismatch (wanted 'boolean[]', published as 'double[]')"));
|
||||
CreateSubscriber(&sub1, "subDouble", NT_DOUBLE_ARRAY, "double[]");
|
||||
CreateSubscriber(&sub2, "subInteger", NT_INTEGER_ARRAY, "int[]");
|
||||
CreateSubscriber(&sub3, "subFloat", NT_FLOAT_ARRAY, "float[]");
|
||||
CreateSubscriber(&sub4, "subBoolean", NT_BOOLEAN_ARRAY, "boolean[]");
|
||||
entry = storage.GetEntry("foo");
|
||||
subentries.emplace_back(entry, NT_UNASSIGNED, "entry");
|
||||
}
|
||||
|
||||
TEST_F(LocalStorageNumberVariantsTest, GetEntryPubAfter) {
|
||||
EXPECT_CALL(network, Subscribe(_, _, _)).Times(5);
|
||||
EXPECT_CALL(network, Publish(_, _, _, _, _, _)).Times(1);
|
||||
EXPECT_CALL(network, SetValue(_, _)).Times(1);
|
||||
CreateSubscribers();
|
||||
auto pub = storage.Publish(fooTopic, NT_DOUBLE, "double", {}, {});
|
||||
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
|
||||
// all subscribers get the actual type and time
|
||||
for (auto&& subentry : subentries) {
|
||||
SCOPED_TRACE(subentry.name);
|
||||
EXPECT_EQ(storage.GetEntryType(subentry.subentry), NT_DOUBLE);
|
||||
EXPECT_EQ(storage.GetEntryLastChange(subentry.subentry), 50);
|
||||
}
|
||||
// for subscribers, they get a converted value or nothing on mismatch
|
||||
EXPECT_EQ(storage.GetEntryValue(sub1), Value::MakeDouble(1.0, 50));
|
||||
EXPECT_EQ(storage.GetEntryValue(sub2), Value::MakeInteger(1, 50));
|
||||
EXPECT_EQ(storage.GetEntryValue(sub3), Value::MakeFloat(1.0, 50));
|
||||
EXPECT_EQ(storage.GetEntryValue(sub4), Value{});
|
||||
// entrys just get whatever the value is
|
||||
EXPECT_EQ(storage.GetEntryValue(entry), Value::MakeDouble(1.0, 50));
|
||||
}
|
||||
|
||||
TEST_F(LocalStorageNumberVariantsTest, GetEntryPubBefore) {
|
||||
EXPECT_CALL(network, Subscribe(_, _, _)).Times(5);
|
||||
EXPECT_CALL(network, Publish(_, _, _, _, _, _)).Times(1);
|
||||
EXPECT_CALL(network, SetValue(_, _)).Times(1);
|
||||
auto pub = storage.Publish(fooTopic, NT_DOUBLE, "double", {}, {});
|
||||
CreateSubscribers();
|
||||
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
|
||||
// all subscribers get the actual type and time
|
||||
for (auto&& subentry : subentries) {
|
||||
SCOPED_TRACE(subentry.name);
|
||||
EXPECT_EQ(storage.GetEntryType(subentry.subentry), NT_DOUBLE);
|
||||
EXPECT_EQ(storage.GetEntryLastChange(subentry.subentry), 50);
|
||||
}
|
||||
// for subscribers, they get a converted value or nothing on mismatch
|
||||
EXPECT_EQ(storage.GetEntryValue(sub1), Value::MakeDouble(1.0, 50));
|
||||
EXPECT_EQ(storage.GetEntryValue(sub2), Value::MakeInteger(1, 50));
|
||||
EXPECT_EQ(storage.GetEntryValue(sub3), Value::MakeFloat(1.0, 50));
|
||||
EXPECT_EQ(storage.GetEntryValue(sub4), Value{});
|
||||
// entrys just get whatever the value is
|
||||
EXPECT_EQ(storage.GetEntryValue(entry), Value::MakeDouble(1.0, 50));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
::testing::Matcher<const T&> TSEq(auto value, int64_t time) {
|
||||
return AllOf(Field("value", &T::value, value), Field("time", &T::time, time));
|
||||
}
|
||||
|
||||
TEST_F(LocalStorageNumberVariantsTest, GetAtomic) {
|
||||
EXPECT_CALL(network, Subscribe(_, _, _)).Times(5);
|
||||
EXPECT_CALL(network, Publish(_, _, _, _, _, _)).Times(1);
|
||||
EXPECT_CALL(network, SetValue(_, _)).Times(1);
|
||||
auto pub = storage.Publish(fooTopic, NT_DOUBLE, "double", {}, {});
|
||||
CreateSubscribers();
|
||||
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
|
||||
|
||||
for (auto&& subentry : subentries) {
|
||||
SCOPED_TRACE(subentry.name);
|
||||
EXPECT_THAT(storage.GetAtomicDouble(subentry.subentry, 0),
|
||||
TSEq<TimestampedDouble>(1.0, 50));
|
||||
EXPECT_THAT(storage.GetAtomicInteger(subentry.subentry, 0),
|
||||
TSEq<TimestampedInteger>(1, 50));
|
||||
EXPECT_THAT(storage.GetAtomicFloat(subentry.subentry, 0),
|
||||
TSEq<TimestampedFloat>(1.0, 50));
|
||||
EXPECT_THAT(storage.GetAtomicBoolean(subentry.subentry, false),
|
||||
TSEq<TimestampedBoolean>(false, 0));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
::testing::Matcher<const T&> TSSpanEq(std::span<U> value, int64_t time) {
|
||||
return AllOf(
|
||||
Field("value", &T::value, wpi::SpanEq(std::span<const U>(value))),
|
||||
Field("time", &T::time, time));
|
||||
}
|
||||
|
||||
TEST_F(LocalStorageNumberVariantsTest, GetAtomicArray) {
|
||||
EXPECT_CALL(network, Subscribe(_, _, _)).Times(5);
|
||||
EXPECT_CALL(network, Publish(_, _, _, _, _, _)).Times(1);
|
||||
EXPECT_CALL(network, SetValue(_, _)).Times(1);
|
||||
auto pub = storage.Publish(fooTopic, NT_DOUBLE_ARRAY, "double[]", {}, {});
|
||||
CreateSubscribersArray();
|
||||
storage.SetEntryValue(pub, Value::MakeDoubleArray({1.0}, 50));
|
||||
|
||||
for (auto&& subentry : subentries) {
|
||||
SCOPED_TRACE(subentry.name);
|
||||
double doubleVal = 1.0;
|
||||
EXPECT_THAT(storage.GetAtomicDoubleArray(subentry.subentry, {}),
|
||||
TSSpanEq<TimestampedDoubleArray>(std::span{&doubleVal, 1}, 50));
|
||||
int64_t intVal = 1;
|
||||
EXPECT_THAT(storage.GetAtomicIntegerArray(subentry.subentry, {}),
|
||||
TSSpanEq<TimestampedIntegerArray>(std::span{&intVal, 1}, 50));
|
||||
float floatVal = 1.0;
|
||||
EXPECT_THAT(storage.GetAtomicFloatArray(subentry.subentry, {}),
|
||||
TSSpanEq<TimestampedFloatArray>(std::span{&floatVal, 1}, 50));
|
||||
EXPECT_THAT(storage.GetAtomicBooleanArray(subentry.subentry, {}),
|
||||
TSSpanEq<TimestampedBooleanArray>(std::span<int>{}, 0));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LocalStorageNumberVariantsTest, ReadQueue) {
|
||||
EXPECT_CALL(network, Subscribe(_, _, _)).Times(5);
|
||||
EXPECT_CALL(network, Publish(_, _, _, _, _, _)).Times(1);
|
||||
EXPECT_CALL(network, SetValue(_, _)).Times(4);
|
||||
auto pub = storage.Publish(fooTopic, NT_DOUBLE, "double", {}, {});
|
||||
CreateSubscribers();
|
||||
|
||||
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
|
||||
for (auto&& subentry : subentries) {
|
||||
SCOPED_TRACE(subentry.name);
|
||||
if (subentry.type == NT_BOOLEAN) {
|
||||
EXPECT_THAT(storage.ReadQueueDouble(subentry.subentry), IsEmpty());
|
||||
} else {
|
||||
EXPECT_THAT(storage.ReadQueueDouble(subentry.subentry),
|
||||
ElementsAre(TSEq<TimestampedDouble>(1.0, 50)));
|
||||
}
|
||||
}
|
||||
|
||||
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
|
||||
for (auto&& subentry : subentries) {
|
||||
SCOPED_TRACE(subentry.name);
|
||||
if (subentry.type == NT_BOOLEAN) {
|
||||
EXPECT_THAT(storage.ReadQueueInteger(subentry.subentry), IsEmpty());
|
||||
} else {
|
||||
EXPECT_THAT(storage.ReadQueueInteger(subentry.subentry),
|
||||
ElementsAre(TSEq<TimestampedInteger>(1, 50)));
|
||||
}
|
||||
}
|
||||
|
||||
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
|
||||
for (auto&& subentry : subentries) {
|
||||
SCOPED_TRACE(subentry.name);
|
||||
if (subentry.type == NT_BOOLEAN) {
|
||||
EXPECT_THAT(storage.ReadQueueFloat(subentry.subentry), IsEmpty());
|
||||
} else {
|
||||
EXPECT_THAT(storage.ReadQueueFloat(subentry.subentry),
|
||||
ElementsAre(TSEq<TimestampedFloat>(1.0, 50)));
|
||||
}
|
||||
}
|
||||
|
||||
storage.SetEntryValue(pub, Value::MakeDouble(1.0, 50));
|
||||
for (auto&& subentry : subentries) {
|
||||
SCOPED_TRACE(subentry.name);
|
||||
EXPECT_THAT(storage.ReadQueueBoolean(subentry.subentry), IsEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nt
|
||||
|
||||
@@ -121,37 +121,37 @@ void PrintTo(const Value& value, std::ostream* os) {
|
||||
case NT_UNASSIGNED:
|
||||
break;
|
||||
case NT_BOOLEAN:
|
||||
*os << (value.GetBoolean() ? "true" : "false");
|
||||
*os << "boolean, " << (value.GetBoolean() ? "true" : "false");
|
||||
break;
|
||||
case NT_DOUBLE:
|
||||
*os << value.GetDouble();
|
||||
*os << "double, " << value.GetDouble();
|
||||
break;
|
||||
case NT_FLOAT:
|
||||
*os << value.GetFloat();
|
||||
*os << "float, " << value.GetFloat();
|
||||
break;
|
||||
case NT_INTEGER:
|
||||
*os << value.GetInteger();
|
||||
*os << "int, " << value.GetInteger();
|
||||
break;
|
||||
case NT_STRING:
|
||||
*os << '"' << value.GetString() << '"';
|
||||
*os << "string, \"" << value.GetString() << '"';
|
||||
break;
|
||||
case NT_RAW:
|
||||
*os << ::testing::PrintToString(value.GetRaw());
|
||||
*os << "raw, " << ::testing::PrintToString(value.GetRaw());
|
||||
break;
|
||||
case NT_BOOLEAN_ARRAY:
|
||||
*os << ::testing::PrintToString(value.GetBooleanArray());
|
||||
*os << "boolean[], " << ::testing::PrintToString(value.GetBooleanArray());
|
||||
break;
|
||||
case NT_DOUBLE_ARRAY:
|
||||
*os << ::testing::PrintToString(value.GetDoubleArray());
|
||||
*os << "double[], " << ::testing::PrintToString(value.GetDoubleArray());
|
||||
break;
|
||||
case NT_FLOAT_ARRAY:
|
||||
*os << ::testing::PrintToString(value.GetFloatArray());
|
||||
*os << "float[], " << ::testing::PrintToString(value.GetFloatArray());
|
||||
break;
|
||||
case NT_INTEGER_ARRAY:
|
||||
*os << ::testing::PrintToString(value.GetIntegerArray());
|
||||
*os << "int[], " << ::testing::PrintToString(value.GetIntegerArray());
|
||||
break;
|
||||
case NT_STRING_ARRAY:
|
||||
*os << ::testing::PrintToString(value.GetStringArray());
|
||||
*os << "string[], " << ::testing::PrintToString(value.GetStringArray());
|
||||
break;
|
||||
default:
|
||||
*os << "UNKNOWN TYPE " << value.type();
|
||||
|
||||
Reference in New Issue
Block a user