Convert signal.h to variadic templates
This commit is contained in:
parent
ecdf7c4fc3
commit
09d0d039e0
5 changed files with 80 additions and 163 deletions
|
@ -23,11 +23,7 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace agi {
|
namespace agi { namespace signal {
|
||||||
namespace signal {
|
|
||||||
|
|
||||||
using namespace std::placeholders;
|
|
||||||
|
|
||||||
class Connection;
|
class Connection;
|
||||||
|
|
||||||
/// Implementation details; nothing outside this file should directly touch
|
/// Implementation details; nothing outside this file should directly touch
|
||||||
|
@ -39,18 +35,17 @@ namespace detail {
|
||||||
friend class SignalBase;
|
friend class SignalBase;
|
||||||
|
|
||||||
SignalBase *signal;
|
SignalBase *signal;
|
||||||
bool blocked;
|
bool blocked = false;
|
||||||
bool claimed;
|
bool claimed = false;
|
||||||
|
|
||||||
ConnectionToken(SignalBase *signal) : signal(signal), blocked(false), claimed(false) { }
|
ConnectionToken(SignalBase *signal) : signal(signal) { }
|
||||||
inline void Disconnect();
|
inline void Disconnect();
|
||||||
public:
|
public:
|
||||||
~ConnectionToken() { Disconnect(); }
|
~ConnectionToken() { Disconnect(); }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @class Connection
|
/// Object representing a connection to a signal
|
||||||
/// @brief Object representing a connection to a signal
|
|
||||||
class Connection {
|
class Connection {
|
||||||
std::unique_ptr<detail::ConnectionToken> token;
|
std::unique_ptr<detail::ConnectionToken> token;
|
||||||
public:
|
public:
|
||||||
|
@ -73,8 +68,8 @@ public:
|
||||||
/// @brief Reenable this connection after it was disabled by Block
|
/// @brief Reenable this connection after it was disabled by Block
|
||||||
void Unblock() { if (token) token->blocked = false; }
|
void Unblock() { if (token) token->blocked = false; }
|
||||||
};
|
};
|
||||||
/// @class UnscopedConnection
|
|
||||||
/// @brief A connection which is not automatically closed
|
/// A connection which is not automatically closed
|
||||||
///
|
///
|
||||||
/// Connections initially start out owned by the signal. If a slot knows that it
|
/// 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
|
/// will outlive a signal and does not need to be able to block a connection, it
|
||||||
|
@ -92,7 +87,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
/// @brief Polymorphic base class for slots
|
/// Polymorphic base class for slots
|
||||||
///
|
///
|
||||||
/// This class has two purposes: to avoid having to make Connection
|
/// This class has two purposes: to avoid having to make Connection
|
||||||
/// templated on what type of connection it is controlling, and to avoid
|
/// templated on what type of connection it is controlling, and to avoid
|
||||||
|
@ -104,10 +99,10 @@ namespace detail {
|
||||||
virtual void Disconnect(ConnectionToken *tok)=0;
|
virtual void Disconnect(ConnectionToken *tok)=0;
|
||||||
|
|
||||||
/// Signals can't be copied
|
/// Signals can't be copied
|
||||||
SignalBase(SignalBase const&);
|
SignalBase(SignalBase const&) = delete;
|
||||||
SignalBase& operator=(SignalBase const&);
|
SignalBase& operator=(SignalBase const&) = delete;
|
||||||
protected:
|
protected:
|
||||||
SignalBase() { }
|
SignalBase() = default;
|
||||||
/// @brief Notify a slot that it has been disconnected
|
/// @brief Notify a slot that it has been disconnected
|
||||||
/// @param tok Token to disconnect
|
/// @param tok Token to disconnect
|
||||||
///
|
///
|
||||||
|
@ -130,158 +125,80 @@ namespace detail {
|
||||||
if (signal) signal->Disconnect(this);
|
if (signal) signal->Disconnect(this);
|
||||||
signal = nullptr;
|
signal = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Templated common code for signals
|
|
||||||
template<class Slot>
|
|
||||||
class SignalBaseImpl : public SignalBase {
|
|
||||||
protected:
|
|
||||||
typedef boost::container::map<ConnectionToken*, Slot> 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<class F, class Arg1>
|
|
||||||
UnscopedConnection Connect(F func, Arg1 a1) {
|
|
||||||
return Connect(std::bind(func, a1));
|
|
||||||
}
|
|
||||||
template<class F, class Arg1, class Arg2>
|
|
||||||
UnscopedConnection Connect(F func, Arg1 a1, Arg2 a2) {
|
|
||||||
return Connect(std::bind(func, a1, a2));
|
|
||||||
}
|
|
||||||
template<class F, class Arg1, class Arg2, class Arg3>
|
|
||||||
UnscopedConnection Connect(F func, Arg1 a1, Arg2 a2, Arg3 a3) {
|
|
||||||
return Connect(std::bind(func, a1, a2, a3));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SIGNALS_H_FOR_EACH_SIGNAL(...) \
|
template<typename... Args>
|
||||||
for (auto cur = slots.begin(); cur != slots.end();) { \
|
class Signal final : private detail::SignalBase {
|
||||||
if (Blocked(cur->first)) \
|
using Slot = std::function<void(Args...)>;
|
||||||
++cur; \
|
boost::container::map<detail::ConnectionToken*, Slot> slots; /// Signals currently connected to this slot
|
||||||
else \
|
|
||||||
(cur++)->second(__VA_ARGS__); \
|
void Disconnect(detail::ConnectionToken *tok) override {
|
||||||
|
slots.erase(tok);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @class Signal
|
UnscopedConnection DoConnect(Slot sig) {
|
||||||
/// @brief Two-argument signal
|
auto token = MakeToken();
|
||||||
/// @param Arg1 Type of first argument to pass to slots
|
slots.insert(std::make_pair(token, sig));
|
||||||
/// @param Arg2 Type of second argument to pass to slots
|
return UnscopedConnection(token);
|
||||||
template<class Arg1 = void, class Arg2 = void>
|
}
|
||||||
class Signal final : public detail::SignalBaseImpl<std::function<void (Arg1, Arg2)> > {
|
|
||||||
typedef detail::SignalBaseImpl<std::function<void (Arg1, Arg2)> > super;
|
|
||||||
using super::Blocked;
|
|
||||||
using super::slots;
|
|
||||||
public:
|
|
||||||
Signal() { }
|
|
||||||
|
|
||||||
/// @brief Trigger this signal
|
public:
|
||||||
/// @param a1 First argument to the signal
|
~Signal() {
|
||||||
/// @param a2 Second argument to the signal
|
for (auto& slot : slots) {
|
||||||
|
DisconnectToken(slot.first);
|
||||||
|
if (!TokenClaimed(slot.first)) delete slot.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trigger this signal
|
||||||
///
|
///
|
||||||
/// The order in which connected slots are called is undefined and should
|
/// The order in which connected slots are called is undefined and should
|
||||||
/// not be relied on
|
/// not be relied on
|
||||||
void operator()(Arg1 a1, Arg2 a2) { SIGNALS_H_FOR_EACH_SIGNAL(a1, a2) }
|
void operator()(Args... args) {
|
||||||
|
for (auto cur = slots.begin(); cur != slots.end(); ) {
|
||||||
// Don't hide the base overloads
|
if (Blocked(cur->first))
|
||||||
using super::Connect;
|
++cur;
|
||||||
|
else
|
||||||
/// @brief Connect a member function with the correct signature to this signal
|
(cur++)->second(args...);
|
||||||
/// @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<class T>
|
|
||||||
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
|
/// @brief Connect a signal to this slot
|
||||||
/// @param func Function to connect
|
/// @param sig Signal to connect
|
||||||
/// @param a1 Object
|
/// @return The connection object
|
||||||
///
|
UnscopedConnection Connect(Slot sig) {
|
||||||
/// This overload is purely for convenience so that classes can do
|
return DoConnect(sig);
|
||||||
/// sig.Connect(&Class::Foo, this) rather than
|
}
|
||||||
/// sig.Connect(&Class::Foo, this, _1)
|
|
||||||
template<class T>
|
// Convenience wrapper for a member function which matches the signal's signature
|
||||||
|
template<typename T>
|
||||||
|
UnscopedConnection Connect(void (T::*func)(Args...), T* a1) {
|
||||||
|
return DoConnect([=](Args... args) { (a1->*func)(args...); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience wrapper for a callable which does not use any signal args
|
||||||
|
template<typename Thunk, typename = decltype((*(Thunk *)0)())>
|
||||||
|
UnscopedConnection Connect(Thunk&& func) {
|
||||||
|
return DoConnect([=](Args... args) mutable { func(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience wrapper for a member function which does not use any signal
|
||||||
|
// args. The match is overly-broad to avoid having two methods with the
|
||||||
|
// same signature when the signal has no args.
|
||||||
|
template<typename T, typename MemberThunk>
|
||||||
|
UnscopedConnection Connect(MemberThunk func, T* obj) {
|
||||||
|
return DoConnect([=](Args... args) { (obj->*func)(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience wrapper for a member function which uses only the first
|
||||||
|
// signal arg.
|
||||||
|
template<typename T, typename Arg1>
|
||||||
UnscopedConnection Connect(void (T::*func)(Arg1), T* a1) {
|
UnscopedConnection Connect(void (T::*func)(Arg1), T* a1) {
|
||||||
return Connect(std::bind(func, a1, _1));
|
return DoConnect(std::bind(func, a1, std::placeholders::_1));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @class Signal
|
} }
|
||||||
/// @brief One-argument signal
|
|
||||||
/// @param Arg1 Type of the argument to pass to slots
|
|
||||||
template<class Arg1>
|
|
||||||
class Signal<Arg1, void> : public detail::SignalBaseImpl<std::function<void (Arg1)> > {
|
|
||||||
typedef detail::SignalBaseImpl<std::function<void (Arg1)> > 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<class T>
|
|
||||||
UnscopedConnection Connect(void (T::*func)(Arg1), T* a1) {
|
|
||||||
return Connect(std::bind(func, a1, _1));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @class Signal
|
|
||||||
/// @brief Zero-argument signal
|
|
||||||
template<>
|
|
||||||
class Signal<void> final : public detail::SignalBaseImpl<std::function<void ()> > {
|
|
||||||
typedef detail::SignalBaseImpl<std::function<void ()> > 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
|
/// @brief Define functions which forward their arguments to the connect method
|
||||||
/// of the named signal
|
/// of the named signal
|
||||||
|
@ -295,7 +212,7 @@ public:
|
||||||
///
|
///
|
||||||
/// Defines AddSignalNameListener
|
/// Defines AddSignalNameListener
|
||||||
#define DEFINE_SIGNAL_ADDERS(sig, method) \
|
#define DEFINE_SIGNAL_ADDERS(sig, method) \
|
||||||
template<class A> agi::signal::UnscopedConnection method(A a) { return sig.Connect(a); } \
|
template<typename... Args> \
|
||||||
template<class A,class B> agi::signal::UnscopedConnection method(A a,B b) { return sig.Connect(a,b); } \
|
agi::signal::UnscopedConnection method(Args&&... args) { \
|
||||||
template<class A,class B,class C> agi::signal::UnscopedConnection method(A a,B b,C c) { return sig.Connect(a,b,c); } \
|
return sig.Connect(std::forward<Args>(args)...); \
|
||||||
template<class A,class B,class C,class D> agi::signal::UnscopedConnection method(A a,B b,C c,D d) { return sig.Connect(a,b,c,d); }
|
}
|
||||||
|
|
|
@ -1159,7 +1159,7 @@ void AudioDisplay::OnAudioOpen(AudioProvider *provider)
|
||||||
{
|
{
|
||||||
if (connections.empty())
|
if (connections.empty())
|
||||||
{
|
{
|
||||||
connections.push_back(controller->AddAudioCloseListener(&AudioDisplay::OnAudioOpen, this, nullptr));
|
connections.push_back(controller->AddAudioCloseListener([=] { OnAudioOpen(nullptr); }));
|
||||||
connections.push_back(controller->AddPlaybackPositionListener(&AudioDisplay::OnPlaybackPosition, this));
|
connections.push_back(controller->AddPlaybackPositionListener(&AudioDisplay::OnPlaybackPosition, this));
|
||||||
connections.push_back(controller->AddPlaybackStopListener(&AudioDisplay::RemoveTrackCursor, this));
|
connections.push_back(controller->AddPlaybackStopListener(&AudioDisplay::RemoveTrackCursor, this));
|
||||||
connections.push_back(controller->AddTimingControllerListener(&AudioDisplay::OnTimingController, this));
|
connections.push_back(controller->AddTimingControllerListener(&AudioDisplay::OnTimingController, this));
|
||||||
|
|
|
@ -224,7 +224,7 @@ FrameMain::FrameMain()
|
||||||
|
|
||||||
StartupLog("Create views and inner main window controls");
|
StartupLog("Create views and inner main window controls");
|
||||||
InitContents();
|
InitContents();
|
||||||
OPT_SUB("Video/Detached/Enabled", &FrameMain::OnVideoDetach, this, agi::signal::_1);
|
OPT_SUB("Video/Detached/Enabled", &FrameMain::OnVideoDetach, this);
|
||||||
|
|
||||||
StartupLog("Set up drag/drop target");
|
StartupLog("Set up drag/drop target");
|
||||||
SetDropTarget(new AegisubFileDropTarget(this));
|
SetDropTarget(new AegisubFileDropTarget(this));
|
||||||
|
|
|
@ -130,8 +130,8 @@ SubsController::SubsController(agi::Context *context)
|
||||||
, undo_connection(context->ass->AddUndoManager(&SubsController::OnCommit, this))
|
, undo_connection(context->ass->AddUndoManager(&SubsController::OnCommit, this))
|
||||||
{
|
{
|
||||||
autosave_timer_changed(&autosave_timer);
|
autosave_timer_changed(&autosave_timer);
|
||||||
OPT_SUB("App/Auto/Save", autosave_timer_changed, &autosave_timer);
|
OPT_SUB("App/Auto/Save", [=] { autosave_timer_changed(&autosave_timer); });
|
||||||
OPT_SUB("App/Auto/Save Every Seconds", autosave_timer_changed, &autosave_timer);
|
OPT_SUB("App/Auto/Save Every Seconds", [=] { autosave_timer_changed(&autosave_timer); });
|
||||||
|
|
||||||
autosave_timer.Bind(wxEVT_TIMER, [=](wxTimerEvent&) {
|
autosave_timer.Bind(wxEVT_TIMER, [=](wxTimerEvent&) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -53,7 +53,7 @@ VideoSlider::VideoSlider (wxWindow* parent, agi::Context *c)
|
||||||
SetMinSize(wxSize(20, 25));
|
SetMinSize(wxSize(20, 25));
|
||||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||||
|
|
||||||
slots.push_back(OPT_SUB("Video/Slider/Show Keyframes", &wxWindow::Refresh, this, false, nullptr));
|
slots.push_back(OPT_SUB("Video/Slider/Show Keyframes", [=] { Refresh(false); }));
|
||||||
slots.push_back(c->videoController->AddSeekListener(&VideoSlider::SetValue, this));
|
slots.push_back(c->videoController->AddSeekListener(&VideoSlider::SetValue, this));
|
||||||
slots.push_back(c->videoController->AddVideoOpenListener(&VideoSlider::VideoOpened, this));
|
slots.push_back(c->videoController->AddVideoOpenListener(&VideoSlider::VideoOpened, this));
|
||||||
slots.push_back(c->videoController->AddKeyframesListener(&VideoSlider::KeyframesChanged, this));
|
slots.push_back(c->videoController->AddKeyframesListener(&VideoSlider::KeyframesChanged, this));
|
||||||
|
|
Loading…
Reference in a new issue