Dear Twisted users,
I recently found myself implementing a
design pattern that I think twisted.pb was specifically designed to
address. I think I'm not using pb correctly so I'd like advice. This is a
somewhat longish post because I need to describe the problem I'm trying
to solve.
I have done internet searches on Stack Overflow and this list but
have not found the answer to my question. If I've missed something
kindly direct me to the appropriate reference.
I want to
implement something functionally equivalent to a network chess game. I
first consider how I would do this on a single computer with no network
(maybe this is bad thinking). Each piece in the game is represented by
an instance of class Agent. Each agent has a .graphics attribute which
is an instance of a class from a GUI toolkit library or equivalent.
Whenever an agent in the game needs to do something there will be
business logic executed by the game objects proper (ie the agents) which
will invoke methods on the .graphics objects to update the screen. This
sort of structure seems natural as it allows easy integration of
drag/drop, mouse click detection etc. It also nicely separates the real
business logic from the GUI.
Now I want to run over the network. The question is how should I set up references between the client and server objects?
Surely
the server will maintain a set of objects representing the pieces in
the game. It seems reasonable that each user's program will have a
corresponding set of objects (with .graphics attributes). The issue is,
what do we mean by "corresponding" and how do these objects talk to one
another? Following is my idea so far:
Each instance of AgentClient has a .server attribute which is a
remote reference to an instance of AgentServer, and each instance of
AgentServer has a .clients attribute which is a list of remote
references to instances of AgentClient.
class AgentServer(pb.referenceable):
def remote_move(self, targetSquare):
"""Handle move request from client"""
if self.thisMoveIsLegal(targetSquare):
self.position = targetSquare
for client in self.clients:
client.callRemote("move", targetSquare)
def thisMoveIsLegal(self, targetSquare):
<check that this is a legal move>
class AgentClient(pb.referenceable):
def requestMove(self, targetSquare):
"""Tell server we'd like to move"""
self.server.callRemote("move", targetSquare)
def remote_move(self, targetSquare):
"""Server told us we moved"""
self.position = targetSquare
self.graphics.setNewPosition(targetSquare)
This isn't THAT bad. The client's requestMove is thin and unecessary (I
put it there for illustration). Still I need to have two separate
classes with corresponding methods to handle moving the piece. This
seems like the kind of thing I could twisted.pb to solve more cleanly if
I only would look in the right place.
This problem gets even worse when I think about how to birth new in-game objects. It would have to look like this:
class PlayerServer(pb.referenceable):
def newAgent(self, asker):
"""Client told us it wants a new Agent"""
if self.thisIsLegal():
a = AgentServer()
self.agents.append(a)
for client in self.clients:
d = client.callRemote("newAgent", a)
d.addCallback(lambda obj: a.clients.append(obj))
class PlayerClient(bp.referenceable):
def requestNewAgent(self):
"""Tell the server we want to spawn a new Agent"""
self.server.callRemote("newAgent", self)
def newAgent(self, serverObj):
a = AgentClient()
self.agents.append(a)
a.server = serverObj
return a
This just looks wrong. Any advice?
Thank you in advance for your help.
Regards,
Daniel Sank