From 1a14499612a17eee01626ffec677e7bde2d6ad48 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Fri, 21 Jul 2023 10:38:35 +0200 Subject: [PATCH] Preallocate pool list --- extras/tests/Helpers/Allocators.hpp | 46 +++++++++---- extras/tests/JsonArray/clear.cpp | 7 +- extras/tests/JsonArray/remove.cpp | 6 +- extras/tests/JsonDeserializer/array.cpp | 4 +- extras/tests/JsonDeserializer/object.cpp | 14 +--- extras/tests/JsonDeserializer/string.cpp | 6 +- extras/tests/JsonDocument/assignment.cpp | 9 +-- extras/tests/JsonDocument/constructor.cpp | 6 +- extras/tests/JsonDocument/garbageCollect.cpp | 4 +- extras/tests/JsonDocument/overflowed.cpp | 4 +- extras/tests/JsonDocument/shrinkToFit.cpp | 64 +++++++------------ extras/tests/JsonObject/copy.cpp | 4 +- .../deserializeVariant.cpp | 26 ++++---- extras/tests/ResourceManager/CMakeLists.txt | 1 + extras/tests/ResourceManager/allocVariant.cpp | 3 +- extras/tests/ResourceManager/shrinkToFit.cpp | 57 +++++++++++++++++ extras/tests/ResourceManager/size.cpp | 2 +- src/ArduinoJson/Configuration.hpp | 2 +- src/ArduinoJson/Memory/VariantPoolList.hpp | 61 ++++++++++-------- 19 files changed, 183 insertions(+), 143 deletions(-) create mode 100644 extras/tests/ResourceManager/shrinkToFit.cpp diff --git a/extras/tests/Helpers/Allocators.hpp b/extras/tests/Helpers/Allocators.hpp index 813b617e..dadec35f 100644 --- a/extras/tests/Helpers/Allocators.hpp +++ b/extras/tests/Helpers/Allocators.hpp @@ -32,38 +32,60 @@ struct FailingAllocator : ArduinoJson::Allocator { class AllocatorLog { public: - static std::string Allocate(size_t s) { + class Entry { + public: + Entry(std::string s, size_t n = 1) : str_(s), count_(n) {} + + const std::string& str() const { + return str_; + } + + size_t count() const { + return count_; + } + + Entry operator*(size_t n) const { + return Entry(str_, n); + } + + private: + std::string str_; + size_t count_; + }; + + static Entry Allocate(size_t s) { char buffer[32]; sprintf(buffer, "allocate(%zu)", s); - return buffer; + return Entry(buffer); } - static std::string AllocateFail(size_t s) { + static Entry AllocateFail(size_t s) { char buffer[32]; sprintf(buffer, "allocate(%zu) -> nullptr", s); - return buffer; + return Entry(buffer); } - static std::string Reallocate(size_t s1, size_t s2) { + static Entry Reallocate(size_t s1, size_t s2) { char buffer[32]; sprintf(buffer, "reallocate(%zu, %zu)", s1, s2); - return buffer; + return Entry(buffer); }; - static std::string ReallocateFail(size_t s1, size_t s2) { + static Entry ReallocateFail(size_t s1, size_t s2) { char buffer[32]; sprintf(buffer, "reallocate(%zu, %zu) -> nullptr", s1, s2); - return buffer; + return Entry(buffer); }; - static std::string Deallocate(size_t s) { + static Entry Deallocate(size_t s) { char buffer[32]; sprintf(buffer, "deallocate(%zu)", s); - return buffer; + return Entry(buffer); }; - AllocatorLog& operator<<(const std::string& s) { - log_ << s << "\n"; + AllocatorLog& operator<<(const Entry& entry) { + for (size_t i = 0; i < entry.count(); i++) + log_ << entry.str() << "\n"; return *this; } diff --git a/extras/tests/JsonArray/clear.cpp b/extras/tests/JsonArray/clear.cpp index ec22ab9c..f2da9e36 100644 --- a/extras/tests/JsonArray/clear.cpp +++ b/extras/tests/JsonArray/clear.cpp @@ -39,10 +39,7 @@ TEST_CASE("JsonArray::clear()") { for (int i = 0; i < ARDUINOJSON_POOL_CAPACITY; i++) array.add(i); - REQUIRE( - allocator.log() == - AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) - << AllocatorLog::Allocate(sizeofPool()) // only one pool - ); + REQUIRE(allocator.log() == AllocatorLog() + << AllocatorLog::Allocate(sizeofPool())); } } diff --git a/extras/tests/JsonArray/remove.cpp b/extras/tests/JsonArray/remove.cpp index acfe2114..556910b9 100644 --- a/extras/tests/JsonArray/remove.cpp +++ b/extras/tests/JsonArray/remove.cpp @@ -105,9 +105,7 @@ TEST_CASE("Removed elements are recycled") { // add one element; it should use the free slot array.add(42); - REQUIRE( - allocator.log() == - AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) - << AllocatorLog::Allocate(sizeofPool()) // only one pool + REQUIRE(allocator.log() == AllocatorLog() << AllocatorLog::Allocate( + sizeofPool()) // only one pool ); } diff --git a/extras/tests/JsonDeserializer/array.cpp b/extras/tests/JsonDeserializer/array.cpp index 55180ce7..44c537f2 100644 --- a/extras/tests/JsonDeserializer/array.cpp +++ b/extras/tests/JsonDeserializer/array.cpp @@ -281,7 +281,7 @@ TEST_CASE("deserialize JSON array under memory constraints") { } SECTION("allocation of pool fails") { - allocator.setCountdown(1); + allocator.setCountdown(0); char input[] = "[1]"; DeserializationError err = deserializeJson(doc, input); @@ -291,7 +291,7 @@ TEST_CASE("deserialize JSON array under memory constraints") { } SECTION("allocation of string fails in array") { - allocator.setCountdown(2); + allocator.setCountdown(1); char input[] = "[0,\"hi!\"]"; DeserializationError err = deserializeJson(doc, input); diff --git a/extras/tests/JsonDeserializer/object.cpp b/extras/tests/JsonDeserializer/object.cpp index ad23b07b..7e706b27 100644 --- a/extras/tests/JsonDeserializer/object.cpp +++ b/extras/tests/JsonDeserializer/object.cpp @@ -344,7 +344,7 @@ TEST_CASE("deserialize JSON object under memory constraints") { REQUIRE(doc.as() == "{}"); } - SECTION("pool list allocation fails") { + SECTION("pool allocation fails") { allocator.setCountdown(2); char input[] = "{\"a\":1}"; @@ -354,18 +354,8 @@ TEST_CASE("deserialize JSON object under memory constraints") { REQUIRE(doc.as() == "{}"); } - SECTION("pool allocation fails") { - allocator.setCountdown(3); - char input[] = "{\"a\":1}"; - - DeserializationError err = deserializeJson(doc, input); - - REQUIRE(err == DeserializationError::NoMemory); - REQUIRE(doc.as() == "{}"); - } - SECTION("string allocation fails") { - allocator.setCountdown(4); + allocator.setCountdown(3); char input[] = "{\"a\":\"b\"}"; DeserializationError err = deserializeJson(doc, input); diff --git a/extras/tests/JsonDeserializer/string.cpp b/extras/tests/JsonDeserializer/string.cpp index 3c9e400c..2f3d68e7 100644 --- a/extras/tests/JsonDeserializer/string.cpp +++ b/extras/tests/JsonDeserializer/string.cpp @@ -111,14 +111,13 @@ TEST_CASE("Allocation of the key fails") { } SECTION("Quoted string, second member") { - timebombAllocator.setCountdown(4); + timebombAllocator.setCountdown(3); REQUIRE(deserializeJson(doc, "{\"hello\":1,\"world\"}") == DeserializationError::NoMemory); REQUIRE(spyingAllocator.log() == AllocatorLog() << AllocatorLog::Allocate(sizeofString(31)) << AllocatorLog::Reallocate(sizeofString(31), sizeofString(5)) - << AllocatorLog::Allocate(sizeofPoolList()) << AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::AllocateFail(sizeofString(31))); } @@ -131,14 +130,13 @@ TEST_CASE("Allocation of the key fails") { } SECTION("Non-Quoted string, second member") { - timebombAllocator.setCountdown(4); + timebombAllocator.setCountdown(3); REQUIRE(deserializeJson(doc, "{hello:1,world}") == DeserializationError::NoMemory); REQUIRE(spyingAllocator.log() == 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 2fd55951..99acc682 100644 --- a/extras/tests/JsonDocument/assignment.cpp +++ b/extras/tests/JsonDocument/assignment.cpp @@ -24,7 +24,6 @@ TEST_CASE("JsonDocument assignment") { REQUIRE(spyingAllocator.log() == AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello - << AllocatorLog::Allocate(sizeofPoolList()) << AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Allocate(sizeofString(5)) // world ); @@ -40,8 +39,7 @@ TEST_CASE("JsonDocument assignment") { REQUIRE(doc2.as() == "[{\"hello\":\"world\"}]"); REQUIRE(spyingAllocator.log() == - AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) - << AllocatorLog::Allocate(sizeofPool()) + AllocatorLog() << AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Allocate(sizeofString(5)) // hello << AllocatorLog::Allocate(sizeofString(5)) // world ); @@ -58,7 +56,6 @@ TEST_CASE("JsonDocument assignment") { REQUIRE(doc2.as() == "{\"hello\":\"world\"}"); REQUIRE(spyingAllocator.log() == AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello - << AllocatorLog::Allocate(sizeofPoolList()) << AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Allocate(sizeofString(5)) // world ); @@ -78,13 +75,11 @@ TEST_CASE("JsonDocument assignment") { 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())); + << AllocatorLog::Deallocate(sizeofPool())); } SECTION("Assign from JsonObject") { diff --git a/extras/tests/JsonDocument/constructor.cpp b/extras/tests/JsonDocument/constructor.cpp index a3f9a062..1cc382a6 100644 --- a/extras/tests/JsonDocument/constructor.cpp +++ b/extras/tests/JsonDocument/constructor.cpp @@ -59,8 +59,7 @@ TEST_CASE("JsonDocument constructor") { REQUIRE(doc2.as() == "{\"hello\":\"world\"}"); REQUIRE(spyingAllocator.log() == - AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) - << AllocatorLog::Allocate(sizeofPool())); + AllocatorLog() << AllocatorLog::Allocate(sizeofPool())); } SECTION("Construct from JsonArray") { @@ -72,8 +71,7 @@ TEST_CASE("JsonDocument constructor") { REQUIRE(doc2.as() == "[\"hello\"]"); REQUIRE(spyingAllocator.log() == - AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) - << AllocatorLog::Allocate(sizeofPool())); + AllocatorLog() << AllocatorLog::Allocate(sizeofPool())); } SECTION("Construct from JsonVariant") { diff --git a/extras/tests/JsonDocument/garbageCollect.cpp b/extras/tests/JsonDocument/garbageCollect.cpp index 6f274092..d7227d28 100644 --- a/extras/tests/JsonDocument/garbageCollect.cpp +++ b/extras/tests/JsonDocument/garbageCollect.cpp @@ -30,11 +30,9 @@ TEST_CASE("JsonDocument::garbageCollect()") { REQUIRE(doc.as() == "{\"dancing\":2}"); REQUIRE(spyingAllocator.log() == AllocatorLog() << AllocatorLog::Allocate(sizeofString(7)) - << AllocatorLog::Allocate(sizeofPoolList()) << AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Deallocate(sizeofString(7)) - << AllocatorLog::Deallocate(sizeofPool()) - << AllocatorLog::Deallocate(sizeofPoolList())); + << AllocatorLog::Deallocate(sizeofPool())); } SECTION("when allocation fails") { diff --git a/extras/tests/JsonDocument/overflowed.cpp b/extras/tests/JsonDocument/overflowed.cpp index cd4c0eeb..fa716ea9 100644 --- a/extras/tests/JsonDocument/overflowed.cpp +++ b/extras/tests/JsonDocument/overflowed.cpp @@ -47,13 +47,13 @@ TEST_CASE("JsonDocument::overflowed()") { } SECTION("returns true after a failed deserialization") { - allocator.setCountdown(1); + allocator.setCountdown(0); deserializeJson(doc, "[1, 2]"); CHECK(doc.overflowed() == true); } SECTION("returns false after a successful deserialization") { - allocator.setCountdown(4); + allocator.setCountdown(3); deserializeJson(doc, "[\"example\"]"); CHECK(doc.overflowed() == false); } diff --git a/extras/tests/JsonDocument/shrinkToFit.cpp b/extras/tests/JsonDocument/shrinkToFit.cpp index 78a5bb42..f9df912a 100644 --- a/extras/tests/JsonDocument/shrinkToFit.cpp +++ b/extras/tests/JsonDocument/shrinkToFit.cpp @@ -105,13 +105,10 @@ TEST_CASE("JsonDocument::shrinkToFit()") { doc.shrinkToFit(); REQUIRE(doc.as() == "{\"key\":42}"); - REQUIRE(spyingAllocator.log() == - AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) - << AllocatorLog::Allocate(sizeofPool()) - << AllocatorLog::Reallocate(sizeofPool(), - sizeofObject(1)) - << AllocatorLog::Reallocate(sizeofPoolList(), - sizeofPoolList(1))); + REQUIRE(spyingAllocator.log() == AllocatorLog() + << AllocatorLog::Allocate(sizeofPool()) + << AllocatorLog::Reallocate( + sizeofPool(), sizeofObject(1))); } SECTION("owned key") { @@ -122,12 +119,9 @@ TEST_CASE("JsonDocument::shrinkToFit()") { REQUIRE(doc.as() == "{\"abcdefg\":42}"); REQUIRE(spyingAllocator.log() == AllocatorLog() << AllocatorLog::Allocate(sizeofString(7)) - << AllocatorLog::Allocate(sizeofPoolList()) << AllocatorLog::Allocate(sizeofPool()) << AllocatorLog::Reallocate(sizeofPool(), - sizeofObject(1)) - << AllocatorLog::Reallocate(sizeofPoolList(), - sizeofPoolList(1))); + sizeofObject(1))); } SECTION("linked string in array") { @@ -136,13 +130,10 @@ TEST_CASE("JsonDocument::shrinkToFit()") { doc.shrinkToFit(); REQUIRE(doc.as() == "[\"hello\"]"); - REQUIRE( - spyingAllocator.log() == - AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) - << AllocatorLog::Allocate(sizeofPool()) - << AllocatorLog::Reallocate(sizeofPool(), sizeofArray(1)) - << AllocatorLog::Reallocate(sizeofPoolList(), - sizeofPoolList(1))); + REQUIRE(spyingAllocator.log() == AllocatorLog() + << AllocatorLog::Allocate(sizeofPool()) + << AllocatorLog::Reallocate( + sizeofPool(), sizeofArray(1))); } SECTION("owned string in array") { @@ -151,14 +142,11 @@ TEST_CASE("JsonDocument::shrinkToFit()") { doc.shrinkToFit(); REQUIRE(doc.as() == "[\"abcdefg\"]"); - REQUIRE( - spyingAllocator.log() == - AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) - << AllocatorLog::Allocate(sizeofPool()) - << AllocatorLog::Allocate(sizeofString(7)) - << AllocatorLog::Reallocate(sizeofPool(), sizeofArray(1)) - << AllocatorLog::Reallocate(sizeofPoolList(), - sizeofPoolList(1))); + REQUIRE(spyingAllocator.log() == + AllocatorLog() << AllocatorLog::Allocate(sizeofPool()) + << AllocatorLog::Allocate(sizeofString(7)) + << AllocatorLog::Reallocate(sizeofPool(), + sizeofArray(1))); } SECTION("linked string in object") { @@ -167,13 +155,10 @@ TEST_CASE("JsonDocument::shrinkToFit()") { doc.shrinkToFit(); REQUIRE(doc.as() == "{\"key\":\"hello\"}"); - REQUIRE(spyingAllocator.log() == - AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) - << AllocatorLog::Allocate(sizeofPool()) - << AllocatorLog::Reallocate(sizeofPool(), - sizeofObject(1)) - << AllocatorLog::Reallocate(sizeofPoolList(), - sizeofPoolList(1))); + REQUIRE(spyingAllocator.log() == AllocatorLog() + << AllocatorLog::Allocate(sizeofPool()) + << AllocatorLog::Reallocate( + sizeofPool(), sizeofObject(1))); } SECTION("owned string in object") { @@ -182,13 +167,10 @@ TEST_CASE("JsonDocument::shrinkToFit()") { doc.shrinkToFit(); REQUIRE(doc.as() == "{\"key\":\"abcdefg\"}"); - REQUIRE( - spyingAllocator.log() == - AllocatorLog() << AllocatorLog::Allocate(sizeofPoolList()) - << AllocatorLog::Allocate(sizeofPool()) - << AllocatorLog::Allocate(sizeofString(7)) - << AllocatorLog::Reallocate(sizeofPool(), sizeofPool(1)) - << AllocatorLog::Reallocate(sizeofPoolList(), - sizeofPoolList(1))); + REQUIRE(spyingAllocator.log() == + AllocatorLog() << AllocatorLog::Allocate(sizeofPool()) + << AllocatorLog::Allocate(sizeofString(7)) + << AllocatorLog::Reallocate(sizeofPool(), + sizeofPool(1))); } } diff --git a/extras/tests/JsonObject/copy.cpp b/extras/tests/JsonObject/copy.cpp index 92aaf958..47b7bed8 100644 --- a/extras/tests/JsonObject/copy.cpp +++ b/extras/tests/JsonObject/copy.cpp @@ -74,7 +74,7 @@ TEST_CASE("JsonObject::set()") { } SECTION("copy fails in the middle of an object") { - TimebombAllocator allocator(3); + TimebombAllocator allocator(2); JsonDocument doc3(&allocator); JsonObject obj3 = doc3.to(); @@ -88,7 +88,7 @@ TEST_CASE("JsonObject::set()") { } SECTION("copy fails in the middle of an array") { - TimebombAllocator allocator(2); + TimebombAllocator allocator(1); JsonDocument doc3(&allocator); JsonObject obj3 = doc3.to(); diff --git a/extras/tests/MsgPackDeserializer/deserializeVariant.cpp b/extras/tests/MsgPackDeserializer/deserializeVariant.cpp index bd6c4979..eb04eab7 100644 --- a/extras/tests/MsgPackDeserializer/deserializeVariant.cpp +++ b/extras/tests/MsgPackDeserializer/deserializeVariant.cpp @@ -178,23 +178,19 @@ TEST_CASE("deserializeMsgPack() under memory constaints") { checkError(0, "\x91\x01", DeserializationError::NoMemory); // [1] checkError(1, "\x91\x01", - DeserializationError::NoMemory); // [1] - checkError(2, "\x91\x01", DeserializationError::Ok); // [1] } SECTION("array 16") { checkError(0, "\xDC\x00\x00", DeserializationError::Ok); checkError(0, "\xDC\x00\x01\x01", DeserializationError::NoMemory); - checkError(1, "\xDC\x00\x01\x01", DeserializationError::NoMemory); - checkError(2, "\xDC\x00\x01\x01", DeserializationError::Ok); + checkError(1, "\xDC\x00\x01\x01", DeserializationError::Ok); } SECTION("array 32") { checkError(0, "\xDD\x00\x00\x00\x00", DeserializationError::Ok); checkError(0, "\xDD\x00\x00\x00\x01\x01", DeserializationError::NoMemory); - checkError(1, "\xDD\x00\x00\x00\x01\x01", DeserializationError::NoMemory); - checkError(2, "\xDD\x00\x00\x00\x01\x01", DeserializationError::Ok); + checkError(1, "\xDD\x00\x00\x00\x01\x01", DeserializationError::Ok); } SECTION("fixmap") { @@ -203,11 +199,11 @@ TEST_CASE("deserializeMsgPack() under memory constaints") { } SECTION("{H:1}") { checkError(0, "\x81\xA1H\x01", DeserializationError::NoMemory); - checkError(4, "\x81\xA1H\x01", DeserializationError::Ok); + checkError(3, "\x81\xA1H\x01", DeserializationError::Ok); } SECTION("{H:1,W:2}") { - checkError(4, "\x82\xA1H\x01\xA1W\x02", DeserializationError::NoMemory); - checkError(6, "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok); + checkError(3, "\x82\xA1H\x01\xA1W\x02", DeserializationError::NoMemory); + checkError(5, "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok); } } @@ -217,12 +213,12 @@ TEST_CASE("deserializeMsgPack() under memory constaints") { } SECTION("{H:1}") { checkError(2, "\xDE\x00\x01\xA1H\x01", DeserializationError::NoMemory); - checkError(4, "\xDE\x00\x01\xA1H\x01", DeserializationError::Ok); + checkError(3, "\xDE\x00\x01\xA1H\x01", DeserializationError::Ok); } SECTION("{H:1,W:2}") { - checkError(4, "\xDE\x00\x02\xA1H\x01\xA1W\x02", + checkError(3, "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::NoMemory); - checkError(6, "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok); + checkError(5, "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok); } } @@ -233,12 +229,12 @@ TEST_CASE("deserializeMsgPack() under memory constaints") { SECTION("{H:1}") { checkError(2, "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::NoMemory); - checkError(4, "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok); + checkError(3, "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok); } SECTION("{H:1,W:2}") { - checkError(4, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", + checkError(3, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::NoMemory); - checkError(6, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", + checkError(5, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok); } } diff --git a/extras/tests/ResourceManager/CMakeLists.txt b/extras/tests/ResourceManager/CMakeLists.txt index a8a7a4be..bfa370bb 100644 --- a/extras/tests/ResourceManager/CMakeLists.txt +++ b/extras/tests/ResourceManager/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(ResourceManagerTests allocVariant.cpp clear.cpp saveString.cpp + shrinkToFit.cpp size.cpp StringBuilder.cpp ) diff --git a/extras/tests/ResourceManager/allocVariant.cpp b/extras/tests/ResourceManager/allocVariant.cpp index 5bf734e5..891dcaae 100644 --- a/extras/tests/ResourceManager/allocVariant.cpp +++ b/extras/tests/ResourceManager/allocVariant.cpp @@ -57,8 +57,7 @@ TEST_CASE("ResourceManager::allocSlot()") { } SECTION("Returns null if pool allocation fails") { - TimebombAllocator allocator(1); - ResourceManager resources(&allocator); + ResourceManager resources(FailingAllocator::instance()); resources.allocSlot(); diff --git a/extras/tests/ResourceManager/shrinkToFit.cpp b/extras/tests/ResourceManager/shrinkToFit.cpp new file mode 100644 index 00000000..17eb6029 --- /dev/null +++ b/extras/tests/ResourceManager/shrinkToFit.cpp @@ -0,0 +1,57 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2023, Benoit BLANCHON +// MIT License + +#include +#include +#include + +#include "Allocators.hpp" + +using namespace ArduinoJson::detail; + +TEST_CASE("ResourceManager::shrinkToFit()") { + TimebombAllocator allocator(100); + SpyingAllocator spyingAllocator(&allocator); + ResourceManager resources(&spyingAllocator); + + SECTION("empty") { + resources.shrinkToFit(); + + REQUIRE(spyingAllocator.log() == AllocatorLog()); + } + + SECTION("only one pool") { + resources.allocSlot(); + + resources.shrinkToFit(); + + REQUIRE(spyingAllocator.log() == + AllocatorLog() << AllocatorLog::Allocate(sizeofPool()) + << AllocatorLog::Reallocate(sizeofPool(), + sizeof(VariantSlot))); + } + + SECTION("more pools than initial count") { + for (size_t i = 0; + i < ARDUINOJSON_POOL_CAPACITY * ARDUINOJSON_INITIAL_POOL_COUNT + 1; + i++) + resources.allocSlot(); + REQUIRE(spyingAllocator.log() == + AllocatorLog() << AllocatorLog::Allocate(sizeofPool()) * + ARDUINOJSON_INITIAL_POOL_COUNT + << AllocatorLog::Allocate(sizeofPoolList( + ARDUINOJSON_INITIAL_POOL_COUNT * 2)) + << AllocatorLog::Allocate(sizeofPool())); + + spyingAllocator.clearLog(); + resources.shrinkToFit(); + + REQUIRE(spyingAllocator.log() == + AllocatorLog() + << AllocatorLog::Reallocate(sizeofPool(), sizeof(VariantSlot)) + << AllocatorLog::Reallocate( + sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT * 2), + sizeofPoolList(ARDUINOJSON_INITIAL_POOL_COUNT + 1))); + } +} diff --git a/extras/tests/ResourceManager/size.cpp b/extras/tests/ResourceManager/size.cpp index aecb733e..96815d01 100644 --- a/extras/tests/ResourceManager/size.cpp +++ b/extras/tests/ResourceManager/size.cpp @@ -19,7 +19,7 @@ TEST_CASE("ResourceManager::size()") { } SECTION("Doesn't grow when allocation of second pool fails") { - allocator.setCountdown(2); + allocator.setCountdown(1); 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 70fbff9e..6869a323 100644 --- a/src/ArduinoJson/Configuration.hpp +++ b/src/ArduinoJson/Configuration.hpp @@ -105,7 +105,7 @@ # endif #endif -// Capacity of each variant pool (in slots) +// Initial capacity of the pool list #ifndef ARDUINOJSON_INITIAL_POOL_COUNT # define ARDUINOJSON_INITIAL_POOL_COUNT 4 #endif diff --git a/src/ArduinoJson/Memory/VariantPoolList.hpp b/src/ArduinoJson/Memory/VariantPoolList.hpp index 71ed152f..9cbf86fd 100644 --- a/src/ArduinoJson/Memory/VariantPoolList.hpp +++ b/src/ArduinoJson/Memory/VariantPoolList.hpp @@ -16,15 +16,21 @@ class VariantPoolList { VariantPoolList() = default; ~VariantPoolList() { - ARDUINOJSON_ASSERT(pools_ == nullptr); + ARDUINOJSON_ASSERT(count_ == 0); } VariantPoolList& operator=(VariantPoolList&& src) { - ARDUINOJSON_ASSERT(pools_ == nullptr); - pools_ = src.pools_; + ARDUINOJSON_ASSERT(count_ == 0); + if (src.pools_ == src.preallocatedPools_) { + memcpy(preallocatedPools_, src.preallocatedPools_, + sizeof(preallocatedPools_)); + pools_ = preallocatedPools_; + } else { + pools_ = src.pools_; + src.pools_ = nullptr; + } count_ = src.count_; capacity_ = src.capacity_; - src.pools_ = nullptr; src.count_ = 0; src.capacity_ = 0; return *this; @@ -37,7 +43,7 @@ class VariantPoolList { } // try to allocate from last pool (other pools are full) - if (pools_) { + if (count_) { auto slot = allocFromLastPool(); if (slot) return slot; @@ -63,14 +69,14 @@ class VariantPoolList { } void clear(Allocator* allocator) { - if (!pools_) - return; for (PoolCount i = 0; i < count_; i++) pools_[i].destroy(allocator); - allocator->deallocate(pools_); - pools_ = nullptr; count_ = 0; - capacity_ = 0; + if (pools_ != preallocatedPools_) { + allocator->deallocate(pools_); + pools_ = preallocatedPools_; + capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT; + } } SlotCount usage() const { @@ -81,9 +87,9 @@ class VariantPoolList { } void shrinkToFit(Allocator* allocator) { - if (pools_) + if (count_ > 0) pools_[count_ - 1].shrinkToFit(allocator); - if (count_ != capacity_) { + if (pools_ != preallocatedPools_ && count_ != capacity_) { pools_ = static_cast( allocator->reallocate(pools_, count_ * sizeof(VariantPool))); ARDUINOJSON_ASSERT(pools_ != nullptr); // realloc to smaller can't fail @@ -95,10 +101,9 @@ class VariantPoolList { SlotWithId allocFromFreeList(); SlotWithId allocFromLastPool() { - ARDUINOJSON_ASSERT(pools_ != nullptr); + ARDUINOJSON_ASSERT(count_ > 0); auto poolIndex = SlotId(count_ - 1); - auto lastPool = count_ ? &pools_[poolIndex] : nullptr; - auto slot = lastPool->allocSlot(); + auto slot = pools_[poolIndex].allocSlot(); if (!slot) return {}; return {slot, SlotId(poolIndex * ARDUINOJSON_POOL_CAPACITY + slot.id())}; @@ -116,28 +121,32 @@ class VariantPoolList { } bool increaseCapacity(Allocator* allocator) { - if (count_ == maxPools) + if (capacity_ == maxPools) return false; void* newPools; - PoolCount newCapacity; - if (pools_) { - newCapacity = PoolCount(capacity_ * 2); + auto newCapacity = PoolCount(capacity_ * 2); + + if (pools_ == preallocatedPools_) { + newPools = allocator->allocate(newCapacity * sizeof(VariantPool)); + if (!newPools) + return false; + memcpy(newPools, preallocatedPools_, sizeof(preallocatedPools_)); + } else { newPools = allocator->reallocate(pools_, newCapacity * sizeof(VariantPool)); - } else { - newCapacity = ARDUINOJSON_INITIAL_POOL_COUNT; - newPools = allocator->allocate(newCapacity * sizeof(VariantPool)); + if (!newPools) + return false; } - if (!newPools) - return false; + pools_ = static_cast(newPools); capacity_ = newCapacity; return true; } - VariantPool* pools_ = nullptr; + VariantPool preallocatedPools_[ARDUINOJSON_INITIAL_POOL_COUNT]; + VariantPool* pools_ = preallocatedPools_; PoolCount count_ = 0; - PoolCount capacity_ = 0; + PoolCount capacity_ = ARDUINOJSON_INITIAL_POOL_COUNT; SlotId freeList_ = NULL_SLOT; public: