from pandac.PandaModules import * from direct.showbase.DirectObject import * from direct.distributed.ClockDelta import * from direct.task import Task from direct.distributed import DistributedObject from direct.directnotify import DirectNotifyGlobal from otp.otpbase import OTPGlobals from direct.showbase import PythonUtil from direct.showbase import GarbageReport import base64 import time import os import sys import re 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') 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)) print('cpu info: %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 as 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]) def checkAvOnDistrictResult(self, context, avId, present): av = self.cr.getDo(avId) if av: av._zombieCheckResult(context, present)