From c1b3705df15dcb4cb839ecfad0bc42faac234cb8 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Sun, 1 Mar 2020 18:01:55 +0100 Subject: [PATCH] Added `BasicJsonDocument::garbageCollect()` (issue #1195) --- CHANGELOG.md | 1 + .../tests/JsonDocument/BasicJsonDocument.cpp | 71 ++++++++++++++++--- .../Document/BasicJsonDocument.hpp | 38 +++++++--- 3 files changed, 91 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e1738a5..e8c2e3cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ HEAD * Fixed enums serialized as booleans (issue #1197) * Fixed incorrect string comparison on some platforms (issue #1198) * Added move-constructor and move-assignment to `BasicJsonDocument` +* Added `BasicJsonDocument::garbageCollect()` (issue #1195) v6.14.1 (2020-01-27) ------- diff --git a/extras/tests/JsonDocument/BasicJsonDocument.cpp b/extras/tests/JsonDocument/BasicJsonDocument.cpp index 07800b00..f0672f32 100644 --- a/extras/tests/JsonDocument/BasicJsonDocument.cpp +++ b/extras/tests/JsonDocument/BasicJsonDocument.cpp @@ -30,22 +30,40 @@ class SpyingAllocator { std::ostream& _log; }; -typedef BasicJsonDocument MyJsonDocument; +class ControllableAllocator { + public: + ControllableAllocator() : _enabled(true) {} + + void* allocate(size_t n) { + return _enabled ? malloc(n) : 0; + } + + void deallocate(void* p) { + free(p); + } + + void disable() { + _enabled = false; + } + + private: + bool _enabled; +}; TEST_CASE("BasicJsonDocument") { std::stringstream log; SECTION("Construct/Destruct") { - { MyJsonDocument doc(4096, log); } + { BasicJsonDocument doc(4096, log); } REQUIRE(log.str() == "A4096F"); } SECTION("Copy construct") { { - MyJsonDocument doc1(4096, log); + BasicJsonDocument doc1(4096, log); doc1.set(std::string("The size of this string is 32!!")); - MyJsonDocument doc2(doc1); + BasicJsonDocument doc2(doc1); REQUIRE(doc1.as() == "The size of this string is 32!!"); REQUIRE(doc2.as() == "The size of this string is 32!!"); @@ -55,10 +73,10 @@ TEST_CASE("BasicJsonDocument") { SECTION("Move construct") { { - MyJsonDocument doc1(4096, log); + BasicJsonDocument doc1(4096, log); doc1.set(std::string("The size of this string is 32!!")); - MyJsonDocument doc2(move(doc1)); + BasicJsonDocument doc2(move(doc1)); REQUIRE(doc2.as() == "The size of this string is 32!!"); #if ARDUINOJSON_HAS_RVALUE_REFERENCES @@ -76,9 +94,9 @@ TEST_CASE("BasicJsonDocument") { SECTION("Copy assign") { { - MyJsonDocument doc1(4096, log); + BasicJsonDocument doc1(4096, log); doc1.set(std::string("The size of this string is 32!!")); - MyJsonDocument doc2(8, log); + BasicJsonDocument doc2(8, log); doc2 = doc1; @@ -90,9 +108,9 @@ TEST_CASE("BasicJsonDocument") { SECTION("Move assign") { { - MyJsonDocument doc1(4096, log); + BasicJsonDocument doc1(4096, log); doc1.set(std::string("The size of this string is 32!!")); - MyJsonDocument doc2(8, log); + BasicJsonDocument doc2(8, log); doc2 = move(doc1); @@ -109,4 +127,37 @@ TEST_CASE("BasicJsonDocument") { REQUIRE(log.str() == "A4096A8FA32FF"); #endif } + + SECTION("garbageCollect()") { + BasicJsonDocument doc(4096); + + SECTION("when allocation succeeds") { + deserializeJson(doc, "{\"blanket\":1,\"dancing\":2}"); + REQUIRE(doc.capacity() == 4096); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + doc.remove("blanket"); + + bool result = doc.garbageCollect(); + + REQUIRE(result == true); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 8); + REQUIRE(doc.capacity() == 4096); + REQUIRE(doc.as() == "{\"dancing\":2}"); + } + + SECTION("when allocation fails") { + deserializeJson(doc, "{\"blanket\":1,\"dancing\":2}"); + REQUIRE(doc.capacity() == 4096); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + doc.remove("blanket"); + doc.allocator().disable(); + + bool result = doc.garbageCollect(); + + REQUIRE(result == false); + REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(2) + 16); + REQUIRE(doc.capacity() == 4096); + REQUIRE(doc.as() == "{\"dancing\":2}"); + } + } } diff --git a/src/ArduinoJson/Document/BasicJsonDocument.hpp b/src/ArduinoJson/Document/BasicJsonDocument.hpp index 9319ce56..a9e9f88d 100644 --- a/src/ArduinoJson/Document/BasicJsonDocument.hpp +++ b/src/ArduinoJson/Document/BasicJsonDocument.hpp @@ -12,10 +12,10 @@ namespace ARDUINOJSON_NAMESPACE { // (we need to store the allocator before constructing JsonDocument) template class AllocatorOwner { - protected: + public: AllocatorOwner() {} AllocatorOwner(const AllocatorOwner& src) : _allocator(src._allocator) {} - AllocatorOwner(TAllocator allocator) : _allocator(allocator) {} + AllocatorOwner(TAllocator a) : _allocator(a) {} void* allocate(size_t size) { return _allocator.allocate(size); @@ -29,6 +29,10 @@ class AllocatorOwner { return _allocator.reallocate(ptr, new_size); } + TAllocator& allocator() { + return _allocator; + } + private: TAllocator _allocator; }; @@ -36,8 +40,8 @@ class AllocatorOwner { template class BasicJsonDocument : AllocatorOwner, public JsonDocument { public: - explicit BasicJsonDocument(size_t capa, TAllocator allocator = TAllocator()) - : AllocatorOwner(allocator), JsonDocument(allocPool(capa)) {} + explicit BasicJsonDocument(size_t capa, TAllocator alloc = TAllocator()) + : AllocatorOwner(alloc), JsonDocument(allocPool(capa)) {} BasicJsonDocument(const BasicJsonDocument& src) : AllocatorOwner(src), @@ -78,11 +82,7 @@ class BasicJsonDocument : AllocatorOwner, public JsonDocument { #if ARDUINOJSON_HAS_RVALUE_REFERENCES BasicJsonDocument& operator=(BasicJsonDocument&& src) { - freePool(); - _data = src._data; - _pool = src._pool; - src._data.setNull(); - src._pool = MemoryPool(0, 0); + moveAssignFrom(src); return *this; } #endif @@ -109,6 +109,18 @@ class BasicJsonDocument : AllocatorOwner, public JsonDocument { _data.movePointers(ptr_offset, ptr_offset - bytes_reclaimed); } + bool garbageCollect() { + // make a temporary clone and move assign + BasicJsonDocument tmp(capacity(), allocator()); + if (!tmp.capacity()) + return false; + tmp.set(*this); + moveAssignFrom(tmp); + return true; + } + + using AllocatorOwner::allocator; + private: MemoryPool allocPool(size_t requiredSize) { size_t capa = addPadding(requiredSize); @@ -125,6 +137,14 @@ class BasicJsonDocument : AllocatorOwner, public JsonDocument { void freePool() { this->deallocate(memoryPool().buffer()); } + + void moveAssignFrom(BasicJsonDocument& src) { + freePool(); + _data = src._data; + _pool = src._pool; + src._data.setNull(); + src._pool = MemoryPool(0, 0); + } }; } // namespace ARDUINOJSON_NAMESPACE