Create more memory pools as needed (resolves #1074)

This commit is contained in:
Benoit Blanchon
2023-07-17 14:39:57 +02:00
parent 65c67d317a
commit 42b2840009
22 changed files with 396 additions and 312 deletions

View File

@ -5,6 +5,7 @@
#pragma once
#include <ArduinoJson/Memory/Allocator.hpp>
#include <ArduinoJson/Memory/VariantPool.hpp>
#include <sstream>
@ -218,3 +219,12 @@ class TimebombAllocator : public ArduinoJson::Allocator {
size_t countdown_ = 0;
Allocator* upstream_;
};
inline size_t sizeofPoolList(size_t n = ARDUINOJSON_INITIAL_POOL_COUNT) {
return sizeof(ArduinoJson::detail::VariantPool) * n;
}
inline size_t sizeofPool(
ArduinoJson::detail::SlotCount n = ARDUINOJSON_POOL_CAPACITY) {
return ArduinoJson::detail::VariantPool::slotsToBytes(n);
}

View File

@ -5,6 +5,8 @@
#include <ArduinoJson.h>
#include <catch.hpp>
#include "Allocators.hpp"
using ArduinoJson::detail::sizeofArray;
TEST_CASE("copyArray()") {
@ -109,17 +111,12 @@ TEST_CASE("copyArray()") {
}
SECTION("int[] -> JsonArray, but not enough memory") {
const size_t SIZE = sizeofArray(2);
JsonDocument doc(SIZE);
JsonDocument doc(0, FailingAllocator::instance());
JsonArray array = doc.to<JsonArray>();
char json[32];
int source[] = {1, 2, 3};
bool ok = copyArray(source, array);
REQUIRE_FALSE(ok);
serializeJson(array, json);
CHECK(std::string("[1,2]") == json);
}
SECTION("int[][] -> JsonArray") {
@ -160,20 +157,12 @@ TEST_CASE("copyArray()") {
}
SECTION("int[][] -> JsonArray, but not enough memory") {
const size_t SIZE = sizeofArray(2) + sizeofArray(3) + sizeofArray(2);
JsonDocument doc(SIZE);
JsonDocument doc(0, FailingAllocator::instance());
JsonArray array = doc.to<JsonArray>();
char json[32] = "";
int source[][3] = {{1, 2, 3}, {4, 5, 6}};
CAPTURE(SIZE);
bool ok = copyArray(source, array);
CAPTURE(doc.memoryUsage());
CHECK_FALSE(ok);
serializeJson(array, json);
CHECK(std::string("[[1,2,3],[4,5]]") == json);
REQUIRE(ok == false);
}
SECTION("JsonArray -> int[], with more space than needed") {

View File

@ -5,6 +5,8 @@
#include <ArduinoJson.h>
#include <catch.hpp>
#include "Allocators.hpp"
using ArduinoJson::detail::sizeofArray;
using ArduinoJson::detail::sizeofObject;
using ArduinoJson::detail::sizeofString;
@ -257,8 +259,11 @@ TEST_CASE("deserialize JSON array") {
}
TEST_CASE("deserialize JSON array under memory constraints") {
SECTION("buffer of the right size for an empty array") {
JsonDocument doc(sizeofArray(0));
TimebombAllocator allocator(100);
JsonDocument doc(0, &allocator);
SECTION("empty array requires no allocation") {
allocator.setCountdown(0);
char input[] = "[]";
DeserializationError err = deserializeJson(doc, input);
@ -266,73 +271,39 @@ TEST_CASE("deserialize JSON array under memory constraints") {
REQUIRE(err == DeserializationError::Ok);
}
SECTION("buffer too small for an array with one element") {
JsonDocument doc(sizeofArray(0));
SECTION("allocation of pool list fails") {
allocator.setCountdown(0);
char input[] = "[1]";
DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "[]");
}
SECTION("buffer of the right size for an array with one element") {
JsonDocument doc(sizeofArray(1));
SECTION("allocation of pool fails") {
allocator.setCountdown(1);
char input[] = "[1]";
DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::Ok);
REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "[]");
}
SECTION("buffer too small for an array with a nested object") {
JsonDocument doc(sizeofArray(0) + sizeofObject(0));
char input[] = "[{}]";
SECTION("allocation of string fails in array") {
allocator.setCountdown(2);
char input[] = "[0,\"hi!\"]";
DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::NoMemory);
}
SECTION("buffer of the right size for an array with a nested object") {
JsonDocument doc(sizeofArray(1) + sizeofObject(0));
char input[] = "[{}]";
DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.as<std::string>() == "[0,null]");
}
SECTION("don't store space characters") {
JsonDocument doc(100);
deserializeJson(doc, " [ \"1234567\" ] ");
REQUIRE(sizeofArray(1) + sizeofString(7) == doc.memoryUsage());
}
SECTION("Should clear the JsonArray") {
JsonDocument doc(sizeofArray(4));
char input[] = "[1,2,3,4]";
deserializeJson(doc, input);
deserializeJson(doc, "[]");
JsonArray arr = doc.as<JsonArray>();
REQUIRE(arr.size() == 0);
REQUIRE(doc.memoryUsage() == sizeofArray(0));
}
SECTION("buffer of the right size for an array with two element") {
JsonDocument doc(sizeofArray(2));
char input[] = "[1,2]";
DeserializationError err = deserializeJson(doc, input);
JsonArray arr = doc.as<JsonArray>();
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.is<JsonArray>());
REQUIRE(doc.memoryUsage() == sizeofArray(2));
REQUIRE(arr[0] == 1);
REQUIRE(arr[1] == 2);
}
}

View File

@ -5,6 +5,8 @@
#include <ArduinoJson.h>
#include <catch.hpp>
#include "Allocators.hpp"
using ArduinoJson::detail::sizeofArray;
using ArduinoJson::detail::sizeofObject;
using ArduinoJson::detail::sizeofString;
@ -320,59 +322,56 @@ TEST_CASE("deserialize JSON object") {
}
TEST_CASE("deserialize JSON object under memory constraints") {
SECTION("buffer for the right size for an empty object") {
JsonDocument doc(sizeofObject(0));
TimebombAllocator allocator(1024);
JsonDocument doc(0, &allocator);
SECTION("empty object requires no allocation") {
allocator.setCountdown(0);
char input[] = "{}";
DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.as<std::string>() == "{}");
}
SECTION("buffer too small for an empty object") {
JsonDocument doc(sizeofObject(0));
SECTION("key allocation fails") {
allocator.setCountdown(0);
char input[] = "{\"a\":1}";
DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "{}");
}
SECTION("buffer of the right size for an object with one member") {
JsonDocument doc(sizeofObject(1));
SECTION("pool list allocation fails") {
allocator.setCountdown(2);
char input[] = "{\"a\":1}";
DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::Ok);
REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "{}");
}
SECTION("buffer too small for an object with a nested array") {
JsonDocument doc(sizeofObject(0) + sizeofArray(0));
char input[] = "{\"a\":[]}";
SECTION("pool allocation fails") {
allocator.setCountdown(3);
char input[] = "{\"a\":1}";
DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "{}");
}
SECTION("buffer of the right size for an object with a nested array") {
JsonDocument doc(sizeofObject(1) + sizeofArray(0));
char input[] = "{\"a\":[]}";
SECTION("string allocation fails") {
allocator.setCountdown(4);
char input[] = "{\"a\":\"b\"}";
DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::Ok);
}
SECTION("Should clear the JsonObject") {
JsonDocument doc(sizeofObject(1));
char input[] = "{\"hello\":\"world\"}";
deserializeJson(doc, input);
deserializeJson(doc, "{}");
REQUIRE(doc.as<JsonObject>().size() == 0);
REQUIRE(doc.memoryUsage() == sizeofObject(0));
REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "{\"a\":null}");
}
}

View File

@ -99,7 +99,7 @@ TEST_CASE("Invalid JSON string") {
}
TEST_CASE("Allocation of the key fails") {
TimebombAllocator timebombAllocator(1);
TimebombAllocator timebombAllocator(0);
SpyingAllocator spyingAllocator(&timebombAllocator);
JsonDocument doc(1024, &spyingAllocator);
@ -107,19 +107,19 @@ TEST_CASE("Allocation of the key fails") {
REQUIRE(deserializeJson(doc, "{\"example\":1}") ==
DeserializationError::NoMemory);
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(1024)
<< AllocatorLog::AllocateFail(sizeofString(31)));
AllocatorLog() << AllocatorLog::AllocateFail(sizeofString(31)));
}
SECTION("Quoted string, second member") {
timebombAllocator.setCountdown(2);
timebombAllocator.setCountdown(4);
REQUIRE(deserializeJson(doc, "{\"hello\":1,\"world\"}") ==
DeserializationError::NoMemory);
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(1024)
<< AllocatorLog::Allocate(sizeofString(31))
AllocatorLog() << AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Reallocate(sizeofString(31),
sizeofString(5))
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::AllocateFail(sizeofString(31)));
}
@ -127,19 +127,19 @@ TEST_CASE("Allocation of the key fails") {
REQUIRE(deserializeJson(doc, "{example:1}") ==
DeserializationError::NoMemory);
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(1024)
<< AllocatorLog::AllocateFail(sizeofString(31)));
AllocatorLog() << AllocatorLog::AllocateFail(sizeofString(31)));
}
SECTION("Non-Quoted string, second member") {
timebombAllocator.setCountdown(2);
timebombAllocator.setCountdown(4);
REQUIRE(deserializeJson(doc, "{hello:1,world}") ==
DeserializationError::NoMemory);
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(1024)
<< AllocatorLog::Allocate(sizeofString(31))
AllocatorLog() << AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Reallocate(sizeofString(31),
sizeofString(5))
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::AllocateFail(sizeofString(31)));
}
}

View File

@ -26,6 +26,8 @@ TEST_CASE("JsonDocument assignment") {
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(5)) // world
);
}
@ -41,8 +43,8 @@ TEST_CASE("JsonDocument assignment") {
REQUIRE(doc2.as<std::string>() == "[{\"hello\":\"world\"}]");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Deallocate(sizeofArray(1))
<< AllocatorLog::Allocate(capacity)
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(5)) // hello
<< AllocatorLog::Allocate(sizeofString(5)) // world
);
@ -59,9 +61,9 @@ TEST_CASE("JsonDocument assignment") {
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Deallocate(4096)
<< AllocatorLog::Allocate(capacity1)
<< AllocatorLog::Allocate(sizeofString(5)) // hello
AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(5)) // world
);
}
@ -69,21 +71,24 @@ TEST_CASE("JsonDocument assignment") {
SECTION("Move assign") {
{
JsonDocument doc1(4096, &spyingAllocator);
doc1.set(std::string("The size of this string is 32!!"));
doc1[std::string("hello")] = std::string("world");
JsonDocument doc2(128, &spyingAllocator);
doc2 = std::move(doc1);
REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
REQUIRE(doc1.as<std::string>() == "null");
}
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Allocate(128)
<< AllocatorLog::Deallocate(128)
<< AllocatorLog::Deallocate(sizeofString(31))
<< AllocatorLog::Deallocate(4096));
REQUIRE(
spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(5)) // world
<< AllocatorLog::Deallocate(sizeofString(5)) // hello
<< AllocatorLog::Deallocate(sizeofString(5)) // world
<< AllocatorLog::Deallocate(sizeofPool())
<< AllocatorLog::Deallocate(sizeofPoolList()));
}
SECTION("Assign from JsonObject") {

View File

@ -16,9 +16,7 @@ TEST_CASE("JsonDocument constructor") {
SECTION("JsonDocument(size_t)") {
{ JsonDocument doc(4096, &spyingAllocator); }
REQUIRE(spyingAllocator.log() == AllocatorLog()
<< AllocatorLog::Allocate(4096)
<< AllocatorLog::Deallocate(4096));
REQUIRE(spyingAllocator.log() == AllocatorLog());
}
SECTION("JsonDocument(const JsonDocument&)") {
@ -33,14 +31,10 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
}
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(capacity)
<< AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Allocate(capacity)
AllocatorLog() << AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Deallocate(sizeofString(31))
<< AllocatorLog::Deallocate(capacity)
<< AllocatorLog::Deallocate(sizeofString(31))
<< AllocatorLog::Deallocate(capacity));
<< AllocatorLog::Deallocate(sizeofString(31)));
}
SECTION("JsonDocument(JsonDocument&&)") {
@ -54,10 +48,8 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc1.as<std::string>() == "null");
}
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Deallocate(sizeofString(31))
<< AllocatorLog::Deallocate(4096));
AllocatorLog() << AllocatorLog::Allocate(sizeofString(31))
<< AllocatorLog::Deallocate(sizeofString(31)));
}
SECTION("JsonDocument(JsonObject)") {
@ -69,7 +61,8 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofObject(1)));
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()));
}
SECTION("Construct from JsonArray") {
@ -80,8 +73,9 @@ TEST_CASE("JsonDocument constructor") {
JsonDocument doc2(arr, &spyingAllocator);
REQUIRE(doc2.as<std::string>() == "[\"hello\"]");
REQUIRE(spyingAllocator.log() == AllocatorLog() << AllocatorLog::Allocate(
addPadding(doc1.memoryUsage())));
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool()));
}
SECTION("Construct from JsonVariant") {
@ -92,8 +86,6 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc2.as<std::string>() == "hello");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(
sizeofString(5)) // TODO: remove
<< AllocatorLog::Allocate(sizeofString(5)));
AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)));
}
}

View File

@ -30,10 +30,12 @@ TEST_CASE("JsonDocument::garbageCollect()") {
REQUIRE(doc.memoryUsage() == sizeofObject(1) + sizeofString(7));
REQUIRE(doc.as<std::string>() == "{\"dancing\":2}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(capacity)
<< AllocatorLog::Allocate(sizeofString(7))
AllocatorLog() << AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Deallocate(sizeofString(7))
<< AllocatorLog::Deallocate(capacity));
<< AllocatorLog::Deallocate(sizeofPool())
<< AllocatorLog::Deallocate(sizeofPoolList()));
}
SECTION("when allocation fails") {
@ -50,7 +52,6 @@ TEST_CASE("JsonDocument::garbageCollect()") {
REQUIRE(doc.as<std::string>() == "{\"dancing\":2}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::AllocateFail(capacity)
<< AllocatorLog::AllocateFail(sizeofString(7)));
AllocatorLog() << AllocatorLog::AllocateFail(sizeofString(7)));
}
}

View File

@ -10,80 +10,81 @@
using ArduinoJson::detail::sizeofArray;
TEST_CASE("JsonDocument::overflowed()") {
TimebombAllocator allocator(10);
JsonDocument doc(0, &allocator);
SECTION("returns false on a fresh object") {
JsonDocument doc(0);
allocator.setCountdown(0);
CHECK(doc.overflowed() == false);
}
SECTION("returns true after a failed insertion") {
JsonDocument doc(0);
allocator.setCountdown(0);
doc.add(0);
CHECK(doc.overflowed() == true);
}
SECTION("returns false after successful insertion") {
JsonDocument doc(sizeofArray(1));
allocator.setCountdown(2);
doc.add(0);
CHECK(doc.overflowed() == false);
}
SECTION("returns true after a failed string copy") {
ControllableAllocator allocator;
JsonDocument doc(sizeofArray(1), &allocator);
allocator.disable();
allocator.setCountdown(0);
doc.add(std::string("example"));
CHECK(doc.overflowed() == true);
}
SECTION("returns false after a successful string copy") {
JsonDocument doc(sizeofArray(1));
allocator.setCountdown(3);
doc.add(std::string("example"));
CHECK(doc.overflowed() == false);
}
SECTION("returns true after a failed member add") {
JsonDocument doc(1);
allocator.setCountdown(0);
doc["example"] = true;
CHECK(doc.overflowed() == true);
}
SECTION("returns true after a failed deserialization") {
JsonDocument doc(sizeofArray(1));
allocator.setCountdown(1);
deserializeJson(doc, "[1, 2]");
CHECK(doc.overflowed() == true);
}
SECTION("returns false after a successful deserialization") {
JsonDocument doc(sizeofArray(1));
allocator.setCountdown(4);
deserializeJson(doc, "[\"example\"]");
CHECK(doc.overflowed() == false);
}
SECTION("returns false after clear()") {
JsonDocument doc(0);
allocator.setCountdown(0);
doc.add(0);
doc.clear();
CHECK(doc.overflowed() == false);
}
SECTION("remains false after shrinkToFit()") {
JsonDocument doc(sizeofArray(1));
allocator.setCountdown(2);
doc.add(0);
allocator.setCountdown(2);
doc.shrinkToFit();
CHECK(doc.overflowed() == false);
}
SECTION("remains true after shrinkToFit()") {
JsonDocument doc(sizeofArray(1));
doc.add(0);
allocator.setCountdown(0);
doc.add(0);
allocator.setCountdown(2);
doc.shrinkToFit();
CHECK(doc.overflowed() == true);
}
SECTION("return false after garbageCollect()") {
JsonDocument doc(sizeofArray(1));
doc.add(0);
allocator.setCountdown(0);
doc.add(0);
doc.garbageCollect();
CHECK(doc.overflowed() == false);

View File

@ -48,9 +48,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "null");
REQUIRE(spyingAllocator.log() == AllocatorLog()
<< AllocatorLog::Allocate(4096)
<< AllocatorLog::Reallocate(4096, 0));
REQUIRE(spyingAllocator.log() == AllocatorLog());
}
SECTION("empty object") {
@ -59,9 +57,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Reallocate(4096, sizeofObject(0)));
REQUIRE(spyingAllocator.log() == AllocatorLog());
}
SECTION("empty array") {
@ -70,9 +66,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "[]");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Reallocate(4096, sizeofArray(0)));
REQUIRE(spyingAllocator.log() == AllocatorLog());
}
SECTION("linked string") {
@ -81,9 +75,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "hello");
REQUIRE(spyingAllocator.log() == AllocatorLog()
<< AllocatorLog::Allocate(4096)
<< AllocatorLog::Reallocate(4096, 0));
REQUIRE(spyingAllocator.log() == AllocatorLog());
}
SECTION("owned string") {
@ -94,9 +86,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(doc.as<std::string>() == "abcdefg");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Reallocate(4096, 0));
AllocatorLog() << AllocatorLog::Allocate(sizeofString(7)));
}
SECTION("raw string") {
@ -106,9 +96,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(doc.as<std::string>() == "[{},12]");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Reallocate(4096, 0));
AllocatorLog() << AllocatorLog::Allocate(sizeofString(7)));
}
SECTION("linked key") {
@ -118,8 +106,12 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(doc.as<std::string>() == "{\"key\":42}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Reallocate(4096, sizeofObject(1)));
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Reallocate(sizeofPool(),
sizeofObject(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
}
SECTION("owned key") {
@ -129,9 +121,13 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(doc.as<std::string>() == "{\"abcdefg\":42}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Reallocate(4096, sizeofObject(1)));
AllocatorLog() << AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Reallocate(sizeofPool(),
sizeofObject(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
}
SECTION("linked string in array") {
@ -140,9 +136,13 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "[\"hello\"]");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Reallocate(4096, sizeofArray(1)));
REQUIRE(
spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Reallocate(sizeofPool(), sizeofArray(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
}
SECTION("owned string in array") {
@ -151,10 +151,14 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "[\"abcdefg\"]");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
REQUIRE(
spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Reallocate(4096, sizeofArray(1)));
<< AllocatorLog::Reallocate(sizeofPool(), sizeofArray(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
}
SECTION("linked string in object") {
@ -164,8 +168,12 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(doc.as<std::string>() == "{\"key\":\"hello\"}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
<< AllocatorLog::Reallocate(4096, sizeofObject(1)));
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Reallocate(sizeofPool(),
sizeofObject(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
}
SECTION("owned string in object") {
@ -174,9 +182,13 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{\"key\":\"abcdefg\"}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(4096)
REQUIRE(
spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList())
<< AllocatorLog::Allocate(sizeofPool())
<< AllocatorLog::Allocate(sizeofString(7))
<< AllocatorLog::Reallocate(4096, sizeofObject(1)));
<< AllocatorLog::Reallocate(sizeofPool(), sizeofPool(1))
<< AllocatorLog::Reallocate(sizeofPoolList(),
sizeofPoolList(1)));
}
}

View File

@ -5,6 +5,8 @@
#include <ArduinoJson.h>
#include <catch.hpp>
#include "Allocators.hpp"
using ArduinoJson::detail::sizeofObject;
TEST_CASE("JsonObject::set()") {
@ -73,12 +75,13 @@ TEST_CASE("JsonObject::set()") {
REQUIRE(obj2["hello"] == std::string("world"));
}
SECTION("destination too small to store the key") {
JsonDocument doc3(sizeofObject(1));
SECTION("copy fails in the middle of an object") {
TimebombAllocator allocator(3);
JsonDocument doc3(0, &allocator);
JsonObject obj3 = doc3.to<JsonObject>();
obj1["a"] = 1;
obj1["b"] = 2;
obj1[std::string("a")] = 1;
obj1[std::string("b")] = 2;
bool success = obj3.set(obj1);
@ -86,16 +89,17 @@ TEST_CASE("JsonObject::set()") {
REQUIRE(doc3.as<std::string>() == "{\"a\":1}");
}
SECTION("destination too small to store the value") {
JsonDocument doc3(sizeofObject(1));
SECTION("copy fails in the middle of an array") {
TimebombAllocator allocator(2);
JsonDocument doc3(0, &allocator);
JsonObject obj3 = doc3.to<JsonObject>();
obj1["hello"][1] = "world";
obj1["hello"][0] = std::string("world");
bool success = obj3.set(obj1);
REQUIRE(success == false);
REQUIRE(doc3.as<std::string>() == "{\"hello\":[]}");
REQUIRE(doc3.as<std::string>() == "{\"hello\":[null]}");
}
SECTION("destination is null") {

View File

@ -43,14 +43,6 @@ TEST_CASE("serializeJson(JsonArray)") {
check(array, "[\"hello\",\"world\"]");
}
SECTION("OneStringOverCapacity") {
array.add("hello");
array.add("world");
array.add("lost");
check(array, "[\"hello\",\"world\"]");
}
SECTION("One double") {
array.add(3.1415927);
check(array, "[3.1415927]");
@ -82,14 +74,6 @@ TEST_CASE("serializeJson(JsonArray)") {
check(array, "[{\"key\":\"value\"}]");
}
SECTION("OneIntegerOverCapacity") {
array.add(1);
array.add(2);
array.add(3);
check(array, "[1,2]");
}
SECTION("OneTrue") {
array.add(true);
@ -109,14 +93,6 @@ TEST_CASE("serializeJson(JsonArray)") {
check(array, "[false,true]");
}
SECTION("OneBooleanOverCapacity") {
array.add(false);
array.add(true);
array.add(false);
check(array, "[false,true]");
}
SECTION("OneEmptyNestedArray") {
array.createNestedArray();

View File

@ -178,33 +178,33 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
}
SECTION("fixarray") {
checkError(sizeofArray(0), 1, "\x90", DeserializationError::Ok); // []
checkError(sizeofArray(0), 0, "\x90", DeserializationError::Ok); // []
checkError(sizeofArray(0), 0, "\x91\x01",
DeserializationError::NoMemory); // [1]
checkError(sizeofArray(0), 1, "\x91\x01",
DeserializationError::NoMemory); // [1]
checkError(sizeofArray(1), 1, "\x91\x01",
checkError(sizeofArray(1), 2, "\x91\x01",
DeserializationError::Ok); // [1]
checkError(sizeofArray(1), 1, "\x92\x01\x02",
DeserializationError::NoMemory); // [1,2]
}
SECTION("array 16") {
checkError(sizeofArray(0), 1, "\xDC\x00\x00", DeserializationError::Ok);
checkError(sizeofArray(0), 0, "\xDC\x00\x00", DeserializationError::Ok);
checkError(sizeofArray(0), 0, "\xDC\x00\x01\x01",
DeserializationError::NoMemory);
checkError(sizeofArray(0), 1, "\xDC\x00\x01\x01",
DeserializationError::NoMemory);
checkError(sizeofArray(1), 1, "\xDC\x00\x01\x01", DeserializationError::Ok);
checkError(sizeofArray(1), 1, "\xDC\x00\x02\x01\x02",
DeserializationError::NoMemory);
checkError(sizeofArray(1), 2, "\xDC\x00\x01\x01", DeserializationError::Ok);
}
SECTION("array 32") {
checkError(sizeofArray(0), 1, "\xDD\x00\x00\x00\x00",
checkError(sizeofArray(0), 0, "\xDD\x00\x00\x00\x00",
DeserializationError::Ok);
checkError(sizeofArray(0), 1, "\xDD\x00\x00\x00\x01\x01",
checkError(sizeofArray(0), 0, "\xDD\x00\x00\x00\x01\x01",
DeserializationError::NoMemory);
checkError(sizeofArray(1), 1, "\xDD\x00\x00\x00\x01\x01",
DeserializationError::Ok);
checkError(sizeofArray(1), 1, "\xDD\x00\x00\x00\x02\x01\x02",
DeserializationError::NoMemory);
checkError(sizeofArray(1), 2, "\xDD\x00\x00\x00\x01\x01",
DeserializationError::Ok);
}
SECTION("fixmap") {
@ -214,13 +214,13 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
SECTION("{H:1}") {
checkError(sizeofObject(0), 0, "\x81\xA1H\x01",
DeserializationError::NoMemory);
checkError(sizeofObject(1) + sizeofString(2), 3, "\x81\xA1H\x01",
checkError(sizeofObject(1) + sizeofString(2), 4, "\x81\xA1H\x01",
DeserializationError::Ok);
}
SECTION("{H:1,W:2}") {
checkError(sizeofObject(1) + sizeofString(2), 3, "\x82\xA1H\x01\xA1W\x02",
checkError(sizeofObject(1) + sizeofString(2), 4, "\x82\xA1H\x01\xA1W\x02",
DeserializationError::NoMemory);
checkError(sizeofObject(2) + 2 * sizeofString(2), 5,
checkError(sizeofObject(2) + 2 * sizeofString(2), 6,
"\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok);
}
}
@ -230,16 +230,16 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
checkError(sizeofObject(0), 0, "\xDE\x00\x00", DeserializationError::Ok);
}
SECTION("{H:1}") {
checkError(sizeofObject(1) + sizeofString(2), 1, "\xDE\x00\x01\xA1H\x01",
checkError(sizeofObject(1) + sizeofString(2), 2, "\xDE\x00\x01\xA1H\x01",
DeserializationError::NoMemory);
checkError(sizeofObject(1) + sizeofString(2), 3, "\xDE\x00\x01\xA1H\x01",
checkError(sizeofObject(1) + sizeofString(2), 4, "\xDE\x00\x01\xA1H\x01",
DeserializationError::Ok);
}
SECTION("{H:1,W:2}") {
checkError(sizeofObject(1) + sizeofString(2), 3,
checkError(sizeofObject(1) + sizeofString(2), 4,
"\xDE\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::NoMemory);
checkError(sizeofObject(2) + 2 * sizeofObject(1), 5,
checkError(sizeofObject(2) + 2 * sizeofObject(1), 6,
"\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok);
}
}
@ -250,17 +250,17 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
DeserializationError::Ok);
}
SECTION("{H:1}") {
checkError(sizeofObject(1) + sizeofString(2), 1,
checkError(sizeofObject(1) + sizeofString(2), 2,
"\xDF\x00\x00\x00\x01\xA1H\x01",
DeserializationError::NoMemory);
checkError(sizeofObject(1) + sizeofString(2), 3,
checkError(sizeofObject(1) + sizeofString(2), 4,
"\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok);
}
SECTION("{H:1,W:2}") {
checkError(sizeofObject(1) + 2 * sizeofString(2), 3,
checkError(sizeofObject(1) + 2 * sizeofString(2), 4,
"\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::NoMemory);
checkError(sizeofObject(2) + 2 * sizeofObject(1), 5,
checkError(sizeofObject(2) + 2 * sizeofObject(1), 6,
"\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::Ok);
}

View File

@ -30,15 +30,7 @@ TEST_CASE("ResourceManager::allocSlot()") {
REQUIRE(isAligned(resources.allocSlot().operator VariantSlot*()));
}
SECTION("Returns zero if capacity is 0") {
ResourceManager resources(0);
auto variant = resources.allocSlot();
REQUIRE(variant.id() == NULL_SLOT);
REQUIRE(static_cast<VariantSlot*>(variant) == nullptr);
}
SECTION("Returns zero if buffer is null") {
SECTION("Returns null if pool list allocation fails") {
ResourceManager resources(4096, FailingAllocator::instance());
auto variant = resources.allocSlot();
@ -46,8 +38,9 @@ TEST_CASE("ResourceManager::allocSlot()") {
REQUIRE(static_cast<VariantSlot*>(variant) == nullptr);
}
SECTION("Returns zero if capacity is insufficient") {
ResourceManager resources(sizeof(VariantSlot));
SECTION("Returns null if pool allocation fails") {
TimebombAllocator allocator(1);
ResourceManager resources(4096, &allocator);
resources.allocSlot();

View File

@ -6,25 +6,21 @@
#include <ArduinoJson/Memory/VariantPoolImpl.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson::detail;
TEST_CASE("ResourceManager::capacity()") {
const size_t capacity = 4 * sizeof(VariantSlot);
ResourceManager resources(capacity);
REQUIRE(capacity == resources.capacity());
}
TEST_CASE("ResourceManager::size()") {
ResourceManager resources(4096);
TimebombAllocator allocator(0);
ResourceManager resources(4096, &allocator);
SECTION("Initial size is 0") {
REQUIRE(0 == resources.size());
}
SECTION("Doesn't grow when memory pool is full") {
const size_t variantCount = resources.capacity() / sizeof(VariantSlot);
for (size_t i = 0; i < variantCount; i++)
SECTION("Doesn't grow when allocation of second pool fails") {
allocator.setCountdown(2);
for (size_t i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++)
resources.allocSlot();
size_t size = resources.size();

View File

@ -90,6 +90,26 @@
# endif
#endif
// Capacity of each variant pool (in slots)
#ifndef ARDUINOJSON_POOL_CAPACITY
# if defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ <= 2
// Address space == 16-bit
# define ARDUINOJSON_POOL_CAPACITY 16 // 128 bytes
# elif defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ >= 8 || \
defined(_WIN64) && _WIN64
// Address space == 64-bit
# define ARDUINOJSON_POOL_CAPACITY 128 // 3072 bytes
# else
// Address space == 32-bit
# define ARDUINOJSON_POOL_CAPACITY 64 // 1024 bytes
# endif
#endif
// Capacity of each variant pool (in slots)
#ifndef ARDUINOJSON_INITIAL_POOL_COUNT
# define ARDUINOJSON_INITIAL_POOL_COUNT 4
#endif
#ifdef ARDUINO
// Enable support for Arduino's String class

View File

@ -94,7 +94,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
bool garbageCollect() {
// make a temporary clone and move assign
JsonDocument tmp(*this);
if (!tmp.resources_.capacity())
if (tmp.overflowed())
return false;
moveAssignFrom(tmp);
return true;

View File

@ -6,7 +6,7 @@
#include <ArduinoJson/Memory/Allocator.hpp>
#include <ArduinoJson/Memory/StringPool.hpp>
#include <ArduinoJson/Memory/VariantPool.hpp>
#include <ArduinoJson/Memory/VariantPoolList.hpp>
#include <ArduinoJson/Polyfills/assert.hpp>
#include <ArduinoJson/Polyfills/utility.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp>
@ -18,15 +18,13 @@ class VariantPool;
class ResourceManager {
public:
ResourceManager(size_t capa,
ResourceManager(size_t /*capa*/,
Allocator* allocator = DefaultAllocator::instance())
: allocator_(allocator), overflowed_(false) {
variantPool_.create(capa, allocator);
}
: allocator_(allocator), overflowed_(false) {}
~ResourceManager() {
stringPool_.clear(allocator_);
variantPool_.destroy(allocator_);
variantPools_.clear(allocator_);
}
ResourceManager(const ResourceManager&) = delete;
@ -34,9 +32,9 @@ class ResourceManager {
ResourceManager& operator=(ResourceManager&& src) {
stringPool_.clear(allocator_);
variantPool_.destroy(allocator_);
variantPools_.clear(allocator_);
allocator_ = src.allocator_;
variantPool_ = detail::move(src.variantPool_);
variantPools_ = detail::move(src.variantPools_);
overflowed_ = src.overflowed_;
stringPool_ = detail::move(src.stringPool_);
return *this;
@ -48,19 +46,19 @@ class ResourceManager {
void reallocPool(size_t requiredSize) {
size_t capa = VariantPool::bytesToSlots(requiredSize);
if (capa == variantPool_.capacity())
if (capa == variantPools_.capacity())
return;
variantPool_.destroy(allocator_);
variantPool_.create(requiredSize, allocator_);
variantPools_.clear(allocator_);
}
// Gets the capacity of the memoryPool in bytes
size_t capacity() const {
return VariantPool::slotsToBytes(variantPool_.capacity());
return VariantPool::slotsToBytes(variantPools_.capacity());
}
size_t size() const {
return VariantPool::slotsToBytes(variantPool_.usage()) + stringPool_.size();
return VariantPool::slotsToBytes(variantPools_.usage()) +
stringPool_.size();
}
bool overflowed() const {
@ -68,14 +66,14 @@ class ResourceManager {
}
SlotWithId allocSlot() {
auto p = variantPool_.allocSlot();
auto p = variantPools_.allocSlot(allocator_);
if (!p)
overflowed_ = true;
return p;
}
VariantSlot* getSlot(SlotId id) const {
return variantPool_.getSlot(id);
return variantPools_.getSlot(id);
}
template <typename TAdaptedString>
@ -122,20 +120,20 @@ class ResourceManager {
}
void clear() {
variantPool_.clear();
variantPools_.clear(allocator_);
overflowed_ = false;
stringPool_.clear(allocator_);
}
void shrinkToFit() {
variantPool_.shrinkToFit(allocator_);
variantPools_.shrinkToFit(allocator_);
}
private:
Allocator* allocator_;
bool overflowed_;
StringPool stringPool_;
VariantPool variantPool_;
VariantPoolList variantPools_;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -31,6 +31,7 @@ class StringBuilder {
StringNode* node = resources_->getString(adaptString(node_->data, size_));
if (!node) {
node = resources_->resizeString(node_, size_);
ARDUINOJSON_ASSERT(node != nullptr); // realloc to smaller can't fail
resources_->saveString(node);
node_ = nullptr; // next time we need a new string
} else {

View File

@ -42,21 +42,7 @@ class SlotWithId {
class VariantPool {
public:
~VariantPool() {
ARDUINOJSON_ASSERT(slots_ == nullptr);
}
VariantPool& operator=(VariantPool&& src) {
capacity_ = src.capacity_;
src.capacity_ = 0;
usage_ = src.usage_;
src.usage_ = 0;
slots_ = src.slots_;
src.slots_ = nullptr;
return *this;
}
void create(size_t cap, Allocator* allocator);
void create(SlotCount cap, Allocator* allocator);
void destroy(Allocator* allocator);
SlotWithId allocSlot();
@ -70,9 +56,9 @@ class VariantPool {
static size_t slotsToBytes(SlotCount);
private:
SlotCount capacity_ = 0;
SlotCount usage_ = 0;
VariantSlot* slots_ = nullptr;
SlotCount capacity_;
SlotCount usage_;
VariantSlot* slots_;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -9,15 +9,12 @@
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline void VariantPool::create(size_t cap, Allocator* allocator) {
ARDUINOJSON_ASSERT(slots_ == nullptr);
if (!cap)
return;
slots_ = reinterpret_cast<VariantSlot*>(allocator->allocate(cap));
if (slots_) {
capacity_ = bytesToSlots(cap);
inline void VariantPool::create(SlotCount cap, Allocator* allocator) {
ARDUINOJSON_ASSERT(cap > 0);
slots_ =
reinterpret_cast<VariantSlot*>(allocator->allocate(slotsToBytes(cap)));
capacity_ = slots_ ? cap : 0;
usage_ = 0;
}
}
inline void VariantPool::destroy(Allocator* allocator) {

View File

@ -0,0 +1,133 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2023, Benoit BLANCHON
// MIT License
#pragma once
#include <ArduinoJson/Memory/VariantPool.hpp>
#include <ArduinoJson/Polyfills/assert.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class VariantPoolList {
public:
VariantPoolList() = default;
~VariantPoolList() {
ARDUINOJSON_ASSERT(pools_ == nullptr);
}
VariantPoolList& operator=(VariantPoolList&& src) {
ARDUINOJSON_ASSERT(pools_ == nullptr);
pools_ = src.pools_;
count_ = src.count_;
capacity_ = src.capacity_;
src.pools_ = nullptr;
src.count_ = 0;
src.capacity_ = 0;
return *this;
}
SlotWithId allocSlot(Allocator* allocator) {
// try to allocate from last pool (other pools are full)
if (pools_) {
auto slot = allocFromLastPool();
if (slot)
return slot;
}
// create a new pool and try again
auto pool = addPool(allocator);
if (!pool)
return {};
return allocFromLastPool();
}
VariantSlot* getSlot(SlotId id) const {
auto poolIndex = SlotId(id / ARDUINOJSON_POOL_CAPACITY);
auto indexInPool = SlotId(id % ARDUINOJSON_POOL_CAPACITY);
if (poolIndex >= count_)
return nullptr;
return pools_[poolIndex].getSlot(indexInPool);
}
void clear(Allocator* allocator) {
if (!pools_)
return;
for (size_t i = 0; i < count_; i++)
pools_[i].destroy(allocator);
allocator->deallocate(pools_);
pools_ = nullptr;
count_ = 0;
capacity_ = 0;
}
SlotCount capacity() const {
SlotCount total = 0;
for (size_t i = 0; i < count_; i++)
total = SlotCount(total + pools_[i].capacity());
return total;
}
SlotCount usage() const {
SlotCount total = 0;
for (size_t i = 0; i < count_; i++)
total = SlotCount(total + pools_[i].usage());
return total;
}
void shrinkToFit(Allocator* allocator) {
if (pools_)
pools_[count_ - 1].shrinkToFit(allocator);
if (count_ != capacity_) {
pools_ = static_cast<VariantPool*>(
allocator->reallocate(pools_, count_ * sizeof(VariantPool)));
ARDUINOJSON_ASSERT(pools_ != nullptr); // realloc to smaller can't fail
capacity_ = count_;
}
}
private:
SlotWithId allocFromLastPool() {
ARDUINOJSON_ASSERT(pools_ != nullptr);
auto poolIndex = SlotId(count_ - 1);
auto lastPool = count_ ? &pools_[poolIndex] : nullptr;
auto slot = lastPool->allocSlot();
if (!slot)
return {};
return {slot, SlotId(poolIndex * ARDUINOJSON_POOL_CAPACITY + slot.id())};
}
VariantPool* addPool(Allocator* allocator) {
if (count_ == capacity_ && !increaseCapacity(allocator))
return nullptr;
auto pool = &pools_[count_++];
pool->create(ARDUINOJSON_POOL_CAPACITY, allocator);
return pool;
}
bool increaseCapacity(Allocator* allocator) {
void* newPools;
size_t newCapacity;
if (pools_) {
newCapacity = capacity_ * 2;
newPools =
allocator->reallocate(pools_, newCapacity * sizeof(VariantPool));
} else {
newCapacity = ARDUINOJSON_INITIAL_POOL_COUNT;
newPools = allocator->allocate(newCapacity * sizeof(VariantPool));
}
if (!newPools)
return false;
pools_ = static_cast<VariantPool*>(newPools);
capacity_ = newCapacity;
return true;
}
VariantPool* pools_ = nullptr;
size_t count_ = 0;
size_t capacity_ = 0;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE