Panda datagrams sending gzipped yaml. With such nice datagram constructor methods and the nice datagram iterator class, this might be considered “doing it wrong”. However, when first starting on a project, it’s as good to be as flexible as possible. Premature optimization can be restrictive.
What’s the benefit? You’re not required to send only simple data structures, you can send instances of complex custom classes directly. They arrive with all of their methods functional and intact, ready to go. This is generally considered ‘insecure’, but we’re not using pickle or anything capable of generating a completely arbitrary object. Yaml is quite safe in this context since we can only load an object if it’s class is available already, so the only ‘validation’ required is to check the objects built-in class so we know what to do with it… I think. Yaml syntax is extremely similar to JSON, and some crazy-folk say it’s JSON with whitespace instead of brackets.
I’m new to both Panda and Python, but I’ve some experience with Ruby. I’m quite proud of this little snip-it, and I feel it could benefit the community as an example. It can no doubt be improved though, and I would very much appreciate any form of critique.
client.py
#! /usr/bin/env python
import zlib
import yaml #apt-get install python-yaml # pyyaml.org
from pandac.PandaModules import *
from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.PyDatagramIterator import PyDatagramIterator
from direct.task import Task
import treasure
ADDRESS = "127.0.0.1"
PORT = 1248
BACKLOG = 100
cManager = QueuedConnectionManager()
cReader = QueuedConnectionReader(cManager, 0)
cWriter = ConnectionWriter(cManager, 0)
activeConnections =[]
print "connecting..."
connection = cManager.openTCPClientConnection(ADDRESS,PORT,3000)
if connection:
cReader.addConnection(connection)
print "connected!"
def processDatagram(datagram):
foo = yaml.load(zlib.decompress(PyDatagramIterator(datagram).getString()))
if foo.__class__ == treasure.Treasure:
foo.sell()
def tskReaderPolling(taskdata):
if cReader.dataAvailable():
datagram = PyDatagram()
if cReader.getData(datagram): processDatagram(datagram)
return Task.cont
loadPrcFile("config.prc") # stub graphics and sound
import direct.directbase.DirectStart # create taskMgr
taskMgr.add(tskReaderPolling, "Poll the connection reader.", -40)
run() # start taskMgr
server.py
#! /usr/bin/env python
import zlib
import yaml #apt-get install python-yaml # pyyaml.org
import treasure
from pandac.PandaModules import *
from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.PyDatagramIterator import PyDatagramIterator
from direct.task import Task
loadPrcFile("config.prc") # stub graphics and sound
import direct.directbase.DirectStart # create taskMgr
PORT = 1248
BACKLOG = 100
cManager = QueuedConnectionManager()
cListener = QueuedConnectionListener(cManager, 0)
cListener = QueuedConnectionListener(cManager, 0)
cReader = QueuedConnectionReader(cManager, 0)
cWriter = ConnectionWriter(cManager, 0)
activeConnections =[]
tcpSocket = cManager.openTCPServerRendezvous(PORT,BACKLOG)
cListener.addConnection(tcpSocket)
def tskListenerPolling(taskdata):
if cListener.newConnectionAvailable():
print "Someone's knocking..."
rendezvous = PointerToConnection()
netAddress = NetAddress()
newConnection = PointerToConnection()
if cListener.getNewConnection(rendezvous, netAddress, newConnection):
newConnection = newConnection.p()
activeConnections.append(newConnection)
cReader.addConnection(newConnection)
print "Come on in!"
return Task.cont
def sendTreasure(taskdata):
datagram = PyDatagram()
datagram.addString(zlib.compress(yaml.dump(treasure.createLoot())))
if activeConnections:
for connection in activeConnections:
cWriter.send(datagram,connection)
return Task.again
taskMgr.add(tskListenerPolling, "Poll the connection listener.", -39)
taskMgr.doMethodLater(3,sendTreasure,"Send a random treasure to the client.")
run() # start taskMgr
treasure.py
import random
class Treasure():
def __init__(self):
pass
value = None;
name = None;
def sell(self):
print "A vendor gives you {0} gold for your new {1}.".format(self.value,self.name)
def createLoot():
loot = Treasure()
loot.value = random.randrange(1,10)
prefix = ("Shiny","Enchanted","Rusty","Dark","Stone","Wooden","Iron","Crystal","Damaged")
base = ("Sword","Shield","Armor","Dagger","Spear","Staff","Wand","Robes")
suffix = ("of Might","of Wisdom","of Doom","of Justice", "", "", "", "", "")
loot.name = "{0} {1} {2}".format(random.choice(prefix),random.choice(base),random.choice(suffix)).strip()
return loot
config.prc
window-type none
audio-library-name null
The server makes a random treasure every 3 seconds, then sends it to all clients (dupe hax!! ). The client then gets a new instance of a treasure object, then uses its sell method to display a message. Both client and server implement the same treasure module and class, but this is not necessary.
Thanks to treeform, Xidram, |Craig|, ThomasEgi and others for their patience and support in IRC.