Synced version of cluster code with select/deselect and limited command support

This commit is contained in:
Mark Mine 2002-04-08 05:59:20 +00:00
parent 54563e7a00
commit 55a3ae9bff
8 changed files with 415 additions and 357 deletions

View File

@ -1,123 +1,28 @@
"""ClusterClient: Master for mutlipiping or PC clusters. """
"""ClusterClient: Master for mutli-piping or PC clusters. """
from PandaModules import *
from ClusterMsgs import *
from ClusterConfig import *
import DirectNotifyGlobal
import DirectObject
import Task
class ClusterConfigItem:
def __init__(self, serverFunction, serverName, port):
self.serverName = serverName
self.serverFunction = serverFunction
self.port = port
# Camera Offset
self.xyz = Vec3(0)
self.hpr = Vec3(0)
# Camera Frustum Data
self.fFrustum = 0
self.focalLength = None
self.filmSize = None
self.filmOffset = None
def setCamOffset(self, xyz, hpr):
self.xyz = xyz
self.hpr = hpr
def setCamFrustum(self, focalLength, filmSize, filmOffset):
self.fFrustum = 1
self.focalLength = focalLength
self.filmSize = filmSize
self.filmOffset = filmOffset
class DisplayConnection:
def __init__(self,qcm,serverName,port,msgHandler):
self.msgHandler = msgHandler
gameServerTimeoutMs = base.config.GetInt(
"game-server-timeout-ms", 20000)
# A big old 20 second timeout.
self.tcpConn = qcm.openTCPClientConnection(
serverName, port, gameServerTimeoutMs)
# Test for bad connection
if self.tcpConn == None:
return None
else:
self.tcpConn.setNoDelay(1)
self.qcr=QueuedConnectionReader(qcm, 0)
self.qcr.addConnection(self.tcpConn)
self.cw=ConnectionWriter(qcm, 0)
def sendCamOffset(self,xyz,hpr):
ClusterManager.notify.debug("send cam offset...")
ClusterManager.notify.debug( ("packet %d xyz,hpr=%f %f %f %f %f %f" %
(self.msgHandler.packetNumber,xyz[0],xyz[1],xyz[2],
hpr[0],hpr[1],hpr[2])) )
datagram = self.msgHandler.makeCamOffsetDatagram(xyz, hpr)
self.cw.send(datagram, self.tcpConn)
def sendCamFrustum(self,focalLength, filmSize, filmOffset):
ClusterManager.notify.debug("send cam frustum...")
ClusterManager.notify.debug(
(("packet %d" % self.msgHandler.packetNumber) +
(" fl, fs, fo=%0.3f, (%0.3f, %0.3f), (%0.3f, %0.3f)" %
(focalLength, filmSize[0], filmSize[1],
filmOffset[0], filmOffset[1])))
)
datagram = self.msgHandler.makeCamFrustumDatagram(
focalLength, filmSize, filmOffset)
self.cw.send(datagram, self.tcpConn)
def sendMoveCam(self,xyz,hpr):
ClusterManager.notify.debug("send cam move...")
ClusterManager.notify.debug( ("packet %d xyz,hpr=%f %f %f %f %f %f" %
(self.msgHandler.packetNumber,xyz[0],xyz[1],xyz[2],
hpr[0],hpr[1],hpr[2])) )
datagram = self.msgHandler.makeMoveCamDatagram(xyz, hpr)
self.cw.send(datagram, self.tcpConn)
# the following should only be called by a synchronized cluster manger
def getSwapReady(self):
while 1:
(datagram, dgi,type) = self.msgHandler.blockingRead(self.qcr)
if type == CLUSTER_SWAP_READY:
break
else:
self.notify.warning('was expecting SWAP_READY, got %d' % type)
# the following should only be called by a synchronized cluster manger
def sendSwapNow(self):
ClusterManager.notify.debug(
"display connect send swap now, packet %d" %
self.msgHandler.packetNumber)
datagram = self.msgHandler.makeSwapNowDatagram()
self.cw.send(datagram, self.tcpConn)
def sendCommandString(self, commandString):
ClusterManager.notify.debug("send command string: %s" % commandString)
datagram = self.msgHandler.makeCommandStringDatagram(commandString)
self.cw.send(datagram, self.tcpConn)
def sendExit(self):
ClusterManager.notify.debug(
"display connect send exit, packet %d" %
self.msgHandler.packetNumber)
datagram = self.msgHandler.makeExitDatagram()
self.cw.send(datagram, self.tcpConn)
class ClusterManager(DirectObject.DirectObject):
class ClusterClient(DirectObject.DirectObject):
notify = DirectNotifyGlobal.directNotify.newCategory("ClusterClient")
MGR_NUM = 1000000
def __init__(self, configList):
self.qcm=QueuedConnectionManager()
self.serverList = []
self.msgHandler = MsgHandler(ClusterManager.MGR_NUM,self.notify)
self.msgHandler = ClusterMsgHandler(ClusterClient.MGR_NUM, self.notify)
for serverConfig in configList:
server = DisplayConnection(self.qcm,serverConfig.serverName,
serverConfig.port,self.msgHandler)
if server == None:
self.notify.error( ('Could not open %s on %s port %d' %
(serverConfig.serverFunction,
serverConfig.serverName,
serverConfig.port)) )
self.notify.error('Could not open %s on %s port %d' %
(serverConfig.serverConfigName,
serverConfig.serverName,
serverConfig.port))
else:
server.sendCamOffset(serverConfig.xyz,serverConfig.hpr)
if serverConfig.fFrustum:
@ -128,6 +33,7 @@ class ClusterManager(DirectObject.DirectObject):
self.startMoveCamTask()
def moveCamera(self, xyz, hpr):
self.notify.debug('moving unsynced camera')
for server in self.serverList:
server.sendMoveCam(xyz,hpr)
@ -136,16 +42,34 @@ class ClusterManager(DirectObject.DirectObject):
def moveCameraTask(self,task):
self.moveCamera(
direct.camera.getPos(render),
direct.camera.getHpr(render))
base.camera.getPos(render),
base.camera.getHpr(render))
return Task.cont
def clusterCommand(self, commandString):
def cmd(self, commandString, fLocally = 1):
# Execute remotely
for server in self.serverList:
server.sendCommandString(commandString)
# Execute locally
exec( commandString, globals())
if fLocally:
# Execute locally
exec( commandString, globals() )
def getNodePathFindCmd(self, nodePath):
import string
pathString = `nodePath`
index = string.find(pathString, '/')
if index != -1:
rootName = pathString[:index]
searchString = pathString[index+1:]
return rootName + ('.find("%s")' % searchString)
else:
return rootName
def selectNodePath(self, nodePath):
self.cmd(self.getNodePathFindCmd(nodePath) + '.select()', 0)
def deselectNodePath(self, nodePath):
self.cmd(self.getNodePathFindCmd(nodePath) + '.deselect()', 0)
def exit(self):
# Execute remotely
@ -155,10 +79,10 @@ class ClusterManager(DirectObject.DirectObject):
import sys
sys.exit()
class ClusterManagerSync(ClusterManager):
class ClusterClientSync(ClusterClient):
def __init__(self, configList):
ClusterManager.__init__(self, configList)
ClusterClient.__init__(self, configList)
#I probably don't need this
self.waitForSwap = 0
self.ready = 0
@ -190,11 +114,167 @@ class ClusterManagerSync(ClusterManager):
def moveCamera(self,xyz,hpr):
if self.ready:
self.notify.debug('moving synced camera')
ClusterManager.moveCamera(self,xyz,hpr)
ClusterClient.moveCamera(self,xyz,hpr)
self.waitForSwap=1
class DisplayConnection:
def __init__(self,qcm,serverName,port,msgHandler):
self.msgHandler = msgHandler
gameServerTimeoutMs = base.config.GetInt(
"game-server-timeout-ms", 20000)
# A big old 20 second timeout.
self.tcpConn = qcm.openTCPClientConnection(
serverName, port, gameServerTimeoutMs)
# Test for bad connection
if self.tcpConn == None:
return None
else:
self.tcpConn.setNoDelay(1)
self.qcr=QueuedConnectionReader(qcm, 0)
self.qcr.addConnection(self.tcpConn)
self.cw=ConnectionWriter(qcm, 0)
def sendCamOffset(self,xyz,hpr):
ClusterClient.notify.debug("send cam offset...")
ClusterClient.notify.debug( ("packet %d xyz,hpr=%f %f %f %f %f %f" %
(self.msgHandler.packetNumber,xyz[0],xyz[1],xyz[2],
hpr[0],hpr[1],hpr[2])) )
datagram = self.msgHandler.makeCamOffsetDatagram(xyz, hpr)
self.cw.send(datagram, self.tcpConn)
def sendCamFrustum(self,focalLength, filmSize, filmOffset):
ClusterClient.notify.info("send cam frustum...")
ClusterClient.notify.info(
(("packet %d" % self.msgHandler.packetNumber) +
(" fl, fs, fo=%0.3f, (%0.3f, %0.3f), (%0.3f, %0.3f)" %
(focalLength, filmSize[0], filmSize[1],
filmOffset[0], filmOffset[1])))
)
datagram = self.msgHandler.makeCamFrustumDatagram(
focalLength, filmSize, filmOffset)
self.cw.send(datagram, self.tcpConn)
def sendMoveCam(self,xyz,hpr):
ClusterClient.notify.debug("send cam move...")
ClusterClient.notify.debug( ("packet %d xyz,hpr=%f %f %f %f %f %f" %
(self.msgHandler.packetNumber,xyz[0],xyz[1],xyz[2],
hpr[0],hpr[1],hpr[2])) )
datagram = self.msgHandler.makeCamMovementDatagram(xyz, hpr)
self.cw.send(datagram, self.tcpConn)
# the following should only be called by a synchronized cluster manger
def getSwapReady(self):
while 1:
(datagram, dgi, type) = self.msgHandler.blockingRead(self.qcr)
if type == CLUSTER_SWAP_READY:
break
else:
self.notify.warning('was expecting SWAP_READY, got %d' % type)
# the following should only be called by a synchronized cluster manger
def sendSwapNow(self):
ClusterClient.notify.debug(
"display connect send swap now, packet %d" %
self.msgHandler.packetNumber)
datagram = self.msgHandler.makeSwapNowDatagram()
self.cw.send(datagram, self.tcpConn)
def sendCommandString(self, commandString):
ClusterClient.notify.debug("send command string: %s" % commandString)
datagram = self.msgHandler.makeCommandStringDatagram(commandString)
self.cw.send(datagram, self.tcpConn)
def sendExit(self):
ClusterClient.notify.debug(
"display connect send exit, packet %d" %
self.msgHandler.packetNumber)
datagram = self.msgHandler.makeExitDatagram()
self.cw.send(datagram, self.tcpConn)
class ClusterConfigItem:
def __init__(self, serverConfigName, serverName, port):
self.serverConfigName = serverConfigName
self.serverName = serverName
self.port = port
# Camera Offset
self.xyz = Vec3(0)
self.hpr = Vec3(0)
# Camera Frustum Data
self.fFrustum = 0
self.focalLength = None
self.filmSize = None
self.filmOffset = None
def setCamOffset(self, xyz, hpr):
self.xyz = xyz
self.hpr = hpr
def setCamFrustum(self, focalLength, filmSize, filmOffset):
self.fFrustum = 1
self.focalLength = focalLength
self.filmSize = filmSize
self.filmOffset = filmOffset
def createClusterClient():
# setup camera offsets based on cluster-config
clusterConfig = base.config.GetString('cluster-config', 'single-server')
# No cluster config specified!
if not ClientConfigs.has_key(clusterConfig):
base.notify.warning(
'createClusterClient: %s cluster-config is undefined.' %
clusterConfig)
return None
# Get display config for each server in the cluster
displayConfigs = []
configList = ClientConfigs[clusterConfig]
numConfigs = len(configList)
for i in range(numConfigs):
configData = configList[i]
displayName = configData.get('display name', ('display%d' % i))
displayMode = configData.get('display mode', 'server')
# Init Cam Offset
pos = configData.get('pos', Vec3(0))
hpr = configData.get('hpr', Vec3(0))
# Init Frustum if specified
fl = configData.get('focal length', None)
fs = configData.get('film size', None)
fo = configData.get('film offset', None)
if displayMode == 'client':
base.camera.setPosHpr(pos,hpr)
lens = base.cam.node().getLens()
lens.setFocalLength(fl)
lens.setFilmSize(fs[0], fs[1])
lens.setFilmOffset(fo[0], fo[1])
else:
serverConfigName = 'cluster-server-%s' % displayName
serverString = base.config.GetString(serverConfigName, '')
if serverString == '':
base.notify.warning(
'%s undefined in Configrc: expected by %s display client.'%
(serverConfigName,clusterConfig))
base.notify.warning('%s will not be used.' % serverConfigName)
else:
serverInfo = string.split(serverString)
serverName = serverInfo[0]
if len(serverInfo) > 1:
port = int(serverInfo[1])
else:
# Use default port
port = CLUSTER_PORT
cci = ClusterConfigItem(
serverConfigName,
serverName,
port)
# Init cam offset
cci.setCamOffset(pos, hpr)
# Init frustum if specified
if fl and fs and fo:
cci.setCamFrustum(fl, fs, fo)
displayConfigs.append(cci)
# Create Cluster Managers (opening connections to servers)
# Are the servers going to be synced?
if base.config.GetBool('cluster-sync', 0):
base.win.setSync(1)
return ClusterClientSync(displayConfigs)
else:
return ClusterClient(displayConfigs)

View File

@ -2,144 +2,101 @@ from PandaObject import *
from ClusterClient import *
import string
# this is an array of offsets for the various servers. For example,
# mono-modelcave-pipe0 has one server with both a pos and hpr of 0.
# For mono-modelcave-pipe0, I decided to set the offsets in the
# actual configuration for the display.
# A dictionary of information for various cluster configurations.
# Dictionary is keyed on cluster-config string
# Each dictionary contains a list of display configurations, one for
# each display in the cluster
# Information that can be specified for each display:
# display name: Name of display (used in Configrc to specify server)
# display type: Used to flag client vs. server
# pos: positional offset of display's camera from main cluster group
# hpr: orientation offset of display's camera from main cluster group
# focal length: display's focal length (in mm)
# film size: display's film size (in inches)
# film offset: offset of film back (in inches)
# Note: Note, this overrides offsets specified in DirectCamConfig.py
# For now we only specify frustum for first display region of configuration
# TODO: Need to handle multiple display regions per cluster node and to
# generalize to non cluster situations
# Specify offset from client for each server's camera group
# Note: If server chan-config consists of multiple display regions
# each display region can have an additional offset as specified in
# DirectCamConfig.py
ClientConfigs = {
'mono-modelcave-pipe0': [{'pos' : Vec3(0),
'single-server' : [{'display name' : 'display0',
'pos' : Vec3(0),
'hpr' : Vec3(0)}
],
'single-server' : [{'pos' : Vec3(0),
'hpr' : Vec3(0)}
],
'two-server' : [{'pos' : Vec3(0),
'two-server' : [{'display name' : 'display0',
'pos' : Vec3(0),
'hpr' : Vec3(-60,0,0)},
{'pos' : Vec3(0),
{'display name' : 'display1',
'pos' : Vec3(0),
'hpr' : Vec3(60,0,0)}
],
'cavetest-old' : [{'pos' : Vec3(-0.105, -0.020, 5.000),
'hpr' : Vec3(51.213, 0.000, 0.000),
'focal length' : 0.809,
'film size' : (1.000, 0.831),
'film offset' : (0.000, 0.173),
},
{'pos' : Vec3(-0.105, -0.020, 5.000),
'hpr' : Vec3(-0.370, 0.000, 0.000),
'focal length' : 0.815,
'film size' : (1.000, 0.831),
'film offset' : (0.000, 0.173),
},
{'pos' : Vec3(-0.105, -0.020, 5.000),
'hpr' : Vec3(-51.675, 0.000, 0.000),
'focal length' : 0.820,
'film size' : (1.000, 0.830),
'film offset' : (-0.000, 0.173),
},
{'pos' : Vec3(0.105, -0.020, 5.000),
'hpr' : Vec3(51.675, 0.000, 0.000),
'focal length' : 0.820,
'film size' : (1.000, 0.830),
'film offset' : (0.000, 0.173),
},
],
'cavetest' : [{'pos' : Vec3(-0.105, -0.020, 5.000),
'hpr' : Vec3(51.213, 0.000, 0.000),
'focal length' : 0.809,
'film size' : (1.000, 0.831),
'film offset' : (0.000, 0.173),
},
{'pos' : Vec3(-0.105, -0.020, 5.000),
'hpr' : Vec3(-0.370, 0.000, 0.000),
'focal length' : 0.815,
'film size' : (1.000, 0.831),
'film offset' : (0.000, 0.173),
},
{'pos' : Vec3(-0.105, -0.020, 5.000),
'hpr' : Vec3(-51.675, 0.000, 0.000),
'focal length' : 0.820,
'film size' : (1.000, 0.830),
'film offset' : (-0.000, 0.173),
},
{'pos' : Vec3(0.105, -0.020, 5.000),
'hpr' : Vec3(51.675, 0.000, 0.000),
'focal length' : 0.820,
'film size' : (1.000, 0.830),
'film offset' : (0.000, 0.173),
},
{'pos' : Vec3(0.105, -0.020, 5.000),
'hpr' : Vec3(0.370, 0.000, 0.000),
'focal length' : 0.815,
'film size' : (1.000, 0.831),
'film offset' : (0.000, 0.173),
},
{'pos' : Vec3(0.105, -0.020, 5.000),
'hpr' : Vec3(-51.213, 0.000, 0.000),
'focal length' : 0.809,
'film size' : (1.000, 0.831),
'film offset' : (-0.000, 0.173),
},
'mono-modelcave-pipe0': [{'display name' : 'display0',
'pos' : Vec3(0),
'hpr' : Vec3(0)},
{'display name' : 'display1',
'pos' : Vec3(0),
'hpr' : Vec3(0)}
],
'cavetestsmall' : [{'display name' : 'la',
'pos' : Vec3(-0.105, -0.020, 5.000),
'hpr' : Vec3(51.213, 0.000, 0.000),
'focal length' : 0.809,
'film size' : (1.000, 0.831),
'film offset' : (0.000, 0.173),
},
{'display name' : 'lb',
'display mode' : 'client',
'pos' : Vec3(-0.105, -0.020, 5.000),
'hpr' : Vec3(-0.370, 0.000, 0.000),
'focal length' : 0.815,
'film size' : (1.000, 0.831),
'film offset' : (0.000, 0.173),
},
],
'cavetest' : [{'display name' : 'la',
'pos' : Vec3(-0.105, -0.020, 5.000),
'hpr' : Vec3(51.213, 0.000, 0.000),
'focal length' : 0.809,
'film size' : (1.000, 0.831),
'film offset' : (0.000, 0.173),
},
{'display name' : 'lb',
'display mode' : 'client',
'pos' : Vec3(-0.105, -0.020, 5.000),
'hpr' : Vec3(-0.370, 0.000, 0.000),
'focal length' : 0.815,
'film size' : (1.000, 0.831),
'film offset' : (0.000, 0.173),
},
{'display name' : 'lc',
'pos' : Vec3(-0.105, -0.020, 5.000),
'hpr' : Vec3(-51.675, 0.000, 0.000),
'focal length' : 0.820,
'film size' : (1.000, 0.830),
'film offset' : (-0.000, 0.173),
},
{'display name' : 'ra',
'pos' : Vec3(0.105, -0.020, 5.000),
'hpr' : Vec3(51.675, 0.000, 0.000),
'focal length' : 0.820,
'film size' : (1.000, 0.830),
'film offset' : (0.000, 0.173),
},
{'display name' : 'rb',
'pos' : Vec3(0.105, -0.020, 5.000),
'hpr' : Vec3(0.370, 0.000, 0.000),
'focal length' : 0.815,
'film size' : (1.000, 0.831),
'film offset' : (0.000, 0.173),
},
{'display name' : 'rc',
'pos' : Vec3(0.105, -0.020, 5.000),
'hpr' : Vec3(-51.213, 0.000, 0.000),
'focal length' : 0.809,
'film size' : (1.000, 0.831),
'film offset' : (-0.000, 0.173),
},
],
}
def createClusterManager():
# setup camera offsets based on cluster-config
clusterConfig = base.config.GetString('cluster-config', 'single-server')
# No cluster config specified!
if not ClientConfigs.has_key(clusterConfig):
base.notify.warning(
'display-client flag set, but %s cluster-config is undefined.' %
clusterConfig)
return None
# Get display config for each server in the cluster
displayConfigs = []
configList = ClientConfigs[clusterConfig]
numConfigs = len(configList)
for i in range(1,numConfigs):
configData = configList[i]
serverConfigName = 'display%d' % i
serverString = base.config.GetString(serverConfigName, '')
if serverString == '':
base.notify.warning(
'%s undefined in Configrc: expected by %s display client.' %
(serverConfigName,clusterConfig))
base.notify.warning('%s will not be used.' % serverConfigName)
else:
serverInfo = string.split(serverString)
serverName = serverInfo[0]
if len(serverInfo) > 1:
port = int(serverInfo[1])
else:
# Use default port
port = CLUSTER_PORT
cci = ClusterConfigItem(
serverConfigName,
serverName,
port)
# Init Cam Offset
pos = configData.get('pos', Vec3(0))
hpr = configData.get('hpr', Vec3(0))
cci.setCamOffset(pos, hpr)
# Init Frustum if specified
fl = configData.get('focalLength', None)
fs = configData.get('filmSize', None)
fo = configData.get('filmOffset', None)
if fl and fs and fo:
cci.setCamFrustum(fl, fs, fo)
displayConfigs.append(cci)
# Create Cluster Managers (opening connections to servers)
# Are the servers going to be synced?
synced = base.config.GetBool('sync-display', 0)
if synced:
base.win.setSync(1)
return ClusterManagerSync(displayConfigs)
else:
return ClusterManager(displayConfigs)

View File

@ -1,11 +1,7 @@
"""ClusterMsgs module: Message types for Cluster rendering"""
# This module is intended to supply routines and dataformats common to
# both ClusterClient and ClusterServer. There is a bit of sloppiness
# though. For example:
# This is where datagrams are constructed for sending, but datagrams
# recieved are handled outside of here, after the header (message type
# and number) are read here.
# both ClusterClient and ClusterServer.
from PandaModules import *
import Datagram
@ -24,11 +20,11 @@ CLUSTER_EXIT = 100
#Port number for cluster rendering
CLUSTER_PORT = 1970
class MsgHandler:
"""MsgHandler: wrapper for PC clusters/multi-piping networking"""
class ClusterMsgHandler:
"""ClusterMsgHandler: wrapper for PC clusters/multi-piping networking"""
def __init__(self,packetStart, notify):
# packetStart can be used to distinguish which MsgHandler sends a
# given packet.
# packetStart can be used to distinguish which ClusterMsgHandler
# sends a given packet.
self.packetNumber = packetStart
self.notify = notify
@ -76,7 +72,7 @@ class MsgHandler:
dgi = DatagramIterator(datagram)
number = dgi.getUint32()
type = dgi.getUint8()
self.notify.debug("Packet %d type %d recieved" % (number,type))
self.notify.debug("Packet %d type %d received" % (number,type))
return (dgi,type)
def makeCamOffsetDatagram(self,xyz,hpr):
@ -92,6 +88,16 @@ class MsgHandler:
datagram.addFloat32(hpr[2])
return datagram
def parseCamOffsetDatagram(self, dgi):
x=dgi.getFloat32()
y=dgi.getFloat32()
z=dgi.getFloat32()
h=dgi.getFloat32()
p=dgi.getFloat32()
r=dgi.getFloat32()
self.notify.debug('new offset=%f %f %f %f %f %f' % (x,y,z,h,p,r))
return (x,y,z,h,p,r)
def makeCamFrustumDatagram(self,focalLength, filmSize, filmOffset):
datagram = Datagram.Datagram()
datagram.addUint32(self.packetNumber)
@ -104,7 +110,16 @@ class MsgHandler:
datagram.addFloat32(filmOffset[1])
return datagram
def makeMoveCamDatagram(self,xyz,hpr):
def parseCamFrustumDatagram(self, dgi):
focalLength = dgi.getFloat32()
filmSize = (dgi.getFloat32(), dgi.getFloat32())
filmOffset = (dgi.getFloat32(),dgi.getFloat32())
self.notify.debug('fl, fs, fo=%f, (%f, %f), (%f, %f)' %
(focalLength, filmSize[0], filmSize[1],
filmOffset[0], filmOffset[1]))
return (focalLength, filmSize, filmOffset)
def makeCamMovementDatagram(self,xyz,hpr):
datagram = Datagram.Datagram()
datagram.addUint32(self.packetNumber)
self.packetNumber = self.packetNumber + 1
@ -117,6 +132,17 @@ class MsgHandler:
datagram.addFloat32(hpr[2])
return datagram
def parseCamMovementDatagram(self, dgi):
x=dgi.getFloat32()
y=dgi.getFloat32()
z=dgi.getFloat32()
h=dgi.getFloat32()
p=dgi.getFloat32()
r=dgi.getFloat32()
self.notify.debug((' new position=%f %f %f %f %f %f' %
(x,y,z,h,p,r)))
return (x,y,z,h,p,r)
def makeCommandStringDatagram(self, commandString):
datagram = Datagram.Datagram()
datagram.addUint32(self.packetNumber)
@ -125,6 +151,10 @@ class MsgHandler:
datagram.addString(commandString)
return datagram
def parseCommandStringDatagram(self, dgi):
command = dgi.getString()
return command
def makeSwapNowDatagram(self):
datagram = Datagram.Datagram()
datagram.addUint32(self.packetNumber)

View File

@ -7,10 +7,6 @@ import DirectNotifyGlobal
import DirectObject
import Task
# Cam offset handling is a little sloppy. The problem is that there is a
# single arc used for both movement of the camera, and the offset of the
# group.
# Also, I'm not sure multiple camera-group configurations are working for the
# cluster system.
@ -18,138 +14,120 @@ class ClusterServer(DirectObject.DirectObject):
notify = DirectNotifyGlobal.directNotify.newCategory("ClusterServer")
MSG_NUM = 2000000
def __init__(self,cameraGroup,camera):
def __init__(self,cameraJig,camera):
# Store information about the cluster's camera
self.cameraGroup = cameraGroup
self.cameraJig = cameraJig
self.camera = camera
self.lens = camera.node().getLens()
# Initialize camera offsets
self.posOffset = Vec3(0,0,0)
self.hprOffset = Vec3(0,0,0)
# Create network layer objects
self.lastConnection = None
self.fPosReceived = 0
# Create network layer objects
self.qcm = QueuedConnectionManager()
self.qcl = QueuedConnectionListener(self.qcm, 0)
self.qcr = QueuedConnectionReader(self.qcm, 0)
self.cw = ConnectionWriter(self.qcm,0)
port = base.config.GetInt("cluster-server-port",CLUSTER_PORT)
port = base.config.GetInt('cluster-server-port', CLUSTER_PORT)
self.tcpRendezvous = self.qcm.openTCPServerRendezvous(port, 1)
self.qcl.addConnection(self.tcpRendezvous)
self.msgHandler = MsgHandler(ClusterServer.MSG_NUM,self.notify)
self.msgHandler = ClusterMsgHandler(ClusterServer.MSG_NUM, self.notify)
# Start cluster tasks
self.startListenerPollTask()
self.startReaderPollTask()
def startListenerPollTask(self):
# Run this task near the start of frame, sometime after the dataloop
taskMgr.add(self.listenerPollTask, "serverListenerPollTask",-40)
def listenerPollTask(self, task):
""" Task to listen for a new connection from the client """
# Run this task after the dataloop
if self.qcl.newConnectionAvailable():
print "New connection is available"
self.notify.info("New connection is available")
rendezvous = PointerToConnection()
netAddress = NetAddress()
newConnection = PointerToConnection()
retVal = self.qcl.getNewConnection(rendezvous, netAddress,
newConnection)
if retVal:
if self.qcl.getNewConnection(rendezvous,netAddress,newConnection):
# Crazy dereferencing
newConnection=newConnection.p()
self.qcr.addConnection(newConnection)
print "Got a connection!"
self.lastConnection = newConnection
self.notify.info("Got a connection!")
else:
self.notify.warning(
"getNewConnection returned false")
self.notify.warning("getNewConnection returned false")
return Task.cont
def startReaderPollTask(self):
""" Task to handle datagrams from client """
# Run this task just after the listener poll task and dataloop
# Run this task just after the listener poll task
taskMgr.add(self.readerPollTask, "serverReaderPollTask", -39)
def readerPollTask(self, state):
# Process all available datagrams
""" Non blocking task to read all available datagrams """
while 1:
(datagram, dgi,type) = self.msgHandler.nonBlockingRead(self.qcr)
# Queue is empty, done for now
if type is CLUSTER_NONE:
break
else:
handleDatagram(dgi, type)
# Got a datagram, handle it
self.handleDatagram(dgi, type)
return Task.cont
def handleDatagram(self, dgi, type):
if type == CLUSTER_NONE:
""" Process a datagram depending upon type flag """
if (type == CLUSTER_NONE):
pass
elif type == CLUSTER_EXIT:
elif (type == CLUSTER_EXIT):
import sys
sys.exit()
elif type == CLUSTER_CAM_OFFSET:
elif (type == CLUSTER_CAM_OFFSET):
self.handleCamOffset(dgi)
elif type == CLUSTER_CAM_FRUSTUM:
elif (type == CLUSTER_CAM_FRUSTUM):
self.handleCamFrustum(dgi)
elif type == CLUSTER_CAM_MOVEMENT:
elif (type == CLUSTER_CAM_MOVEMENT):
self.handleCamMovement(dgi)
elif type == CLUSTER_COMMAND_STRING:
elif (type == CLUSTER_COMMAND_STRING):
self.handleCommandString(dgi)
elif (type == CLUSTER_SWAP_READY):
pass
elif (type == CLUSTER_SWAP_NOW):
self.notify.debug('swapping')
base.win.swap()
else:
self.notify.warning("Recieved unknown packet type:" % type)
self.notify.warning("Received unknown packet type:" % type)
return type
# Server specific tasks
def handleCamOffset(self,dgi):
x=dgi.getFloat32()
y=dgi.getFloat32()
z=dgi.getFloat32()
h=dgi.getFloat32()
p=dgi.getFloat32()
r=dgi.getFloat32()
self.notify.debug((' new offset=%f %f %f %f %f %f' %
(x,y,z,h,p,r)))
self.posOffset = Vec3(x,y,z)
self.hprOffset = Vec3(h,p,r)
""" Set offset of camera from cameraJig """
(x,y,z,h,p,r) = self.msgHandler.parseCamOffsetDatagram(dgi)
self.lens.setIodOffset(x)
self.lens.setViewHpr(h,p,r)
def handleCamFrustum(self,dgi):
focalLength=dgi.getFloat32()
filmSize=(dgi.getFloat32(), dgi.getFloat32())
filmOffset=(dgi.getFloat32(),dgi.getFloat32())
self.notify.debug(' fl, fs, fo=%f, (%f, %f), (%f, %f)' %
(focalLength, filmSize[0], filmSize[1],
filmOffset[0], filmOffset[1]))
self.lens.setFocalLength(focalLength)
self.lens.setFilmSize(filmSize[0], filmSize[1])
self.lens.setFilmOffset(filmOffset[0], filmOffset[1])
""" Adjust camera frustum based on parameters sent by client """
(fl,fs,fo) = self.msgHandler.parseCamFrustumDatagram(dgi)
self.lens.setFocalLength(fl)
self.lens.setFilmSize(fs[0], fs[1])
self.lens.setFilmOffset(fo[0], fo[1])
def handleCamMovement(self,dgi):
x=dgi.getFloat32()
y=dgi.getFloat32()
z=dgi.getFloat32()
h=dgi.getFloat32()
p=dgi.getFloat32()
r=dgi.getFloat32()
self.notify.debug((' new position=%f %f %f %f %f %f' %
(x,y,z,h,p,r)))
finalX = x + self.posOffset[0]
finalY = y + self.posOffset[1]
finalZ = z + self.posOffset[2]
finalH = h + self.hprOffset[0]
finalP = p + self.hprOffset[1]
finalR = r + self.hprOffset[2]
self.cameraGroup.setPosHpr(render,finalX,finalY,finalZ,
finalH,finalP,finalR)
""" Update cameraJig position to reflect latest position """
(x,y,z,h,p,r) = self.msgHandler.parseCamMovementDatagram(dgi)
self.cameraJig.setPosHpr(render,x,y,z,h,p,r)
self.fPosReceived = 1
def handleCommandString(self, dgi):
command = dgi.getString()
exec( command, globals())
""" Handle arbitrary command string from client """
command = self.msgHandler.parseCommandStringDatagram(dgi)
exec( command, globals() )
class ClusterServerSync(ClusterServer):
def __init__(self,cameraGroup,camera):
def __init__(self,cameraJig,camera):
self.notify.info('starting ClusterServerSync')
self.posRecieved = 0
ClusterServer.__init__(self,cameraGroup,camera)
ClusterServer.__init__(self,cameraJig,camera)
self.startSwapCoordinator()
return None
def readerPollTask(self, task):
if self.lastConnection is None:
@ -158,42 +136,32 @@ class ClusterServerSync(ClusterServer):
# Process datagrams till you get a postion update
type = CLUSTER_NONE
while type != CLUSTER_CAM_MOVEMENT:
# Block until you get a new datagram
(datagram,dgi,type) = self.msgHandler.blockingRead(self.qcr)
if type == CLUSTER_CAM_MOVEMENT:
# Move camera
self.handleCamMovement(dgi)
# Set flag for swap coordinator
self.posRecieved = 1
elif type == CLUSTER_CAM_OFFSET:
# Update camera offset
self.handleCamOffset(dgi)
elif type == CLUSTER_COMMAND_STRING:
# Got a command, execute it
self.handleCommandString(dgi)
# Process datagram
self.handleDatagram(dgi,type)
return Task.cont
def sendSwapReady(self):
self.notify.debug( ('send swap ready packet %d' %
self.msgHandler.packetNumber ) )
self.notify.debug(
'send swap ready packet %d' % self.msgHandler.packetNumber)
datagram = self.msgHandler.makeSwapReadyDatagram()
self.cw.send(datagram, self.lastConnection)
def startSwapCoordinator(self):
taskMgr.add(self.swapCoordinatorTask, "serverSwapCoordinator", 51)
return None
def swapCoordinatorTask(self, task):
if self.posRecieved:
self.posRecieved = 0
if self.fPosReceived:
self.fPosReceived = 0
# Alert client that this server is ready to swap
self.sendSwapReady()
# Wait for swap command (processing any intermediate datagrams)
while 1:
(datagram,dgi,type) = self.msgHandler.blockingRead(self.qcr)
self.handleDatagram(dgi,type)
if type == CLUSTER_SWAP_NOW:
self.notify.debug('swapping')
base.win.swap()
break
else:
self.handleDatagram(dgi,type)
return Task.cont

View File

@ -9,7 +9,6 @@ class DirectLight(NodePath):
NodePath.__init__(self)
# Record light and name
self.light = light
self.name = light.getName()
# Upcast the light object to its node base pointer
if isinstance(light, Spotlight):
@ -25,7 +24,8 @@ class DirectLight(NodePath):
pass
def getName(self):
return self.name
return self.light.getName()
def getLight(self):
return self.light

View File

@ -113,6 +113,9 @@ class DirectManipulationControl(PandaObject):
self.fScaling = 0
direct.selected.highlightAll()
self.objectHandles.showAllHandles()
if direct.clusterMode == 'client':
direct.cluster.cmd(
'direct.manipulationControl.objectHandles.showAllHandles()')
self.objectHandles.hideGuides()
# Restart followSelectedNodePath task
self.spawnFollowSelectedNodePathTask()
@ -177,6 +180,11 @@ class DirectManipulationControl(PandaObject):
self.objectHandles.showGuides()
self.objectHandles.hideAllHandles()
self.objectHandles.showHandle(self.constraint)
if direct.clusterMode == 'client':
oh = 'direct.manipulationControl.objectHandles'
direct.cluster.cmd(oh + '.showGuides()', 0)
direct.cluster.cmd(oh + '.hideAllHandles()', 0)
direct.cluster.cmd(oh + ('.showHandle("%s")'% self.constraint))
# Record relationship between selected nodes and widget
direct.selected.getWrtAll()
# hide the bbox of the selected objects during interaction

View File

@ -13,8 +13,6 @@ class DirectNodePath(NodePath):
# Initialize the superclass
NodePath.__init__(self)
self.assign(nodePath)
# Get a reasonable name
self.name = self.getName()
# Create a bounding box
self.bbox = DirectBoundingBox(self)
center = self.bbox.getCenter()
@ -45,10 +43,6 @@ class DirectNodePath(NodePath):
def getMax(self):
return self.bbox.getMax()
def __repr__(self):
return ('NodePath:\t%s\n' % self.name)
class SelectedNodePaths(PandaObject):
def __init__(self):
self.reset()
@ -91,6 +85,9 @@ class SelectedNodePaths(PandaObject):
self.selectedDict[dnp.id()] = dnp
# And update last
__builtin__.last = self.last = dnp
# Update cluster servers if this is a cluster client
if direct.clusterMode == 'client':
direct.cluster.selectNodePath(dnp)
return dnp
def deselect(self, nodePath):
@ -109,6 +106,9 @@ class SelectedNodePaths(PandaObject):
self.deselectedDict[id] = dnp
# Send a message
messenger.send('DIRECT_deselectedNodePath', [dnp])
# Update cluster servers if this is a cluster client
if direct.clusterMode == 'client':
direct.cluster.deselectNodePath(dnp)
return dnp
def getSelectedAsList(self):
@ -173,6 +173,17 @@ class SelectedNodePaths(PandaObject):
def moveWrtWidgetAll(self):
self.forEachSelectedNodePathDo(self.moveWrtWidget)
# Update cluster if current display is a cluster client
if (direct.clusterMode == 'client') and (last is not None):
pos = Point3(0)
hpr = VBase3(0)
scale = VBase3(1)
decomposeMatrix(last.getMat(), scale, hpr, pos)
direct.cluster.cmd(
'last.setPosHprScale(%f,%f,%f,%f,%f,%f,%f,%f,%f)' %
(pos[0], pos[1], pos[2],
hpr[0], hpr[1], hpr[2],
scale[0], scale[1], scale[2]))
def moveWrtWidget(self, nodePath):
nodePath.setMat(direct.widget, nodePath.mDnp2Widget)

View File

@ -7,7 +7,6 @@ from DirectGrid import *
from DirectGeometry import *
from DirectLights import *
from DirectSessionPanel import *
from DirectCamConfig import *
from ClusterClient import *
from ClusterServer import *
from tkSimpleDialog import askstring
@ -241,7 +240,7 @@ class DirectSession(PandaObject):
self.oobeVis = loader.loadModelOnce('models/misc/camera')
if self.oobeVis:
self.oobeVis.arc().setFinal(1)
self.oobeVis.node().setFinal(1)
if self.oobeMode:
# Position a target point to lerp the oobe camera to
@ -259,8 +258,7 @@ class DirectSession(PandaObject):
# Remove any transformation on the models arc
self.oobeVis.clearMat()
# Make oobeCamera be a sibling of wherever camera is now.
cameraParent = NodePath(self.camera)
cameraParent.shorten(1)
cameraParent = self.camera.getParent()
# Prepare oobe camera
self.oobeCamera.reparentTo(cameraParent)
self.oobeCamera.iPosHpr(self.trueCamera)
@ -396,6 +394,12 @@ class DirectSession(PandaObject):
self.undo()
elif (input == ']') or (input == '}'):
self.redo()
#Pass along certain events if this display is a cluster client
if self.clusterMode == 'client':
if input in ('v','b','l','p', 'r', 'shift-r', 's', 't',
'shift-a', 'w'):
self.cluster.cmd('messenger.send("%s")' % input,0)
def select(self, nodePath, fMultiSelect = 0, fResetAncestry = 1):
dnp = self.selected.select(nodePath, fMultiSelect)
@ -409,7 +413,7 @@ class DirectSession(PandaObject):
# Update the selectedNPReadout
self.selectedNPReadout.reparentTo(aspect2d)
self.selectedNPReadout.setText(
'Selected:' + dnp.name)
'Selected:' + dnp.getName())
# Show the manipulation widget
self.widget.showWidget()
# Update camera controls coa to this point