G'day Bob,

This awesome bit of code is from Stephen Thorne, possibly around 26th
June 2007, during an IRC collaboration while we were organising a
holiday technology camp.

I've gathered it into my mind again just now, so that I can better
explain it.

The main requirement was a list of packet types and sizes, for
processing the data stream from the server.  I had built something
that was a bit ugly, using my limited knowledge of Python.  Stephen
optimised it by using metaclasses.  I still have the IRC log from

sp_table is a dictionary of server packet types, indexed by the packet
type, each entry holding a packet size in bytes and a packet handler

sp_table is populated by the __new__ method of the ServerPacket class,
which is called for each of the SP_* classes as they are defined.  The
method calculates the size of the packet using the struct format
string.  This saves having to track the packet format or size in more
than one place.

A lowercase global name is also added.  This ensures an instance of
each object is preserved during execution, as well as making the
object available for direct access.  Direct access is common for cp_*
objects, but not for sp_* objects.  Examples of cp_* access are every
keyboard handler.  Examples of sp_* access are the references to
sp_badversion.why and sp_warning.synthetic().

When the Client object is created in nt_init(), a pointer to the only
instance of the SP class is given to it, and it is preserved in the
Client object and used whenever a packet is received.  In particular,
the find method of the SP class is called.

So when a packet is received (in tcp_readable);

- the first byte is examined,

- the sp_table is referenced, to obtain the packet size,

- enough bytes to fill the packet are dequeued from the network
  socket, "if we have not got the complete packet, read some more",

- the packet is passed to a packet type handler, (instance.handler()).

The only thing I don't quite understand is how sp_table is used
without triggering an error "UnboundLocalError: local variable
'sp_table' referenced before assignment", but it works.

sp_table looks like this (print repr(sp_table)):

{1: (84, <gytha.SP_MESSAGE object at 0xa394b0c>),
2: (4, <gytha.SP_PLAYER_INFO object at 0xa39436c>),
3: (8, <gytha.SP_KILLS object at 0xa3943ac>),
4: (12, <gytha.SP_PLAYER object at 0xa3944ac>),
5: (8, <gytha.SP_TORP_INFO object at 0xa3947ec>),
6: (12, <gytha.SP_TORP object at 0xa39484c>),
7: (16, <gytha.SP_PHASER object at 0xa394a0c>),
8: (8, <gytha.SP_PLASMA_INFO object at 0xa3948cc>),
9: (12, <gytha.SP_PLASMA object at 0xa39490c>),
10: (84, <gytha.SP_WARNING object at 0xa394c0c>),
11: (84, <gytha.SP_MOTD object at 0xa3940cc>),
12: (32, <gytha.SP_YOU object at 0xa39416c>),
13: (4, <gytha.SP_QUEUE object at 0xa3941ec>),
14: (28, <gytha.SP_STATUS object at 0xa39498c>),
15: (12, <gytha.SP_PLANET object at 0xa394a8c>),
16: (4, <gytha.SP_PICKOK object at 0xa39470c>),
17: (104, <gytha.SP_LOGIN object at 0xa39460c>),
18: (8, <gytha.SP_FLAGS object at 0xa39452c>),
19: (4, <gytha.SP_MASK object at 0xa39468c>),
20: (4, <gytha.SP_PSTATUS object at 0xa39442c>),
21: (4, <gytha.SP_BADVERSION object at 0xa394cec>),
22: (4, <gytha.SP_HOSTILE object at 0xa3942ec>),
23: (56, <gytha.SP_STATS object at 0xa394b8c>),
24: (52, <gytha.SP_PL_LOGIN object at 0xa39426c>),
25: (20, <gytha.SP_RESERVED object at 0xa39476c>),
26: (28, <gytha.SP_PLANET_LOC object at 0xa3945ac>),
28: (8, <gytha.SP_UDP_REPLY object at 0xa394dac>),
29: (4, <gytha.SP_SEQUENCE object at 0xa394e0c>),
32: (32, <gytha.SP_GENERIC_32 object at 0xa394f0c>),
39: (60, <gytha.SP_SHIP_CAP object at 0xa394e8c>),
46: (8, <gytha.SP_PING object at 0xa394d2c>),
60: (88, <gytha.SP_FEATURE object at 0xa394c6c>),
-1: (0, <gytha.SP object at 0xa39406c>)}

James Cameron