From 55a3ae9bff8cd8c6725e2efb4237907cee91811e Mon Sep 17 00:00:00 2001 From: Mark Mine Date: Mon, 8 Apr 2002 05:59:20 +0000 Subject: [PATCH] Synced version of cluster code with select/deselect and limited command support --- direct/src/cluster/ClusterClient.py | 302 ++++++++++++------- direct/src/cluster/ClusterConfig.py | 219 ++++++-------- direct/src/cluster/ClusterMsgs.py | 52 +++- direct/src/cluster/ClusterServer.py | 150 ++++----- direct/src/directtools/DirectLights.py | 4 +- direct/src/directtools/DirectManipulation.py | 8 + direct/src/directtools/DirectSelection.py | 23 +- direct/src/directtools/DirectSession.py | 14 +- 8 files changed, 415 insertions(+), 357 deletions(-) diff --git a/direct/src/cluster/ClusterClient.py b/direct/src/cluster/ClusterClient.py index 532da0b55f..9d526b63b1 100644 --- a/direct/src/cluster/ClusterClient.py +++ b/direct/src/cluster/ClusterClient.py @@ -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) + + diff --git a/direct/src/cluster/ClusterConfig.py b/direct/src/cluster/ClusterConfig.py index 306a40e3ae..c36cf398e9 100644 --- a/direct/src/cluster/ClusterConfig.py +++ b/direct/src/cluster/ClusterConfig.py @@ -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) - - - diff --git a/direct/src/cluster/ClusterMsgs.py b/direct/src/cluster/ClusterMsgs.py index 30231c4851..d258546c27 100644 --- a/direct/src/cluster/ClusterMsgs.py +++ b/direct/src/cluster/ClusterMsgs.py @@ -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) diff --git a/direct/src/cluster/ClusterServer.py b/direct/src/cluster/ClusterServer.py index cfee4c8258..d2d7e05f27 100644 --- a/direct/src/cluster/ClusterServer.py +++ b/direct/src/cluster/ClusterServer.py @@ -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 diff --git a/direct/src/directtools/DirectLights.py b/direct/src/directtools/DirectLights.py index 539c15b837..8c92f3b661 100644 --- a/direct/src/directtools/DirectLights.py +++ b/direct/src/directtools/DirectLights.py @@ -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 diff --git a/direct/src/directtools/DirectManipulation.py b/direct/src/directtools/DirectManipulation.py index 2ac9687d1b..0db07eaf6c 100644 --- a/direct/src/directtools/DirectManipulation.py +++ b/direct/src/directtools/DirectManipulation.py @@ -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 diff --git a/direct/src/directtools/DirectSelection.py b/direct/src/directtools/DirectSelection.py index 92c7616970..ba6e7511d1 100644 --- a/direct/src/directtools/DirectSelection.py +++ b/direct/src/directtools/DirectSelection.py @@ -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) diff --git a/direct/src/directtools/DirectSession.py b/direct/src/directtools/DirectSession.py index af2c7e7072..f63a7da10a 100644 --- a/direct/src/directtools/DirectSession.py +++ b/direct/src/directtools/DirectSession.py @@ -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