oldschool-toontown/otp/ai/TimeManager.py

344 lines
14 KiB
Python
Raw Normal View History

from panda3d.core import *
2019-11-02 17:27:54 -05:00
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
2021-06-29 20:58:21 -05:00
from otp.otpbase.PythonUtil import describeException
2019-11-02 17:27:54 -05:00
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)
2019-11-02 17:27:54 -05:00
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):
2021-06-29 20:58:21 -05:00
info = describeException()
2019-11-02 17:27:54 -05:00
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)
2019-11-02 17:27:54 -05:00
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)
2019-11-02 17:27:54 -05:00
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('<key>ProductUserVisibleVersion</key>\\s*' + '<string>(.*?)</string>', 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:
2019-11-02 17:27:54 -05:00
self.notify.debug('getMacOsInfo %s' % str(e))
self.notify.debug('getMacOsInfo returning %s' % str(result))
return result