Project

General

Profile

SimpleChat made easy » History » Version 12

Alex V, 08/27/2014 12:09 PM
removed spam

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 5 Omer Katz
			static 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 1 Omer Katz
		{
124 5 Omer Katz
			boost::mutex::scoped_lock  lock(CometMutex);
125
126 2 Omer Katz
			if ( widgets.find(this) != widgets.end() ) // Erases the current session from the widget map
127
				widgets.erase(this);
128
		}
129
130 1 Omer Katz
		CometApplication::WidgetMapType CometApplication::widgets;
131 5 Omer Katz
		CometApplication::boost::mutex CometMutex;
132 2 Omer Katz
	}
133
}
134
</pre>
135
136
h3. Code Review
137
138
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.
139
# 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.
140
# 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.
141
142 1 Omer Katz
As you can see the CometApplication instance only refers to *this*, which means only to the current session.
143 3 Omer Katz
I have befriended all other classes because registerWidget, unregisterWidget and getWidget should only be accessed by those classes.
144
This doesn't indicate that my code has a design problem in my opinion.
145
Sometimes you actually have to use class friendship.
146
It makes sense that the end user will not control anything related to registering/unregistering widgets as you will see.
147
148
149
After that we create a factory which will instanciate a widget for each session.
150
Here the magic is introduced into the system.
151
152
h2. CometWidgetFactory.hpp
153
154
<pre>
155
#ifndef CometWidgetFactory_HPP
156
#define CometWidgetFactory_HPP
157
158
#include <Wt/WApplication>
159
#include <Wt/WWidget>
160
161 4 Omer Katz
#include <boost/thread/mutex.hpp>
162
163 3 Omer Katz
#include "CometApplication.hpp"
164
165
namespace UI
166
{
167
	namespace Widgets
168
	{
169
		// CometWidgetFactory creates a widget of type WidgetType, WidgetType is derived from CometWidget
170
		template <class WidgetType>
171
		class CometWidgetFactory
172
		{
173
		private:
174
			static unsigned long int idCounter; // Counts how many ids there are
175
176
			unsigned long int id; // Group id
177
		public:
178
			CometWidgetFactory()
179
			: id(idCounter)
180
			{
181
				idCounter++; // New factory instance has been created.
182
			}
183
184
			~CometWidgetFactory()
185
			{
186
				idCounter--; // New factory instance has been destroyed
187
			}
188
189
			void create()
190
			{
191
				WidgetType *w = new WidgetType; // Creates a new widget
192
				w->id = id; // Assigns the group id
193
194
				// Registers the widget to the current session
195
				dynamic_cast<UI::Application::CometApplication *>(wApp)->registerWidget(id, w); 
196
			}
197
198
			void destroy()
199
			{
200
				// Unregisters the widget from the current session
201
				dynamic_cast<UI::Application::CometApplication *>(wApp)->unregisterWidget(id);
202
			}
203
204
			// Those operator overloadings make the factory "point" to the current widget we're refering to in the current session
205
			WidgetType *operator->()
206
			{
207
				return dynamic_cast<UI::Application::CometApplication *>(wApp)->getWidget<WidgetType>(id);
208
			}
209
210
			operator Wt::WWidget *()
211
			{
212
				return dynamic_cast<UI::Application::CometApplication *>(wApp)->getWidget<WidgetType>(id);
213
			}
214
		};
215
216
		template <class T>
217
		unsigned long int CometWidgetFactory<T>::idCounter = 0;
218
	}
219
}
220
221
#endif
222
</pre>
223
224
h3. Code Review
225
226
This class looks very simple but it has a trick.
227
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.
228
For example:
229
<pre>
230
class someWidget : public CometWidget<Wt::LineEdit>
231
{
232
//...
233
};
234
235
class MyPage : Wt::WContainer
236
{
237
private:
238
  static CometWidgetFactory<someWidget> myWidget;
239
public:
240
  MyPage()
241
  {
242
    myWidget.create(); // Creates a widget on the same group id
243
  }
244
245
  ~MyPage()
246
  {
247
    myWidget.destroy();
248
  }
249
};
250
</pre>
251
252
For each session a someWidget will be created and you can access the widget on the current session.
253
254
h2. CometWidget.hpp
255
256
<pre>
257
#ifndef CometWidget_HPP
258
#define CometWidget_HPP
259
260
#include <string>
261
262
#include <boost/thread/mutex.hpp>
263
264
#include <Wt/WContainerWidget>
265
266
#include "CometApplication.hpp"
267
268
namespace UI
269
{
270
    namespace Widgets
271
    {
272
        template <class WidgetType>
273
        class CometWidget : public WidgetType
274
        {
275
        protected:
276
            virtual void updateOthers()
277
            {
278
		boost::mutex::scoped_lock  lock(CometMutex);
279
280
                for ( Application::CometApplication::WidgetMapIteratorType iter = Application::CometApplication::widgets.begin(); iter != Application::CometApplication::widgets.end(); ++iter )
281
                {
282
                    Application::CometApplication::WidgetRangeType range = Application::CometApplication::widgets.equal_range(iter->first);
283
284
                    for ( Application::CometApplication::WidgetMapIteratorType iter2 = range.first; iter2 != range.second; ++iter2 )
285
                    {
286
                        if ( iter2->second.first == id )
287
                        {
288
                            Wt::WApplication::UpdateLock uiLock = iter->first->getUpdateLock();
289
                            dynamic_cast<CometWidget *>(iter2->second.second)->updateWidget();
290
                            iter->first->triggerUpdate();
291
                        }
292
                    }
293
                }
294
            }
295
296
            virtual void updateWidget() = 0; // TBD: Convert this to a signal/slot instead of a virtual function
297
298
            template <class>
299
            friend class CometWidgetFactory;
300
        private:
301
            unsigned long int id; // Group ID
302
303
            boost::mutex CometMutex;
304
        };
305
    }
306
}
307
308
#endif
309
</pre>
310
311
h3. Code Review
312
313
updateOthers() updates each widget in the widget group and it should be called whenever a change was made in the widget.
314
updateWidget should be a slot that will be connected to signals that update the widget itself.
315
316
In the next part of the article I will demonstrate how to re-write the SimpleChat example very simply.