/* * 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 "nocopy.hpp" #include #include #include namespace cruft { namespace reduce { /// Returns the short-circuited logical-and of the results of all /// connected callbacks. struct logical_and { template decltype(auto) operator() (InputT first, InputT last, Args&&... args) { while (first != last) if (!(std::invoke (*first++, args...))) return false; return true; } }; /// Returns the short-circuited logical-or of the results of all /// connected callbacks. struct logical_or { template decltype(auto) operator() (InputT first, InputT last, Args&&... args) { while (first != last) if (std::invoke (*first++, args...)) return true; return false; } }; /// Unconditionally evaluates all connected callbacks. Returns nothing. struct noop { template void operator() (InputT first, InputT last, Args&&... args) { while (first != last) { std::invoke (*first++, args...); } } }; } template < typename FunctionT, typename ReductionT = reduce::noop > class signal { public: using reduction_type = ReductionT; using function_type = FunctionT; typedef std::list group; /////////////////////////////////////////////////////////////////////// struct cookie { cookie (cookie const&) = delete; cookie& operator= (cookie const&) = delete; cookie (typename group::iterator _position, signal &_parent): m_position (_position), m_parent (_parent) { ; } cookie (cookie &&rhs) noexcept: m_position (rhs.m_position), m_parent (rhs.m_parent) { rhs.m_position = rhs.m_parent.m_children.end (); } cookie& operator= (cookie &&rhs) noexcept { CHECK_EQ (&m_parent, &rhs.m_parent); std::swap (m_position, rhs.m_position); return *this; } ~cookie () { if (m_parent.m_children.end () != m_position) m_parent.disconnect (*this); } void reset (FunctionT &&cb) { *m_position = std::move (cb); } typename group::iterator m_position; signal &m_parent; }; public: signal () = default; signal (signal const&) = default; signal& operator= (signal const&) = default; signal (signal &&) noexcept = default; signal& operator= (signal &&) noexcept = default; ~signal () { CHECK (empty ()); } /// Add a callback to list. cookie connect [[nodiscard]] (FunctionT &&_cb) { return cookie ( m_children.insert ( m_children.end (), std::move (_cb) ), *this ); } cookie connect [[nodiscard]] (FunctionT const &_cb) { return cookie ( m_children.insert ( m_children.end (), std::move (_cb) ), *this ); } void disconnect (cookie &c) { m_children.erase (c.m_position); c.m_position = m_children.end (); } /// Disconnect all callbacks void clear (void) { m_children.clear (); } /// Returns the number of callbacks connected. size_t size (void) const { return m_children.size (); } bool empty (void) const { return m_children.empty (); } /// Execute all callbacks template decltype(auto) operator() (ArgsT&&... tail) { return ReductionT {} ( m_children.begin (), m_children.end (), std::forward (tail)... ); } private: group m_children; }; /////////////////////////////////////////////////////////////////////////// // 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; }; }