diff --git a/src/CardinalPlugin.cpp b/src/CardinalPlugin.cpp index ff932cf..8433586 100644 --- a/src/CardinalPlugin.cpp +++ b/src/CardinalPlugin.cpp @@ -34,6 +34,7 @@ #include "PluginContext.hpp" #include "WindowParameters.hpp" #include "extra/Base64.hpp" +#include "extra/SharedResourcePointer.hpp" namespace rack { namespace plugin { @@ -47,7 +48,7 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------------------------------------------- struct Initializer { - Initializer() + Initializer(const CardinalBasePlugin* const plugin) { using namespace rack; @@ -60,10 +61,12 @@ struct Initializer { settings::skipLoadOnLaunch = true; settings::showTipsOnLaunch = false; settings::threadCount = 1; + system::init(); asset::init(); logger::init(); random::init(); + ui::init(); // Make system dir point to source code location. It is good enough for now asset::systemDir = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR "Rack"; @@ -73,7 +76,6 @@ struct Initializer { INFO("%s", system::getOperatingSystemInfo().c_str()); INFO("System directory: %s", asset::systemDir.c_str()); INFO("User directory: %s", asset::userDir.c_str()); - INFO("System time: %s", string::formatTimeISO(system::getUnixTime()).c_str()); // Check existence of the system res/ directory const std::string resDir = asset::system("res"); @@ -83,42 +85,54 @@ struct Initializer { "Make sure Cardinal was downloaded and installed correctly.", resDir.c_str()); } - INFO("Initializing environment"); - audio::init(); // does nothing - midi::init(); // does nothing - + INFO("Initializing audio driver"); rack::audio::addDriver(0, new CardinalAudioDriver); + INFO("Initializing plugins"); plugin::initStaticPlugins(); - ui::init(); } ~Initializer() { using namespace rack; - ui::destroy(); // does nothing - INFO("Destroying plugins"); plugin::destroyStaticPlugins(); + INFO("Destroying MIDI devices"); midi::destroy(); + + INFO("Destroying audio devices"); audio::destroy(); + INFO("Destroying logger"); logger::destroy(); } }; -static const Initializer& getInitializerInstance() -{ - static const Initializer init; - return init; -} +// ----------------------------------------------------------------------------------------------------------- + +struct ScopedContext { + const MutexLocker cml; + + ScopedContext(const CardinalBasePlugin* const plugin) + : cml(plugin->context->mutex) + { + rack::contextSet(plugin->context); + } + + ~ScopedContext() + { + rack::contextSet(nullptr); + } +}; // ----------------------------------------------------------------------------------------------------------- class CardinalPlugin : public CardinalBasePlugin { + SharedResourcePointer fInitializer; + float* fAudioBufferIn; float* fAudioBufferOut; std::string fAutosavePath; @@ -130,24 +144,10 @@ class CardinalPlugin : public CardinalBasePlugin float fWindowParameters[kWindowParameterCount]; - struct ScopedContext { - const MutexLocker cml; - - ScopedContext(const CardinalPlugin* const plugin) - : cml(plugin->context->mutex) - { - rack::contextSet(plugin->context); - } - - ~ScopedContext() - { - rack::contextSet(nullptr); - } - }; - public: CardinalPlugin() : CardinalBasePlugin(kModuleParameters + kWindowParameterCount, 0, 1), + fInitializer(this), fAudioBufferIn(nullptr), fAudioBufferOut(nullptr), fIsActive(false), @@ -517,7 +517,6 @@ CardinalPluginContext* getRackContextFromPlugin(void* const ptr) Plugin* createPlugin() { - getInitializerInstance(); return new CardinalPlugin(); } diff --git a/src/extra/Atomic.hpp b/src/extra/Atomic.hpp new file mode 100644 index 0000000..c5eae61 --- /dev/null +++ b/src/extra/Atomic.hpp @@ -0,0 +1,281 @@ +/* + ============================================================================== + + This file is part of the Water library. + Copyright (c) 2016 ROLI Ltd. + Copyright (C) 2017 Filipe Coelho + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ============================================================================== +*/ + +#ifndef WATER_ATOMIC_HPP_INCLUDED +#define WATER_ATOMIC_HPP_INCLUDED + +#include "DistrhoUtils.hpp" + +START_NAMESPACE_DISTRHO + +//============================================================================== +/** + Simple class to hold a primitive value and perform atomic operations on it. + + The type used must be a 32 or 64 bit primitive, like an int, pointer, etc. + There are methods to perform most of the basic atomic operations. +*/ +template +class Atomic +{ +public: + /** Creates a new value, initialised to zero. */ + inline Atomic() noexcept + : value (0) + { + } + + /** Creates a new value, with a given initial value. */ + inline explicit Atomic (const Type initialValue) noexcept + : value (initialValue) + { + } + + /** Copies another value (atomically). */ + inline Atomic (const Atomic& other) noexcept + : value (other.get()) + { + } + + /** Destructor. */ + inline ~Atomic() noexcept + { +#ifdef DISTRHO_PROPER_CPP11_SUPPORT + // This class can only be used for types which are 32 or 64 bits in size. + static_assert (sizeof (Type) == 4 || sizeof (Type) == 8, "Only for 32 or 64 bits"); +#endif + } + + /** Atomically reads and returns the current value. */ + Type get() const noexcept; + + /** Copies another value onto this one (atomically). */ + inline Atomic& operator= (const Atomic& other) noexcept { exchange (other.get()); return *this; } + + /** Copies another value onto this one (atomically). */ + inline Atomic& operator= (const Type newValue) noexcept { exchange (newValue); return *this; } + + /** Atomically sets the current value. */ + void set (Type newValue) noexcept { exchange (newValue); } + + /** Atomically sets the current value, returning the value that was replaced. */ + Type exchange (Type value) noexcept; + + /** Atomically adds a number to this value, returning the new value. */ + Type operator+= (Type amountToAdd) noexcept; + + /** Atomically subtracts a number from this value, returning the new value. */ + Type operator-= (Type amountToSubtract) noexcept; + + /** Atomically increments this value, returning the new value. */ + Type operator++() noexcept; + + /** Atomically decrements this value, returning the new value. */ + Type operator--() noexcept; + + /** Atomically compares this value with a target value, and if it is equal, sets + this to be equal to a new value. + + This operation is the atomic equivalent of doing this: + @code + bool compareAndSetBool (Type newValue, Type valueToCompare) + { + if (get() == valueToCompare) + { + set (newValue); + return true; + } + + return false; + } + @endcode + + @returns true if the comparison was true and the value was replaced; false if + the comparison failed and the value was left unchanged. + @see compareAndSetValue + */ + bool compareAndSetBool (Type newValue, Type valueToCompare) noexcept; + + /** Atomically compares this value with a target value, and if it is equal, sets + this to be equal to a new value. + + This operation is the atomic equivalent of doing this: + @code + Type compareAndSetValue (Type newValue, Type valueToCompare) + { + Type oldValue = get(); + if (oldValue == valueToCompare) + set (newValue); + + return oldValue; + } + @endcode + + @returns the old value before it was changed. + @see compareAndSetBool + */ + Type compareAndSetValue (Type newValue, Type valueToCompare) noexcept; + + /** Implements a memory read/write barrier. */ + static void memoryBarrier() noexcept; + + //============================================================================== + /** The raw value that this class operates on. + This is exposed publicly in case you need to manipulate it directly + for performance reasons. + */ + #if defined(__LP64__) || defined(_LP64) || defined(__arm64__) || defined(__aarch64__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(_M_ARM64) + __attribute__ ((aligned (8))) + #else + __attribute__ ((aligned (4))) + #endif + volatile Type value; + +private: + template + static inline Dest castTo (Source value) noexcept { union { Dest d; Source s; } u; u.s = value; return u.d; } + + static inline Type castFrom32Bit (int32_t value) noexcept { return castTo (value); } + static inline Type castFrom64Bit (int64_t value) noexcept { return castTo (value); } + static inline Type castFrom32Bit (uint32_t value) noexcept { return castTo (value); } + static inline Type castFrom64Bit (uint64_t value) noexcept { return castTo (value); } + static inline int32_t castTo32Bit (Type value) noexcept { return castTo (value); } + static inline int64_t castTo64Bit (Type value) noexcept { return castTo (value); } + + Type operator++ (int); // better to just use pre-increment with atomics.. + Type operator-- (int); + + /** This templated negate function will negate pointers as well as integers */ + template + inline ValueType negateValue (ValueType n) noexcept + { + return sizeof (ValueType) == 1 ? (ValueType) -(signed char) n + : (sizeof (ValueType) == 2 ? (ValueType) -(short) n + : (sizeof (ValueType) == 4 ? (ValueType) -(int) n + : ((ValueType) -(int64_t) n))); + } + + /** This templated negate function will negate pointers as well as integers */ + template + inline PointerType* negateValue (PointerType* n) noexcept + { + return reinterpret_cast (-reinterpret_cast (n)); + } +}; + +//============================================================================== +template<> +inline int32_t Atomic::get() const noexcept +{ +#ifdef DISTRHO_PROPER_CPP11_SUPPORT + static_assert (sizeof (int32_t) == 4, "int32_t must be size 4"); +#endif + return castFrom32Bit ((int32_t) __sync_add_and_fetch (const_cast(&value), 0)); +} + +template<> +inline int64_t Atomic::get() const noexcept +{ +#ifdef DISTRHO_PROPER_CPP11_SUPPORT + static_assert (sizeof (int64_t) == 8, "int64_t must be size 8"); +#endif + return castFrom64Bit ((int64_t) __sync_add_and_fetch (const_cast(&value), 0)); +} + +template<> +inline uint32_t Atomic::get() const noexcept +{ +#ifdef DISTRHO_PROPER_CPP11_SUPPORT + static_assert (sizeof (uint32_t) == 4, "uint32_t must be size 4"); +#endif + return castFrom32Bit ((uint32_t) __sync_add_and_fetch (const_cast(&value), 0)); +} + +template<> +inline uint64_t Atomic::get() const noexcept +{ +#ifdef DISTRHO_PROPER_CPP11_SUPPORT + static_assert (sizeof (uint64_t) == 8, "uint64_t must be size 8"); +#endif + return castFrom64Bit ((uint64_t) __sync_add_and_fetch (const_cast(&value), 0)); +} + +template +inline Type Atomic::exchange (const Type newValue) noexcept +{ + Type currentVal = value; + while (! compareAndSetBool (newValue, currentVal)) { currentVal = value; } + return currentVal; +} + +template +inline Type Atomic::operator+= (const Type amountToAdd) noexcept +{ + return (Type) __sync_add_and_fetch (&value, amountToAdd); +} + +template +inline Type Atomic::operator-= (const Type amountToSubtract) noexcept +{ + return operator+= (negateValue (amountToSubtract)); +} + +template +inline Type Atomic::operator++() noexcept +{ + return sizeof (Type) == 4 ? (Type) __sync_add_and_fetch (&value, (Type) 1) + : (Type) __sync_add_and_fetch ((volatile int64_t*) &value, 1); +} + +template +inline Type Atomic::operator--() noexcept +{ + return sizeof (Type) == 4 ? (Type) __sync_add_and_fetch (&value, (Type) -1) + : (Type) __sync_add_and_fetch ((volatile int64_t*) &value, -1); +} + +template +inline bool Atomic::compareAndSetBool (const Type newValue, const Type valueToCompare) noexcept +{ + return sizeof (Type) == 4 ? __sync_bool_compare_and_swap ((volatile int32_t*) &value, castTo32Bit (valueToCompare), castTo32Bit (newValue)) + : __sync_bool_compare_and_swap ((volatile int64_t*) &value, castTo64Bit (valueToCompare), castTo64Bit (newValue)); +} + +template +inline Type Atomic::compareAndSetValue (const Type newValue, const Type valueToCompare) noexcept +{ + return sizeof (Type) == 4 ? castFrom32Bit ((int32_t) __sync_val_compare_and_swap ((volatile int32_t*) &value, castTo32Bit (valueToCompare), castTo32Bit (newValue))) + : castFrom64Bit ((int64_t) __sync_val_compare_and_swap ((volatile int64_t*) &value, castTo64Bit (valueToCompare), castTo64Bit (newValue))); +} + +template +inline void Atomic::memoryBarrier() noexcept +{ + __sync_synchronize(); +} + +END_NAMESPACE_DISTRHO + +#endif // WATER_ATOMIC_HPP_INCLUDED diff --git a/src/extra/ReferenceCountedObject.hpp b/src/extra/ReferenceCountedObject.hpp new file mode 100644 index 0000000..b7b1256 --- /dev/null +++ b/src/extra/ReferenceCountedObject.hpp @@ -0,0 +1,417 @@ +/* + ============================================================================== + + This file is part of the Water library. + Copyright (c) 2016 ROLI Ltd. + Copyright (C) 2017 Filipe Coelho + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ============================================================================== +*/ + +#ifndef WATER_REFERENCECOUNTEDOBJECT_HPP_INCLUDED +#define WATER_REFERENCECOUNTEDOBJECT_HPP_INCLUDED + +#include "Atomic.hpp" + +START_NAMESPACE_DISTRHO + +//============================================================================== +/** + A base class which provides methods for reference-counting. + + To add reference-counting to a class, derive it from this class, and + use the ReferenceCountedObjectPtr class to point to it. + + e.g. @code + class MyClass : public ReferenceCountedObject + { + void foo(); + + // This is a neat way of declaring a typedef for a pointer class, + // rather than typing out the full templated name each time.. + typedef ReferenceCountedObjectPtr Ptr; + }; + + MyClass::Ptr p = new MyClass(); + MyClass::Ptr p2 = p; + p = nullptr; + p2->foo(); + @endcode + + Once a new ReferenceCountedObject has been assigned to a pointer, be + careful not to delete the object manually. + + This class uses an Atomic value to hold the reference count, so that it + the pointers can be passed between threads safely. For a faster but non-thread-safe + version, use SingleThreadedReferenceCountedObject instead. + + @see ReferenceCountedObjectPtr, ReferenceCountedArray, SingleThreadedReferenceCountedObject +*/ +class ReferenceCountedObject +{ +public: + //============================================================================== + /** Increments the object's reference count. + + This is done automatically by the smart pointer, but is public just + in case it's needed for nefarious purposes. + */ + void incReferenceCount() noexcept + { + ++refCount; + } + + /** Decreases the object's reference count. + If the count gets to zero, the object will be deleted. + */ + void decReferenceCount() noexcept + { + DISTRHO_SAFE_ASSERT_RETURN (getReferenceCount() > 0,); + + if (--refCount == 0) + delete this; + } + + /** Decreases the object's reference count. + If the count gets to zero, the object will not be deleted, but this method + will return true, allowing the caller to take care of deletion. + */ + bool decReferenceCountWithoutDeleting() noexcept + { + DISTRHO_SAFE_ASSERT_RETURN (getReferenceCount() > 0, false); + return --refCount == 0; + } + + /** Returns the object's current reference count. */ + int getReferenceCount() const noexcept { return refCount.get(); } + + +protected: + //============================================================================== + /** Creates the reference-counted object (with an initial ref count of zero). */ + ReferenceCountedObject() + : refCount() {} + + /** Destructor. */ + virtual ~ReferenceCountedObject() + { + // it's dangerous to delete an object that's still referenced by something else! + DISTRHO_SAFE_ASSERT (getReferenceCount() == 0); + } + + /** Resets the reference count to zero without deleting the object. + You should probably never need to use this! + */ + void resetReferenceCount() noexcept + { + refCount = 0; + } + +private: + //============================================================================== + Atomic refCount; + + DISTRHO_DECLARE_NON_COPYABLE (ReferenceCountedObject) +}; + + +//============================================================================== +/** + Adds reference-counting to an object. + + This is effectively a version of the ReferenceCountedObject class, but which + uses a non-atomic counter, and so is not thread-safe (but which will be more + efficient). + For more details on how to use it, see the ReferenceCountedObject class notes. + + @see ReferenceCountedObject, ReferenceCountedObjectPtr, ReferenceCountedArray +*/ +class SingleThreadedReferenceCountedObject +{ +public: + //============================================================================== + /** Increments the object's reference count. + + This is done automatically by the smart pointer, but is public just + in case it's needed for nefarious purposes. + */ + void incReferenceCount() noexcept + { + ++refCount; + } + + /** Decreases the object's reference count. + If the count gets to zero, the object will be deleted. + */ + void decReferenceCount() noexcept + { + DISTRHO_SAFE_ASSERT_RETURN (getReferenceCount() > 0,); + + if (--refCount == 0) + delete this; + } + + /** Decreases the object's reference count. + If the count gets to zero, the object will not be deleted, but this method + will return true, allowing the caller to take care of deletion. + */ + bool decReferenceCountWithoutDeleting() noexcept + { + DISTRHO_SAFE_ASSERT_RETURN (getReferenceCount() > 0, false); + return --refCount == 0; + } + + /** Returns the object's current reference count. */ + int getReferenceCount() const noexcept { return refCount; } + + +protected: + //============================================================================== + /** Creates the reference-counted object (with an initial ref count of zero). */ + SingleThreadedReferenceCountedObject() : refCount (0) {} + + /** Destructor. */ + virtual ~SingleThreadedReferenceCountedObject() + { + // it's dangerous to delete an object that's still referenced by something else! + DISTRHO_SAFE_ASSERT_RETURN (getReferenceCount() == 0,); + } + +private: + //============================================================================== + int refCount; + + DISTRHO_DECLARE_NON_COPYABLE (SingleThreadedReferenceCountedObject) +}; + + +//============================================================================== +/** + A smart-pointer class which points to a reference-counted object. + + The template parameter specifies the class of the object you want to point to - the easiest + way to make a class reference-countable is to simply make it inherit from ReferenceCountedObject + or SingleThreadedReferenceCountedObject, but if you need to, you can roll your own reference-countable + class by implementing a set of methods called incReferenceCount(), decReferenceCount(), and + decReferenceCountWithoutDeleting(). See ReferenceCountedObject for examples of how these methods + should behave. + + When using this class, you'll probably want to create a typedef to abbreviate the full + templated name - e.g. + @code + struct MyClass : public ReferenceCountedObject + { + typedef ReferenceCountedObjectPtr Ptr; + ... + @endcode + + @see ReferenceCountedObject, ReferenceCountedObjectArray +*/ +template +class ReferenceCountedObjectPtr +{ +public: + /** The class being referenced by this pointer. */ + typedef ReferenceCountedObjectClass ReferencedType; + + //============================================================================== + /** Creates a pointer to a null object. */ + ReferenceCountedObjectPtr() noexcept + : referencedObject (nullptr) + { + } + + /** Creates a pointer to an object. + This will increment the object's reference-count. + */ + ReferenceCountedObjectPtr (ReferencedType* refCountedObject) noexcept + : referencedObject (refCountedObject) + { + incIfNotNull (refCountedObject); + } + + /** Copies another pointer. + This will increment the object's reference-count. + */ + ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr& other) noexcept + : referencedObject (other.referencedObject) + { + incIfNotNull (referencedObject); + } + + /** Copies another pointer. + This will increment the object's reference-count (if it is non-null). + */ + template + ReferenceCountedObjectPtr (const ReferenceCountedObjectPtr& other) noexcept + : referencedObject (static_cast (other.get())) + { + incIfNotNull (referencedObject); + } + + /** Changes this pointer to point at a different object. + The reference count of the old object is decremented, and it might be + deleted if it hits zero. The new object's count is incremented. + */ + ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr& other) + { + return operator= (other.referencedObject); + } + + /** Changes this pointer to point at a different object. + The reference count of the old object is decremented, and it might be + deleted if it hits zero. The new object's count is incremented. + */ + template + ReferenceCountedObjectPtr& operator= (const ReferenceCountedObjectPtr& other) + { + return operator= (static_cast (other.get())); + } + + /** Changes this pointer to point at a different object. + + The reference count of the old object is decremented, and it might be + deleted if it hits zero. The new object's count is incremented. + */ + ReferenceCountedObjectPtr& operator= (ReferencedType* const newObject) + { + if (referencedObject != newObject) + { + incIfNotNull (newObject); + ReferencedType* const oldObject = referencedObject; + referencedObject = newObject; + decIfNotNull (oldObject); + } + + return *this; + } + + #if WATER_COMPILER_SUPPORTS_MOVE_SEMANTICS + /** Takes-over the object from another pointer. */ + ReferenceCountedObjectPtr (ReferenceCountedObjectPtr&& other) noexcept + : referencedObject (other.referencedObject) + { + other.referencedObject = nullptr; + } + + /** Takes-over the object from another pointer. */ + ReferenceCountedObjectPtr& operator= (ReferenceCountedObjectPtr&& other) + { + std::swap (referencedObject, other.referencedObject); + return *this; + } + #endif + + /** Destructor. + This will decrement the object's reference-count, which will cause the + object to be deleted when the ref-count hits zero. + */ + ~ReferenceCountedObjectPtr() + { + ReferencedType* const oldObject = referencedObject; // need to null the pointer before deleting the object + referencedObject = nullptr; // in case this ptr is itself deleted as a side-effect + decIfNotNull (oldObject); // of the destructor + } + + //============================================================================== + /** Returns the object that this pointer references. + The pointer returned may be null, of course. + */ + operator ReferencedType*() const noexcept { return referencedObject; } + + /** Returns the object that this pointer references. + The pointer returned may be null, of course. + */ + ReferencedType* get() const noexcept { return referencedObject; } + + /** Returns the object that this pointer references. + The pointer returned may be null, of course. + */ + ReferencedType* getObject() const noexcept { return referencedObject; } + + // the -> operator is called on the referenced object + ReferencedType* operator->() const noexcept + { + DISTRHO_SAFE_ASSERT (referencedObject != nullptr); // null pointer method call! + return referencedObject; + } + +private: + //============================================================================== + ReferencedType* referencedObject; + + static void incIfNotNull (ReferencedType* o) noexcept + { + if (o != nullptr) + o->incReferenceCount(); + } + + static void decIfNotNull (ReferencedType* o) noexcept + { + if (o != nullptr && o->decReferenceCountWithoutDeleting()) + delete o; + } +}; + + +//============================================================================== +/** Compares two ReferenceCountedObjectPtrs. */ +template +bool operator== (const ReferenceCountedObjectPtr& object1, ReferenceCountedObjectClass* const object2) noexcept +{ + return object1.get() == object2; +} + +/** Compares two ReferenceCountedObjectPtrs. */ +template +bool operator== (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1.get() == object2.get(); +} + +/** Compares two ReferenceCountedObjectPtrs. */ +template +bool operator== (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1 == object2.get(); +} + +/** Compares two ReferenceCountedObjectPtrs. */ +template +bool operator!= (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectClass* object2) noexcept +{ + return object1.get() != object2; +} + +/** Compares two ReferenceCountedObjectPtrs. */ +template +bool operator!= (const ReferenceCountedObjectPtr& object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1.get() != object2.get(); +} + +/** Compares two ReferenceCountedObjectPtrs. */ +template +bool operator!= (ReferenceCountedObjectClass* object1, const ReferenceCountedObjectPtr& object2) noexcept +{ + return object1 != object2.get(); +} + +} + +#endif // WATER_REFERENCECOUNTEDOBJECT_HPP_INCLUDED diff --git a/src/extra/ScopedLock.hpp b/src/extra/ScopedLock.hpp new file mode 100644 index 0000000..419e60a --- /dev/null +++ b/src/extra/ScopedLock.hpp @@ -0,0 +1,238 @@ +/* + ============================================================================== + + This file is part of the Water library. + Copyright (c) 2016 ROLI Ltd. + Copyright (C) 2017 Filipe Coelho + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ============================================================================== +*/ + +#ifndef WATER_SCOPEDLOCK_HPP_INCLUDED +#define WATER_SCOPEDLOCK_HPP_INCLUDED + +#include "DistrhoUtils.hpp" + +START_NAMESPACE_DISTRHO + +//============================================================================== +/** + Automatically locks and unlocks a mutex object. + + Use one of these as a local variable to provide RAII-based locking of a mutex. + + The templated class could be a CriticalSection, SpinLock, or anything else that + provides enter() and exit() methods. + + e.g. @code + CriticalSection myCriticalSection; + + for (;;) + { + const GenericScopedLock myScopedLock (myCriticalSection); + // myCriticalSection is now locked + + ...do some stuff... + + // myCriticalSection gets unlocked here. + } + @endcode + + @see GenericScopedUnlock, CriticalSection, SpinLock, ScopedLock, ScopedUnlock +*/ +template +class GenericScopedLock +{ +public: + //============================================================================== + /** Creates a GenericScopedLock. + + As soon as it is created, this will acquire the lock, and when the GenericScopedLock + object is deleted, the lock will be released. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit GenericScopedLock (const LockType& lock) noexcept : lock_ (lock) { lock.enter(); } + + /** Destructor. + The lock will be released when the destructor is called. + Make sure this object is created and deleted by the same thread, otherwise there are + no guarantees what will happen! + */ + inline ~GenericScopedLock() noexcept { lock_.exit(); } + +private: + //============================================================================== + const LockType& lock_; + + DISTRHO_DECLARE_NON_COPYABLE (GenericScopedLock) +}; + + +//============================================================================== +/** + Automatically unlocks and re-locks a mutex object. + + This is the reverse of a GenericScopedLock object - instead of locking the mutex + for the lifetime of this object, it unlocks it. + + Make sure you don't try to unlock mutexes that aren't actually locked! + + e.g. @code + + CriticalSection myCriticalSection; + + for (;;) + { + const GenericScopedLock myScopedLock (myCriticalSection); + // myCriticalSection is now locked + + ... do some stuff with it locked .. + + while (xyz) + { + ... do some stuff with it locked .. + + const GenericScopedUnlock unlocker (myCriticalSection); + + // myCriticalSection is now unlocked for the remainder of this block, + // and re-locked at the end. + + ...do some stuff with it unlocked ... + } + + // myCriticalSection gets unlocked here. + } + @endcode + + @see GenericScopedLock, CriticalSection, ScopedLock, ScopedUnlock +*/ +template +class GenericScopedUnlock +{ +public: + //============================================================================== + /** Creates a GenericScopedUnlock. + + As soon as it is created, this will unlock the CriticalSection, and + when the ScopedLock object is deleted, the CriticalSection will + be re-locked. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit GenericScopedUnlock (const LockType& lock) noexcept : lock_ (lock) { lock.exit(); } + + /** Destructor. + + The CriticalSection will be unlocked when the destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~GenericScopedUnlock() noexcept { lock_.enter(); } + + +private: + //============================================================================== + const LockType& lock_; + + DISTRHO_DECLARE_NON_COPYABLE (GenericScopedUnlock) +}; + + +//============================================================================== +/** + Automatically locks and unlocks a mutex object. + + Use one of these as a local variable to provide RAII-based locking of a mutex. + + The templated class could be a CriticalSection, SpinLock, or anything else that + provides enter() and exit() methods. + + e.g. @code + + CriticalSection myCriticalSection; + + for (;;) + { + const GenericScopedTryLock myScopedTryLock (myCriticalSection); + + // Unlike using a ScopedLock, this may fail to actually get the lock, so you + // should test this with the isLocked() method before doing your thread-unsafe + // action.. + if (myScopedTryLock.isLocked()) + { + ...do some stuff... + } + else + { + ..our attempt at locking failed because another thread had already locked it.. + } + + // myCriticalSection gets unlocked here (if it was locked) + } + @endcode + + @see CriticalSection::tryEnter, GenericScopedLock, GenericScopedUnlock +*/ +template +class GenericScopedTryLock +{ +public: + //============================================================================== + /** Creates a GenericScopedTryLock. + + As soon as it is created, this will attempt to acquire the lock, and when the + GenericScopedTryLock is deleted, the lock will be released (if the lock was + successfully acquired). + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! Best just to use it + as a local stack object, rather than creating one with the new() operator. + */ + inline explicit GenericScopedTryLock (const LockType& lock) noexcept + : lock_ (lock), lockWasSuccessful (lock.tryEnter()) {} + + /** Destructor. + + The mutex will be unlocked (if it had been successfully locked) when the + destructor is called. + + Make sure this object is created and deleted by the same thread, + otherwise there are no guarantees what will happen! + */ + inline ~GenericScopedTryLock() noexcept { if (lockWasSuccessful) lock_.exit(); } + + /** Returns true if the mutex was successfully locked. */ + bool isLocked() const noexcept { return lockWasSuccessful; } + +private: + //============================================================================== + const LockType& lock_; + const bool lockWasSuccessful; + + DISTRHO_DECLARE_NON_COPYABLE (GenericScopedTryLock) +}; + +END_NAMESPACE_DISTRHO + +#endif // WATER_SCOPEDLOCK_HPP_INCLUDED diff --git a/src/extra/SharedResourcePointer.hpp b/src/extra/SharedResourcePointer.hpp new file mode 100644 index 0000000..005c42e --- /dev/null +++ b/src/extra/SharedResourcePointer.hpp @@ -0,0 +1,189 @@ +/* + ============================================================================== + + This file is part of the Water library. + Copyright (c) 2016 ROLI Ltd. + Copyright (C) 2017-2019 Filipe Coelho + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ============================================================================== +*/ + +#ifndef WATER_SHAREDRESOURCEPOINTER_HPP_INCLUDED +#define WATER_SHAREDRESOURCEPOINTER_HPP_INCLUDED + +#include "ReferenceCountedObject.hpp" +#include "SpinLock.hpp" +#include "extra/ScopedPointer.hpp" + +START_NAMESPACE_DISTRHO + +//============================================================================== +/** + A smart-pointer that automatically creates and manages the lifetime of a + shared static instance of a class. + + The SharedObjectType template type indicates the class to use for the shared + object - the only requirements on this class are that it must have a public + default constructor and destructor. + + The SharedResourcePointer offers a pattern that differs from using a singleton or + static instance of an object, because it uses reference-counting to make sure that + the underlying shared object is automatically created/destroyed according to the + number of SharedResourcePointer objects that exist. When the last one is deleted, + the underlying object is also immediately destroyed. This allows you to use scoping + to manage the lifetime of a shared resource. + + Note: the construction/deletion of the shared object must not involve any + code that makes recursive calls to a SharedResourcePointer, or you'll cause + a deadlock. + + Example: + @code + // An example of a class that contains the shared data you want to use. + struct MySharedData + { + // There's no need to ever create an instance of this class directly yourself, + // but it does need a public constructor that does the initialisation. + MySharedData() + { + sharedStuff = generateHeavyweightStuff(); + } + + Array sharedStuff; + }; + + struct DataUserClass + { + DataUserClass() + { + // Multiple instances of the DataUserClass will all have the same + // shared common instance of MySharedData referenced by their sharedData + // member variables. + useSharedStuff (sharedData->sharedStuff); + } + + // By keeping this pointer as a member variable, the shared resource + // is guaranteed to be available for as long as the DataUserClass object. + SharedResourcePointer sharedData; + }; + + @endcode + */ +template +class SharedResourcePointer +{ +public: + /** Creates an instance of the shared object. + If other SharedResourcePointer objects for this type already exist, then + this one will simply point to the same shared object that they are already + using. Otherwise, if this is the first SharedResourcePointer to be created, + then a shared object will be created automatically. + */ + SharedResourcePointer() + : sharedObject(nullptr) + { + initialise(); + } + + template + SharedResourcePointer(const T* const variant) + : sharedObject(nullptr) + { + initialise_variant(variant); + } + + SharedResourcePointer (const SharedResourcePointer&) + : sharedObject(nullptr) + { + initialise(); + } + + /** Destructor. + If no other SharedResourcePointer objects exist, this will also delete + the shared object to which it refers. + */ + ~SharedResourcePointer() + { + SharedObjectHolder& holder = getSharedObjectHolder(); + const SpinLock::ScopedLockType sl (holder.lock); + + if (--(holder.refCount) == 0) + holder.sharedInstance = nullptr; + } + + /** Returns the shared object. */ + operator SharedObjectType*() const noexcept { return sharedObject; } + + /** Returns the shared object. */ + SharedObjectType& get() const noexcept { return *sharedObject; } + + /** Returns the object that this pointer references. + The pointer returned may be a nullptr, of course. + */ + SharedObjectType& getObject() const noexcept { return *sharedObject; } + SharedObjectType* getPointer() const noexcept { return sharedObject; } + + SharedObjectType* operator->() const noexcept { return sharedObject; } + +private: + struct SharedObjectHolder : public ReferenceCountedObject + { + SpinLock lock; + ScopedPointer sharedInstance; + int refCount; + }; + + static SharedObjectHolder& getSharedObjectHolder() noexcept + { + static void* holder [(sizeof (SharedObjectHolder) + sizeof(void*) - 1) / sizeof(void*)] = { nullptr }; + return *reinterpret_cast (holder); + } + + SharedObjectType* sharedObject; + + void initialise() + { + SharedObjectHolder& holder = getSharedObjectHolder(); + const SpinLock::ScopedLockType sl (holder.lock); + + if (++(holder.refCount) == 1) + holder.sharedInstance = new SharedObjectType(); + + sharedObject = holder.sharedInstance; + } + + template + void initialise_variant(const T* const variant) + { + SharedObjectHolder& holder = getSharedObjectHolder(); + const SpinLock::ScopedLockType sl (holder.lock); + + if (++(holder.refCount) == 1) + holder.sharedInstance = new SharedObjectType(variant); + + sharedObject = holder.sharedInstance; + } + + // There's no need to assign to a SharedResourcePointer because every + // instance of the class is exactly the same! + SharedResourcePointer& operator= (const SharedResourcePointer&) = delete; +}; + +} + +#endif // WATER_SHAREDRESOURCEPOINTER_HPP_INCLUDED diff --git a/src/extra/SpinLock.hpp b/src/extra/SpinLock.hpp new file mode 100644 index 0000000..210f1dd --- /dev/null +++ b/src/extra/SpinLock.hpp @@ -0,0 +1,110 @@ +/* + ============================================================================== + + This file is part of the Water library. + Copyright (c) 2016 ROLI Ltd. + Copyright (C) 2017 Filipe Coelho + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. + + ============================================================================== +*/ + +#ifndef WATER_SPINLOCK_HPP_INCLUDED +#define WATER_SPINLOCK_HPP_INCLUDED + +#include "Atomic.hpp" +#include "ScopedLock.hpp" + +START_NAMESPACE_DISTRHO + +//============================================================================== +/** + A simple spin-lock class that can be used as a simple, low-overhead mutex for + uncontended situations. + + Note that unlike a CriticalSection, this type of lock is not re-entrant, and may + be less efficient when used it a highly contended situation, but it's very small and + requires almost no initialisation. + It's most appropriate for simple situations where you're only going to hold the + lock for a very brief time. + + @see CriticalSection +*/ +class SpinLock +{ +public: + inline SpinLock() noexcept : lock() {} + inline ~SpinLock() noexcept {} + + /** Acquires the lock. + This will block until the lock has been successfully acquired by this thread. + Note that a SpinLock is NOT re-entrant, and is not smart enough to know whether the + caller thread already has the lock - so if a thread tries to acquire a lock that it + already holds, this method will never return! + + It's strongly recommended that you never call this method directly - instead use the + ScopedLockType class to manage the locking using an RAII pattern instead. + */ + void enter() const noexcept + { + if (! tryEnter()) + { + for (int i = 20; --i >= 0;) + if (tryEnter()) + return; + + while (! tryEnter()) + { +#ifdef DISTRHO_OS_WINDOWS + Sleep (0); +#else + sched_yield(); +#endif + } + } + } + + /** Attempts to acquire the lock, returning true if this was successful. */ + inline bool tryEnter() const noexcept + { + return lock.compareAndSetBool (1, 0); + } + + /** Releases the lock. */ + inline void exit() const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN (lock.get() == 1,); + lock = 0; + } + + //============================================================================== + /** Provides the type of scoped lock to use for locking a SpinLock. */ + typedef GenericScopedLock ScopedLockType; + + /** Provides the type of scoped unlocker to use with a SpinLock. */ + typedef GenericScopedUnlock ScopedUnlockType; + +private: + //============================================================================== + mutable Atomic lock; + + DISTRHO_DECLARE_NON_COPYABLE (SpinLock) +}; + +END_NAMESPACE_DISTRHO + +#endif // WATER_SPINLOCK_HPP_INCLUDED