ClusterClient.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. """ClusterClient: Master for multi-piping or PC clusters."""
  2. from panda3d.core import (
  3. ClockObject,
  4. ConnectionWriter,
  5. Point3,
  6. QueuedConnectionManager,
  7. QueuedConnectionReader,
  8. VBase3,
  9. Vec3,
  10. decomposeMatrix,
  11. )
  12. from .ClusterMsgs import (
  13. CLUSTER_DAEMON_PORT,
  14. CLUSTER_NAMED_MOVEMENT_DONE,
  15. CLUSTER_NAMED_OBJECT_MOVEMENT,
  16. CLUSTER_NONE,
  17. CLUSTER_SERVER_PORT,
  18. CLUSTER_SWAP_READY,
  19. SERVER_STARTUP_STRING,
  20. ClusterMsgHandler,
  21. )
  22. from .ClusterConfig import ClientConfigs
  23. from direct.directnotify import DirectNotifyGlobal
  24. from direct.showbase import DirectObject
  25. from direct.task import Task
  26. from direct.task.TaskManagerGlobal import taskMgr
  27. import os
  28. class ClusterClient(DirectObject.DirectObject):
  29. notify = DirectNotifyGlobal.directNotify.newCategory("ClusterClient")
  30. MGR_NUM = 1000000
  31. def __init__(self, configList, clusterSyncFlag):
  32. # Set name so cluster __call__ function can be used in Intervals
  33. self.__name__ = 'cluster'
  34. # First start up servers using direct daemon
  35. # What is the name of the client machine?
  36. clusterClientDaemonHost = base.config.GetString(
  37. 'cluster-client-daemon', 'None')
  38. if clusterClientDaemonHost == 'None':
  39. clusterClientDaemonHost = os.popen('uname -n').read()
  40. clusterClientDaemonHost = clusterClientDaemonHost.replace('\n', '')
  41. # What daemon port are we using to communicate between client/servers
  42. clusterClientDaemonPort = base.config.GetInt(
  43. 'cluster-client-daemon-port', CLUSTER_DAEMON_PORT)
  44. # Create a daemon
  45. self.daemon = DirectD()
  46. # Start listening for the response
  47. self.daemon.listenTo(clusterClientDaemonPort)
  48. # Contact server daemons and start up remote server application
  49. for serverConfig in configList:
  50. # First kill existing application
  51. self.daemon.tellServer(serverConfig.serverName,
  52. serverConfig.serverDaemonPort,
  53. 'ka')
  54. # Now start up new application
  55. serverCommand = (SERVER_STARTUP_STRING %
  56. (serverConfig.serverMsgPort,
  57. clusterSyncFlag,
  58. clusterClientDaemonHost,
  59. clusterClientDaemonPort))
  60. self.daemon.tellServer(serverConfig.serverName,
  61. serverConfig.serverDaemonPort,
  62. serverCommand)
  63. print('Begin waitForServers')
  64. if not self.daemon.waitForServers(len(configList)):
  65. print('Cluster Client, no response from servers')
  66. print('End waitForServers')
  67. self.qcm=QueuedConnectionManager()
  68. self.serverList = []
  69. self.serverQueues = []
  70. self.msgHandler = ClusterMsgHandler(ClusterClient.MGR_NUM, self.notify)
  71. # A dictionary of objects that can be accessed by name
  72. self.objectMappings = {}
  73. self.objectHasColor = {}
  74. # a dictionary of name objects and the corresponding names of
  75. # objects they are to control on the server side
  76. self.controlMappings = {}
  77. self.controlOffsets = {}
  78. self.taggedObjects = {}
  79. self.controlPriorities = {}
  80. self.sortedControlMappings = []
  81. for serverConfig in configList:
  82. server = DisplayConnection(
  83. self.qcm, serverConfig.serverName,
  84. serverConfig.serverMsgPort, self.msgHandler)
  85. if server is None:
  86. self.notify.error('Could not open %s on %s port %d' %
  87. (serverConfig.serverConfigName,
  88. serverConfig.serverName,
  89. serverConfig.serverMsgPort))
  90. else:
  91. self.notify.debug('send cam pos')
  92. #server.sendMoveCam(Point3(0), Vec3(0))
  93. self.notify.debug('send cam offset')
  94. server.sendCamOffset(serverConfig.xyz, serverConfig.hpr)
  95. if serverConfig.fFrustum:
  96. self.notify.debug('send cam frustum')
  97. server.sendCamFrustum(serverConfig.focalLength,
  98. serverConfig.filmSize,
  99. serverConfig.filmOffset)
  100. self.serverList.append(server)
  101. self.serverQueues.append([])
  102. self.notify.debug('pre startTimeTask')
  103. self.startSynchronizeTimeTask()
  104. self.notify.debug('pre startMoveCam')
  105. self.startMoveCamTask()
  106. self.notify.debug('post startMoveCam')
  107. self.startMoveSelectedTask()
  108. def startReaderPollTask(self):
  109. """ Task to handle datagrams from server """
  110. # Run this task just after the listener poll task
  111. taskMgr.add(self._readerPollTask, "clientReaderPollTask", -39)
  112. def _readerPollTask(self, state):
  113. """ Non blocking task to read all available datagrams """
  114. for i in range(len(self.serverList)):
  115. server = self.serverList[i]
  116. datagrams = server.poll()
  117. for data in datagrams:
  118. self.handleDatagram(data[0],data[1],i)
  119. return Task.cont
  120. def startControlObjectTask(self):
  121. self.notify.debug("moving control objects")
  122. taskMgr.add(self.controlObjectTask,"controlObjectTask",50)
  123. def startSynchronizeTimeTask(self):
  124. self.notify.debug('broadcasting frame time')
  125. taskMgr.add(self.synchronizeTimeTask, "synchronizeTimeTask", -40)
  126. def synchronizeTimeTask(self, task):
  127. clock = ClockObject.getGlobalClock()
  128. frameCount = clock.getFrameCount()
  129. frameTime = clock.getFrameTime()
  130. dt = clock.dt
  131. for server in self.serverList:
  132. server.sendTimeData(frameCount, frameTime, dt)
  133. return Task.cont
  134. def startMoveCamTask(self):
  135. self.notify.debug('adding move cam')
  136. taskMgr.add(self.moveCameraTask, "moveCamTask", 49)
  137. def controlObjectTask(self, task):
  138. for pair in self.sortedControlMappings:
  139. object = pair[1]
  140. name = self.controlMappings[object][0]
  141. serverList = self.controlMappings[object][1]
  142. if object in self.objectMappings:
  143. self.moveObject(self.objectMappings[object],name,serverList,
  144. self.controlOffsets[object], self.objectHasColor[object])
  145. self.sendNamedMovementDone()
  146. return Task.cont
  147. def sendNamedMovementDone(self, serverList = None):
  148. if serverList is None:
  149. serverList = range(len(self.serverList))
  150. for server in serverList:
  151. self.serverList[server].sendNamedMovementDone()
  152. def redoSortedPriorities(self):
  153. self.sortedControlMappings = sorted(
  154. [self.controlPriorities[key], key] for key in self.controlMappings
  155. )
  156. def moveObject(self, nodePath, object, serverList, offset, hasColor = True):
  157. self.notify.debug('moving object '+object)
  158. xyz = nodePath.getPos(render) + offset
  159. hpr = nodePath.getHpr(render)
  160. scale = nodePath.getScale(render)
  161. hidden = nodePath.isHidden()
  162. if hasColor:
  163. color = nodePath.getColor()
  164. else:
  165. color = [1,1,1,1]
  166. for server in serverList:
  167. self.serverList[server].sendMoveNamedObject(xyz,hpr,scale,color,hidden,object)
  168. def moveCameraTask(self, task):
  169. self.moveCamera(
  170. base.camera.getPos(render),
  171. base.camera.getHpr(render))
  172. return Task.cont
  173. def moveCamera(self, xyz, hpr):
  174. self.notify.debug('moving unsynced camera')
  175. for server in self.serverList:
  176. server.sendMoveCam(xyz, hpr)
  177. def startMoveSelectedTask(self):
  178. taskMgr.add(self.moveSelectedTask, "moveSelectedTask", 48)
  179. def moveSelectedTask(self, state):
  180. # Update cluster if current display is a cluster client
  181. if last is not None:
  182. self.notify.debug('moving selected node path')
  183. xyz = Point3(0)
  184. hpr = VBase3(0)
  185. scale = VBase3(1)
  186. decomposeMatrix(last.getMat(), scale, hpr, xyz)
  187. for server in self.serverList:
  188. server.sendMoveSelected(xyz, hpr, scale)
  189. return Task.cont
  190. def addNamedObjectMapping(self, object, name, hasColor = True):
  191. if name not in self.objectMappings:
  192. self.objectMappings[name] = object
  193. self.objectHasColor[name] = hasColor
  194. else:
  195. self.notify.debug('attempt to add duplicate named object: '+name)
  196. def removeObjectMapping(self,name):
  197. if name in self.objectMappings:
  198. self.objectMappings.pop(name)
  199. def addControlMapping(self, objectName, controlledName, serverList = None,
  200. offset = None, priority = 0):
  201. if objectName not in self.controlMappings:
  202. if serverList is None:
  203. serverList = range(len(self.serverList))
  204. if offset is None:
  205. offset = Vec3(0,0,0)
  206. self.controlMappings[objectName] = [controlledName,serverList]
  207. self.controlOffsets[objectName] = offset
  208. self.controlPriorities[objectName] = priority
  209. else:
  210. oldList = self.controlMappings[objectName]
  211. mergedList = []
  212. for item in oldList:
  213. mergedList.append(item)
  214. for item in serverList:
  215. if item not in mergedList:
  216. mergedList.append(item)
  217. self.redoSortedPriorities()
  218. #self.notify.debug('attempt to add duplicate controlled object: '+name)
  219. def setControlMappingOffset(self, objectName, offset):
  220. if objectName in self.controlMappings:
  221. self.controlOffsets[objectName] = offset
  222. def removeControlMapping(self, name, serverList = None):
  223. if name in self.controlMappings:
  224. if serverList is None:
  225. self.controlMappings.pop(name)
  226. self.controlPriorities.pop(name)
  227. else:
  228. oldList = self.controlMappings[key][1]
  229. newList = []
  230. for server in oldList:
  231. if server not in serverList:
  232. newList.append(server)
  233. self.controlMappings[key][1] = newList
  234. if len(newList) == 0:
  235. self.controlMappings.pop(name)
  236. self.controlPriorities.pop(name)
  237. self.redoSortedPriorities()
  238. def getNodePathFindCmd(self, nodePath):
  239. pathString = repr(nodePath)
  240. index = pathString.find('/')
  241. if index != -1:
  242. rootName = pathString[:index]
  243. searchString = pathString[index+1:]
  244. return rootName + ('.find("%s")' % searchString)
  245. else:
  246. return rootName
  247. def getNodePathName(self, nodePath):
  248. pathString = repr(nodePath)
  249. index = pathString.find('/')
  250. if index != -1:
  251. name = pathString[index+1:]
  252. return name
  253. else:
  254. return pathString
  255. def addObjectTag(self,object,selectFunction,deselectFunction,selectArgs,deselectArgs):
  256. newTag = {}
  257. newTag["selectFunction"] = selectFunction
  258. newTag["selectArgs"] = selectArgs
  259. newTag["deselectFunction"] = deselectFunction
  260. newTag["deselectArgs"] = deselectArgs
  261. self.taggedObjects[object] = newTag
  262. def removeObjectTag(self,object):
  263. self.taggedObjects.pop(object)
  264. def selectNodePath(self, nodePath):
  265. name = self.getNodePathName(nodePath)
  266. if name in self.taggedObjects:
  267. taskMgr.remove("moveSelectedTask")
  268. tag = self.taggedObjects[name]
  269. function = tag["selectFunction"]
  270. args = tag["selectArgs"]
  271. if function is not None:
  272. function(*args)
  273. else:
  274. self(self.getNodePathFindCmd(nodePath) + '.select()', 0)
  275. def deselectNodePath(self, nodePath):
  276. name = self.getNodePathName(nodePath)
  277. if name in self.taggedObjects:
  278. tag = self.taggedObjects[name]
  279. function = tag["deselectFunction"]
  280. args = tag["deselectArgs"]
  281. if function is not None:
  282. function(*args)
  283. self.startMoveSelectedTask()
  284. self(self.getNodePathFindCmd(nodePath) + '.deselect()', 0)
  285. def sendCamFrustum(self, focalLength, filmSize, filmOffset, indexList=[]):
  286. if indexList:
  287. serverList = [self.serverList[i] for i in indexList]
  288. else:
  289. serverList = self.serverList
  290. for server in serverList:
  291. self.notify.debug('updating camera frustum')
  292. server.sendCamFrustum(focalLength, filmSize, filmOffset)
  293. def loadModel(self, nodePath):
  294. pass
  295. def __call__(self, commandString, fLocally = 1, serverList = []):
  296. # Execute remotely
  297. if serverList:
  298. # Passed in list of servers
  299. for serverNum in serverList:
  300. self.serverList[serverNum].sendCommandString(commandString)
  301. else:
  302. # All servers
  303. for server in self.serverList:
  304. server.sendCommandString(commandString)
  305. if fLocally:
  306. # Execute locally
  307. exec(commandString, __builtins__)
  308. def handleDatagram(self,dgi,type,server):
  309. if type == CLUSTER_NONE:
  310. pass
  311. elif type == CLUSTER_NAMED_OBJECT_MOVEMENT:
  312. self.serverQueues[server].append(self.msgHandler.parseNamedMovementDatagram(dgi))
  313. #self.handleNamedMovement(dgi)
  314. # when we recieve a 'named movement done' packet from a server we handle
  315. # all of its messages
  316. elif type == CLUSTER_NAMED_MOVEMENT_DONE:
  317. self.handleMessageQueue(server)
  318. else:
  319. self.notify.warning("Received unsupported packet type:" % type)
  320. return type
  321. def handleMessageQueue(self,server):
  322. queue = self.serverQueues[server]
  323. # handle all messages in the queue
  324. for data in queue:
  325. #print dgi
  326. self.handleNamedMovement(data)
  327. # clear the queue
  328. self.serverQueues[server] = []
  329. def handleNamedMovement(self, data):
  330. """ Update cameraJig position to reflect latest position """
  331. (name,x, y, z, h, p, r, sx, sy, sz,red,g,b,a, hidden) = data
  332. #print "name"
  333. #if name == "camNode":
  334. # print x,y,z,h,p,r, sx, sy, sz,red,g,b,a, hidden
  335. if name in self.objectMappings:
  336. self.objectMappings[name].setPosHpr(render, x, y, z, h, p, r)
  337. self.objectMappings[name].setScale(render,sx,sy,sz)
  338. if self.objectHasColor[name]:
  339. self.objectMappings[name].setColor(red,g,b,a)
  340. if hidden:
  341. self.objectMappings[name].hide()
  342. else:
  343. self.objectMappings[name].show()
  344. else:
  345. self.notify.debug("recieved unknown named object command: "+name)
  346. def exit(self):
  347. # Execute remotely
  348. for server in self.serverList:
  349. server.sendExit()
  350. # Execute locally
  351. import sys
  352. sys.exit()
  353. class ClusterClientSync(ClusterClient):
  354. def __init__(self, configList, clusterSyncFlag):
  355. ClusterClient.__init__(self, configList, clusterSyncFlag)
  356. #I probably don't need this
  357. self.waitForSwap = 0
  358. self.ready = 0
  359. print("creating synced client")
  360. self.startSwapCoordinatorTask()
  361. def startSwapCoordinatorTask(self):
  362. taskMgr.add(self.swapCoordinator, "clientSwapCoordinator", 51)
  363. def swapCoordinator(self, task):
  364. self.ready = 1
  365. if self.waitForSwap:
  366. self.waitForSwap=0
  367. self.notify.debug(
  368. "START get swaps----------------------------------")
  369. for server in self.serverList:
  370. server.getSwapReady()
  371. self.notify.debug(
  372. "----------------START swap now--------------------")
  373. for server in self.serverList:
  374. server.sendSwapNow()
  375. self.notify.debug(
  376. "------------------------------START swap----------")
  377. base.graphicsEngine.flipFrame()
  378. self.notify.debug(
  379. "------------------------------------------END swap")
  380. #print "syncing"
  381. return Task.cont
  382. def moveCamera(self, xyz, hpr):
  383. if self.ready:
  384. self.notify.debug('moving synced camera')
  385. ClusterClient.moveCamera(self, xyz, hpr)
  386. self.waitForSwap=1
  387. class DisplayConnection:
  388. def __init__(self, qcm, serverName, port, msgHandler):
  389. self.msgHandler = msgHandler
  390. gameServerTimeoutMs = base.config.GetInt(
  391. "cluster-server-timeout-ms", 300000)
  392. # A giant 300 second timeout.
  393. self.tcpConn = qcm.openTCPClientConnection(
  394. serverName, port, gameServerTimeoutMs)
  395. # Test for bad connection
  396. if self.tcpConn is None:
  397. return None
  398. else:
  399. self.tcpConn.setNoDelay(1)
  400. self.qcr=QueuedConnectionReader(qcm, 0)
  401. self.qcr.addConnection(self.tcpConn)
  402. self.cw=ConnectionWriter(qcm, 0)
  403. def poll(self):
  404. """ Non blocking task to read all available datagrams """
  405. dataGrams = []
  406. while 1:
  407. (datagram, dgi, type) = self.msgHandler.nonBlockingRead(self.qcr)
  408. # Queue is empty, done for now
  409. if type is CLUSTER_NONE:
  410. break
  411. else:
  412. # Got a datagram, add it to the list
  413. dataGrams.append([dgi, type, datagram])
  414. return dataGrams
  415. def sendCamOffset(self, xyz, hpr):
  416. ClusterClient.notify.debug("send cam offset...")
  417. ClusterClient.notify.debug(("packet %d xyz, hpr=%f %f %f %f %f %f" %
  418. (self.msgHandler.packetNumber, xyz[0], xyz[1], xyz[2],
  419. hpr[0], hpr[1], hpr[2])))
  420. datagram = self.msgHandler.makeCamOffsetDatagram(xyz, hpr)
  421. self.cw.send(datagram, self.tcpConn)
  422. def sendCamFrustum(self, focalLength, filmSize, filmOffset):
  423. ClusterClient.notify.info("send cam frustum...")
  424. ClusterClient.notify.info(
  425. (("packet %d" % self.msgHandler.packetNumber) +
  426. (" fl, fs, fo=%0.3f, (%0.3f, %0.3f), (%0.3f, %0.3f)" %
  427. (focalLength, filmSize[0], filmSize[1],
  428. filmOffset[0], filmOffset[1])))
  429. )
  430. datagram = self.msgHandler.makeCamFrustumDatagram(
  431. focalLength, filmSize, filmOffset)
  432. self.cw.send(datagram, self.tcpConn)
  433. def sendNamedMovementDone(self):
  434. datagram = self.msgHandler.makeNamedMovementDone()
  435. self.cw.send(datagram, self.tcpConn)
  436. def sendMoveNamedObject(self, xyz, hpr, scale, color, hidden, name):
  437. ClusterClient.notify.debug("send named object move...")
  438. ClusterClient.notify.debug(("packet %d xyz, hpr=%f %f %f %f %f %f" %
  439. (self.msgHandler.packetNumber, xyz[0], xyz[1], xyz[2],
  440. hpr[0], hpr[1], hpr[2])))
  441. datagram = self.msgHandler.makeNamedObjectMovementDatagram(xyz,hpr,scale,
  442. color,hidden,
  443. name)
  444. self.cw.send(datagram, self.tcpConn)
  445. def sendMoveCam(self, xyz, hpr):
  446. ClusterClient.notify.debug("send cam move...")
  447. ClusterClient.notify.debug(("packet %d xyz, hpr=%f %f %f %f %f %f" %
  448. (self.msgHandler.packetNumber, xyz[0], xyz[1], xyz[2],
  449. hpr[0], hpr[1], hpr[2])))
  450. datagram = self.msgHandler.makeCamMovementDatagram(xyz, hpr)
  451. self.cw.send(datagram, self.tcpConn)
  452. def sendMoveSelected(self, xyz, hpr, scale):
  453. ClusterClient.notify.debug("send move selected...")
  454. ClusterClient.notify.debug(
  455. "packet %d xyz, hpr=%f %f %f %f %f %f %f %f %f" %
  456. (self.msgHandler.packetNumber,
  457. xyz[0], xyz[1], xyz[2],
  458. hpr[0], hpr[1], hpr[2],
  459. scale[0], scale[1], scale[2]))
  460. datagram = self.msgHandler.makeSelectedMovementDatagram(xyz, hpr, scale)
  461. self.cw.send(datagram, self.tcpConn)
  462. # the following should only be called by a synchronized cluster manger
  463. def getSwapReady(self):
  464. while 1:
  465. (datagram, dgi, type) = self.msgHandler.blockingRead(self.qcr)
  466. if type == CLUSTER_SWAP_READY:
  467. break
  468. else:
  469. self.notify.warning('was expecting SWAP_READY, got %d' % type)
  470. # the following should only be called by a synchronized cluster manger
  471. def sendSwapNow(self):
  472. ClusterClient.notify.debug(
  473. "display connect send swap now, packet %d" %
  474. self.msgHandler.packetNumber)
  475. datagram = self.msgHandler.makeSwapNowDatagram()
  476. self.cw.send(datagram, self.tcpConn)
  477. def sendCommandString(self, commandString):
  478. ClusterClient.notify.debug("send command string: %s" % commandString)
  479. datagram = self.msgHandler.makeCommandStringDatagram(commandString)
  480. self.cw.send(datagram, self.tcpConn)
  481. def sendExit(self):
  482. ClusterClient.notify.debug(
  483. "display connect send exit, packet %d" %
  484. self.msgHandler.packetNumber)
  485. datagram = self.msgHandler.makeExitDatagram()
  486. self.cw.send(datagram, self.tcpConn)
  487. def sendTimeData(self, frameCount, frameTime, dt):
  488. ClusterClient.notify.debug("send time data...")
  489. datagram = self.msgHandler.makeTimeDataDatagram(
  490. frameCount, frameTime, dt)
  491. self.cw.send(datagram, self.tcpConn)
  492. class ClusterConfigItem:
  493. def __init__(self, serverConfigName, serverName,
  494. serverDaemonPort, serverMsgPort):
  495. self.serverConfigName = serverConfigName
  496. self.serverName = serverName
  497. self.serverDaemonPort = serverDaemonPort
  498. self.serverMsgPort = serverMsgPort
  499. # Camera Offset
  500. self.xyz = Vec3(0)
  501. self.hpr = Vec3(0)
  502. # Camera Frustum Data
  503. self.fFrustum = 0
  504. self.focalLength = None
  505. self.filmSize = None
  506. self.filmOffset = None
  507. def setCamOffset(self, xyz, hpr):
  508. self.xyz = xyz
  509. self.hpr = hpr
  510. def setCamFrustum(self, focalLength, filmSize, filmOffset):
  511. self.fFrustum = 1
  512. self.focalLength = focalLength
  513. self.filmSize = filmSize
  514. self.filmOffset = filmOffset
  515. def createClusterClient():
  516. # setup camera offsets based on cluster-config
  517. clusterConfig = base.config.GetString('cluster-config', 'single-server')
  518. # No cluster config specified!
  519. if clusterConfig not in ClientConfigs:
  520. base.notify.warning(
  521. 'createClusterClient: %s cluster-config is undefined.' %
  522. clusterConfig)
  523. return None
  524. # Get display config for each server in the cluster
  525. displayConfigs = []
  526. configList = ClientConfigs[clusterConfig]
  527. numConfigs = len(configList)
  528. for i in range(numConfigs):
  529. configData = configList[i]
  530. displayName = configData.get('display name', ('display%d' % i))
  531. displayMode = configData.get('display mode', 'server')
  532. # Init Cam Offset
  533. pos = configData.get('pos', Vec3(0))
  534. hpr = configData.get('hpr', Vec3(0))
  535. # Init Frustum if specified
  536. fl = configData.get('focal length', None)
  537. fs = configData.get('film size', None)
  538. fo = configData.get('film offset', None)
  539. if displayMode == 'client':
  540. #lens.setInterocularDistance(pos[0])
  541. base.cam.setPos(pos)
  542. lens = base.cam.node().getLens()
  543. lens.setViewHpr(hpr)
  544. if fl is not None:
  545. lens.setFocalLength(fl)
  546. if fs is not None:
  547. lens.setFilmSize(fs[0], fs[1])
  548. if fo is not None:
  549. lens.setFilmOffset(fo[0], fo[1])
  550. else:
  551. serverConfigName = 'cluster-server-%s' % displayName
  552. serverName = base.config.GetString(serverConfigName, '')
  553. if serverName == '':
  554. base.notify.warning(
  555. '%s undefined in Configrc: expected by %s display client.'%
  556. (serverConfigName, clusterConfig))
  557. base.notify.warning('%s will not be used.' % serverConfigName)
  558. else:
  559. # Daemon port
  560. serverDaemonPortConfigName = (
  561. 'cluster-server-daemon-port-%s' % displayName)
  562. serverDaemonPort = base.config.GetInt(
  563. serverDaemonPortConfigName,
  564. CLUSTER_DAEMON_PORT)
  565. # TCP Server port
  566. serverMsgPortConfigName = (
  567. 'cluster-server-msg-port-%s' % displayName)
  568. serverMsgPort = base.config.GetInt(serverMsgPortConfigName,
  569. CLUSTER_SERVER_PORT)
  570. cci = ClusterConfigItem(
  571. serverConfigName,
  572. serverName,
  573. serverDaemonPort,
  574. serverMsgPort)
  575. # Init cam offset
  576. cci.setCamOffset(pos, hpr)
  577. # Init frustum if specified
  578. if fl and fs and fo:
  579. cci.setCamFrustum(fl, fs, fo)
  580. displayConfigs.append(cci)
  581. # Create Cluster Managers (opening connections to servers)
  582. # Are the servers going to be synced?
  583. if base.clusterSyncFlag:
  584. base.notify.warning('autoflip')
  585. base.graphicsEngine.setAutoFlip(0)
  586. base.notify.warning('ClusterClientSync')
  587. return ClusterClientSync(displayConfigs, base.clusterSyncFlag)
  588. else:
  589. return ClusterClient(displayConfigs, base.clusterSyncFlag)
  590. class DummyClusterClient(DirectObject.DirectObject):
  591. """ Dummy class to handle command strings when not in cluster mode """
  592. notify = DirectNotifyGlobal.directNotify.newCategory("DummyClusterClient")
  593. def __init__(self):
  594. pass
  595. def __call__(self, commandString, fLocally = 1, serverList = None):
  596. if fLocally:
  597. # Execute locally
  598. exec(commandString, __builtins__)