mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Audio: Add AudioWorkgroup support
This allows real-time threads to join an audio workgroup on Apple platforms.
This commit is contained in:
parent
2843983a21
commit
7d9cdd3016
60 changed files with 4949 additions and 116 deletions
238
modules/juce_core/containers/juce_FixedSizeFunction.h
Normal file
238
modules/juce_core/containers/juce_FixedSizeFunction.h
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#ifndef DOXYGEN
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <typename Ret, typename... Args>
|
||||
struct Vtable
|
||||
{
|
||||
using Storage = void*;
|
||||
|
||||
using Move = void (*) (Storage, Storage);
|
||||
using Call = Ret (*) (Storage, Args...);
|
||||
using Clear = void (*) (Storage);
|
||||
|
||||
constexpr Vtable (Move moveIn, Call callIn, Clear clearIn) noexcept
|
||||
: move (moveIn), call (callIn), clear (clearIn) {}
|
||||
|
||||
Move move = nullptr;
|
||||
Call call = nullptr;
|
||||
Clear clear = nullptr;
|
||||
};
|
||||
|
||||
template <typename Fn>
|
||||
void move (void* from, void* to)
|
||||
{
|
||||
new (to) Fn (std::move (*reinterpret_cast<Fn*> (from)));
|
||||
}
|
||||
|
||||
template <typename Fn, typename Ret, typename... Args>
|
||||
std::enable_if_t<std::is_same_v<Ret, void>, Ret> call (void* s, Args... args)
|
||||
{
|
||||
(*reinterpret_cast<Fn*> (s)) (args...);
|
||||
}
|
||||
|
||||
template <typename Fn, typename Ret, typename... Args>
|
||||
std::enable_if_t<! std::is_same_v<Ret, void>, Ret> call (void* s, Args... args)
|
||||
{
|
||||
return (*reinterpret_cast<Fn*> (s)) (std::forward<Args> (args)...);
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void clear (void* s)
|
||||
{
|
||||
// I know this looks insane, for some reason MSVC 14 sometimes thinks fn is unreferenced
|
||||
[[maybe_unused]] auto& fn = *reinterpret_cast<Fn*> (s);
|
||||
fn.~Fn();
|
||||
}
|
||||
|
||||
template <typename Fn, typename Ret, typename... Args>
|
||||
constexpr Vtable<Ret, Args...> makeVtable()
|
||||
{
|
||||
return { move <Fn>, call <Fn, Ret, Args...>, clear<Fn> };
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <size_t len, typename T>
|
||||
class FixedSizeFunction;
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
A type similar to `std::function` that holds a callable object.
|
||||
|
||||
Unlike `std::function`, the callable object will always be stored in
|
||||
a buffer of size `len` that is internal to the FixedSizeFunction instance.
|
||||
This in turn means that creating a FixedSizeFunction instance will never allocate,
|
||||
making FixedSizeFunctions suitable for use in realtime contexts.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <size_t len, typename Ret, typename... Args>
|
||||
class FixedSizeFunction<len, Ret (Args...)>
|
||||
{
|
||||
private:
|
||||
using Storage = std::aligned_storage_t<len>;
|
||||
|
||||
template <typename Item>
|
||||
using Decay = std::decay_t<Item>;
|
||||
|
||||
template <typename Item, typename Fn = Decay<Item>>
|
||||
using IntIfValidConversion = std::enable_if_t<sizeof (Fn) <= len
|
||||
&& alignof (Fn) <= alignof (Storage)
|
||||
&& ! std::is_same_v<FixedSizeFunction, Fn>,
|
||||
int>;
|
||||
|
||||
public:
|
||||
/** Create an empty function. */
|
||||
FixedSizeFunction() noexcept = default;
|
||||
|
||||
/** Create an empty function. */
|
||||
FixedSizeFunction (std::nullptr_t) noexcept
|
||||
: FixedSizeFunction() {}
|
||||
|
||||
FixedSizeFunction (const FixedSizeFunction&) = delete;
|
||||
|
||||
/** Forwards the passed Callable into the internal storage buffer. */
|
||||
template <typename Callable,
|
||||
typename Fn = Decay<Callable>,
|
||||
IntIfValidConversion<Callable> = 0>
|
||||
FixedSizeFunction (Callable&& callable)
|
||||
{
|
||||
static_assert (sizeof (Fn) <= len,
|
||||
"The requested function cannot fit in this FixedSizeFunction");
|
||||
static_assert (alignof (Fn) <= alignof (Storage),
|
||||
"FixedSizeFunction cannot accommodate the requested alignment requirements");
|
||||
|
||||
static constexpr auto vtableForCallable = detail::makeVtable<Fn, Ret, Args...>();
|
||||
vtable = &vtableForCallable;
|
||||
|
||||
auto* ptr = new (&storage) Fn (std::forward<Callable> (callable));
|
||||
jassertquiet ((void*) ptr == (void*) &storage);
|
||||
}
|
||||
|
||||
/** Move constructor. */
|
||||
FixedSizeFunction (FixedSizeFunction&& other) noexcept
|
||||
: vtable (other.vtable)
|
||||
{
|
||||
move (std::move (other));
|
||||
}
|
||||
|
||||
/** Converting constructor from smaller FixedSizeFunctions. */
|
||||
template <size_t otherLen, std::enable_if_t<(otherLen < len), int> = 0>
|
||||
FixedSizeFunction (FixedSizeFunction<otherLen, Ret (Args...)>&& other) noexcept
|
||||
: vtable (other.vtable)
|
||||
{
|
||||
move (std::move (other));
|
||||
}
|
||||
|
||||
/** Nulls this instance. */
|
||||
FixedSizeFunction& operator= (std::nullptr_t) noexcept
|
||||
{
|
||||
return *this = FixedSizeFunction();
|
||||
}
|
||||
|
||||
FixedSizeFunction& operator= (const FixedSizeFunction&) = delete;
|
||||
|
||||
/** Assigns a new callable to this instance. */
|
||||
template <typename Callable, IntIfValidConversion<Callable> = 0>
|
||||
FixedSizeFunction& operator= (Callable&& callable)
|
||||
{
|
||||
return *this = FixedSizeFunction (std::forward<Callable> (callable));
|
||||
}
|
||||
|
||||
/** Move assignment from smaller FixedSizeFunctions. */
|
||||
template <size_t otherLen, std::enable_if_t<(otherLen < len), int> = 0>
|
||||
FixedSizeFunction& operator= (FixedSizeFunction<otherLen, Ret (Args...)>&& other) noexcept
|
||||
{
|
||||
return *this = FixedSizeFunction (std::move (other));
|
||||
}
|
||||
|
||||
/** Move assignment operator. */
|
||||
FixedSizeFunction& operator= (FixedSizeFunction&& other) noexcept
|
||||
{
|
||||
clear();
|
||||
vtable = other.vtable;
|
||||
move (std::move (other));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Destructor. */
|
||||
~FixedSizeFunction() noexcept { clear(); }
|
||||
|
||||
/** If this instance is currently storing a callable object, calls that object,
|
||||
otherwise throws `std::bad_function_call`.
|
||||
*/
|
||||
Ret operator() (Args... args) const
|
||||
{
|
||||
if (vtable != nullptr)
|
||||
return vtable->call (&storage, std::forward<Args> (args)...);
|
||||
|
||||
throw std::bad_function_call();
|
||||
}
|
||||
|
||||
/** Returns true if this instance currently holds a callable. */
|
||||
explicit operator bool() const noexcept { return vtable != nullptr; }
|
||||
|
||||
private:
|
||||
template <size_t, typename>
|
||||
friend class FixedSizeFunction;
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
if (vtable != nullptr)
|
||||
vtable->clear (&storage);
|
||||
}
|
||||
|
||||
template <size_t otherLen, typename T>
|
||||
void move (FixedSizeFunction<otherLen, T>&& other) noexcept
|
||||
{
|
||||
if (vtable != nullptr)
|
||||
vtable->move (&other.storage, &storage);
|
||||
}
|
||||
|
||||
const detail::Vtable<Ret, Args...>* vtable = nullptr;
|
||||
mutable Storage storage;
|
||||
};
|
||||
|
||||
template <size_t len, typename T>
|
||||
bool operator!= (const FixedSizeFunction<len, T>& fn, std::nullptr_t) { return bool (fn); }
|
||||
|
||||
template <size_t len, typename T>
|
||||
bool operator!= (std::nullptr_t, const FixedSizeFunction<len, T>& fn) { return bool (fn); }
|
||||
|
||||
template <size_t len, typename T>
|
||||
bool operator== (const FixedSizeFunction<len, T>& fn, std::nullptr_t) { return ! (fn != nullptr); }
|
||||
|
||||
template <size_t len, typename T>
|
||||
bool operator== (std::nullptr_t, const FixedSizeFunction<len, T>& fn) { return ! (fn != nullptr); }
|
||||
|
||||
|
||||
}
|
||||
352
modules/juce_core/containers/juce_FixedSizeFunction_test.cpp
Normal file
352
modules/juce_core/containers/juce_FixedSizeFunction_test.cpp
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_ENABLE_ALLOCATION_HOOKS
|
||||
#define JUCE_FAIL_ON_ALLOCATION_IN_SCOPE const UnitTestAllocationChecker checker (*this)
|
||||
#else
|
||||
#define JUCE_FAIL_ON_ALLOCATION_IN_SCOPE
|
||||
#endif
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
class ConstructCounts
|
||||
{
|
||||
auto tie() const noexcept { return std::tie (constructions, copies, moves, calls, destructions); }
|
||||
|
||||
public:
|
||||
int constructions = 0;
|
||||
int copies = 0;
|
||||
int moves = 0;
|
||||
int calls = 0;
|
||||
int destructions = 0;
|
||||
|
||||
ConstructCounts withConstructions (int i) const noexcept { auto c = *this; c.constructions = i; return c; }
|
||||
ConstructCounts withCopies (int i) const noexcept { auto c = *this; c.copies = i; return c; }
|
||||
ConstructCounts withMoves (int i) const noexcept { auto c = *this; c.moves = i; return c; }
|
||||
ConstructCounts withCalls (int i) const noexcept { auto c = *this; c.calls = i; return c; }
|
||||
ConstructCounts withDestructions (int i) const noexcept { auto c = *this; c.destructions = i; return c; }
|
||||
|
||||
bool operator== (const ConstructCounts& other) const noexcept { return tie() == other.tie(); }
|
||||
bool operator!= (const ConstructCounts& other) const noexcept { return tie() != other.tie(); }
|
||||
};
|
||||
|
||||
String& operator<< (String& str, const ConstructCounts& c)
|
||||
{
|
||||
return str << "{ constructions: " << c.constructions
|
||||
<< ", copies: " << c.copies
|
||||
<< ", moves: " << c.moves
|
||||
<< ", calls: " << c.calls
|
||||
<< ", destructions: " << c.destructions
|
||||
<< " }";
|
||||
}
|
||||
|
||||
class FixedSizeFunctionTest : public UnitTest
|
||||
{
|
||||
static void toggleBool (bool& b) { b = ! b; }
|
||||
|
||||
struct ConstructCounter
|
||||
{
|
||||
explicit ConstructCounter (ConstructCounts& countsIn)
|
||||
: counts (countsIn) {}
|
||||
|
||||
ConstructCounter (const ConstructCounter& c)
|
||||
: counts (c.counts)
|
||||
{
|
||||
counts.copies += 1;
|
||||
}
|
||||
|
||||
ConstructCounter (ConstructCounter&& c) noexcept
|
||||
: counts (c.counts)
|
||||
{
|
||||
counts.moves += 1;
|
||||
}
|
||||
|
||||
~ConstructCounter() noexcept { counts.destructions += 1; }
|
||||
|
||||
void operator()() const noexcept { counts.calls += 1; }
|
||||
|
||||
ConstructCounts& counts;
|
||||
};
|
||||
|
||||
public:
|
||||
FixedSizeFunctionTest()
|
||||
: UnitTest ("Fixed Size Function", UnitTestCategories::containers)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("Can be constructed and called from a lambda");
|
||||
{
|
||||
JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
|
||||
|
||||
const auto result = 5;
|
||||
bool wasCalled = false;
|
||||
const auto lambda = [&] { wasCalled = true; return result; };
|
||||
|
||||
const FixedSizeFunction<sizeof (lambda), int()> fn (lambda);
|
||||
const auto out = fn();
|
||||
|
||||
expect (wasCalled);
|
||||
expectEquals (result, out);
|
||||
}
|
||||
|
||||
beginTest ("void fn can be constructed from function with return value");
|
||||
{
|
||||
JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
|
||||
|
||||
bool wasCalled = false;
|
||||
const auto lambda = [&] { wasCalled = true; return 5; };
|
||||
const FixedSizeFunction<sizeof (lambda), void()> fn (lambda);
|
||||
|
||||
fn();
|
||||
expect (wasCalled);
|
||||
}
|
||||
|
||||
beginTest ("Can be constructed and called from a function pointer");
|
||||
{
|
||||
JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
|
||||
|
||||
bool state = false;
|
||||
|
||||
const FixedSizeFunction<sizeof (void*), void (bool&)> fn (toggleBool);
|
||||
|
||||
fn (state);
|
||||
expect (state);
|
||||
|
||||
fn (state);
|
||||
expect (! state);
|
||||
|
||||
fn (state);
|
||||
expect (state);
|
||||
}
|
||||
|
||||
beginTest ("Default constructed functions throw if called");
|
||||
{
|
||||
const auto a = FixedSizeFunction<8, void()>();
|
||||
expectThrowsType (a(), std::bad_function_call)
|
||||
|
||||
const auto b = FixedSizeFunction<8, void()> (nullptr);
|
||||
expectThrowsType (b(), std::bad_function_call)
|
||||
}
|
||||
|
||||
beginTest ("Functions can be moved");
|
||||
{
|
||||
JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
|
||||
|
||||
ConstructCounts counts;
|
||||
|
||||
auto a = FixedSizeFunction<sizeof (ConstructCounter), void()> (ConstructCounter { counts });
|
||||
expectEquals (counts, ConstructCounts().withMoves (1).withDestructions (1)); // The temporary gets destroyed
|
||||
|
||||
a();
|
||||
expectEquals (counts, ConstructCounts().withMoves (1).withDestructions (1).withCalls (1));
|
||||
|
||||
const auto b = std::move (a);
|
||||
expectEquals (counts, ConstructCounts().withMoves (2).withDestructions (1).withCalls (1));
|
||||
|
||||
b();
|
||||
expectEquals (counts, ConstructCounts().withMoves (2).withDestructions (1).withCalls (2));
|
||||
|
||||
b();
|
||||
expectEquals (counts, ConstructCounts().withMoves (2).withDestructions (1).withCalls (3));
|
||||
}
|
||||
|
||||
beginTest ("Functions are destructed properly");
|
||||
{
|
||||
JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
|
||||
|
||||
ConstructCounts counts;
|
||||
const ConstructCounter toCopy { counts };
|
||||
|
||||
{
|
||||
auto a = FixedSizeFunction<sizeof (ConstructCounter), void()> (toCopy);
|
||||
expectEquals (counts, ConstructCounts().withCopies (1));
|
||||
}
|
||||
|
||||
expectEquals (counts, ConstructCounts().withCopies (1).withDestructions (1));
|
||||
}
|
||||
|
||||
beginTest ("Avoid destructing functions that fail to construct");
|
||||
{
|
||||
struct BadConstructor
|
||||
{
|
||||
explicit BadConstructor (ConstructCounts& c)
|
||||
: counts (c)
|
||||
{
|
||||
counts.constructions += 1;
|
||||
throw std::runtime_error { "this was meant to happen" };
|
||||
}
|
||||
|
||||
BadConstructor (const BadConstructor&) = default;
|
||||
BadConstructor& operator= (const BadConstructor&) = delete;
|
||||
|
||||
~BadConstructor() noexcept { counts.destructions += 1; }
|
||||
|
||||
void operator()() const noexcept { counts.calls += 1; }
|
||||
|
||||
ConstructCounts& counts;
|
||||
};
|
||||
|
||||
ConstructCounts counts;
|
||||
|
||||
expectThrowsType ((FixedSizeFunction<sizeof (BadConstructor), void()> (BadConstructor { counts })),
|
||||
std::runtime_error)
|
||||
|
||||
expectEquals (counts, ConstructCounts().withConstructions (1));
|
||||
}
|
||||
|
||||
beginTest ("Equality checks work");
|
||||
{
|
||||
JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
|
||||
|
||||
FixedSizeFunction<8, void()> a;
|
||||
expect (! bool (a));
|
||||
expect (a == nullptr);
|
||||
expect (nullptr == a);
|
||||
expect (! (a != nullptr));
|
||||
expect (! (nullptr != a));
|
||||
|
||||
FixedSizeFunction<8, void()> b ([] {});
|
||||
expect (bool (b));
|
||||
expect (b != nullptr);
|
||||
expect (nullptr != b);
|
||||
expect (! (b == nullptr));
|
||||
expect (! (nullptr == b));
|
||||
}
|
||||
|
||||
beginTest ("Functions can be cleared");
|
||||
{
|
||||
JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
|
||||
|
||||
FixedSizeFunction<8, void()> fn ([] {});
|
||||
expect (bool (fn));
|
||||
|
||||
fn = nullptr;
|
||||
expect (! bool (fn));
|
||||
}
|
||||
|
||||
beginTest ("Functions can be assigned");
|
||||
{
|
||||
JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
|
||||
|
||||
using Fn = FixedSizeFunction<8, void()>;
|
||||
|
||||
int numCallsA = 0;
|
||||
int numCallsB = 0;
|
||||
|
||||
Fn x;
|
||||
Fn y;
|
||||
expect (! bool (x));
|
||||
expect (! bool (y));
|
||||
|
||||
x = [&] { numCallsA += 1; };
|
||||
y = [&] { numCallsB += 1; };
|
||||
expect (bool (x));
|
||||
expect (bool (y));
|
||||
|
||||
x();
|
||||
expectEquals (numCallsA, 1);
|
||||
expectEquals (numCallsB, 0);
|
||||
|
||||
y();
|
||||
expectEquals (numCallsA, 1);
|
||||
expectEquals (numCallsB, 1);
|
||||
|
||||
x = std::move (y);
|
||||
expectEquals (numCallsA, 1);
|
||||
expectEquals (numCallsB, 1);
|
||||
|
||||
x();
|
||||
expectEquals (numCallsA, 1);
|
||||
expectEquals (numCallsB, 2);
|
||||
}
|
||||
|
||||
beginTest ("Functions may mutate internal state");
|
||||
{
|
||||
JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
|
||||
|
||||
using Fn = FixedSizeFunction<64, void()>;
|
||||
|
||||
Fn x;
|
||||
expect (! bool (x));
|
||||
|
||||
int numCalls = 0;
|
||||
x = [&numCalls, counter = 0]() mutable { counter += 1; numCalls = counter; };
|
||||
expect (bool (x));
|
||||
|
||||
expectEquals (numCalls, 0);
|
||||
|
||||
x();
|
||||
expectEquals (numCalls, 1);
|
||||
|
||||
x();
|
||||
expectEquals (numCalls, 2);
|
||||
}
|
||||
|
||||
beginTest ("Functions can sink move-only parameters");
|
||||
{
|
||||
JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
|
||||
|
||||
using Fn = FixedSizeFunction<64, int (std::unique_ptr<int>)>;
|
||||
|
||||
auto value = 5;
|
||||
auto ptr = std::make_unique<int> (value);
|
||||
|
||||
Fn fn = [] (std::unique_ptr<int> p) { return *p; };
|
||||
|
||||
expect (value == fn (std::move (ptr)));
|
||||
}
|
||||
|
||||
beginTest ("Functions be converted from smaller functions");
|
||||
{
|
||||
JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
|
||||
|
||||
using SmallFn = FixedSizeFunction<20, void()>;
|
||||
using LargeFn = FixedSizeFunction<21, void()>;
|
||||
|
||||
bool smallCalled = false;
|
||||
bool largeCalled = false;
|
||||
|
||||
SmallFn small = [&smallCalled, a = std::array<char, 8>{}] { smallCalled = true; ignoreUnused (a); };
|
||||
LargeFn large = [&largeCalled, a = std::array<char, 8>{}] { largeCalled = true; ignoreUnused (a); };
|
||||
|
||||
large = std::move (small);
|
||||
|
||||
large();
|
||||
|
||||
expect (smallCalled);
|
||||
expect (! largeCalled);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FixedSizeFunctionTest fixedSizedFunctionTest;
|
||||
|
||||
}
|
||||
}
|
||||
#undef JUCE_FAIL_ON_ALLOCATION_IN_SCOPE
|
||||
Loading…
Add table
Add a link
Reference in a new issue