Просмотр исходного кода

JsonArray::remove() and JsonObject::remove() now release the memory of the variant

Benoit Blanchon 7 лет назад
Родитель
Сommit
d8d939660b

+ 1 - 0
CHANGELOG.md

@@ -8,6 +8,7 @@ HEAD
 * Removed `JsonObject::is<T>(k)` and `JsonObject::set(k,v)`
 * Replaced `T JsonArray::get<T>(i)` with `JsonVariant JsonArray::get(i)`
 * Replaced `T JsonObject::get<T>(k)` with `JsonVariant JsonObject::get(k)`
+* `JsonArray::remove()` and `JsonObject::remove()` now release the memory of the variant
 
 v6.5.0-beta (2018-10-13)
 -----------

+ 7 - 4
src/ArduinoJson/Data/ArrayFunctions.hpp

@@ -13,10 +13,11 @@ namespace ARDUINOJSON_NAMESPACE {
 inline JsonVariantData* arrayAdd(JsonArrayData* arr, MemoryPool* pool) {
   if (!arr) return 0;
 
-  Slot* slot = new (pool) Slot();
+  Slot* slot = pool->allocSlot();
   if (!slot) return 0;
 
   slot->next = 0;
+  slot->value.type = JSON_NULL;
 
   if (arr->tail) {
     slot->prev = arr->tail;
@@ -41,7 +42,7 @@ inline JsonVariantData* arrayGet(const JsonArrayData* arr, size_t index) {
   return slot ? &slot->value : 0;
 }
 
-inline void arrayRemove(JsonArrayData* arr, Slot* slot) {
+inline void arrayRemove(JsonArrayData* arr, Slot* slot, MemoryPool* pool) {
   if (!arr || !slot) return;
 
   if (slot->prev)
@@ -52,10 +53,12 @@ inline void arrayRemove(JsonArrayData* arr, Slot* slot) {
     slot->next->prev = slot->prev;
   else
     arr->tail = slot->prev;
+
+  slotFree(slot, pool);
 }
 
-inline void arrayRemove(JsonArrayData* arr, size_t index) {
-  arrayRemove(arr, arrayGetSlot(arr, index));
+inline void arrayRemove(JsonArrayData* arr, size_t index, MemoryPool* pool) {
+  arrayRemove(arr, arrayGetSlot(arr, index), pool);
 }
 
 inline void arrayClear(JsonArrayData* arr) {

+ 6 - 2
src/ArduinoJson/Data/ObjectFunctions.hpp

@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include "../Memory/MemoryPool.hpp"
 #include "JsonVariantData.hpp"
 #include "SlotFunctions.hpp"
 
@@ -28,10 +29,11 @@ inline bool objectContainsKey(const JsonObjectData* obj, const TKey& key) {
 template <typename TKey>
 inline JsonVariantData* objectAdd(JsonObjectData* obj, TKey key,
                                   MemoryPool* pool) {
-  Slot* slot = new (pool) Slot();
+  Slot* slot = pool->allocSlot();
   if (!slot) return 0;
 
   slot->next = 0;
+  slot->value.type = JSON_NULL;
 
   if (obj->tail) {
     slot->prev = obj->tail;
@@ -74,7 +76,7 @@ inline void objectClear(JsonObjectData* obj) {
   obj->tail = 0;
 }
 
-inline void objectRemove(JsonObjectData* obj, Slot* slot) {
+inline void objectRemove(JsonObjectData* obj, Slot* slot, MemoryPool* pool) {
   if (!obj) return;
   if (!slot) return;
   if (slot->prev)
@@ -85,6 +87,8 @@ inline void objectRemove(JsonObjectData* obj, Slot* slot) {
     slot->next->prev = slot->prev;
   else
     obj->tail = slot->prev;
+
+  slotFree(slot, pool);
 }
 
 inline size_t objectSize(const JsonObjectData* obj) {

+ 1 - 2
src/ArduinoJson/Data/Slot.hpp

@@ -4,12 +4,11 @@
 
 #pragma once
 
-#include "../Memory/AllocableInMemoryPool.hpp"
 #include "JsonVariantData.hpp"
 
 namespace ARDUINOJSON_NAMESPACE {
 
-struct Slot : AllocableInMemoryPool {
+struct Slot {
   JsonVariantData value;
   struct Slot* next;
   struct Slot* prev;

+ 12 - 0
src/ArduinoJson/Data/SlotFunctions.hpp

@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include "../Memory/MemoryPool.hpp"
 #include "../Strings/StringTypes.hpp"
 #include "JsonVariantData.hpp"
 #include "Slot.hpp"
@@ -56,4 +57,15 @@ inline size_t slotSize(const Slot* slot) {
   }
   return n;
 }
+
+inline void slotFree(Slot* slot, MemoryPool* pool) {
+  const JsonVariantData& v = slot->value;
+  if (v.type == JSON_ARRAY || v.type == JSON_OBJECT) {
+    for (Slot* s = v.content.asObject.head; s; s = s->next) {
+      slotFree(s, pool);
+    }
+  }
+
+  pool->freeSlot(slot);
+}
 }  // namespace ARDUINOJSON_NAMESPACE

+ 2 - 2
src/ArduinoJson/JsonArray.hpp

@@ -197,12 +197,12 @@ class JsonArray : public JsonArrayProxy<JsonArrayData>, public Visitable {
 
   // Removes element at specified position.
   FORCE_INLINE void remove(iterator it) const {
-    arrayRemove(_data, it.internal());
+    arrayRemove(_data, it.internal(), _memoryPool);
   }
 
   // Removes element at specified index.
   FORCE_INLINE void remove(size_t index) const {
-    arrayRemove(_data, index);
+    arrayRemove(_data, index, _memoryPool);
   }
 
   template <typename Visitor>

+ 2 - 2
src/ArduinoJson/JsonObject.hpp

@@ -227,7 +227,7 @@ class JsonObject : public JsonObjectProxy<JsonObjectData>, public Visitable {
   }
 
   FORCE_INLINE void remove(iterator it) const {
-    objectRemove(_data, it.internal());
+    objectRemove(_data, it.internal(), _memoryPool);
   }
 
   // Removes the specified key and the associated value.
@@ -278,7 +278,7 @@ class JsonObject : public JsonObjectProxy<JsonObjectData>, public Visitable {
 
   template <typename TStringRef>
   FORCE_INLINE void remove_impl(TStringRef key) const {
-    objectRemove(_data, objectFindSlot(_data, key));
+    objectRemove(_data, objectFindSlot(_data, key), _memoryPool);
   }
 
   MemoryPool* _memoryPool;

+ 0 - 19
src/ArduinoJson/Memory/AllocableInMemoryPool.hpp

@@ -1,19 +0,0 @@
-// ArduinoJson - arduinojson.org
-// Copyright Benoit Blanchon 2014-2018
-// MIT License
-
-#pragma once
-
-#include "MemoryPool.hpp"
-
-namespace ARDUINOJSON_NAMESPACE {
-
-class AllocableInMemoryPool {
- public:
-  void *operator new(size_t n, MemoryPool *memoryPool) NOEXCEPT {
-    return memoryPool->alloc(n);
-  }
-
-  void operator delete(void *, MemoryPool *)NOEXCEPT {}
-};
-}  // namespace ARDUINOJSON_NAMESPACE

+ 1 - 1
src/ArduinoJson/Memory/DynamicMemoryPool.hpp

@@ -58,7 +58,7 @@ class DynamicMemoryPoolBase : public MemoryPool {
   }
 
   // Gets the number of bytes occupied in the memoryPool
-  size_t size() const {
+  virtual size_t allocated_bytes() const {
     size_t total = 0;
     for (const Block* b = _head; b; b = b->next) total += b->size;
     return total;

+ 28 - 0
src/ArduinoJson/Memory/MemoryPool.hpp

@@ -9,6 +9,7 @@
 #include <string.h>
 
 #include "../Configuration.hpp"
+#include "../Data/Slot.hpp"
 #include "../Polyfills/attributes.hpp"
 
 namespace ARDUINOJSON_NAMESPACE {
@@ -23,12 +24,36 @@ class MemoryPool {
 
   virtual char *realloc(char *oldPtr, size_t oldSize, size_t newSize) = 0;
 
+  Slot *allocSlot() {
+    if (_freeSlots) {
+      Slot *s = _freeSlots;
+      _freeSlots = s->next;
+      return s;
+    }
+    return reinterpret_cast<Slot *>(alloc(sizeof(Slot)));
+  }
+
+  void freeSlot(Slot *slot) {
+    slot->next = _freeSlots;
+    _freeSlots = slot;
+  }
+
+  size_t size() const {
+    size_t result = allocated_bytes();
+    for (Slot *s = _freeSlots; s; s = s->next) result -= sizeof(Slot);
+    return result;
+  }
+
  protected:
+  MemoryPool() : _freeSlots(0) {}
+
   // CAUTION: NO VIRTUAL DESTRUCTOR!
   // If we add a virtual constructor the Arduino compiler will add malloc()
   // and free() to the binary, adding 706 useless bytes.
   ~MemoryPool() {}
 
+  virtual size_t allocated_bytes() const = 0;
+
   // Preserve aligment if necessary
   static FORCE_INLINE size_t round_size_up(size_t bytes) {
 #if ARDUINOJSON_ENABLE_ALIGNMENT
@@ -38,5 +63,8 @@ class MemoryPool {
     return bytes;
 #endif
   }
+
+ private:
+  Slot *_freeSlots;
 };
 }  // namespace ARDUINOJSON_NAMESPACE

+ 5 - 5
src/ArduinoJson/Memory/StaticMemoryPool.hpp

@@ -18,11 +18,6 @@ class StaticMemoryPoolBase : public MemoryPool {
     return _capacity;
   }
 
-  // Gets the current usage of the memoryPool in bytes
-  size_t size() const {
-    return _size;
-  }
-
   // Allocates the specified amount of bytes in the memoryPool
   virtual char* alloc(size_t bytes) {
     alignNextAlloc();
@@ -53,6 +48,11 @@ class StaticMemoryPoolBase : public MemoryPool {
 
   ~StaticMemoryPoolBase() {}
 
+  // Gets the current usage of the memoryPool in bytes
+  virtual size_t allocated_bytes() const {
+    return _size;
+  }
+
  private:
   void alignNextAlloc() {
     _size = round_size_up(_size);

+ 1 - 0
test/DynamicMemoryPool/CMakeLists.txt

@@ -4,6 +4,7 @@
 
 add_executable(DynamicMemoryPoolTests 
 	alloc.cpp
+	allocSlot.cpp
 	no_memory.cpp
 	size.cpp
 	startString.cpp

+ 27 - 0
test/DynamicMemoryPool/allocSlot.cpp

@@ -0,0 +1,27 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#include <ArduinoJson/Memory/DynamicMemoryPool.hpp>
+#include <catch.hpp>
+
+using namespace ARDUINOJSON_NAMESPACE;
+
+TEST_CASE("DynamicMemoryPool::allocSlot()") {
+  DynamicMemoryPool memoryPool;
+
+  SECTION("Returns different pointer") {
+    Slot* s1 = memoryPool.allocSlot();
+    Slot* s2 = memoryPool.allocSlot();
+
+    REQUIRE(s1 != s2);
+  }
+
+  SECTION("Returns same pointer after freeSlot()") {
+    Slot* s1 = memoryPool.allocSlot();
+    memoryPool.freeSlot(s1);
+    Slot* s2 = memoryPool.allocSlot();
+
+    REQUIRE(s1 == s2);
+  }
+}

+ 19 - 0
test/DynamicMemoryPool/size.cpp

@@ -26,4 +26,23 @@ TEST_CASE("DynamicMemoryPool::size()") {
     memoryPool.clear();
     REQUIRE(0 == memoryPool.size());
   }
+
+  SECTION("Increases after allocSlot()") {
+    memoryPool.allocSlot();
+    REQUIRE(sizeof(Slot) == memoryPool.size());
+
+    memoryPool.allocSlot();
+    REQUIRE(2 * sizeof(Slot) == memoryPool.size());
+  }
+
+  SECTION("Decreases after freeSlot()") {
+    Slot* s1 = memoryPool.allocSlot();
+    Slot* s2 = memoryPool.allocSlot();
+
+    memoryPool.freeSlot(s1);
+    REQUIRE(sizeof(Slot) == memoryPool.size());
+
+    memoryPool.freeSlot(s2);
+    REQUIRE(0 == memoryPool.size());
+  }
 }

+ 62 - 0
test/JsonDocument/DynamicJsonDocument.cpp

@@ -92,4 +92,66 @@ TEST_CASE("DynamicJsonDocument") {
     REQUIRE(json == "{\"hello\":\"world\"}");
     REQUIRE(ddoc.nestingLimit == 42);
   }
+
+  SECTION("memoryUsage()") {
+    typedef ARDUINOJSON_NAMESPACE::Slot Slot;
+
+    SECTION("Increases after adding value to array") {
+      JsonArray arr = doc.to<JsonArray>();
+
+      arr.add(42);
+      REQUIRE(sizeof(Slot) == doc.memoryUsage());
+      arr.add(43);
+      REQUIRE(2 * sizeof(Slot) == doc.memoryUsage());
+    }
+
+    SECTION("Decreases after remove value from array") {
+      JsonArray arr = doc.to<JsonArray>();
+      arr.add(42);
+      arr.add(43);
+
+      arr.remove(1);
+      REQUIRE(sizeof(Slot) == doc.memoryUsage());
+      arr.remove(0);
+      REQUIRE(0 == doc.memoryUsage());
+    }
+
+    SECTION("Increases after adding value to object") {
+      JsonObject obj = doc.to<JsonObject>();
+
+      obj["a"] = 1;
+      REQUIRE(sizeof(Slot) == doc.memoryUsage());
+      obj["b"] = 2;
+      REQUIRE(2 * sizeof(Slot) == doc.memoryUsage());
+    }
+
+    SECTION("Decreases after removing value from object") {
+      JsonObject obj = doc.to<JsonObject>();
+      obj["a"] = 1;
+      obj["b"] = 2;
+
+      obj.remove("a");
+      REQUIRE(sizeof(Slot) == doc.memoryUsage());
+      obj.remove("b");
+      REQUIRE(0 == doc.memoryUsage());
+    }
+
+    SECTION("Decreases after removing nested object from array") {
+      JsonArray arr = doc.to<JsonArray>();
+      JsonObject obj = arr.createNestedObject();
+      obj["hello"] = "world";
+
+      arr.remove(0);
+      REQUIRE(0 == doc.memoryUsage());
+    }
+
+    SECTION("Decreases after removing nested array from object") {
+      JsonObject obj = doc.to<JsonObject>();
+      JsonArray arr = obj.createNestedArray("hello");
+      arr.add("world");
+
+      obj.remove("hello");
+      REQUIRE(0 == doc.memoryUsage());
+    }
+  }
 }