Forráskód Böngészése

AI sends edited spec to clients, level is in its own zone to prevent race conditions

Darren Ranalli 22 éve
szülő
commit
0f7a02e1d3

+ 78 - 33
direct/src/level/DistributedLevel.py

@@ -51,13 +51,16 @@ class DistributedLevel(DistributedObject.DistributedObject,
         self.zonesEnteredList = []
 
     def generate(self):
-        self.notify.debug('generate')
+        DistributedLevel.notify.debug('generate')
         DistributedObject.DistributedObject.generate(self)
 
         # this dict stores entity reparents if the parent hasn't been
         # created yet
         self.parent2ChildIds = {}
 
+        # if the AI sends us a full spec, it will be put here
+        self.curSpec = None
+
         # Most (if not all) of the timed entities of levels
         # run on looping intervals that are started once based on
         # the level's start time.
@@ -70,38 +73,77 @@ class DistributedLevel(DistributedObject.DistributedObject,
         # add factory menu to SpeedChat
         toonbase.localToon.chatMgr.chatInputSpeedChat.addFactoryMenu()
 
+    # the real required fields
+    def setLevelZoneId(self, zoneId):
+        # this is the zone that the level is in; we should listen to this
+        # zone the entire time we're in here
+        self.levelZone = zoneId
+
     # "required" fields (these ought to be required fields, but
     # the AI level obj doesn't know the data values until it has been
     # generated.)
     def setZoneIds(self, zoneIds):
-        self.notify.debug('setZoneIds: %s' % zoneIds)
+        DistributedLevel.notify.debug('setZoneIds: %s' % zoneIds)
         self.zoneIds = zoneIds
 
     def setStartTimestamp(self, timestamp):
-        self.notify.debug('setStartTimestamp: %s' % timestamp)
+        DistributedLevel.notify.debug('setStartTimestamp: %s' % timestamp)
         self.startTime = globalClockDelta.networkToLocalTime(timestamp,bits=32)
 
     def setScenarioIndex(self, scenarioIndex):
         self.scenarioIndex = scenarioIndex
 
-        # ugly hack: we treat these DC fields as if they were required,
+        # ugly hack: we treat a few DC fields as if they were required,
         # and use 'levelAnnounceGenerate()' in place of regular old
         # announceGenerate(). Note that we have to call
-        # levelAnnounceGenerate() in the last 'faux-required' DC update
+        # gotAllRequired() in the last 'faux-required' DC update
         # handler. If you add another field, move this to the last one.
-        self.levelAnnounceGenerate()
+        if __debug__:
+            # if we're in debug, ask the server if it wants to send us
+            # a full spec
+            self.sendUpdate('requestCurrentLevelSpec', [])
+        else:
+            self.gotAllRequired()
 
+    if __debug__:
+        def setSpecSenderDoId(self, 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()
+
+            if blobSender.isComplete():
+                setSpecBlob(blobSender.getBlob())
+            else:
+                evtName = self.uniqueName('specDone')
+                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."""
-        Level.Level.initializeLevel(self, self.doId,
-                                    levelSpec, self.scenarioIndex)
+        # if the AI sent us a full spec, use it instead
+        if self.curSpec is not None:
+            levelSpec = self.curSpec
+        Level.Level.initializeLevel(self, self.doId, levelSpec,
+                                    self.scenarioIndex)
+
+        # all of the local entities have been created now.
+        # TODO: have any of the distributed entities been created at this point?
 
-        # all of the entities have been created now.
         # there should not be any pending reparents left at this point
+        # TODO: is it possible for a local entity to be parented to a
+        # distributed entity? I think so!
         assert len(self.parent2ChildIds) == 0
         # make sure the zoneNums from the model match the zoneNums from
         # the zone entities
@@ -137,7 +179,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
             num2node = {}
             for potentialNode in potentialNodes:
                 name = potentialNode.getName()
-                self.notify.debug('potential match for %s: %s' %
+                DistributedLevel.notify.debug('potential match for %s: %s' %
                                   (baseString, name))
                 try:
                     num = int(name[len(baseString):])
@@ -155,7 +197,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
 
         self.zoneNums = self.zoneNum2node.keys()
         self.zoneNums.sort()
-        self.notify.debug('zones: %s' % self.zoneNums)
+        DistributedLevel.notify.debug('zones: %s' % self.zoneNums)
 
         # fix up the floor collisions for walkable zones *before*
         # any entities get put under the model
@@ -196,20 +238,21 @@ class DistributedLevel(DistributedObject.DistributedObject,
         self.doorwayNum2Node = findNumberedNodes('Doorway')
 
     def announceGenerate(self):
-        self.notify.debug('announceGenerate')
+        DistributedLevel.notify.debug('announceGenerate')
         DistributedObject.DistributedObject.announceGenerate(self)
 
     def disable(self):
-        self.notify.debug('disable')
+        DistributedLevel.notify.debug('disable')
 
         # geom is owned by the levelMgr
-        del self.geom
+        if hasattr(self, 'geom'):
+            del self.geom
 
         self.destroyLevel()
         DistributedObject.DistributedObject.disable(self)
         self.ignoreAll()
 
-        # NOTE: this should be moved to FactoryInterior
+        # NOTE:  this should be moved to FactoryInterior
         if self.smallTitleText:
             self.smallTitleText.cleanup()
             self.smallTitleText = None
@@ -218,11 +261,11 @@ class DistributedLevel(DistributedObject.DistributedObject,
             self.titleText = None
         self.zonesEnteredList = []
         
-        # NOTE:  this should be moved to ZoneEntity.disable
+        # NOTE: this should be moved to ZoneEntity.disable
         toonbase.localToon.chatMgr.chatInputSpeedChat.removeFactoryMenu()
 
     def delete(self):
-        self.notify.debug('delete')
+        DistributedLevel.notify.debug('delete')
         DistributedObject.DistributedObject.delete(self)
         # remove factory menu to SpeedChat
         toonbase.localToon.chatMgr.chatInputSpeedChat.removeFactoryMenu()
@@ -242,7 +285,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
             entity.reparentTo(parent.getNodePath())
         else:
             # parent hasn't been created yet; schedule the reparent
-            self.notify.debug(
+            DistributedLevel.notify.debug(
                 'entity %s requesting reparent to %s, not yet created' %
                 (entity, parentId))
 
@@ -260,7 +303,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
                     parent=self.getEntity(parentId)
                     for entId in self.parent2ChildIds[parentId]:
                         entity=self.getEntity(entId)
-                        self.notify.debug(
+                        DistributedLevel.notify.debug(
                             'performing pending reparent of %s to %s' %
                             (entity, parent))
                         entity.reparentTo(parent.getNodePath())
@@ -302,8 +345,9 @@ class DistributedLevel(DistributedObject.DistributedObject,
                 try:
                     zoneNum = int(name[prefixLen:])
                 except:
-                    self.notify.debug('Invalid zone floor collision node: %s'
-                                      % name)
+                    DistributedLevel.notify.debug(
+                        'Invalid zone floor collision node: %s'
+                        % name)
                 else:
                     self.camEnterZone(zoneNum)
         self.accept('on-floor', handleCameraRayFloorCollision)
@@ -315,7 +359,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
             self.setVisibility(zoneNums)
 
     def toonEnterZone(self, zoneNum):
-        self.notify.debug('toonEnterZone%s' % zoneNum)
+        DistributedLevel.notify.debug('toonEnterZone%s' % zoneNum)
         if zoneNum != self.lastToonZone:
             self.lastToonZone = zoneNum
             print "made zone transition to %s" % zoneNum
@@ -324,11 +368,11 @@ class DistributedLevel(DistributedObject.DistributedObject,
             self.spawnTitleText()
             
     def camEnterZone(self, zoneNum):
-        self.notify.debug('camEnterZone%s' % zoneNum)
+        DistributedLevel.notify.debug('camEnterZone%s' % zoneNum)
         self.enterZone(zoneNum)
 
     def enterZone(self, zoneNum):
-        self.notify.debug("entering zone %s" % zoneNum)
+        DistributedLevel.notify.debug("entering zone %s" % zoneNum)
 
         if not DistributedLevel.WantVisibility:
             return
@@ -360,10 +404,10 @@ class DistributedLevel(DistributedObject.DistributedObject,
                 else:
                     removedZoneNums.append(vz)
             # show the new, hide the old
-            self.notify.debug('showing zones %s' % addedZoneNums)
+            DistributedLevel.notify.debug('showing zones %s' % addedZoneNums)
             for az in addedZoneNums:
                 self.showZone(az)
-            self.notify.debug('hiding zones %s' % removedZoneNums)
+            DistributedLevel.notify.debug('hiding zones %s' % removedZoneNums)
             for rz in removedZoneNums:
                 self.hideZone(rz)
 
@@ -376,14 +420,15 @@ class DistributedLevel(DistributedObject.DistributedObject,
         # accepts list of visible zone numbers
         # convert the zone numbers into their actual zoneIds
         # always include Toontown and factory uberZones
-        factoryUberZone = self.getZoneId(zoneNum=Level.Level.UberZoneNum)
-        visibleZoneIds = [ToontownGlobals.UberZone, factoryUberZone]
+        uberZone = self.getZoneId(zoneNum=Level.Level.UberZoneNum)
+        # the level itself is in the 'level zone'
+        visibleZoneIds = [ToontownGlobals.UberZone, self.levelZone, uberZone]
         for vz in vizList:
             visibleZoneIds.append(self.getZoneId(zoneNum=vz))
         assert(uniqueElements(visibleZoneIds))
-        self.notify.debug('new viz list: %s' % visibleZoneIds)
+        DistributedLevel.notify.debug('new viz list: %s' % visibleZoneIds)
 
-        toonbase.tcr.sendSetZoneMsg(factoryUberZone, visibleZoneIds)
+        toonbase.tcr.sendSetZoneMsg(self.levelZone, visibleZoneIds)
 
     if __debug__:
         # level editing stuff
@@ -455,12 +500,12 @@ class DistributedLevel(DistributedObject.DistributedObject,
             taskMgr.add(seq, "titleText")
         
     def showTitleTextTask(self, task):
-        assert(self.notify.debug("hideTitleTextTask()"))
+        assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
         self.titleText.show()
         return Task.done
 
     def hideTitleTextTask(self, task):
-        assert(self.notify.debug("hideTitleTextTask()"))
+        assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
         self.titleText.hide()
         return Task.done
 
@@ -472,7 +517,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
         return Task.done
     
     def hideSmallTitleTextTask(self, task):
-        assert(self.notify.debug("hideTitleTextTask()"))
+        assert(DistributedLevel.notify.debug("hideTitleTextTask()"))
         self.smallTitleText.hide()
         return Task.done
             

+ 23 - 12
direct/src/level/DistributedLevelAI.py

@@ -1,5 +1,6 @@
 """DistributedLevelAI.py: contains the DistributedLevelAI class"""
 
+from AIBaseGlobal import *
 from ClockDelta import *
 import DistributedObjectAI
 import Level
@@ -15,7 +16,9 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
     def __init__(self, air, zoneId):
         DistributedObjectAI.DistributedObjectAI.__init__(self, air)
         Level.Level.__init__(self)
-        self.uberZoneId = zoneId
+        # this is one of the required fields
+        self.zoneId = zoneId
+        self.hasBeenEdited = 0
 
     def generate(self, levelSpec):
         self.notify.debug('generate')
@@ -27,6 +30,12 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
         self.sendUpdate('setStartTimestamp', [self.startTimestamp])
         self.sendUpdate('setScenarioIndex', [self.scenarioIndex])
 
+    def getLevelZoneId(self):
+        """no entities should be generated in the level's zone; it causes
+        nasty race conditions on the client if there are entities in the
+        same zone with the level"""
+        return self.zoneId
+
     def delete(self):
         self.notify.debug('delete')
         self.destroyLevel()
@@ -45,8 +54,7 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
         wc = WeightedChoice.WeightedChoice(lol)
         scenarioIndex = wc.choose()[1]
 
-        Level.Level.initializeLevel(self, self.doId,
-                                    levelSpec, scenarioIndex)
+        Level.Level.initializeLevel(self, self.doId, levelSpec, scenarioIndex)
 
     def createEntityCreator(self):
         """Create the object that will be used to create Entities.
@@ -83,14 +91,17 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
             # send a copy to the client-side level obj
             self.sendUpdate('setAttribChange',
                             [entId, attribName, valueStr])
+            self.hasBeenEdited = 1
 
-        def getCurrentLevelSpec(self):
-            """returns the complete, current spec, including any edits"""
-            return self.levelSpec
+        def requestCurrentLevelSpec(self):
+            senderId = self.air.msgSender
+            spec = self.levelSpec
+            specStr = repr(spec)
 
-        """
-        def getSpecOverride(self):
-            # This is the value we'll send until someone actually edits
-            # the level
-            return repr(None)
-        """
+            import DistributedLargeBlobSenderAI
+            largeBlob = DistributedLargeBlobSenderAI.\
+                        DistributedLargeBlobSenderAI(
+                self.air, self.zoneId, senderId, specStr,
+                useDisk=simbase.config.GetBool('spec-by-disk', 0))
+            self.sendUpdateToAvatarId(senderId,
+                                      'setSpecSenderDoId', [largeBlob.doId])

+ 2 - 0
direct/src/level/EntityTypeRegistry.py

@@ -39,6 +39,7 @@ class EntityTypeRegistry:
 
     def getAttribNames(self, entityTypeName):
         """ returns ordered list of attribute names for entity type """
+        assert entityTypeName in self.typeName2class
         # TODO: precompute this
         attribDescs = self.typeName2class[entityTypeName]._attribDescs
         attribNames = []
@@ -48,6 +49,7 @@ class EntityTypeRegistry:
 
     def getAttribDescs(self, entityTypeName):
         """ returns dict of attribName -> attribDescriptor """
+        assert entityTypeName in self.typeName2class
         # TODO: precompute this
         attribDescs = self.typeName2class[entityTypeName]._attribDescs
         attribNames = self.getAttribNames(entityTypeName)

+ 0 - 1
direct/src/level/EntityTypes.py

@@ -9,7 +9,6 @@ class Entity:
         ('comment', ''),
         )
 
-
 class ActiveCell(Entity):
     type = 'activeCell'
     attribs = (

+ 7 - 0
direct/src/level/Level.py

@@ -35,6 +35,7 @@ class Level:
 
     def __init__(self):
         self.levelSpec = None
+        self.initialized = 0
 
     def initializeLevel(self, levelId, levelSpec, scenarioIndex):
         """ subclass should call this as soon as it has located
@@ -65,7 +66,13 @@ class Level:
         self.levelSpec.setAttribChangeEventName(self.getAttribChangeEvent())
         self.accept(self.getAttribChangeEvent(), self.handleAttribChange)
 
+        self.initialized = 1
+
+    def isInitialized(self):
+        return self.initialized
+
     def destroyLevel(self):
+        self.initialized = 0
         if hasattr(self, 'createdEntities'):
             # destroy the entities in reverse order
             while len(self.createdEntities) > 0:

+ 3 - 8
direct/src/level/ZoneEntityAI.py

@@ -6,14 +6,9 @@ class ZoneEntityAI(ZoneEntityBase.ZoneEntityBase):
     def __init__(self, level, entId):
         ZoneEntityBase.ZoneEntityBase.__init__(self, level, entId)
 
-        if self.isUberZone():
-            print 'uberZone'
-            # the uberzone is already allocated
-            self.setZoneId(self.level.uberZoneId)
-        else:
-            # allocate a network zoneId for this zone
-            # there is error checking in air.allocateZone
-            self.setZoneId(self.level.air.allocateZone())
+        # allocate a network zoneId for this zone
+        # there is error checking in air.allocateZone
+        self.setZoneId(self.level.air.allocateZone())
         
     def destroy(self):
         if not self.isUberZone():