Darren Ranalli 17 anni fa
parent
commit
ef0ba78f1d

+ 113 - 0
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
+    

+ 22 - 0
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

+ 2 - 0
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

+ 4 - 0
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)

+ 36 - 0
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
 

+ 1 - 0
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)