/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2011-2019 Danny Robson */ #pragma once #include "types/traits.hpp" #include "debug/assert.hpp" #include namespace cruft { /// A signal object whose clients can listen for invocations. /// /// The arguments supplied to the invocation of the parent are supplied /// to the callback of each child. /// /// Every client must store the cookie from `connect`. When this goes out /// of scope it will disconnect the callback from the signal object. /// It is permissible to release a cookie while it is being invoked, but /// no other cookie for the duration. /// /// All cookies _should_ be destroyed or released before the signal is /// destroyed. /// /// \tparam FunctionT The type of the callback template < typename FunctionT > class signal { public: using function_type = FunctionT; /////////////////////////////////////////////////////////////////////// /// A single node in a doubly linked list of callbacks to invoke when /// a signal is invoked. /// /// They perform no direct processing by themselves. Instead they are /// entirely focused on managing lifetimes of, and pointers to, the /// callbacks that will be invoked. struct cookie { cookie (FunctionT &&_callback) : callback (std::move (_callback)) , next (nullptr) , prev (nullptr) { ; } cookie (cookie &&rhs) noexcept : cookie (std::move (rhs.callback)) { replace (rhs); } cookie& operator= (cookie &&rhs) noexcept { replace (rhs); callback = std::move (rhs.callback); return *this; } cookie (cookie const&) = delete; cookie& operator= (cookie const&) = delete; ~cookie () { release (); } /// Take the position of `rhs` in the linked list. void replace (cookie &rhs) { // It could be more efficient to special case these // operations but this shouldn't be an operation that // occurs in a hot loop anyway. release (); rhs.append (*this); rhs.release (); } /// Link the provided cookie into the linked list as our /// successor. /// /// The provided cookie must not currently be part of any /// linked list. void append (cookie &rhs) { CHECK (rhs.next == nullptr); CHECK (rhs.prev == nullptr); if (next) { CHECK_EQ (next->prev, this); next->prev = &rhs; } rhs.next = next; rhs.prev = this; next = &rhs; } /// If this cookie is part of a linked list then remove it /// from the linked list. Otherwise this is a noop. void release (void) { if (next) next->prev = prev; if (prev) prev->next = next; next = nullptr; prev = nullptr; } FunctionT callback; /// The next node in the linked list. Or nullptr if this is the end. cookie *next; /// The previous node in the linked list. Or nullptr if this is the start. cookie *prev; }; public: signal () : m_head (FunctionT{}) { ; } signal (signal const&) = delete; signal& operator= (signal const&) = delete; signal (signal &&rhs) noexcept = default; signal& operator= (signal &&rhs) noexcept = default; ~signal () { CHECK (empty ()); } /// Add a callback to list. cookie connect [[nodiscard]] (FunctionT &&_callback) { cookie res (std::move (_callback)); m_head.append (res); return res; } /// Disconnect all callbacks void clear (void); /// Returns the number of callbacks connected. std::size_t size (void) const { std::size_t accum = 0; for (auto cursor = m_head.next; cursor; cursor = cursor->next) ++accum; return accum; } bool empty (void) const { return m_head.next == nullptr; } /// Execute all callbacks template decltype(auto) operator() (ArgsT&&... tail) { // We need to cache the cursor and advance _before_ we invoke // the cookie so that we have saved a pointer to the next // child in case it gets released on us during the call. cookie *cursor = m_head.next; while (cursor) { auto now = cursor; cursor = cursor->next; std::invoke (now->callback, tail...); } } private: /// The first node in the linked list of callbacks. We /// unconditionally store this node to simplify anchoring the /// linked list to the signal object and removing nodes via their /// destructors. cookie m_head; }; //------------------------------------------------------------------------- // Function references are _actually_ used as if they are std::function // objects in our libraries. // // TODO: avoid this implicit conversion without need a massive rewrite. template class signal : public signal> { }; /////////////////////////////////////////////////////////////////////////// // wrap a value in a signal and trigger on assignment //template class ReductionT> template > class value_signal : public signal { public: explicit value_signal (ValueT _value): m_value (_value) { ; } value_signal () = default; operator const ValueT&() const { return m_value; } value_signal& operator= (const ValueT &t) { m_value = t; (*this) (m_value); return *this; } private: ValueT m_value; }; }