فهرست منبع

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

Benoit Blanchon 5 سال پیش
والد
کامیت
c1b3705df1
3فایلهای تغییر یافته به همراه91 افزوده شده و 19 حذف شده
  1. 1 0
      CHANGELOG.md
  2. 61 10
      extras/tests/JsonDocument/BasicJsonDocument.cpp
  3. 29 9
      src/ArduinoJson/Document/BasicJsonDocument.hpp

+ 1 - 0
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)
 -------

+ 61 - 10
extras/tests/JsonDocument/BasicJsonDocument.cpp

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

+ 29 - 9
src/ArduinoJson/Document/BasicJsonDocument.hpp

@@ -12,10 +12,10 @@ namespace ARDUINOJSON_NAMESPACE {
 // (we need to store the allocator before constructing JsonDocument)
 template <typename TAllocator>
 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 <typename TAllocator>
 class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument {
  public:
-  explicit BasicJsonDocument(size_t capa, TAllocator allocator = TAllocator())
-      : AllocatorOwner<TAllocator>(allocator), JsonDocument(allocPool(capa)) {}
+  explicit BasicJsonDocument(size_t capa, TAllocator alloc = TAllocator())
+      : AllocatorOwner<TAllocator>(alloc), JsonDocument(allocPool(capa)) {}
 
   BasicJsonDocument(const BasicJsonDocument& src)
       : AllocatorOwner<TAllocator>(src),
@@ -78,11 +82,7 @@ class BasicJsonDocument : AllocatorOwner<TAllocator>, 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<TAllocator>, public JsonDocument {
     _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:
   MemoryPool allocPool(size_t requiredSize) {
     size_t capa = addPadding(requiredSize);
@@ -125,6 +137,14 @@ class BasicJsonDocument : AllocatorOwner<TAllocator>, 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