Bladeren bron

`MemoryPool` calls the `Allocator` directly

Benoit Blanchon 2 jaren geleden
bovenliggende
commit
5faa3df43f

+ 28 - 0
extras/tests/Helpers/Allocators.hpp

@@ -0,0 +1,28 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2023, Benoit BLANCHON
+// MIT License
+
+#pragma once
+
+#include <ArduinoJson/Memory/Allocator.hpp>
+
+struct FailingAllocator : ArduinoJson::Allocator {
+  static FailingAllocator* instance() {
+    static FailingAllocator allocator;
+    return &allocator;
+  }
+
+ private:
+  FailingAllocator() = default;
+  ~FailingAllocator() = default;
+
+  void* allocate(size_t) override {
+    return nullptr;
+  }
+
+  void deallocate(void*) override {}
+
+  void* reallocate(void*, size_t) override {
+    return nullptr;
+  }
+};

+ 5 - 8
extras/tests/MemoryPool/StringCopier.cpp

@@ -8,10 +8,8 @@
 using namespace ArduinoJson::detail;
 
 TEST_CASE("StringCopier") {
-  char buffer[4096];
-
   SECTION("Works when buffer is big enough") {
-    MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(5)));
+    MemoryPool pool(addPadding(JSON_STRING_SIZE(5)));
     StringCopier str(&pool);
 
     str.startString();
@@ -23,7 +21,7 @@ TEST_CASE("StringCopier") {
   }
 
   SECTION("Returns null when too small") {
-    MemoryPool pool(buffer, sizeof(void*));
+    MemoryPool pool(sizeof(void*));
     StringCopier str(&pool);
 
     str.startString();
@@ -34,7 +32,7 @@ TEST_CASE("StringCopier") {
   }
 
   SECTION("Increases size of memory pool") {
-    MemoryPool pool(buffer, addPadding(JSON_STRING_SIZE(6)));
+    MemoryPool pool(addPadding(JSON_STRING_SIZE(6)));
     StringCopier str(&pool);
 
     str.startString();
@@ -45,7 +43,7 @@ TEST_CASE("StringCopier") {
   }
 
   SECTION("Works when memory pool is 0 bytes") {
-    MemoryPool pool(buffer, 0);
+    MemoryPool pool(0);
     StringCopier str(&pool);
 
     str.startString();
@@ -62,8 +60,7 @@ static const char* addStringToPool(MemoryPool& pool, const char* s) {
 }
 
 TEST_CASE("StringCopier::save() deduplicates strings") {
-  char buffer[4096];
-  MemoryPool pool(buffer, 4096);
+  MemoryPool pool(4096);
 
   SECTION("Basic") {
     const char* s1 = addStringToPool(pool, "hello");

+ 7 - 7
extras/tests/MemoryPool/allocVariant.cpp

@@ -5,13 +5,13 @@
 #include <ArduinoJson/Memory/MemoryPool.hpp>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+
 using namespace ArduinoJson::detail;
 
 TEST_CASE("MemoryPool::allocVariant()") {
-  char buffer[4096];
-
   SECTION("Returns different pointer") {
-    MemoryPool pool(buffer, sizeof(buffer));
+    MemoryPool pool(4096);
 
     VariantSlot* s1 = pool.allocVariant();
     REQUIRE(s1 != 0);
@@ -22,26 +22,26 @@ TEST_CASE("MemoryPool::allocVariant()") {
   }
 
   SECTION("Returns aligned pointers") {
-    MemoryPool pool(buffer, sizeof(buffer));
+    MemoryPool pool(4096);
 
     REQUIRE(isAligned(pool.allocVariant()));
     REQUIRE(isAligned(pool.allocVariant()));
   }
 
   SECTION("Returns zero if capacity is 0") {
-    MemoryPool pool(buffer, 0);
+    MemoryPool pool(0);
 
     REQUIRE(pool.allocVariant() == 0);
   }
 
   SECTION("Returns zero if buffer is null") {
-    MemoryPool pool(0, sizeof(buffer));
+    MemoryPool pool(4096, FailingAllocator::instance());
 
     REQUIRE(pool.allocVariant() == 0);
   }
 
   SECTION("Returns zero if capacity is insufficient") {
-    MemoryPool pool(buffer, sizeof(VariantSlot));
+    MemoryPool pool(sizeof(VariantSlot));
 
     pool.allocVariant();
 

+ 1 - 2
extras/tests/MemoryPool/clear.cpp

@@ -11,8 +11,7 @@ using namespace ArduinoJson::detail;
 static const size_t poolCapacity = 512;
 
 TEST_CASE("MemoryPool::clear()") {
-  char buffer[poolCapacity];
-  MemoryPool pool(buffer, sizeof(buffer));
+  MemoryPool pool(poolCapacity);
 
   SECTION("Discards allocated variants") {
     pool.allocVariant();

+ 5 - 4
extras/tests/MemoryPool/saveString.cpp

@@ -6,6 +6,8 @@
 #include <ArduinoJson/Strings/StringAdapters.hpp>
 #include <catch.hpp>
 
+#include "Allocators.hpp"
+
 using namespace ArduinoJson::detail;
 
 static const char* saveString(MemoryPool& pool, const char* s) {
@@ -17,8 +19,7 @@ static const char* saveString(MemoryPool& pool, const char* s, size_t n) {
 }
 
 TEST_CASE("MemoryPool::saveString()") {
-  char buffer[32];
-  MemoryPool pool(buffer, 32);
+  MemoryPool pool(32);
 
   SECTION("Duplicates different strings") {
     const char* a = saveString(pool, "hello");
@@ -72,12 +73,12 @@ TEST_CASE("MemoryPool::saveString()") {
   }
 
   SECTION("Returns NULL when buffer is NULL") {
-    MemoryPool pool2(0, 32);
+    MemoryPool pool2(32, FailingAllocator::instance());
     REQUIRE(0 == saveString(pool2, "a"));
   }
 
   SECTION("Returns NULL when capacity is 0") {
-    MemoryPool pool2(buffer, 0);
+    MemoryPool pool2(0);
     REQUIRE(0 == saveString(pool2, "a"));
   }
 

+ 3 - 5
extras/tests/MemoryPool/size.cpp

@@ -8,22 +8,20 @@
 using namespace ArduinoJson::detail;
 
 TEST_CASE("MemoryPool::capacity()") {
-  char buffer[4096];
   const size_t capacity = 64;
-  MemoryPool pool(buffer, capacity);
+  MemoryPool pool(capacity);
   REQUIRE(capacity == pool.capacity());
 }
 
 TEST_CASE("MemoryPool::size()") {
-  char buffer[4096];
-  MemoryPool pool(buffer, sizeof(buffer));
+  MemoryPool pool(4096);
 
   SECTION("Initial size is 0") {
     REQUIRE(0 == pool.size());
   }
 
   SECTION("Doesn't grow when memory pool is full") {
-    const size_t variantCount = sizeof(buffer) / sizeof(VariantSlot);
+    const size_t variantCount = pool.capacity() / sizeof(VariantSlot);
 
     for (size_t i = 0; i < variantCount; i++)
       pool.allocVariant();

+ 1 - 2
extras/tests/Misc/Utf8.cpp

@@ -10,8 +10,7 @@
 using namespace ArduinoJson::detail;
 
 static void testCodepoint(uint32_t codepoint, std::string expected) {
-  char buffer[4096];
-  MemoryPool pool(buffer, 4096);
+  MemoryPool pool(4096);
   StringCopier str(&pool);
   str.startString();
 

+ 12 - 48
src/ArduinoJson/Document/JsonDocument.hpp

@@ -9,6 +9,7 @@
 #include <ArduinoJson/Memory/MemoryPool.hpp>
 #include <ArduinoJson/Object/JsonObject.hpp>
 #include <ArduinoJson/Object/MemberProxy.hpp>
+#include <ArduinoJson/Polyfills/utility.hpp>
 #include <ArduinoJson/Strings/StoragePolicy.hpp>
 #include <ArduinoJson/Variant/JsonVariantConst.hpp>
 #include <ArduinoJson/Variant/VariantTo.hpp>
@@ -23,16 +24,16 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
  public:
   explicit JsonDocument(size_t capa,
                         Allocator* alloc = detail::DefaultAllocator::instance())
-      : _allocator(alloc), _pool(allocPool(capa)) {}
+      : _pool(capa, alloc) {}
 
   // Copy-constructor
   JsonDocument(const JsonDocument& src)
-      : JsonDocument(src.capacity(), src._allocator) {
+      : JsonDocument(src.capacity(), src.allocator()) {
     set(src);
   }
 
   // Move-constructor
-  JsonDocument(JsonDocument&& src) : _allocator(src._allocator), _pool(0, 0) {
+  JsonDocument(JsonDocument&& src) : _pool(0, src.allocator()) {
     // TODO: use the copy and swap idiom
     moveAssignFrom(src);
   }
@@ -57,10 +58,6 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
     set(src);
   }
 
-  ~JsonDocument() {
-    freePool();
-  }
-
   JsonDocument& operator=(const JsonDocument& src) {
     // TODO: use the copy and swap idiom
     copyAssignFrom(src);
@@ -77,26 +74,19 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
   JsonDocument& operator=(const T& src) {
     size_t requiredSize = src.memoryUsage();
     if (requiredSize > capacity())
-      reallocPool(requiredSize);
+      _pool.reallocPool(requiredSize);
     set(src);
     return *this;
   }
 
+  Allocator* allocator() const {
+    return _pool.allocator();
+  }
+
   // Reduces the capacity of the memory pool to match the current usage.
   // https://arduinojson.org/v6/api/JsonDocument/shrinktofit/
   void shrinkToFit() {
-    ptrdiff_t bytes_reclaimed = _pool.squash();
-    if (bytes_reclaimed == 0)
-      return;
-
-    void* old_ptr = _pool.buffer();
-    void* new_ptr = _allocator->reallocate(old_ptr, _pool.capacity());
-
-    ptrdiff_t ptr_offset =
-        static_cast<char*>(new_ptr) - static_cast<char*>(old_ptr);
-
-    _pool.movePointers(ptr_offset);
-    _data.movePointers(ptr_offset, ptr_offset - bytes_reclaimed);
+    _pool.shrinkToFit(_data);
   }
 
   // Reclaims the memory leaked when removing and replacing values.
@@ -365,10 +355,6 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
   }
 
  private:
-  void replacePool(detail::MemoryPool pool) {
-    _pool = pool;
-  }
-
   JsonVariant getVariant() {
     return JsonVariant(&_pool, &_data);
   }
@@ -377,36 +363,15 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
     return JsonVariantConst(&_data);
   }
 
-  detail::MemoryPool allocPool(size_t requiredSize) {
-    size_t capa = detail::addPadding(requiredSize);
-    return {reinterpret_cast<char*>(_allocator->allocate(capa)), capa};
-  }
-
-  void reallocPool(size_t requiredSize) {
-    size_t capa = detail::addPadding(requiredSize);
-    if (capa == _pool.capacity())
-      return;
-    freePool();
-    replacePool(allocPool(detail::addPadding(requiredSize)));
-  }
-
-  void freePool() {
-    auto p = getPool()->buffer();
-    if (p)
-      _allocator->deallocate(p);
-  }
-
   void copyAssignFrom(const JsonDocument& src) {
-    reallocPool(src.capacity());
+    _pool.reallocPool(src.capacity());
     set(src);
   }
 
   void moveAssignFrom(JsonDocument& src) {
-    freePool();
     _data = src._data;
-    _pool = src._pool;
     src._data.setNull();
-    src._pool = {0, 0};
+    _pool = move(src._pool);
   }
 
   detail::MemoryPool* getPool() {
@@ -425,7 +390,6 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
     return &_data;
   }
 
-  Allocator* _allocator;
   detail::MemoryPool _pool;
   detail::VariantData _data;
 };

+ 64 - 10
src/ArduinoJson/Memory/MemoryPool.hpp

@@ -5,6 +5,7 @@
 #pragma once
 
 #include <ArduinoJson/Memory/Alignment.hpp>
+#include <ArduinoJson/Memory/Allocator.hpp>
 #include <ArduinoJson/Polyfills/assert.hpp>
 #include <ArduinoJson/Polyfills/mpl/max.hpp>
 #include <ArduinoJson/Strings/StringAdapters.hpp>
@@ -36,15 +37,42 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
 
 class MemoryPool {
  public:
-  MemoryPool(char* buf, size_t capa)
-      : _begin(buf),
-        _left(buf),
-        _right(buf ? buf + capa : 0),
-        _end(buf ? buf + capa : 0),
-        _overflowed(false) {
-    ARDUINOJSON_ASSERT(isAligned(_begin));
-    ARDUINOJSON_ASSERT(isAligned(_right));
-    ARDUINOJSON_ASSERT(isAligned(_end));
+  MemoryPool(size_t capa, Allocator* allocator = DefaultAllocator::instance())
+      : _allocator(allocator), _overflowed(false) {
+    allocPool(addPadding(capa));
+  }
+
+  ~MemoryPool() {
+    if (_begin)
+      _allocator->deallocate(_begin);
+  }
+
+  MemoryPool(const MemoryPool&) = delete;
+  MemoryPool& operator=(const MemoryPool& src) = delete;
+
+  MemoryPool& operator=(MemoryPool&& src) {
+    if (_begin)
+      _allocator->deallocate(_begin);
+    _allocator = src._allocator;
+    _begin = src._begin;
+    _end = src._end;
+    _left = src._left;
+    _right = src._right;
+    _overflowed = src._overflowed;
+    src._begin = src._end = src._left = src._right = nullptr;
+    return *this;
+  }
+
+  Allocator* allocator() const {
+    return _allocator;
+  }
+
+  void reallocPool(size_t requiredSize) {
+    size_t capa = addPadding(requiredSize);
+    if (capa == capacity())
+      return;
+    _allocator->deallocate(_begin);
+    allocPool(requiredSize);
   }
 
   void* buffer() {
@@ -132,6 +160,23 @@ class MemoryPool {
     return p;
   }
 
+  void shrinkToFit(VariantData& variant) {
+    ptrdiff_t bytes_reclaimed = squash();
+    if (bytes_reclaimed == 0)
+      return;
+
+    void* old_ptr = _begin;
+    void* new_ptr = _allocator->reallocate(old_ptr, capacity());
+
+    ptrdiff_t ptr_offset =
+        static_cast<char*>(new_ptr) - static_cast<char*>(old_ptr);
+
+    movePointers(ptr_offset);
+    reinterpret_cast<VariantSlot&>(variant).movePointers(
+        ptr_offset, ptr_offset - bytes_reclaimed);
+  }
+
+ private:
   // Squash the free space between strings and variants
   //
   // _begin                    _end
@@ -166,7 +211,6 @@ class MemoryPool {
     _end += offset;
   }
 
- private:
   void checkInvariants() {
     ARDUINOJSON_ASSERT(_begin <= _left);
     ARDUINOJSON_ASSERT(_left <= _right);
@@ -215,6 +259,16 @@ class MemoryPool {
     return _right;
   }
 
+  void allocPool(size_t capa) {
+    auto buf = capa ? reinterpret_cast<char*>(_allocator->allocate(capa)) : 0;
+    _begin = _left = buf;
+    _end = _right = buf ? buf + capa : 0;
+    ARDUINOJSON_ASSERT(isAligned(_begin));
+    ARDUINOJSON_ASSERT(isAligned(_right));
+    ARDUINOJSON_ASSERT(isAligned(_end));
+  }
+
+  Allocator* _allocator;
   char *_begin, *_left, *_right, *_end;
   bool _overflowed;
 };

+ 5 - 0
src/ArduinoJson/Polyfills/utility.hpp

@@ -13,4 +13,9 @@ T&& forward(typename remove_reference<T>::type& t) noexcept {
   return static_cast<T&&>(t);
 }
 
+template <class T>
+typename remove_reference<T>::type&& move(T&& t) {
+  return static_cast<typename remove_reference<T>::type&&>(t);
+}
+
 ARDUINOJSON_END_PRIVATE_NAMESPACE