Packet Auto Build/Decomposition Design

Started by
12 comments, last by lordcorm 15 years, 5 months ago
Quote:Original post by hplus0603
A thread pool plus asynchronous I/O is usually well performing on Windows.

Could you elaborate on this? There are quite a few people that talk about using thread pools but there's rarely much detail. Is it primarily for pushing application-specific message decoding logic to a background thread so that the main I/O thread can continue?

Advertisement
The way that I/O completion ports work on Windows, a number of threads (a pool) can sit waiting for I/O completion to come in. When the completion comes in, the next ready thread picks it up and runs with it. The theory is that the blocking I/O will be handled by the overlapped (async) I/O request, and the computation-bound processing of I/O is handled by the thread pool. For high-load situations, you will then spawn one thread pool thread per physical core, plus one or a few threds for UI and management that doesn't normally need intensive processing.

In general, thread pools are a useful way of spreading load that you can compartmentalize across available physical computation cores. If you have a "here's some work to do" abstraction, then you build a queue of those work items, and have a set of threads pull work from that queue and perform it. That way, you don't need to have one thread per subsystem, but can scale up to the number of available cores (as long as there is work to do). The draw-back is that some systems don't really decompose well into right-sized computational chunks.
enum Bool { True, False, FileNotFound };
I've used various systems in the past, auto-generated packets, hand coded seralziation fuctions, etc.. Eventually I settled upon a macro based scheme where you define the serazlation fucitons inline with the class/struct and it auto generates the read/write functional hooks. I also used marcos to support versioning since I've used this scheme for both networking and general save/load scheme, but not in this version. In the same vien, for packet registeration it's also auto-regsiterting but you do have to define some additional marcos in the implementation.

Looks something like this.

	class netFileDownloadPacket : public netPacketBase	{		NET_PACKET_SIMPLE(netFileDownloadPacket,eNetFileDownloadPacket);	public:		netFileDownloadPacket(){}		BIND_(1, SvcNet::NetBuffer,		mData);			BIND_(2, StringType,			mPath);			BIND_END_VIRTUAL(2,netFileDownloadPacket);	};	BIND_OBJECT(netFileDownloadPacket);	NET_PACKET_REGISTER(netFileDownloadPacket);


Some people don't like the marcos being in the class, header but it's very convient to keep the implementatino and serazlation in sync as they are one in the same.

Here's a snippet of the marcos

//serialization macros create interface functions //n = number of the param, will generate a read/write function for the given number//t = type of the member variable //m = member variable name itself#define BIND_(n,t,m) 	t	m;	void _W##n(SvcNet::NetBuffer& in) const {in.Write(m);}	void _R##n(SvcNet::NetBuffer& out){out.Read(m);}	//similar to BIND_, but defines a fixed sized array instead, param (s) is the size of the array#define BIND_A(n,t,m,s) 	t	m;	<span class="cpp-keyword">void</span> _W##n(SvcNet::NetBuffer&amp; in) <span class="cpp-keyword">const</span> {in.WriteArray(m,s);}	<span class="cpp-keyword">void</span> _R##n(SvcNet::NetBuffer&amp; out){out.ReadArray(m,s);}<br><br><span class="cpp-comment">//similar to BIND_, but defines custom read/write functions (r,w)</span><br><span class="cpp-directive">#define</span> BIND_C(n,t,m,r,w) 	t	m;	<span class="cpp-keyword">void</span> _W##n(SvcNet::NetBuffer&amp; in) <span class="cpp-keyword">const</span> {r(in,m);}	<span class="cpp-keyword">void</span> _R##n(SvcNet::NetBuffer&amp; out){w(out,m);}	<br><br><br><br></pre></div><!–ENDSCRIPT–><br><br>this scheme does depend upon global templated functions hooks for read/write, that's what the BIND_OBJECT does as such<br><br><!–STARTSCRIPT–><!–source lang="cpp"–><div class="source"><pre><br><br><span class="cpp-comment">//defines global binding function, the object must implement the binding interface of Write/Read</span><br><span class="cpp-directive">#define</span> BIND_OBJECT(T) 	<span class="cpp-keyword">inline</span> <span class="cpp-keyword">void</span> Write	(<span class="cpp-keyword">const</span> T&amp;t, SvcNet::NetBuffer&amp; buff){t.Write(buff);}	<span class="cpp-keyword">inline</span> <span class="cpp-keyword">void</span> Read	(T&amp;t, SvcNet::NetBuffer&amp; buff){t.Read(buff);}<br><br><span class="cpp-comment">//similar as BIND_END but makes the function virtual, allow for inheritance on the read/write</span><br><span class="cpp-directive">#define</span> BIND_END_VIRTUAL(n,p)	<span class="cpp-keyword">virtual</span> <span class="cpp-keyword">void</span> Write	(SvcNet::NetBuffer&amp; buff)<span class="cpp-keyword">const</span>{BIND_WRITE(p,n);}	<span class="cpp-keyword">virtual</span> <span class="cpp-keyword">void</span> Read	(SvcNet::NetBuffer&amp; buff){BIND_READ(p,n);}<br><br><br></pre></div><!–ENDSCRIPT–><br><br>Ultimately the hooks are called inside the NetBuffers read/write functions to complete seralzaition/deseralzitions. The last bit of the puzzle is the autogernation of the seralzaition functions with the proper number of calls to the auto generated read/write funcitons. You'll have to use a marco unrolling technique, it's a common template meta-programming techinque, used in libraries such as Boost, Luabind, etc..<br><br>It looks like this:<br><br><!–STARTSCRIPT–><!–source lang="cpp"–><div class="source"><pre><br><br><span class="cpp-directive">#define</span> BIND_WRITE1(type,n)		type::_W1(buff);<br><span class="cpp-directive">#define</span> BIND_WRITE2(type,n)		BIND_WRITE1(type,n-<span class="cpp-number">1</span>)	type::_W2(buff);	<br><span class="cpp-directive">#define</span> BIND_WRITE3(type,n)		BIND_WRITE2(type,n-<span class="cpp-number">1</span>)	type::_W3(buff);	<br><span class="cpp-directive">#define</span> BIND_WRITE4(type,n)		BIND_WRITE3(type,n-<span class="cpp-number">1</span>)	type::_W4(buff);	<br><span class="cpp-directive">#define</span> BIND_WRITE5(type,n)		BIND_WRITE4(type,n-<span class="cpp-number">1</span>)	type::_W5(buff);	<br><span class="cpp-directive">#define</span> BIND_WRITE(type,n)		BIND_WRITE##n(type,n-<span class="cpp-number">1</span>)<br><br><br><br></pre></div><!–ENDSCRIPT–><br><br>And there u go! <br><br>Enjoy!<br><br>-ddn
Very nice ddn3! Very organized, very efficient.

Props to you.

This topic is closed to new replies.

Advertisement