Browse Source

added editMgr, entity insertion, removal

Darren Ranalli 22 years ago
parent
commit
ecdb18879f

+ 3 - 2
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')

+ 3 - 2
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()

+ 21 - 0
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)

+ 1 - 0
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:])

+ 2 - 0
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,

+ 2 - 0
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,

+ 1 - 1
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

+ 7 - 0
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 = (

+ 65 - 23
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))
 
+        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)
+
     if __debug__:
-        def getAttribChangeEvent(self):
+        # the level generates these events when the spec changes
+        def getAttribChangeEventName(self):
             return 'attribChange-%s' % self.levelId
-
-        # This handler is called immediately after a new attribute value
-        # has been set in the level's spec.
+        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()

+ 56 - 28
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',