From 42b28400098e275b850a607f3a78e8aeb93badbf Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Mon, 17 Jul 2023 14:39:57 +0200 Subject: [PATCH] Create more memory pools as needed (resolves #1074) --- extras/tests/Helpers/Allocators.hpp | 10 ++ extras/tests/JsonArray/copyArray.cpp | 21 +-- extras/tests/JsonDeserializer/array.cpp | 65 +++------ extras/tests/JsonDeserializer/object.cpp | 49 ++++--- extras/tests/JsonDeserializer/string.cpp | 22 +-- extras/tests/JsonDocument/assignment.cpp | 33 +++-- extras/tests/JsonDocument/constructor.cpp | 30 ++-- extras/tests/JsonDocument/garbageCollect.cpp | 11 +- extras/tests/JsonDocument/overflowed.cpp | 33 ++--- extras/tests/JsonDocument/shrinkToFit.cpp | 84 ++++++----- extras/tests/JsonObject/copy.cpp | 20 +-- extras/tests/JsonSerializer/JsonArray.cpp | 24 ---- .../deserializeVariant.cpp | 46 +++--- extras/tests/ResourceManager/allocVariant.cpp | 15 +- extras/tests/ResourceManager/size.cpp | 18 +-- src/ArduinoJson/Configuration.hpp | 20 +++ src/ArduinoJson/Document/JsonDocument.hpp | 2 +- src/ArduinoJson/Memory/ResourceManager.hpp | 34 +++-- src/ArduinoJson/Memory/StringBuilder.hpp | 1 + src/ArduinoJson/Memory/VariantPool.hpp | 22 +-- src/ArduinoJson/Memory/VariantPoolImpl.hpp | 15 +- src/ArduinoJson/Memory/VariantPoolList.hpp | 133 ++++++++++++++++++ 22 files changed, 396 insertions(+), 312 deletions(-) create mode 100644 src/ArduinoJson/Memory/VariantPoolList.hpp diff --git a/extras/tests/Helpers/Allocators.hpp b/extras/tests/Helpers/Allocators.hpp index f37658f9..813b617e 100644 --- a/extras/tests/Helpers/Allocators.hpp +++ b/extras/tests/Helpers/Allocators.hpp @@ -5,6 +5,7 @@ #pragma once #include +#include #include @@ -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); +} diff --git a/extras/tests/JsonArray/copyArray.cpp b/extras/tests/JsonArray/copyArray.cpp index 2a09158d..0c986843 100644 --- a/extras/tests/JsonArray/copyArray.cpp +++ b/extras/tests/JsonArray/copyArray.cpp @@ -5,6 +5,8 @@ #include #include +#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(); - 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(); - 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") { diff --git a/extras/tests/JsonDeserializer/array.cpp b/extras/tests/JsonDeserializer/array.cpp index dd4ced02..4b897644 100644 --- a/extras/tests/JsonDeserializer/array.cpp +++ b/extras/tests/JsonDeserializer/array.cpp @@ -5,6 +5,8 @@ #include #include +#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() == "[]"); } - 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() == "[]"); } - 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() == "[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(); - 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(); - - REQUIRE(err == DeserializationError::Ok); - REQUIRE(doc.is()); - REQUIRE(doc.memoryUsage() == sizeofArray(2)); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 2); - } } diff --git a/extras/tests/JsonDeserializer/object.cpp b/extras/tests/JsonDeserializer/object.cpp index 9bda9776..cd57d507 100644 --- a/extras/tests/JsonDeserializer/object.cpp +++ b/extras/tests/JsonDeserializer/object.cpp @@ -5,6 +5,8 @@ #include #include +#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() == "{}"); } - 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() == "{}"); } - 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() == "{}"); } - 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() == "{}"); } - 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().size() == 0); - REQUIRE(doc.memoryUsage() == sizeofObject(0)); + REQUIRE(err == DeserializationError::NoMemory); + REQUIRE(doc.as() == "{\"a\":null}"); } } diff --git a/extras/tests/JsonDeserializer/string.cpp b/extras/tests/JsonDeserializer/string.cpp index 489dd591..24c6bfa0 100644 --- a/extras/tests/JsonDeserializer/string.cpp +++ b/extras/tests/JsonDeserializer/string.cpp @@ -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))); } } diff --git a/extras/tests/JsonDocument/assignment.cpp b/extras/tests/JsonDocument/assignment.cpp index 173ecef9..58296509 100644 --- a/extras/tests/JsonDocument/assignment.cpp +++ b/extras/tests/JsonDocument/assignment.cpp @@ -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() == "[{\"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() == "{\"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() == "The size of this string is 32!!"); + REQUIRE(doc2.as() == "{\"hello\":\"world\"}"); REQUIRE(doc1.as() == "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") { diff --git a/extras/tests/JsonDocument/constructor.cpp b/extras/tests/JsonDocument/constructor.cpp index 52febf30..31db4ff4 100644 --- a/extras/tests/JsonDocument/constructor.cpp +++ b/extras/tests/JsonDocument/constructor.cpp @@ -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() == "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() == "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() == "{\"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() == "[\"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() == "hello"); REQUIRE(spyingAllocator.log() == - AllocatorLog() << AllocatorLog::Allocate( - sizeofString(5)) // TODO: remove - << AllocatorLog::Allocate(sizeofString(5))); + AllocatorLog() << AllocatorLog::Allocate(sizeofString(5))); } } diff --git a/extras/tests/JsonDocument/garbageCollect.cpp b/extras/tests/JsonDocument/garbageCollect.cpp index 15e9ee89..449b0b5c 100644 --- a/extras/tests/JsonDocument/garbageCollect.cpp +++ b/extras/tests/JsonDocument/garbageCollect.cpp @@ -30,10 +30,12 @@ TEST_CASE("JsonDocument::garbageCollect()") { REQUIRE(doc.memoryUsage() == sizeofObject(1) + sizeofString(7)); REQUIRE(doc.as() == "{\"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() == "{\"dancing\":2}"); REQUIRE(spyingAllocator.log() == - AllocatorLog() << AllocatorLog::AllocateFail(capacity) - << AllocatorLog::AllocateFail(sizeofString(7))); + AllocatorLog() << AllocatorLog::AllocateFail(sizeofString(7))); } } diff --git a/extras/tests/JsonDocument/overflowed.cpp b/extras/tests/JsonDocument/overflowed.cpp index a02d4ed5..d27dd49f 100644 --- a/extras/tests/JsonDocument/overflowed.cpp +++ b/extras/tests/JsonDocument/overflowed.cpp @@ -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); diff --git a/extras/tests/JsonDocument/shrinkToFit.cpp b/extras/tests/JsonDocument/shrinkToFit.cpp index 5600e3ad..670954ab 100644 --- a/extras/tests/JsonDocument/shrinkToFit.cpp +++ b/extras/tests/JsonDocument/shrinkToFit.cpp @@ -48,9 +48,7 @@ TEST_CASE("JsonDocument::shrinkToFit()") { doc.shrinkToFit(); REQUIRE(doc.as() == "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() == "{}"); - 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() == "[]"); - 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() == "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() == "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() == "[{},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() == "{\"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() == "{\"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() == "[\"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() == "[\"abcdefg\"]"); - REQUIRE(spyingAllocator.log() == - AllocatorLog() << AllocatorLog::Allocate(4096) - << AllocatorLog::Allocate(sizeofString(7)) - << AllocatorLog::Reallocate(4096, sizeofArray(1))); + REQUIRE( + spyingAllocator.log() == + AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) + << AllocatorLog::Allocate(sizeofPool()) + << AllocatorLog::Allocate(sizeofString(7)) + << 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() == "{\"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() == "{\"key\":\"abcdefg\"}"); - REQUIRE(spyingAllocator.log() == - AllocatorLog() << AllocatorLog::Allocate(4096) - << AllocatorLog::Allocate(sizeofString(7)) - << AllocatorLog::Reallocate(4096, sizeofObject(1))); + REQUIRE( + spyingAllocator.log() == + AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) + << AllocatorLog::Allocate(sizeofPool()) + << AllocatorLog::Allocate(sizeofString(7)) + << AllocatorLog::Reallocate(sizeofPool(), sizeofPool(1)) + << AllocatorLog::Reallocate(sizeofPoolList(), + sizeofPoolList(1))); } } diff --git a/extras/tests/JsonObject/copy.cpp b/extras/tests/JsonObject/copy.cpp index 85de6bfc..a62815aa 100644 --- a/extras/tests/JsonObject/copy.cpp +++ b/extras/tests/JsonObject/copy.cpp @@ -5,6 +5,8 @@ #include #include +#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(); - 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() == "{\"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(); - obj1["hello"][1] = "world"; + obj1["hello"][0] = std::string("world"); bool success = obj3.set(obj1); REQUIRE(success == false); - REQUIRE(doc3.as() == "{\"hello\":[]}"); + REQUIRE(doc3.as() == "{\"hello\":[null]}"); } SECTION("destination is null") { diff --git a/extras/tests/JsonSerializer/JsonArray.cpp b/extras/tests/JsonSerializer/JsonArray.cpp index fc18c901..dd1b8963 100644 --- a/extras/tests/JsonSerializer/JsonArray.cpp +++ b/extras/tests/JsonSerializer/JsonArray.cpp @@ -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(); diff --git a/extras/tests/MsgPackDeserializer/deserializeVariant.cpp b/extras/tests/MsgPackDeserializer/deserializeVariant.cpp index 0656f3c1..57787718 100644 --- a/extras/tests/MsgPackDeserializer/deserializeVariant.cpp +++ b/extras/tests/MsgPackDeserializer/deserializeVariant.cpp @@ -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); } diff --git a/extras/tests/ResourceManager/allocVariant.cpp b/extras/tests/ResourceManager/allocVariant.cpp index 34bea43c..80e308ef 100644 --- a/extras/tests/ResourceManager/allocVariant.cpp +++ b/extras/tests/ResourceManager/allocVariant.cpp @@ -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(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(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(); diff --git a/extras/tests/ResourceManager/size.cpp b/extras/tests/ResourceManager/size.cpp index 240cf75a..af83ea06 100644 --- a/extras/tests/ResourceManager/size.cpp +++ b/extras/tests/ResourceManager/size.cpp @@ -6,25 +6,21 @@ #include #include +#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(); diff --git a/src/ArduinoJson/Configuration.hpp b/src/ArduinoJson/Configuration.hpp index 63cc861e..70fbff9e 100644 --- a/src/ArduinoJson/Configuration.hpp +++ b/src/ArduinoJson/Configuration.hpp @@ -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 diff --git a/src/ArduinoJson/Document/JsonDocument.hpp b/src/ArduinoJson/Document/JsonDocument.hpp index cb7e9813..08ee82e1 100644 --- a/src/ArduinoJson/Document/JsonDocument.hpp +++ b/src/ArduinoJson/Document/JsonDocument.hpp @@ -94,7 +94,7 @@ class JsonDocument : public detail::VariantOperators { 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; diff --git a/src/ArduinoJson/Memory/ResourceManager.hpp b/src/ArduinoJson/Memory/ResourceManager.hpp index a2fc4674..cb47374c 100644 --- a/src/ArduinoJson/Memory/ResourceManager.hpp +++ b/src/ArduinoJson/Memory/ResourceManager.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include @@ -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 @@ -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 diff --git a/src/ArduinoJson/Memory/StringBuilder.hpp b/src/ArduinoJson/Memory/StringBuilder.hpp index 497ea8dd..cc2bb33a 100644 --- a/src/ArduinoJson/Memory/StringBuilder.hpp +++ b/src/ArduinoJson/Memory/StringBuilder.hpp @@ -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 { diff --git a/src/ArduinoJson/Memory/VariantPool.hpp b/src/ArduinoJson/Memory/VariantPool.hpp index 5d0334c4..ef40cc87 100644 --- a/src/ArduinoJson/Memory/VariantPool.hpp +++ b/src/ArduinoJson/Memory/VariantPool.hpp @@ -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 diff --git a/src/ArduinoJson/Memory/VariantPoolImpl.hpp b/src/ArduinoJson/Memory/VariantPoolImpl.hpp index 39b0a3e1..8e173878 100644 --- a/src/ArduinoJson/Memory/VariantPoolImpl.hpp +++ b/src/ArduinoJson/Memory/VariantPoolImpl.hpp @@ -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(allocator->allocate(cap)); - if (slots_) { - capacity_ = bytesToSlots(cap); - usage_ = 0; - } +inline void VariantPool::create(SlotCount cap, Allocator* allocator) { + ARDUINOJSON_ASSERT(cap > 0); + slots_ = + reinterpret_cast(allocator->allocate(slotsToBytes(cap))); + capacity_ = slots_ ? cap : 0; + usage_ = 0; } inline void VariantPool::destroy(Allocator* allocator) { diff --git a/src/ArduinoJson/Memory/VariantPoolList.hpp b/src/ArduinoJson/Memory/VariantPoolList.hpp new file mode 100644 index 00000000..7a6000ce --- /dev/null +++ b/src/ArduinoJson/Memory/VariantPoolList.hpp @@ -0,0 +1,133 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2023, Benoit BLANCHON +// MIT License + +#pragma once + +#include +#include + +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( + 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(newPools); + capacity_ = newCapacity; + return true; + } + + VariantPool* pools_ = nullptr; + size_t count_ = 0; + size_t capacity_ = 0; +}; + +ARDUINOJSON_END_PRIVATE_NAMESPACE