Really Easy Networking

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!! :stuck_out_tongue:). 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. :smiley:

glad to test anything :slight_smile:
so here goes:

for Panda3d 1.7.0 on Windows, the yaml pack is here: pyyaml.org/download/pyyaml/PyYAM … -py2.6.exe

Now, when launching i get:

What i usually do for server-type scripts is add at the very beginning:

from pandac.PandaModules import loadPrcFileData 
loadPrcFileData("", "window-type none") 
loadPrcFileData("", "audio-library-name null")
loadPrcFileData("", "lock-to-one-cpu 0")
loadPrcFileData("", "client-sleep 0.0001")

You might not need the last 2 lines, I use them to avoid problems and limit the load on the processor. The last one makes a huge difference on CPU load even for your scripts, for both client and server.

For the functionality part, all works well, i spawned 8 clients and no surprises.
I still need to better understand how I could benefit of this but other than that … good work!

Thanks for the advice. I was going to edit the prc in-memory as you describe but I figured this way would cut back on code duplication a bit… which is entirely pointless since I obviously want the client and server to differ from each-other in this regard. I was unaware of the other two configure options though. What the program displays isn’t as cool as what it does. It turns.

instance of class on server -> convert into data structure -> transmit in datagram -> determine type of datagram -> reconstruct class based on type

into

instance of class on server -> magic happens -> instance of class on client

It makes sense to me, but maybe that’s because I don’t quite get it.

Ok now, it’s starting to make sense to me, and I must say that although I had already seen most of your code in other examples, the fact that you reduced it to basics was extremely helpful.

I have devised additional routines to perform pretty much the same tasks, so now clients are also sending similar information back to the server.