From 5c113af66a1bac2a4fa9ae703b31f02ec678ed7a Mon Sep 17 00:00:00 2001 From: Darren Ranalli Date: Fri, 26 Sep 2003 01:58:46 +0000 Subject: [PATCH] added LevelSpec data wrapper --- direct/src/level/DistributedLevel.py | 14 +- direct/src/level/DistributedLevelAI.py | 24 +-- direct/src/level/Level.py | 33 ++-- direct/src/level/LevelSpec.py | 203 +++++++++++++++++++++++++ 4 files changed, 234 insertions(+), 40 deletions(-) create mode 100755 direct/src/level/LevelSpec.py diff --git a/direct/src/level/DistributedLevel.py b/direct/src/level/DistributedLevel.py index 3483c24dd1..fe9897bc0b 100755 --- a/direct/src/level/DistributedLevel.py +++ b/direct/src/level/DistributedLevel.py @@ -64,11 +64,11 @@ class DistributedLevel(DistributedObject.DistributedObject, def levelAnnounceGenerate(self): pass - def initializeLevel(self, spec): - """subclass should call this as soon as it's located its spec data. + 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, - spec, self.scenarioIndex) + levelSpec, self.scenarioIndex) # all of the entities have been created now. # there should not be any pending reparents left at this point @@ -287,7 +287,7 @@ class DistributedLevel(DistributedObject.DistributedObject, return zoneEntId = self.zoneNum2entId[zoneNum] - zoneSpec = self.entId2spec[zoneEntId] + zoneSpec = self.levelSpec.getEntitySpec(zoneEntId) # use dicts to efficiently ensure that there are no duplicates visibleZoneNums = list2dict([zoneNum]) visibleZoneNums.update(list2dict(zoneSpec['visibility'])) @@ -352,12 +352,12 @@ class DistributedLevel(DistributedObject.DistributedObject, if __debug__: # if someone has edited the level, we'll get the full up-to-date # spec in this message - def setSpecOverride(self, specStr): - if self.spec is not None: + def setLevelSpecOverride(self, specStr): + if self.levelSpec is not None: return try: - self.spec = eval(specStr) + self.levelSpec = eval(specStr) except Exception, e: print ('Exception in %s(%s):\n\t%s' % (lineInfo()[2], specStr, e)) diff --git a/direct/src/level/DistributedLevelAI.py b/direct/src/level/DistributedLevelAI.py index 23b1c4e73c..eb340eda96 100755 --- a/direct/src/level/DistributedLevelAI.py +++ b/direct/src/level/DistributedLevelAI.py @@ -17,11 +17,11 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI, Level.Level.__init__(self) self.uberZoneId = zoneId - def generate(self, spec): + def generate(self, levelSpec): self.notify.debug('generate') DistributedObjectAI.DistributedObjectAI.generate(self) - self.initializeLevel(spec) + self.initializeLevel(levelSpec) self.sendUpdate('setZoneIds', [self.zoneIds]) self.sendUpdate('setStartTimestamp', [self.startTimestamp]) @@ -32,19 +32,21 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI, self.destroyLevel() DistributedObjectAI.DistributedObjectAI.delete(self) - def initializeLevel(self, spec): + def initializeLevel(self, levelSpec): # record the level's start time so that we can sync the clients self.startTime = globalClock.getRealTime() self.startTimestamp = globalClockDelta.localToNetworkTime( self.startTime, bits=32) # choose a scenario - wc = WeightedChoice.WeightedChoice(spec['scenarios'], 1) - scenario = wc.choose() - scenarioIndex = spec['scenarios'].index(scenario) + # make list of lists: [(weight, scenarioIndex), ...] + lol = zip(levelSpec.getScenarioWeights(), + range(levelSpec.getNumScenarios())) + wc = WeightedChoice.WeightedChoice(lol) + scenarioIndex = wc.choose()[1] Level.Level.initializeLevel(self, self.doId, - spec, scenarioIndex) + levelSpec, scenarioIndex) def createEntityCreator(self): """Create the object that will be used to create Entities. @@ -56,7 +58,7 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI, # this func is called before the entity has been created; look # into the spec data, since we can't yet get a handle on the # object itself at this point - spec = self.entId2spec[entId] + spec = self.levelSpec.getEntitySpec(entId) type = spec['type'] if type == 'zone': if not hasattr(self, 'zoneNum2zoneId'): @@ -85,11 +87,11 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI, # send a copy of the entire spec for any new users that # might come in - ##self.sendUpdate('setSpecOverride', [repr(self.spec)]) + ##self.sendUpdate('setSpecOverride', [repr(self.levelSpec)]) - def getCurrentSpec(self): + def getCurrentLevelSpec(self): """returns the complete, current spec, including any edits""" - return self.spec + return self.levelSpec """ def getSpecOverride(self): diff --git a/direct/src/level/Level.py b/direct/src/level/Level.py index 7c9cd42acd..7429071a5f 100755 --- a/direct/src/level/Level.py +++ b/direct/src/level/Level.py @@ -34,31 +34,22 @@ class Level: UberZoneEntId = 0 def __init__(self): - self.spec = None + self.levelSpec = None - def initializeLevel(self, levelId, spec, scenarioIndex): + def initializeLevel(self, levelId, levelSpec, scenarioIndex): """ subclass should call this as soon as it has located its spec data """ self.levelId = levelId - self.spec = spec + self.levelSpec = levelSpec self.scenarioIndex = scenarioIndex - # create a complete set of global and scenario-specific entity specs - globalEntities = self.spec['globalEntities'] - scenarioEntities = self.spec['scenarios'][self.scenarioIndex][0] - entId2spec = {} - entId2spec.update(globalEntities) - entId2spec.update(scenarioEntities) - self.entId2spec = entId2spec + self.levelSpec.setScenario(self.scenarioIndex) # create some handy tables # entity type -> list of entIds - self.entType2ids = {} - for entId, spec in self.entId2spec.items(): - entType = spec['type'] - self.entType2ids.setdefault(entType, []) - self.entType2ids[entType].append(entId) + self.entType2ids = self.levelSpec.getEntType2ids( + self.levelSpec.getAllEntIds()) # there should be one and only one levelMgr assert len(self.entType2ids['levelMgr']) == 1 @@ -83,10 +74,8 @@ class Level: del self.createdEntities if hasattr(self, 'entities'): del self.entities - if hasattr(self, 'entId2spec'): - del self.entId2spec - if hasattr(self, 'spec'): - del self.spec + if hasattr(self, 'levelSpec'): + del self.levelSpec def createEntityCreator(self): self.notify.error( @@ -131,7 +120,7 @@ class Level: def createEntity(self, entId): assert not self.entities.has_key(entId) - spec = self.entId2spec[entId] + spec = self.levelSpec.getEntitySpec(entId) self.notify.debug('creating %s %s' % (spec['type'], entId)) entity = self.entityCreator.createEntity(entId) # NOTE: the entity is not considered to really be created until @@ -156,7 +145,7 @@ class Level: entities; this is called directly by Entity. """ entId = entity.entId - spec = self.entId2spec[entId] + spec = self.levelSpec.getEntitySpec(entId) # on initialization, set items directly on entity for key,value in spec.items(): if key in ('type', 'name', 'comment',): @@ -170,7 +159,7 @@ class Level: return self.entities.get(entId) def getEntityType(self, entId): - return self.entId2spec[entId]['type'] + return self.levelSpec.getEntityType(entId) def getZoneId(self, dummy=None, zoneNum=None, entId=None): """look up network zoneId by zoneNum or entId""" diff --git a/direct/src/level/LevelSpec.py b/direct/src/level/LevelSpec.py new file mode 100755 index 0000000000..6f3b8c6eb6 --- /dev/null +++ b/direct/src/level/LevelSpec.py @@ -0,0 +1,203 @@ +"""LevelSpec module: contains the LevelSpec class""" + +from PythonUtil import list2dict +import string + +class LevelSpec: + """contains spec data for a level, is responsible for handing the data + out upon request, as well as recording changes made during editing, and + saving out modified spec data""" + def __init__(self, specDict, scenario=0): + self.specDict = specDict + + # this maps an entId to the dict that holds its spec; + # entities are either in the global dict or a scenario dict + # update the map of entId to spec dict + self.entId2specDict = {} + self.entId2specDict.update( + list2dict(self.getGlobalEntIds(), + value=self.privGetGlobalEntityDict())) + for i in range(self.getNumScenarios()): + self.entId2specDict.update( + list2dict(self.getScenarioEntIds(i), + value=self.privGetScenarioEntityDict(i))) + + self.setScenario(scenario) + + def getNumScenarios(self): + return len(self.specDict['scenarios']) + + def getScenarioWeights(self): + weights = [] + for entry in self.specDict['scenarios']: + weights.append(entry[1]) + return weights + + def setScenario(self, scenario): + assert scenario in range(0, self.getNumScenarios()) + self.scenario = scenario + + def getScenario(self): + return self.scenario + + def getGlobalEntIds(self): + return self.privGetGlobalEntityDict().keys() + + def getScenarioEntIds(self, scenario=None): + if scenario is None: + scenario = self.scenario + return self.privGetScenarioEntityDict(scenario).keys() + + def getAllEntIds(self): + return self.getGlobalEntIds() + self.getScenarioEntIds() + + def getEntitySpec(self, entId): + assert entId in self.entId2specDict + specDict = self.entId2specDict[entId] + return specDict[entId] + + def getEntityType(self, entId): + return self.getEntitySpec(entId)['type'] + + def getEntType2ids(self, entIds): + """given list of entIds, return dict of entType 2 entIds""" + entType2ids = {} + for entId in entIds: + type = self.getEntityType(entId) + entType2ids.setdefault(type, []) + entType2ids[type].append(entId) + return entType2ids + + # private support functions to abstract dict structure + def privGetGlobalEntityDict(self): + return self.specDict['globalEntities'] + + def privGetScenarioEntityDict(self, scenario): + return self.specDict['scenarios'][scenario][0] + + if __debug__: + def setAttribChange(self, entId, attrib, value): + pass + + def getSpecImportsModuleName(self): + # name of module that should be imported by spec py file + return 'SpecImports' + + def getPrettyString(self): + """Returns a string that contains the spec data, nicely formatted. + This should be used when writing the spec out to file.""" + import pprint + + tabWidth = 4 + tab = ' ' * tabWidth + # structure names + globalEntitiesName = 'GlobalEntities' + scenarioEntitiesName = 'Scenario%s' + scenarioWeightName = 'Scenarios' + topLevelName = 'levelSpec' + def getPrettyEntityDictStr(name, dict, tabs=0): + def t(n): + return (tabs+n)*tab + def sortList(lst, firstElements=[]): + """sort list; elements in firstElements will be put + first, in the order that they appear in firstElements; + rest of elements will follow, sorted""" + elements = list(lst) + # put elements in order + result = [] + for el in firstElements: + if el in elements: + result.append(el) + elements.remove(el) + elements.sort() + result.extend(elements) + return result + + firstTypes = ('levelMgr', 'zone',) + firstAttribs = ('type', 'name', 'comment', 'parent', + 'pos', 'x', 'y', 'z', + 'hpr', 'h', 'p', 'r', + 'scale', 'sx', 'sy', 'sz', + 'color', + 'model', + ) + str = t(0)+'%s = {\n' % name + # get list of types + entIds = dict.keys() + entType2ids = self.getEntType2ids(entIds) + # put types in order + types = sortList(entType2ids.keys(), firstTypes) + for type in types: + str += t(1)+'# %s\n' % string.upper(type) + entIds = entType2ids[type] + entIds.sort() + for entId in entIds: + str += t(1)+'%s: {\n' % entId + spec = dict[entId] + attribs = sortList(spec.keys(), firstAttribs) + for attrib in attribs: + str += t(2)+"'%s': %s,\n" % (attrib, + repr(spec[attrib])) + str += t(2)+'},\n' + + str += t(1)+'}\n' + return str + def getPrettyScenarioWeightTableStr(tabs=0, self=self): + def t(n): + return (tabs+n)*tab + str = t(0)+'%s = [\n' % scenarioWeightName + for i in range(self.getNumScenarios()): + str += t(1)+'[%s, %s],\n' % (scenarioEntitiesName % i, + self.getScenarioWeights()[i]) + str += t(1)+']\n' + return str + def getPrettyTopLevelDictStr(tabs=0): + def t(n): + return (tabs+n)*tab + str = t(0)+'%s = {\n' % topLevelName + str += t(1)+"'globalEntities': %s,\n" % globalEntitiesName + str += t(1)+"'scenarios': %s,\n" % scenarioWeightName + str += t(1)+'}\n' + return str + + str = 'from %s import *\n' % self.getSpecImportsModuleName() + str += '\n' + + # add the global entities + str += getPrettyEntityDictStr('GlobalEntities', + self.privGetGlobalEntityDict()) + str += '\n' + + # add the scenario entities + numScenarios = self.getNumScenarios() + for i in range(numScenarios): + str += getPrettyEntityDictStr('Scenario%s' % i, + self.privGetScenarioEntityDict(i)) + str += '\n' + + # add the scenario weight table + str += getPrettyScenarioWeightTableStr() + str += '\n' + + # add the top-level table + str += getPrettyTopLevelDictStr() + + self.testPrettyString(prettyString=str) + + return str + + def testPrettyString(self, prettyString=None): + # execute the pretty output in our local scope + if prettyString is None: + prettyString=self.getPrettyString() + exec(prettyString) + assert levelSpec == self.specDict, ( + 'LevelSpec pretty string does not match spec data.\n' + 'pretty=%s\n' + 'specData=%s' % + (levelSpec, self.specDict) + ) + + def __repr__(self): + return 'LevelSpec(%s, scenario=%s)' % (repr(self.specDict), + self.scenario)