| /* |
| * |
| * Copyright (c) 2024 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 <lib/core/CHIPCallback.h> |
| #include <lib/support/CodeUtils.h> |
| |
| #include <utility> |
| |
| namespace chip { |
| namespace Callback { |
| |
| namespace detail { |
| // Internal helper functions |
| template <size_t Index> |
| void TaggedDequeueGroup(Cancelable * cancelable); |
| void EnqueueWithGroup(Cancelable * cancelable, Cancelable *& group, Cancelable * anchor, void (*cancelFn)(Cancelable *)); |
| void LinkGroup(Cancelable * prev, Cancelable * next); |
| inline Cancelable * ClearCancelable(Cancelable * cancelable); |
| } // namespace detail |
| |
| /** |
| * A GroupedCallbackList manages a list of Callback objects (see CHIPCallback.h). |
| * The state of the list is maintained using the prev/next pointers of each Callback. |
| * Unlike a normal linked list where entries are managed individually, this class |
| * manages a number of related callbacks as a group, with the callback function types |
| * given as template parameters. |
| * |
| * For example, a `GroupedCallbackList<SuccessFn, FailureFn>` manages groups of a |
| * `Callback<SuccessFn>` and a `Callback<FailureFn>`. |
| * |
| * Groups of callbacks are enqueued and dequeued (or cancelled) as a unit. |
| * Within a group each callback is optional (i.e. can be null), however attempting |
| * to enqueue a group where all callbacks are null has no effect. |
| */ |
| template <typename... T> |
| class GroupedCallbackList : protected Cancelable |
| { |
| public: |
| GroupedCallbackList() = default; |
| ~GroupedCallbackList() { Clear(); } |
| |
| GroupedCallbackList(GroupedCallbackList const &) = delete; |
| GroupedCallbackList & operator=(GroupedCallbackList const &) = delete; |
| |
| bool IsEmpty() { return mNext == this; } |
| |
| /** |
| * Enqueues the specified group of callbacks, any of which may be null. |
| */ |
| void Enqueue(Callback<T> *... callback) { Enqueue(std::index_sequence_for<T...>{}, callback...); } |
| |
| /** |
| * If the list is non-empty, populates the reference arguments with the first |
| * group of callbacks and returns true. Returns false if the list is empty. |
| */ |
| bool Peek(Callback<T> *&... callback) const { return Peek(std::index_sequence_for<T...>{}, callback...); } |
| |
| /** |
| * Like Peek(), but additionally removes the first group of callbacks from the list. |
| */ |
| bool Take(Callback<T> *&... callback) |
| { |
| VerifyOrReturnValue(Peek(callback...), false); |
| mNext->Cancel(); |
| return true; |
| } |
| |
| /** |
| * Moves all elements of the source list into this list, leaving the source list empty. |
| */ |
| void EnqueueTakeAll(GroupedCallbackList & source) |
| { |
| VerifyOrReturn(!source.IsEmpty() && this != &source); |
| detail::LinkGroup(mPrev, source.mNext); |
| source.mPrev->mNext = this; |
| mPrev = source.mPrev; |
| |
| source.mPrev = source.mNext = &source; |
| } |
| |
| void Clear() |
| { |
| Cancelable * next = mNext; |
| while (next != this) |
| { |
| next = detail::ClearCancelable(next); |
| } |
| mPrev = mNext = this; |
| } |
| |
| private: |
| /* |
| * The grouped list structure is similar to a normal doubly linked list, |
| * with the list object itself (via inheriting from Cancelable) acting as |
| * an external "anchor" node that is both the head and tail of the list. |
| * |
| * However we have the additional requirement of representing node grouping. |
| * Due to the requirement so support sparse groups (one or more callbacks may |
| * not be present in a particular group) we cannot rely on a fixed group size. |
| * This problem is solved by having the "prev" pointer for all nodes in a group |
| * point to the node before the group, as illustrated below: |
| * |
| * |Anchor| |Grp 1| |====== Group 2 ======| |
| * _______________________________________________ |
| * / \ |
| * \ +---+ +---+ +---+ +---+ +---+ / |
| * ->|###|----->| |----->| |-->| |-->| |-- |
| * |###| | | | | | | | | |
| * --|###|<-----| |<-----| | -| | -| |<- |
| * / +---+ +---+ \ +---+ / +---+ / +---+ \ |
| * | \_______/ / | |
| * | \_____________/ | |
| * \_______________________________________________/ |
| * |
| * This allows the start of a group to be reached from any group member via |
| * ->prev->next. Nodes in a group can be enumerated by via the "next" chain, |
| * inspecting the "prev" pointers to detect the end of the group. The price |
| * for encoding grouping in this way is that upon removal of a group we have |
| * to update not just the "prev" pointer of the following node, but of all |
| * nodes in the following group. |
| * |
| * When retrieving a (sparse) group from the list, we also need to be able |
| * to tell which callbacks are present: In a grouped list with types (A, B) |
| * both (a, nullptr) and (nullptr, b) are by necessity represented by only |
| * a single node in the list. To be able to recover this information, we use |
| * distinct trampolines that tag the "cancel" function pointer stored in each |
| * node with the index of the callback type within the argument type tuple. |
| */ |
| |
| template <std::size_t... Index> |
| void Enqueue(std::index_sequence<Index...>, Callback<T> *... callback) |
| { |
| Cancelable * group = nullptr; |
| ( |
| [&] { |
| VerifyOrReturn(callback != nullptr); |
| detail::EnqueueWithGroup(callback->Cancel(), group, this, &detail::TaggedDequeueGroup<Index>); |
| }(), |
| ...); |
| } |
| |
| template <std::size_t... Index> |
| bool Peek(std::index_sequence<Index...>, Callback<T> *&... callback) const |
| { |
| Cancelable * cancelable = mNext; |
| VerifyOrReturnValue(cancelable != this, false); |
| Cancelable * groupPrev = cancelable->mPrev; |
| ( |
| [&] { |
| if (cancelable->mPrev == groupPrev && cancelable->mCancel == &detail::TaggedDequeueGroup<Index>) |
| { |
| callback = Callback<decltype(callback->mCall)>::FromCancelable(cancelable); |
| cancelable = cancelable->mNext; |
| } |
| else |
| { |
| callback = nullptr; |
| } |
| }(), |
| ...); |
| return true; |
| } |
| }; |
| |
| namespace detail { |
| |
| // Inserts `cancelable` before `anchor`, either starting a new `group` |
| // (populating the passed pointer if it is null) or adding to it. |
| inline void EnqueueWithGroup(Cancelable * cancelable, Cancelable *& group, Cancelable * anchor, void (*cancelFn)(Cancelable *)) |
| { |
| cancelable->mCancel = cancelFn; |
| cancelable->mNext = anchor; |
| if (!group) |
| { |
| group = cancelable; |
| cancelable->mPrev = anchor->mPrev; |
| } |
| else |
| { |
| cancelable->mPrev = group->mPrev; |
| } |
| anchor->mPrev->mNext = cancelable; |
| anchor->mPrev = cancelable; |
| } |
| |
| // Establish prev/next links between `prev` and the group starting at `cancelable`. |
| inline void LinkGroup(Cancelable * prev, Cancelable * cancelable) |
| { |
| prev->mNext = cancelable; |
| |
| Cancelable * groupPrev = cancelable->mPrev; |
| do |
| { |
| cancelable->mPrev = prev; |
| cancelable = cancelable->mNext; |
| } while (cancelable->mPrev == groupPrev); |
| } |
| |
| // Clears the state of a cancelable and returns the following one. |
| // Does NOT touch the state of adjacent nodes. |
| inline Cancelable * ClearCancelable(Cancelable * cancelable) |
| { |
| auto * next = cancelable->mNext; |
| cancelable->mPrev = cancelable->mNext = cancelable; |
| cancelable->mCancel = nullptr; |
| return next; |
| } |
| |
| // Dequeues `cancelable` and all otehr nodes in the same group. |
| inline void DequeueGroup(Cancelable * cancelable) |
| { |
| Cancelable * prev = cancelable->mPrev; |
| Cancelable * next = prev->mNext; |
| do |
| { |
| next = ClearCancelable(next); |
| } while (next->mPrev == prev); |
| LinkGroup(prev, next); |
| } |
| |
| template <size_t Index> |
| void TaggedDequeueGroup(Cancelable * cancelable) |
| { |
| (void) Index; // not used, we only care that instantiations have unique addresses |
| DequeueGroup(cancelable); |
| } |
| |
| } // namespace detail |
| } // namespace Callback |
| } // namespace chip |