diff --git a/modules/juce_graphics/detail/juce_Ranges.cpp b/modules/juce_graphics/detail/juce_Ranges.cpp index 3faecf8ebe..2c88c9fb1a 100644 --- a/modules/juce_graphics/detail/juce_Ranges.cpp +++ b/modules/juce_graphics/detail/juce_Ranges.cpp @@ -195,9 +195,12 @@ public: auto ops = ranges.split (47); - expectEquals ((int64) ops.size(), (int64) 2, ""); - expect (std::get_if (&ops[0]) != nullptr); - expect (std::get_if (&ops[1]) != nullptr); + expectEquals ((int64) ops.size(), (int64) 1, ""); + const auto op0 = std::get_if (&ops[0]); + expect (op0 != nullptr); + + if (op0 != nullptr) + expectEquals ((int) op0->index, 0, "The 0th element should be split"); expectEquals ((int64) ranges.size(), (int64) 3, ""); expectRange (ranges.get (0), { 0, 47 }); @@ -252,6 +255,157 @@ public: expect (ranges.isEmpty()); expect (ops.empty()); } + + const auto getTestRanges = [] + { + Ranges ranges; + + ranges.set ({ 0, 48 }); + ranges.set ({ 48, 49 }); + ranges.set ({ 55, 94 }); + ranges.set ({ 94, 127 }); + + return ranges; + }; + + beginTest ("Ranges::eraseFrom() - erasing beyond all ranges has no effect"); + { + auto ranges = getTestRanges(); + ranges.eraseFrom (ranges.get (ranges.size() - 1).getEnd() + 5); + + expectEquals ((int64) ranges.size(), (int64) 4, ""); + expectRange (ranges.get (0), { 0, 48 }); + expectRange (ranges.get (1), { 48, 49 }); + expectRange (ranges.get (2), { 55, 94 }); + expectRange (ranges.get (3), { 94, 127 }); + } + + beginTest ("Ranges::eraseFrom() - erasing modifies the range that encloses the starting index"); + { + auto ranges = getTestRanges(); + ranges.eraseFrom (122); + + expectEquals ((int64) ranges.size(), (int64) 4, ""); + expectRange (ranges.get (0), { 0, 48 }); + expectRange (ranges.get (1), { 48, 49 }); + expectRange (ranges.get (2), { 55, 94 }); + expectRange (ranges.get (3), { 94, 122 }); + } + + beginTest ("Ranges::eraseFrom() - ranges starting after the erased index are deleted entirely"); + { + auto ranges = getTestRanges(); + ranges.eraseFrom (60); + + expectEquals ((int64) ranges.size(), (int64) 3, ""); + expectRange (ranges.get (0), { 0, 48 }); + expectRange (ranges.get (1), { 48, 49 }); + expectRange (ranges.get (2), { 55, 60 }); + } + + beginTest ("Ranges::eraseFrom() - erasing from a location outside any ranges will still drop subsequent ranges"); + { + auto ranges = getTestRanges(); + ranges.eraseFrom (51); + + expectEquals ((int64) ranges.size(), (int64) 2, ""); + expectRange (ranges.get (0), { 0, 48 }); + expectRange (ranges.get (1), { 48, 49 }); + } + + beginTest ("Ranges::erase() - erasing a zero length range has no effect"); + { + auto ranges = getTestRanges(); + + ranges.erase ({ 30, 30 }); + + expectEquals ((int64) ranges.size(), (int64) 4, ""); + expectRange (ranges.get (0), { 0, 48 }); + expectRange (ranges.get (1), { 48, 49 }); + expectRange (ranges.get (2), { 55, 94 }); + expectRange (ranges.get (3), { 94, 127 }); + } + + beginTest ("Ranges::erase() - erasing inside a range splits that range"); + { + auto ranges = getTestRanges(); + + ranges.erase ({ 30, 31 }); + + expectEquals ((int64) ranges.size(), (int64) 5, ""); + expectRange (ranges.get (0), { 0, 30 }); + expectRange (ranges.get (1), { 31, 48 }); + expectRange (ranges.get (2), { 48, 49 }); + expectRange (ranges.get (3), { 55, 94 }); + expectRange (ranges.get (4), { 94, 127 }); + } + + beginTest ("Ranges::erase() - erasing a range that completely overlaps existing ranges erases those"); + { + auto ranges = getTestRanges(); + + ranges.erase ({ 30, 70 }); + + expectEquals ((int64) ranges.size(), (int64) 3, ""); + expectRange (ranges.get (0), { 0, 30 }); + expectRange (ranges.get (1), { 70, 94 }); + expectRange (ranges.get (2), { 94, 127 }); + } + + beginTest ("Ranges::erase() - erasing a range that no previous ranges covered has no effect"); + { + auto ranges = getTestRanges(); + + ranges.erase ({ 51, 53 }); + + expectEquals ((int64) ranges.size(), (int64) 4, ""); + expectRange (ranges.get (0), { 0, 48 }); + expectRange (ranges.get (1), { 48, 49 }); + expectRange (ranges.get (2), { 55, 94 }); + expectRange (ranges.get (3), { 94, 127 }); + } + + beginTest ("Ranges::erase() - erasing over a range beyond all limits clears all ranges"); + { + auto ranges = getTestRanges(); + + ranges.erase ({ -1000, 1000 }); + + expect (ranges.isEmpty()); + } + + beginTest ("Ranges::drop() - dropping a range shifts all following ranges downward"); + { + auto ranges = getTestRanges(); + + ranges.drop ({ 48, 49 }); + + expectEquals ((int64) ranges.size(), (int64) 3, ""); + expectRange (ranges.get (0), { 0, 48 }); + expectRange (ranges.get (1), { 54, 93 }); + expectRange (ranges.get (2), { 93, 126 }); + } + + beginTest ("Ranges::drop() - dropping a range shifts all following ranges downward even if no range covered the dropped range previously"); + { + auto ranges = getTestRanges(); + + ranges.drop ({ 51, 53 }); + + expectEquals ((int64) ranges.size(), (int64) 4, ""); + expectRange (ranges.get (0), { 0, 48 }); + expectRange (ranges.get (1), { 48, 49 }); + expectRange (ranges.get (2), { 53, 92 }); + expectRange (ranges.get (3), { 92, 125 }); + } + + beginTest ("Ranges::drop() - dropping a range covering all other ranges empties the collection"); + { + auto ranges = getTestRanges(); + + ranges.drop ({ -1000, 1000 }); + expect (ranges.isEmpty()); + } } }; @@ -481,6 +635,79 @@ public: expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a'); } } + + const auto createRangedValuesObjectForErase = [&] + { + RangedValues rangedValues; + + rangedValues.set ({ 0, 10 }, 'a'); + rangedValues.set ({ 11, 20 }, 'b'); + rangedValues.set ({ 23, 30 }, 'c'); + rangedValues.set ({ 35, 45 }, 'c'); + rangedValues.set ({ 45, 60 }, 'd'); + + return rangedValues; + }; + + beginTest ("RangedValues::erase() - erase will not shift subsequent ranges downward"); + { + auto rangedValues = createRangedValuesObjectForErase(); + + rangedValues.erase ({ 15, 16 }); + + expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (1), { 11, 15 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (2), { 16, 20 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c'); + expectRangedValuesItem (rangedValues.getItem (4), { 35, 45 }, 'c'); + expectRangedValuesItem (rangedValues.getItem (5), { 45, 60 }, 'd'); + } + + beginTest ("RangedValues::drop() - drop shifts ranges downward on the right side"); + { + auto rangedValues = createRangedValuesObjectForErase(); + + rangedValues.drop ({ 15, 16 }); + + expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (1), { 11, 15 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (2), { 15, 19 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (3), { 22, 29 }, 'c'); + expectRangedValuesItem (rangedValues.getItem (4), { 34, 44 }, 'c'); + expectRangedValuesItem (rangedValues.getItem (5), { 44, 59 }, 'd'); + } + + beginTest ("RangedValues::drop() - drop can be used to merge equal values"); + { + auto rangedValues = createRangedValuesObjectForErase(); + + rangedValues.drop ({ 15, 16 }); + + expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a'); + expectRangedValuesItem (rangedValues.getItem (1), { 11, 19 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (2), { 22, 29 }, 'c'); + expectRangedValuesItem (rangedValues.getItem (3), { 34, 44 }, 'c'); + expectRangedValuesItem (rangedValues.getItem (4), { 44, 59 }, 'd'); + } + + beginTest ("RangedValues::drop() - the merge happens at the seam caused by the drop and does not extend beyond"); + { + auto rangedValues = createRangedValuesObjectForErase(); + rangedValues.set ({ 20, 30 }, 'b'); + + rangedValues.drop ({ 15, 16 }); + + expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a'); + + // These two items are not merged, even though they form a contiguous range, because + // they were disjoint before the drop and they don't touch each other at the drop + // seam of 15. + expectRangedValuesItem (rangedValues.getItem (1), { 11, 19 }, 'b'); + expectRangedValuesItem (rangedValues.getItem (2), { 19, 29 }, 'b'); + + expectRangedValuesItem (rangedValues.getItem (3), { 34, 44 }, 'c'); + expectRangedValuesItem (rangedValues.getItem (4), { 44, 59 }, 'd'); + } } }; diff --git a/modules/juce_graphics/detail/juce_Ranges.h b/modules/juce_graphics/detail/juce_Ranges.h index e84f496e56..2a175e6f8a 100644 --- a/modules/juce_graphics/detail/juce_Ranges.h +++ b/modules/juce_graphics/detail/juce_Ranges.h @@ -45,19 +45,15 @@ inline std::optional> getRangeIntersectionWith (Range r1, Ra return Range { intersection }; } -inline size_t clampCast (int64 v) -{ - return v < 0 ? 0 : (size_t) v; -} - -/* Used in Ranges::getAffectedElements(). Yes means that if a change occurs to range i, we also - want ranges i - 1 and i + 1 included in the affected elements. +/* This is to get rid of the warning where advance isn't of type difference_type. */ -enum class IncludeAdjacentRanges +template +auto iteratorWithAdvance (Iterator&& it, Value advance) { - no, - yes -}; + auto outIt = std::move (it); + std::advance (outIt, static_cast::difference_type> (advance)); + return outIt; +} struct Ranges final { @@ -65,16 +61,53 @@ struct Ranges final { Ops() = delete; - struct Erased { Range range; }; - struct Inserted { size_t index; }; - struct Reinserted { size_t index; }; - struct Changed { size_t index; }; + struct New + { + New() = delete; + + size_t index; + }; + + struct Split + { + Split() = delete; + + size_t index; + }; + + struct Erase + { + Erase() = delete; + + Range range; + }; + + struct Change + { + Change() = delete; + + size_t index; + }; }; - using Op = std::variant; + using Op = std::variant; using Operations = std::vector; + static auto withOperationsFrom (const Operations& ops, const Operations& newOps) + { + auto result = ops; + result.insert (result.end(), newOps.begin(), newOps.end()); + return result; + } + + static auto withOperationsFrom (const Operations& ops, Op newOp) + { + auto result = ops; + result.insert (result.end(), newOp); + return result; + } + Ranges() = default; explicit Ranges (std::vector> rangesIn) @@ -88,11 +121,182 @@ struct Ranges final #endif } + //============================================================================== + // Basic operations + Operations split (int64 i) + { + Operations ops; + + const auto elemIndex = getIndexForEnclosingRange (i); + + if (! elemIndex.has_value()) + return {}; + + auto& elem = ranges[*elemIndex]; + + if (elem.getStart() == i) + return {}; + + ops = withOperationsFrom (ops, Ops::Split { *elemIndex }); + + const auto oldLength = elem.getLength(); + elem.setEnd (i); + + ranges.insert (iteratorWithAdvance (ranges.begin(), *elemIndex + 1), + { i, i + oldLength - elem.getLength() }); + + return ops; + } + + Operations erase (Range r) + { + if (r.isEmpty()) + return {}; + + Operations ops; + + for (auto i : { r.getStart(), r.getEnd() }) + ops = withOperationsFrom (ops, split (i)); + + const auto firstToDelete = std::lower_bound (ranges.begin(), + ranges.end(), + r.getStart(), + [] (auto& elem, auto& value) + { return elem.getStart() < value; }); + + const auto beyondLastToDelete = std::lower_bound (firstToDelete, + ranges.end(), + r.getEnd(), + [] (auto& elem, auto& value) + { return elem.getStart() < value; }); + + if (firstToDelete != ranges.end()) + ops = withOperationsFrom (ops, Ops::Erase { { getIndex (firstToDelete), getIndex (beyondLastToDelete) } }); + + ranges.erase (firstToDelete, beyondLastToDelete); + + return ops; + } + + Operations drop (Range r) + { + auto ops = erase (r); + ops = withOperationsFrom (ops, shift (r.getEnd(), -r.getLength())); + return ops; + } + + /* Shift all ranges starting at or beyond the specified from parameter, by the specified amount. + */ + Operations shift (int64 from, int64 amount) + { + if (amount == 0) + return {}; + + const auto shiftStartingFrom = std::lower_bound (ranges.begin(), + ranges.end(), + from, + [] (auto& elem, auto& value) + { return elem.getStart() < value; }); + + Operations ops; + + for (auto it = shiftStartingFrom; it < ranges.end(); ++it) + { + *it += amount; + ops = withOperationsFrom (ops, Ops::Change { getIndex (it) }); + } + + return ops; + } + + Operations set (Range newRange) + { + if (newRange.isEmpty()) + return {}; + + Operations ops; + + ops = withOperationsFrom (ops, erase (newRange)); + + const auto insertBefore = std::lower_bound (ranges.begin(), + ranges.end(), + newRange.getStart(), + [] (auto& elem, auto& value) + { return elem.getStart() < value; }); + + ops = withOperationsFrom (ops, Ops::New { getIndex (insertBefore) }); + ranges.insert (insertBefore, newRange); + + return ops; + } + + Operations insert (Range newRange) + { + if (newRange.isEmpty()) + return {}; + + Operations ops; + + ops = withOperationsFrom (ops, split (newRange.getStart())); + ops = withOperationsFrom (ops, shift (newRange.getStart(), newRange.getLength())); + + const auto insertBefore = std::lower_bound (ranges.begin(), + ranges.end(), + newRange.getStart(), + [] (auto& elem, auto& value) + { return elem.getStart() < value; }); + + const auto insertBeforeIndex = getIndex (insertBefore); + + ranges.insert (insertBefore, newRange); + ops = withOperationsFrom (ops, Ops::New { insertBeforeIndex }); + + return ops; + } + + //============================================================================== + // Convenience functions void clear() { ranges.clear(); } + Operations eraseFrom (int64 i) + { + if (ranges.empty()) + return {}; + + return erase ({ i, ranges.back().getEnd() }); + } + + /* Merges neighbouring ranges backwards if they form a contiguous range. + */ + Operations mergeBack (size_t i) + { + jassert (isPositiveAndBelow (i, ranges.size())); + + if (i == 0 || i >= ranges.size()) + return {}; + + const auto start = i - 1; + const auto end = i; + + if (ranges[start].getEnd() != ranges[end].getStart()) + return {}; + + Operations ops; + + ops = withOperationsFrom (ops, Ops::Change { start }); + ranges[start].setEnd (ranges[end].getEnd()); + + ops = withOperationsFrom (ops, Ops::Erase { { end, end + 1 } }); + + ranges.erase (iteratorWithAdvance (ranges.begin(), end), + iteratorWithAdvance (ranges.begin(), end + 1)); + + return ops; + } + /* Returns the ranges that have an intersection with the provided range. */ std::vector> getIntersectionsWith (Range r) const { @@ -127,232 +331,6 @@ struct Ranges final return result; } - Operations set (Range newRange) - { - if (newRange.isEmpty()) - return {}; - - Operations ops; - - const auto firstStartingBeforeNewRange = [&] - { - auto it = std::lower_bound (ranges.begin(), - ranges.end(), - newRange, - [] (auto& elem, auto& value) - { return elem.getStart() < value.getStart(); }); - - if (! ranges.empty() && it != ranges.begin()) - return it - 1; - - return ranges.end(); - }(); - - const auto getFirstEndingAfterNewRange = [&] - { - auto it = std::lower_bound (ranges.begin(), - ranges.end(), - newRange, - [] (auto& elem, auto& value) - { return elem.getEnd() <= value.getEnd(); }); - - return it; - }; - - auto firstEndingAfterNewRange = getFirstEndingAfterNewRange(); - - // This variable helps with handling the corner case, when the newValue to be set lies - // entirely inside an existing range. The set() operation in this case is expected to split - // the existing range. - auto remainderRangeBecauseOfSplit = [&]() -> std::optional> - { - if (firstStartingBeforeNewRange == ranges.end() || firstStartingBeforeNewRange != firstEndingAfterNewRange) - return std::nullopt; - - return Range { std::max (newRange.getEnd(), firstEndingAfterNewRange->getStart()), - firstEndingAfterNewRange->getEnd() }; - }(); - - if (firstStartingBeforeNewRange != ranges.end()) - { - const auto oldEnd = firstStartingBeforeNewRange->getEnd(); - const auto newEnd = std::min (oldEnd, newRange.getStart()); - - firstStartingBeforeNewRange->setEnd (newEnd); - - if (oldEnd != newEnd) - ops.push_back (Ops::Changed { getIndex (firstStartingBeforeNewRange) }); - } - - if (! remainderRangeBecauseOfSplit.has_value() - && firstEndingAfterNewRange != ranges.end()) - { - const auto oldStart = firstEndingAfterNewRange->getStart(); - const auto newStart = std::max (firstEndingAfterNewRange->getStart(), newRange.getEnd()); - - firstEndingAfterNewRange->setStart (newStart); - - if (oldStart != newStart) - ops.push_back (Ops::Changed { getIndex (firstStartingBeforeNewRange) }); - } - - const auto firstToDelete = std::lower_bound (ranges.begin(), - ranges.end(), - newRange, - [] (auto& elem, auto& value) - { return elem.getStart() < value.getStart(); }); - - firstEndingAfterNewRange = getFirstEndingAfterNewRange(); - - if (firstToDelete != ranges.end() && firstToDelete != firstEndingAfterNewRange) - ops.push_back (Ops::Erased { { getIndex (firstToDelete), - getIndex (firstEndingAfterNewRange) } }); - - const auto beyondLastRemoved = ranges.erase (firstToDelete, firstEndingAfterNewRange); - - const auto insertIt = ranges.insert (beyondLastRemoved, newRange); - ops.push_back (Ops::Inserted { getIndex (insertIt) }); - - if (remainderRangeBecauseOfSplit.has_value()) - { - const auto it = ranges.insert (insertIt + 1, *remainderRangeBecauseOfSplit); - ops.push_back (Ops::Reinserted { getIndex (it) }); - } - - return ops; - } - - size_t getIndex (std::vector>::const_iterator it) const - { - return (size_t) std::distance (ranges.cbegin(), it); - } - - Operations insert (Range newRange) - { - if (newRange.isEmpty()) - return {}; - - Operations ops; - - auto it = std::lower_bound (ranges.begin(), - ranges.end(), - newRange, - [] (auto& elem, auto& value) - { return elem.getEnd() <= value.getStart(); }); - - if (it != ranges.end() && it->getStart() < newRange.getStart()) - { - const auto oldEnd = it->getEnd(); - it->setEnd (newRange.getStart()); - ops.push_back (Ops::Changed { getIndex (it) }); - - Range newItems[] = { newRange, - { newRange.getEnd(), newRange.getEnd() + oldEnd - it->getEnd() } }; - - it = ranges.insert (it + 1, std::begin (newItems), std::end (newItems)); - - ops.push_back (Ops::Inserted { getIndex (it) }); - ops.push_back (Ops::Inserted { getIndex (it + 1) }); - - ++it; - } - else - { - it = ranges.insert (it, newRange); - - ops.push_back (Ops::Inserted { getIndex (it) }); - } - - for (auto& range : makeRange (std::next (it), ranges.end())) - range += newRange.getLength(); - - return ops; - } - - Operations split (int64 i) - { - Operations ops; - - const auto elemIndex = getIndexForEnclosingRange (i); - - if (! elemIndex.has_value()) - return ops; - - auto& elem = ranges[*elemIndex]; - - if (elem.getStart() == i) - return ops; - - const auto oldLength = elem.getLength(); - elem.setEnd (i); - ops.push_back (Ops::Changed { *elemIndex }); - - auto setOps = set (Range { Range { i, i + oldLength - elem.getLength() } }); - ops.insert (ops.end(), setOps.begin(), setOps.end()); - - return ops; - } - - Operations eraseFrom (int64 i) - { - Operations ops; - - const auto elemIndex = getIndexForEnclosingRange (i); - - if (elemIndex.has_value()) - { - ranges[*elemIndex].setEnd (i); - ops.push_back (Ops::Changed { *elemIndex }); - } - - const auto firstToDelete = std::lower_bound (ranges.begin(), - ranges.end(), - i, - [] (auto& elem, auto& value) - { return elem.getStart() < value; }); - - if (firstToDelete != ranges.end()) - ops.push_back (Ops::Erased { { getIndex (firstToDelete), getIndex (ranges.end()) } }); - - ranges.erase (firstToDelete, ranges.end()); - - return ops; - } - - Operations merge (Range elements) - { - jassert (elements.getEnd() <= ranges.size()); - - Operations ops; - - for (auto i = elements.getStart(), j = i + 1; j < elements.getEnd(); ++j) - { - const auto inLastIteration = j == elements.getEnd() - 1; - - if (inLastIteration || ranges[j].getEnd() != ranges[j + 1].getStart()) - { - ranges[i].setEnd (ranges[j].getEnd()); - ranges.erase (ranges.begin() + (int) i + 1, ranges.begin() + (int) j + 1); - - // I like that the merging algorithm works regardless of i being equal to j, so - // I didn't add this if earlier. No need to handle a corner case where there is - // none. However, I don't want to omit events if nothing changed. - if (i != j) - { - ops.push_back (Ops::Changed { i }); - ops.push_back (Ops::Erased { { i + 1, j + 1 } }); - } - - const auto numItemsDeleted = j - i; - elements.setEnd (elements.getEnd() - numItemsDeleted); - i = j + 1 - numItemsDeleted; - j = i; - } - } - - return ops; - } - std::optional getIndexForEnclosingRange (int64 positionInTextRange) const { auto it = std::lower_bound (ranges.begin(), @@ -366,11 +344,7 @@ struct Ranges final return std::nullopt; } - Range get (size_t rangeIndex) const - { - return ranges[rangeIndex]; - } - + //============================================================================== size_t size() const { return ranges.size(); @@ -381,100 +355,12 @@ struct Ranges final return ranges.empty(); } - auto& operator[] (size_t rangeIndex) + Range get (size_t rangeIndex) const { return ranges[rangeIndex]; } - auto& operator[] (size_t rangeIndex) const - { - return ranges[rangeIndex]; - } - - [[nodiscard]] Range getAffectedElements (const Ranges::Operations& ops, - IncludeAdjacentRanges includeAdjacent = IncludeAdjacentRanges::yes) const - { - if (ops.empty()) - return {}; - - int64 start = std::numeric_limits::max(); - int64 end = std::numeric_limits::min(); - - const auto startIsValid = [&start] { return start != std::numeric_limits::max(); }; - - const int64 includeAdjacentOffset = includeAdjacent == IncludeAdjacentRanges::yes ? 1 : 0; - - const auto adjacentOffsetFor = [includeAdjacent, this] (size_t index, int64 offset) -> int64 - { - if (includeAdjacent == IncludeAdjacentRanges::no) - return 0; - - const auto adjacentRangeIndex = (int64) index + offset; - - if (! isPositiveAndBelow (adjacentRangeIndex, (int64) ranges.size())) - return 0; - - if (offset < 0) - return ranges[(size_t) adjacentRangeIndex].getEnd() == ranges[index].getStart() ? offset : 0; - - return ranges[index].getEnd() == ranges[(size_t) adjacentRangeIndex].getStart() ? offset : 0; - }; - - for (auto& op : ops) - { - if (auto* inserted = std::get_if (&op)) - { - if (startIsValid() && (int64) inserted->index < start) - start += 1; - - if ((int64) inserted->index < end) - end += 1; - - start = std::min (start, (int64) inserted->index + adjacentOffsetFor (inserted->index, -1)); - end = std::max (end, (int64) inserted->index + adjacentOffsetFor (inserted->index, 1) + 1); - } - else if (auto* reinserted = std::get_if (&op)) - { - if (startIsValid() && (int64) reinserted->index < start) - start += 1; - - if ((int64) reinserted->index < end) - end += 1; - - start = std::min (start, (int64) reinserted->index + adjacentOffsetFor (reinserted->index, -1)); - end = std::max (end, (int64) reinserted->index + adjacentOffsetFor (reinserted->index, 1) + 1); - } - else if (auto* erased = std::get_if (&op)) - { - const auto eraseStart = (int64) erased->range.getStart(); - - if (startIsValid() && eraseStart < start) - start -= (int64) erased->range.getLength(); - - if (eraseStart < end - 1) - end -= (int64) erased->range.getLength(); - } - else if (auto* changed = std::get_if (&op)) - { - start = std::min (start, (int64) changed->index - includeAdjacentOffset); - end = std::max (end, (int64) changed->index + includeAdjacentOffset); - } - } - - return { clampCast (start), std::min (clampCast (end), ranges.size()) }; - } - - [[nodiscard]] Range getSpannedRange (Range r) const - { - auto start = (int64) r.getStart(); - auto end = (int64) r.getEnd(); - - jassert (start < (int64) ranges.size() && end <= (int64) ranges.size()); - - return { ranges[(size_t) start].getStart(), - ranges[(size_t) std::max (start, end - 1)].getEnd() }; - } - + //============================================================================== auto begin() const { return ranges.cbegin(); @@ -496,6 +382,11 @@ struct Ranges final } private: + size_t getIndex (std::vector>::const_iterator it) const + { + return (size_t) std::distance (ranges.cbegin(), it); + } + std::vector> ranges; }; @@ -512,15 +403,6 @@ constexpr auto hasEqualityOperator = false; template constexpr auto hasEqualityOperator() == std::declval())>> = true; -/* This is to get rid of the warning where advance isn't of type difference_type. - */ -template -auto iteratorWithAdvance (Iterator&& it, Value advance) -{ - auto outIt = std::move (it); - std::advance (outIt, static_cast::difference_type> (advance)); - return outIt; -} /* Data structure for storing values associated with non-overlapping ranges. @@ -632,6 +514,7 @@ public: template static auto makeIterator (X* x, Y y) { return RangedValuesIterator (x, y); } + //============================================================================== auto begin() { return makeIterator (this, ranges.cbegin()); @@ -674,78 +557,23 @@ public: const T& value; }; - void clear() - { - ranges.clear(); - values.clear(); - } - + //============================================================================== + // Basic operations template auto set (Range r, T v) { static_assert (mergeEquals == MergeEqualItems::no || canMergeEqualItems, "You can't use MergeEqualItems::yes if your type doesn't have operator==."); - auto ops = ranges.set (r); + Ranges::Operations ops; - // We use the copy constructor to avoid any dependency or restriction on the types default - // constructor. - T changedValue = v; - - #if JUCE_DEBUG - int numInsert{}; - #endif - - for (auto& op : ops) - { - if (auto* changed = std::get_if (&op)) - { - if (changed->index < values.size()) - changedValue = values[changed->index]; - } - else if (auto* inserted = std::get_if (&op)) - { - #if JUCE_DEBUG - ++numInsert; - #endif - - values.insert (iteratorWithAdvance (values.begin(), inserted->index), v); - } - else if (auto* reinserted = std::get_if (&op)) - { - values.insert (iteratorWithAdvance (values.begin(), reinserted->index), changedValue); - } - else if (auto* erased = std::get_if (&op)) - { - values.erase (iteratorWithAdvance (values.begin(), erased->range.getStart()), - iteratorWithAdvance (values.begin(), erased->range.getEnd())); - } - } - - #if JUCE_DEBUG - jassert (numInsert <= 1); - #endif + ops = Ranges::withOperationsFrom (ops, ranges.set (r)); + applyOperations (ops, std::move (v)); if constexpr (mergeEquals == MergeEqualItems::yes) { - const auto mergeOps = mergeEqualItems (ranges.getAffectedElements (ops)); - ops.insert (ops.begin(), mergeOps.begin(), mergeOps.end()); - } - - return ops; - } - - /** Create a RangedValues object from non-overlapping ranges. */ - template - auto setForEach (Iterable begin, Iterable end) - { - Ranges::Operations ops; - - for (auto it = begin; it != end; ++it) - { - const auto& [range, value] = *it; - const auto subOps = set (range, value); - ops.insert (ops.end(), subOps.begin(), subOps.end()); + ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getStart())); + ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getEnd())); } return ops; @@ -758,103 +586,68 @@ public: "You can't use MergeEqualItems::yes if your type doesn't have operator==."); auto ops = ranges.insert (r); - - std::optional oldValue; - auto newValueInserted = false; - - for (auto& op : ops) - { - if (auto* changed = std::get_if (&op)) - { - oldValue = values[changed->index]; - } - else if (auto* inserted = std::get_if (&op)) - { - if (! std::exchange (newValueInserted, true)) - { - values.insert (values.begin() + (int) inserted->index, v); - } - else - { - jassert (oldValue.has_value()); - values.insert (values.begin() + (int) inserted->index, *oldValue); - oldValue.reset(); - } - } - else - jassertfalse; - } + applyOperations (ops, std::move (v)); if constexpr (mergeEquals == MergeEqualItems::yes) { - const auto mergeOps = mergeEqualItems (ranges.getAffectedElements (ops)); - ops.insert (ops.begin(), mergeOps.begin(), mergeOps.end()); + ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getStart())); + ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getEnd())); } return ops; } - auto eraseFrom (int64 i) + // erase will always cause a discontinuity and thus, there is no opportunity to merge + auto erase (Range r) { - auto ops = ranges.eraseFrom (i); - - for (auto& op : ops) - { - if (auto* erased = std::get_if (&op)) - { - values.erase (iteratorWithAdvance (values.begin(), erased->range.getStart()), - iteratorWithAdvance (values.begin(), erased->range.getEnd())); - } - } + auto ops = ranges.erase (r); + applyOperations (ops); + return ops; } - Range getEqualElements (Range rangesToCheck) + // drop moves subsequent ranges downward, and can end up in these ranges forming a contiguous + // range with the ones on the left side of the drop. Hence, it makes sense to ask if we want + // merging behaviour. + template + auto drop (Range r) { - if constexpr (canMergeEqualItems) - { - std::optional start; - size_t end{}; + static_assert (mergeEquals == MergeEqualItems::no || canMergeEqualItems, + "You can't use MergeEqualItems::yes if your type doesn't have operator==."); - for (auto i = rangesToCheck.getStart(); i < rangesToCheck.getEnd() - 1; ++i) - { - if (exactlyEqual (values[i], values[i + 1])) - { - if (! start.has_value()) - start = i; + auto ops = ranges.drop (r); + applyOperations (ops); - end = i + 2; - } - else - { - if (start.has_value()) - break; - } - } + if constexpr (mergeEquals == MergeEqualItems::yes) + ops = Ranges::withOperationsFrom (ops, mergeEqualItems (r.getStart())); - return start.has_value() ? Range { *start, end } - : Range { rangesToCheck.getStart(), - rangesToCheck.getStart() }; - } - else - { + return ops; + } + + //============================================================================== + void clear() + { + ranges.clear(); + values.clear(); + } + + Ranges::Operations eraseFrom (int64 i) + { + if (ranges.isEmpty()) return {}; - } + + return erase ({ i, ranges.get (ranges.size() - 1).getEnd() }); } - auto mergeEqualItems (Range elements) + /** Create a RangedValues object from non-overlapping ranges. */ + template + auto setForEach (Iterable begin, Iterable end) { - if (elements.isEmpty()) - return Ranges::Operations{}; + Ranges::Operations ops; - auto ops = ranges.merge (getEqualElements (elements)); - - for (const auto& op : ops) + for (auto it = begin; it != end; ++it) { - if (auto* erased = std::get_if (&op)) - { - values.erase (values.begin() + (int) erased->range.getStart(), - values.begin() + (int) erased->range.getEnd()); - } + const auto& [range, value] = *it; + ops = Ranges::withOperationsFrom (ops, set (range, value)); } return ops; @@ -941,6 +734,70 @@ public: } private: + Ranges::Operations mergeEqualItems (int64 i) + { + const auto endOpt = ranges.getIndexForEnclosingRange (i); + + if (! endOpt.has_value() || *endOpt == 0) + return {}; + + const auto end = *endOpt; + const auto start = end - 1; + + if (! exactlyEqual (values[start], values[end])) + return {}; + + const auto ops = ranges.mergeBack (end); + applyOperations (ops); + + return ops; + } + + void applyOperation (const Ranges::Op& op) + { + if (auto* split = std::get_if (&op)) + { + values.insert (iteratorWithAdvance (values.begin(), split->index), values[split->index]); + return; + } + + if (auto* erase = std::get_if (&op)) + { + values.erase (iteratorWithAdvance (values.begin(), erase->range.getStart()), + iteratorWithAdvance (values.begin(), erase->range.getEnd())); + return; + } + + // This function can't handle New. + jassert (std::get_if (&op) == nullptr); + + // This entire type doesn't have to do anything to handle Ranges::Ops::Change. + } + + void applyOperation (const Ranges::Op& op, T v) + { + if (auto* newOp = std::get_if (&op)) + { + values.insert (iteratorWithAdvance (values.begin(), newOp->index), std::move (v)); + } + else + { + applyOperation (op); + } + } + + void applyOperations (const Ranges::Operations& ops) + { + for (const auto& op : ops) + applyOperation (op); + } + + void applyOperations (const Ranges::Operations& ops, T v) + { + for (const auto& op : ops) + applyOperation (op, v); + } + Ranges ranges; std::vector values; };