| /* |
| * |
| * Copyright (c) 2020 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. |
| */ |
| |
| /** |
| * @file |
| * This file contains definitions for Callback objects for registering with |
| * Clusters and the Device |
| */ |
| |
| #pragma once |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <lib/core/CHIPConfig.h> |
| |
| namespace chip { |
| |
| namespace Callback { |
| |
| /** |
| * @class Cancelable |
| * |
| * Private members of a Callback for use by subsystems that accept |
| * Callbacks for event registration/notification. |
| * |
| */ |
| class Cancelable |
| { |
| typedef void (*CancelFn)(Cancelable *); |
| |
| public: |
| /** |
| * @brief for use by Callback callees, i.e. those that accept callbacks for |
| * event registration. The names suggest how to use these members, but |
| * implementations can choose. |
| */ |
| Cancelable * mNext; |
| Cancelable * mPrev; |
| |
| // CHIP_CONFIG_CANCELABLE_HAS_INFO_STRING_FIELD allows consumers that were |
| // using this field to opt into having it (and the resulting memory bloat) |
| // while allowing everyone else to save the memory. |
| #if CHIP_CONFIG_CANCELABLE_HAS_INFO_STRING_FIELD |
| alignas(uint64_t) char mInfo[24]; |
| #endif // CHIP_CONFIG_CANCELABLE_HAS_INFO_STRING_FIELD |
| |
| /** |
| * @brief when non-null, indicates the Callback is registered with |
| * a subsystem and that Cancelable members belong to |
| * that subsystem |
| */ |
| CancelFn mCancel; |
| |
| Cancelable() |
| { |
| mNext = mPrev = this; |
| mCancel = nullptr; |
| } |
| |
| /** |
| * @brief run whatever function the callee/registrar has specified in order |
| * to clean up any resource allocation associated with the registration, |
| * and surrender ownership of the Cancelable's fields |
| */ |
| Cancelable * Cancel() |
| { |
| if (mCancel != nullptr) |
| { |
| CancelFn cancel = mCancel; |
| mCancel = nullptr; |
| cancel(this); |
| } |
| return this; |
| } |
| ~Cancelable() { Cancel(); } |
| |
| Cancelable(const Cancelable &) = delete; |
| }; |
| |
| typedef void (*CallFn)(void *); |
| |
| /** |
| * @class Callback |
| * |
| * Base struct used for registration of items of interest, includes |
| * memory for list management and storing information about the registration's |
| * meaning. Callback also defines cancellation. |
| * Callbacks can be registered with exactly one callee at a time. While |
| * registered (as indicated by a non-null mCancel function), all fields of |
| * the Callback save usercontext are "owned" by the callee, and should not |
| * be touched unless Cancel() has first been called. |
| * When a callee accepts a Callback for registration, step one is always Cancel(), |
| * in order to take ownership of Cancelable members next, prev, info_ptr, and info_scalar. |
| * This template class also defines a default notification function prototype. |
| * |
| * One-shot semantics can be accomplished by calling Cancel() before calling mCall. |
| * Persistent registration semantics would skip that. |
| * |
| * There is no provision for queueing data passed as arguments to a Callback's mCall |
| * function. If such a thing is required, the normal pattern is to take an output |
| * parameter at Callback registration time. |
| * |
| */ |
| template <class T = CallFn> |
| class Callback : private Cancelable |
| { |
| public: |
| /** |
| * pointer to owner context, normally passed to the run function |
| */ |
| void * mContext; |
| |
| /** |
| * where to call when the event of interest has occurred |
| */ |
| T mCall; |
| |
| /** |
| * Indication that the Callback is registered with a notifier |
| */ |
| bool IsRegistered() { return (mCancel != nullptr); } |
| |
| /** |
| * Cancel, i.e. de-register interest in the event, |
| * This is the only way to get access to the Cancelable, to enqueue, |
| * store any per-registration state. |
| * There are 3 primary use cases for this API: |
| * 1. For the owner of the Callback, Cancel() means "where-ever this Callback |
| * was put in a list or registered for an event, gimme back, remove interest". |
| * 2. To a new registrar, during a registration call, it means "hey cleanup any |
| * current registrations, let me use the internal fields of Cancelable |
| * to keep track of what the owner is interested in. |
| * 3. To any current registrar (i.e. when mCancel is non-null), Cancel() means: |
| * "remove this Callback from any internal lists and free any resources |
| * you've allocated to track the interest". |
| * |
| * For example: a sockets library with an API like Socket::Readable(Callback<> *cb) |
| * using an underlying persistent registration API with the OS (like epoll()) |
| * might store the file descriptor and interest mask in the scalar, put the |
| * Callback in a list. Cancel() would dequeue the callback and remove |
| * the socket from the interest set |
| * |
| */ |
| Cancelable * Cancel() { return Cancelable::Cancel(); } |
| |
| /** |
| * public constructor |
| */ |
| Callback(T call, void * context) : mContext(context), mCall(call) { Cancelable(); } |
| |
| /** |
| * TODO: type-safety? It'd be nice if Cancelables that aren't Callbacks returned null |
| * here. https://github.com/project-chip/connectedhomeip/issues/1350 |
| */ |
| static Callback * FromCancelable(Cancelable * ca) { return static_cast<Callback *>(ca); } |
| }; |
| |
| /** |
| * @brief core of a simple doubly-linked list Callback keeper-tracker-of |
| * |
| */ |
| class CallbackDeque : public Cancelable |
| { |
| public: |
| /** |
| * @brief appends with overridden cancel function, in case the |
| * list change requires some other state update. |
| */ |
| void Enqueue(Cancelable * ca, void (*cancel)(Cancelable *)) |
| { |
| // add to a doubly-linked list, set cancel function |
| InsertBefore(ca, this, cancel); |
| } |
| /** |
| * @brief appends |
| */ |
| void Enqueue(Cancelable * ca) { Enqueue(ca, Dequeue); } |
| |
| /** |
| * @brief dequeue, but don't cancel, all cas that match the by() |
| */ |
| void DequeueBy(bool (*by)(uint64_t, const Cancelable *), uint64_t p, Cancelable & dequeued) |
| { |
| for (Cancelable * ca = mNext; ca != this;) |
| { |
| Cancelable * next = ca->mNext; |
| if (by(p, ca)) |
| { |
| _Dequeue(ca); |
| _InsertBefore(ca, &dequeued); |
| } |
| ca = next; |
| } |
| } |
| |
| /** |
| * @brief insert the node in a queue in order, sorted by "sortby(a, b)" |
| * sortby(a, b) should return 1 if a > b, -1 if a < b and 0 if a == b |
| */ |
| void InsertBy(Cancelable * ca, int (*sortby)(void *, const Cancelable *, const Cancelable *), void * p, |
| void (*cancel)(Cancelable *)) |
| { |
| Cancelable * where; // node before which we need to insert |
| for (where = mNext; where != this; where = where->mNext) |
| { |
| if (sortby(p, ca, where) <= 0) |
| { |
| break; |
| } |
| } |
| InsertBefore(ca, where, cancel); |
| } |
| |
| void InsertBy(Cancelable * ca, int (*sortby)(void *, const Cancelable *, const Cancelable *), void * p) |
| { |
| InsertBy(ca, sortby, p, Dequeue); |
| } |
| |
| /** |
| * @brief insert the node in a the list at a specific point |
| */ |
| void InsertBefore(Cancelable * ca, Cancelable * where, void (*cancel)(Cancelable *)) |
| { |
| ca->Cancel(); // make doubly-sure we're not corrupting another list somewhere |
| ca->mCancel = cancel; |
| _InsertBefore(ca, where); |
| } |
| void InsertBefore(Cancelable * ca, Cancelable * where) { InsertBefore(ca, where, Dequeue); } |
| |
| /** |
| * @brief returns first item unless list is empty, otherwise returns NULL |
| */ |
| Cancelable * First() { return (mNext != this) ? mNext : nullptr; } |
| |
| /** |
| * @brief Dequeue all, return in a stub. does not cancel the cas, as the list |
| * members are still in use |
| */ |
| void DequeueAll(Cancelable & ready) |
| { |
| if (mNext != this) |
| { |
| ready.mNext = mNext; |
| ready.mPrev = mPrev; |
| ready.mPrev->mNext = &ready; |
| ready.mNext->mPrev = &ready; |
| |
| mNext = mPrev = this; |
| } |
| } |
| |
| /** |
| * @brief dequeue but don't cancel, useful if |
| * immediately putting on another list |
| */ |
| static void Dequeue(Cancelable * ca) |
| { |
| _Dequeue(ca); |
| ca->mCancel = nullptr; |
| } |
| |
| /** |
| * @brief empty? |
| */ |
| bool IsEmpty() { return mNext == this; } |
| |
| private: |
| static void _Dequeue(Cancelable * ca) |
| { |
| ca->mNext->mPrev = ca->mPrev; |
| ca->mPrev->mNext = ca->mNext; |
| ca->mNext = ca->mPrev = ca; |
| } |
| void _InsertBefore(Cancelable * ca, Cancelable * where) |
| { |
| ca->mPrev = where->mPrev; |
| where->mPrev->mNext = ca; |
| where->mPrev = ca; |
| ca->mNext = where; |
| } |
| }; |
| |
| } // namespace Callback |
| } // namespace chip |