From ef0ba78f1d3dcdced9fafa5d05331ffe90c728c9 Mon Sep 17 00:00:00 2001 From: Darren Ranalli Date: Wed, 21 May 2008 18:29:13 +0000 Subject: [PATCH] added CRDataCache --- direct/src/distributed/CRDataCache.py | 113 ++++++++++++++++++ direct/src/distributed/CachedDOData.py | 22 ++++ direct/src/distributed/ClientRepository.py | 2 + .../src/distributed/ClientRepositoryBase.py | 4 + direct/src/distributed/DistributedObject.py | 36 ++++++ direct/src/distributed/OldClientRepository.py | 1 + 6 files changed, 178 insertions(+) create mode 100755 direct/src/distributed/CRDataCache.py create mode 100755 direct/src/distributed/CachedDOData.py diff --git a/direct/src/distributed/CRDataCache.py b/direct/src/distributed/CRDataCache.py new file mode 100755 index 0000000000..835378d8da --- /dev/null +++ b/direct/src/distributed/CRDataCache.py @@ -0,0 +1,113 @@ +from direct.distributed.CachedDOData import CachedDOData + +class CRDataCache: + # Stores cached data for DistributedObjects between instantiations on the client + + def __init__(self): + self._doId2name2data = {} + # maximum # of objects we will cache data for + self._size = config.GetInt('crdatacache-size', 10) + assert self._size > 0 + # used to preserve the cache size + self._junkIndex = 0 + + def destroy(self): + del self._doId2name2data + + def setCachedData(self, doId, name, data): + # stores a set of named data for a DistributedObject + assert isinstance(data, CachedDOData) + if len(self._doId2name2data) >= self._size: + # cache is full, throw out a random doId's data + if self._junkIndex >= len(self._doId2name2data): + self._junkIndex = 0 + junkDoId = self._doId2name2data.keys()[self._junkIndex] + self._junkIndex += 1 + for name in self._doId2name2data[junkDoId]: + self._doId2name2data[junkDoId][name].flush() + del self._doId2name2data[junkDoId] + + self._doId2name2data.setdefault(doId, {}) + cachedData = self._doId2name2data[doId].get(name) + if cachedData: + cachedData.flush() + cachedData.destroy() + self._doId2name2data[doId][name] = data + + def hasCachedData(self, doId): + return doId in self._doId2name2data + + def popCachedData(self, doId): + # retrieves all cached data for a DistributedObject and removes it from the cache + data = self._doId2name2data[doId] + del self._doId2name2data[doId] + return data + + def flush(self): + # get rid of all cached data + for doId in self._doId2name2data: + for name in self._doId2name2data[doId]: + self._doId2name2data[doId][name].flush() + self._doId2name2data = {} + + if __debug__: + def _startMemLeakCheck(self): + self._len = len(self._doId2name2data) + + def _stopMemLeakCheck(self): + del self._len + + def _checkMemLeaks(self): + assert self._len == len(self._doId2name2data) + +if __debug__: + class TestCachedData(CachedDOData): + def __init__(self): + CachedDOData.__init__(self) + self._destroyed = False + self._flushed = False + def destroy(self): + CachedDOData.destroy(self) + self._destroyed = True + def flush(self): + CachedDOData.flush(self) + self._flushed = True + + dc = CRDataCache() + dc._startMemLeakCheck() + + cd = CachedDOData() + cd.foo = 34 + dc.setCachedData(1, 'testCachedData', cd) + del cd + cd = CachedDOData() + cd.bar = 45 + dc.setCachedData(1, 'testCachedData2', cd) + del cd + assert dc.hasCachedData(1) + assert dc.hasCachedData(1) + assert not dc.hasCachedData(2) + # data is dict of dataName->data + data = dc.popCachedData(1) + assert len(data) == 2 + assert 'testCachedData' in data + assert 'testCachedData2' in data + assert data['testCachedData'].foo == 34 + assert data['testCachedData2'].bar == 45 + for cd in data.itervalues(): + cd.flush() + del data + dc._checkMemLeaks() + + cd = CachedDOData() + cd.bar = 1234 + dc.setCachedData(43, 'testCachedData2', cd) + del cd + assert dc.hasCachedData(43) + dc.flush() + dc._checkMemLeaks() + + dc._stopMemLeakCheck() + dc.destroy() + del dc + diff --git a/direct/src/distributed/CachedDOData.py b/direct/src/distributed/CachedDOData.py new file mode 100755 index 0000000000..a780815c72 --- /dev/null +++ b/direct/src/distributed/CachedDOData.py @@ -0,0 +1,22 @@ + +class CachedDOData: + # base class for objects that are used to store data in the CRDataCache + # + # stores a minimal set of cached data for DistributedObjects between instantiations + + def __init__(self): + # override and store cached data + # this object now owns the data + # ownership will either pass back to another instantion of the object, + # or the data will be flushed + pass + + def destroy(self): + # override and handle this object being destroyed + # this is destruction of this object, not the cached data (see flush) + pass + + def flush(self): + # override and destroy the cached data + # cached data is typically created by the DistributedObject and destroyed here + pass diff --git a/direct/src/distributed/ClientRepository.py b/direct/src/distributed/ClientRepository.py index 79737a242d..7e3263a9c3 100644 --- a/direct/src/distributed/ClientRepository.py +++ b/direct/src/distributed/ClientRepository.py @@ -58,6 +58,7 @@ class ClientRepository(ClientRepositoryBase): obj.doId = id self.doId2do[id] = obj obj.generateInit() + obj._retrieveCachedData() obj.generate() obj.announceGenerate() datagram = dclass.clientFormatGenerate(obj, id, zoneId, optionalFields) @@ -201,6 +202,7 @@ class ClientRepository(ClientRepositoryBase): self.doId2do[doId] = distObj # Update the required fields distObj.generateInit() # Only called when constructed + distObj._retrieveCachedData() distObj.generate() distObj.updateRequiredFields(dclass, di) # updateRequiredFields calls announceGenerate diff --git a/direct/src/distributed/ClientRepositoryBase.py b/direct/src/distributed/ClientRepositoryBase.py index 1c3cfde7b2..c66aca6ee3 100644 --- a/direct/src/distributed/ClientRepositoryBase.py +++ b/direct/src/distributed/ClientRepositoryBase.py @@ -3,6 +3,7 @@ from MsgTypes import * from direct.task import Task from direct.directnotify import DirectNotifyGlobal import CRCache +from direct.distributed.CRDataCache import CRDataCache from direct.distributed.ConnectionRepository import ConnectionRepository from direct.showbase import PythonUtil import ParentMgr @@ -44,6 +45,7 @@ class ClientRepositoryBase(ConnectionRepository): self.readDCFile(dcFileNames) self.cache=CRCache.CRCache() + self.doDataCache = CRDataCache() self.cacheOwner=CRCache.CRCache() self.serverDelta = 0 @@ -365,6 +367,7 @@ class ClientRepositoryBase(ConnectionRepository): self.doId2do[doId] = distObj # Update the required fields distObj.generateInit() # Only called when constructed + distObj._retrieveCachedData() distObj.generate() distObj.setLocation(parentId, zoneId) distObj.updateRequiredFields(dclass, di) @@ -412,6 +415,7 @@ class ClientRepositoryBase(ConnectionRepository): self.doId2do[doId] = distObj # Update the required fields distObj.generateInit() # Only called when constructed + distObj._retrieveCachedData() distObj.generate() distObj.setLocation(parentId, zoneId) distObj.updateRequiredOtherFields(dclass, di) diff --git a/direct/src/distributed/DistributedObject.py b/direct/src/distributed/DistributedObject.py index 88335588a1..2dc78442c7 100644 --- a/direct/src/distributed/DistributedObject.py +++ b/direct/src/distributed/DistributedObject.py @@ -154,6 +154,35 @@ class DistributedObject(DistributedObjectBase): def getNeverDisable(self): return self.neverDisable + def _retrieveCachedData(self): + # once we know our doId, grab any data that might be stored in the data cache + # from the last time we were on the client + if self.cr.doDataCache.hasCachedData(self.doId): + self._cachedData = self.cr.doDataCache.popCachedData(self.doId) + + def setCachedData(self, name, data): + assert type(name) == type('') + # ownership of the data passes to the repository data cache + self.cr.doDataCache.setCachedData(self.doId, name, data) + + def hasCachedData(self, name): + assert type(name) == type('') + if not hasattr(self, '_cachedData'): + return False + return name in self._cachedData + + def getCachedData(self, name): + assert type(name) == type('') + # ownership of the data passes to the caller of this method + data = self._cachedData[name] + del self._cachedData[name] + return data + + def flushCachedData(self, name): + assert type(name) == type('') + # call this to throw out cached data from a previous instantiation + self._cachedData[name].flush() + def setCacheable(self, bool): assert bool == 1 or bool == 0 self.cacheable = bool @@ -272,6 +301,13 @@ class DistributedObject(DistributedObjectBase): # after this is called, the object is no longer a DistributedObject # but may still be used as a DelayDeleted object self.destroyDoStackTrace = StackTrace() + # check for leftover cached data that was not retrieved or flushed by this object + # this will catch typos in the data name in calls to get/setCachedData + if hasattr(self, '_cachedData'): + for name, cachedData in self._cachedData.iteritems(): + self.notify.warning('flushing unretrieved cached data: %s' % name) + cachedData.flush() + del self._cachedData self.cr = None self.dclass = None diff --git a/direct/src/distributed/OldClientRepository.py b/direct/src/distributed/OldClientRepository.py index d613370212..65c3e47e86 100644 --- a/direct/src/distributed/OldClientRepository.py +++ b/direct/src/distributed/OldClientRepository.py @@ -58,6 +58,7 @@ class OldClientRepository(ClientRepositoryBase): obj.doId = id self.doId2do[id] = obj obj.generateInit() + obj._retrieveCachedData() obj.generate() obj.announceGenerate() datagram = dclass.clientFormatGenerate(obj, id, zoneId, optionalFields)