mirror of
				https://github.com/eledio-devices/thirdparty-ArduinoJson.git
				synced 2025-10-31 16:14:11 +01:00 
			
		
		
		
	Added BasicJsonDocument::shrinkToFit()
				
					
				
			This commit is contained in:
		| @@ -1,6 +1,11 @@ | |||||||
| ArduinoJson: change log | ArduinoJson: change log | ||||||
| ======================= | ======================= | ||||||
|  |  | ||||||
|  | HEAD | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  | * Added `BasicJsonDocument::shrinkToFit()` | ||||||
|  |  | ||||||
| v6.13.0 (2019-11-01) | v6.13.0 (2019-11-01) | ||||||
| ------- | ------- | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ add_executable(JsonDocumentTests | |||||||
| 	isNull.cpp | 	isNull.cpp | ||||||
| 	nesting.cpp | 	nesting.cpp | ||||||
| 	remove.cpp | 	remove.cpp | ||||||
|  | 	shrinkToFit.cpp | ||||||
| 	size.cpp | 	size.cpp | ||||||
| 	StaticJsonDocument.cpp | 	StaticJsonDocument.cpp | ||||||
| 	subscript.cpp | 	subscript.cpp | ||||||
|   | |||||||
							
								
								
									
										151
									
								
								extras/tests/JsonDocument/shrinkToFit.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								extras/tests/JsonDocument/shrinkToFit.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | // ArduinoJson - arduinojson.org | ||||||
|  | // Copyright Benoit Blanchon 2014-2019 | ||||||
|  | // MIT License | ||||||
|  |  | ||||||
|  | #include <ArduinoJson.h> | ||||||
|  | #include <catch.hpp> | ||||||
|  |  | ||||||
|  | #include <stdlib.h>  // malloc, free | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | using ARDUINOJSON_NAMESPACE::addPadding; | ||||||
|  |  | ||||||
|  | class ArmoredAllocator { | ||||||
|  |  public: | ||||||
|  |   ArmoredAllocator() : _ptr(0), _size(0) {} | ||||||
|  |  | ||||||
|  |   void* allocate(size_t size) { | ||||||
|  |     _ptr = malloc(size); | ||||||
|  |     _size = size; | ||||||
|  |     return _ptr; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void deallocate(void* ptr) { | ||||||
|  |     REQUIRE(ptr == _ptr); | ||||||
|  |     free(ptr); | ||||||
|  |     _ptr = 0; | ||||||
|  |     _size = 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void* reallocate(void* ptr, size_t new_size) { | ||||||
|  |     REQUIRE(ptr == _ptr); | ||||||
|  |     // don't call realloc, instead alloc a new buffer and erase the old one | ||||||
|  |     // this way we make sure we support relocation | ||||||
|  |     void* new_ptr = malloc(new_size); | ||||||
|  |     memcpy(new_ptr, _ptr, std::min(new_size, _size)); | ||||||
|  |     memset(_ptr, '#', _size);  // erase | ||||||
|  |     free(_ptr); | ||||||
|  |     _ptr = new_ptr; | ||||||
|  |     return new_ptr; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   void* _ptr; | ||||||
|  |   size_t _size; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | typedef BasicJsonDocument<ArmoredAllocator> ShrinkToFitTestDocument; | ||||||
|  |  | ||||||
|  | void testShrinkToFit(ShrinkToFitTestDocument& doc, std::string expected_json, | ||||||
|  |                      size_t expected_size) { | ||||||
|  |   doc.shrinkToFit(); | ||||||
|  |  | ||||||
|  |   REQUIRE(doc.capacity() == expected_size); | ||||||
|  |   REQUIRE(doc.memoryUsage() == expected_size); | ||||||
|  |  | ||||||
|  |   std::string json; | ||||||
|  |   serializeJson(doc, json); | ||||||
|  |   REQUIRE(json == expected_json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TEST_CASE("BasicJsonDocument::shrinkToFit()") { | ||||||
|  |   ShrinkToFitTestDocument doc(4096); | ||||||
|  |  | ||||||
|  |   SECTION("null") { | ||||||
|  |     testShrinkToFit(doc, "null", 0); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("empty object") { | ||||||
|  |     deserializeJson(doc, "{}"); | ||||||
|  |     testShrinkToFit(doc, "{}", JSON_OBJECT_SIZE(0)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("empty array") { | ||||||
|  |     deserializeJson(doc, "[]"); | ||||||
|  |     testShrinkToFit(doc, "[]", JSON_ARRAY_SIZE(0)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("linked string") { | ||||||
|  |     doc.set("hello"); | ||||||
|  |     testShrinkToFit(doc, "\"hello\"", 0); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("owned string") { | ||||||
|  |     doc.set(std::string("abcdefg")); | ||||||
|  |     testShrinkToFit(doc, "\"abcdefg\"", 8); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("linked raw") { | ||||||
|  |     doc.set(serialized("[{},123]")); | ||||||
|  |     testShrinkToFit(doc, "[{},123]", 0); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("owned raw") { | ||||||
|  |     doc.set(serialized(std::string("[{},123]"))); | ||||||
|  |     testShrinkToFit(doc, "[{},123]", 8); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("linked key") { | ||||||
|  |     doc["key"] = 42; | ||||||
|  |     testShrinkToFit(doc, "{\"key\":42}", JSON_OBJECT_SIZE(1)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("owned key") { | ||||||
|  |     doc[std::string("abcdefg")] = 42; | ||||||
|  |     testShrinkToFit(doc, "{\"abcdefg\":42}", JSON_OBJECT_SIZE(1) + 8); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("linked string in array") { | ||||||
|  |     doc.add("hello"); | ||||||
|  |     testShrinkToFit(doc, "[\"hello\"]", JSON_ARRAY_SIZE(1)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("owned string in array") { | ||||||
|  |     doc.add(std::string("abcdefg")); | ||||||
|  |     testShrinkToFit(doc, "[\"abcdefg\"]", JSON_ARRAY_SIZE(1) + 8); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("linked string in object") { | ||||||
|  |     doc["key"] = "hello"; | ||||||
|  |     testShrinkToFit(doc, "{\"key\":\"hello\"}", JSON_OBJECT_SIZE(1)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("owned string in object") { | ||||||
|  |     doc["key"] = std::string("abcdefg"); | ||||||
|  |     testShrinkToFit(doc, "{\"key\":\"abcdefg\"}", JSON_ARRAY_SIZE(1) + 8); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   SECTION("unaligned") { | ||||||
|  |     doc.add(std::string("?"));  // two bytes in the string pool | ||||||
|  |     REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 2); | ||||||
|  |  | ||||||
|  |     doc.shrinkToFit(); | ||||||
|  |  | ||||||
|  |     // the new capacity should be padded to align the pointers | ||||||
|  |     REQUIRE(doc.capacity() == JSON_OBJECT_SIZE(1) + sizeof(void*)); | ||||||
|  |     REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 2); | ||||||
|  |     REQUIRE(doc[0] == "?"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TEST_CASE("DynamicJsonDocument::shrinkToFit()") { | ||||||
|  |   DynamicJsonDocument doc(4096); | ||||||
|  |  | ||||||
|  |   deserializeJson(doc, "{\"hello\":[\"world\"]"); | ||||||
|  |  | ||||||
|  |   doc.shrinkToFit(); | ||||||
|  |  | ||||||
|  |   std::string json; | ||||||
|  |   serializeJson(doc, json); | ||||||
|  |   REQUIRE(json == "{\"hello\":[\"world\"]}"); | ||||||
|  | } | ||||||
| @@ -5,6 +5,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <ArduinoJson/Namespace.hpp> | #include <ArduinoJson/Namespace.hpp> | ||||||
|  | #include <ArduinoJson/Polyfills/assert.hpp> | ||||||
|  |  | ||||||
| #include <stddef.h>  // size_t | #include <stddef.h>  // size_t | ||||||
|  |  | ||||||
| @@ -63,6 +64,8 @@ class CollectionData { | |||||||
|   size_t nesting() const; |   size_t nesting() const; | ||||||
|   size_t size() const; |   size_t size() const; | ||||||
|  |  | ||||||
|  |   void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance); | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   VariantSlot *getSlot(size_t index) const; |   VariantSlot *getSlot(size_t index) const; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -160,4 +160,20 @@ inline size_t CollectionData::size() const { | |||||||
|   return slotSize(_head); |   return slotSize(_head); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | template <typename T> | ||||||
|  | inline void movePointer(T*& p, ptrdiff_t offset) { | ||||||
|  |   if (!p) return; | ||||||
|  |   p = reinterpret_cast<T*>( | ||||||
|  |       reinterpret_cast<void*>(reinterpret_cast<char*>(p) + offset)); | ||||||
|  |   ARDUINOJSON_ASSERT(isAligned(p)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline void CollectionData::movePointers(ptrdiff_t stringDistance, | ||||||
|  |                                          ptrdiff_t variantDistance) { | ||||||
|  |   movePointer(_head, variantDistance); | ||||||
|  |   movePointer(_tail, variantDistance); | ||||||
|  |   for (VariantSlot* slot = _head; slot; slot = slot->next()) | ||||||
|  |     slot->movePointers(stringDistance, variantDistance); | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace ARDUINOJSON_NAMESPACE | }  // namespace ARDUINOJSON_NAMESPACE | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ | |||||||
|  |  | ||||||
| namespace ARDUINOJSON_NAMESPACE { | namespace ARDUINOJSON_NAMESPACE { | ||||||
|  |  | ||||||
|  | // Helper to implement the "base-from-member" idiom | ||||||
|  | // (we need to store the allocator before constructing JsonDocument) | ||||||
| template <typename TAllocator> | template <typename TAllocator> | ||||||
| class AllocatorOwner { | class AllocatorOwner { | ||||||
|  protected: |  protected: | ||||||
| @@ -15,12 +17,16 @@ class AllocatorOwner { | |||||||
|   AllocatorOwner(const AllocatorOwner& src) : _allocator(src._allocator) {} |   AllocatorOwner(const AllocatorOwner& src) : _allocator(src._allocator) {} | ||||||
|   AllocatorOwner(TAllocator allocator) : _allocator(allocator) {} |   AllocatorOwner(TAllocator allocator) : _allocator(allocator) {} | ||||||
|  |  | ||||||
|   void* allocate(size_t n) { |   void* allocate(size_t size) { | ||||||
|     return _allocator.allocate(n); |     return _allocator.allocate(size); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void deallocate(void* p) { |   void deallocate(void* ptr) { | ||||||
|     _allocator.deallocate(p); |     _allocator.deallocate(ptr); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void* reallocate(void* ptr, size_t new_size) { | ||||||
|  |     return _allocator.reallocate(ptr, new_size); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
| @@ -69,6 +75,20 @@ class BasicJsonDocument : AllocatorOwner<TAllocator>, public JsonDocument { | |||||||
|     return *this; |     return *this; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void shrinkToFit() { | ||||||
|  |     ptrdiff_t bytes_reclaimed = _pool.squash(); | ||||||
|  |     if (bytes_reclaimed == 0) return; | ||||||
|  |  | ||||||
|  |     void* old_ptr = _pool.buffer(); | ||||||
|  |     void* new_ptr = this->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); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   MemoryPool allocPool(size_t requiredSize) { |   MemoryPool allocPool(size_t requiredSize) { | ||||||
|     size_t capa = addPadding(requiredSize); |     size_t capa = addPadding(requiredSize); | ||||||
|   | |||||||
| @@ -11,12 +11,16 @@ | |||||||
| namespace ARDUINOJSON_NAMESPACE { | namespace ARDUINOJSON_NAMESPACE { | ||||||
|  |  | ||||||
| struct DefaultAllocator { | struct DefaultAllocator { | ||||||
|   void* allocate(size_t n) { |   void* allocate(size_t size) { | ||||||
|     return malloc(n); |     return malloc(size); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void deallocate(void* p) { |   void deallocate(void* ptr) { | ||||||
|     free(p); |     free(ptr); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void* reallocate(void* ptr, size_t new_size) { | ||||||
|  |     return realloc(ptr, new_size); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -301,7 +301,6 @@ class JsonDocument : public Visitable { | |||||||
|     _pool = pool; |     _pool = pool; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   VariantRef getVariant() { |   VariantRef getVariant() { | ||||||
|     return VariantRef(&_pool, &_data); |     return VariantRef(&_pool, &_data); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -21,6 +21,12 @@ inline size_t addPadding(size_t bytes) { | |||||||
|   return (bytes + mask) & ~mask; |   return (bytes + mask) & ~mask; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | template <typename T> | ||||||
|  | inline T *addPadding(T *p) { | ||||||
|  |   size_t address = addPadding(reinterpret_cast<size_t>(p)); | ||||||
|  |   return reinterpret_cast<T *>(address); | ||||||
|  | } | ||||||
|  |  | ||||||
| template <size_t bytes> | template <size_t bytes> | ||||||
| struct AddPadding { | struct AddPadding { | ||||||
|   static const size_t mask = sizeof(void *) - 1; |   static const size_t mask = sizeof(void *) - 1; | ||||||
|   | |||||||
| @@ -10,13 +10,15 @@ | |||||||
| #include <ArduinoJson/Polyfills/mpl/max.hpp> | #include <ArduinoJson/Polyfills/mpl/max.hpp> | ||||||
| #include <ArduinoJson/Variant/VariantSlot.hpp> | #include <ArduinoJson/Variant/VariantSlot.hpp> | ||||||
|  |  | ||||||
|  | #include <string.h>  // memmove | ||||||
|  |  | ||||||
| namespace ARDUINOJSON_NAMESPACE { | namespace ARDUINOJSON_NAMESPACE { | ||||||
|  |  | ||||||
| // _begin                                _end | // _begin                                   _end | ||||||
| // v                                        v | // v                                           v | ||||||
| // +-------------+--------------+-----------+ | // +-------------+--------------+--------------+ | ||||||
| // | strings...  |   (free)     |  ...slots | | // | strings...  |   (free)     |  ...variants | | ||||||
| // +-------------+--------------+-----------+ | // +-------------+--------------+--------------+ | ||||||
| //               ^              ^ | //               ^              ^ | ||||||
| //             _left          _right | //             _left          _right | ||||||
|  |  | ||||||
| @@ -101,6 +103,39 @@ class MemoryPool { | |||||||
|     return p; |     return p; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Squash the free space between strings and variants | ||||||
|  |   // | ||||||
|  |   // _begin                    _end | ||||||
|  |   // v                            v | ||||||
|  |   // +-------------+--------------+ | ||||||
|  |   // | strings...  |  ...variants | | ||||||
|  |   // +-------------+--------------+ | ||||||
|  |   //               ^ | ||||||
|  |   //          _left _right | ||||||
|  |   // | ||||||
|  |   // This funcion is called before a realloc. | ||||||
|  |   ptrdiff_t squash() { | ||||||
|  |     char* new_right = addPadding(_left); | ||||||
|  |     if (new_right >= _right) return 0; | ||||||
|  |  | ||||||
|  |     size_t right_size = static_cast<size_t>(_end - _right); | ||||||
|  |     memmove(new_right, _right, right_size); | ||||||
|  |  | ||||||
|  |     ptrdiff_t bytes_reclaimed = _right - new_right; | ||||||
|  |     _right = new_right; | ||||||
|  |     _end = new_right + right_size; | ||||||
|  |     return bytes_reclaimed; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Move all pointers together | ||||||
|  |   // This funcion is called after a realloc. | ||||||
|  |   void movePointers(ptrdiff_t offset) { | ||||||
|  |     _begin += offset; | ||||||
|  |     _left += offset; | ||||||
|  |     _right += offset; | ||||||
|  |     _end += offset; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   StringSlot* allocStringSlot() { |   StringSlot* allocStringSlot() { | ||||||
|     return allocRight<StringSlot>(); |     return allocRight<StringSlot>(); | ||||||
|   | |||||||
| @@ -16,14 +16,14 @@ namespace ARDUINOJSON_NAMESPACE { | |||||||
| enum { | enum { | ||||||
|   VALUE_MASK = 0x7F, |   VALUE_MASK = 0x7F, | ||||||
|  |  | ||||||
|   OWNERSHIP_BIT = 0x01, |   VALUE_IS_OWNED = 0x01, | ||||||
|   VALUE_IS_NULL = 0, |   VALUE_IS_NULL = 0, | ||||||
|   VALUE_IS_LINKED_RAW = 0x02, |   VALUE_IS_LINKED_RAW = 0x02, | ||||||
|   VALUE_IS_OWNED_RAW = 0x03, |   VALUE_IS_OWNED_RAW = 0x03, | ||||||
|   VALUE_IS_LINKED_STRING = 0x04, |   VALUE_IS_LINKED_STRING = 0x04, | ||||||
|   VALUE_IS_OWNED_STRING = 0x05, |   VALUE_IS_OWNED_STRING = 0x05, | ||||||
|  |  | ||||||
|   // CAUTION: no OWNERSHIP_BIT below |   // CAUTION: no VALUE_IS_OWNED below | ||||||
|   VALUE_IS_BOOLEAN = 0x06, |   VALUE_IS_BOOLEAN = 0x06, | ||||||
|   VALUE_IS_POSITIVE_INTEGER = 0x08, |   VALUE_IS_POSITIVE_INTEGER = 0x08, | ||||||
|   VALUE_IS_NEGATIVE_INTEGER = 0x0A, |   VALUE_IS_NEGATIVE_INTEGER = 0x0A, | ||||||
|   | |||||||
| @@ -103,7 +103,7 @@ class VariantData { | |||||||
|  |  | ||||||
|   bool equals(const VariantData &other) const { |   bool equals(const VariantData &other) const { | ||||||
|     // Check that variant have the same type, but ignore string ownership |     // Check that variant have the same type, but ignore string ownership | ||||||
|     if ((type() | OWNERSHIP_BIT) != (other.type() | OWNERSHIP_BIT)) |     if ((type() | VALUE_IS_OWNED) != (other.type() | VALUE_IS_OWNED)) | ||||||
|       return false; |       return false; | ||||||
|  |  | ||||||
|     switch (type()) { |     switch (type()) { | ||||||
| @@ -352,6 +352,12 @@ class VariantData { | |||||||
|     return _content.asCollection.add(key, pool); |     return _content.asCollection.add(key, pool); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance) { | ||||||
|  |     if (_flags & VALUE_IS_OWNED) _content.asString += stringDistance; | ||||||
|  |     if (_flags & COLLECTION_MASK) | ||||||
|  |       _content.asCollection.movePointers(stringDistance, variantDistance); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   uint8_t type() const { |   uint8_t type() const { | ||||||
|     return _flags & VALUE_MASK; |     return _flags & VALUE_MASK; | ||||||
|   | |||||||
| @@ -14,8 +14,6 @@ namespace ARDUINOJSON_NAMESPACE { | |||||||
|  |  | ||||||
| typedef conditional<sizeof(void*) <= 2, int8_t, int16_t>::type VariantSlotDiff; | typedef conditional<sizeof(void*) <= 2, int8_t, int16_t>::type VariantSlotDiff; | ||||||
|  |  | ||||||
| class VairantData; |  | ||||||
|  |  | ||||||
| class VariantSlot { | class VariantSlot { | ||||||
|   // CAUTION: same layout as VariantData |   // CAUTION: same layout as VariantData | ||||||
|   // we cannot use composition because it adds padding |   // we cannot use composition because it adds padding | ||||||
| @@ -93,6 +91,13 @@ class VariantSlot { | |||||||
|     _flags = 0; |     _flags = 0; | ||||||
|     _key = 0; |     _key = 0; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void movePointers(ptrdiff_t stringDistance, ptrdiff_t variantDistance) { | ||||||
|  |     if (_flags & KEY_IS_OWNED) _key += stringDistance; | ||||||
|  |     if (_flags & VALUE_IS_OWNED) _content.asString += stringDistance; | ||||||
|  |     if (_flags & COLLECTION_MASK) | ||||||
|  |       _content.asCollection.movePointers(stringDistance, variantDistance); | ||||||
|  |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace ARDUINOJSON_NAMESPACE | }  // namespace ARDUINOJSON_NAMESPACE | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user