diff --git a/modules/common/chowdsp_data_structures/Structures/chowdsp_DestructiblePointer.h b/modules/common/chowdsp_data_structures/Structures/chowdsp_DestructiblePointer.h new file mode 100644 index 000000000..e9ddebfc3 --- /dev/null +++ b/modules/common/chowdsp_data_structures/Structures/chowdsp_DestructiblePointer.h @@ -0,0 +1,95 @@ +#pragma once + +namespace chowdsp +{ +/** + * A wrapper for a pointer that will call the destructor + * for its underlying object when it goes out of scope, + * but WILL NOT free the underlying memory. + * + * This may be useful if the pointer is referring to some + * stack memory, or if the memory will be freed elsewhere, + * e.g. as part of a memory arena. + */ +template +struct DestructiblePointer +{ + DestructiblePointer() = default; + DestructiblePointer (T* ptr) : pointer (ptr) {} // NOLINT(google-explicit-constructor) + DestructiblePointer (const DestructiblePointer&) = delete; + DestructiblePointer& operator= (const DestructiblePointer&) = delete; + DestructiblePointer (DestructiblePointer&& other) noexcept : pointer (other.pointer) + { + other.pointer = nullptr; + } + DestructiblePointer& operator= (DestructiblePointer&& other) noexcept + { + if (this != &other) + { + destroy(); + pointer = other.pointer; + other.pointer = nullptr; + } + return *this; + } + + ~DestructiblePointer() + { + destroy(); + } + + /** + * Calls the object destructor, and resets the pointer to null. + * Does nothing if the pointer is already null. + */ + void destroy() + { + if (pointer != nullptr) + pointer->~T(); + pointer = nullptr; + } + + /** Releases the pointer without destroying. */ + T* release() + { + auto* ptr = pointer; + pointer = nullptr; + return pointer; + } + + [[nodiscard]] T* get() { return pointer; } + [[nodiscard]] const T* get() const { return pointer; } + + [[nodiscard]] T* operator->() { return get(); } + [[nodiscard]] const T* operator->() const { return get(); } + [[nodiscard]] T& operator*() { return *get(); } + [[nodiscard]] const T& operator*() const { return *get(); } + +private: + T* pointer = nullptr; +}; + +template +bool operator== (const DestructiblePointer& p1, std::nullptr_t) +{ + return p1.get() == nullptr; +} + +template +bool operator!= (const DestructiblePointer& p1, std::nullptr_t) +{ + return p1.get() != nullptr; +} + +template +bool operator== (std::nullptr_t, const DestructiblePointer& p2) +{ + return nullptr == p2.get(); +} + +template +bool operator!= (std::nullptr_t, const DestructiblePointer& p2) +{ + return nullptr != p2.get(); +} +} // namespace chowdsp diff --git a/modules/common/chowdsp_data_structures/Structures/chowdsp_LocalPointer.h b/modules/common/chowdsp_data_structures/Structures/chowdsp_LocalPointer.h index a39172667..2836e9dc7 100644 --- a/modules/common/chowdsp_data_structures/Structures/chowdsp_LocalPointer.h +++ b/modules/common/chowdsp_data_structures/Structures/chowdsp_LocalPointer.h @@ -70,7 +70,7 @@ class LocalPointer reset(); pointer = new (data.data()) C (std::forward (args)...); - return reinterpret_cast (pointer); + return reinterpret_cast (pointer.get()); } /** @@ -81,21 +81,13 @@ class LocalPointer { if (pointer != nullptr) { - // Why are we calling the destructor explicitly here? - // For some types it won't matter since we're zero-ing - // the local storage anyway, but if the type has something - // like a std::vector or std::unique_ptr inside it, or has - // some custom logic in its destructor, then we need to make - // sure that stuff gets taken care of before destroying the object. - pointer->~T(); - - pointer = nullptr; + pointer.destroy(); std::fill (std::begin (data), std::end (data), std::byte {}); } } - [[nodiscard]] T* get() { return pointer; } - [[nodiscard]] const T* get() const { return pointer; } + [[nodiscard]] T* get() { return pointer.get(); } + [[nodiscard]] const T* get() const { return pointer.get(); } [[nodiscard]] T* operator->() { return get(); } [[nodiscard]] const T* operator->() const { return get(); } @@ -104,7 +96,7 @@ class LocalPointer private: alignas (Alignment) std::array data {}; - T* pointer = nullptr; + DestructiblePointer pointer = nullptr; }; template diff --git a/modules/common/chowdsp_data_structures/Structures/chowdsp_SmallVector.h b/modules/common/chowdsp_data_structures/Structures/chowdsp_SmallVector.h index 40c86dd0a..761b25774 100644 --- a/modules/common/chowdsp_data_structures/Structures/chowdsp_SmallVector.h +++ b/modules/common/chowdsp_data_structures/Structures/chowdsp_SmallVector.h @@ -260,7 +260,15 @@ class SmallVector void clear() noexcept { if (usingArray) + { + internal_needs_destruction = make_array (true); + for (size_t i = 0; i < internal_array_size_used; ++i) + { + internal_array[i].~T(); + internal_needs_destruction[i] = false; + } internal_array_size_used = 0; + } internal_vector.clear(); } @@ -443,7 +451,8 @@ class SmallVector { if (internal_array_size_used + 1 <= head_size) { - internal_array[internal_array_size_used].~T(); + if (internal_needs_destruction[internal_array_size_used]) + internal_array[internal_array_size_used].~T(); new (&internal_array[internal_array_size_used]) T (args...); internal_array_size_used++; return internal_array[internal_array_size_used - 1]; @@ -517,6 +526,7 @@ class SmallVector std::array internal_array {}; size_t internal_array_size_used = 0; + std::array internal_needs_destruction = make_array (true); std::vector internal_vector {}; diff --git a/modules/common/chowdsp_data_structures/chowdsp_data_structures.h b/modules/common/chowdsp_data_structures/chowdsp_data_structures.h index 08a1b82a0..c983e5e4d 100644 --- a/modules/common/chowdsp_data_structures/chowdsp_data_structures.h +++ b/modules/common/chowdsp_data_structures/chowdsp_data_structures.h @@ -24,18 +24,19 @@ BEGIN_JUCE_MODULE_DECLARATION #include "third_party/short_alloc.h" +#include "Helpers/chowdsp_ArrayHelpers.h" +#include "Helpers/chowdsp_TupleHelpers.h" +#include "Helpers/chowdsp_VectorHelpers.h" +#include "Helpers/chowdsp_Iterators.h" + #include "Structures/chowdsp_DoubleBuffer.h" +#include "Structures/chowdsp_DestructiblePointer.h" #include "Structures/chowdsp_LocalPointer.h" #include "Structures/chowdsp_OptionalPointer.h" #include "Structures/chowdsp_RawObject.h" #include "Structures/chowdsp_SmallVector.h" #include "Structures/chowdsp_StringLiteral.h" -#include "Helpers/chowdsp_ArrayHelpers.h" -#include "Helpers/chowdsp_TupleHelpers.h" -#include "Helpers/chowdsp_VectorHelpers.h" -#include "Helpers/chowdsp_Iterators.h" - #include "Allocators/chowdsp_ArenaAllocator.h" #include "Allocators/chowdsp_ChainedArenaAllocator.h" diff --git a/modules/gui/chowdsp_gui/Helpers/chowdsp_ComponentArena.h b/modules/gui/chowdsp_gui/Helpers/chowdsp_ComponentArena.h new file mode 100644 index 000000000..59a0dc407 --- /dev/null +++ b/modules/gui/chowdsp_gui/Helpers/chowdsp_ComponentArena.h @@ -0,0 +1,67 @@ +#pragma once + +namespace chowdsp +{ +/** + * A memory arena designed to be used with juce::Component objects + * (although other types can be allocated into the arena as well). + */ +template +class ComponentArena +{ +public: + ComponentArena() = default; + ComponentArena (const ComponentArena&) = delete; + ComponentArena& operator= (const ComponentArena&) = delete; + ComponentArena (ComponentArena&&) noexcept = delete; + ComponentArena& operator= (ComponentArena&&) noexcept = delete; + + ~ComponentArena() + { + clear_all(); + } + + /** Allocates a single object into the arena. */ + template + T* allocate (Args&&... args) + { + auto* bytes = allocator.allocate_bytes (sizeof (T), alignof (T)); + auto* new_component = new (bytes) T { std::forward (args)... }; + + if constexpr (std::is_base_of_v) + component_list.emplace_back (new_component); + + return new_component; + } + + /** Allocates multiple objects into the arena. */ + template + nonstd::span allocate_n (size_t n, Args&&... args) + { + auto* bytes = allocator.allocate_bytes (sizeof (T) * n, alignof (T)); + auto span = nonstd::span { reinterpret_cast (bytes), n }; + for (auto& ptr : span) + { + auto* new_component = new (&ptr) T { std::forward (args)... }; + if constexpr (std::is_base_of_v) + component_list.emplace_back (new_component); + } + return span; + } + + /** + * Reclaims the arena memory, and destroys any components + * that have been allocated into the arena. + */ + void clear_all() + { + component_list.clear(); + allocator.clear(); + } + + ChainedArenaAllocator> allocator {}; + +private: + SmallVector, approx_component_count> component_list {}; +}; +} // namespace chowdsp diff --git a/modules/gui/chowdsp_gui/chowdsp_gui.h b/modules/gui/chowdsp_gui/chowdsp_gui.h index 3dc75c85d..3eb40575a 100644 --- a/modules/gui/chowdsp_gui/chowdsp_gui.h +++ b/modules/gui/chowdsp_gui/chowdsp_gui.h @@ -8,7 +8,7 @@ version: 2.1.0 name: ChowDSP GUI Utilities description: Commonly used GUI utilities for ChowDSP plugins - dependencies: juce_core, juce_audio_processors, juce_gui_basics, chowdsp_core + dependencies: juce_core, juce_audio_processors, juce_gui_basics, chowdsp_core, chowdsp_data_structures website: https://ccrma.stanford.edu/~jatin/chowdsp license: GPLv3 @@ -33,6 +33,7 @@ #include #include +#include #if JUCE_MODULE_AVAILABLE_juce_opengl && CHOWDSP_ENABLE_OPEN_GL_CONTEXT #define CHOWDSP_OPENGL_IS_AVAILABLE 1 @@ -45,6 +46,7 @@ #include "LookAndFeel/chowdsp_ChowLNF.h" +#include "Helpers/chowdsp_ComponentArena.h" #include "Helpers/chowdsp_LongPressActionHelper.h" #include "Helpers/chowdsp_PopupMenuHelper.h" #include "Helpers/chowdsp_OpenGLHelper.h" diff --git a/tests/common_tests/chowdsp_data_structures_test/SmallVectorTest.cpp b/tests/common_tests/chowdsp_data_structures_test/SmallVectorTest.cpp index 5a7eaca6c..1818da8b0 100644 --- a/tests/common_tests/chowdsp_data_structures_test/SmallVectorTest.cpp +++ b/tests/common_tests/chowdsp_data_structures_test/SmallVectorTest.cpp @@ -276,6 +276,30 @@ TEST_CASE ("Small Vector Test", "[common][data-structures]") REQUIRE (vec.empty()); } + SECTION ("Clear w/ Destruction Test") + { + chowdsp::SmallVector, 4> vec { + std::make_shared (1), + std::make_shared (2), + std::make_shared (3), + }; + vec.resize (3); + REQUIRE (*vec[2] == 3); + + vec.clear(); + REQUIRE (vec.empty()); + + vec.emplace_back (std::make_shared (0)); + vec.emplace_back (std::make_shared (1)); + vec.emplace_back (std::make_shared (2)); + vec.emplace_back (std::make_shared (3)); + vec.emplace_back (std::make_shared (4)); + REQUIRE (vec.size() == 5); + + vec.clear(); + REQUIRE (vec.empty()); + } + SECTION ("Insert/Emplace Single Value Test") { chowdsp::SmallVector vec { 1.0, 2.0 }; @@ -409,7 +433,6 @@ TEST_CASE ("Small Vector Test", "[common][data-structures]") vec.erase (vec.begin() + 1, vec.begin() + 3); REQUIRE (vec.size() == 3); REQUIRE (vec[0] == 1); - ; REQUIRE (vec[1] == 4); REQUIRE (vec[2] == 5); diff --git a/tests/gui_tests/chowdsp_gui_test/CMakeLists.txt b/tests/gui_tests/chowdsp_gui_test/CMakeLists.txt index 5d52347e9..5b855ba9b 100644 --- a/tests/gui_tests/chowdsp_gui_test/CMakeLists.txt +++ b/tests/gui_tests/chowdsp_gui_test/CMakeLists.txt @@ -6,6 +6,7 @@ target_sources(chowdsp_gui_test PRIVATE PopupMenuHelperTest.cpp WindowInPluginTest.cpp HostContextProviderTest.cpp + ComponentArenaTest.cpp # OversamplingMenuTest.cpp # PresetsCompTest.cpp diff --git a/tests/gui_tests/chowdsp_gui_test/ComponentArenaTest.cpp b/tests/gui_tests/chowdsp_gui_test/ComponentArenaTest.cpp new file mode 100644 index 000000000..1641ae9b1 --- /dev/null +++ b/tests/gui_tests/chowdsp_gui_test/ComponentArenaTest.cpp @@ -0,0 +1,44 @@ +#include +#include + +TEST_CASE ("Component Arena Test", "[gui][data-structures]") +{ + struct TestComponent : juce::Component + { + int& destructor_counter; + explicit TestComponent (int& dest_counter) : destructor_counter (dest_counter) {} + ~TestComponent() override { ++destructor_counter; } + }; + + struct NotAComponent + { + int& destructor_counter; + explicit NotAComponent (int& dest_counter) : destructor_counter (dest_counter) {} + ~NotAComponent() { ++destructor_counter; } + }; + + int destructor_counter = 0; + chowdsp::ComponentArena<> arena {}; + + SECTION ("Only allocate components") + { + arena.allocate (destructor_counter); + arena.allocate_n (4, destructor_counter); + + REQUIRE (destructor_counter == 0); + arena.clear_all(); + REQUIRE (destructor_counter == 5); + } + + SECTION ("Allocate other types too") + { + arena.allocate (destructor_counter); + arena.allocate (destructor_counter); + arena.allocate_n (4, destructor_counter); + arena.allocate_n (3, destructor_counter); + + REQUIRE (destructor_counter == 0); + arena.clear_all(); + REQUIRE (destructor_counter == 5); + } +}