Compare commits

...

10 Commits

Author SHA1 Message Date
53f858f55c Remove SlotData 2025-06-02 15:57:34 +02:00
6cdd59034d Clang-format 2025-05-31 18:35:36 +02:00
6ccac18fd4 Fix comments 2025-05-31 18:34:07 +02:00
3f6fff7d0c Fix JsonVariantTests 2025-05-31 16:29:46 +00:00
cc2fea45cd All tests pass 2025-05-31 16:47:03 +02:00
4159af66eb JsonDeserializerTests pass 2025-05-31 16:41:09 +02:00
05942b620a JsonVariantTests pass 2025-05-31 16:38:36 +02:00
23c73b061e JsonArrayTests pass 2025-05-31 16:37:08 +02:00
c2e756d942 ResourceManagerTests pass 2025-05-31 16:31:31 +02:00
94aacf873e Replace extension with eight-byte values 2025-05-31 16:20:08 +02:00
17 changed files with 126 additions and 114 deletions

View File

@ -12,7 +12,7 @@ static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
static_assert(ARDUINOJSON_USE_DOUBLE == 0, "ARDUINOJSON_USE_DOUBLE");
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 6, "slot size");
static_assert(sizeof(ArduinoJson::detail::VariantData) == 6, "slot size");
void setup() {}
void loop() {}

View File

@ -10,7 +10,7 @@ static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE");
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 8, "slot size");
static_assert(sizeof(VariantData) == 8, "slot size");
void setup() {}
void loop() {}

View File

@ -10,7 +10,6 @@ static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE");
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 16,
"slot size");
static_assert(sizeof(ArduinoJson::detail::VariantData) == 16, "slot size");
int main() {}

View File

@ -10,6 +10,6 @@ static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE");
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 8, "slot size");
static_assert(sizeof(ArduinoJson::detail::VariantData) == 8, "slot size");
int main() {}

View File

@ -269,10 +269,10 @@ inline size_t sizeofPoolList(size_t n = ARDUINOJSON_INITIAL_POOL_COUNT) {
return sizeof(MemoryPool<VariantData>) * n;
}
template <typename T = ArduinoJson::detail::VariantData>
inline size_t sizeofPool(
ArduinoJson::detail::SlotCount n = ARDUINOJSON_POOL_CAPACITY) {
using namespace ArduinoJson::detail;
return MemoryPool<VariantData>::slotsToBytes(n);
return ArduinoJson::detail::MemoryPool<T>::slotsToBytes(n);
}
inline size_t sizeofStaticStringPool(

View File

@ -8,7 +8,7 @@
#include "Allocators.hpp"
#include "Literals.hpp"
using ArduinoJson::detail::sizeofArray;
using namespace ArduinoJson::detail;
TEST_CASE("JsonArray::add(T)") {
SpyingAllocator spy;
@ -33,7 +33,8 @@ TEST_CASE("JsonArray::add(T)") {
REQUIRE(array[0].is<double>());
REQUIRE_FALSE(array[0].is<bool>());
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofPool<VariantData>()),
Allocate(sizeofPool<EightByteValue>()),
});
}

View File

@ -7,7 +7,7 @@
#include "Allocators.hpp"
using ArduinoJson::detail::sizeofArray;
using namespace ArduinoJson::detail;
TEST_CASE("deserialize JSON array") {
SpyingAllocator spy;
@ -92,8 +92,12 @@ TEST_CASE("deserialize JSON array") {
REQUIRE(arr[0].as<double>() == Approx(4.2123456));
REQUIRE(arr[1] == -7E89);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Reallocate(sizeofPool(), sizeofPool(4)),
Allocate(sizeofPool<VariantData>()),
Allocate(sizeofPool<EightByteValue>()),
Reallocate(sizeofPool<VariantData>(),
sizeofPool<VariantData>(2)),
Reallocate(sizeofPool<EightByteValue>(),
sizeofPool<EightByteValue>(2)),
});
}

View File

@ -121,7 +121,7 @@ TEST_CASE("deserializeJson() returns NoMemory if string length overflows") {
}
}
TEST_CASE("deserializeJson() returns NoMemory if extension allocation fails") {
TEST_CASE("deserializeJson() returns NoMemory if 8-bit slot allocation fails") {
JsonDocument doc(FailingAllocator::instance());
SECTION("uint32_t should pass") {

View File

@ -8,7 +8,7 @@
#include "Allocators.hpp"
#include "Literals.hpp"
using ArduinoJson::detail::sizeofObject;
using namespace ArduinoJson::detail;
enum ErrorCode { ERROR_01 = 1, ERROR_10 = 10 };
@ -197,11 +197,11 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true);
REQUIRE(variant.is<double>() == true);
REQUIRE(variant.as<double>() == 1.2);
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot
});
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool<EightByteValue>()),
Reallocate(sizeofPool<EightByteValue>(),
sizeofPool<EightByteValue>(1)),
});
}
SECTION("int32_t") {
@ -220,11 +220,11 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true);
REQUIRE(variant.is<int64_t>() == true);
REQUIRE(variant.as<int64_t>() == -2147483649LL);
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot
});
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool<EightByteValue>()),
Reallocate(sizeofPool<EightByteValue>(),
sizeofPool<EightByteValue>(1)),
});
}
SECTION("uint32_t") {
@ -243,11 +243,11 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true);
REQUIRE(variant.is<uint64_t>() == true);
REQUIRE(variant.as<uint64_t>() == 4294967296);
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot
});
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool<EightByteValue>()),
Reallocate(sizeofPool<EightByteValue>(),
sizeofPool<EightByteValue>(1)),
});
}
SECTION("JsonDocument") {
@ -378,7 +378,7 @@ TEST_CASE("JsonVariant::set() releases the previous value") {
}
SECTION("float") {
v.set(1.2);
v.set(1.2f);
REQUIRE(spy.log() == AllocatorLog{
Deallocate(sizeofString("world")),
});
@ -393,7 +393,7 @@ TEST_CASE("JsonVariant::set() releases the previous value") {
}
}
TEST_CASE("JsonVariant::set() reuses extension slot") {
TEST_CASE("JsonVariant::set() reuses 8-bit slot") {
SpyingAllocator spy;
JsonDocument doc(&spy);
JsonVariant variant = doc.to<JsonVariant>();

View File

@ -73,7 +73,7 @@ inline bool ArrayData::addValue(const T& value, ResourceManager* resources) {
// Returns the size (in bytes) of an array with n elements.
constexpr size_t sizeofArray(size_t n) {
return n * ResourceManager::slotSize;
return n * sizeof(VariantData);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -274,9 +274,9 @@
#endif
#if ARDUINOJSON_USE_LONG_LONG || ARDUINOJSON_USE_DOUBLE
# define ARDUINOJSON_USE_EXTENSIONS 1
# define ARDUINOJSON_USE_8_BYTE_POOL 1
#else
# define ARDUINOJSON_USE_EXTENSIONS 0
# define ARDUINOJSON_USE_8_BYTE_POOL 0
#endif
#if defined(nullptr)

View File

@ -18,16 +18,7 @@ class VariantData;
class VariantWithId;
class ResourceManager {
union SlotData {
VariantData variant;
#if ARDUINOJSON_USE_EXTENSIONS
VariantExtension extension;
#endif
};
public:
constexpr static size_t slotSize = sizeof(SlotData);
ResourceManager(Allocator* allocator = DefaultAllocator::instance())
: allocator_(allocator), overflowed_(false) {}
@ -35,6 +26,9 @@ class ResourceManager {
stringPool_.clear(allocator_);
variantPools_.clear(allocator_);
staticStringsPools_.clear(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.clear(allocator_);
#endif
}
ResourceManager(const ResourceManager&) = delete;
@ -44,6 +38,9 @@ class ResourceManager {
swap(a.stringPool_, b.stringPool_);
swap(a.variantPools_, b.variantPools_);
swap(a.staticStringsPools_, b.staticStringsPools_);
#if ARDUINOJSON_USE_8_BYTE_POOL
swap(a.eightBytePools_, b.eightBytePools_);
#endif
swap_(a.allocator_, b.allocator_);
swap_(a.overflowed_, b.overflowed_);
}
@ -64,10 +61,10 @@ class ResourceManager {
void freeVariant(Slot<VariantData> slot);
VariantData* getVariant(SlotId id) const;
#if ARDUINOJSON_USE_EXTENSIONS
Slot<VariantExtension> allocExtension();
void freeExtension(SlotId slot);
VariantExtension* getExtension(SlotId id) const;
#if ARDUINOJSON_USE_8_BYTE_POOL
Slot<EightByteValue> allocEightByte();
void freeEightByte(SlotId slot);
EightByteValue* getEightByte(SlotId id) const;
#endif
template <typename TAdaptedString>
@ -136,19 +133,28 @@ class ResourceManager {
variantPools_.clear(allocator_);
stringPool_.clear(allocator_);
staticStringsPools_.clear(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.clear(allocator_);
#endif
}
void shrinkToFit() {
variantPools_.shrinkToFit(allocator_);
staticStringsPools_.shrinkToFit(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.shrinkToFit(allocator_);
#endif
}
private:
Allocator* allocator_;
bool overflowed_;
StringPool stringPool_;
MemoryPoolList<SlotData> variantPools_;
MemoryPoolList<VariantData> variantPools_;
MemoryPoolList<const char*> staticStringsPools_;
#if ARDUINOJSON_USE_8_BYTE_POOL
MemoryPoolList<EightByteValue> eightBytePools_;
#endif
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -12,40 +12,41 @@
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline Slot<VariantData> ResourceManager::allocVariant() {
auto p = variantPools_.allocSlot(allocator_);
if (!p) {
auto slot = variantPools_.allocSlot(allocator_);
if (!slot) {
overflowed_ = true;
return {};
}
return {new (&p->variant) VariantData, p.id()};
new (slot.ptr()) VariantData();
return slot;
}
inline void ResourceManager::freeVariant(Slot<VariantData> variant) {
variant->clear(this);
variantPools_.freeSlot({alias_cast<SlotData*>(variant.ptr()), variant.id()});
inline void ResourceManager::freeVariant(Slot<VariantData> slot) {
slot->clear(this);
variantPools_.freeSlot(slot);
}
inline VariantData* ResourceManager::getVariant(SlotId id) const {
return reinterpret_cast<VariantData*>(variantPools_.getSlot(id));
}
#if ARDUINOJSON_USE_EXTENSIONS
inline Slot<VariantExtension> ResourceManager::allocExtension() {
auto p = variantPools_.allocSlot(allocator_);
if (!p) {
#if ARDUINOJSON_USE_8_BYTE_POOL
inline Slot<EightByteValue> ResourceManager::allocEightByte() {
auto slot = eightBytePools_.allocSlot(allocator_);
if (!slot) {
overflowed_ = true;
return {};
}
return {&p->extension, p.id()};
return slot;
}
inline void ResourceManager::freeExtension(SlotId id) {
auto p = getExtension(id);
variantPools_.freeSlot({reinterpret_cast<SlotData*>(p), id});
inline void ResourceManager::freeEightByte(SlotId id) {
auto p = getEightByte(id);
eightBytePools_.freeSlot({p, id});
}
inline VariantExtension* ResourceManager::getExtension(SlotId id) const {
return &variantPools_.getSlot(id)->extension;
inline EightByteValue* ResourceManager::getEightByte(SlotId id) const {
return eightBytePools_.getSlot(id);
}
#endif

View File

@ -86,7 +86,7 @@ inline VariantData* ObjectData::addPair(VariantData** value,
// Returns the size (in bytes) of an object with n members.
constexpr size_t sizeofObject(size_t n) {
return 2 * n * ResourceManager::slotSize;
return 2 * n * sizeof(VariantData);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -16,9 +16,7 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
enum class VariantTypeBits : uint8_t {
OwnedStringBit = 0x01, // 0000 0001
NumberBit = 0x08, // 0000 1000
#if ARDUINOJSON_USE_EXTENSIONS
ExtensionBit = 0x10, // 0001 0000
#endif
EightByteBit = 0x10, // 0001 0000
CollectionMask = 0x60,
};
@ -64,8 +62,8 @@ union VariantContent {
char asTinyString[tinyStringMaxLength + 1];
};
#if ARDUINOJSON_USE_EXTENSIONS
union VariantExtension {
#if ARDUINOJSON_USE_8_BYTE_POOL
union EightByteValue {
# if ARDUINOJSON_USE_LONG_LONG
uint64_t asUint64;
int64_t asInt64;
@ -74,6 +72,9 @@ union VariantExtension {
double asDouble;
# endif
};
static_assert(sizeof(EightByteValue) == 8,
"sizeof(EightByteValue) must be 8 bytes");
#endif
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -53,8 +53,8 @@ class VariantData {
template <typename TVisitor>
typename TVisitor::result_type accept(
TVisitor& visit, const ResourceManager* resources) const {
#if ARDUINOJSON_USE_EXTENSIONS
auto extension = getExtension(resources);
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte(resources);
#else
(void)resources; // silence warning
#endif
@ -64,7 +64,7 @@ class VariantData {
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return visit.visit(extension->asDouble);
return visit.visit(eightByteValue->asDouble);
#endif
case VariantType::Array:
@ -95,10 +95,10 @@ class VariantData {
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Int64:
return visit.visit(extension->asInt64);
return visit.visit(eightByteValue->asInt64);
case VariantType::Uint64:
return visit.visit(extension->asUint64);
return visit.visit(eightByteValue->asUint64);
#endif
case VariantType::Boolean:
@ -145,8 +145,8 @@ class VariantData {
}
bool asBoolean(const ResourceManager* resources) const {
#if ARDUINOJSON_USE_EXTENSIONS
auto extension = getExtension(resources);
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte(resources);
#else
(void)resources; // silence warning
#endif
@ -160,14 +160,14 @@ class VariantData {
return content_.asFloat != 0;
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return extension->asDouble != 0;
return eightByteValue->asDouble != 0;
#endif
case VariantType::Null:
return false;
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
case VariantType::Int64:
return extension->asUint64 != 0;
return eightByteValue->asUint64 != 0;
#endif
default:
return true;
@ -193,8 +193,8 @@ class VariantData {
template <typename T>
T asFloat(const ResourceManager* resources) const {
static_assert(is_floating_point<T>::value, "T must be a floating point");
#if ARDUINOJSON_USE_EXTENSIONS
auto extension = getExtension(resources);
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte(resources);
#else
(void)resources; // silence warning
#endif
@ -208,9 +208,9 @@ class VariantData {
return static_cast<T>(content_.asInt32);
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return static_cast<T>(extension->asUint64);
return static_cast<T>(eightByteValue->asUint64);
case VariantType::Int64:
return static_cast<T>(extension->asInt64);
return static_cast<T>(eightByteValue->asInt64);
#endif
case VariantType::TinyString:
str = content_.asTinyString;
@ -225,7 +225,7 @@ class VariantData {
return static_cast<T>(content_.asFloat);
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return static_cast<T>(extension->asDouble);
return static_cast<T>(eightByteValue->asDouble);
#endif
default:
return 0.0;
@ -238,8 +238,8 @@ class VariantData {
template <typename T>
T asIntegral(const ResourceManager* resources) const {
static_assert(is_integral<T>::value, "T must be an integral type");
#if ARDUINOJSON_USE_EXTENSIONS
auto extension = getExtension(resources);
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte(resources);
#else
(void)resources; // silence warning
#endif
@ -253,9 +253,9 @@ class VariantData {
return convertNumber<T>(content_.asInt32);
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return convertNumber<T>(extension->asUint64);
return convertNumber<T>(eightByteValue->asUint64);
case VariantType::Int64:
return convertNumber<T>(extension->asInt64);
return convertNumber<T>(eightByteValue->asInt64);
#endif
case VariantType::TinyString:
str = content_.asTinyString;
@ -270,7 +270,7 @@ class VariantData {
return convertNumber<T>(content_.asFloat);
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return convertNumber<T>(extension->asDouble);
return convertNumber<T>(eightByteValue->asDouble);
#endif
default:
return 0;
@ -314,8 +314,8 @@ class VariantData {
}
}
#if ARDUINOJSON_USE_EXTENSIONS
const VariantExtension* getExtension(const ResourceManager* resources) const;
#if ARDUINOJSON_USE_8_BYTE_POOL
const EightByteValue* getEightByte(const ResourceManager* resources) const;
#endif
VariantData* getElement(size_t index,
@ -378,7 +378,7 @@ class VariantData {
template <typename T>
bool isInteger(const ResourceManager* resources) const {
#if ARDUINOJSON_USE_LONG_LONG
auto extension = getExtension(resources);
auto eightByteValue = getEightByte(resources);
#else
(void)resources; // silence warning
#endif
@ -391,10 +391,10 @@ class VariantData {
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return canConvertNumber<T>(extension->asUint64);
return canConvertNumber<T>(eightByteValue->asUint64);
case VariantType::Int64:
return canConvertNumber<T>(extension->asInt64);
return canConvertNumber<T>(eightByteValue->asInt64);
#endif
default:

View File

@ -61,9 +61,9 @@ inline void VariantData::clear(ResourceManager* resources) {
if (type_ & VariantTypeBits::OwnedStringBit)
resources->dereferenceString(content_.asOwnedString->data);
#if ARDUINOJSON_USE_EXTENSIONS
if (type_ & VariantTypeBits::ExtensionBit)
resources->freeExtension(content_.asSlotId);
#if ARDUINOJSON_USE_8_BYTE_POOL
if (type_ & VariantTypeBits::EightByteBit)
resources->freeEightByte(content_.asSlotId);
#endif
auto collection = asCollection();
@ -73,12 +73,12 @@ inline void VariantData::clear(ResourceManager* resources) {
type_ = VariantType::Null;
}
#if ARDUINOJSON_USE_EXTENSIONS
inline const VariantExtension* VariantData::getExtension(
#if ARDUINOJSON_USE_8_BYTE_POOL
inline const EightByteValue* VariantData::getEightByte(
const ResourceManager* resources) const {
return type_ & VariantTypeBits::ExtensionBit
? resources->getExtension(content_.asSlotId)
: nullptr;
return type_ & VariantTypeBits::EightByteBit
? resources->getEightByte(content_.asSlotId)
: 0;
}
#endif
@ -101,12 +101,12 @@ enable_if_t<sizeof(T) == 8, bool> VariantData::setFloat(
type_ = VariantType::Float;
content_.asFloat = valueAsFloat;
} else {
auto extension = resources->allocExtension();
if (!extension)
auto slot = resources->allocEightByte();
if (!slot)
return false;
type_ = VariantType::Double;
content_.asSlotId = extension.id();
extension->asDouble = value;
content_.asSlotId = slot.id();
slot->asDouble = value;
}
#else
type_ = VariantType::Float;
@ -127,12 +127,12 @@ enable_if_t<is_signed<T>::value, bool> VariantData::setInteger(
}
#if ARDUINOJSON_USE_LONG_LONG
else {
auto extension = resources->allocExtension();
if (!extension)
auto slot = resources->allocEightByte();
if (!slot)
return false;
type_ = VariantType::Int64;
content_.asSlotId = extension.id();
extension->asInt64 = value;
content_.asSlotId = slot.id();
slot->asInt64 = value;
}
#endif
return true;
@ -150,12 +150,12 @@ enable_if_t<is_unsigned<T>::value, bool> VariantData::setInteger(
}
#if ARDUINOJSON_USE_LONG_LONG
else {
auto extension = resources->allocExtension();
if (!extension)
auto slot = resources->allocEightByte();
if (!slot)
return false;
type_ = VariantType::Uint64;
content_.asSlotId = extension.id();
extension->asUint64 = value;
content_.asSlotId = slot.id();
slot->asUint64 = value;
}
#endif
return true;