Browse Source

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

Mark Mine 24 years ago
parent
commit
55a3ae9bff

+ 191 - 111
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)
+    
+    

+ 88 - 131
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)
-    
-
-

+ 41 - 11
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)

+ 59 - 91
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
 
 

+ 2 - 2
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
 

+ 8 - 0
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

+ 17 - 6
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)

+ 9 - 5
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