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

This commit is contained in:
Benoit Blanchon
2018-10-19 19:40:21 +02:00
parent ae089dcff7
commit d8d939660b
15 changed files with 174 additions and 37 deletions

View File

@ -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)
-----------

View File

@ -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) {

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

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

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}
}