Commits

illume committed 52a01b1

Added in the PodSixNet package, so we don't need to install separately.

Moved the client.py and server.py into place.

  • Participants
  • Parent commits 9c1b1f7

Comments (0)

Files changed (11)

File alchemymadness/PodSixNet/Channel.py

+import sys, traceback
+
+from async import asynchat
+from rencode import loads, dumps
+
+class Channel(asynchat.async_chat):
+	endchars = '\0---\0'
+	def __init__(self, conn=None, addr=(), server=None, map=None):
+		asynchat.async_chat.__init__(self, conn, map)
+		self.addr = addr
+		self._server = server
+		self._ibuffer = ""
+		self.set_terminator(self.endchars)
+		self.sendqueue = []
+	
+	def collect_incoming_data(self, data):
+		self._ibuffer += data
+	
+	def found_terminator(self):
+		data = loads(self._ibuffer)
+		self._ibuffer = ""
+		
+		if type(dict()) == type(data) and data.has_key('action'):
+			[getattr(self, n)(data) for n in ('Network', 'Network_' + data['action']) if hasattr(self, n)]
+		else:
+			print "OOB data (no such Network_action):", data
+	
+	def Pump(self):
+		[asynchat.async_chat.push(self, d) for d in self.sendqueue]
+		self.sendqueue = []
+	
+	def Send(self, data):
+		"""Returns the number of bytes sent after enoding."""
+		outgoing = dumps(data) + self.endchars
+		self.sendqueue.append(outgoing)
+		return len(outgoing)
+	
+	def handle_connect(self):
+		if hasattr(self, "Connected"):
+			self.Connected()
+		else:
+			print "Unhandled Connected()"
+	
+	def handle_error(self):
+		try:
+			self.close()
+		except:
+			pass
+		if hasattr(self, "Error"):
+			self.Error(sys.exc_info()[1])
+		else:
+			asynchat.async_chat.handle_error(self)
+	
+	def handle_expt(self):
+		pass
+	
+	def handle_close(self):
+		if hasattr(self, "Close"):
+			self.Close()
+		asynchat.async_chat.handle_close(self)
+

File alchemymadness/PodSixNet/Connection.py

+"""
+A client's connection to the server.
+
+This module contains two components: a singleton called 'connection' and a class called 'ConnectionListener'.
+
+'connection' is a singleton instantiation of an EndPoint which will be connected to the server at the other end. It's a singleton because each client should only need one of these in most multiplayer scenarios. (If a client needs more than one connection to the server, a more complex architecture can be built out of instantiated EndPoint()s.) The connection is based on Python's asyncore and so it should have it's polling loop run periodically, probably once per gameloop. This just means putting "from Connection import connection; connection.Pump()" somewhere in your top level gameloop.
+
+Subclass ConnectionListener in order to have an object that will receive network events. For example, you might have a GUI element which is a label saying how many players there are online. You would declare it like 'class NumPlayersLabel(ConnectionListener, ...):' Later you'd instantitate it 'n = NumPlayersLabel()' and then somewhere in your loop you'd have 'n.Pump()' which asks the connection singleton if there are any new messages from the network, and calls the 'Network_' callbacks for each bit of new data from the server. So you'd implement a method like "def Network_players(self, data):" which would be called whenever a message from the server arrived which looked like {"action": "players", "number": 5}.
+"""
+
+from EndPoint import EndPoint
+
+connection = EndPoint()
+
+class ConnectionListener:
+	"""
+	Looks at incoming data and calls "Network_" methods in self, based on what messages come in.
+	Subclass this to have your own classes monitor incoming network messages.
+	For example, a method called "Network_players(self, data)" will be called when a message arrives like:
+		{"action": "players", "number": 5, ....}
+	"""
+	def Connect(self, *args, **kwargs):
+		connection.DoConnect(*args, **kwargs)
+		# check for connection errors:
+		self.Pump()
+	
+	def Pump(self):
+		for data in connection.GetQueue():
+			[getattr(self, n)(data) for n in ("Network_" + data['action'], "Network") if hasattr(self, n)]
+	
+	def Send(self, data):
+		""" Convenience method to allow this listener to appear to send network data, whilst actually using connection. """
+		connection.Send(data)
+
+if __name__ == "__main__":
+	from time import sleep
+	from sys import exit
+	class ConnectionTest(ConnectionListener):
+		def Network(self, data):
+			print "Network:", data
+		
+		def Network_error(self, error):
+			print "error:", error['error']
+			print "Did you start a server?"
+			exit(-1)
+		
+		def Network_connected(self, data):
+			print "connection test Connected"
+	
+	c = ConnectionTest()
+	
+	c.Connect()
+	while 1:
+		connection.Pump()
+		c.Pump()
+		sleep(0.001)
+

File alchemymadness/PodSixNet/EndPoint.py

+# coding=utf-8
+import socket
+import sys
+
+from async import poll
+from Channel import Channel
+
+class EndPoint(Channel):
+	"""
+	The endpoint queues up all network events for other classes to read.
+	"""
+	def __init__(self, address=("127.0.0.1", 31425), map=None):
+		self.address = address
+		self.isConnected = False
+		self.queue = []
+		if map is None:
+			self._map = {}
+		else:
+			self._map = map
+	
+	def DoConnect(self, address=None):
+		if address:
+			self.address = address
+		try:
+			Channel.__init__(self, map=self._map)
+			self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+			self.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+			self.connect(self.address)
+		except socket.gaierror, e:
+			self.queue.append({"action": "error", "error": e.args})
+		except socket.error, e:
+			self.queue.append({"action": "error", "error": e.args})
+	
+	def GetQueue(self):
+		return self.queue
+	
+	def Pump(self):
+		Channel.Pump(self)
+		self.queue = []
+		poll(map=self._map)
+	
+	# methods to add network data to the queue depending on network events
+	
+	def Close(self):
+		self.isConnected = False
+		self.close()
+		self.queue.append({"action": "disconnected"})
+	
+	def Connected(self):
+		self.queue.append({"action": "socketConnect"})
+	
+	def Network_connected(self, data):
+		self.isConnected = True
+	
+	def Network(self, data):
+		self.queue.append(data)
+	
+	def Error(self, error):
+		self.queue.append({"action": "error", "error": error})
+	
+	def ConnectionError(self):
+		self.isConnected = False
+		self.queue.append({"action": "error", "error": (-1, "Connection error")})
+
+if __name__ == "__main__":
+	import unittest
+	from time import sleep, time
+	
+	class FailEndPointTestCase(unittest.TestCase):
+		def setUp(self):
+			print
+			print "Trying failed endpoint"
+			print "----------------------"
+			class FailEndPoint(EndPoint):
+				def __init__(self):
+					EndPoint.__init__(self, ("localhost", 31429))
+					self.result = ""
+				
+				def Error(self, error):
+					print "Received error message:", error
+					self.result = error
+				
+				def Test(self):
+					self.DoConnect()
+					start = time()
+					while not self.result and time() - start < 10:
+						self.Pump()
+						sleep(0.001)
+			
+			self.endpoint_bad = FailEndPoint()
+		
+		def runTest(self):
+			self.endpoint_bad.Test()
+			want = (61, 'Connection refused')
+			self.assertEqual(list(self.endpoint_bad.result), list(want), "Socket got %s instead of %s" % (str(self.endpoint_bad.result), str(want)))
+			print
+		
+		def tearDown(self):
+			del self.endpoint_bad
+			print "FailEndPointTestCase complete"
+	
+	from Server import Server
+	class EndPointTestCase(unittest.TestCase):
+		def setUp(self):
+			self.outgoing = [
+				{"action": "hello", "data": {"a": 321, "b": [2, 3, 4], "c": ["afw", "wafF", "aa", "weEEW", "w234r"], "d": ["x"] * 256}},
+				{"action": "hello", "data": [454, 35, 43, 543, "aabv"]},
+				{"action": "hello", "data": [10] * 512},
+				{"action": "hello", "data": [10] * 512, "otherstuff": "hello\0---\0goodbye", "x": [0, "---", 0], "y": "zäö"},
+			]
+			self.count = len(self.outgoing)
+			self.lengths = [len(data['data']) for data in self.outgoing]
+			
+			print
+			print "Trying successful endpoint"
+			print "--------------------------"
+			class ServerChannel(Channel):
+				def Network_hello(self, data):
+					print "*Server* received:", data
+					self._server.received.append(data)
+					self._server.count += 1
+					self.Send({"action": "gotit", "data": "Yeah, we got it: " + str(len(data['data'])) + " elements"})
+			
+			class TestEndPoint(EndPoint):
+				received = []
+				connected = False
+				count = 0
+				
+				def Network_connected(self, data):
+					self.connected = True
+				
+				def Network_gotit(self, data):
+					self.received.append(data)
+					self.count += 1
+					print "gotit:", data
+			
+			class TestServer(Server):
+				connected = False
+				received = []
+				count = 0
+				
+				def Connected(self, channel, addr):
+					self.connected = True
+			
+			self.server = TestServer(channelClass=ServerChannel)
+			self.endpoint = TestEndPoint(("localhost", 31425))
+		
+		def runTest(self):
+			self.endpoint.DoConnect()
+			for o in self.outgoing:
+				self.endpoint.Send(o)
+			
+			print "polling for half a second"
+			for x in range(50):
+				self.server.Pump()
+				self.endpoint.Pump()
+				
+				# see if what we receive from the server is what we expect
+				for r in self.server.received:
+					self.failUnless(r == self.outgoing.pop(0))
+				self.server.received = []
+				
+				# see if what we receive from the client is what we expect
+				for r in self.endpoint.received:
+					self.failUnless(r['data'] == "Yeah, we got it: %d elements" % self.lengths.pop(0))
+				self.endpoint.received = []
+				
+				sleep(0.001)
+			
+			self.assertTrue(self.server.connected, "Server is not connected")
+			self.assertTrue(self.endpoint.connected, "Endpoint is not connected")
+			
+			self.failUnless(self.server.count == self.count, "Didn't receive the right number of messages")
+			self.failUnless(self.endpoint.count == self.count, "Didn't receive the right number of messages")
+			
+			self.endpoint.Close()
+			print self.endpoint.GetQueue()
+			print
+		
+		def tearDown(self):
+			del self.server
+			del self.endpoint
+			print "EndPointTestCase complete"
+	
+	unittest.main()
+	

File alchemymadness/PodSixNet/Server.py

+import socket
+import sys
+
+from async import poll, asyncore
+from Channel import Channel
+
+class Server(asyncore.dispatcher):
+	channelClass = Channel
+	
+	def __init__(self, channelClass=None, localaddr=("127.0.0.1", 31425), listeners=5):
+		if channelClass:
+			self.channelClass = channelClass
+		self._map = {}
+		self.channels = []
+		asyncore.dispatcher.__init__(self, map=self._map)
+		self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+		self.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+		self.set_reuse_addr()
+		self.bind(localaddr)
+		self.listen(listeners)
+	
+	def handle_accept(self):
+		try:
+			conn, addr = self.accept()
+		except socket.error:
+			print 'warning: server accept() threw an exception'
+			return
+		except TypeError:
+			print 'warning: server accept() threw EWOULDBLOCK'
+			return
+		
+		self.channels.append(self.channelClass(conn, addr, self, self._map))
+		self.channels[-1].Send({"action": "connected"})
+		if hasattr(self, "Connected"):
+			self.Connected(self.channels[-1], addr)
+	
+	def Pump(self):
+		[c.Pump() for c in self.channels]
+		poll(map=self._map)
+
+#########################
+#	Test stub	#
+#########################
+
+if __name__ == "__main__":
+	import unittest
+	
+	class ServerTestCase(unittest.TestCase):
+		testdata = {"action": "hello", "data": {"a": 321, "b": [2, 3, 4], "c": ["afw", "wafF", "aa", "weEEW", "w234r"], "d": ["x"] * 256}}
+		def setUp(self):
+			print "ServerTestCase"
+			print "--------------"
+			
+			class ServerChannel(Channel):
+				def Network_hello(self, data):
+					print "*Server* ran test method for 'hello' action"
+					print "*Server* received:", data
+					self._server.received = data
+			
+			class EndPointChannel(Channel):
+				connected = False
+				def Connected(self):
+					print "*EndPoint* Connected()"
+				
+				def Network_connected(self, data):
+					self.connected = True
+					print "*EndPoint* Network_connected(", data, ")"
+					print "*EndPoint* initiating send"
+					self.Send(ServerTestCase.testdata)
+			
+			class TestServer(Server):
+				connected = False
+				received = None
+				def Connected(self, channel, addr):
+					self.connected = True
+					print "*Server* Connected() ", channel, "connected on", addr
+			
+			self.server = TestServer(channelClass=ServerChannel)
+			
+			sender = asyncore.dispatcher(map=self.server._map)
+			sender.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+			sender.connect(("localhost", 31425))
+			self.outgoing = EndPointChannel(sender, map=self.server._map)
+			
+		def runTest(self):
+			from time import sleep
+			print "*** polling for half a second"
+			for x in range(250):
+				self.server.Pump()
+				self.outgoing.Pump()
+				if self.server.received:
+					self.failUnless(self.server.received == self.testdata)
+					self.server.received = None
+				sleep(0.001)
+			self.failUnless(self.server.connected == True, "Server is not connected")
+			self.failUnless(self.outgoing.connected == True, "Outgoing socket is not connected")
+		
+		def tearDown(self):
+			pass
+			del self.server
+			del self.outgoing
+	
+	unittest.main()

File alchemymadness/PodSixNet/__init__.py

Empty file added.

File alchemymadness/PodSixNet/async.py

+""" monkey patched version of asynchat to allow map argument on all version of Python, and the best version of the poll function. """
+from sys import version
+
+import asynchat
+import asyncore
+
+if float(version[:3]) < 2.5:
+	from asyncore import poll2 as poll
+else:
+	from asyncore import poll
+
+# monkey patch older versions to support maps in asynchat. Yuck.
+if float(version[:3]) < 2.6:
+	def asynchat_monkey_init(self, conn=None, map=None):
+		self.ac_in_buffer = ''
+		self.ac_out_buffer = ''
+		self.producer_fifo = asynchat.fifo()
+		asyncore.dispatcher.__init__ (self, sock=conn, map=map)
+	asynchat.async_chat.__init__ = asynchat_monkey_init
+

File alchemymadness/PodSixNet/rencode.py

+
+"""
+rencode -- Web safe object pickling/unpickling.
+
+The rencode module is a modified version of bencode from the
+BitTorrent project.  For complex, heterogeneous data structures with
+many small elements, r-encodings take up significantly less space than
+b-encodings:
+
+ >>> len(rencode.dumps({'a':0, 'b':[1,2], 'c':99}))
+ 13
+ >>> len(bencode.bencode({'a':0, 'b':[1,2], 'c':99}))
+ 26
+
+The rencode format is not standardized, and may change with different
+rencode module versions, so you should check that you are using the
+same rencode version throughout your project.
+"""
+
+__version__ = '1.0.0-ntk'
+__all__ = ['dumps', 'loads', 'serializable']
+
+from base64 import b64encode, b64decode
+
+##TODO
+# - Why the hell it encodes both tuples and lists to tuple?
+#   Try loads(dumps([1,(2,3)]))
+#   Grr
+#
+# - extend it! Support other basic types, f.e. Set()
+#
+##
+
+# Original bencode module by Petru Paler, et al.
+#
+# Modifications by Daniele Tricoli:
+#
+#  - Added support for instances
+#    Only registered instances can be serialized. An instance to be serialized
+#    must provide a '_pack` method.
+#    E.g.
+#
+#    class X(object):
+#
+#        def __init__(self, x):
+#            self.x = x
+#
+#        def _pack(self)
+#           return (self.x,) # a tuple
+#
+#  - Lists are decoded again as list
+#
+# Modifications by Connelly Barnes:
+#
+#  - Added support for floats (sent as 32-bit or 64-bit in network
+#    order), bools, None.
+#  - Allowed dict keys to be of any serializable type.
+#  - Lists/tuples are always decoded as tuples (thus, tuples can be
+#    used as dict keys).
+#  - Embedded extra information in the 'typecodes' to save some space.
+#  - Added a restriction on integer length, so that malicious hosts
+#    cannot pass us large integers which take a long time to decode.
+#
+# Licensed by Bram Cohen under the "MIT license":
+#
+#  "Copyright (C) 2001-2002 Bram Cohen
+#
+#  Permission is hereby granted, free of charge, to any person
+#  obtaining a copy of this software and associated documentation files
+#  (the "Software"), to deal in the Software without restriction,
+#  including without limitation the rights to use, copy, modify, merge,
+#  publish, distribute, sublicense, and/or sell copies of the Software,
+#  and to permit persons to whom the Software is furnished to do so,
+#  subject to the following conditions:
+#
+#  The above copyright notice and this permission notice shall be
+#  included in all copies or substantial portions of the Software.
+#
+#  The Software is provided "AS IS", without warranty of any kind,
+#  express or implied, including but not limited to the warranties of
+#  merchantability,  fitness for a particular purpose and
+#  noninfringement. In no event shall the  authors or copyright holders
+#  be liable for any claim, damages or other liability, whether in an
+#  action of contract, tort or otherwise, arising from, out of or in
+#  connection with the Software or the use or other dealings in the
+#  Software."
+#
+# (The rencode module is licensed under the above license as well).
+#
+
+import inspect
+import struct
+
+from threading import Lock
+
+from types import (StringType,
+                   UnicodeType,
+                   IntType,
+                   LongType,
+                   DictType,
+                   ListType,
+                   TupleType,
+                   FloatType,
+                   NoneType)
+
+class AlreadyRegistered(Exception): pass
+
+class NotRegistered(Exception):
+
+    def __init__(self, class_):
+        self.class_ = class_
+
+    def __str__(self):
+        return 'Class %s is not registered' % self.class_
+
+class NotSerializable(Exception): pass
+
+def add_class_name(func):
+
+    if inspect.ismethod(func):
+        def decorate(*args, **kargs):
+            result = func(*args, **kargs)
+            result = (str(func.im_class.__name__),) + result
+            return result
+
+        return decorate
+
+class _SerializableRegistry(object):
+
+    def __init__(self):
+        self._registry = {}
+
+    def __contains__(self, item):
+        return item in self._registry
+
+    def __getitem__(self, key):
+        return self._registry[key]
+
+    def register(self, cls):
+        '''   '''
+        if inspect.isclass(cls):
+
+            if cls.__name__ in self._registry:
+                msg = 'Class %s is already registered' % cls.__name__
+                raise AlreadyRegistered(msg)
+
+            try:
+                if inspect.ismethod(cls._pack):
+                    cls._pack = add_class_name(cls._pack)
+                    self._registry[cls.__name__] = cls
+            except AttributeError, err:
+                raise NotSerializable(err)
+
+    def unregister(self, cls):
+        '''   '''
+        if inspect.isclass(cls):
+
+            if cls in self._registry:
+                del self._registry[cls.__name__]
+            else: 
+                raise NotRegistered(cls.__name__)
+
+serializable = _SerializableRegistry()
+
+# Number of bits for serialized floats, either 32 or 64.
+FLOAT_BITS = 32
+
+# Maximum length of integer when written as base 10 string.
+MAX_INT_LENGTH = 64
+
+# The bencode 'typecodes' such as i, d, etc have been extended and
+# relocated on the base-256 character set.
+# Can't be used chr(48) to chr(57) because they are manually set
+CHR_INSTANCE = chr(47) 
+CHR_TUPLE = chr(58)
+CHR_LIST = chr(59)
+CHR_DICT = chr(60)
+CHR_INT = chr(61)
+CHR_INT1 = chr(62)
+CHR_INT2 = chr(63)
+CHR_INT4 = chr(64)
+CHR_INT8 = chr(65)
+CHR_FLOAT = chr(66)
+CHR_TRUE = chr(67)
+CHR_FALSE = chr(68)
+CHR_NONE = chr(69)
+CHR_TERM = chr(127)
+
+# Positive integers with value embedded in typecode.
+INT_POS_FIXED_START = 0
+INT_POS_FIXED_COUNT = 32
+
+# Dictionaries with length embedded in typecode.
+DICT_FIXED_START = 102
+DICT_FIXED_COUNT = 25
+
+# Negative integers with value embedded in typecode.
+INT_NEG_FIXED_START = 70
+INT_NEG_FIXED_COUNT = 32
+
+# Strings with length embedded in typecode.
+STR_FIXED_START = 128
+STR_FIXED_COUNT = 64
+
+# Lists with length embedded in typecode.
+LIST_FIXED_START = STR_FIXED_START + STR_FIXED_COUNT
+LIST_FIXED_COUNT = 32
+
+# Tuples with length embedded in typecode.
+TUPLE_FIXED_START = LIST_FIXED_START + LIST_FIXED_COUNT
+TUPLE_FIXED_COUNT = 32
+
+def decode_int(x, f):
+    f += 1
+    newf = x.index(CHR_TERM, f)
+    if newf - f >= MAX_INT_LENGTH:
+        raise ValueError('overflow')
+    try:
+        n = int(x[f:newf])
+    except (OverflowError, ValueError):
+        n = long(x[f:newf])
+    if x[f] == '-':
+        if x[f + 1] == '0':
+            raise ValueError
+    elif x[f] == '0' and newf != f+1:
+        raise ValueError
+    return (n, newf+1)
+
+def decode_intb(x, f):
+    f += 1
+    return (struct.unpack('!b', x[f:f+1])[0], f+1)
+
+def decode_inth(x, f):
+    f += 1
+    return (struct.unpack('!h', x[f:f+2])[0], f+2)
+
+def decode_intl(x, f):
+    f += 1
+    return (struct.unpack('!l', x[f:f+4])[0], f+4)
+
+def decode_intq(x, f):
+    f += 1
+    return (struct.unpack('!q', x[f:f+8])[0], f+8)
+
+def decode_float(x, f):
+    f += 1
+    if FLOAT_BITS == 32:
+        n = struct.unpack('!f', x[f:f+4])[0]
+        return (n, f+4)
+    elif FLOAT_BITS == 64:
+        n = struct.unpack('!d', x[f:f+8])[0]
+        return (n, f+8)
+    else:
+        raise ValueError
+
+def decode_string(x, f):
+    colon = x.index(':', f)
+    try:
+        n = int(x[f:colon])
+    except (OverflowError, ValueError):
+        n = long(x[f:colon])
+    if x[f] == '0' and colon != f+1:
+        raise ValueError
+    colon += 1
+    return (b64decode(x[colon:colon+n]), colon+n)
+
+def decode_list(x, f):
+    r, f = [], f+1
+    while x[f] != CHR_TERM:
+        v, f = decode_func[x[f]](x, f)
+        r.append(v)
+    return (r, f + 1)
+
+def decode_tuple(x, f):
+    r, f = [], f+1
+    while x[f] != CHR_TERM:
+        v, f = decode_func[x[f]](x, f)
+        r.append(v)
+    return (tuple(r), f + 1)
+
+def decode_dict(x, f):
+    r, f = {}, f+1
+    while x[f] != CHR_TERM:
+        k, f = decode_func[x[f]](x, f)
+        r[k], f = decode_func[x[f]](x, f)
+    return (r, f + 1)
+
+def decode_true(x, f):
+  return (True, f+1)
+
+def decode_false(x, f):
+  return (False, f+1)
+
+def decode_none(x, f):
+  return (None, f+1)
+
+def decode_instance(x, f):
+    f += 1
+    while x[f] != CHR_TERM:
+        v, f = decode_func[x[f]](x, f)
+    if v[0] in serializable:
+        r = serializable[v[0]](*v[1:])
+    else:
+        raise NotRegistered(v[0])
+    return (r, f+1)
+
+decode_func = {}
+decode_func['0'] = decode_string
+decode_func['1'] = decode_string
+decode_func['2'] = decode_string
+decode_func['3'] = decode_string
+decode_func['4'] = decode_string
+decode_func['5'] = decode_string
+decode_func['6'] = decode_string
+decode_func['7'] = decode_string
+decode_func['8'] = decode_string
+decode_func['9'] = decode_string
+decode_func[CHR_LIST ] = decode_list
+decode_func[CHR_TUPLE] = decode_tuple
+decode_func[CHR_DICT ] = decode_dict
+decode_func[CHR_INT  ] = decode_int
+decode_func[CHR_INT1 ] = decode_intb
+decode_func[CHR_INT2 ] = decode_inth
+decode_func[CHR_INT4 ] = decode_intl
+decode_func[CHR_INT8 ] = decode_intq
+decode_func[CHR_FLOAT] = decode_float
+decode_func[CHR_TRUE ] = decode_true
+decode_func[CHR_FALSE] = decode_false
+decode_func[CHR_NONE ] = decode_none
+decode_func[CHR_INSTANCE] = decode_instance
+
+def make_fixed_length_string_decoders():
+    def make_decoder(slen):
+        def f_fixed_string(x, f):
+            return (b64decode(x[f+1:f+1+slen]), f+1+slen)
+        return f_fixed_string
+    for i in range(STR_FIXED_COUNT):
+        decode_func[chr(STR_FIXED_START+i)] = make_decoder(i)
+
+make_fixed_length_string_decoders()
+
+def make_fixed_length_list_decoders():
+    def make_decoder(slen):
+        def f_fixed_list(x, f):
+            r, f = [], f+1
+            for i in range(slen):
+                v, f = decode_func[x[f]](x, f)
+                r.append(v)
+            return (r, f)
+        return f_fixed_list
+    for i in range(LIST_FIXED_COUNT):
+        decode_func[chr(LIST_FIXED_START+i)] = make_decoder(i)
+
+make_fixed_length_list_decoders()
+
+def make_fixed_length_tuple_decoders():
+    def make_decoder(slen):
+        def f_fixed_tuple(x, f):
+            r, f = [], f+1
+            for i in range(slen):
+                v, f = decode_func[x[f]](x, f)
+                r.append(v)
+            return (tuple(r), f)
+        return f_fixed_tuple
+    for i in range(TUPLE_FIXED_COUNT):
+        decode_func[chr(TUPLE_FIXED_START+i)] = make_decoder(i)
+
+make_fixed_length_tuple_decoders()
+
+def make_fixed_length_int_decoders():
+    def make_decoder(j):
+        def f(x, f):
+            return (j, f+1)
+        return f
+    for i in range(INT_POS_FIXED_COUNT):
+        decode_func[chr(INT_POS_FIXED_START+i)] = make_decoder(i)
+    for i in range(INT_NEG_FIXED_COUNT):
+        decode_func[chr(INT_NEG_FIXED_START+i)] = make_decoder(-1-i)
+
+make_fixed_length_int_decoders()
+
+def make_fixed_length_dict_decoders():
+    def make_decoder(slen):
+        def f(x, f):
+            r, f = {}, f+1
+            for j in range(slen):
+                k, f = decode_func[x[f]](x, f)
+                r[k], f = decode_func[x[f]](x, f)
+            return (r, f)
+        return f
+    for i in range(DICT_FIXED_COUNT):
+        decode_func[chr(DICT_FIXED_START+i)] = make_decoder(i)
+
+make_fixed_length_dict_decoders()
+
+def loads(x):
+    try:
+        r, l = decode_func[x[0]](x, 0)
+    except (IndexError, KeyError):
+        raise 
+    if l != len(x):
+        raise ValueError
+    return r
+
+def encode_int(x, r):
+    if 0 <= x < INT_POS_FIXED_COUNT:
+        r.append(chr(INT_POS_FIXED_START+x))
+    elif -INT_NEG_FIXED_COUNT <= x < 0:
+        r.append(chr(INT_NEG_FIXED_START-1-x))
+    elif -128 <= x < 128:
+        r.extend((CHR_INT1, struct.pack('!b', x)))
+    elif -32768 <= x < 32768:
+        r.extend((CHR_INT2, struct.pack('!h', x)))
+    elif -2147483648 <= x < 2147483648:
+        r.extend((CHR_INT4, struct.pack('!l', x)))
+    elif -9223372036854775808 <= x < 9223372036854775808:
+        r.extend((CHR_INT8, struct.pack('!q', x)))
+    else:
+        s = str(x)
+        if len(s) >= MAX_INT_LENGTH:
+            raise ValueError('overflow')
+        r.extend((CHR_INT, s, CHR_TERM))
+
+def encode_float(x, r):
+    if FLOAT_BITS == 32:
+        r.extend((CHR_FLOAT, struct.pack('!f', x)))
+    elif FLOAT_BITS == 64:
+        r.extend((CHR_FLOAT, struct.pack('!d', x)))
+    else:
+        raise ValueError
+
+def encode_bool(x, r):
+    r.extend({False: CHR_FALSE, True: CHR_TRUE}[bool(x)])
+
+def encode_none(x, r):
+    r.extend(CHR_NONE)
+
+def encode_string(x, r):
+    x = b64encode(x)
+    if len(x) < STR_FIXED_COUNT:
+        r.extend((chr(STR_FIXED_START + len(x)), x))
+    else:
+        r.extend((str(len(x)), ':', x))
+
+def encode_list(x, r):
+    if len(x) < LIST_FIXED_COUNT:
+        r.append(chr(LIST_FIXED_START + len(x)))
+        for i in x:
+            encode_func.get(type(i), encode_instance)(i, r)
+    else:
+        r.append(CHR_LIST)
+        for i in x:
+            encode_func.get(type(i), encode_instance)(i, r)
+        r.append(CHR_TERM)
+
+
+def encode_tuple(x, r):
+    if len(x) < TUPLE_FIXED_COUNT:
+        r.append(chr(TUPLE_FIXED_START + len(x)))
+        for i in x:
+            encode_func.get(type(i), encode_instance)(i, r)
+    else:
+        r.append(CHR_TUPLE)
+        for i in x:
+            encode_func.get(type(i), encode_instance)(i, r)
+        r.append(CHR_TERM)
+
+def encode_dict(x,r):
+    if len(x) < DICT_FIXED_COUNT:
+        r.append(chr(DICT_FIXED_START + len(x)))
+        for k, v in x.items():
+            encode_func[type(k)](k, r)
+            encode_func[type(v)](v, r)
+    else:
+        r.append(CHR_DICT)
+        for k, v in x.items():
+            encode_func[type(k)](k, r)
+            encode_func[type(v)](v, r)
+        r.append(CHR_TERM)
+
+encode_func = {}
+encode_func[IntType] = encode_int
+encode_func[LongType] = encode_int
+encode_func[FloatType] = encode_float
+encode_func[StringType] = encode_string
+encode_func[UnicodeType] = encode_string
+encode_func[ListType] = encode_list
+encode_func[TupleType] = encode_tuple
+encode_func[DictType] = encode_dict
+encode_func[NoneType] = encode_none
+
+try:
+    from types import BooleanType
+    encode_func[BooleanType] = encode_bool
+except ImportError:
+    pass
+
+def encode_instance(x, r):
+    if hasattr(x, '_pack'):
+        if x.__class__.__name__ in serializable:
+            # Calling the class of instance `x' passing it to the
+            # unbound method
+            result = serializable[x.__class__.__name__]._pack(x)
+            r.append(CHR_INSTANCE)
+            encode_func[type(result)](result, r)
+            r.append(CHR_TERM)
+        else:
+            raise NotRegistered(x.__class__.__name__)
+
+lock = Lock()
+
+def dumps(x):
+    lock.acquire()
+    r = []
+    encode_func.get(type(x), encode_instance)(x, r)
+    lock.release()
+    return ''.join(r)
+
+def test():
+    f1 = struct.unpack('!f', struct.pack('!f', 25.5))[0]
+    f2 = struct.unpack('!f', struct.pack('!f', 29.3))[0]
+    f3 = struct.unpack('!f', struct.pack('!f', -0.6))[0]
+    L = (({'a':15, 'bb':f1, 'ccc':f2, '':(f3,(),False,True,'')},('a',10**20),tuple(range(-100000,100000)),'b'*31,'b'*62,'b'*64,2**30,2**33,2**62,2**64,2**30,2**33,2**62,2**64,False,False, True, -1, 2, 0),)
+    assert loads(dumps(L)) == L
+    d = dict(zip(range(-100000,100000),range(-100000,100000)))
+    d.update({'a':20, 20:40, 40:41, f1:f2, f2:f3, f3:False, False:True, True:False})
+    L = (d, {}, {5:6}, {7:7,True:8}, {9:10, 22:39, 49:50, 44: ''})
+    assert loads(dumps(L)) == L
+    L = ('', 'a'*10, 'a'*100, 'a'*1000, 'a'*10000, 'a'*100000, 'a'*1000000, 'a'*10000000)
+    assert loads(dumps(L)) == L
+    L = tuple([dict(zip(range(n),range(n))) for n in range(100)]) + ('b',)
+    assert loads(dumps(L)) == L
+    L = tuple([dict(zip(range(n),range(-n,0))) for n in range(100)]) + ('b',)
+    assert loads(dumps(L)) == L
+    L = tuple([tuple(range(n)) for n in range(100)]) + ('b',)
+    assert loads(dumps(L)) == L
+    L = tuple(['a'*n for n in range(100)]) + ('b',)
+    assert loads(dumps(L)) == L
+    L = tuple(['a'*n for n in range(100)]) + (None,True,None)
+    assert loads(dumps(L)) == L
+    L = list(['a'*n for n in range(100)]) + [None,True,None]
+    assert loads(dumps(L)) == L
+    assert loads(dumps(None)) == None
+    assert loads(dumps({None:None})) == {None:None}
+
+    class A(object):
+        def __init__(self, a, b, c):
+            self.a = a
+            self.b = b
+            self.c = c
+
+        def _pack(self):
+            return (self.a, self.b, self.c)
+
+    serializable.register(A)
+
+    instance = [A(1,2,3), 1, A(1,3,4), 'sss']
+    print loads(dumps(instance))
+
+if __name__ == '__main__':
+  test()

File alchemymadness/client.py

+import sys
+from threading import Thread, Event
+from PodSixNet.Connection import ConnectionListener, connection
+
+class RemoteObject(object):
+    synch = ()
+    
+    def __init__(self):
+        self.changed = False
+        self.updating = Event()
+        self.__setattr__ = self._setattr
+    
+    def _synch_var(self):
+        return ((k,v) for k,v in self.__dict__.iteritems() if k in self.synch)
+        
+    def _setattr(self, name, value):
+        object.__setattr__(self, name, value)
+        if not self.updating.is_set():
+            object.__setattr__(self, '_changed', self.changed or name in self.synch)
+
+class Player(RemoteObject):
+    synch = ('test',)
+    def __init__(self):
+        super(Player, self).__init__()
+        self.test = 'aaa'
+
+class GameClient(ConnectionListener):
+    def __init__(self, host, port):
+        self.player = Player()
+        self.others = {}
+        self._stop = Event()
+        self.input_thread = Thread(target = self.input_handler)
+        self.input_thread.daemon = True
+        self.input_thread.start()
+        self.Connect((host, port))
+        
+    def step(self, *args):
+        connection.Pump()
+        if self.player.changed:
+            self.Send(dict(self.player._synch_var(), action='update'))
+            self.player.changed = False
+        self.Pump()
+        
+    #def Network(self, data):
+    #    print data
+        
+    def Network_connected(self, data):
+        print '# Connected'
+        self._stop.clear()
+        if not self.input_thread.is_alive():
+            self.input_thread.start()
+
+    def Network_error(self, data):
+        print '# Error:', data['error'][1]
+        self.connection.Close()
+        self._stop.set()
+
+    def Network_disconnected(self, data):
+        print '# Disconnected'
+        self._stop.set()
+        
+    def Network_info(self, data):
+        for msg in data['info'].split('/n'):
+            print '#', msg
+        
+    def Network_msg(self, data):
+        print '%s: %s' % (data['id'], data['msg'])
+        
+    def Network_init(self, data):
+        self.player.updating.set()
+        self.player.test = data['test']
+        self.player.updating.clear()
+        
+    def Network_add(self, data):
+        p = Player()
+        self.others[data['id']] = p
+        
+    def Network_del(self, data):
+        del self.others[data['id']]
+        
+    def Network_update(self, data):
+        p = self.others[data['id']]
+        del data['action']
+        p.test = data['test']
+    
+    def input_handler(self):
+        while not self._stop.is_set():
+            msg = raw_input()
+            if len(msg) > 0:
+                self.Send({'action': 'msg', 'msg': msg[:255]})
+                
+    def run(self):
+        while not self._stop.wait(0.001):
+            self.step()
+
+if __name__ == '__main__':
+    addr = 'localhost'
+    if len(sys.argv) > 1:
+        addr = sys.argv[1]
+    GameClient(addr, 10000).run()

File alchemymadness/network_tmp/client.py

-import sys
-from threading import Thread, Event
-from PodSixNet.Connection import ConnectionListener, connection
-
-class RemoteObject(object):
-    synch = ()
-    
-    def __init__(self):
-        self.changed = False
-        self.updating = Event()
-        self.__setattr__ = self._setattr
-    
-    def _synch_var(self):
-        return ((k,v) for k,v in self.__dict__.iteritems() if k in self.synch)
-        
-    def _setattr(self, name, value):
-        object.__setattr__(self, name, value)
-        if not self.updating.is_set():
-            object.__setattr__(self, '_changed', self.changed or name in self.synch)
-
-class Player(RemoteObject):
-    synch = ('test',)
-    def __init__(self):
-        super(Player, self).__init__()
-        self.test = 'aaa'
-
-class GameClient(ConnectionListener):
-    def __init__(self, host, port):
-        self.player = Player()
-        self.others = {}
-        self._stop = Event()
-        self.input_thread = Thread(target = self.input_handler)
-        self.input_thread.daemon = True
-        self.input_thread.start()
-        self.Connect((host, port))
-        
-    def step(self, *args):
-        connection.Pump()
-        if self.player.changed:
-            self.Send(dict(self.player._synch_var(), action='update'))
-            self.player.changed = False
-        self.Pump()
-        
-    #def Network(self, data):
-    #    print data
-        
-    def Network_connected(self, data):
-        print '# Connected'
-        self._stop.clear()
-        if not self.input_thread.is_alive():
-            self.input_thread.start()
-
-    def Network_error(self, data):
-        print '# Error:', data['error'][1]
-        self.connection.Close()
-        self._stop.set()
-
-    def Network_disconnected(self, data):
-        print '# Disconnected'
-        self._stop.set()
-        
-    def Network_info(self, data):
-        for msg in data['info'].split('/n'):
-            print '#', msg
-        
-    def Network_msg(self, data):
-        print '%s: %s' % (data['id'], data['msg'])
-        
-    def Network_init(self, data):
-        self.player.updating.set()
-        self.player.test = data['test']
-        self.player.updating.clear()
-        
-    def Network_add(self, data):
-        p = Player()
-        self.others[data['id']] = p
-        
-    def Network_del(self, data):
-        del self.others[data['id']]
-        
-    def Network_update(self, data):
-        p = self.others[data['id']]
-        del data['action']
-        p.test = data['test']
-    
-    def input_handler(self):
-        while not self._stop.is_set():
-            msg = raw_input()
-            if len(msg) > 0:
-                self.Send({'action': 'msg', 'msg': msg[:255]})
-                
-    def run(self):
-        while not self._stop.wait(0.001):
-            self.step()
-
-if __name__ == '__main__':
-    addr = 'localhost'
-    if len(sys.argv) > 1:
-        addr = sys.argv[1]
-    GameClient(addr, 10000).run()

File alchemymadness/network_tmp/server.py

-from weakref import WeakKeyDictionary
-from threading import Thread, Timer, Event
-from PodSixNet.Server import Server
-from PodSixNet.Channel import Channel
-
-MAX_CLIENTS = 4
-
-class ClientChannel(Channel):
-    def __init__(self, *args, **kwargs):
-        Channel.__init__(self, *args, **kwargs)
-        self.id = -1
-        self.data = {'test': 123}
-        self._server.add_player(self)
-    
-    def Network(self, data):
-        print data
-        
-    def Network_update(self, data):
-        del data['action']
-        self.data = data
-        self._server.send_to_all(dict(data, action='update', id=self.id), self)
-        
-    def Network_msg(self, data):
-        self._server.send_to_all({'action':'msg', 'id':self.id, 'msg':data['msg']}, self)
-
-    def Close(self):
-        self._server.del_player(self)
-
-class GameServer(Server):
-    channelClass = ClientChannel
-    
-    def __init__(self, port, *args, **kwargs):
-        Server.__init__(self, localaddr=('', port), *args, **kwargs)
-        self.players = WeakKeyDictionary()
-        self._stop = Event()
-        self.av_id = 0
-
-    def Connected(self, channel, addr):
-        print 'New connection:', channel
-        
-    def add_player(self, player):
-        if len(self.players) >= MAX_CLIENTS:
-            player.Send({'action':'info', 'info':'Server full'})
-            player.Pump()
-            player.close()
-            return
-        else:
-            player.id = self.av_id
-            self.av_id += 1
-            print 'New player #%d (%s:%s)' % (player.id, player.addr[0], player.addr[0])
-            player.Send(dict(player.data, action='init'))
-            for p in self.players:
-                player.Send({'action':'add', 'id':p.id})
-            player.Pump()            
-            self.send_to_all({'action':'add', 'id':player.id}, player)
-            self.players[player] = True
-
-    def del_player(self, player):
-        print 'Deleting player @ %s:%s' % player.addr
-        if self.players.has_key(player):
-            self.send_to_all({'action': 'del', 'id':player.id}, player)
-            del self.players[player]
-    
-    def send_to_all(self, data, exclude=None):
-        for p in self.players:
-            if not p is exclude:
-                p.Send(data)
-        
-    def run(self):
-        while not self._stop.wait(0.001):
-            self.Pump()
-        
-if __name__ == "__main__":
-    print "serving @10000"
-    GameServer(10000).run()

File alchemymadness/server.py

+from weakref import WeakKeyDictionary
+from threading import Thread, Timer, Event
+from PodSixNet.Server import Server
+from PodSixNet.Channel import Channel
+
+MAX_CLIENTS = 4
+
+class ClientChannel(Channel):
+    def __init__(self, *args, **kwargs):
+        Channel.__init__(self, *args, **kwargs)
+        self.id = -1
+        self.data = {'test': 123}
+        self._server.add_player(self)
+    
+    def Network(self, data):
+        print data
+        
+    def Network_update(self, data):
+        del data['action']
+        self.data = data
+        self._server.send_to_all(dict(data, action='update', id=self.id), self)
+        
+    def Network_msg(self, data):
+        self._server.send_to_all({'action':'msg', 'id':self.id, 'msg':data['msg']}, self)
+
+    def Close(self):
+        self._server.del_player(self)
+
+class GameServer(Server):
+    channelClass = ClientChannel
+    
+    def __init__(self, port, *args, **kwargs):
+        Server.__init__(self, localaddr=('', port), *args, **kwargs)
+        self.players = WeakKeyDictionary()
+        self._stop = Event()
+        self.av_id = 0
+
+    def Connected(self, channel, addr):
+        print 'New connection:', channel
+        
+    def add_player(self, player):
+        if len(self.players) >= MAX_CLIENTS:
+            player.Send({'action':'info', 'info':'Server full'})
+            player.Pump()
+            player.close()
+            return
+        else:
+            player.id = self.av_id
+            self.av_id += 1
+            print 'New player #%d (%s:%s)' % (player.id, player.addr[0], player.addr[0])
+            player.Send(dict(player.data, action='init'))
+            for p in self.players:
+                player.Send({'action':'add', 'id':p.id})
+            player.Pump()            
+            self.send_to_all({'action':'add', 'id':player.id}, player)
+            self.players[player] = True
+
+    def del_player(self, player):
+        print 'Deleting player @ %s:%s' % player.addr
+        if self.players.has_key(player):
+            self.send_to_all({'action': 'del', 'id':player.id}, player)
+            del self.players[player]
+    
+    def send_to_all(self, data, exclude=None):
+        for p in self.players:
+            if not p is exclude:
+                p.Send(data)
+        
+    def run(self):
+        while not self._stop.wait(0.001):
+            self.Pump()
+        
+if __name__ == "__main__":
+    print "serving @10000"
+    GameServer(10000).run()