Project

General

Profile

SimpleChat made easy » History » Revision 5

Revision 4 (Omer Katz, 03/23/2010 10:12 PM) → Revision 5/12 (Omer Katz, 03/23/2010 10:14 PM)

h1. SimpleChat made easy - Part 1 

 Currently the situation with Wt's Comet is that you have to track the sessions you want to push data to yourself. 
 I have written some classes that help dealing with this issue and simplifies building the simple chat example. 
 We start with coding a new application type that is derived from Wt application, a Wt application that uses my comet example should use this application class instead of the normal WApplication class. 

 h2. CometApplication.hpp 

 <pre> 
 #ifndef CometApplication_HPP 
 #define CometApplication_HPP 

 // <STL> 
 #include <list> 
 #include <string> 
 #include <map> 
 // </STL> 

 // <Boost> 
 #include <boost/unordered_map.hpp> 
 // </Boost> 

 // <Wt> 
 #include <Wt/WApplication> 
 #include <Wt/WWidget> 
 // </Wt> 

 namespace UI 
 { 
	 namespace Widgets 
	 { 
                 // Forward declerations 
		 class CometWidget; 

		 template <class> 
		 class CometWidgetFactory; 
	 } 

	 namespace Application 
	 { 
		 class CometApplication : public Wt::WApplication 
		 { 
		 public: 
			 CometApplication(const Wt::WEnvironment &env); // Ctor, passes enviorment variables to WApplication 
			 ~CometApplication(); // Dtor 

			 friend class Widgets::CometWidget; // Befriending with CometWidget so it can access the widget map 

			 template <class> 
			 friend class Widgets::CometWidgetFactory; // Same here 
		 private: 
			 typedef std::pair<int, Wt::WWidget *> WidgetEntryType; 
			 typedef boost::unordered_multimap<CometApplication *, WidgetEntryType > WidgetMapType; 
			 typedef boost::unordered_multimap<CometApplication *, WidgetEntryType >::iterator WidgetMapIteratorType; 
			 typedef std::pair<WidgetMapIteratorType, WidgetMapIteratorType> WidgetRangeType; 

			 static WidgetMapType widgets; 

			 // Pushes a new widget to the current session with group id and the widget itself 
			 inline void registerWidget(unsigned long int id, Wt::WWidget *w) 
			 { 
				 boost::mutex::scoped_lock    lock(CometMutex); 

				 widgets.insert(WidgetMapType::value_type(this, WidgetEntryType(id, w))); 
			 } 

			 // Searches for the widget of the group id for the current session and erases it 
			 inline void unregisterWidget(unsigned long int id) 
			 { 
				 boost::mutex::scoped_lock    lock(CometMutex); 

				 WidgetRangeType range = widgets.equal_range(this); 

				 for ( WidgetMapIteratorType iter = range.first; iter != range.second; ++iter ) 
				 { 
					 if ( iter->second.first == id ) 
					 { 
						 widgets.erase(iter); 
						 break; 
					 } 
				 } 
			 } 

			 // Searches for a widget in the current session, casts it to the right widget type and returns it 
			 template <class T> 
			 inline T *getWidget(unsigned long int id) 
			 { 
				 boost::mutex::scoped_lock    lock(CometMutex); 

				 WidgetRangeType range = widgets.equal_range(this); 

				 for ( WidgetMapIteratorType iter = range.first; iter != range.second; ++iter ) 
					 if ( iter->second.first == id ) 
						 return dynamic_cast<T *>(iter->second.second); 

				 return NULL; 
			 } 

			 static boost::mutex CometMutex; 
		 }; 
	 } 
 } 

 #endif 
 </pre> 

 h2. CometApplication.cpp 

 <pre> 
 #include "CometApplication.hpp" 

 namespace UI 
 { 
	 namespace Application 
	 { 
		 CometApplication::CometApplication(const Wt::WEnvironment &env) 
		 : WApplication(env) 
		 { 
			 enableUpdates(); // Enables ajax-push for the current session 
		 } 

		 CometApplication::~CometApplication() 
		 { 
			 boost::mutex::scoped_lock    lock(CometMutex); 

			 if ( widgets.find(this) != widgets.end() ) // Erases the current session from the widget map 
				 widgets.erase(this); 
		 } 

		 CometApplication::WidgetMapType CometApplication::widgets; 
		 CometApplication::boost::mutex CometMutex; 
	 } 
 } 
 </pre> 

 h3. Code Review 

 I have created an unordered multimap that might look a little strange to you guys but I'm going to elaborate about it in a minute. 
 # The multimap key is the pointer to the current session which is represented by CometApplication, because as you guys already know, a session is an application instance. That way I can access the current session's widgets using *this* or iterate through all sessions. 
 # The value of the multimap is a pair of a group id and a widget. Different widgets in different sessions with the same group id can be accessed this way. 

 As you can see the CometApplication instance only refers to *this*, which means only to the current session. 
 I have befriended all other classes because registerWidget, unregisterWidget and getWidget should only be accessed by those classes. 
 This doesn't indicate that my code has a design problem in my opinion. 
 Sometimes you actually have to use class friendship. 
 It makes sense that the end user will not control anything related to registering/unregistering widgets as you will see. 


 After that we create a factory which will instanciate a widget for each session. 
 Here the magic is introduced into the system. 

 h2. CometWidgetFactory.hpp 

 <pre> 
 #ifndef CometWidgetFactory_HPP 
 #define CometWidgetFactory_HPP 

 #include <Wt/WApplication> 
 #include <Wt/WWidget> 

 #include <boost/thread/mutex.hpp> 

 #include "CometApplication.hpp" 

 namespace UI 
 { 
	 namespace Widgets 
	 { 
		 // CometWidgetFactory creates a widget of type WidgetType, WidgetType is derived from CometWidget 
		 template <class WidgetType> 
		 class CometWidgetFactory 
		 { 
		 private: 
			 static unsigned long int idCounter; // Counts how many ids there are 

			 unsigned long int id; // Group id 
		 public: 
			 CometWidgetFactory() 
			 : id(idCounter) 
			 { 
				 idCounter++; // New factory instance has been created. 
			 } 

			 ~CometWidgetFactory() 
			 { 
				 idCounter--; // New factory instance has been destroyed 
			 } 

			 void create() 
			 { 
				 WidgetType *w = new WidgetType; // Creates a new widget 
				 w->id = id; // Assigns the group id 

				 // Registers the widget to the current session 
				 dynamic_cast<UI::Application::CometApplication *>(wApp)->registerWidget(id, w);  
			 } 

			 void destroy() 
			 { 
				 // Unregisters the widget from the current session 
				 dynamic_cast<UI::Application::CometApplication *>(wApp)->unregisterWidget(id); 
			 } 

			 // Those operator overloadings make the factory "point" to the current widget we're refering to in the current session 
			 WidgetType *operator->() 
			 { 
				 return dynamic_cast<UI::Application::CometApplication *>(wApp)->getWidget<WidgetType>(id); 
			 } 

			 operator Wt::WWidget *() 
			 { 
				 return dynamic_cast<UI::Application::CometApplication *>(wApp)->getWidget<WidgetType>(id); 
			 } 
		 }; 

		 template <class T> 
		 unsigned long int CometWidgetFactory<T>::idCounter = 0; 
	 } 
 } 

 #endif 
 </pre> 

 h3. Code Review 

 This class looks very simple but it has a trick. 
 If you declare it as static on your target widget it will create a group id for a certain type of widgets for all sessions. 
 For example: 
 <pre> 
 class someWidget : public CometWidget<Wt::LineEdit> 
 { 
 //... 
 }; 

 class MyPage : Wt::WContainer 
 { 
 private: 
   static CometWidgetFactory<someWidget> myWidget; 
 public: 
   MyPage() 
   { 
     myWidget.create(); // Creates a widget on the same group id 
   } 

   ~MyPage() 
   { 
     myWidget.destroy(); 
   } 
 }; 
 </pre> 

 For each session a someWidget will be created and you can access the widget on the current session. 

 h2. CometWidget.hpp 

 <pre> 
 #ifndef CometWidget_HPP 
 #define CometWidget_HPP 

 #include <string> 

 #include <boost/thread/mutex.hpp> 

 #include <Wt/WContainerWidget> 

 #include "CometApplication.hpp" 

 namespace UI 
 { 
     namespace Widgets 
     { 
         template <class WidgetType> 
         class CometWidget : public WidgetType 
         { 
         protected: 
             virtual void updateOthers() 
             { 
		 boost::mutex::scoped_lock    lock(CometMutex); 

                 for ( Application::CometApplication::WidgetMapIteratorType iter = Application::CometApplication::widgets.begin(); iter != Application::CometApplication::widgets.end(); ++iter ) 
                 { 
                     Application::CometApplication::WidgetRangeType range = Application::CometApplication::widgets.equal_range(iter->first); 

                     for ( Application::CometApplication::WidgetMapIteratorType iter2 = range.first; iter2 != range.second; ++iter2 ) 
                     { 
                         if ( iter2->second.first == id ) 
                         { 
                             Wt::WApplication::UpdateLock uiLock = iter->first->getUpdateLock(); 
                             dynamic_cast<CometWidget *>(iter2->second.second)->updateWidget(); 
                             iter->first->triggerUpdate(); 
                         } 
                     } 
                 } 
             } 

             virtual void updateWidget() = 0; // TBD: Convert this to a signal/slot instead of a virtual function 

             template <class> 
             friend class CometWidgetFactory; 
         private: 
             unsigned long int id; // Group ID 

             boost::mutex CometMutex; 
         }; 
     } 
 } 

 #endif 
 </pre> 

 h3. Code Review 

 updateOthers() updates each widget in the widget group and it should be called whenever a change was made in the widget. 
 updateWidget should be a slot that will be connected to signals that update the widget itself. 

 In the next part of the article I will demonstrate how to re-write the SimpleChat example very simply.