oldschool-toontown/otp/ai/TimeManager.py
Little Cat 1801d2b9fb
all: replace pandac.PandaModules imports.
UD/AI + Client boots up.
2022-12-16 20:40:57 -04:00

343 lines
14 KiB
Python

from panda3d.core 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 otp.otpbase.PythonUtil import describeException
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 = 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('<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:
self.notify.debug('getMacOsInfo %s' % str(e))
self.notify.debug('getMacOsInfo returning %s' % str(result))
return result