From 825320dafaa076e764bc0a16884d68d4ca8743fa Mon Sep 17 00:00:00 2001 From: hogliux Date: Mon, 30 Apr 2018 09:59:57 +0100 Subject: [PATCH 1/5] Adding missing locks in ListenerList when the underlying array of the ListenerList uses a CriticalSection --- modules/juce_core/containers/juce_ListenerList.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/juce_core/containers/juce_ListenerList.h b/modules/juce_core/containers/juce_ListenerList.h index 6e537ee879..493ece2a0b 100644 --- a/modules/juce_core/containers/juce_ListenerList.h +++ b/modules/juce_core/containers/juce_ListenerList.h @@ -118,6 +118,8 @@ public: template void call (Callback&& callback) { + typename ArrayType::ScopedLockType lock (listeners.getLock()); + for (Iterator iter (*this); iter.next();) callback (*iter.getListener()); } @@ -128,6 +130,8 @@ public: template void callExcluding (ListenerClass* listenerToExclude, Callback&& callback) { + typename ArrayType::ScopedLockType lock (listeners.getLock()); + for (Iterator iter (*this); iter.next();) { auto* l = iter.getListener(); @@ -143,6 +147,8 @@ public: template void callChecked (const BailOutCheckerType& bailOutChecker, Callback&& callback) { + typename ArrayType::ScopedLockType lock (listeners.getLock()); + for (Iterator iter (*this); iter.next (bailOutChecker);) callback (*iter.getListener()); } @@ -156,6 +162,8 @@ public: const BailOutCheckerType& bailOutChecker, Callback&& callback) { + typename ArrayType::ScopedLockType lock (listeners.getLock()); + for (Iterator iter (*this); iter.next (bailOutChecker);) { auto* l = iter.getListener(); @@ -252,6 +260,8 @@ public: template void call (void (ListenerClass::*callbackFunction) (MethodArgs...), Args&&... args) { + typename ArrayType::ScopedLockType lock (listeners.getLock()); + for (Iterator iter (*this); iter.next();) (iter.getListener()->*callbackFunction) (static_cast::type> (args)...); } @@ -261,6 +271,8 @@ public: void (ListenerClass::*callbackFunction) (MethodArgs...), Args&&... args) { + typename ArrayType::ScopedLockType lock (listeners.getLock()); + for (Iterator iter (*this); iter.next();) if (iter.getListener() != listenerToExclude) (iter.getListener()->*callbackFunction) (static_cast::type> (args)...); @@ -271,6 +283,8 @@ public: void (ListenerClass::*callbackFunction) (MethodArgs...), Args&&... args) { + typename ArrayType::ScopedLockType lock (listeners.getLock()); + for (Iterator iter (*this); iter.next (bailOutChecker);) (iter.getListener()->*callbackFunction) (static_cast::type> (args)...); } @@ -281,6 +295,8 @@ public: void (ListenerClass::*callbackFunction) (MethodArgs...), Args&&... args) { + typename ArrayType::ScopedLockType lock (listeners.getLock()); + for (Iterator iter (*this); iter.next (bailOutChecker);) if (iter.getListener() != listenerToExclude) (iter.getListener()->*callbackFunction) (static_cast::type> (args)...); From 8782bdd6ba5fee42e4db22b36506b1e18193df6c Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Mon, 23 Apr 2018 11:48:03 +0100 Subject: [PATCH 2/5] Fixed a bug handling queued analytics events after a failed server connection attempt --- .../juce_ThreadedAnalyticsDestination.cpp | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp b/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp index 14027d3501..74032e049c 100644 --- a/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp +++ b/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp @@ -89,20 +89,24 @@ void ThreadedAnalyticsDestination::EventDispatcher::run() while (! threadShouldExit()) { - auto eventsToSendCapacity = maxBatchSize - eventsToSend.size(); - - if (eventsToSendCapacity > 0) { - const ScopedLock lock (queueAccess); - - const auto numEventsInQueue = (int) eventQueue.size(); + const auto numEventsInBatch = eventsToSend.size(); + const auto freeBatchCapacity = maxBatchSize - numEventsInBatch; - if (numEventsInQueue > 0) + if (freeBatchCapacity > 0) { - const auto numEventsToAdd = jmin (eventsToSendCapacity, numEventsInQueue); + const auto numNewEvents = (int) eventQueue.size() - numEventsInBatch; + + if (numNewEvents > 0) + { + const ScopedLock lock (queueAccess); + + const auto numEventsToAdd = jmin (numNewEvents, freeBatchCapacity); + const auto newBatchSize = numEventsInBatch + numEventsToAdd; - for (size_t i = 0; i < (size_t) numEventsToAdd; ++i) - eventsToSend.add (eventQueue[i]); + for (size_t i = numEventsInBatch; i < (size_t) newBatchSize; ++i) + eventsToSend.add (eventQueue[i]); + } } } From 58f88ffeb8e91175478ad17fb73e4a3bf0562328 Mon Sep 17 00:00:00 2001 From: hogliux Date: Tue, 24 Apr 2018 10:17:46 +0100 Subject: [PATCH 3/5] macOS/iOS: Continue to use text layout fallback for fonts loaded from memory You can override this by defining JUCE_FORCE_USE_NATIVE_TEXT_LAYOUT_FOR_MEMORY_FONTS=1 --- modules/juce_graphics/native/juce_mac_Fonts.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/juce_graphics/native/juce_mac_Fonts.mm b/modules/juce_graphics/native/juce_mac_Fonts.mm index 92a5c143a7..c89fb5068e 100644 --- a/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/modules/juce_graphics/native/juce_mac_Fonts.mm @@ -815,12 +815,12 @@ Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) // fallback layout algorithm. static bool canAllTypefacesBeUsedInLayout (const AttributedString& text) { - #if JUCE_MAC && defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 + #if JUCE_MAC && defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 && JUCE_FORCE_USE_NATIVE_TEXT_LAYOUT_FOR_MEMORY_FONTS ignoreUnused (text); return true; #else - #if JUCE_MAC + #if JUCE_MAC && JUCE_FORCE_USE_NATIVE_TEXT_LAYOUT_FOR_MEMORY_FONTS if (SystemStats::getOperatingSystemType() >= SystemStats::OperatingSystemType::MacOSX_10_11) return true; #endif From 9edfea24860b6e12d43bc67e4a7f5c7836978988 Mon Sep 17 00:00:00 2001 From: Lukasz Kozakiewicz Date: Wed, 25 Apr 2018 16:00:15 +0200 Subject: [PATCH 4/5] SparseSet: fix removeRange() and add unit tests. --- .../juce_core/containers/juce_SparseSet.cpp | 204 ++++++++++++++++++ modules/juce_core/containers/juce_SparseSet.h | 28 ++- modules/juce_core/juce_core.cpp | 1 + 3 files changed, 224 insertions(+), 9 deletions(-) create mode 100644 modules/juce_core/containers/juce_SparseSet.cpp diff --git a/modules/juce_core/containers/juce_SparseSet.cpp b/modules/juce_core/containers/juce_SparseSet.cpp new file mode 100644 index 0000000000..da215de89f --- /dev/null +++ b/modules/juce_core/containers/juce_SparseSet.cpp @@ -0,0 +1,204 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2018 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided 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. + + 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 +{ + +#if JUCE_UNIT_TESTS + +class SparseSetTests : public UnitTest +{ +public: + SparseSetTests() : UnitTest ("SparseSet class", "Containers") {} + + void runTest() override + { + beginTest ("basic operations"); + { + SparseSet set; + + expect (set.isEmpty()); + expectEquals (set.size(), 0); + expectEquals (set.getNumRanges(), 0); + expect (set.getTotalRange().isEmpty()); + + set.addRange ({0, 10}); + expect (! set.isEmpty()); + expectEquals (set.size(), 10); + expectEquals (set.getNumRanges(), 1); + expect (! set.getTotalRange().isEmpty()); + expect (set.getRange (0) == Range (0, 10)); + + expectEquals (set[0], 0); + expectEquals (set[5], 5); + expectEquals (set[9], 9); + // Index out of range yields a default value for a type + expectEquals (set[10], 0); + expect (set.contains (0)); + expect (set.contains (9)); + expect (! set.contains (10)); + } + + beginTest ("adding ranges"); + { + SparseSet set; + + // Adding same range twice should yield just a single range + set.addRange ({0, 10}); + set.addRange ({0, 10}); + expectEquals (set.getNumRanges(), 1); + expect (set.getRange (0) == Range (0, 10)); + + // Adding already included range does not increase num ranges + set.addRange ({0, 2}); + expectEquals (set.getNumRanges(), 1); + set.addRange ({8, 10}); + expectEquals (set.getNumRanges(), 1); + set.addRange ({2, 5}); + expectEquals (set.getNumRanges(), 1); + + // Adding non adjacent range includes total number of ranges + set.addRange ({-10, -5}); + expectEquals (set.getNumRanges(), 2); + expect (set.getRange (0) == Range (-10, -5)); + expect (set.getRange (1) == Range (0, 10)); + expect (set.getTotalRange() == Range (-10, 10)); + + set.addRange ({15, 20}); + expectEquals (set.getNumRanges(), 3); + expect (set.getRange (0) == Range (-10, -5)); + expect (set.getRange (1) == Range (0, 10)); + expect (set.getRange (2) == Range (15, 20)); + expect (set.getTotalRange() == Range (-10, 20)); + + // Adding adjacent ranges merges them. + set.addRange ({-5, -3}); + expectEquals (set.getNumRanges(), 3); + expect (set.getRange (0) == Range (-10, -3)); + expect (set.getRange (1) == Range (0, 10)); + expect (set.getRange (2) == Range (15, 20)); + expect (set.getTotalRange() == Range (-10, 20)); + + set.addRange ({20, 25}); + expectEquals (set.getNumRanges(), 3); + expect (set.getRange (0) == Range (-10, -3)); + expect (set.getRange (1) == Range (0, 10)); + expect (set.getRange (2) == Range (15, 25)); + expect (set.getTotalRange() == Range (-10, 25)); + + // Adding range containing other ranges merges them + set.addRange ({-50, 50}); + expectEquals (set.getNumRanges(), 1); + expect (set.getRange (0) == Range (-50, 50)); + expect (set.getTotalRange() == Range (-50, 50)); + } + + beginTest ("removing ranges"); + { + SparseSet set; + + set.addRange ({-20, -10}); + set.addRange ({0, 10}); + set.addRange ({20, 30}); + expectEquals (set.getNumRanges(), 3); + + // Removing ranges not included in the set has no effect + set.removeRange ({-5, 5}); + expectEquals (set.getNumRanges(), 3); + + // Removing partially overlapping range + set.removeRange ({-15, 5}); + expectEquals (set.getNumRanges(), 3); + expect (set.getRange (0) == Range (-20, -15)); + expect (set.getRange (1) == Range (5, 10)); + expect (set.getRange (2) == Range (20, 30)); + + // Removing subrange of existing range + set.removeRange ({20, 22}); + expectEquals (set.getNumRanges(), 3); + expect (set.getRange (2) == Range (22, 30)); + + set.removeRange ({28, 30}); + expectEquals (set.getNumRanges(), 3); + expect (set.getRange (2) == Range (22, 28)); + + set.removeRange ({24, 26}); + expectEquals (set.getNumRanges(), 4); + expect (set.getRange (0) == Range (-20, -15)); + expect (set.getRange (1) == Range (5, 10)); + expect (set.getRange (2) == Range (22, 24)); + expect (set.getRange (3) == Range (26, 28)); + } + + beginTest ("XORing ranges"); + { + SparseSet set; + set.addRange ({0, 10}); + + set.invertRange ({0, 10}); + expectEquals (set.getNumRanges(), 0); + set.invertRange ({0, 10}); + expectEquals (set.getNumRanges(), 1); + + set.invertRange ({4, 6}); + expectEquals (set.getNumRanges(), 2); + expect (set.getRange (0) == Range (0, 4)); + expect (set.getRange (1) == Range (6, 10)); + + set.invertRange ({-2, 2}); + expectEquals (set.getNumRanges(), 3); + expect (set.getRange (0) == Range (-2, 0)); + expect (set.getRange (1) == Range (2, 4)); + expect (set.getRange (2) == Range (6, 10)); + } + + beginTest ("range contains & overlaps checks"); + { + SparseSet set; + set.addRange ({0, 10}); + + expect (set.containsRange (Range (0, 2))); + expect (set.containsRange (Range (8, 10))); + expect (set.containsRange (Range (0, 10))); + + expect (! set.containsRange (Range (-2, 0))); + expect (! set.containsRange (Range (-2, 10))); + expect (! set.containsRange (Range (10, 12))); + expect (! set.containsRange (Range (0, 12))); + + expect (set.overlapsRange (Range (0, 2))); + expect (set.overlapsRange (Range (8, 10))); + expect (set.overlapsRange (Range (0, 10))); + + expect (! set.overlapsRange (Range (-2, 0))); + expect ( set.overlapsRange (Range (-2, 10))); + expect (! set.overlapsRange (Range (10, 12))); + expect ( set.overlapsRange (Range (0, 12))); + } + } +}; + +static SparseSetTests sparseSetTests; + +#endif + +} // namespace juce diff --git a/modules/juce_core/containers/juce_SparseSet.h b/modules/juce_core/containers/juce_SparseSet.h index d1c0d2c836..11c2c8ee73 100644 --- a/modules/juce_core/containers/juce_SparseSet.h +++ b/modules/juce_core/containers/juce_SparseSet.h @@ -169,23 +169,33 @@ public: if (r.getStart() >= rangeToRemove.getEnd()) continue; - if (r.contains (rangeToRemove)) + if (rangeToRemove.contains (r)) { - auto start = r.withEnd (rangeToRemove.getStart()); - r.setStart (rangeToRemove.getEnd()); - ranges.insert (i, start); + ranges.remove (i); } - else if (rangeToRemove.contains (r)) + else if (r.contains (rangeToRemove)) { - ranges.remove (i); + auto r1 = r.withEnd (rangeToRemove.getStart()); + auto r2 = r.withStart (rangeToRemove.getEnd()); + + // this should be covered in if (rangeToRemove.contains (r)) + jassert (! r1.isEmpty() || ! r2.isEmpty()); + + r = r1; + + if (r.isEmpty()) + r = r2; + + if (! r1.isEmpty() && ! r2.isEmpty()) + ranges.insert (i + 1, r2); } - else if (rangeToRemove.getEnd() > r.getStart()) + else if (rangeToRemove.getEnd() > r.getEnd()) { - r.setStart (rangeToRemove.getEnd()); + r.setEnd (rangeToRemove.getStart()); } else { - r.setEnd (rangeToRemove.getStart()); + r.setStart (rangeToRemove.getEnd()); } } } diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp index 4784f65936..9725a1b5a6 100644 --- a/modules/juce_core/juce_core.cpp +++ b/modules/juce_core/juce_core.cpp @@ -120,6 +120,7 @@ #include "containers/juce_AbstractFifo.cpp" #include "containers/juce_NamedValueSet.cpp" #include "containers/juce_PropertySet.cpp" +#include "containers/juce_SparseSet.cpp" #include "containers/juce_Variant.cpp" #include "files/juce_DirectoryIterator.cpp" #include "files/juce_File.cpp" From a9a2dd5607e5b37c42642a637530efc513782138 Mon Sep 17 00:00:00 2001 From: hogliux Date: Mon, 30 Apr 2018 10:14:51 +0100 Subject: [PATCH 5/5] Fixed a sign conversion warning --- .../destinations/juce_ThreadedAnalyticsDestination.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp b/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp index 74032e049c..7596bbbcec 100644 --- a/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp +++ b/modules/juce_analytics/destinations/juce_ThreadedAnalyticsDestination.cpp @@ -104,7 +104,7 @@ void ThreadedAnalyticsDestination::EventDispatcher::run() const auto numEventsToAdd = jmin (numNewEvents, freeBatchCapacity); const auto newBatchSize = numEventsInBatch + numEventsToAdd; - for (size_t i = numEventsInBatch; i < (size_t) newBatchSize; ++i) + for (size_t i = (size_t) numEventsInBatch; i < (size_t) newBatchSize; ++i) eventsToSend.add (eventQueue[i]); } }