ServerRepository.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. """ServerRepository module: contains the ServerRepository class"""
  2. from pandac.PandaModules import *
  3. #from TaskManagerGlobal import *
  4. from direct.distributed.MsgTypes import *
  5. from direct.task import Task
  6. from direct.directnotify import DirectNotifyGlobal
  7. #from NetDatagram import NetDatagram
  8. #from Datagram import Datagram
  9. #from Datagram import *
  10. from pandac import Datagram
  11. from pandac import DatagramIterator
  12. from direct.distributed.PyDatagram import PyDatagram
  13. from direct.distributed.PyDatagramIterator import PyDatagramIterator
  14. #from PointerToConnection import PointerToConnection
  15. import time
  16. import types
  17. class ServerRepository:
  18. """ This maintains the server-side connection with a Panda server.
  19. It is only for use with the Panda LAN server provided by CMU."""
  20. notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
  21. def __init__(self, tcpPort, udpPort):
  22. self.qcm = QueuedConnectionManager()
  23. self.qcl = QueuedConnectionListener(self.qcm, 0)
  24. self.qcr = QueuedConnectionReader(self.qcm, 0)
  25. self.cw = ConnectionWriter(self.qcm,0)
  26. self.tcpRendezvous = self.qcm.openTCPServerRendezvous(tcpPort, 10)
  27. print self.tcpRendezvous
  28. self.qcl.addConnection(self.tcpRendezvous)
  29. taskMgr.add(self.listenerPoll, "serverListenerPollTask")
  30. taskMgr.add(self.readerPollUntilEmpty, "serverReaderPollTask")
  31. taskMgr.add(self.clientHardDisconnectTask, "clientHardDisconnect")
  32. self.ClientIP = {}
  33. self.ClientZones = {}
  34. self.ClientDOIDbase = {}
  35. self.ClientObjects = {}
  36. self.DOIDnext = 1
  37. self.DOIDrange = 1000000
  38. self.DOIDtoClient = {}
  39. self.DOIDtoZones = {}
  40. self.DOIDtoDClass = {}
  41. self.ZonesToClients = {}
  42. self.ZonetoDOIDs = {}
  43. self.dcFile = DCFile()
  44. self.dcSuffix = ''
  45. self.readDCFile()
  46. def importModule(self, dcImports, moduleName, importSymbols):
  47. """ Imports the indicated moduleName and all of its symbols
  48. into the current namespace. This more-or-less reimplements
  49. the Python import command. """
  50. module = __import__(moduleName, globals(), locals(), importSymbols)
  51. if importSymbols:
  52. # "from moduleName import symbolName, symbolName, ..."
  53. # Copy just the named symbols into the dictionary.
  54. if importSymbols == ['*']:
  55. # "from moduleName import *"
  56. if hasattr(module, "__all__"):
  57. importSymbols = module.__all__
  58. else:
  59. importSymbols = module.__dict__.keys()
  60. for symbolName in importSymbols:
  61. if hasattr(module, symbolName):
  62. dcImports[symbolName] = getattr(module, symbolName)
  63. else:
  64. raise StandardError, 'Symbol %s not defined in module %s.' % (symbolName, moduleName)
  65. else:
  66. # "import moduleName"
  67. # Copy the root module name into the dictionary.
  68. # Follow the dotted chain down to the actual module.
  69. components = moduleName.split('.')
  70. dcImports[components[0]] = module
  71. def readDCFile(self, dcFileNames = None):
  72. """
  73. Reads in the dc files listed in dcFileNames, or if
  74. dcFileNames is None, reads in all of the dc files listed in
  75. the Configrc file.
  76. """
  77. dcFile = self.dcFile
  78. dcFile.clear()
  79. self.dclassesByName = {}
  80. self.dclassesByNumber = {}
  81. self.hashVal = 0
  82. dcImports = {}
  83. if dcFileNames == None:
  84. readResult = dcFile.readAll()
  85. if not readResult:
  86. self.notify.error("Could not read dc file.")
  87. else:
  88. for dcFileName in dcFileNames:
  89. readResult = dcFile.read(Filename(dcFileName))
  90. if not readResult:
  91. self.notify.error("Could not read dc file: %s" % (dcFileName))
  92. self.hashVal = dcFile.getHash()
  93. # Now import all of the modules required by the DC file.
  94. for n in range(dcFile.getNumImportModules()):
  95. moduleName = dcFile.getImportModule(n)
  96. # Maybe the module name is represented as "moduleName/AI".
  97. suffix = moduleName.split('/')
  98. moduleName = suffix[0]
  99. if self.dcSuffix and self.dcSuffix in suffix[1:]:
  100. moduleName += self.dcSuffix
  101. importSymbols = []
  102. for i in range(dcFile.getNumImportSymbols(n)):
  103. symbolName = dcFile.getImportSymbol(n, i)
  104. # Maybe the symbol name is represented as "symbolName/AI".
  105. suffix = symbolName.split('/')
  106. symbolName = suffix[0]
  107. if self.dcSuffix and self.dcSuffix in suffix[1:]:
  108. symbolName += self.dcSuffix
  109. importSymbols.append(symbolName)
  110. self.importModule(dcImports, moduleName, importSymbols)
  111. # Now get the class definition for the classes named in the DC
  112. # file.
  113. for i in range(dcFile.getNumClasses()):
  114. dclass = dcFile.getClass(i)
  115. number = dclass.getNumber()
  116. className = dclass.getName() + self.dcSuffix
  117. # Does the class have a definition defined in the newly
  118. # imported namespace?
  119. classDef = dcImports.get(className)
  120. if classDef == None:
  121. self.notify.info("No class definition for %s." % (className))
  122. else:
  123. if type(classDef) == types.ModuleType:
  124. if not hasattr(classDef, className):
  125. self.notify.error("Module %s does not define class %s." % (className, className))
  126. classDef = getattr(classDef, className)
  127. if type(classDef) != types.ClassType:
  128. self.notify.error("Symbol %s is not a class name." % (className))
  129. else:
  130. dclass.setClassDef(classDef)
  131. self.dclassesByName[className] = dclass
  132. if number >= 0:
  133. self.dclassesByNumber[number] = dclass
  134. # listens for new clients
  135. def listenerPoll(self, task):
  136. if self.qcl.newConnectionAvailable():
  137. rendezvous = PointerToConnection()
  138. netAddress = NetAddress()
  139. newConnection = PointerToConnection()
  140. retVal = self.qcl.getNewConnection(rendezvous, netAddress,
  141. newConnection)
  142. if retVal:
  143. # Crazy dereferencing
  144. newConnection=newConnection.p()
  145. self.qcr.addConnection(newConnection)
  146. # Add clients infomation to dictionary
  147. self.ClientIP[newConnection] = netAddress.getIpString()
  148. self.ClientZones[newConnection] = []
  149. self.ClientObjects[newConnection] = []
  150. self.lastConnection = newConnection
  151. self.sendDOIDrange(self.lastConnection)
  152. else:
  153. self.notify.warning(
  154. "getNewConnection returned false")
  155. return Task.cont
  156. # continuously polls for new messages on the server
  157. def readerPollUntilEmpty(self, task):
  158. while self.readerPollOnce():
  159. pass
  160. return Task.cont
  161. # checks for available messages to the server
  162. def readerPollOnce(self):
  163. availGetVal = self.qcr.dataAvailable()
  164. if availGetVal:
  165. datagram = NetDatagram()
  166. readRetVal = self.qcr.getData(datagram)
  167. if readRetVal:
  168. # need to send to message processing unit
  169. self.handleDatagram(datagram)
  170. else:
  171. self.notify.warning("getData returned false")
  172. return availGetVal
  173. # switching station for messages
  174. def handleDatagram(self, datagram):
  175. dgi = DatagramIterator.DatagramIterator(datagram)
  176. type = dgi.getUint16()
  177. if type == CLIENT_DISCONNECT:
  178. self.handleClientDisconnect(datagram.getConnection())
  179. elif type == CLIENT_SET_ZONE:
  180. self.handleSetZone(dgi, datagram.getConnection())
  181. elif type == CLIENT_REMOVE_ZONE:
  182. self.handleRemoveZone(dgi, datagram.getConnection())
  183. elif type == CLIENT_CREATE_OBJECT_REQUIRED:
  184. self.handleClientCreateObjectRequired(datagram, dgi)
  185. elif type == CLIENT_OBJECT_UPDATE_FIELD:
  186. self.handleClientUpdateField(datagram, dgi)
  187. elif type == CLIENT_OBJECT_DELETE:
  188. self.handleClientDeleteObject(datagram, dgi.getUint32())
  189. elif type == CLIENT_OBJECT_DISABLE:
  190. self.handleClientDisable(datagram, dgi.getUint32())
  191. else:
  192. self.notify.error("unrecognized message")
  193. # client wants to create an object, so we store appropriate data,
  194. # and then pass message along to corresponding zones
  195. def handleClientCreateObjectRequired(self, datagram, dgi):
  196. connection = datagram.getConnection()
  197. # no need to create a new message, just forward the received
  198. # message as it has the same msg type number
  199. zone = dgi.getUint32()
  200. classid = dgi.getUint16()
  201. doid = dgi.getUint32()
  202. rest = dgi.getRemainingBytes()
  203. datagram = NetDatagram()
  204. datagram.addUint16(CLIENT_CREATE_OBJECT_REQUIRED)
  205. datagram.addUint16(classid)
  206. datagram.addUint32(doid)
  207. datagram.appendData(rest)
  208. dclass = self.dclassesByNumber[classid]
  209. if self.ClientObjects[connection].count(doid) == 0:
  210. self.ClientObjects[connection].append(doid)
  211. self.DOIDtoZones[doid] = zone
  212. self.DOIDtoDClass[doid] = dclass
  213. if zone in self.ZonetoDOIDs:
  214. if self.ZonetoDOIDs[zone].count(doid)==0:
  215. self.ZonetoDOIDs[zone].append(doid)
  216. else:
  217. self.ZonetoDOIDs[zone] = [doid]
  218. self.sendToZoneExcept(zone, datagram, connection)
  219. return None
  220. # client wants to update an object, forward message along
  221. # to corresponding zone
  222. def handleClientUpdateField(self, datagram, dgi):
  223. connection = datagram.getConnection()
  224. doid = dgi.getUint32()
  225. fieldid = dgi.getUint16()
  226. dclass = self.DOIDtoDClass[doid]
  227. dcfield = dclass.getFieldByIndex(fieldid)
  228. if dcfield == None:
  229. self.notify.error(
  230. "Received update for field %s on object %s; no such field for class %s." % (
  231. fieldid, doid, dclass.getName()))
  232. return
  233. if (dcfield.isBroadcast()):
  234. if (dcfield.isP2p()):
  235. self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, 0)
  236. else:
  237. self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, connection)
  238. elif (dcfield.isP2p()):
  239. doidbase = (doid / self.DOIDrange) * self.DOIDrange
  240. self.cw.send(datagram, self.DOIDtoClient[doidbase])
  241. else:
  242. self.notify.warning(
  243. "Message is not broadcast, p2p, or broadcast+p2p")
  244. return None
  245. # client disables an object, let everyone know who is in
  246. # that zone know about it
  247. def handleClientDisable(self, datagram, doid):
  248. # now send disable message to all clients that need to know
  249. if doid in self.DOIDtoZones:
  250. self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, 0)
  251. return None
  252. # client deletes an object, let everyone who is in zone with
  253. # object know about it
  254. def handleClientDeleteObject(self, datagram, doid):
  255. if doid in self.DOIDtoZones:
  256. self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, 0)
  257. self.ClientObjects[datagram.getConnection()].remove(doid)
  258. self.ZonetoDOIDs[self.DOIDtoZones[doid]].remove(doid)
  259. del self.DOIDtoZones[doid]
  260. del self.DOIDtoDClass[doid]
  261. return None
  262. def sendAvatarGenerate(self):
  263. datagram = PyDatagram()
  264. # Message type is 1
  265. datagram.addUint16(ALL_OBJECT_GENERATE_WITH_REQUIRED)
  266. # Avatar class type is 2
  267. datagram.addUint8(2)
  268. # A sample id
  269. datagram.addUint32(10)
  270. # The only required field is the zone field
  271. datagram.addUint32(999)
  272. self.cw.send(datagram, self.lastConnection)
  273. # sends the client the range of doid's that the client can use
  274. def sendDOIDrange(self, connection):
  275. # reuse DOID assignments if we can
  276. id = self.DOIDnext + self.DOIDrange
  277. self.DOIDnext = self.DOIDnext + self.DOIDrange
  278. self.DOIDtoClient[id] = connection
  279. self.ClientDOIDbase[connection] = id
  280. datagram = NetDatagram()
  281. datagram.addUint16(CLIENT_SET_DOID_RANGE)
  282. datagram.addUint32(id)
  283. datagram.addUint32(self.DOIDrange)
  284. print "Sending DOID range: ",id,self.DOIDrange
  285. self.cw.send(datagram, connection)
  286. return None
  287. # a client disconnected from us, we need to update our data, also tell other clients to remove
  288. # the disconnected clients objects
  289. def handleClientDisconnect(self, connection):
  290. if (self.ClientIP.has_key(connection)):
  291. del self.DOIDtoClient[self.ClientDOIDbase[connection]]
  292. for zone in self.ClientZones[connection]:
  293. if len(self.ZonesToClients[zone]) == 1:
  294. del self.ZonesToClients[zone]
  295. else:
  296. self.ZonesToClients[zone].remove(connection)
  297. for obj in self.ClientObjects[connection]:
  298. #create and send delete message
  299. datagram = NetDatagram()
  300. datagram.addUint16(CLIENT_OBJECT_DELETE_RESP)
  301. datagram.addUint32(obj)
  302. self.sendToZoneExcept(self.DOIDtoZones[obj], datagram, 0)
  303. self.ZonetoDOIDs[self.DOIDtoZones[obj]].remove(obj)
  304. del self.DOIDtoZones[obj]
  305. del self.DOIDtoDClass[obj]
  306. del self.ClientIP[connection]
  307. del self.ClientZones[connection]
  308. del self.ClientDOIDbase[connection]
  309. del self.ClientObjects[connection]
  310. return None
  311. # client told us it's zone(s), store information
  312. def handleSetZone(self, dgi, connection):
  313. while dgi.getRemainingSize() > 0:
  314. ZoneID = dgi.getUint32()
  315. if self.ClientZones[connection].count(ZoneID) == 0:
  316. self.ClientZones[connection].append(ZoneID)
  317. if ZoneID in self.ZonesToClients:
  318. if self.ZonesToClients[ZoneID].count(connection) == 0:
  319. self.ZonesToClients[ZoneID].append(connection)
  320. else:
  321. self.ZonesToClients[ZoneID] = [connection]
  322. # We have a new member, need to get all of the data from clients who may have objects in this zone
  323. datagram = NetDatagram()
  324. datagram.addUint16(CLIENT_REQUEST_GENERATES)
  325. datagram.addUint32(ZoneID)
  326. self.sendToAll(datagram)
  327. print "SENDING REQUEST GENERATES (",ZoneID,") TO ALL"
  328. return None
  329. # client has moved zones, need to update them
  330. def handleRemoveZone(self, dgi, connection):
  331. while dgi.getRemainingSize() > 0:
  332. ZoneID = dgi.getUint32()
  333. if self.ClientZones[connection].count(ZoneID) == 1:
  334. self.ClientZones[connection].remove(ZoneID)
  335. if ZoneID in self.ZonesToClients:
  336. if self.ZonesToClients[ZoneID].count(connection) == 1:
  337. self.ZonesToClients[ZoneID].remove(connection)
  338. for i in self.ZonetoDOIDs[ZoneID]:
  339. datagram = NetDatagram()
  340. datagram.addUint16(CLIENT_OBJECT_DELETE)
  341. datagram.addUint32(i)
  342. self.cw.send(datagram, connection)
  343. return None
  344. # client did not tell us he was leaving but we lost connection to him, so we need to update our data and tell others
  345. def clientHardDisconnectTask(self, task):
  346. for i in self.ClientIP.keys():
  347. if (not self.qcr.isConnectionOk(i)):
  348. self.handleClientDisconnect(i)
  349. return Task.cont
  350. # sends a message to everyone who is in the zone
  351. def sendToZoneExcept(self, ZoneID, datagram, connection):
  352. if ZoneID in self.ZonesToClients:
  353. for conn in self.ZonesToClients[ZoneID]:
  354. if (conn != connection): self.cw.send(datagram, conn)
  355. return None
  356. # sends a message to all connected clients
  357. def sendToAll(self, datagram):
  358. for client in self.ClientIP.keys():
  359. self.cw.send(datagram, client)
  360. return None