<p dir="ltr">On behalf of Stephen Thorne of 2007, Stephen Thorne of 2012 would like to apologise for using metaclasses. </p>
<div class="gmail_quote">On Sep 26, 2012 9:47 PM, "James Cameron" <<a href="mailto:quozl@us.netrek.org">quozl@us.netrek.org</a>> wrote:<br type="attribution"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
G'day Bob,<br>
<br>
This awesome bit of code is from Stephen Thorne, possibly around 26th<br>
June 2007, during an IRC collaboration while we were organising a<br>
holiday technology camp.<br>
<br>
I've gathered it into my mind again just now, so that I can better<br>
explain it.<br>
<br>
The main requirement was a list of packet types and sizes, for<br>
processing the data stream from the server.  I had built something<br>
that was a bit ugly, using my limited knowledge of Python.  Stephen<br>
optimised it by using metaclasses.  I still have the IRC log from<br>
#netrek.<br>
<br>
sp_table is a dictionary of server packet types, indexed by the packet<br>
type, each entry holding a packet size in bytes and a packet handler<br>
object.<br>
<br>
sp_table is populated by the __new__ method of the ServerPacket class,<br>
which is called for each of the SP_* classes as they are defined.  The<br>
method calculates the size of the packet using the struct format<br>
string.  This saves having to track the packet format or size in more<br>
than one place.<br>
<br>
A lowercase global name is also added.  This ensures an instance of<br>
each object is preserved during execution, as well as making the<br>
object available for direct access.  Direct access is common for cp_*<br>
objects, but not for sp_* objects.  Examples of cp_* access are every<br>
keyboard handler.  Examples of sp_* access are the references to<br>
sp_badversion.why and sp_warning.synthetic().<br>
<br>
When the Client object is created in nt_init(), a pointer to the only<br>
instance of the SP class is given to it, and it is preserved in the<br>
Client object and used whenever a packet is received.  In particular,<br>
the find method of the SP class is called.<br>
<br>
So when a packet is received (in tcp_readable);<br>
<br>
- the first byte is examined,<br>
<br>
- the sp_table is referenced, to obtain the packet size,<br>
  (self.sp.find()),<br>
<br>
- enough bytes to fill the packet are dequeued from the network<br>
  socket, "if we have not got the complete packet, read some more",<br>
<br>
- the packet is passed to a packet type handler, (instance.handler()).<br>
<br>
The only thing I don't quite understand is how sp_table is used<br>
without triggering an error "UnboundLocalError: local variable<br>
'sp_table' referenced before assignment", but it works.<br>
<br>
sp_table looks like this (print repr(sp_table)):<br>
<br>
{1: (84, <gytha.SP_MESSAGE object at 0xa394b0c>),<br>
2: (4, <gytha.SP_PLAYER_INFO object at 0xa39436c>),<br>
3: (8, <gytha.SP_KILLS object at 0xa3943ac>),<br>
4: (12, <gytha.SP_PLAYER object at 0xa3944ac>),<br>
5: (8, <gytha.SP_TORP_INFO object at 0xa3947ec>),<br>
6: (12, <gytha.SP_TORP object at 0xa39484c>),<br>
7: (16, <gytha.SP_PHASER object at 0xa394a0c>),<br>
8: (8, <gytha.SP_PLASMA_INFO object at 0xa3948cc>),<br>
9: (12, <gytha.SP_PLASMA object at 0xa39490c>),<br>
10: (84, <gytha.SP_WARNING object at 0xa394c0c>),<br>
11: (84, <gytha.SP_MOTD object at 0xa3940cc>),<br>
12: (32, <gytha.SP_YOU object at 0xa39416c>),<br>
13: (4, <gytha.SP_QUEUE object at 0xa3941ec>),<br>
14: (28, <gytha.SP_STATUS object at 0xa39498c>),<br>
15: (12, <gytha.SP_PLANET object at 0xa394a8c>),<br>
16: (4, <gytha.SP_PICKOK object at 0xa39470c>),<br>
17: (104, <gytha.SP_LOGIN object at 0xa39460c>),<br>
18: (8, <gytha.SP_FLAGS object at 0xa39452c>),<br>
19: (4, <gytha.SP_MASK object at 0xa39468c>),<br>
20: (4, <gytha.SP_PSTATUS object at 0xa39442c>),<br>
21: (4, <gytha.SP_BADVERSION object at 0xa394cec>),<br>
22: (4, <gytha.SP_HOSTILE object at 0xa3942ec>),<br>
23: (56, <gytha.SP_STATS object at 0xa394b8c>),<br>
24: (52, <gytha.SP_PL_LOGIN object at 0xa39426c>),<br>
25: (20, <gytha.SP_RESERVED object at 0xa39476c>),<br>
26: (28, <gytha.SP_PLANET_LOC object at 0xa3945ac>),<br>
28: (8, <gytha.SP_UDP_REPLY object at 0xa394dac>),<br>
29: (4, <gytha.SP_SEQUENCE object at 0xa394e0c>),<br>
32: (32, <gytha.SP_GENERIC_32 object at 0xa394f0c>),<br>
39: (60, <gytha.SP_SHIP_CAP object at 0xa394e8c>),<br>
46: (8, <gytha.SP_PING object at 0xa394d2c>),<br>
60: (88, <gytha.SP_FEATURE object at 0xa394c6c>),<br>
-1: (0, <gytha.SP object at 0xa39406c>)}<br>
<br>
--<br>
James Cameron<br>
<a href="http://quozl.linux.org.au/" target="_blank">http://quozl.linux.org.au/</a><br>
</blockquote></div>