Added BasicJsonDocument::garbageCollect() (issue #1195)

This commit is contained in:
Benoit Blanchon
2020-03-01 18:01:55 +01:00
parent 2540b4e01b
commit c1b3705df1
3 changed files with 91 additions and 19 deletions

View File

@ -12,6 +12,7 @@ HEAD
* Fixed enums serialized as booleans (issue #1197) * Fixed enums serialized as booleans (issue #1197)
* Fixed incorrect string comparison on some platforms (issue #1198) * Fixed incorrect string comparison on some platforms (issue #1198)
* Added move-constructor and move-assignment to `BasicJsonDocument` * Added move-constructor and move-assignment to `BasicJsonDocument`
* Added `BasicJsonDocument::garbageCollect()` (issue #1195)
v6.14.1 (2020-01-27) v6.14.1 (2020-01-27)
------- -------

View File

@ -30,22 +30,40 @@ class SpyingAllocator {
std::ostream& _log; std::ostream& _log;
}; };
typedef BasicJsonDocument<SpyingAllocator> 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") { TEST_CASE("BasicJsonDocument") {
std::stringstream log; std::stringstream log;
SECTION("Construct/Destruct") { SECTION("Construct/Destruct") {
{ MyJsonDocument doc(4096, log); } { BasicJsonDocument<SpyingAllocator> doc(4096, log); }
REQUIRE(log.str() == "A4096F"); REQUIRE(log.str() == "A4096F");
} }
SECTION("Copy construct") { SECTION("Copy construct") {
{ {
MyJsonDocument doc1(4096, log); BasicJsonDocument<SpyingAllocator> doc1(4096, log);
doc1.set(std::string("The size of this string is 32!!")); doc1.set(std::string("The size of this string is 32!!"));
MyJsonDocument doc2(doc1); BasicJsonDocument<SpyingAllocator> doc2(doc1);
REQUIRE(doc1.as<std::string>() == "The size of this string is 32!!"); REQUIRE(doc1.as<std::string>() == "The size of this string is 32!!");
REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!"); REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
@ -55,10 +73,10 @@ TEST_CASE("BasicJsonDocument") {
SECTION("Move construct") { SECTION("Move construct") {
{ {
MyJsonDocument doc1(4096, log); BasicJsonDocument<SpyingAllocator> doc1(4096, log);
doc1.set(std::string("The size of this string is 32!!")); doc1.set(std::string("The size of this string is 32!!"));
MyJsonDocument doc2(move(doc1)); BasicJsonDocument<SpyingAllocator> doc2(move(doc1));
REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!"); REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
#if ARDUINOJSON_HAS_RVALUE_REFERENCES #if ARDUINOJSON_HAS_RVALUE_REFERENCES
@ -76,9 +94,9 @@ TEST_CASE("BasicJsonDocument") {
SECTION("Copy assign") { SECTION("Copy assign") {
{ {
MyJsonDocument doc1(4096, log); BasicJsonDocument<SpyingAllocator> doc1(4096, log);
doc1.set(std::string("The size of this string is 32!!")); doc1.set(std::string("The size of this string is 32!!"));
MyJsonDocument doc2(8, log); BasicJsonDocument<SpyingAllocator> doc2(8, log);
doc2 = doc1; doc2 = doc1;
@ -90,9 +108,9 @@ TEST_CASE("BasicJsonDocument") {
SECTION("Move assign") { SECTION("Move assign") {
{ {
MyJsonDocument doc1(4096, log); BasicJsonDocument<SpyingAllocator> doc1(4096, log);
doc1.set(std::string("The size of this string is 32!!")); doc1.set(std::string("The size of this string is 32!!"));
MyJsonDocument doc2(8, log); BasicJsonDocument<SpyingAllocator> doc2(8, log);
doc2 = move(doc1); doc2 = move(doc1);
@ -109,4 +127,37 @@ TEST_CASE("BasicJsonDocument") {
REQUIRE(log.str() == "A4096A8FA32FF"); REQUIRE(log.str() == "A4096A8FA32FF");
#endif #endif
} }
SECTION("garbageCollect()") {
BasicJsonDocument<ControllableAllocator> 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<std::string>() == "{\"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<std::string>() == "{\"dancing\":2}");
}
}
} }

View File

@ -12,10 +12,10 @@ namespace ARDUINOJSON_NAMESPACE {
// (we need to store the allocator before constructing JsonDocument) // (we need to store the allocator before constructing JsonDocument)
template <typename TAllocator> template <typename TAllocator>
class AllocatorOwner { class AllocatorOwner {
protected: public:
AllocatorOwner() {} AllocatorOwner() {}
AllocatorOwner(const AllocatorOwner& src) : _allocator(src._allocator) {} AllocatorOwner(const AllocatorOwner& src) : _allocator(src._allocator) {}
AllocatorOwner(TAllocator allocator) : _allocator(allocator) {} AllocatorOwner(TAllocator a) : _allocator(a) {}
void* allocate(size_t size) { void* allocate(size_t size) {
return _allocator.allocate(size); return _allocator.allocate(size);
@ -29,6 +29,10 @@ class AllocatorOwner {
return _allocator.reallocate(ptr, new_size); return _allocator.reallocate(ptr, new_size);
} }
TAllocator& allocator() {
return _allocator;
}
private: private:
TAllocator _allocator; TAllocator _allocator;
}; };
@ -36,8 +40,8 @@ class AllocatorOwner {
template <typename TAllocator> template <typename TAllocator>
class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument { class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument {
public: public:
explicit BasicJsonDocument(size_t capa, TAllocator allocator = TAllocator()) explicit BasicJsonDocument(size_t capa, TAllocator alloc = TAllocator())
: AllocatorOwner<TAllocator>(allocator), JsonDocument(allocPool(capa)) {} : AllocatorOwner<TAllocator>(alloc), JsonDocument(allocPool(capa)) {}
BasicJsonDocument(const BasicJsonDocument& src) BasicJsonDocument(const BasicJsonDocument& src)
: AllocatorOwner<TAllocator>(src), : AllocatorOwner<TAllocator>(src),
@ -78,11 +82,7 @@ class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument {
#if ARDUINOJSON_HAS_RVALUE_REFERENCES #if ARDUINOJSON_HAS_RVALUE_REFERENCES
BasicJsonDocument& operator=(BasicJsonDocument&& src) { BasicJsonDocument& operator=(BasicJsonDocument&& src) {
freePool(); moveAssignFrom(src);
_data = src._data;
_pool = src._pool;
src._data.setNull();
src._pool = MemoryPool(0, 0);
return *this; return *this;
} }
#endif #endif
@ -109,6 +109,18 @@ class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument {
_data.movePointers(ptr_offset, ptr_offset - bytes_reclaimed); _data.movePointers(ptr_offset, ptr_offset - bytes_reclaimed);
} }
bool garbageCollect() {
// make a temporary clone and move assign
BasicJsonDocument<TAllocator> tmp(capacity(), allocator());
if (!tmp.capacity())
return false;
tmp.set(*this);
moveAssignFrom(tmp);
return true;
}
using AllocatorOwner<TAllocator>::allocator;
private: private:
MemoryPool allocPool(size_t requiredSize) { MemoryPool allocPool(size_t requiredSize) {
size_t capa = addPadding(requiredSize); size_t capa = addPadding(requiredSize);
@ -125,6 +137,14 @@ class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument {
void freePool() { void freePool() {
this->deallocate(memoryPool().buffer()); 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 } // namespace ARDUINOJSON_NAMESPACE