Project

General

Profile

SimpleChat made easy » History » Version 4

Omer Katz, 03/23/2010 10:12 PM
Added a mutex to CometApplication

1 2 Omer Katz
h1. SimpleChat made easy - Part 1
2 1 Omer Katz
3 2 Omer Katz
Currently the situation with Wt's Comet is that you have to track the sessions you want to push data to yourself.
4
I have written some classes that help dealing with this issue and simplifies building the simple chat example.
5 3 Omer Katz
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.
6 2 Omer Katz
7
h2. CometApplication.hpp
8
9
<pre>
10
#ifndef CometApplication_HPP
11
#define CometApplication_HPP
12
13
// <STL>
14
#include <list>
15
#include <string>
16
#include <map>
17
// </STL>
18
19
// <Boost>
20
#include <boost/unordered_map.hpp>
21
// </Boost>
22
23
// <Wt>
24
#include <Wt/WApplication>
25
#include <Wt/WWidget>
26
// </Wt>
27
28
namespace UI
29
{
30
	namespace Widgets
31
	{
32
                // Forward declerations
33
		class CometWidget;
34
35
		template <class>
36
		class CometWidgetFactory;
37
	}
38
39
	namespace Application
40
	{
41
		class CometApplication : public Wt::WApplication
42
		{
43
		public:
44
			CometApplication(const Wt::WEnvironment &env); // Ctor, passes enviorment variables to WApplication
45
			~CometApplication(); // Dtor
46
47
			friend class Widgets::CometWidget; // Befriending with CometWidget so it can access the widget map
48
49
			template <class>
50
			friend class Widgets::CometWidgetFactory; // Same here
51
		private:
52
			typedef std::pair<int, Wt::WWidget *> WidgetEntryType;
53
			typedef boost::unordered_multimap<CometApplication *, WidgetEntryType > WidgetMapType;
54
			typedef boost::unordered_multimap<CometApplication *, WidgetEntryType >::iterator WidgetMapIteratorType;
55
			typedef std::pair<WidgetMapIteratorType, WidgetMapIteratorType> WidgetRangeType;
56
57
			static WidgetMapType widgets;
58
59
			// Pushes a new widget to the current session with group id and the widget itself
60
			inline void registerWidget(unsigned long int id, Wt::WWidget *w)
61
			{
62 4 Omer Katz
				boost::mutex::scoped_lock  lock(CometMutex);
63
64 2 Omer Katz
				widgets.insert(WidgetMapType::value_type(this, WidgetEntryType(id, w)));
65
			}
66
67
			// Searches for the widget of the group id for the current session and erases it
68
			inline void unregisterWidget(unsigned long int id)
69
			{
70 4 Omer Katz
				boost::mutex::scoped_lock  lock(CometMutex);
71
72 2 Omer Katz
				WidgetRangeType range = widgets.equal_range(this);
73
74
				for ( WidgetMapIteratorType iter = range.first; iter != range.second; ++iter )
75
				{
76
					if ( iter->second.first == id )
77
					{
78
						widgets.erase(iter);
79
						break;
80
					}
81
				}
82
			}
83
84
			// Searches for a widget in the current session, casts it to the right widget type and returns it
85
			template <class T>
86
			inline T *getWidget(unsigned long int id)
87
			{
88 4 Omer Katz
				boost::mutex::scoped_lock  lock(CometMutex);
89
90 2 Omer Katz
				WidgetRangeType range = widgets.equal_range(this);
91
92
				for ( WidgetMapIteratorType iter = range.first; iter != range.second; ++iter )
93
					if ( iter->second.first == id )
94
						return dynamic_cast<T *>(iter->second.second);
95
96
				return NULL;
97
			}
98 4 Omer Katz
99
			boost::mutex CometMutex;
100 2 Omer Katz
		};
101
	}
102
}
103
104
#endif
105
</pre>
106
107
h2. CometApplication.cpp
108
109
<pre>
110
#include "CometApplication.hpp"
111
112
namespace UI
113
{
114
	namespace Application
115
	{
116
		CometApplication::CometApplication(const Wt::WEnvironment &env)
117
		: WApplication(env)
118
		{
119
			enableUpdates(); // Enables ajax-push for the current session
120
		}
121
122
		CometApplication::~CometApplication()
123
		{
124
			if ( widgets.find(this) != widgets.end() ) // Erases the current session from the widget map
125
				widgets.erase(this);
126
		}
127
128
		CometApplication::WidgetMapType CometApplication::widgets;
129
	}
130
}
131
</pre>
132
133
h3. Code Review
134
135
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.
136
# 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.
137
# 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.
138
139 1 Omer Katz
As you can see the CometApplication instance only refers to *this*, which means only to the current session.
140 3 Omer Katz
I have befriended all other classes because registerWidget, unregisterWidget and getWidget should only be accessed by those classes.
141
This doesn't indicate that my code has a design problem in my opinion.
142
Sometimes you actually have to use class friendship.
143
It makes sense that the end user will not control anything related to registering/unregistering widgets as you will see.
144
145
146
After that we create a factory which will instanciate a widget for each session.
147
Here the magic is introduced into the system.
148
149
h2. CometWidgetFactory.hpp
150
151
<pre>
152
#ifndef CometWidgetFactory_HPP
153
#define CometWidgetFactory_HPP
154
155
#include <Wt/WApplication>
156
#include <Wt/WWidget>
157
158 4 Omer Katz
#include <boost/thread/mutex.hpp>
159
160 3 Omer Katz
#include "CometApplication.hpp"
161
162
namespace UI
163
{
164
	namespace Widgets
165
	{
166
		// CometWidgetFactory creates a widget of type WidgetType, WidgetType is derived from CometWidget
167
		template <class WidgetType>
168
		class CometWidgetFactory
169
		{
170
		private:
171
			static unsigned long int idCounter; // Counts how many ids there are
172
173
			unsigned long int id; // Group id
174
		public:
175
			CometWidgetFactory()
176
			: id(idCounter)
177
			{
178
				idCounter++; // New factory instance has been created.
179
			}
180
181
			~CometWidgetFactory()
182
			{
183
				idCounter--; // New factory instance has been destroyed
184
			}
185
186
			void create()
187
			{
188
				WidgetType *w = new WidgetType; // Creates a new widget
189
				w->id = id; // Assigns the group id
190
191
				// Registers the widget to the current session
192
				dynamic_cast<UI::Application::CometApplication *>(wApp)->registerWidget(id, w); 
193
			}
194
195
			void destroy()
196
			{
197
				// Unregisters the widget from the current session
198
				dynamic_cast<UI::Application::CometApplication *>(wApp)->unregisterWidget(id);
199
			}
200
201
			// Those operator overloadings make the factory "point" to the current widget we're refering to in the current session
202
			WidgetType *operator->()
203
			{
204
				return dynamic_cast<UI::Application::CometApplication *>(wApp)->getWidget<WidgetType>(id);
205
			}
206
207
			operator Wt::WWidget *()
208
			{
209
				return dynamic_cast<UI::Application::CometApplication *>(wApp)->getWidget<WidgetType>(id);
210
			}
211
		};
212
213
		template <class T>
214
		unsigned long int CometWidgetFactory<T>::idCounter = 0;
215
	}
216
}
217
218
#endif
219
</pre>
220
221
h3. Code Review
222
223
This class looks very simple but it has a trick.
224
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.
225
For example:
226
<pre>
227
class someWidget : public CometWidget<Wt::LineEdit>
228
{
229
//...
230
};
231
232
class MyPage : Wt::WContainer
233
{
234
private:
235
  static CometWidgetFactory<someWidget> myWidget;
236
public:
237
  MyPage()
238
  {
239
    myWidget.create(); // Creates a widget on the same group id
240
  }
241
242
  ~MyPage()
243
  {
244
    myWidget.destroy();
245
  }
246
};
247
</pre>
248
249
For each session a someWidget will be created and you can access the widget on the current session.
250
251
h2. CometWidget.hpp
252
253
<pre>
254
#ifndef CometWidget_HPP
255
#define CometWidget_HPP
256
257
#include <string>
258
259
#include <boost/thread/mutex.hpp>
260
261
#include <Wt/WContainerWidget>
262
263
#include "CometApplication.hpp"
264
265
namespace UI
266
{
267
    namespace Widgets
268
    {
269
        template <class WidgetType>
270
        class CometWidget : public WidgetType
271
        {
272
        protected:
273
            virtual void updateOthers()
274
            {
275
		boost::mutex::scoped_lock  lock(CometMutex);
276
277
                for ( Application::CometApplication::WidgetMapIteratorType iter = Application::CometApplication::widgets.begin(); iter != Application::CometApplication::widgets.end(); ++iter )
278
                {
279
                    Application::CometApplication::WidgetRangeType range = Application::CometApplication::widgets.equal_range(iter->first);
280
281
                    for ( Application::CometApplication::WidgetMapIteratorType iter2 = range.first; iter2 != range.second; ++iter2 )
282
                    {
283
                        if ( iter2->second.first == id )
284
                        {
285
                            Wt::WApplication::UpdateLock uiLock = iter->first->getUpdateLock();
286
                            dynamic_cast<CometWidget *>(iter2->second.second)->updateWidget();
287
                            iter->first->triggerUpdate();
288
                        }
289
                    }
290
                }
291
            }
292
293
            virtual void updateWidget() = 0; // TBD: Convert this to a signal/slot instead of a virtual function
294
295
            template <class>
296
            friend class CometWidgetFactory;
297
        private:
298
            unsigned long int id; // Group ID
299
300
            boost::mutex CometMutex;
301
        };
302
    }
303
}
304
305
#endif
306
</pre>
307
308
h3. Code Review
309
310
updateOthers() updates each widget in the widget group and it should be called whenever a change was made in the widget.
311
updateWidget should be a slot that will be connected to signals that update the widget itself.
312
313
In the next part of the article I will demonstrate how to re-write the SimpleChat example very simply.