Newer
Older
Import / research / ui / toolkit / include / SignalSlot.h
#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