diff --git a/direct/src/level/DistributedEntity.py b/direct/src/level/DistributedEntity.py index a612e39092..90857b42de 100755 --- a/direct/src/level/DistributedEntity.py +++ b/direct/src/level/DistributedEntity.py @@ -28,7 +28,7 @@ class DistributedEntity(DistributedObject.DistributedObject, Entity.Entity): self.entId = entId def announceGenerate(self): - DistributedEntity.notify.debug('announceGenerate') + DistributedEntity.notify.debug('announceGenerate (%s)' % self.entId) # ask our level obj for our spec data level = toonbase.tcr.doId2do[self.levelDoId] @@ -39,8 +39,9 @@ class DistributedEntity(DistributedObject.DistributedObject, Entity.Entity): DistributedObject.DistributedObject.announceGenerate(self) def disable(self): - DistributedEntity.notify.debug('disable') + DistributedEntity.notify.debug('disable (%s)' % self.entId) # stop things + self.destroy() def delete(self): DistributedEntity.notify.debug('delete') diff --git a/direct/src/level/DistributedLevelAI.py b/direct/src/level/DistributedLevelAI.py index 64a2d0387c..95bf627c06 100755 --- a/direct/src/level/DistributedLevelAI.py +++ b/direct/src/level/DistributedLevelAI.py @@ -89,10 +89,11 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI, # entities def setAttribChange(self, entId, attribName, valueStr): value = eval(valueStr) - self.levelSpec.setAttribChange(entId, attribName, value) - # send a copy to the client-side level obj + # send a copy to the client-side level obj FIRST + # (it may be a message that creates an entity) self.sendUpdate('setAttribChange', [entId, attribName, valueStr]) + self.levelSpec.setAttribChange(entId, attribName, value) self.modified = 1 self.scheduleSave() diff --git a/direct/src/level/EditMgr.py b/direct/src/level/EditMgr.py new file mode 100755 index 0000000000..84647d9098 --- /dev/null +++ b/direct/src/level/EditMgr.py @@ -0,0 +1,21 @@ +"""EditMgr module: contains the EditMgr class""" + +import Entity + +class EditMgr(Entity.Entity): + """This class handles entity/level functionality used by the level editor""" + def __init__(self, level, entId): + Entity.Entity.__init__(self, level, entId) + + def destroy(self): + Entity.Entity.destroy(self) + self.ignoreAll() + + def setInsertEntity(self, data): + self.level.levelSpec.insertEntity(data['entId'], + data['entType'], + data['parentEntId'], + ) + + def setRemoveEntity(self, entId): + self.level.levelSpec.removeEntity(entId) diff --git a/direct/src/level/Entity.py b/direct/src/level/Entity.py index 04ac9281cf..aa52552a85 100755 --- a/direct/src/level/Entity.py +++ b/direct/src/level/Entity.py @@ -28,6 +28,7 @@ class Entity(DirectObject): def destroy(self): self.level.onEntityDestroy(self.entId) del self.level + del self.entId def privGetSetter(self, attrib): setFuncName = 'set%s%s' % (string.upper(attrib[0]), attrib[1:]) diff --git a/direct/src/level/EntityCreator.py b/direct/src/level/EntityCreator.py index 5a897145b5..2d96b42647 100755 --- a/direct/src/level/EntityCreator.py +++ b/direct/src/level/EntityCreator.py @@ -3,6 +3,7 @@ import EntityCreatorBase import BasicEntities import DirectNotifyGlobal +import EditMgr import LevelMgr import ZoneEntity @@ -20,6 +21,7 @@ class EntityCreator(EntityCreatorBase.EntityCreatorBase): EntityCreatorBase.EntityCreatorBase.__init__(self, level) self.level = level self.privRegisterTypes({ + 'editMgr': EditMgr.EditMgr, 'levelMgr': LevelMgr.LevelMgr, 'logicGate': nothing, 'nodepath': BasicEntities.NodePathEntity, diff --git a/direct/src/level/EntityCreatorAI.py b/direct/src/level/EntityCreatorAI.py index 051b7bca68..e314075799 100755 --- a/direct/src/level/EntityCreatorAI.py +++ b/direct/src/level/EntityCreatorAI.py @@ -2,6 +2,7 @@ import EntityCreatorBase import LogicGateAI +import EditMgr import LevelMgrAI import ZoneEntityAI from PythonUtil import Functor @@ -41,6 +42,7 @@ class EntityCreatorAI(EntityCreatorBase.EntityCreatorBase): cLE = createLocalEntity self.privRegisterTypes({ + 'editMgr': Functor(cLE, EditMgr.EditMgr), 'levelMgr': Functor(cLE, LevelMgrAI.LevelMgrAI), 'logicGate': Functor(cLE, LogicGateAI.LogicGateAI), 'nodepath': nothing, diff --git a/direct/src/level/EntityTypeRegistry.py b/direct/src/level/EntityTypeRegistry.py index 668a319dda..1f07629aa7 100755 --- a/direct/src/level/EntityTypeRegistry.py +++ b/direct/src/level/EntityTypeRegistry.py @@ -47,7 +47,7 @@ class EntityTypeRegistry: attribNames.append(desc.getName()) return attribNames - def getAttribDescs(self, entityTypeName): + def getAttribDescDict(self, entityTypeName): """ returns dict of attribName -> attribDescriptor """ assert entityTypeName in self.typeName2class # TODO: precompute this diff --git a/direct/src/level/EntityTypes.py b/direct/src/level/EntityTypes.py index 4b159e4a51..09c571bb6f 100755 --- a/direct/src/level/EntityTypes.py +++ b/direct/src/level/EntityTypes.py @@ -25,6 +25,13 @@ class LevelMgr(Entity): ('modelFilename', None), ) +class EditMgr(Entity): + type = 'editMgr' + attribs = ( + ('insertEntity', None), + ('removeEntity', None), + ) + class LogicGate(Entity): type = 'logicGate' attribs = ( diff --git a/direct/src/level/Level.py b/direct/src/level/Level.py index 2052da85ab..d9840e2c52 100755 --- a/direct/src/level/Level.py +++ b/direct/src/level/Level.py @@ -45,6 +45,8 @@ class Level: self.scenarioIndex = scenarioIndex self.levelSpec.setScenario(self.scenarioIndex) + if __debug__: + self.levelSpec.setLevel(self) # create some handy tables @@ -57,31 +59,25 @@ class Level: # make sure the uberzone is there assert Level.UberZoneEntId in self.entType2ids['zone'] + # this list contains the entIds of entities that we have actually + # created, in order of creation + self.createdEntIds = [] + # get an entity creator object self.entityCreator = self.createEntityCreator() # create all the entities # TODO: we should leave this to a subclass or the level user self.createAllEntities(priorityTypes=['levelMgr','zone']) - self.levelSpec.setAttribChangeEventName(self.getAttribChangeEvent()) - self.accept(self.getAttribChangeEvent(), self.handleAttribChange) - self.initialized = 1 def isInitialized(self): return self.initialized def destroyLevel(self): + self.destroyAllEntities() self.initialized = 0 - if hasattr(self, 'createdEntities'): - # destroy the entities in reverse order - while len(self.createdEntities) > 0: - entity = self.createdEntities.pop() - self.notify.debug('destroying %s entity %s' % - (self.getEntityType(entity.entId), - entity.entId)) - entity.destroy() - del self.createdEntities + del self.createdEntIds if hasattr(self, 'entities'): del self.entities if hasattr(self, 'levelSpec'): @@ -97,8 +93,6 @@ class Level: # this will be filled in as the entities are created and report in # this includes distributed objects on the client self.entities = {} - # this list contains the entities that we have actually created - self.createdEntities = [] # get list of all entity types we need to create entTypes = self.entType2ids.keys() @@ -117,6 +111,22 @@ class Level: self.onLevelPostCreate() + def destroyAllEntities(self): + # destroy the entities in reverse order + while len(self.createdEntIds) > 0: + entId = self.createdEntIds[-1] + entity = self.getEntity(entId) + if entity is not None: + self.notify.debug('destroying %s %s' % ( + self.getEntityType(entId), entId)) + # this removes the entId from self.createdEntIds + # in order to support editor-based entity removal + entity.destroy() + else: + self.notify.warning('trying to destroy entity %s, but ' + 'it is already gone' % entId) + self.createdEntIds.pop() + def createAllEntitiesOfType(self, entType): """creates all entities of a given type""" assert entType in self.entType2ids @@ -129,7 +139,7 @@ class Level: self.onEntityTypePostCreate(entType) def createEntity(self, entId): - assert not self.entities.has_key(entId) + assert not entId in self.createdEntIds spec = self.levelSpec.getEntitySpec(entId) self.notify.debug('creating %s %s' % (spec['type'], entId)) entity = self.entityCreator.createEntity(entId) @@ -137,13 +147,13 @@ class Level: # it has all of its initial spec data; see 'initializeEntity' # below. if entity is not None: - self.createdEntities.append(entity) + self.createdEntIds.append(entId) # call the create handler # we used to do this in initializeEntity, but that did not # allow for additional initialization to be performed in - # derived entity __init__ funcs - # note that now DistributedEntity's are responsible for calling + # derived entity __init__ funcs before their presence was announced + # Note that now DistributedEntity's are responsible for calling # this for themselves self.onEntityCreate(entId) @@ -163,7 +173,8 @@ class Level: entity.setAttribInit(key, value) # entity is initialized, add it to the list of entities - self.entities[entity.entId] = entity + assert not entId in self.entities + self.entities[entId] = entity def getEntity(self, entId): return self.entities.get(entId) @@ -243,17 +254,48 @@ class Level: return 'entityDestroy-%s-%s' % (self.levelId, entId) def onEntityDestroy(self, entId): """Level is about to destroy this entity""" + assert entId in self.entities # send the entity-destroy event messenger.send(self.getEntityDestroyEvent(entId)) - if __debug__: - def getAttribChangeEvent(self): - return 'attribChange-%s' % self.levelId + del self.entities[entId] + # if we created this entity, remove its entId from the + # createdEntIds list + if len(self.createdEntIds) > 0: + # most often, we are destroying the entities in reverse order + if entId == self.createdEntIds[-1]: + self.createdEntIds.pop() + elif entId in self.createdEntIds: + # this should only happen if someone deleted an entity + # with an editor + self.createdEntIds.remove(entId) - # This handler is called immediately after a new attribute value - # has been set in the level's spec. + if __debug__: + # the level generates these events when the spec changes + def getAttribChangeEventName(self): + return 'attribChange-%s' % self.levelId + def getInsertEntityEventName(self): + return 'insertEntity-%s' % self.levelId + def getRemoveEntityEventName(self): + return 'removeEntity-%s' % self.levelId + + # these handlers are called directly by our levelSpec def handleAttribChange(self, entId, attrib, value): entity = self.getEntity(entId) # the entity might be AI- or client-only if entity is not None: entity.handleAttribChange(attrib, value) + messenger.send(self.getAttribChangeEventName(), + [entId, attrib, value]) + + def handleEntityInsert(self, entId): + self.createEntity(entId) + messenger.send(self.getInsertEntityEventName(), [entId]) + + def handleEntityRemove(self, entId): + messenger.send(self.getRemoveEntityEventName(), [entId]) + # if we didn't create it, don't destroy it (probably a distributed + # entity on the client; wait for AI to destroy it) + if entId in self.createdEntIds: + entity = self.getEntity(entId) + entity.destroy() diff --git a/direct/src/level/LevelSpec.py b/direct/src/level/LevelSpec.py index 194793e93b..495674dfe5 100755 --- a/direct/src/level/LevelSpec.py +++ b/direct/src/level/LevelSpec.py @@ -45,7 +45,7 @@ class LevelSpec: assert spec.has_key('type') attribNames = entTypeReg.getAttribNames(spec['type']) - attribDescs = entTypeReg.getAttribDescs(spec['type']) + attribDescs = entTypeReg.getAttribDescDict(spec['type']) # are there any unknown attribs in the spec? for attrib in spec.keys(): @@ -119,6 +119,9 @@ class LevelSpec: return self.specDict['scenarios'][scenario][0] if __debug__: + def setLevel(self, level): + self.level = level + def setAttribEditEventName(self, event): self.attribEditEventName = event def setAttribEdit(self, entId, attrib, value): @@ -127,54 +130,79 @@ class LevelSpec: # with it messenger.send(self.attribEditEventName, [entId, attrib, value]) - def setAttribChangeEventName(self, event): - self.attribChangeEventName = event def setAttribChange(self, entId, attrib, value): specDict = self.entId2specDict[entId] specDict[entId][attrib] = value - # locally broadcast the fact that this attribute value has + # let the level know that this attribute value has # officially changed - if self.attribChangeEventName is not None: - messenger.send(self.attribChangeEventName, - [entId, attrib, value]) + self.level.handleAttribChange(entId, attrib, value) + + def insertEntity(self, entId, entType, parentEntId): + assert entId not in self.entId2specDict + globalEnts = self.privGetGlobalEntityDict() + self.entId2specDict[entId] = globalEnts + + # create a new entity spec entry w/ default values + globalEnts[entId] = {} + spec = globalEnts[entId] + attribDescs = self.entTypeReg.getAttribDescDict(entType) + for name, desc in attribDescs.items(): + spec[name] = desc.getDefaultValue() + spec['type'] = entType + if 'parent' in spec: + spec['parent'] = parentEntId + + # notify the level + self.level.handleEntityInsert(entId) + + def removeEntity(self, entId): + assert entId in self.entId2specDict + # notify the level + self.level.handleEntityRemove(entId) + # remove the entity's spec + dict = self.entId2specDict[entId] + del dict[entId] + del self.entId2specDict[entId] def getSpecImportsModuleName(self): # name of module that should be imported by spec py file return 'SpecImports' - def saveToDisk(self, filename=None): + def saveToDisk(self, filename=None, createBackup=1): """returns zero on failure""" import os if filename is None: filename = self.filename - # create a backup - try: - # does the file exist? - exists = 0 + if createBackup: + # create a backup try: - os.stat(filename) - exists = 1 - except OSError: - pass - if exists: - def getBackupFilename(num, filename=filename): - return '%s.%03i' % (filename, num) - numBackups = 50 + # does the file exist? + exists = 0 try: - os.unlink(getBackupFilename(numBackups-1)) + os.stat(filename) + exists = 1 except OSError: pass - for i in range(numBackups-1,0,-1): + if exists: + def getBackupFilename(num, filename=filename): + return '%s.%03i' % (filename, num) + numBackups = 100 try: - os.rename(getBackupFilename(i-1), - getBackupFilename(i)) + os.unlink(getBackupFilename(numBackups-1)) except OSError: pass - os.rename(filename, getBackupFilename(0)) - except OSError, e: - LevelSpec.notify.warning('error during backup: %s' % str(e)) + 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 @@ -216,7 +244,7 @@ class LevelSpec: result.extend(elements) return result - firstTypes = ('levelMgr', 'zone',) + firstTypes = ('levelMgr', 'editMgr', 'zone',) firstAttribs = ('type', 'name', 'comment', 'parent', 'pos', 'x', 'y', 'z', 'hpr', 'h', 'p', 'r',