diff --git a/src/lib/support/IntrusiveList.h b/src/lib/support/IntrusiveList.h new file mode 100644 index 00000000000000..4d9bc3e3b6ceae --- /dev/null +++ b/src/lib/support/IntrusiveList.h @@ -0,0 +1,295 @@ +/* + * 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 +#include + +#include + +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 ConstIteratorBase + { + private: + friend class IntrusiveListBase; + ConstIteratorBase(const IntrusiveListNodeBase * cur) : mCurrent(cur) {} + + const IntrusiveListNodeBase & operator*() { return *mCurrent; } + + public: + using difference_type = std::ptrdiff_t; + using iterator_category = std::bidirectional_iterator_tag; + + ConstIteratorBase(const ConstIteratorBase &) = default; + ConstIteratorBase(ConstIteratorBase &&) = default; + ConstIteratorBase & operator=(const ConstIteratorBase &) = default; + ConstIteratorBase & operator=(ConstIteratorBase &&) = default; + + bool operator==(const ConstIteratorBase & that) const { return mCurrent == that.mCurrent; } + bool operator!=(const ConstIteratorBase & that) const { return !(*this == that); } + + ConstIteratorBase & operator++() + { + mCurrent = mCurrent->mNext; + return *this; + } + + ConstIteratorBase operator++(int) + { + ConstIteratorBase res(mCurrent); + mCurrent = mCurrent->mNext; + return res; + } + + ConstIteratorBase & operator--() + { + mCurrent = mCurrent->mPrev; + return *this; + } + + ConstIteratorBase operator--(int) + { + ConstIteratorBase res(mCurrent); + mCurrent = mCurrent->mPrev; + return res; + } + + protected: + const IntrusiveListNodeBase * mCurrent; + }; + + 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(first) -> item -> ... -> item(last) -> mNode + // ^ | + // \------------------------------------------/ + // + IntrusiveListBase() : mNode(&mNode, &mNode) {} + ~IntrusiveListBase() { mNode.Remove(); /* clear mNode such that the destructor checking mNode.IsInList doesn't fail */ } + + ConstIteratorBase begin() const { return ConstIteratorBase(mNode.mNext); } + ConstIteratorBase end() const { return ConstIteratorBase(&mNode); } + 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) { pos.mCurrent->Prepend(node); } + void InsertAfter(IteratorBase pos, IntrusiveListNodeBase * node) { pos.mCurrent->Append(node); } + void Remove(IntrusiveListNodeBase * node) { node->Remove(); } + + bool Contains(IntrusiveListNodeBase * node) const + { + for (auto & iter : *this) + { + if (&iter == node) + return true; + } + return false; + } + +private: + IntrusiveListNodeBase mNode; +}; + +/// The hook convert between node object T and IntrusiveListNodeBase +/// +/// When using this hook, the node type (T) MUST inherit from IntrusiveListNodeBase. +/// +template +class IntrusiveListBaseHook +{ +public: + static_assert(std::is_base_of::value, "T must be derived from IntrusiveListNodeBase"); + static T * ToObject(IntrusiveListNodeBase * node) { return static_cast(node); } + static IntrusiveListNodeBase * ToNode(T * object) { return static_cast(object); } +}; + +/// A double-linked list where the data is stored together with the previous/next pointers for cache efficiency / and compactness. +/// +/// The default hook (IntrusiveListBaseHook) requires T inherit from IntrusiveListNodeBase. +/// +/// IntrusiveListNodeBase object associated with a node is assumed to be longer than the list they belong to. A list is effcively a +/// single node into / a chain of other nodes referenced by pointers. +/// +/// A node may only belong to a single list. The code will assert (via VerifyOrDie) on this invariant. +/// +/// Example usage: +/// +/// class ListNode : public IntrusiveListNodeBase {}; +/// // ... +/// ListNode a,b,c; +/// IntrusiveList list; // NOTE: node lifetime >= list lifetime +/// +/// list.PushBack(&a); +/// list.PushFront(&b); +/// assert(list.Contains(&a) && list.Contains(&b) && !list.Contains(&c)); +template > +class IntrusiveList : public IntrusiveListBase +{ +public: + static_assert(std::is_base_of::value, "T must derive from IntrusiveListNodeBase"); + + IntrusiveList() : IntrusiveListBase() {} + + class ConstIterator : public IntrusiveListBase::ConstIteratorBase + { + public: + using value_type = const T; + using pointer = const T *; + using reference = const T &; + + ConstIterator(IntrusiveListBase::ConstIteratorBase && base) : IntrusiveListBase::ConstIteratorBase(std::move(base)) {} + const T * operator->() { return Hook::ToObject(mCurrent); } + const T & operator*() { return *Hook::ToObject(mCurrent); } + }; + + 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); } + }; + + ConstIterator begin() const { return IntrusiveListBase::begin(); } + ConstIterator end() const { return IntrusiveListBase::end(); } + 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) const { return IntrusiveListBase::Contains(Hook::ToNode(value)); } +}; + +} // namespace chip diff --git a/src/lib/support/tests/BUILD.gn b/src/lib/support/tests/BUILD.gn index 7ef9c6c8be1ad5..e43146452192a1 100644 --- a/src/lib/support/tests/BUILD.gn +++ b/src/lib/support/tests/BUILD.gn @@ -33,6 +33,7 @@ chip_test_suite("tests") { "TestErrorStr.cpp", "TestFixedBufferAllocator.cpp", "TestFold.cpp", + "TestIntrusiveList.cpp", "TestOwnerOf.cpp", "TestPool.cpp", "TestPrivateHeap.cpp", diff --git a/src/lib/support/tests/TestIntrusiveList.cpp b/src/lib/support/tests/TestIntrusiveList.cpp new file mode 100644 index 00000000000000..7e9bbb73051903 --- /dev/null +++ b/src/lib/support/tests/TestIntrusiveList.cpp @@ -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 +#include + +#include +#include + +#include + +namespace { + +using namespace chip; + +class ListNode : public IntrusiveListNodeBase +{ +}; + +void TestIntrusiveListRandom(nlTestSuite * inSuite, void * inContext) +{ + IntrusiveList l1; + ListNode node[100]; + std::list l2; + + auto op = [&](auto fun) { + if (l2.empty()) + return; + + auto l1p = l1.begin(); + auto l2p = l2.begin(); + for (size_t pos = static_cast(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(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);