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

This commit is contained in:
Darren Ranalli 2003-10-06 22:50:39 +00:00
parent c04a6237ec
commit 0f7a02e1d3
6 changed files with 113 additions and 54 deletions

View File

@ -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

View File

@ -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])

View File

@ -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)

View File

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

View File

@ -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:

View File

@ -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():