Skip to content

Commit

Permalink
OptionalRef and OptionalArray (#541)
Browse files Browse the repository at this point in the history
* OptionalRef

* Using OptionalRef

* Refactoring EnumMap to use OptionalRef

* Revert EnumMap changes

* More OptionalRef tests

* OptionalArray

* Apply clang-format

* Trying to fix missing include

* More includes

* Trying to bring back macos CI tests

* Fixing clang-tidy

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
jatinchowdhury18 and github-actions[bot] authored Jun 19, 2024
1 parent c3ddb33 commit e4d6a5b
Show file tree
Hide file tree
Showing 12 changed files with 462 additions and 25 deletions.
11 changes: 4 additions & 7 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ jobs:
cmake_args: "-DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug"
run_coverage: true
build_type: "Debug"
# - name: "Coverage"
# os: macos-latest
# cmake_args: "-DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug"
# run_coverage: true (this is breaking for some reason @TODO)
# build_type: "Debug"
- name: "Test"
os: macos-14
cmake_args: "-DCMAKE_BUILD_TYPE=Debug"
build_type: "Debug"
- name: "Live GUI Test"
os: ubuntu-22.04
tests: "live_gui_test"
Expand All @@ -58,8 +57,6 @@ jobs:
build_type: "Debug"
exclude:
# so we don't break GitHub Actions concurrency limit
- name: "Test"
os: macos-14
- name: "Coverage"
os: macos-14
- name: "Coverage"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,29 @@ void AbstractTree<ElementType, DerivedType>::clear()
}

template <typename ElementType, typename DerivedType>
ElementType* AbstractTree<ElementType, DerivedType>::findElement (const ElementType& element)
OptionalRef<ElementType> AbstractTree<ElementType, DerivedType>::findElement (const ElementType& element)
{
ElementType* result = nullptr;
OptionalRef<ElementType> result {};
doForAllElements (
[&result, element] (ElementType& candidate)
{
if (element == candidate)
result = &candidate;
result = candidate;
});
return result;
}

template <typename ElementType, typename DerivedType>
const ElementType* AbstractTree<ElementType, DerivedType>::findElement (const ElementType& element) const
OptionalRef<const ElementType> AbstractTree<ElementType, DerivedType>::findElement (const ElementType& element) const
{
return const_cast<AbstractTree&> (*this).findElement (element); // NOSONAR
OptionalRef<const ElementType> result {};
doForAllElements (
[&result, element] (const ElementType& candidate)
{
if (element == candidate)
result = candidate;
});
return result;
}

template <typename ElementType, typename DerivedType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ class AbstractTree
[[nodiscard]] int size() const { return count; }

/** Checks if the tree currently contains an element. If true, then return the element, else return nullptr. */
[[nodiscard]] ElementType* findElement (const ElementType& element);
[[nodiscard]] OptionalRef<ElementType> findElement (const ElementType& element);

/** Checks if the tree currently contains an element. If true, then return the element, else return nullptr. */
[[nodiscard]] const ElementType* findElement (const ElementType& element) const;
[[nodiscard]] OptionalRef<const ElementType> findElement (const ElementType& element) const;

template <typename Callable>
void doForAllNodes (Callable&& callable);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#pragma once

namespace chowdsp
{
/**
* A data structure similar to std::array<std::optional<T>, N>, but with less memory overhead.
*
* This implementation is still a work-in-progress.
*/
template <typename T, size_t N>
class OptionalArray
{
std::array<RawObject<T>, N> objects {};
std::bitset<N> optional_flags {};

public:
using value_type = T;
static constexpr auto max_size = N;

OptionalArray() = default;
OptionalArray (const OptionalArray&) = default;
OptionalArray& operator= (const OptionalArray&) = default;
OptionalArray (OptionalArray&&) noexcept = default;
OptionalArray& operator= (OptionalArray&&) noexcept = default;

~OptionalArray()
{
for (auto [idx, object] : enumerate (objects))
{
if (optional_flags[idx])
object.destruct();
}
}

[[nodiscard]] bool has_value (size_t idx) const noexcept
{
return optional_flags[idx];
}

[[nodiscard]] bool empty() const noexcept
{
return optional_flags.none();
}

[[nodiscard]] size_t count_values() const noexcept
{
return optional_flags.count();
}

OptionalRef<T> operator[] (size_t idx)
{
if (! optional_flags[idx])
return {};
return objects[idx].item();
}

OptionalRef<const T> operator[] (size_t idx) const
{
if (! optional_flags[idx])
return {};
return objects[idx].item();
}

template <typename... Args>
T& emplace (size_t idx, Args&&... args)
{
if (optional_flags[idx])
objects[idx].destruct();
optional_flags[idx] = true;
return *objects[idx].construct (std::forward<Args> (args)...);
}

void erase (size_t idx)
{
if (optional_flags[idx])
{
objects[idx].destruct();
optional_flags[idx] = false;
}
}

template <bool is_const = false>
struct iterator
{
size_t index {};
std::conditional_t<is_const, const OptionalArray&, OptionalArray&> array {};

bool operator!= (const iterator& other) const noexcept
{
return &array != &other.array || index != other.index;
}
void operator++() noexcept
{
do
{
++index;
} while (index < N && ! array.optional_flags[index]);
}
auto& operator*() const noexcept
{
return array.objects[index].item();
}
};

auto begin()
{
return iterator<> { 0, *this };
}

auto end()
{
return iterator<> { N, *this };
}

auto begin() const
{
return cbegin();
}

auto end() const
{
return cend();
}

auto cbegin() const
{
return iterator<true> { 0, *this };
}

auto cend() const
{
return iterator<true> { N, *this };
}
};
} // namespace chowdsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#pragma once

namespace chowdsp
{
/**
* This class is basically an implementation of std::optional<T&>.
* Under the hood, it's basically just a pointer but with an
* optional-like interface.
*
* Make sure that an OptionalRef never holds on to a reference
* past the lifetime of the referenced object.
*/
template <typename T>
class OptionalRef
{
T* ptr = nullptr;

public:
OptionalRef() = default;
OptionalRef (const OptionalRef&) = default;
OptionalRef& operator= (const OptionalRef&) = default;
OptionalRef (OptionalRef&&) noexcept = default;
OptionalRef& operator= (OptionalRef&&) noexcept = default;

OptionalRef (std::nullopt_t) // NOLINT(google-explicit-constructor)
{
}

OptionalRef (T& value) // NOLINT(google-explicit-constructor)
: ptr { &value }
{
}

OptionalRef& operator= (std::nullopt_t) noexcept // NOLINT
{
ptr = nullptr;
return *this;
}

OptionalRef& operator= (T& value) noexcept // NOLINT
{
ptr = &value;
return *this;
}

T* operator->() noexcept { return ptr; }
const T* operator->() const noexcept { return ptr; }
T& operator*() noexcept { return *ptr; }
const T& operator*() const noexcept { return *ptr; }

[[nodiscard]] explicit operator bool() const noexcept { return has_value(); }
[[nodiscard]] bool has_value() const noexcept { return ptr != nullptr; }

T& value()
{
if (! has_value())
throw std::bad_optional_access();
return *ptr;
}

const T& value() const
{
if (! has_value())
throw std::bad_optional_access();
return *ptr;
}

void reset() noexcept { ptr = nullptr; }
void swap (OptionalRef& other) noexcept { std::swap (ptr, other.ptr); }

const T& value_or (const T& other) const { return has_value() ? *ptr : other; }
};

template <typename T>
bool operator== (const OptionalRef<T>& p1, const OptionalRef<T>& p2)
{
if (p1.has_value() != p2.has_value())
return false;

if (! p1.has_value())
return true; // both nullopt

return *p1 == *p2;
}

template <typename T>
bool operator!= (const OptionalRef<T>& p1, const OptionalRef<T>& p2)
{
return ! (p1 == p2);
}

template <typename T>
bool operator== (const OptionalRef<T>& p1, std::nullopt_t)
{
return ! p1.has_value();
}

template <typename T>
bool operator!= (const OptionalRef<T>& p1, std::nullopt_t)
{
return p1.has_value();
}

template <typename T>
bool operator== (std::nullopt_t, const OptionalRef<T>& p1)
{
return ! p1.has_value();
}

template <typename T>
bool operator!= (std::nullopt_t, const OptionalRef<T>& p1)
{
return p1.has_value();
}

template <typename T>
bool operator== (const OptionalRef<T>& p1, const std::remove_cv_t<T>& p2)
{
if (! p1.has_value())
return false;
return *p1 == p2;
}

template <typename T>
bool operator!= (const OptionalRef<T>& p1, const std::remove_cv_t<T>& p2)
{
return ! (p1 == p2);
}

template <typename T>
bool operator== (const std::remove_cv_t<T>& p2, const OptionalRef<T>& p1)
{
return p1 == p2;
}

template <typename T>
bool operator!= (const std::remove_cv_t<T>& p2, const OptionalRef<T>& p1)
{
return p1 != p2;
}
} // namespace chowdsp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ BEGIN_JUCE_MODULE_DECLARATION

#pragma once

#include <bitset>
#include <optional>

#include <chowdsp_core/chowdsp_core.h>

#include "third_party/short_alloc.h"
Expand All @@ -36,7 +39,9 @@ BEGIN_JUCE_MODULE_DECLARATION
#include "Structures/chowdsp_OptionalPointer.h"
#include "Structures/chowdsp_SmallVector.h"
#include "Structures/chowdsp_StringLiteral.h"
#include "Structures/chowdsp_OptionalRef.h"
#include "Structures/chowdsp_EnumMap.h"
#include "Structures/chowdsp_OptionalArray.h"

#include "Allocators/chowdsp_ArenaAllocator.h"
#include "Allocators/chowdsp_ChainedArenaAllocator.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ void PresetManager::saveUserPreset (const juce::File& file, Preset&& preset) //
preset.toFile (file);
loadUserPresetsFromFolder (getUserPresetPath());

if (const auto* justSavedPreset = presetTree.findElement (preset))
if (const auto justSavedPreset = presetTree.findElement (preset))
loadPreset (*justSavedPreset);
else
jassertfalse; // preset was not saved correctly!
Expand All @@ -76,9 +76,9 @@ void PresetManager::setDefaultPreset (Preset&& newDefaultPreset)
// default preset must be a valid preset!
jassert (newDefaultPreset.isValid());

if (const auto* foundDefaultPreset = presetTree.findElement (newDefaultPreset))
if (const auto foundDefaultPreset = presetTree.findElement (newDefaultPreset))
{
defaultPreset = foundDefaultPreset;
defaultPreset = &foundDefaultPreset.value();
return;
}

Expand Down
Loading

0 comments on commit e4d6a5b

Please sign in to comment.