#pragma once
#include "Variant.h"
#include "Common.h"
BEGIN_NAMESPACE
class InvokableInterface : public InterfaceBase
{
public:
virtual void operator()(Variant& a_params) = 0;
};
template <typename Method>
class StaticFunctionCallback : public InvokableInterface
{
public:
StaticFunctionCallback(Method a_method)
: m_method(a_method)
{
}
virtual void operator()(Variant& a_params)
{
(*m_method)(a_params);
}
private:
Method m_method;
};
template <class Class, typename Method>
class MethodCallback : public InvokableInterface
{
public:
MethodCallback(Class* a_obj, Method a_method)
: m_object(a_obj)
, m_method(a_method)
{
}
MethodCallback(MethodCallback const& a_other)
{
m_object = a_other.m_object;
//m_object.addRef();
m_method = a_other.m_method;
}
MethodCallback& operator=(MethodCallback const& a_other)
{
m_object = a_other.m_object;
//m_object.addRef();
m_method = a_other.m_method;
return *this;
}
virtual void operator()(Variant& a_params)
{
(m_object->*m_method)(a_params);
}
private:
Class* m_object;
Method m_method;
};
class Slot
{
public:
/*
// rule of three
// How to handle copying of slot objects
// Also need to ensure the safety of the object and method this slot holds going out of scope or being deleted
// Really need object passed in to be a SharedPtr or something that will mean that this slot will know about it
// being deleted
Slot(Slot const& a_other);
Slot& operator=(Slot const & a_other)
{
a_other.swap(*this);
return *this;
}
void swap(Slot & a_other) {
std::swap(placementNewBuffer, a_other.placementNewBuffer);
std::swap(m_callback, a_other.m_callback);
std::swap(m_variant, a_other.m_variant);
}
*/
Slot() : m_callback(0)
{
}
~Slot(void)
{
}
/*
Slot(Slot const& a_other) {
memcpy(placementNewBuffer, a_other.placementNewBuffer, sizeof(placementNewBuffer));
m_callback = a_other.m_callback;
MethodCallback* cb = dynamic_cast<MethodCallback>(m_callback);
if (cb)
cb->m
m_variant = a_other.m_variant;
}
Slot& operator=(Slot const & a_other)
{
memcpy(placementNewBuffer, a_other.placementNewBuffer, sizeof(placementNewBuffer));
m_callback = a_other.m_callback;
m_variant = a_other.m_variant;
return *this;
}
*/
template <typename Method>
Slot(Method a_method, Variant a_v = Variant())
: m_callback(new(placementNewBuffer) StaticFunctionCallback<Method>(a_method)), m_variant(a_v)
{
STATIC_ASSERT(sizeof(StaticFunctionCallback<Method>) <= sizeof(placementNewBuffer), increaseBufferBize);
}
template <class Class, typename Method>
Slot(Class* object, Method a_method, Variant a_v = Variant())
: m_callback(new(placementNewBuffer) MethodCallback<Class,Method>(object, a_method)), m_variant(a_v)
{
STATIC_ASSERT(sizeof(MethodCallback<Class,Method>) <= sizeof(placementNewBuffer), increaseBufferBize);
}
// Need some kind of way to notify when object gets destroyed so that it can remove this slot
void operator()()
{
(*m_callback)(m_variant);
}
void operator()(Variant& a_params)
{
if (m_variant.isValid())
(*m_callback)(m_variant);
else
(*m_callback)(a_params);
}
template <typename T>
void operator()(const T& a_params)
{
Variant v(a_params);
(*this)(v);
}
private:
#ifdef _WIN32
char placementNewBuffer[24]; // by trial and error, 24 was min to compile x64 using MSVC
#else
char placementNewBuffer[32]; // by trial and error, 1024 was min to compile on MacOSX
#endif
InvokableInterface *m_callback;
Variant m_variant;
};
class Signal
{
public:
// Because a Signal holds a list of Slots, the same needs to be
// thought through about the life time of objects and the places that hold references
Signal() : m_insideInvokation(false) {}
//~Signal() { m_connectionList.clear(); }
void addConnection(SharedPtr<Slot>& a_delegate) { m_connectionList.push_back(a_delegate); }
// Need some kind of callback or something that when a slot with an object has that object deleted,
// then the slot get destroyed, and when that happens it disconnects the slot from this signal
void disconnectAll() { m_connectionList.clear(); }
bool disconnectOne(SharedPtr<Slot>& a_delegate) { /* TODO */ return true; }
void operator()()
{
if (!m_insideInvokation) // prevent recursion
{
m_insideInvokation = true;
for (uint32_t i = 0; i < m_connectionList.size(); i++)
(*m_connectionList[i])();
m_insideInvokation = false;
}
}
void operator()(Variant& a_param)
{
if (!m_insideInvokation) // prevent recursion
{
m_insideInvokation = true;
for (uint32_t i = 0; i < m_connectionList.size(); i++)
(*m_connectionList[i])(a_param);
m_insideInvokation = false;
}
}
Signal& operator+=(SharedPtr<Slot>& a_delegate)
{
addConnection(a_delegate);
return *this;
}
Signal& operator-=(SharedPtr<Slot>& a_delegate)
{
disconnectOne(a_delegate);
return *this;
}
void invoke(Variant& a_param)
{
operator()(a_param);
}
private:
Signal(Signal const& a_other);
Signal& operator=(Signal const & a_other);
bool m_insideInvokation; // TODO: could this be perhaps static or thread_local? Can a signal cause another signal? Or only stop a signal causing itself to be raised?
Vector<SharedPtr<Slot> > m_connectionList; // TODO: could optimize the size overhead of a signal if it is not connected to?
};
template <typename ActionT>
class InvokableActionInterface : public InterfaceBase
{
public:
virtual void operator()(const ActionT& a_action) = 0;
};
template <>
class InvokableActionInterface<void> : public InterfaceBase
{
public:
virtual void operator()(void) = 0;
};
template <typename Method, typename ActionT>
class StaticFunctionActionCallback : public InvokableActionInterface<ActionT>
{
public:
StaticFunctionActionCallback(Method a_method)
: m_method(a_method)
{
}
virtual void operator()(const ActionT& a_action)
{
(*m_method)(a_action);
}
private:
Method m_method;
};
template <class Class, typename Method, typename ActionT>
class MethodActionCallback : public InvokableActionInterface<ActionT>
{
public:
MethodActionCallback(Class* a_obj, Method a_method)
: m_object(a_obj)
, m_method(a_method)
{
}
MethodActionCallback(MethodActionCallback const& a_other)
{
m_object = a_other.m_object;
//m_object.addRef();
m_method = a_other.m_method;
}
MethodActionCallback& operator=(MethodActionCallback const& a_other)
{
m_object = a_other.m_object;
//m_object.addRef();
m_method = a_other.m_method;
return *this;
}
virtual void operator()(const ActionT& a_action)
{
(m_object->*m_method)(a_action);
}
private:
Class* m_object;
Method m_method;
};
template <class Class, typename Method>
class MethodActionCallback<Class, Method, void> : public InvokableActionInterface<void>
{
public:
MethodActionCallback(Class* a_obj, Method a_method)
: m_object(a_obj)
, m_method(a_method)
{
}
MethodActionCallback(MethodActionCallback const& a_other)
{
m_object = a_other.m_object;
//m_object.addRef();
m_method = a_other.m_method;
}
MethodActionCallback& operator=(MethodActionCallback const& a_other)
{
m_object = a_other.m_object;
//m_object.addRef();
m_method = a_other.m_method;
return *this;
}
virtual void operator()(void)
{
(m_object->*m_method)();
}
private:
Class* m_object;
Method m_method;
};
template <typename ActionT>
class Delegate
{
public:
Delegate() : m_callback(0)
{
}
~Delegate()
{
}
template <typename Method>
Delegate(Method a_method)
: m_callback(new(placementNewBuffer) StaticFunctionActionCallback<Method,ActionT>(a_method))
{
STATIC_ASSERT(sizeof(StaticFunctionActionCallback<Method,ActionT>) <= sizeof(placementNewBuffer), increaseBufferBize);
}
template <class Class, typename Method>
Delegate(Class* object, Method a_method)
: m_callback(new(placementNewBuffer) MethodActionCallback<Class,Method,ActionT>(object, a_method))
{
STATIC_ASSERT(sizeof(MethodActionCallback<Class,Method,ActionT>) <= sizeof(placementNewBuffer), increaseBufferBize);
}
// Need some kind of way to notify when object gets destroyed so that it can remove this slot
void operator()(const ActionT& a_action)
{
(*m_callback)(a_action);
}
// Perhaps should try to handle ... type params
template <typename T>
void operator()(const T& a_params)
{
(*this)( (ActionT)(a_params) );
}
private:
#ifdef _WIN32
char placementNewBuffer[24]; // by trial and error, 24 was min to compile x64 using MSVC
#else
char placementNewBuffer[32]; // by trial and error, 1024 was min to compile on MacOSX
#endif
InvokableActionInterface<ActionT> *m_callback;
};
template <>
class Delegate<void>
{
public:
Delegate() : m_callback(0)
{
}
~Delegate()
{
}
template <typename Method>
Delegate(Method a_method)
: m_callback(new(placementNewBuffer) StaticFunctionActionCallback<Method,void>(a_method))
{
STATIC_ASSERT(sizeof(StaticFunctionActionCallback<Method,void>) <= sizeof(placementNewBuffer), increaseBufferBize);
}
template <class Class, typename Method>
Delegate(Class* object, Method a_method)
: m_callback(new(placementNewBuffer) MethodActionCallback<Class,Method,void>(object, a_method))
{
STATIC_ASSERT(sizeof(MethodActionCallback<Class,Method,void>) <= sizeof(placementNewBuffer), increaseBufferBize);
}
// Need some kind of way to notify when object gets destroyed so that it can remove this slot
void operator()()
{
(*m_callback)();
}
private:
#ifdef _WIN32
char placementNewBuffer[24]; // by trial and error, 24 was min to compile x64 using MSVC
#else
char placementNewBuffer[32]; // by trial and error, 1024 was min to compile on MacOSX
#endif
InvokableActionInterface<void> *m_callback;
};
template <typename ActionT>
class Action
{
public:
Action() : m_insideInvokation(false) {}
//~Action() { disconnectAll(); }
void disconnectAll()
{
m_paramlessConnectionList.clear();
m_connectionList.clear();
}
void operator()(const ActionT& a_param)
{
if (!m_insideInvokation) // prevent recursion
{
m_insideInvokation = true;
for (uint32_t i = 0; i < m_paramlessConnectionList.size(); i++)
(*m_paramlessConnectionList[i])();
for (uint32_t i = 0; i < m_connectionList.size(); i++)
(*m_connectionList[i])(a_param);
m_insideInvokation = false;
}
}
void invoke(const ActionT& a_action)
{
operator()(a_action);
}
void operator+=(SharedPtr<Delegate<void> >& a_delegate)
{
m_paramlessConnectionList.push_back(a_delegate);
//return *this;
}
void operator+=(SharedPtr<Delegate<ActionT> >& a_delegate)
{
m_connectionList.push_back(a_delegate);
//return *this;
}
void operator-=(SharedPtr<Delegate<ActionT> >& a_delegate)
{
// TODO
//return *this;
}
void operator-=(SharedPtr<Delegate<void> >& a_delegate)
{
// TODO
//return *this;
}
private:
Action(Action<ActionT> const& a_other);
Action& operator=(Action<ActionT> const & a_other);
bool m_insideInvokation; // TODO: could this be perhaps static or thread_local? Can a signal cause another signal? Or only stop a signal causing itself to be raised?
Vector<SharedPtr<Delegate<ActionT> > > m_connectionList; // TODO: could optimize the size overhead of a signal if it is not connected to?
Vector<SharedPtr<Delegate<void> > > m_paramlessConnectionList; // Delegates which don't require an argument
};
//
// Terminology
//
// Event / Signal / Action -> synonyms - a signal or an action that causes the event to be dispatched / invoked
//
// There is an event sender and an event receiver
//
// Receiver / Slot / Observer / Listener / Delegate -> synonyms for the consumer of an event/signal/action etc.
//
// Main idea is to decouple the sender from the receiver of the event/action etc
//
// In C, a typical pattern used for decoupling is the use of callbacks, eg passing a function pointer to a function.
// This is a form of in-direction, an indirect call. The pointer to the function must be de-referenced before being called.
// C style callbacks can lack type safety as to provide decoupling you may need to do unsafe type-casting.
// C++ tries to avoid the need for callbacks by the introduction of virtual function calls which provides extensibility
// and another way to perform an indirect function call. However it doesn't fully answer the decoupling requirements of
// most complex projects, and a pattern in C++ which is followed to provide this decoupling the GOF refer to it as the
// observer pattern.
//
// In Qt, they refer to it as signals and slots. Registering a slot to be called when a signal is raised is a connection.
// Qt uses MOC, the meta-object compiler to achieve a clean and simple syntax for defining these signals and slots.
// Newer versions of Qt avoid the need for moc. In addition to signals and slots, Qt also has an event system which is
// based on virtual functions and uses sub-classing of the QEvent base class to provide specific event types. The event
// system is lighter in weight and used more for system events and signals and slots are more flexible but a little more costly.
//
// In games programming, they may have actions, and sometimes actions and also events. In the case of both actions and events
// typically the events might be parameterless and the actions have parameters. Actions might be serializable to send over a
// network whereas events might just be within the same process. It might also be that the actions are serialized and sent over
// the network, but are dispatched using the event system.
//
// Desired features:
// type safety -> best if at compile time, only compatible sender and receiver signatures can be coupled
// network -> de-coupling not just within process but across processes or over network -> requires arg serialization (action struct)
//
// .Net / C# terminology and syntax:
//
// events and delegates:
// public event EventHandler Event;
// public delegate void EventHandler(object sender, EventArgs e);
//
// EventArgs is sub-classed to provide event specific args and for type-safe matching:
// public class AlarmEventArgs : EventArgs
// {
// }
//
// Producing events:
// public class AlarmClock
// {
// public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);
// public event AlarmEventHandler Alarm;
//
// void func()
// {
// AlarmEventArgs args = new AlarmEventArgs(currentTime);
// Alarm(this, args);
// }
// }
//
// Consuming:
// public class WakeMeUp
// {
// public void AlarmRang(object sender, AlarmEventArgs e)
// {
// }
// }
//
// Connecting:
// WakeMeUp w = new WakeMeUp();
// AlarmClock clock = new AlarmClock();
// clock.Alarm += new AlarmEventHandler(w.AlarmRang);
//
// Other systems with similarities:
//
// RPC, CORBA - Allows objects to communicate over a network with an interface broker
// Requires an IDL - similar requirement to Qt's moc with another compilation step
// Not exactly the same, but if have support for actions over a network, then it
// can supply an RPC capability and would avoid the need for some of these other technologies.
//
// Try to take the best of all the systems:
//
// C# has a good idea using += syntax for connecting and event/delegate terminology
// Qt has an easy way to declare their signals and slots
// Games have a good idea with being able to send actions over a network
// If consider the arguments to the function to be a struct, an action as struct is the way to go
//
// struct AlarmAction : Action<AlarmAction>
// {
// ACTION_PARAM(int, arg1);
// ACTION_PARAM(int, arg2);
// }
//
// void AlarmDelegate(const AlarmAction& alarm)
// {
// }
//
// Event<AlarmAction> alarm;
//
// alarm += AlarmDelegate;
//
// alarm();
// or:
// alarm.invoke();
//
// How to tunnel it through a network:
//
// void NetworkDelegate(const Action& genericAction)
// {
// // serialize and send it
// }
// alarm += NetworkDelegate;
//
// Perhaps need to make the network case simpler
//
//
//typedef Signal Event;
//typedef Slot Delegate;
static inline void connect(Signal& a_signal, Slot& a_slot)
{
SharedPtr<Slot> slot(new Slot(a_slot));
a_signal.addConnection(slot);
}
template <class Class, typename Method>
static inline void connect(Signal& a_signal, Class *a_object, Method a_method, Variant a_v = Variant())
{
SharedPtr<Slot> slot(new Slot(a_object, a_method, a_v));
a_signal.addConnection(slot);
}
template <class Class, typename Method, typename ActionT>
static inline void connect(Action<ActionT>& a_signal, Class *a_object, Method a_method)
{
SharedPtr<Delegate<ActionT> > slot(new Delegate<ActionT>(a_object, a_method));
a_signal += slot;
}
template <class Class, typename ActionT>
static inline void connect(Action<ActionT>& a_signal, Class *a_object, void (Class::*a_method)(void))
{
SharedPtr<Delegate<void> > slot(new Delegate<void>(a_object, a_method));
a_signal += slot;
}
END_NAMESPACE