// Copyright (c) 2010, Thomas Goyne // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. /// @file signal.h /// @brief /// @ingroup libaegisub #pragma once #include #include #include namespace agi { namespace signal { using namespace std::placeholders; class Connection; /// Implementation details; nothing outside this file should directly touch /// anything in the detail namespace namespace detail { class SignalBase; class ConnectionToken { friend class agi::signal::Connection; friend class SignalBase; SignalBase *signal; bool blocked; bool claimed; ConnectionToken(SignalBase *signal) : signal(signal), blocked(false), claimed(false) { } inline void Disconnect(); public: ~ConnectionToken() { Disconnect(); } }; } /// @class Connection /// @brief Object representing a connection to a signal class Connection { std::unique_ptr token; public: Connection() { } Connection(Connection&& that) : token(std::move(that.token)) { } Connection(detail::ConnectionToken *token) : token(token) { token->claimed = true; } Connection& operator=(Connection&& that) { token = std::move(that.token); return *this; } /// @brief End this connection /// /// This normally does not need to be manually called, as a connection is /// automatically closed when all Connection objects referring to it are /// gone. To temporarily enable or disable a connection, use Block/Unblock /// instead void Disconnect() { if (token) token->Disconnect(); } /// @brief Disable this connection until Unblock is called void Block() { if (token) token->blocked = true; } /// @brief Reenable this connection after it was disabled by Block void Unblock() { if (token) token->blocked = false; } }; /// @class UnscopedConnection /// @brief A connection which is not automatically closed /// /// Connections initially start out owned by the signal. If a slot knows that it /// will outlive a signal and does not need to be able to block a connection, it /// can simply ignore the return value of Connect. /// /// If a slot needs to be able to disconnect from a signal, it should store the /// returned connection in a Connection, which transfers ownership of the /// connection to the slot. If there is any chance that the signal will outlive /// the slot, this must be done. class UnscopedConnection { detail::ConnectionToken *token; public: UnscopedConnection(detail::ConnectionToken *token) : token(token) { } operator Connection() { return Connection(token); } }; namespace detail { /// @brief Polymorphic base class for slots /// /// This class has two purposes: to avoid having to make Connection /// templated on what type of connection it is controlling, and to avoid /// some messiness with templated friend classes class SignalBase { friend class ConnectionToken; /// @brief Disconnect the passed slot from the signal /// @param tok Token to disconnect virtual void Disconnect(ConnectionToken *tok)=0; /// Signals can't be copied SignalBase(SignalBase const&); SignalBase& operator=(SignalBase const&); protected: SignalBase() { } /// @brief Notify a slot that it has been disconnected /// @param tok Token to disconnect /// /// Used by the signal when the signal wishes to end a connection (such /// as if the signal is being destroyed while slots are still connected /// to it) void DisconnectToken(ConnectionToken *tok) { tok->signal = nullptr; } /// @brief Has a token been claimed by a scoped connection object? bool TokenClaimed(ConnectionToken *tok) { return tok->claimed; } /// @brief Create a new connection to this slot ConnectionToken *MakeToken() { return new ConnectionToken(this); } /// @brief Check if a connection currently wants to receive signals bool Blocked(ConnectionToken *tok) { return tok->blocked; } }; inline void ConnectionToken::Disconnect() { if (signal) signal->Disconnect(this); signal = nullptr; } /// @brief Templated common code for signals template class SignalBaseImpl final : public SignalBase { protected: typedef boost::container::map SlotMap; SlotMap slots; /// Signals currently connected to this slot void Disconnect(ConnectionToken *tok) override { slots.erase(tok); } /// Protected destructor so that we don't need a virtual destructor ~SignalBaseImpl() { for (auto& slot : slots) { DisconnectToken(slot.first); if (!TokenClaimed(slot.first)) delete slot.first; } } public: /// @brief Connect a signal to this slot /// @param sig Signal to connect /// @return The connection object UnscopedConnection Connect(Slot sig) { ConnectionToken *token = MakeToken(); slots.insert(std::make_pair(token, sig)); return UnscopedConnection(token); } template UnscopedConnection Connect(F func, Arg1 a1) { return Connect(std::bind(func, a1)); } template UnscopedConnection Connect(F func, Arg1 a1, Arg2 a2) { return Connect(std::bind(func, a1, a2)); } template UnscopedConnection Connect(F func, Arg1 a1, Arg2 a2, Arg3 a3) { return Connect(std::bind(func, a1, a2, a3)); } }; } #define SIGNALS_H_FOR_EACH_SIGNAL(...) \ for (auto cur = slots.begin(); cur != slots.end();) { \ if (Blocked(cur->first)) \ ++cur; \ else \ (cur++)->second(__VA_ARGS__); \ } /// @class Signal /// @brief Two-argument signal /// @param Arg1 Type of first argument to pass to slots /// @param Arg2 Type of second argument to pass to slots template class Signal final : public detail::SignalBaseImpl > { typedef detail::SignalBaseImpl > super; using super::Blocked; using super::slots; public: Signal() { } /// @brief Trigger this signal /// @param a1 First argument to the signal /// @param a2 Second argument to the signal /// /// The order in which connected slots are called is undefined and should /// not be relied on void operator()(Arg1 a1, Arg2 a2) { SIGNALS_H_FOR_EACH_SIGNAL(a1, a2) } // Don't hide the base overloads using super::Connect; /// @brief Connect a member function with the correct signature to this signal /// @param func Function to connect /// @param a1 Object /// /// This overload is purely for convenience so that classes can do /// sig.Connect(&Class::Foo, this) rather than /// sig.Connect(&Class::Foo, this, _1, _2) template UnscopedConnection Connect(void (T::*func)(Arg1, Arg2), T* a1) { return Connect(std::bind(func, a1, _1, _2)); } /// @brief Connect a member function with the correct signature to this signal /// @param func Function to connect /// @param a1 Object /// /// This overload is purely for convenience so that classes can do /// sig.Connect(&Class::Foo, this) rather than /// sig.Connect(&Class::Foo, this, _1) template UnscopedConnection Connect(void (T::*func)(Arg1), T* a1) { return Connect(std::bind(func, a1, _1)); } }; /// @class Signal /// @brief One-argument signal /// @param Arg1 Type of the argument to pass to slots template class Signal : public detail::SignalBaseImpl > { typedef detail::SignalBaseImpl > super; using super::Blocked; using super::slots; public: Signal() { } /// @brief Trigger this signal /// @param a1 The argument to the signal /// /// The order in which connected slots are called is undefined and should /// not be relied on void operator()(Arg1 a1) { SIGNALS_H_FOR_EACH_SIGNAL(a1) } // Don't hide the base overloads using super::Connect; /// @brief Connect a member function with the correct signature to this signal /// @param func Function to connect /// @param a1 Object /// /// This overload is purely for convenience so that classes can do /// sig.Connect(&Class::Foo, this) rather than sig.Connect(&Class::Foo, this, _1) template UnscopedConnection Connect(void (T::*func)(Arg1), T* a1) { return Connect(std::bind(func, a1, _1)); } }; /// @class Signal /// @brief Zero-argument signal template<> class Signal final : public detail::SignalBaseImpl > { typedef detail::SignalBaseImpl > super; using super::Blocked; using super::slots; public: Signal() { } /// @brief Trigger this signal /// /// The order in which connected slots are called is undefined and should /// not be relied on void operator()() { SIGNALS_H_FOR_EACH_SIGNAL() } }; #undef SIGNALS_H_FOR_EACH_SIGNAL } } /// @brief Define functions which forward their arguments to the connect method /// of the named signal /// @param sig Name of the signal /// @param method Name of the connect method /// /// When a signal is a member of a class, we typically want other objects to be /// able to connect to the signal, but not to be able to trigger it. To do this, /// make this signal private then use this macro to create public subscription /// methods /// /// Defines AddSignalNameListener #define DEFINE_SIGNAL_ADDERS(sig, method) \ template agi::signal::UnscopedConnection method(A a) { return sig.Connect(a); } \ template agi::signal::UnscopedConnection method(A a,B b) { return sig.Connect(a,b); } \ template agi::signal::UnscopedConnection method(A a,B b,C c) { return sig.Connect(a,b,c); } \ template agi::signal::UnscopedConnection method(A a,B b,C c,D d) { return sig.Connect(a,b,c,d); }