2
0
Эх сурвалжийг харах

levelAI only sends spec if hash is different, spec is saved by AI

Darren Ranalli 22 жил өмнө
parent
commit
c27e85f890

+ 21 - 18
direct/src/level/DistributedLevel.py

@@ -98,24 +98,37 @@ class DistributedLevel(DistributedObject.DistributedObject,
         # announceGenerate(). Note that we have to call
         # gotAllRequired() in the last 'faux-required' DC update
         # handler. If you add another field, move this to the last one.
+        self.privGotAllRequired()
+
+    def privGotAllRequired(self):
+        self.levelAnnounceGenerate()
+    def levelAnnounceGenerate(self):
+        pass
+
+    def initializeLevel(self, levelSpec):
+        """subclass should call this as soon as it's located its level spec.
+        Must be called after obj has been generated."""
         if __debug__:
-            # if we're in debug, ask the server if it wants to send us
+            # if we're in debug, give the server the opportunity to send us
             # a full spec
-            self.sendUpdate('requestCurrentLevelSpec', [])
+            self.candidateSpec = levelSpec
+            self.sendUpdate('requestCurrentLevelSpec', [hash(levelSpec)])
         else:
-            self.gotAllRequired()
+            self.privGotSpec(levelSpec)
 
     if __debug__:
         def setSpecSenderDoId(self, doId):
-            DistributedLevel.notify.debug(
-                'setSpecSenderDoId: %s' % doId)
+            DistributedLevel.notify.debug('setSpecSenderDoId: %s' % doId)
             blobSender = toonbase.tcr.doId2do[doId]
 
             def setSpecBlob(specBlob, blobSender=blobSender, self=self):
                 blobSender.sendAck()
                 from LevelSpec import LevelSpec
-                self.curSpec = eval(specBlob)
-                self.gotAllRequired()
+                spec = eval(specBlob)
+                if spec is None:
+                    spec = self.candidateSpec
+                del self.candidateSpec
+                self.privGotSpec(spec)
 
             if blobSender.isComplete():
                 setSpecBlob(blobSender.getBlob())
@@ -124,17 +137,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
                 blobSender.setDoneEvent(evtName)
                 self.acceptOnce(evtName, setSpecBlob)
 
-    def gotAllRequired(self):
-        self.levelAnnounceGenerate()
-    def levelAnnounceGenerate(self):
-        pass
-
-    def initializeLevel(self, levelSpec):
-        """subclass should call this as soon as it's located its level spec.
-        Must be called after obj has been generated."""
-        # if the AI sent us a full spec, use it instead
-        if self.curSpec is not None:
-            levelSpec = self.curSpec
+    def privGotSpec(self, levelSpec):
         Level.Level.initializeLevel(self, self.doId, levelSpec,
                                     self.scenarioIndex)
 

+ 28 - 4
direct/src/level/DistributedLevelAI.py

@@ -18,7 +18,7 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
         Level.Level.__init__(self)
         # this is one of the required fields
         self.zoneId = zoneId
-        self.hasBeenEdited = 0
+        self.modified = 0
 
     def generate(self, levelSpec):
         self.notify.debug('generate')
@@ -38,6 +38,8 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
 
     def delete(self):
         self.notify.debug('delete')
+        if __debug__:
+            self.saveSpec()
         self.destroyLevel()
         DistributedObjectAI.DistributedObjectAI.delete(self)
 
@@ -91,11 +93,33 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
             # send a copy to the client-side level obj
             self.sendUpdate('setAttribChange',
                             [entId, attribName, valueStr])
-            self.hasBeenEdited = 1
 
-        def requestCurrentLevelSpec(self):
+            self.modified = 1
+            self.scheduleSave()
+
+        SavePeriod = simbase.config.GetFloat('factory-save-period', 10)
+
+        def scheduleSave(self):
+            if hasattr(self, 'saveTask'):
+                return
+            self.saveTask = taskMgr.doMethodLater(
+                DistributedLevelAI.SavePeriod,
+                self.saveSpec,
+                self.uniqueName('saveSpec'))
+
+        def saveSpec(self, task=None):
+            DistributedLevelAI.notify.info('saving spec')
+            if hasattr(self, 'saveTask'):
+                del self.saveTask
+            if self.modified:
+                self.levelSpec.saveToDisk()
+                self.modified = 0
+
+        def requestCurrentLevelSpec(self, specHash):
             senderId = self.air.msgSender
-            spec = self.levelSpec
+            spec = None
+            if hash(self.levelSpec) != specHash:
+                spec = self.levelSpec
             specStr = repr(spec)
 
             import DistributedLargeBlobSenderAI

+ 49 - 1
direct/src/level/LevelSpec.py

@@ -10,8 +10,9 @@ class LevelSpec:
     saving out modified spec data"""
     notify = DirectNotifyGlobal.directNotify.newCategory("LevelSpec")
     
-    def __init__(self, specDict, scenario=0, entTypeReg=None):
+    def __init__(self, specDict, scenario=0, filename=None, entTypeReg=None):
         self.specDict = specDict
+        self.filename = filename
         self.entTypeReg = entTypeReg
 
         # this maps an entId to the dict that holds its spec;
@@ -141,6 +142,50 @@ class LevelSpec:
             # name of module that should be imported by spec py file
             return 'SpecImports'
 
+        def saveToDisk(self, filename=None):
+            """returns zero on failure"""
+            import os
+
+            if filename is None:
+                filename = self.filename
+
+            # create a backup
+            try:
+                # does the file exist?
+                exists = 0
+                try:
+                    os.stat(filename)
+                    exists = 1
+                except OSError:
+                    pass
+                if exists:
+                    def getBackupFilename(num, filename=filename):
+                        return '%s.%03i' % (filename, num)
+                    numBackups = 50
+                    try:
+                        os.unlink(getBackupFilename(numBackups-1))
+                    except OSError:
+                        pass
+                    for i in range(numBackups-1,0,-1):
+                        try:
+                            os.rename(getBackupFilename(i-1),
+                                      getBackupFilename(i))
+                        except OSError:
+                            pass
+                    os.rename(filename, getBackupFilename(0))
+            except OSError, e:
+                LevelSpec.notify.warning('error during backup: %s' % str(e))
+
+            retval = 1
+            # wb to create a UNIX-format file
+            f = file(filename, 'wb')
+            try:
+                f.write(self.getPrettyString())
+            except IOError:
+                retval = 0
+            f.close()
+            return retval
+
         def getPrettyString(self):
             """Returns a string that contains the spec data, nicely formatted.
             This should be used when writing the spec out to file."""
@@ -256,6 +301,9 @@ class LevelSpec:
                 (levelSpec, self.specDict)
                 )
 
+        def __hash__(self):
+            return hash(repr(self))
+
         def __repr__(self):
             return 'LevelSpec(%s, scenario=%s)' % (repr(self.specDict),
                                                    self.scenario)