ClientRepositoryBase.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. from panda3d.core import (
  2. ClockObject,
  3. ConfigVariableBool,
  4. ConfigVariableDouble,
  5. Datagram,
  6. DatagramIterator,
  7. )
  8. from direct.task import Task
  9. from direct.task.TaskManagerGlobal import taskMgr
  10. from direct.directnotify import DirectNotifyGlobal
  11. from direct.distributed.CRDataCache import CRDataCache
  12. from direct.distributed.ConnectionRepository import ConnectionRepository
  13. from direct.showbase.PythonUtil import safeRepr, itype, makeList
  14. from direct.showbase.MessengerGlobal import messenger
  15. from .MsgTypes import CLIENT_ENTER_OBJECT_REQUIRED_OTHER, MsgId2Names
  16. from . import CRCache
  17. from . import ParentMgr
  18. from . import RelatedObjectMgr
  19. import time
  20. class ClientRepositoryBase(ConnectionRepository):
  21. """
  22. This maintains a client-side connection with a Panda server.
  23. This base class exists to collect the common code between
  24. ClientRepository, which is the CMU-provided, open-source version
  25. of the client repository code, and OTPClientRepository, which is
  26. the VR Studio's implementation of the same.
  27. """
  28. notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepositoryBase")
  29. def __init__(self, dcFileNames = None, dcSuffix = '',
  30. connectMethod = None, threadedNet = None):
  31. if connectMethod is None:
  32. connectMethod = self.CM_HTTP
  33. ConnectionRepository.__init__(self, connectMethod, base.config, hasOwnerView = True, threadedNet = threadedNet)
  34. self.dcSuffix = dcSuffix
  35. if hasattr(self, 'setVerbose'):
  36. if ConfigVariableBool('verbose-clientrepository', False):
  37. self.setVerbose(1)
  38. self.context=100000
  39. self.setClientDatagram(1)
  40. self.deferredGenerates = []
  41. self.deferredDoIds = {}
  42. self.lastGenerate = 0
  43. self.setDeferInterval(ConfigVariableDouble('deferred-generate-interval', 0.2).value)
  44. self.noDefer = False # Set this True to temporarily disable deferring.
  45. self.recorder = base.recorder
  46. self.readDCFile(dcFileNames)
  47. self.cache=CRCache.CRCache()
  48. self.doDataCache = CRDataCache()
  49. self.cacheOwner=CRCache.CRCache()
  50. self.serverDelta = 0
  51. self.bootedIndex = None
  52. self.bootedText = None
  53. # create a parentMgr to handle distributed reparents
  54. # this used to be 'token2nodePath'
  55. self.parentMgr = ParentMgr.ParentMgr()
  56. # The RelatedObjectMgr helps distributed objects find each
  57. # other.
  58. self.relatedObjectMgr = RelatedObjectMgr.RelatedObjectMgr(self)
  59. # This will be filled in when a TimeManager is created.
  60. self.timeManager = None
  61. # Keep track of how recently we last sent a heartbeat message.
  62. # We want to keep these coming at heartbeatInterval seconds.
  63. self.heartbeatInterval = ConfigVariableDouble('heartbeat-interval', 10).value
  64. self.heartbeatStarted = 0
  65. self.lastHeartbeat = 0
  66. self._delayDeletedDOs = {}
  67. self.specialNameNumber = 0
  68. def setDeferInterval(self, deferInterval):
  69. """Specifies the minimum amount of time, in seconds, that must
  70. elapse before generating any two DistributedObjects whose
  71. class type is marked "deferrable". Set this to 0 to indicate
  72. no deferring will occur."""
  73. self.deferInterval = deferInterval
  74. self.setHandleCUpdates(self.deferInterval == 0)
  75. if self.deferredGenerates:
  76. taskMgr.remove('deferredGenerate')
  77. taskMgr.doMethodLater(self.deferInterval, self.doDeferredGenerate, 'deferredGenerate')
  78. ## def queryObjectAll(self, doID, context=0):
  79. ## """
  80. ## Get a one-time snapshot look at the object.
  81. ## """
  82. ## assert self.notify.debugStateCall(self)
  83. ## # Create a message
  84. ## datagram = PyDatagram()
  85. ## datagram.addServerHeader(
  86. ## doID, localAvatar.getDoId(), 2020)
  87. ## # A context that can be used to index the response if needed
  88. ## datagram.addUint32(context)
  89. ## self.send(datagram)
  90. ## # Make sure the message gets there.
  91. ## self.flush()
  92. def specialName(self, label):
  93. name = f"SpecialName {self.specialNameNumber} {label}"
  94. self.specialNameNumber += 1
  95. return name
  96. def getTables(self, ownerView):
  97. if ownerView:
  98. return self.doId2ownerView, self.cacheOwner
  99. else:
  100. return self.doId2do, self.cache
  101. def _getMsgName(self, msgId):
  102. # we might get a list of message names, use the first one
  103. return makeList(MsgId2Names.get(msgId, 'UNKNOWN MESSAGE: %s' % msgId))[0]
  104. def allocateContext(self):
  105. self.context+=1
  106. return self.context
  107. def setServerDelta(self, delta):
  108. """
  109. Indicates the approximate difference in seconds between the
  110. client's clock and the server's clock, in universal time (not
  111. including timezone shifts). This is mainly useful for
  112. reporting synchronization information to the logs; don't
  113. depend on it for any precise timing requirements.
  114. Also see Notify.setServerDelta(), which also accounts for a
  115. timezone shift.
  116. """
  117. self.serverDelta = delta
  118. def getServerDelta(self):
  119. return self.serverDelta
  120. def getServerTimeOfDay(self):
  121. """
  122. Returns the current time of day (seconds elapsed since the
  123. 1972 epoch) according to the server's clock. This is in GMT,
  124. and hence is irrespective of timezones.
  125. The value is computed based on the client's clock and the
  126. known delta from the server's clock, which is not terribly
  127. precisely measured and may drift slightly after startup, but
  128. it should be accurate plus or minus a couple of seconds.
  129. """
  130. return time.time() + self.serverDelta
  131. def doGenerate(self, parentId, zoneId, classId, doId, di):
  132. # Look up the dclass
  133. assert parentId == self.GameGlobalsId or parentId in self.doId2do
  134. dclass = self.dclassesByNumber[classId]
  135. assert self.notify.debug("performing generate for %s %s" % (dclass.getName(), doId))
  136. dclass.startGenerate()
  137. # Create a new distributed object, and put it in the dictionary
  138. distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
  139. dclass.stopGenerate()
  140. def flushGenerates(self):
  141. """ Forces all pending generates to be performed immediately. """
  142. while self.deferredGenerates:
  143. msgType, extra = self.deferredGenerates[0]
  144. del self.deferredGenerates[0]
  145. self.replayDeferredGenerate(msgType, extra)
  146. taskMgr.remove('deferredGenerate')
  147. def replayDeferredGenerate(self, msgType, extra):
  148. """ Override this to do something appropriate with deferred
  149. "generate" messages when they are replayed().
  150. """
  151. if msgType == CLIENT_ENTER_OBJECT_REQUIRED_OTHER:
  152. # It's a generate message.
  153. doId = extra
  154. if doId in self.deferredDoIds:
  155. args, deferrable, dg, updates = self.deferredDoIds[doId]
  156. del self.deferredDoIds[doId]
  157. self.doGenerate(*args)
  158. if deferrable:
  159. self.lastGenerate = ClockObject.getGlobalClock().getFrameTime()
  160. for dg, di in updates:
  161. # non-DC updates that need to be played back in-order are
  162. # stored as (msgType, (dg, di))
  163. if isinstance(di, tuple):
  164. msgType = dg
  165. dg, di = di
  166. self.replayDeferredGenerate(msgType, (dg, di))
  167. else:
  168. # ovUpdated is set to True since its OV
  169. # is assumbed to have occured when the
  170. # deferred update was originally received
  171. self.__doUpdate(doId, di, True)
  172. else:
  173. self.notify.warning("Ignoring deferred message %s" % (msgType))
  174. def doDeferredGenerate(self, task):
  175. """ This is the task that generates an object on the deferred
  176. queue. """
  177. now = ClockObject.getGlobalClock().getFrameTime()
  178. while self.deferredGenerates:
  179. if now - self.lastGenerate < self.deferInterval:
  180. # Come back later.
  181. return Task.again
  182. # Generate the next deferred object.
  183. msgType, extra = self.deferredGenerates[0]
  184. del self.deferredGenerates[0]
  185. self.replayDeferredGenerate(msgType, extra)
  186. # All objects are generaetd.
  187. return Task.done
  188. def generateWithRequiredFields(self, dclass, doId, di, parentId, zoneId):
  189. if doId in self.doId2do:
  190. # ...it is in our dictionary.
  191. # Just update it.
  192. distObj = self.doId2do[doId]
  193. assert distObj.dclass == dclass
  194. distObj.generate()
  195. distObj.setLocation(parentId, zoneId)
  196. distObj.updateRequiredFields(dclass, di)
  197. # updateRequiredFields calls announceGenerate
  198. elif self.cache.contains(doId):
  199. # ...it is in the cache.
  200. # Pull it out of the cache:
  201. distObj = self.cache.retrieve(doId)
  202. assert distObj.dclass == dclass
  203. # put it in the dictionary:
  204. self.doId2do[doId] = distObj
  205. # and update it.
  206. distObj.generate()
  207. # make sure we don't have a stale location
  208. distObj.parentId = None
  209. distObj.zoneId = None
  210. distObj.setLocation(parentId, zoneId)
  211. distObj.updateRequiredFields(dclass, di)
  212. # updateRequiredFields calls announceGenerate
  213. else:
  214. # ...it is not in the dictionary or the cache.
  215. # Construct a new one
  216. classDef = dclass.getClassDef()
  217. if classDef is None:
  218. self.notify.error("Could not create an undefined %s object." % (dclass.getName()))
  219. distObj = classDef(self)
  220. distObj.dclass = dclass
  221. # Assign it an Id
  222. distObj.doId = doId
  223. # Put the new do in the dictionary
  224. self.doId2do[doId] = distObj
  225. # Update the required fields
  226. distObj.generateInit() # Only called when constructed
  227. distObj._retrieveCachedData()
  228. distObj.generate()
  229. distObj.setLocation(parentId, zoneId)
  230. distObj.updateRequiredFields(dclass, di)
  231. # updateRequiredFields calls announceGenerate
  232. self.notify.debug("New DO:%s, dclass:%s" % (doId, dclass.getName()))
  233. return distObj
  234. def generateWithRequiredOtherFields(self, dclass, doId, di,
  235. parentId = None, zoneId = None):
  236. if doId in self.doId2do:
  237. # ...it is in our dictionary.
  238. # Just update it.
  239. distObj = self.doId2do[doId]
  240. assert distObj.dclass == dclass
  241. distObj.generate()
  242. distObj.setLocation(parentId, zoneId)
  243. distObj.updateRequiredOtherFields(dclass, di)
  244. # updateRequiredOtherFields calls announceGenerate
  245. elif self.cache.contains(doId):
  246. # ...it is in the cache.
  247. # Pull it out of the cache:
  248. distObj = self.cache.retrieve(doId)
  249. assert distObj.dclass == dclass
  250. # put it in the dictionary:
  251. self.doId2do[doId] = distObj
  252. # and update it.
  253. distObj.generate()
  254. # make sure we don't have a stale location
  255. distObj.parentId = None
  256. distObj.zoneId = None
  257. distObj.setLocation(parentId, zoneId)
  258. distObj.updateRequiredOtherFields(dclass, di)
  259. # updateRequiredOtherFields calls announceGenerate
  260. else:
  261. # ...it is not in the dictionary or the cache.
  262. # Construct a new one
  263. classDef = dclass.getClassDef()
  264. if classDef is None:
  265. self.notify.error("Could not create an undefined %s object." % (dclass.getName()))
  266. distObj = classDef(self)
  267. distObj.dclass = dclass
  268. # Assign it an Id
  269. distObj.doId = doId
  270. # Put the new do in the dictionary
  271. self.doId2do[doId] = distObj
  272. # Update the required fields
  273. distObj.generateInit() # Only called when constructed
  274. distObj._retrieveCachedData()
  275. distObj.generate()
  276. distObj.setLocation(parentId, zoneId)
  277. distObj.updateRequiredOtherFields(dclass, di)
  278. # updateRequiredOtherFields calls announceGenerate
  279. return distObj
  280. def generateWithRequiredOtherFieldsOwner(self, dclass, doId, di):
  281. if doId in self.doId2ownerView:
  282. # ...it is in our dictionary.
  283. # Just update it.
  284. self.notify.error('duplicate owner generate for %s (%s)' % (
  285. doId, dclass.getName()))
  286. distObj = self.doId2ownerView[doId]
  287. assert distObj.dclass == dclass
  288. distObj.generate()
  289. distObj.updateRequiredOtherFields(dclass, di)
  290. # updateRequiredOtherFields calls announceGenerate
  291. elif self.cacheOwner.contains(doId):
  292. # ...it is in the cache.
  293. # Pull it out of the cache:
  294. distObj = self.cacheOwner.retrieve(doId)
  295. assert distObj.dclass == dclass
  296. # put it in the dictionary:
  297. self.doId2ownerView[doId] = distObj
  298. # and update it.
  299. distObj.generate()
  300. distObj.updateRequiredOtherFields(dclass, di)
  301. # updateRequiredOtherFields calls announceGenerate
  302. else:
  303. # ...it is not in the dictionary or the cache.
  304. # Construct a new one
  305. classDef = dclass.getOwnerClassDef()
  306. if classDef is None:
  307. self.notify.error("Could not create an undefined %s object. Have you created an owner view?" % (dclass.getName()))
  308. distObj = classDef(self)
  309. distObj.dclass = dclass
  310. # Assign it an Id
  311. distObj.doId = doId
  312. # Put the new do in the dictionary
  313. self.doId2ownerView[doId] = distObj
  314. # Update the required fields
  315. distObj.generateInit() # Only called when constructed
  316. distObj.generate()
  317. distObj.updateRequiredOtherFields(dclass, di)
  318. # updateRequiredOtherFields calls announceGenerate
  319. return distObj
  320. def disableDoId(self, doId, ownerView=False):
  321. table, cache = self.getTables(ownerView)
  322. # Make sure the object exists
  323. if doId in table:
  324. # Look up the object
  325. distObj = table[doId]
  326. # remove the object from the dictionary
  327. del table[doId]
  328. # Only cache the object if it is a "cacheable" type
  329. # object; this way we don't clutter up the caches with
  330. # trivial objects that don't benefit from caching.
  331. # also don't try to cache an object that is delayDeleted
  332. cached = False
  333. if distObj.getCacheable() and distObj.getDelayDeleteCount() <= 0:
  334. cached = cache.cache(distObj)
  335. if not cached:
  336. distObj.deleteOrDelay()
  337. if distObj.getDelayDeleteCount() <= 0:
  338. # make sure we're not leaking
  339. distObj.detectLeaks()
  340. elif doId in self.deferredDoIds:
  341. # The object had been deferred. Great; we don't even have
  342. # to generate it now.
  343. del self.deferredDoIds[doId]
  344. i = self.deferredGenerates.index((CLIENT_ENTER_OBJECT_REQUIRED_OTHER, doId))
  345. del self.deferredGenerates[i]
  346. if len(self.deferredGenerates) == 0:
  347. taskMgr.remove('deferredGenerate')
  348. else:
  349. self._logFailedDisable(doId, ownerView)
  350. def _logFailedDisable(self, doId, ownerView):
  351. self.notify.warning(
  352. "Disable failed. DistObj "
  353. + str(doId) +
  354. " is not in dictionary, ownerView=%s" % ownerView)
  355. def handleDelete(self, di):
  356. # overridden by ClientRepository
  357. assert 0
  358. def handleUpdateField(self, di):
  359. """
  360. This method is called when a CLIENT_OBJECT_UPDATE_FIELD
  361. message is received; it decodes the update, unpacks the
  362. arguments, and calls the corresponding method on the indicated
  363. DistributedObject.
  364. In fact, this method is exactly duplicated by the C++ method
  365. cConnectionRepository::handle_update_field(), which was
  366. written to optimize the message loop by handling all of the
  367. CLIENT_OBJECT_UPDATE_FIELD messages in C++. That means that
  368. nowadays, this Python method will probably never be called,
  369. since UPDATE_FIELD messages will not even be passed to the
  370. Python message handlers. But this method remains for
  371. documentation purposes, and also as a "just in case" handler
  372. in case we ever do come across a situation in the future in
  373. which python might handle the UPDATE_FIELD message.
  374. """
  375. # Get the DO Id
  376. doId = di.getUint32()
  377. ovUpdated = self.__doUpdateOwner(doId, di)
  378. if doId in self.deferredDoIds:
  379. # This object hasn't really been generated yet. Sit on
  380. # the update.
  381. args, deferrable, dg0, updates = self.deferredDoIds[doId]
  382. # Keep a copy of the datagram, and move the di to the copy
  383. dg = Datagram(di.getDatagram())
  384. di = DatagramIterator(dg, di.getCurrentIndex())
  385. updates.append((dg, di))
  386. else:
  387. # This object has been fully generated. It's OK to update.
  388. self.__doUpdate(doId, di, ovUpdated)
  389. def __doUpdate(self, doId, di, ovUpdated):
  390. # Find the DO
  391. do = self.doId2do.get(doId)
  392. if do is not None:
  393. # Let the dclass finish the job
  394. do.dclass.receiveUpdate(do, di)
  395. elif not ovUpdated:
  396. # this next bit is looking for avatar handles so that if you get an update
  397. # for an avatar that isn't in your doId2do table but there is a
  398. # avatar handle for that object then it's messages will be forwarded to that
  399. # object. We are currently using that for whisper echoing
  400. # if you need a more general perpose system consider registering proxy objects on
  401. # a dict and adding the avatar handles to that dict when they are created
  402. # then change/remove the old method. I didn't do that because I couldn't think
  403. # of a use for it. -JML
  404. try:
  405. handle = self.identifyAvatar(doId)
  406. if handle:
  407. dclass = self.dclassesByName[handle.dclassName]
  408. dclass.receiveUpdate(handle, di)
  409. else:
  410. self.notify.warning(
  411. "Asked to update non-existent DistObj " + str(doId))
  412. except:
  413. self.notify.warning(
  414. "Asked to update non-existent DistObj " + str(doId) + "and failed to find it")
  415. def __doUpdateOwner(self, doId, di):
  416. ovObj = self.doId2ownerView.get(doId)
  417. if ovObj:
  418. odg = Datagram(di.getDatagram())
  419. odi = DatagramIterator(odg, di.getCurrentIndex())
  420. ovObj.dclass.receiveUpdate(ovObj, odi)
  421. return True
  422. return False
  423. def handleGoGetLost(self, di):
  424. # The server told us it's about to drop the connection on us.
  425. # Get ready!
  426. if di.getRemainingSize() > 0:
  427. self.bootedIndex = di.getUint16()
  428. self.bootedText = di.getString()
  429. self.notify.warning(
  430. "Server is booting us out (%d): %s" % (self.bootedIndex, self.bootedText))
  431. else:
  432. self.bootedIndex = None
  433. self.bootedText = None
  434. self.notify.warning(
  435. "Server is booting us out with no explanation.")
  436. # disconnect now, don't wait for send/recv to fail
  437. self.stopReaderPollTask()
  438. self.lostConnection()
  439. def handleServerHeartbeat(self, di):
  440. # Got a heartbeat message from the server.
  441. if ConfigVariableBool('server-heartbeat-info', True):
  442. self.notify.info("Server heartbeat.")
  443. def handleSystemMessage(self, di):
  444. # Got a system message from the server.
  445. message = di.getString()
  446. self.notify.info('Message from server: %s' % (message))
  447. return message
  448. def handleSystemMessageAknowledge(self, di):
  449. # Got a system message from the server.
  450. message = di.getString()
  451. self.notify.info('Message with aknowledge from server: %s' % (message))
  452. messenger.send("system message aknowledge", [message])
  453. return message
  454. def getObjectsOfClass(self, objClass):
  455. """ returns dict of doId:object, containing all objects
  456. that inherit from 'class'. returned dict is safely mutable. """
  457. doDict = {}
  458. for doId, do in self.doId2do.items():
  459. if isinstance(do, objClass):
  460. doDict[doId] = do
  461. return doDict
  462. def getObjectsOfExactClass(self, objClass):
  463. """ returns dict of doId:object, containing all objects that
  464. are exactly of type 'class' (neglecting inheritance). returned
  465. dict is safely mutable. """
  466. doDict = {}
  467. for doId, do in self.doId2do.items():
  468. if do.__class__ == objClass:
  469. doDict[doId] = do
  470. return doDict
  471. def considerHeartbeat(self):
  472. """Send a heartbeat message if we haven't sent one recently."""
  473. if not self.heartbeatStarted:
  474. self.notify.debug("Heartbeats not started; not sending.")
  475. return
  476. elapsed = ClockObject.getGlobalClock().getRealTime() - self.lastHeartbeat
  477. if elapsed < 0 or elapsed > self.heartbeatInterval:
  478. # It's time to send the heartbeat again (or maybe someone
  479. # reset the clock back).
  480. self.notify.info("Sending heartbeat mid-frame.")
  481. self.startHeartbeat()
  482. def stopHeartbeat(self):
  483. taskMgr.remove("heartBeat")
  484. self.heartbeatStarted = 0
  485. def startHeartbeat(self):
  486. self.stopHeartbeat()
  487. self.heartbeatStarted = 1
  488. self.sendHeartbeat()
  489. self.waitForNextHeartBeat()
  490. def sendHeartbeatTask(self, task):
  491. self.sendHeartbeat()
  492. return Task.again
  493. def waitForNextHeartBeat(self):
  494. taskMgr.doMethodLater(self.heartbeatInterval, self.sendHeartbeatTask,
  495. "heartBeat", taskChain = 'net')
  496. def replaceMethod(self, oldMethod, newFunction):
  497. return 0
  498. def getWorld(self, doId):
  499. # Get the world node for this object
  500. obj = self.doId2do[doId]
  501. worldNP = obj.getParent()
  502. while 1:
  503. nextNP = worldNP.getParent()
  504. if nextNP == render:
  505. break
  506. elif worldNP.isEmpty():
  507. return None
  508. return worldNP
  509. def isLive(self):
  510. if ConfigVariableBool('force-live', False):
  511. return True
  512. return not (__dev__ or launcher.isTestServer())
  513. def isLocalId(self, id):
  514. # By default, no ID's are local. See also
  515. # ClientRepository.isLocalId().
  516. return 0
  517. # methods for tracking delaydeletes
  518. def _addDelayDeletedDO(self, do):
  519. # use the id of the object, it's possible to have multiple DelayDeleted instances
  520. # with identical doIds if an object gets deleted then re-generated
  521. key = id(do)
  522. assert key not in self._delayDeletedDOs
  523. self._delayDeletedDOs[key] = do
  524. def _removeDelayDeletedDO(self, do):
  525. key = id(do)
  526. del self._delayDeletedDOs[key]
  527. def printDelayDeletes(self):
  528. print('DelayDeletes:')
  529. print('=============')
  530. for obj in self._delayDeletedDOs.values():
  531. print('%s\t%s (%s)\tdelayDeletes=%s' % (
  532. obj.doId, safeRepr(obj), itype(obj), obj.getDelayDeleteNames()))