import base64 from direct.directnotify import DirectNotifyGlobal from direct.distributed import DistributedObject from direct.distributed.ClockDelta import * from direct.showbase import GarbageReport from direct.showbase import PythonUtil from direct.showbase.DirectObject import * from direct.task import Task import os from pandac.PandaModules import * import re import sys import time from otp.otpbase import OTPGlobals from toontown.chat.ChatGlobals import * class TimeManager(DistributedObject.DistributedObject): notify = DirectNotifyGlobal.directNotify.newCategory('TimeManager') neverDisable = 1 def __init__(self, cr): DistributedObject.DistributedObject.__init__(self, cr) self.updateFreq = base.config.GetFloat('time-manager-freq', 1800) self.minWait = base.config.GetFloat('time-manager-min-wait', 10) self.maxUncertainty = base.config.GetFloat('time-manager-max-uncertainty', 1) self.maxAttempts = base.config.GetInt('time-manager-max-attempts', 5) self.extraSkew = base.config.GetInt('time-manager-extra-skew', 0) if self.extraSkew != 0: self.notify.info('Simulating clock skew of %0.3f s' % self.extraSkew) self.reportFrameRateInterval = base.config.GetDouble('report-frame-rate-interval', 300.0) self.talkResult = 0 self.thisContext = -1 self.nextContext = 0 self.attemptCount = 0 self.start = 0 self.lastAttempt = -self.minWait * 2 self.setFrameRateInterval(self.reportFrameRateInterval) self._numClientGarbage = 0 def generate(self): self._gotFirstTimeSync = False if self.cr.timeManager != None: self.cr.timeManager.delete() self.cr.timeManager = self DistributedObject.DistributedObject.generate(self) self.accept(OTPGlobals.SynchronizeHotkey, self.handleHotkey) self.accept('clock_error', self.handleClockError) if __dev__ and base.config.GetBool('enable-garbage-hotkey', 0): self.accept(OTPGlobals.DetectGarbageHotkey, self.handleDetectGarbageHotkey) if self.updateFreq > 0: self.startTask() return def announceGenerate(self): DistributedObject.DistributedObject.announceGenerate(self) self.synchronize('TimeManager.announceGenerate') def gotInitialTimeSync(self): return self._gotFirstTimeSync def disable(self): self.ignore(OTPGlobals.SynchronizeHotkey) if __dev__: self.ignore(OTPGlobals.DetectGarbageHotkey) self.ignore('clock_error') self.stopTask() taskMgr.remove('frameRateMonitor') if self.cr.timeManager == self: self.cr.timeManager = None del self._gotFirstTimeSync DistributedObject.DistributedObject.disable(self) return def delete(self): self.ignore(OTPGlobals.SynchronizeHotkey) self.ignore(OTPGlobals.DetectGarbageHotkey) self.ignore('clock_error') self.stopTask() taskMgr.remove('frameRateMonitor') if self.cr.timeManager == self: self.cr.timeManager = None DistributedObject.DistributedObject.delete(self) return def startTask(self): self.stopTask() taskMgr.doMethodLater(self.updateFreq, self.doUpdate, 'timeMgrTask') def stopTask(self): taskMgr.remove('timeMgrTask') def doUpdate(self, task): self.synchronize('timer') taskMgr.doMethodLater(self.updateFreq, self.doUpdate, 'timeMgrTask') return Task.done def handleHotkey(self): self.lastAttempt = -self.minWait * 2 if self.synchronize('user hotkey'): self.talkResult = 1 else: base.localAvatar.setChatAbsolute('Too soon.', CFSpeech | CFTimeout) def handleClockError(self): self.synchronize('clock error') def synchronize(self, description): now = globalClock.getRealTime() if now - self.lastAttempt < self.minWait: self.notify.debug('Not resyncing (too soon): %s' % description) return 0 self.talkResult = 0 self.thisContext = self.nextContext self.attemptCount = 0 self.nextContext = self.nextContext + 1 & 255 self.notify.info('Clock sync: %s' % description) self.start = now self.lastAttempt = now self.sendUpdate('requestServerTime', [self.thisContext]) return 1 def serverTime(self, context, timestamp, timeOfDay): end = globalClock.getRealTime() aiTimeSkew = timeOfDay - self.cr.getServerTimeOfDay() if context != self.thisContext: self.notify.info('Ignoring TimeManager response for old context %d' % context) return elapsed = end - self.start self.attemptCount += 1 self.notify.info('Clock sync roundtrip took %0.3f ms' % (elapsed * 1000.0)) self.notify.info('AI time delta is %s from server delta' % PythonUtil.formatElapsedSeconds(aiTimeSkew)) average = (self.start + end) / 2.0 - self.extraSkew uncertainty = (end - self.start) / 2.0 + abs(self.extraSkew) globalClockDelta.resynchronize(average, timestamp, uncertainty) self.notify.info('Local clock uncertainty +/- %.3f s' % globalClockDelta.getUncertainty()) if globalClockDelta.getUncertainty() > self.maxUncertainty: if self.attemptCount < self.maxAttempts: self.notify.info('Uncertainty is too high, trying again.') self.start = globalClock.getRealTime() self.sendUpdate('requestServerTime', [self.thisContext]) return self.notify.info('Giving up on uncertainty requirement.') if self.talkResult: base.localAvatar.setChatAbsolute('latency %0.0f ms, sync \xc2\xb1%0.0f ms' % (elapsed * 1000.0, globalClockDelta.getUncertainty() * 1000.0), CFSpeech | CFTimeout) self._gotFirstTimeSync = True messenger.send('gotTimeSync') toontownTimeManager = getattr(base.cr, 'toontownTimeManager', None) if toontownTimeManager: toontownTimeManager.updateLoginTimes(timeOfDay, int(time.time()), globalClock.getRealTime()) def setDisconnectReason(self, disconnectCode): self.notify.info('Client disconnect reason %s.' % disconnectCode) self.sendUpdate('setDisconnectReason', [disconnectCode]) def setExceptionInfo(self): info = PythonUtil.describeException() self.notify.info('Client exception: %s' % info) self.sendUpdate('setExceptionInfo', [info]) self.cr.flush() def setStackDump(self, dump): self.notify.debug('Stack dump: %s' % fastRepr(dump)) maxLen = 900 dataLeft = base64.b64encode(dump) index = 0 while dataLeft: if len(dataLeft) >= maxLen: data = dataLeft[:maxLen] dataLeft = dataLeft[maxLen:] else: data = dataLeft dataLeft = None self.sendUpdate('setStackDump', [index, data]) index += 1 self.cr.flush() return def d_setSignature(self, signature, hash, pyc): self.sendUpdate('setSignature', [signature, hash, pyc]) def sendCpuInfo(self): if not base.pipe: return di = base.pipe.getDisplayInformation() if di.getNumCpuCores() == 0 and hasattr(base.pipe, 'lookupCpuData'): base.pipe.lookupCpuData() di = base.pipe.getDisplayInformation() di.updateCpuFrequency(0) try: cacheStatus = preloadCache() except NameError: cacheStatus = '' ooghz = 1e-09 cpuSpeed = (di.getMaximumCpuFrequency() * ooghz, di.getCurrentCpuFrequency() * ooghz) numCpuCores = di.getNumCpuCores() numLogicalCpus = di.getNumLogicalCpus() info = '%s|%s|%d|%d|%s|%s cpus' % (di.getCpuVendorString(), di.getCpuBrandString(), di.getCpuVersionInformation(), di.getCpuBrandIndex(), '%0.03f,%0.03f' % cpuSpeed, '%d,%d' % (numCpuCores, numLogicalCpus)) self.notify.debug('setCpuInfo: "%s"' % info) self.sendUpdate('setCpuInfo', [info, cacheStatus]) def setFrameRateInterval(self, frameRateInterval): if frameRateInterval == 0: return if not base.frameRateMeter: maxFrameRateInterval = base.config.GetDouble('max-frame-rate-interval', 30.0) globalClock.setAverageFrameRateInterval(min(frameRateInterval, maxFrameRateInterval)) taskMgr.remove('frameRateMonitor') taskMgr.doMethodLater(frameRateInterval, self.frameRateMonitor, 'frameRateMonitor') def frameRateMonitor(self, task): from otp.avatar.Avatar import Avatar vendorId = 0 deviceId = 0 processMemory = 0 pageFileUsage = 0 physicalMemory = 0 pageFaultCount = 0 osInfo = (os.name, 0, 0, 0) cpuSpeed = (0, 0) numCpuCores = 0 numLogicalCpus = 0 apiName = 'None' if getattr(base, 'pipe', None): di = base.pipe.getDisplayInformation() if di.getDisplayState() == DisplayInformation.DSSuccess: vendorId = di.getVendorId() deviceId = di.getDeviceId() di.updateMemoryInformation() oomb = 1.0 / (1024.0 * 1024.0) processMemory = di.getProcessMemory() * oomb pageFileUsage = di.getPageFileUsage() * oomb physicalMemory = di.getPhysicalMemory() * oomb pageFaultCount = di.getPageFaultCount() / 1000.0 osInfo = (os.name, di.getOsPlatformId(), di.getOsVersionMajor(), di.getOsVersionMinor()) if sys.platform == 'darwin': osInfo = self.getMacOsInfo(osInfo) di.updateCpuFrequency(0) ooghz = 1e-09 cpuSpeed = (di.getMaximumCpuFrequency() * ooghz, di.getCurrentCpuFrequency() * ooghz) numCpuCores = di.getNumCpuCores() numLogicalCpus = di.getNumLogicalCpus() apiName = base.pipe.getInterfaceName() self.d_setFrameRate(max(0, globalClock.getAverageFrameRate()), max(0, globalClock.calcFrameRateDeviation()), len(Avatar.ActiveAvatars), base.locationCode or '', max(0, time.time() - base.locationCodeChanged), max(0, globalClock.getRealTime()), base.gameOptionsCode, vendorId, deviceId, processMemory, pageFileUsage, physicalMemory, pageFaultCount, osInfo, cpuSpeed, numCpuCores, numLogicalCpus, apiName) return task.again def d_setFrameRate(self, fps, deviation, numAvs, locationCode, timeInLocation, timeInGame, gameOptionsCode, vendorId, deviceId, processMemory, pageFileUsage, physicalMemory, pageFaultCount, osInfo, cpuSpeed, numCpuCores, numLogicalCpus, apiName): info = '%0.1f fps|%0.3fd|%s avs|%s|%d|%d|%s|0x%04x|0x%04x|%0.1fMB|%0.1fMB|%0.1fMB|%d|%s|%s|%s cpus|%s' % (fps, deviation, numAvs, locationCode, timeInLocation, timeInGame, gameOptionsCode, vendorId, deviceId, processMemory, pageFileUsage, physicalMemory, pageFaultCount, '%s.%d.%d.%d' % osInfo, '%0.03f,%0.03f' % cpuSpeed, '%d,%d' % (numCpuCores, numLogicalCpus), apiName) print 'frame rate: %s' % info self.sendUpdate('setFrameRate', [fps, deviation, numAvs, locationCode, timeInLocation, timeInGame, gameOptionsCode, vendorId, deviceId, processMemory, pageFileUsage, physicalMemory, pageFaultCount, osInfo, cpuSpeed, numCpuCores, numLogicalCpus, apiName]) if __dev__: def handleDetectGarbageHotkey(self): self._numClientGarbage = GarbageReport.b_checkForGarbageLeaks(wantReply=True) if self._numClientGarbage: s = '%s client garbage cycles found, see log' % self._numClientGarbage else: s = '0 client garbage cycles found' localAvatar.setChatAbsolute(s, CFSpeech | CFTimeout) def d_checkForGarbageLeaks(self, wantReply): self.sendUpdate('checkForGarbageLeaks', [wantReply]) def setNumAIGarbageLeaks(self, numLeaks): if self._numClientGarbage and numLeaks: s = '%s client and %s AI garbage cycles found, see logs' % (self._numClientGarbage, numLeaks) elif numLeaks: s = '0 client and %s AI garbage cycles found, see log' % numLeaks else: s = '0 client and 0 AI garbage cycles found' localAvatar.setChatAbsolute(s, CFSpeech | CFTimeout) def d_setClientGarbageLeak(self, num, description): self.sendUpdate('setClientGarbageLeak', [num, description]) def getMacOsInfo(self, defaultOsInfo): result = defaultOsInfo try: theFile = open('/System/Library/CoreServices/SystemVersion.plist') except IOError: pass else: key = re.search('ProductUserVisibleVersion\\s*' + '(.*?)', theFile.read()) theFile.close() if key is not None: try: verString = key.group(1) parts = verString.split('.') major = int(parts[0]) minor = int(parts[1]) bugfix = int(parts[2]) result = (sys.platform, bugfix, major, minor) except Exception, e: self.notify.debug('getMacOsInfo %s' % str(e)) self.notify.debug('getMacOsInfo returning %s' % str(result)) return result def checkAvOnDistrict(self, av, context): self.sendUpdate('checkAvOnDistrict', [context, av.doId])