import from custom branch of UXP: ported from mozilla: Add UnorderedRemoveElement[s]At to nsTArray, r=froydnj (3fa9e61e)

This commit is contained in:
2025-03-23 12:38:50 +08:00
parent 623d347978
commit 3da5d9cf8e
3 changed files with 260 additions and 1 deletions
+55
View File
@@ -306,6 +306,61 @@ nsTArray_base<Alloc, Copy>::ShiftData(index_type aStart,
}
}
template<class Alloc, class Copy>
template<typename ActualAlloc>
void
nsTArray_base<Alloc, Copy>::SwapFromEnd(index_type aStart,
size_type aCount,
size_type aElemSize,
size_t aElemAlign)
{
// This method is part of the implementation of
// nsTArray::SwapRemoveElement{s,}At. For more information, read the
// documentation on that method.
if (aCount == 0) {
return;
}
// We are going to be removing aCount elements. Update our length to point to
// the new end of the array.
size_type oldLength = mHdr->mLength;
mHdr->mLength -= aCount;
if (mHdr->mLength == 0) {
// If we have no elements remaining in the array, we can free our buffer.
ShrinkCapacity(aElemSize, aElemAlign);
return;
}
// Determine how many elements we need to move from the end of the array into
// the now-removed section. This will either be the number of elements which
// were removed (if there are more elements in the tail of the array), or the
// entire tail of the array, whichever is smaller.
size_type relocCount = std::min(aCount, mHdr->mLength - aStart);
if (relocCount == 0) {
return;
}
// Move the elements which are now stranded after the end of the array back
// into the now-vacated memory.
index_type sourceBytes = (oldLength - relocCount) * aElemSize;
index_type destBytes = aStart * aElemSize;
// Perform the final copy. This is guaranteed to be a non-overlapping copy
// as our source contains only still-valid entries, and the destination
// contains only invalid entries which need to be overwritten.
MOZ_ASSERT(sourceBytes >= destBytes,
"The source should be after the destination.");
MOZ_ASSERT(sourceBytes - destBytes >= relocCount * aElemSize,
"The range should be nonoverlapping");
char* baseAddr = reinterpret_cast<char*>(mHdr + 1);
Copy::MoveNonOverlappingRegion(baseAddr + destBytes,
baseAddr + sourceBytes,
relocCount,
aElemSize);
}
template<class Alloc, class Copy>
template<typename ActualAlloc>
typename ActualAlloc::ResultTypeProxy
+85 -1
View File
@@ -559,6 +559,17 @@ protected:
void ShiftData(index_type aStart, size_type aOldLen, size_type aNewLen,
size_type aElemSize, size_t aElemAlign);
// This method may be called to swap elements from the end of the array to
// fill a "gap" in the array. If the resulting array has zero elements, then
// the array's memory is free'd.
// @param aStart The starting index of the gap.
// @param aCount The length of the gap.
// @param aElemSize The size of an array element.
// @param aElemAlign The alignment in bytes of an array element.
template<typename ActualAlloc>
void SwapFromEnd(index_type aStart, size_type aCount,
size_type aElemSize, size_t aElemAlign);
// This method increments the length member of the array's header.
// Note that mHdr may actually be sEmptyHdr in the case where a
// zero-length array is inserted into our array. But then aNum should
@@ -1853,7 +1864,58 @@ public:
// A variation on the RemoveElementsAt method defined above.
void RemoveElementAt(index_type aIndex) { RemoveElementsAt(aIndex, 1); }
// A variation on the RemoveElementsAt method defined above.
// This method performs index-based removals from an array without preserving
// the order of the array. This is useful if you are using the array as a
// set-like data structure.
//
// These removals are efficient, as they move as few elements as possible. At
// most N elements, where N is the number of removed elements, will have to
// be relocated.
//
// ## Examples
//
// When removing an element from the end of the array, it can be removed in
// place, by destroying it and decrementing the length.
//
// [ 1, 2, 3 ] => [ 1, 2 ]
// ^
//
// When removing any other single element, it is removed by swapping it with
// the last element, and then decrementing the length as before.
//
// [ 1, 2, 3, 4, 5, 6 ] => [ 1, 6, 3, 4, 5 ]
// ^
//
// This method also supports efficiently removing a range of elements. If they
// are at the end, then they can all be removed like in the one element case.
//
// [ 1, 2, 3, 4, 5, 6 ] => [ 1, 2 ]
// ^--------^
//
// If more elements are removed than exist after the removed section, the
// remaining elements will be shifted down like in a normal removal.
//
// [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 2, 7, 8 ]
// ^--------^
//
// And if fewer elements are removed than exist after the removed section,
// elements will be moved from the end of the array to fill the vacated space.
//
// [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 7, 8, 4, 5, 6 ]
// ^--^
//
// @param aStart The starting index of the elements to remove. @param aCount
// The number of elements to remove.
void UnorderedRemoveElementsAt(index_type aStart, size_type aCount);
// A variation on the UnorderedRemoveElementsAt method defined above to remove
// a single element. This operation is sometimes called `SwapRemove`.
//
// This method is O(1), but does not preserve the order of the elements.
void UnorderedRemoveElementAt(index_type aIndex) {
UnorderedRemoveElementsAt(aIndex, 1);
}
void Clear() { RemoveElementsAt(0, Length()); }
// This method removes elements based on the return value of the
@@ -2184,6 +2246,28 @@ nsTArray_Impl<E, Alloc>::RemoveElementsAt(index_type aStart, size_type aCount)
MOZ_ALIGNOF(elem_type));
}
template<typename E, class Alloc>
void
nsTArray_Impl<E, Alloc>::UnorderedRemoveElementsAt(index_type aStart, size_type aCount)
{
MOZ_ASSERT(aCount == 0 || aStart < Length(), "Invalid aStart index");
mozilla::CheckedInt<index_type> rangeEnd = aStart;
rangeEnd += aCount;
if (MOZ_UNLIKELY(!rangeEnd.isValid() || rangeEnd.value() > Length())) {
InvalidArrayIndex_CRASH(aStart, Length());
}
// Destroy the elements which are being removed, and then swap elements in to
// replace them from the end. See the docs on the declaration of this
// function.
DestructRange(aStart, aCount);
this->template SwapFromEnd<InfallibleAlloc>(aStart, aCount,
sizeof(elem_type),
MOZ_ALIGNOF(elem_type));
}
template<typename E, class Alloc>
template<typename Predicate>
void
+120
View File
@@ -203,4 +203,124 @@ TEST(TArray, CopyOverlappingBackwards)
}
}
TEST(TArray, UnorderedRemoveElements)
{
// When removing an element from the end of the array, it can be removed in
// place, by destroying it and decrementing the length.
//
// [ 1, 2, 3 ] => [ 1, 2 ]
// ^
{
nsTArray<int> array{ 1, 2, 3 };
array.UnorderedRemoveElementAt(2);
nsTArray<int> goal{ 1, 2 };
ASSERT_EQ(array, goal);
}
// When removing any other single element, it is removed by swapping it with
// the last element, and then decrementing the length as before.
//
// [ 1, 2, 3, 4, 5, 6 ] => [ 1, 6, 3, 4, 5 ]
// ^
{
nsTArray<int> array{1, 2, 3, 4, 5, 6};
array.UnorderedRemoveElementAt(1);
nsTArray<int> goal{1, 6, 3, 4, 5};
ASSERT_EQ(array, goal);
}
// This method also supports efficiently removing a range of elements. If they
// are at the end, then they can all be removed like in the one element case.
//
// [ 1, 2, 3, 4, 5, 6 ] => [ 1, 2 ]
// ^--------^
{
nsTArray<int> array{1, 2, 3, 4, 5, 6};
array.UnorderedRemoveElementsAt(2, 4);
nsTArray<int> goal{1, 2};
ASSERT_EQ(array, goal);
}
// If more elements are removed than exist after the removed section, the
// remaining elements will be shifted down like in a normal removal.
//
// [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 2, 7, 8 ]
// ^--------^
{
nsTArray<int> array{1, 2, 3, 4, 5, 6, 7, 8};
array.UnorderedRemoveElementsAt(2, 4);
nsTArray<int> goal{1, 2, 7, 8};
ASSERT_EQ(array, goal);
}
// And if fewer elements are removed than exist after the removed section,
// elements will be moved from the end of the array to fill the vacated space.
//
// [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 7, 8, 4, 5, 6 ]
// ^--^
{
nsTArray<int> array{1, 2, 3, 4, 5, 6, 7, 8};
array.UnorderedRemoveElementsAt(1, 2);
nsTArray<int> goal{1, 7, 8, 4, 5, 6};
ASSERT_EQ(array, goal);
}
// We should do the right thing if we drain the entire array.
{
nsTArray<int> array{1, 2, 3, 4, 5};
array.UnorderedRemoveElementsAt(0, 5);
nsTArray<int> goal{};
ASSERT_EQ(array, goal);
}
{
nsTArray<int> array{1};
array.UnorderedRemoveElementAt(0);
nsTArray<int> goal{};
ASSERT_EQ(array, goal);
}
// We should do the right thing if we remove the same number of elements that
// we have remaining.
{
nsTArray<int> array{1, 2, 3, 4, 5, 6};
array.UnorderedRemoveElementsAt(2, 2);
nsTArray<int> goal{1, 2, 5, 6};
ASSERT_EQ(array, goal);
}
{
nsTArray<int> array{1, 2, 3};
array.UnorderedRemoveElementAt(1);
nsTArray<int> goal{1, 3};
ASSERT_EQ(array, goal);
}
// We should be able to remove elements from the front without issue.
{
nsTArray<int> array{1, 2, 3, 4, 5, 6};
array.UnorderedRemoveElementsAt(0, 2);
nsTArray<int> goal{5, 6, 3, 4};
ASSERT_EQ(array, goal);
}
{
nsTArray<int> array{1, 2, 3, 4};
array.UnorderedRemoveElementAt(0);
nsTArray<int> goal{4, 2, 3};
ASSERT_EQ(array, goal);
}
}
} // namespace TestTArray