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

View File

@ -1,5 +1,6 @@
"""DistributedLevelAI.py: contains the DistributedLevelAI class""" """DistributedLevelAI.py: contains the DistributedLevelAI class"""
from AIBaseGlobal import *
from ClockDelta import * from ClockDelta import *
import DistributedObjectAI import DistributedObjectAI
import Level import Level
@ -15,7 +16,9 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
def __init__(self, air, zoneId): def __init__(self, air, zoneId):
DistributedObjectAI.DistributedObjectAI.__init__(self, air) DistributedObjectAI.DistributedObjectAI.__init__(self, air)
Level.Level.__init__(self) Level.Level.__init__(self)
self.uberZoneId = zoneId # this is one of the required fields
self.zoneId = zoneId
self.hasBeenEdited = 0
def generate(self, levelSpec): def generate(self, levelSpec):
self.notify.debug('generate') self.notify.debug('generate')
@ -27,6 +30,12 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
self.sendUpdate('setStartTimestamp', [self.startTimestamp]) self.sendUpdate('setStartTimestamp', [self.startTimestamp])
self.sendUpdate('setScenarioIndex', [self.scenarioIndex]) 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): def delete(self):
self.notify.debug('delete') self.notify.debug('delete')
self.destroyLevel() self.destroyLevel()
@ -45,8 +54,7 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
wc = WeightedChoice.WeightedChoice(lol) wc = WeightedChoice.WeightedChoice(lol)
scenarioIndex = wc.choose()[1] scenarioIndex = wc.choose()[1]
Level.Level.initializeLevel(self, self.doId, Level.Level.initializeLevel(self, self.doId, levelSpec, scenarioIndex)
levelSpec, scenarioIndex)
def createEntityCreator(self): def createEntityCreator(self):
"""Create the object that will be used to create Entities. """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 # send a copy to the client-side level obj
self.sendUpdate('setAttribChange', self.sendUpdate('setAttribChange',
[entId, attribName, valueStr]) [entId, attribName, valueStr])
self.hasBeenEdited = 1
def getCurrentLevelSpec(self): def requestCurrentLevelSpec(self):
"""returns the complete, current spec, including any edits""" senderId = self.air.msgSender
return self.levelSpec spec = self.levelSpec
specStr = repr(spec)
""" import DistributedLargeBlobSenderAI
def getSpecOverride(self): largeBlob = DistributedLargeBlobSenderAI.\
# This is the value we'll send until someone actually edits DistributedLargeBlobSenderAI(
# the level self.air, self.zoneId, senderId, specStr,
return repr(None) 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): def getAttribNames(self, entityTypeName):
""" returns ordered list of attribute names for entity type """ """ returns ordered list of attribute names for entity type """
assert entityTypeName in self.typeName2class
# TODO: precompute this # TODO: precompute this
attribDescs = self.typeName2class[entityTypeName]._attribDescs attribDescs = self.typeName2class[entityTypeName]._attribDescs
attribNames = [] attribNames = []
@ -48,6 +49,7 @@ class EntityTypeRegistry:
def getAttribDescs(self, entityTypeName): def getAttribDescs(self, entityTypeName):
""" returns dict of attribName -> attribDescriptor """ """ returns dict of attribName -> attribDescriptor """
assert entityTypeName in self.typeName2class
# TODO: precompute this # TODO: precompute this
attribDescs = self.typeName2class[entityTypeName]._attribDescs attribDescs = self.typeName2class[entityTypeName]._attribDescs
attribNames = self.getAttribNames(entityTypeName) attribNames = self.getAttribNames(entityTypeName)

View File

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

View File

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

View File

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