-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ComponentArena for creating/storing JUCE components (#517)
* Setting up a Component Arenal * SmallVector fixing when we call destructor * Book-keeping for manual destruction * Apply clang-format * Silence clang-tidy warning --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
f7e1f27
commit 8843e52
Showing
9 changed files
with
256 additions
and
21 deletions.
There are no files selected for viewing
95 changes: 95 additions & 0 deletions
95
modules/common/chowdsp_data_structures/Structures/chowdsp_DestructiblePointer.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <typename T> | ||
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 <typename T> | ||
bool operator== (const DestructiblePointer<T>& p1, std::nullptr_t) | ||
{ | ||
return p1.get() == nullptr; | ||
} | ||
|
||
template <typename T> | ||
bool operator!= (const DestructiblePointer<T>& p1, std::nullptr_t) | ||
{ | ||
return p1.get() != nullptr; | ||
} | ||
|
||
template <typename T> | ||
bool operator== (std::nullptr_t, const DestructiblePointer<T>& p2) | ||
{ | ||
return nullptr == p2.get(); | ||
} | ||
|
||
template <typename T> | ||
bool operator!= (std::nullptr_t, const DestructiblePointer<T>& p2) | ||
{ | ||
return nullptr != p2.get(); | ||
} | ||
} // namespace chowdsp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <size_t arena_chunk_size_bytes = 8192, size_t approx_component_count = 32> | ||
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 <typename T, typename... Args> | ||
T* allocate (Args&&... args) | ||
{ | ||
auto* bytes = allocator.allocate_bytes (sizeof (T), alignof (T)); | ||
auto* new_component = new (bytes) T { std::forward<Args> (args)... }; | ||
|
||
if constexpr (std::is_base_of_v<juce::Component, T>) | ||
component_list.emplace_back (new_component); | ||
|
||
return new_component; | ||
} | ||
|
||
/** Allocates multiple objects into the arena. */ | ||
template <typename T, typename... Args> | ||
nonstd::span<T> allocate_n (size_t n, Args&&... args) | ||
{ | ||
auto* bytes = allocator.allocate_bytes (sizeof (T) * n, alignof (T)); | ||
auto span = nonstd::span<T> { reinterpret_cast<T*> (bytes), n }; | ||
for (auto& ptr : span) | ||
{ | ||
auto* new_component = new (&ptr) T { std::forward<Args> (args)... }; | ||
if constexpr (std::is_base_of_v<juce::Component, T>) | ||
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<std::array<std::byte, arena_chunk_size_bytes>> allocator {}; | ||
|
||
private: | ||
SmallVector<DestructiblePointer<juce::Component>, approx_component_count> component_list {}; | ||
}; | ||
} // namespace chowdsp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#include <CatchUtils.h> | ||
#include <chowdsp_gui/chowdsp_gui.h> | ||
|
||
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<TestComponent> (destructor_counter); | ||
arena.allocate_n<TestComponent> (4, destructor_counter); | ||
|
||
REQUIRE (destructor_counter == 0); | ||
arena.clear_all(); | ||
REQUIRE (destructor_counter == 5); | ||
} | ||
|
||
SECTION ("Allocate other types too") | ||
{ | ||
arena.allocate<TestComponent> (destructor_counter); | ||
arena.allocate<NotAComponent> (destructor_counter); | ||
arena.allocate_n<TestComponent> (4, destructor_counter); | ||
arena.allocate_n<NotAComponent> (3, destructor_counter); | ||
|
||
REQUIRE (destructor_counter == 0); | ||
arena.clear_all(); | ||
REQUIRE (destructor_counter == 5); | ||
} | ||
} |