Skip to content

Commit

Permalink
Merge 6953bac into 83b2f46
Browse files Browse the repository at this point in the history
  • Loading branch information
kghost authored Dec 26, 2021
2 parents 83b2f46 + 6953bac commit 4197752
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 0 deletions.
222 changes: 222 additions & 0 deletions src/lib/support/IntrusiveList.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* Copyright (c) 2021 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <iterator>
#include <utility>

#include <lib/support/CodeUtils.h>

namespace chip {

class IntrusiveListBase;

class IntrusiveListNodeBase
{
public:
IntrusiveListNodeBase() : mPrev(nullptr), mNext(nullptr) {}
~IntrusiveListNodeBase() { VerifyOrDie(!IsInList()); }

bool IsInList() const { return (mPrev != nullptr && mNext != nullptr); }

private:
friend class IntrusiveListBase;
IntrusiveListNodeBase(IntrusiveListNodeBase * prev, IntrusiveListNodeBase * next) : mPrev(prev), mNext(next) {}

void Prepend(IntrusiveListNodeBase * node)
{
VerifyOrDie(IsInList());
VerifyOrDie(!node->IsInList());
node->mPrev = mPrev;
node->mNext = this;
mPrev->mNext = node;
mPrev = node;
}

void Append(IntrusiveListNodeBase * node)
{
VerifyOrDie(IsInList());
VerifyOrDie(!node->IsInList());
node->mPrev = this;
node->mNext = mNext;
mNext->mPrev = node;
mNext = node;
}

void Remove()
{
VerifyOrDie(IsInList());
mPrev->mNext = mNext;
mNext->mPrev = mPrev;
mPrev = nullptr;
mNext = nullptr;
}

IntrusiveListNodeBase * mPrev;
IntrusiveListNodeBase * mNext;
};

// non template part of IntrusiveList
class IntrusiveListBase
{
public:
class IteratorBase
{
private:
friend class IntrusiveListBase;
IteratorBase(IntrusiveListNodeBase * cur) : mCurrent(cur) {}

IntrusiveListNodeBase & operator*() { return *mCurrent; }

public:
using difference_type = std::ptrdiff_t;
using iterator_category = std::bidirectional_iterator_tag;

IteratorBase(const IteratorBase &) = default;
IteratorBase(IteratorBase &&) = default;
IteratorBase & operator=(const IteratorBase &) = default;
IteratorBase & operator=(IteratorBase &&) = default;

bool operator==(const IteratorBase & that) const { return mCurrent == that.mCurrent; }
bool operator!=(const IteratorBase & that) const { return !(*this == that); }

IteratorBase & operator++()
{
mCurrent = mCurrent->mNext;
return *this;
}

IteratorBase operator++(int)
{
IteratorBase res(mCurrent);
mCurrent = mCurrent->mNext;
return res;
}

IteratorBase & operator--()
{
mCurrent = mCurrent->mPrev;
return *this;
}

IteratorBase operator--(int)
{
IteratorBase res(mCurrent);
mCurrent = mCurrent->mPrev;
return res;
}

protected:
IntrusiveListNodeBase * mCurrent;
};

bool Empty() const { return mNode.mNext == &mNode; }

void Erase(IteratorBase pos) { pos.mCurrent->Remove(); }

protected:
// The list is formed as a ring with mNode being the end.
//
// begin end
// v v
// item -> item -> ... -> mNode
// ^ |
// \--------------------/
//
IntrusiveListBase() : mNode(&mNode, &mNode) {}
~IntrusiveListBase() { mNode.Remove(); }

IteratorBase begin() { return IteratorBase(mNode.mNext); }
IteratorBase end() { return IteratorBase(&mNode); }

void PushFront(IntrusiveListNodeBase * node) { mNode.Append(node); }
void PushBack(IntrusiveListNodeBase * node) { mNode.Prepend(node); }

void InsertBefore(IteratorBase pos, IntrusiveListNodeBase * node)
{
VerifyOrDie(pos.mCurrent->IsInList());
pos.mCurrent->Prepend(node);
}

void InsertAfter(IteratorBase pos, IntrusiveListNodeBase * node)
{
VerifyOrDie(pos.mCurrent->IsInList());
pos.mCurrent->Append(node);
}

void Remove(IntrusiveListNodeBase * node)
{
VerifyOrDie(Contains(node));
node->Remove();
}

bool Contains(IntrusiveListNodeBase * node)
{
for (auto & iter : *this)
{
if (&iter == node)
return true;
}
return false;
}

private:
IntrusiveListNodeBase mNode;
};

template <typename T>
class IntrusiveListBaseHook
{
public:
static_assert(std::is_base_of<IntrusiveListNodeBase, T>::value, "T must be derived from IntrusiveListNodeBase");
static T * ToObject(IntrusiveListNodeBase * node) { return static_cast<T *>(node); }
static IntrusiveListNodeBase * ToNode(T * object) { return static_cast<IntrusiveListNodeBase *>(object); }
};

template <typename T, typename Hook = IntrusiveListBaseHook<T>>
class IntrusiveList : public IntrusiveListBase
{
public:
static_assert(std::is_base_of<IntrusiveListNodeBase, T>::value, "T must derive from IntrusiveListNodeBase");

IntrusiveList() : IntrusiveListBase() {}

class Iterator : public IntrusiveListBase::IteratorBase
{
public:
using value_type = T;
using pointer = T *;
using reference = T &;

Iterator(IntrusiveListBase::IteratorBase && base) : IntrusiveListBase::IteratorBase(std::move(base)) {}
T * operator->() { return Hook::ToObject(mCurrent); }
T & operator*() { return *Hook::ToObject(mCurrent); }
};

Iterator begin() { return IntrusiveListBase::begin(); }
Iterator end() { return IntrusiveListBase::end(); }
void PushFront(T * value) { IntrusiveListBase::PushFront(Hook::ToNode(value)); }
void PushBack(T * value) { IntrusiveListBase::PushBack(Hook::ToNode(value)); }
void InsertBefore(Iterator pos, T * value) { IntrusiveListBase::InsertBefore(pos, Hook::ToNode(value)); }
void InsertAfter(Iterator pos, T * value) { IntrusiveListBase::InsertAfter(pos, Hook::ToNode(value)); }
void Remove(T * value) { IntrusiveListBase::Remove(Hook::ToNode(value)); }
bool Contains(T * value) { return IntrusiveListBase::Contains(Hook::ToNode(value)); }

private:
};

} // namespace chip
1 change: 1 addition & 0 deletions src/lib/support/tests/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ chip_test_suite("tests") {
"TestErrorStr.cpp",
"TestFixedBufferAllocator.cpp",
"TestFold.cpp",
"TestIntrusiveList.cpp",
"TestOwnerOf.cpp",
"TestPool.cpp",
"TestPrivateHeap.cpp",
Expand Down
131 changes: 131 additions & 0 deletions src/lib/support/tests/TestIntrusiveList.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <ctime>
#include <list>

#include <lib/support/IntrusiveList.h>
#include <lib/support/UnitTestRegistration.h>

#include <nlunit-test.h>

namespace {

using namespace chip;

class ListNode : public IntrusiveListNodeBase
{
};

void TestIntrusiveListRandom(nlTestSuite * inSuite, void * inContext)
{
IntrusiveList<ListNode> l1;
ListNode node[100];
std::list<ListNode *> l2;

auto op = [&](auto fun) {
if (l2.empty())
return;

auto l1p = l1.begin();
auto l2p = l2.begin();
for (size_t pos = static_cast<size_t>(std::rand()) % l2.size(); pos > 0; --pos)
{
++l1p;
++l2p;
}

fun(l1p, l2p);
};

for (int i = 0; i < 100; ++i)
{
switch (std::rand() % 5)
{
case 0: // PushFront
l1.PushFront(&node[i]);
l2.push_front(&node[i]);
break;
case 1: // PushBack
l1.PushBack(&node[i]);
l2.push_back(&node[i]);
break;
case 2: // InsertBefore
op([&](auto & l1p, auto & l2p) {
l1.InsertBefore(l1p, &node[i]);
l2.insert(l2p, &node[i]);
});
break;
case 3: // InsertAfter
op([&](auto & l1p, auto & l2p) {
l1.InsertAfter(l1p, &node[i]);
l2.insert(++l2p, &node[i]);
});
break;
case 4: // Remove
op([&](auto & l1p, auto & l2p) {
l1.Remove(&*l1p);
l2.erase(l2p);
});
break;
default:
break;
}

NL_TEST_ASSERT(inSuite,
std::equal(l1.begin(), l1.end(), l2.begin(), l2.end(),
[](const ListNode & p1, const ListNode * p2) { return &p1 == p2; }));
}

while (!l1.Empty())
{
l1.Remove(&*l1.begin());
}
}

int Setup(void * inContext)
{
return SUCCESS;
}

int Teardown(void * inContext)
{
return SUCCESS;
}

} // namespace

#define NL_TEST_DEF_FN(fn) NL_TEST_DEF("Test " #fn, fn)
/**
* Test Suite. It lists all the test functions.
*/
static const nlTest sTests[] = { NL_TEST_DEF_FN(TestIntrusiveListRandom), NL_TEST_SENTINEL() };

int TestIntrusiveList()
{
nlTestSuite theSuite = { "CHIP IntrusiveList tests", &sTests[0], Setup, Teardown };

unsigned seed = static_cast<unsigned>(std::time(nullptr));
printf("Running " __FILE__ " using seed %d", seed);
std::srand(seed);

// Run test suit againt one context.
nlTestRunner(&theSuite, nullptr);
return nlTestRunnerStats(&theSuite);
}

CHIP_REGISTER_TEST_SUITE(TestIntrusiveList);

0 comments on commit 4197752

Please sign in to comment.