Bug 1163445: Part1. Keep IntervalSet normalized. r=mattwoodrow

This is the easiest to ensure consistency when making calculations on them.
This commit is contained in:
Jean-Yves Avenard 2015-05-18 16:13:20 +10:00
parent 40f359ebfb
commit 4265018a9f
2 changed files with 157 additions and 111 deletions

View File

@ -118,7 +118,7 @@ public:
bool Contains(const SelfType& aOther) const
{
return (mStart - mFuzz <= aOther.mStart + aOther.mFuzz) &&
(aOther.mEnd + aOther.mFuzz <= mEnd - mFuzz);
(aOther.mEnd - aOther.mFuzz <= mEnd + mFuzz);
}
bool ContainsStrict(const SelfType& aOther) const
@ -127,6 +127,13 @@ public:
}
bool Intersects(const SelfType& aOther) const
{
return (mStart - mFuzz < aOther.mEnd + aOther.mFuzz) &&
(aOther.mStart - aOther.mFuzz < mEnd + mFuzz);
}
// Same as Intersects, but including the boundaries.
bool Touches(const SelfType& aOther) const
{
return (mStart - mFuzz <= aOther.mEnd + aOther.mFuzz) &&
(aOther.mStart - aOther.mFuzz <= mEnd + mFuzz);
@ -139,6 +146,16 @@ public:
return mEnd <= aOther.mStart && aOther.mStart - mEnd <= mFuzz + aOther.mFuzz;
}
bool RightOf(const SelfType& aOther) const
{
return aOther.mEnd - aOther.mFuzz <= mStart + mFuzz;
}
bool LeftOf(const SelfType& aOther) const
{
return mEnd - mFuzz <= aOther.mStart + aOther.mFuzz;
}
SelfType Span(const SelfType& aOther) const
{
SelfType result(*this);
@ -183,6 +200,8 @@ public:
private:
};
// An IntervalSet in a collection of Intervals. The IntervalSet is always
// normalized.
template<typename T>
class IntervalSet
{
@ -205,18 +224,22 @@ public:
}
IntervalSet(SelfType&& aOther)
: mIntervals(Move(aOther.mIntervals))
{
mIntervals.MoveElementsFrom(Move(aOther.mIntervals));
}
explicit IntervalSet(const ElemType& aOther)
{
mIntervals.AppendElement(aOther);
if (!aOther.IsEmpty()) {
mIntervals.AppendElement(aOther);
}
}
explicit IntervalSet(ElemType&& aOther)
{
mIntervals.AppendElement(Move(aOther));
if (!aOther.IsEmpty()) {
mIntervals.AppendElement(Move(aOther));
}
}
SelfType& operator= (const SelfType& aOther)
@ -247,19 +270,55 @@ public:
return *this;
}
// + and += operator will append the provided interval or intervalset.
// Note that the result is not normalized. Call Normalize() as required.
// Alternatively, use Union()
SelfType& Add(const SelfType& aIntervals)
{
mIntervals.AppendElements(aIntervals.mIntervals);
Normalize();
return *this;
}
SelfType& Add(const ElemType& aInterval)
{
mIntervals.AppendElement(aInterval);
if (aInterval.IsEmpty()) {
return *this;
}
if (mIntervals.IsEmpty()) {
mIntervals.AppendElement(aInterval);
return *this;
}
// Most of our actual usage is adding an interval that will be outside the
// range. We can speed up normalization here.
if (aInterval.RightOf(mIntervals.LastElement()) &&
!aInterval.Touches(mIntervals.LastElement())) {
mIntervals.AppendElement(aInterval);
return *this;
}
ContainerType normalized;
ElemType current(aInterval);
bool inserted = false;
IndexType i = 0;
for (; i < mIntervals.Length(); i++) {
ElemType& interval = mIntervals[i];
if (current.Touches(interval)) {
current = current.Span(interval);
} else if (current.LeftOf(interval)) {
normalized.AppendElement(Move(current));
inserted = true;
break;
} else {
normalized.AppendElement(Move(interval));
}
}
if (!inserted) {
normalized.AppendElement(Move(current));
}
for (; i < mIntervals.Length(); i++) {
normalized.AppendElement(Move(mIntervals[i]));
}
mIntervals.Clear();
mIntervals.MoveElementsFrom(Move(normalized));
return *this;
}
@ -299,18 +358,15 @@ public:
}
// Mutate this IntervalSet to be the union of this and aOther.
// Resulting IntervalSet is normalized.
SelfType& Union(const SelfType& aOther)
{
Add(aOther);
Normalize();
return *this;
}
SelfType& Union(const ElemType& aInterval)
{
Add(aInterval);
Normalize();
return *this;
}
@ -331,8 +387,8 @@ public:
j++;
}
}
mIntervals = intersection;
mIntervals.Clear();
mIntervals.MoveElementsFrom(Move(intersection));
return *this;
}
@ -435,33 +491,6 @@ public:
return false;
}
void Normalize()
{
if (mIntervals.Length() >= 2) {
ContainerType normalized;
mIntervals.Sort(CompareIntervals());
// This merges the intervals.
ElemType current(mIntervals[0]);
for (IndexType i = 1; i < mIntervals.Length(); i++) {
if (current.Contains(mIntervals[i])) {
continue;
}
if (current.Intersects(mIntervals[i])) {
current = current.Span(mIntervals[i]);
} else {
normalized.AppendElement(current);
current = mIntervals[i];
}
}
normalized.AppendElement(current);
mIntervals = normalized;
}
}
// Shift all values by aOffset.
void Shift(T aOffset)
{
@ -487,6 +516,31 @@ protected:
ContainerType mIntervals;
private:
void Normalize()
{
if (mIntervals.Length() >= 2) {
ContainerType normalized;
mIntervals.Sort(CompareIntervals());
// This merges the intervals.
ElemType current(mIntervals[0]);
for (IndexType i = 1; i < mIntervals.Length(); i++) {
ElemType& interval = mIntervals[i];
if (current.Touches(interval)) {
current = current.Span(interval);
} else {
normalized.AppendElement(Move(current));
current = Move(interval);
}
}
normalized.AppendElement(Move(current));
mIntervals.Clear();
mIntervals.MoveElementsFrom(Move(normalized));
}
}
struct CompareIntervals
{
bool Equals(const ElemType& aT1, const ElemType& aT2) const

View File

@ -89,6 +89,10 @@ TEST(IntervalSet, TimeIntervalsConstructors)
media::TimeIntervals blah5 = CreateTimeIntervals(start.mValue, end.mValue);
(void)test1; (void)test2; (void)test3; (void)test4; (void)test5;
(void)blah1; (void)blah2; (void)blah3; (void)blah4; (void)blah5;
media::TimeIntervals i0{media::TimeInterval(media::TimeUnit::FromSeconds(0),
media::TimeUnit::FromSeconds(0))};
EXPECT_EQ(0u, i0.Length()); // Constructing with an empty time interval.
}
TEST(IntervalSet, Length)
@ -97,6 +101,23 @@ TEST(IntervalSet, Length)
EXPECT_EQ(10, i.Length());
}
TEST(IntervalSet, Intersects)
{
EXPECT_TRUE(IntInterval(1,5).Intersects(IntInterval(3,4)));
EXPECT_TRUE(IntInterval(1,5).Intersects(IntInterval(3,7)));
EXPECT_TRUE(IntInterval(1,5).Intersects(IntInterval(-1,3)));
EXPECT_TRUE(IntInterval(1,5).Intersects(IntInterval(-1,7)));
EXPECT_FALSE(IntInterval(1,5).Intersects(IntInterval(6,7)));
EXPECT_FALSE(IntInterval(1,5).Intersects(IntInterval(-1,0)));
// End boundary is exclusive of the interval.
EXPECT_FALSE(IntInterval(1,5).Intersects(IntInterval(5,7)));
EXPECT_FALSE(IntInterval(1,5).Intersects(IntInterval(0,1)));
// Empty identical interval do not intersect.
EXPECT_FALSE(IntInterval(1,1).Intersects(IntInterval(1,1)));
// Empty interval do not intersect.
EXPECT_FALSE(IntInterval(1,1).Intersects(IntInterval(2,2)));
}
TEST(IntervalSet, Intersection)
{
IntInterval i0(10, 20);
@ -104,6 +125,14 @@ TEST(IntervalSet, Intersection)
IntInterval i = i0.Intersection(i1);
EXPECT_EQ(15, i.mStart);
EXPECT_EQ(20, i.mEnd);
IntInterval j0(10, 20);
IntInterval j1(20, 25);
IntInterval j = j0.Intersection(j1);
EXPECT_TRUE(j.IsEmpty());
IntInterval k0(2, 2);
IntInterval k1(2, 2);
IntInterval k = k0.Intersection(k1);
EXPECT_TRUE(k.IsEmpty());
}
TEST(IntervalSet, Equals)
@ -150,46 +179,19 @@ TEST(IntervalSet, IntersectionIntervalSet)
}
template<typename T>
static void Compare(media::IntervalSet<T> aI1, media::IntervalSet<T> aI2)
static void Compare(const media::IntervalSet<T>& aI1,
const media::IntervalSet<T>& aI2)
{
media::IntervalSet<T> i1(aI1);
media::IntervalSet<T> i2(aI1);
EXPECT_EQ(i1.Length(), i2.Length());
if (i1.Length() != i2.Length()) {
EXPECT_EQ(aI1.Length(), aI2.Length());
if (aI1.Length() != aI2.Length()) {
return;
}
for (uint32_t i = 0; i < i1.Length(); i++) {
EXPECT_EQ(i1[i].mStart, i2[i].mStart);
EXPECT_EQ(i1[i].mEnd, i2[i].mEnd);
for (uint32_t i = 0; i < aI1.Length(); i++) {
EXPECT_EQ(aI1[i].mStart, aI2[i].mStart);
EXPECT_EQ(aI1[i].mEnd, aI2[i].mEnd);
}
}
TEST(IntervalSet, IntersectionNormalizedIntervalSet)
{
media::IntervalSet<int> i0;
i0 += IntInterval(5, 10);
i0 += IntInterval(8, 25);
i0 += IntInterval(24, 60);
media::IntervalSet<int> i1;
i1.Add(IntInterval(7, 15));
i1.Add(IntInterval(10, 27));
i1.Add(IntInterval(45, 50));
i1.Add(IntInterval(53, 57));
// Compare intersections to ensure an intersection of normalized intervalsets
// is equal to the intersection of non-normalized intervalsets.
media::IntervalSet<int> intersection = media::Intersection(i0, i1);
media::IntervalSet<int> i0_normalize(i0);
i0_normalize.Normalize();
media::IntervalSet<int> i1_normalize(i1);
i1_normalize.Normalize();
media::IntervalSet<int> intersection_normalize =
media::Intersection(i0_normalize, i1_normalize);
Compare(intersection, intersection_normalize);
}
static void GeneratePermutations(media::IntervalSet<int> aI1,
media::IntervalSet<int> aI2)
{
@ -211,17 +213,20 @@ static void GeneratePermutations(media::IntervalSet<int> aI1,
for (uint32_t i = 0; i < comb1.size(); i++) {
i_0 += aI1[comb1[i]];
}
// Test that intervals are always normalized.
Compare(aI1, i_0);
media::IntervalSet<int> i_1;
for (uint32_t i = 0; i < comb2.size(); i++) {
i_1 += aI2[comb2[i]];
}
Compare(aI2, i_1);
// Check intersections yield the same result.
Compare(i_0.Intersection(i_1), i_ref);
} while (std::next_permutation(comb2.begin(), comb2.end()));
} while (std::next_permutation(comb1.begin(), comb1.end()));
}
TEST(IntervalSet, IntersectionUnorderedIntervalSet)
TEST(IntervalSet, IntersectionNormalizedIntervalSet)
{
media::IntervalSet<int> i0;
i0 += IntInterval(5, 10);
@ -270,21 +275,27 @@ TEST(IntervalSet, Normalize)
i = IntInterval(1, 8) + i;
media::IntervalSet<int> interval;
interval += IntInterval(5, 10);
// Test += with move.
// Test += with rval move.
i += Duplicate(interval);
// Test = with move and add with move.
i = Duplicate(interval) + i;
media::IntervalSet<int> o(i);
o.Normalize();
EXPECT_EQ(2u, i.Length());
EXPECT_EQ(2u, o.Length());
EXPECT_EQ(1, i[0].mStart);
EXPECT_EQ(10, i[0].mEnd);
EXPECT_EQ(1, o[0].mStart);
EXPECT_EQ(10, o[0].mEnd);
EXPECT_EQ(20, i[1].mStart);
EXPECT_EQ(30, i[1].mEnd);
EXPECT_EQ(20, o[1].mStart);
EXPECT_EQ(30, o[1].mEnd);
media::TimeIntervals ti;
ti += media::TimeInterval(media::TimeUnit::FromSeconds(0.0),
media::TimeUnit::FromSeconds(3.203333));
ti += media::TimeInterval(media::TimeUnit::FromSeconds(3.203366),
media::TimeUnit::FromSeconds(10.010065));
EXPECT_EQ(2u, ti.Length());
ti += media::TimeInterval(ti.Start(0), ti.End(0), media::TimeUnit::FromMicroseconds(35000));
EXPECT_EQ(1u, ti.Length());
}
TEST(IntervalSet, Union)
@ -347,7 +358,6 @@ TEST(IntervalSet, NormalizeFuzz)
i0 += IntInterval(11, 25, 0);
i0 += IntInterval(5, 10, 1);
i0 += IntInterval(40, 60, 1);
i0.Normalize();
EXPECT_EQ(2u, i0.Length());
@ -381,7 +391,6 @@ TEST(IntervalSet, UnionFuzz)
EXPECT_EQ(40, i[1].mStart);
EXPECT_EQ(60, i[1].mEnd);
i0.Normalize();
EXPECT_EQ(2u, i0.Length());
EXPECT_EQ(5, i0[0].mStart);
EXPECT_EQ(25, i0[0].mEnd);
@ -421,28 +430,19 @@ TEST(IntervalSet, TimeRangesSeconds)
EXPECT_EQ(tr->End(index, rv), i[index].mEnd.ToSeconds());
EXPECT_EQ(tr->End(index, rv), i.End(index).ToSeconds());
}
i.Normalize();
tr->Normalize();
EXPECT_EQ(tr->Length(), i.Length());
for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) {
ErrorResult rv;
EXPECT_EQ(tr->Start(index, rv), i[index].mStart.ToSeconds());
EXPECT_EQ(tr->Start(index, rv), i.Start(index).ToSeconds());
EXPECT_EQ(tr->End(index, rv), i[index].mEnd.ToSeconds());
EXPECT_EQ(tr->End(index, rv), i.End(index).ToSeconds());
}
}
static void CheckTimeRanges(dom::TimeRanges* aTr, const media::TimeIntervals& aTi)
{
EXPECT_EQ(aTr->Length(), aTi.Length());
for (dom::TimeRanges::index_type i = 0; i < aTr->Length(); i++) {
nsRefPtr<dom::TimeRanges> tr = new dom::TimeRanges;
tr->Union(aTr, 0); // This will normalize the time range.
EXPECT_EQ(tr->Length(), aTi.Length());
for (dom::TimeRanges::index_type i = 0; i < tr->Length(); i++) {
ErrorResult rv;
EXPECT_EQ(aTr->Start(i, rv), aTi[i].mStart.ToSeconds());
EXPECT_EQ(aTr->Start(i, rv), aTi.Start(i).ToSeconds());
EXPECT_EQ(aTr->End(i, rv), aTi[i].mEnd.ToSeconds());
EXPECT_EQ(aTr->End(i, rv), aTi.End(i).ToSeconds());
EXPECT_EQ(tr->Start(i, rv), aTi[i].mStart.ToSeconds());
EXPECT_EQ(tr->Start(i, rv), aTi.Start(i).ToSeconds());
EXPECT_EQ(tr->End(i, rv), aTi[i].mEnd.ToSeconds());
EXPECT_EQ(tr->End(i, rv), aTi.End(i).ToSeconds());
}
}
@ -469,11 +469,6 @@ TEST(IntervalSet, TimeRangesConversion)
i3 = tr;
CheckTimeRanges(tr, i3);
i1.Normalize();
tr->Normalize();
CheckTimeRanges(tr, i1);
// operator= test
i1 = tr.get();
CheckTimeRanges(tr, i1);
@ -511,7 +506,6 @@ TEST(IntervalSet, TimeRangesMicroseconds)
EXPECT_EQ(tr->End(index, rv), i.End(index).ToSeconds());
}
i.Normalize();
tr->Normalize();
EXPECT_EQ(tr->Length(), i.Length());
for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) {
@ -583,8 +577,6 @@ TEST(IntervalSet, FooIntervalSet)
is.Add(i);
is = is + i;
is = i + is;
EXPECT_EQ(5u, is.Length());
is.Normalize();
EXPECT_EQ(1u, is.Length());
EXPECT_EQ(Foo<int>(), is[0].mStart);
EXPECT_EQ(Foo<int>(4,5,6), is[0].mEnd);