Modifying Geometry in Asynctask freezes game

Hi to all,

I don’t know if this issue has been fixed in the latest version(s) of Panda, but whenever I modify geometry, for instance by extending its polygons, within an asynctask, the game will eventually freeze if I have added any collisionHandler to a collisionTraverser. So something like this will freeze the game for example:

#add a collision nodepath to a collisionHandlerQueue:
def encapsulateModel(self,gotNp):
        box=CollisionBox(pointMin,pointMax)
        cn=render.attachNewNode(CollisionNode('cnode'))
        cn.node().addSolid(box)
        cn.setName("x1")
        cn.show()
        
        cn.setPos(gotNp.getPos())
        cn.setX(cn.getX()+(self.blockDimensions.x*1))
        
        cn.wrtReparentTo(gotNp)
        cn.node().setIntoCollideMask(BitMask32.allOff())
        
        base.cTrav.addCollider(cn, self.boxEmptyLiquidQueue)

self.encapsulateModel(sendNp)
taskMgr.add(self.extendModel, 'extendModelTaskName', taskChain = 'threadTwo')
...
def extendModel(self):
    #add faces to some model here

Something like that will cause a freeze at some point. However, a freeze does not occur if the task is not asynchronous but instead belongs to the main task chain. A freeze does not also occur if no solid is added to the collision nodepath.

So in brief: the game will freeze if a collisionHandler, that handles a collision nodepath with a collisionSolid added to it, exists and is added to a collisionTraverser. If existing procedural geometry is modified in an asynchronous task when such a collisionHandler has already been added to a collisionTraverser, then the game inevitably freezes.

I will provide a complete test-case later, but before I do, I’m wondering if such an issue was known and then fixed in the latest version(s)?

If anything is unclear, just tell me and I’ll clarify.
Thanks.

What version of Panda3D are you using? Previous versions of Panda3D had an issue whereby creating geometry in a thread could cause the application to freeze, but this should be fixed in Panda3D 1.10.x.

However, I’m not sure if anyone ever tried adding a collider in a thread; it might be that this is a separate issue if you are already on a recent version.

I’m using an older devel version from December 2017…but I am in the process of fully upgrading.

A bit of a misunderstanding here. The collider is not added in a thread. The collider is added in the main thread. What occurs in a separate thread is modifying procedural geometry. So for instance, a collider is added to a traverser in the main thread. Then, later within the application, geometry is modified in a separate thread and this causes a freeze. However, if no collider is added to a traverser in the main thread, then no freeze occurs whenever geometry is modified later on in a separate thread.

I will finish upgrading to the most recent version and then see if this issue persists, or if it was already fixed. If it persists, then I will provide a full test-case in the next few days. I just wanted to know if anyone ever came across anything like this before. Thanks.

Ah, yes, then I highly recommend upgrading to 1.10.7. The geometry generation freeze was a known bug that was finally fixed in 1.10.0, and various other threading-related instabilities have been fixed in later 1.10.x releases.

Alright, I will, thanks.

So, I managed to upgrade to the latest stable version, 1.10.7 and sadly, the problem persists.
I tried quickly writing up a test-case that should reproduce the issue:

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from direct.gui.OnscreenText import OnscreenText

class run_me(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        self.blockDimensions=Point3(4,4,4)
        #generate a movable cube:
        self.gotMoveableCube=self.generateStandAloneCube(Point4(1,1,1,1.1),Point3(1,1,1),True,False)
        self.gotMoveableCube.setX(self.gotMoveableCube.getX()-(self.blockDimensions.x*2))
        #generate a starting cube, whose geometry may be extended:
        self.extendableNodePath=self.generateStandAloneCube(Point4(0.0352941, 0.968628, 0.905882,1.0),Point3(1,1,1),True,True)
        #add the controls for moving the cube and for adding geometry to the starting cube: 
        self.accept('w',self.moveCube,['front'])
        self.accept('s',self.moveCube,['back'])
        self.accept('a',self.moveCube,['left'])
        self.accept('d',self.moveCube,['right'])
        self.accept('q',self.moveCube,['up'])
        self.accept('e',self.moveCube,['down'])
        self.accept("o", self.addPositionalRequestWrapper)
        self.accept("p", self.deletePositionalRequestWrapper)
        #add a traverser and a collisionHandlerQueue:
        self.traverser = CollisionTraverser('traverser name')
        base.cTrav = self.traverser
        self.boxEmptyQueue=CollisionHandlerQueue()
        #the event that causes the application to freeze:
        self.accept("t", self.addColliderToTraverser)
        #the list that will act as a queue, storing any requests to extend the starting face geometry:
        self.additionalFacesList=[]
        self.deleteFacesList=[]
        #set up the additional task-chain:
        taskMgr.setupTaskChain('asyncTaskName', numThreads = 1, tickClock = None,
                       threadPriority = None, frameBudget = None,
                       frameSync = True, timeslicePriority = None)
        #the asyncTask that will monitor the list, acting on any requests it finds in there:
        taskMgr.add(self.addDeleteCubesTaskRun, 'addDeleteCubesTaskRunName', taskChain = 'asyncTaskName')
        #some instructions on the screen:
        textA="[WASDQE]: Move the white cube."
        textB="[OP]: Add and Remove geometry to the blue cube."
        textC="[T]: Add a collider to a traverser."
        textD="[After adding a collider to a traverser, as you continue to \n add and remove geometry from the blue cube, the app freezes.]"
        textE="FREEZE BUG?*_*"
        textF="A collider was added to the traverser."
        motionText = OnscreenText(text=textA,style=1,parent=base.a2dTopLeft, align=TextNode.ALeft, fg=(1, 1, 1, 1), pos=(0.06, -0.1), scale=.05,shadow=(0, 0, 0, 0.5))
        geometryManipulationText = OnscreenText(text=textB,style=1,parent=base.a2dTopLeft, align=TextNode.ALeft,pos=(0.06, -0.18), fg=(1, 1, 1, 1), scale=.05,shadow=(0, 0, 0, 0.5))
        traverserAddText = OnscreenText(text=textC,style=1,parent=base.a2dTopLeft, align=TextNode.ALeft,pos=(0.06, -0.26), fg=(1, 1, 1, 1), scale=.05,shadow=(0, 0, 0, 0.5))
        bugInfoText = OnscreenText(text=textD,style=1,parent=base.a2dTopLeft, align=TextNode.ALeft,pos=(0.06, -0.34), fg=(1, 1, 1, 1), scale=.05,shadow=(0, 0, 0, 0.5))
        self.colliderAddedText = OnscreenText(text=textF,style=1,parent=base.a2dTopLeft, align=TextNode.ALeft,pos=(0.06, -0.46), fg=(0.91, 0.31, 0.41, 1), scale=.065,shadow=(0, 0, 0, 0.5))
        titleText = OnscreenText(text=textE ,style=1, fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1),pos=(0.8, -0.95), scale = .07)
        self.colliderAddedText.hide()
        #move the camera behind a bit:
        base.cam.setY(base.cam, -100)
    
    #This adds a collider to a collisionTraverser:
    def addColliderToTraverser(self):
        if(not render.find("dummyNp")):
            gotNp=render.attachNewNode("dummyNp")
            pointMin=Point3(self.blockDimensions.y*0.05,self.blockDimensions.y*0.05,self.blockDimensions.z*0.05)
            pointMax=Point3(self.blockDimensions.x*0.95,self.blockDimensions.y*0.95,self.blockDimensions.z*0.95)
            #x 1:
            box=CollisionBox(pointMin,pointMax)
            cn=render.attachNewNode(CollisionNode('cnode'))
            cn.node().addSolid(box)
            cn.setName("x1")
            cn.show()
            
            cn.setPos(gotNp.getPos())
            cn.setX(cn.getX()+self.blockDimensions.x)
            
            cn.wrtReparentTo(gotNp)
            cn.node().setIntoCollideMask(BitMask32.allOff())
            
            base.cTrav.addCollider(cn, self.boxEmptyQueue)
            self.colliderAddedText.show()
        
    #This is a task on another thread that adds a request to extend the blue cube's geometry into the queue:
    def addRequestsAsyncAdd(self,task):
        if(self.counterReqAdd==1):
            if(self.gotMoveableCube.getPos() not in self.additionalFacesList):
                self.additionalFacesList.append(self.gotMoveableCube.getPos())
            self.counterReqAdd=0
            taskMgr.remove("addRequestsAsyncAddNom")
        else:
            self.counterReqAdd+=1
        return task.cont
    
    #This is a wrapper that adds a task to another thread dealing with adding requests to extend the blue cube's geometry:
    def addPositionalRequestWrapper(self):
        self.counterReqAdd=0
        taskMgr.add(self.addRequestsAsyncAdd, 'addRequestsAsyncAddNom',taskChain = 'asyncTaskName')
    
    #This is a task on another thread that adds a request to delete the blue cube's geometry into the queue:
    def addRequestsAsyncDel(self,task):
        if(self.counterReqDel==1):
            if(self.gotMoveableCube.getPos() not in self.deleteFacesList):
                self.deleteFacesList.append(self.gotMoveableCube.getPos())
            self.counterReqDel=0
            taskMgr.remove("addRequestsAsyncDelNom")
        else:
            self.counterReqDel+=1
        return task.cont
    
    #This is a wrapper that adds a task to another thread dealing with adding requests to delete the blue cube's geometry:
    def deletePositionalRequestWrapper(self):
        self.counterReqDel=0
        taskMgr.add(self.addRequestsAsyncDel, 'addRequestsAsyncDelNom',taskChain = 'asyncTaskName')
    
    #This simply moves the movable cube around:
    def moveCube(self,value):
        if(value=="front"):
            self.gotMoveableCube.setY(self.gotMoveableCube,self.blockDimensions.y)
        if(value=="back"):
            self.gotMoveableCube.setY(self.gotMoveableCube,-self.blockDimensions.y)
        if(value=="left"):
            self.gotMoveableCube.setX(self.gotMoveableCube,-self.blockDimensions.x)
        if(value=="right"):
            self.gotMoveableCube.setX(self.gotMoveableCube,self.blockDimensions.x)
        if(value=="up"):
            self.gotMoveableCube.setZ(self.gotMoveableCube,self.blockDimensions.z)
        if(value=="down"):
            self.gotMoveableCube.setZ(self.gotMoveableCube,-self.blockDimensions.z)
        
    #This is the asyncTask that calls the methods which add or remove geometry from the blue cube:
    def addDeleteCubesTaskRun(self,task):
        if(len(self.additionalFacesList)>0):
            sentPoint=self.additionalFacesList[0]
            self.extendExistingNodePath([sentPoint,[]],self.extendableNodePath)
            del self.additionalFacesList[0]
        elif(len(self.deleteFacesList)>0):
            sentPoint=self.deleteFacesList[0]
            self.deleteCubePoint(sentPoint,self.extendableNodePath)
            del self.deleteFacesList[0]
        return task.cont
    
    #This calls the method that removes geometry from each face of the blue cube:
    def deleteCubePoint(self,sentPoint,sentNp):
        extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[1]],sentNp)
        extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[2]],sentNp)
        extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[3]],sentNp)
        extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[4]],sentNp)
        extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[5]],sentNp)
        extraTimes=self.removeFaceFromExistingNodePath([sentPoint,[6]],sentNp)
    
    #This is the actual method that will remove geometry from the blue cube:
    def removeFaceFromExistingNodePath(self,pointFaceHybridList,sentNp):
        allFaceArrayPoints=[]
        for index in range(0,len(pointFaceHybridList),2):
            sentPoint=pointFaceHybridList[index]
            faceSideList=pointFaceHybridList[index+1]
            for faceSide in faceSideList:
                gotFaceArrayPoints=self.returnProperFaceCoordinates(sentPoint,self.blockDimensions,faceSide)
                allFaceArrayPoints.append(gotFaceArrayPoints)
        terrainGeomNode=sentNp.node()
        specificGeom=terrainGeomNode.modifyGeom(0)

        vdata = specificGeom.modifyVertexData()
        vertexReader = GeomVertexReader(vdata, 'vertex')
        prim=specificGeom.modifyPrimitive(0)
        
        vertexArrayData=prim.modifyVertices()
        vertexArrayHandleP=vertexArrayData.modifyHandle()
        vertexArrayFormatP=vertexArrayHandleP.getArrayFormat()
        lengthP=vertexArrayFormatP.getStride()
        primitiveArrayModder=GeomVertexWriter(vertexArrayData,0)
        primitiveArrayReader=GeomVertexReader(vertexArrayData,0)
        
        vertexArray=vdata.modifyArray(0)
        vertexArrayHandle=vertexArray.modifyHandle()
        vertexArrayFormat=vertexArrayHandle.getArrayFormat()
        length=vertexArrayFormat.getStride()
        
        bufferDat=[]
        gotIt=prim.getVertexList()
        if(len(gotIt)>0):
            minVal=min(gotIt)
            maxVal=max(gotIt)
            totalPointsToRemove=len(allFaceArrayPoints)*4
            totalPointsFound=0
            for indxx in range(minVal,maxVal,4):
                vi=indxx
                vii=indxx+1
                viii=indxx+2
                viv=indxx+3
                
                vertexReader.setRow(vi)
                v1 = vertexReader.getData3f()

                vertexReader.setRow(vii)
                v2 = vertexReader.getData3f()

                vertexReader.setRow(viii)
                v3 = vertexReader.getData3f()

                vertexReader.setRow(viv)
                v4 = vertexReader.getData3f()
                faceArrayTest=[v1,v2,v3,v4]
                for gotFaceArrayPoints in allFaceArrayPoints:
                    foundPoints=0
                    for singlePoint in faceArrayTest:
                        if(singlePoint in gotFaceArrayPoints):
                            foundPoints+=1
                        if(foundPoints==4):
                            bufferDat.extend([indxx,indxx+1,indxx+2,indxx+3])
                            totalPointsFound+=4
                            break
                    if(foundPoints==4):
                        break
                if(totalPointsFound==totalPointsToRemove):
                    break
        totalPointsFound=0
        for kid in sentNp.getChildren():
            for kid2 in kid.getChildren():
                if isinstance(kid2.node(),CollisionNode):
                    lenzz=kid2.node().getNumSolids()
                    startNum=0
                    while startNum<lenzz:
                        faceArrayTest=[]
                        collisionSolidToUze=kid2.node().getSolid(startNum)
                        for indexx in range(4):
                            pointToAppend=Point3(collisionSolidToUze.getPoint(indexx).x,collisionSolidToUze.getPoint(indexx).y,collisionSolidToUze.getPoint(indexx).z)
                            faceArrayTest.append(pointToAppend)
                        
                        
                        for gotFaceArrayPoints in allFaceArrayPoints:
                            foundPoints=0
                            for singlePoint in faceArrayTest:
                                if(singlePoint in gotFaceArrayPoints):
                                    foundPoints+=1
                                if(foundPoints==4):
                                    #remove the solid:
                                    kid2.node().removeSolid(startNum)
                                    startNum-=1
                                    lenzz=kid2.node().getNumSolids()
                                    totalPointsFound+=4
                                    break
                            if(foundPoints==4):
                                break
                        if(totalPointsFound==totalPointsToRemove):
                            break
                        startNum+=1
                    if(kid2.node().getNumSolids()==0):
                        kid2.removeNode()
                        break
        #now, remove the relevant vertices from the primitive:
        delPrimz=[]
        kounter=0
        while not primitiveArrayReader.isAtEnd():
             rowIndex=primitiveArrayReader.getData1i()
             if(rowIndex in bufferDat):
                 delPrimz.append(kounter)
             #print "HNU!",rowIndex
             kounter+=1
        for start in sorted(delPrimz, reverse=True):
            vertexArrayHandleP.setSubdata(start*lengthP, lengthP, b'')
        rNumz2=vertexArrayData.getNumRows()
        oldRowIndices=list(range(vdata.getNumRows()))
        numVertReduce=0
        for start in sorted(bufferDat, reverse=True):
            vertexArrayHandle.setSubdata(start*length, length, b'')
            numVertReduce-=1
            del oldRowIndices[start]
        if(len(delPrimz)>0):
            lenRem=len(delPrimz)
            lRNum=rNumz2
            beginUpdateRow=min(delPrimz)
            startRow=primitiveArrayReader.getStartRow()
            primitiveArrayReader.setRow(startRow)
            kounter=0
            while not primitiveArrayReader.isAtEnd():
                rowNow=primitiveArrayReader.getReadRow()-1
                oldRowIndex=primitiveArrayReader.getData1i()
                if(kounter>=beginUpdateRow):
                    oldRowIndex+=numVertReduce
                primitiveArrayModder.setData1i(oldRowIndex)
                kounter+=1
    
    #This is the actual method that adds geometry to the blue cube:
    def extendExistingNodePath(self,sentPointHybridBlob,sentNpToExtend):
        activeColourMod=Point4(0.0352941, 0.968628, 0.905882,1.0)
        gnode=sentNpToExtend.node()
        geom=gnode.modifyGeom(0)
        vdata = geom.modifyVertexData()
        
        vertexMod = GeomVertexWriter(vdata, 'vertex')
        normalMod = GeomVertexWriter(vdata, 'normal')
        colorMod = GeomVertexWriter(vdata, 'color')
        texcoordMod = GeomVertexWriter(vdata, 'texcoord')
        
        vertex = GeomVertexReader(vdata, 'vertex')
        
        counter=vdata.getNumRows()
        primitive=geom.modifyPrimitive(0)

        collNodeII=CollisionNode("blockTestCollision")
        counter=vdata.getNumRows()
        for index in range(0,len(sentPointHybridBlob),2):
            sentPoint=sentPointHybridBlob[index]
            exemptFaces=sentPointHybridBlob[index+1]
            gotArrayIs=self.generateCubeGeneric(sentPoint,self.blockDimensions,exemptFaces)
            for currentArray in gotArrayIs:
                self.drawAFaceMod(vertexMod,normalMod,colorMod,texcoordMod,primitive,counter,activeColourMod,collNodeII,currentArray)
                counter+=4
        
        primitive.closePrimitive()
        
        parentNodePath=sentNpToExtend.find("standAloneCubePolySet")
        parentNodePath.attachNewNode(collNodeII)
    
    #This is a method that draws faces whenever the geometry of the blue-cube is being extended (it also adds collisionPolygons):
    def drawAFaceMod(self,*args):
        #structure is: 
        #0->positional data.
        #1->normal data.
        #2->color data.
        #3->uv data.
        #4->primitive.
        #5->current starting index for primitive.
        vertexMod=args[0]
        normalMod=args[1]
        colorMod=args[2]
        textureMod=args[3]
        prim_dat=args[4]
        numbr=args[5]
        activeColourMod=args[6]
        collNodeII=args[7]
        currentArray=args[8]
        setNum=0
        setNum=numbr
        
        opacityLevel=1
        for specificPoint in currentArray:
            vertexMod.setRow(setNum)
            textureMod.setRow(setNum)
            colorMod.setRow(setNum)
            normalMod.setRow(setNum)
            vertexMod.addData3f(specificPoint.x,specificPoint.y,specificPoint.z)
            normalMod.addData3f(self.myNormalize(Vec3(2*specificPoint.x-1,2*specificPoint.y-1,2*specificPoint.z-1)))
            gotColour=activeColourMod
            colorMod.addData4f(gotColour.x, gotColour.y, gotColour.z, opacityLevel)
            textureMod.addData2f(1, 1)
            setNum+=1
        #give the face a collision:
        colQuadSolid=CollisionPolygon(currentArray[0],currentArray[1],currentArray[2],currentArray[3])
        collNodeII.addSolid(colQuadSolid)
        prim_dat.addVertices(numbr, numbr+1, numbr+2)
        prim_dat.addVertices(numbr, numbr+2, numbr+3)
    
    #This is a method that will return a cube upon being called:
    def generateStandAloneCube(self,*args):
        #0->colour.
        #1->size.
        #2->transparency state.
        #3->collision state.
        #4->lines
        activeColorGenUse=args[0]
        colourUse=args[0]
        sizeSet=args[1]
        transparencyState=args[2]
        collisionState=args[3]
        array = GeomVertexArrayFormat()
        array.addColumn(InternalName.make('vertex'), 3,Geom.NTFloat32, Geom.CPoint)
        array.addColumn(InternalName.make('texcoord'), 2,Geom.NTFloat32, Geom.CTexcoord)
        array.addColumn(InternalName.make('normal'), 3,Geom.NTFloat32, Geom.CNormal)
        array.addColumn(InternalName.make('color'), 4,Geom.NTFloat32, Geom.CColor)
        format = GeomVertexFormat()
        format.addArray(array)
        format = GeomVertexFormat.registerFormat(format)
        node = GeomNode("standAloneCube")
        collNodeGen='n'
        if(collisionState):
            collNodeGen=CollisionNode("blockTestCollision")
        #self.collNodeGen=CollisionNode("blockTestCollision")
        #the writers and geom and primitive:
        vdata = GeomVertexData('VertexData', format, Geom.UHStatic)
        vertex = GeomVertexWriter(vdata, 'vertex')
        normal = GeomVertexWriter(vdata, 'normal')
        color = GeomVertexWriter(vdata, 'color')
        texcoord = GeomVertexWriter(vdata, 'texcoord')
        tileGeom=Geom(vdata)
        tileGeom.setBoundsType (3)
        prim = GeomTriangles(Geom.UHStatic)
        counter=0
        #okay, now, to draw the faces:
        genericCubeListPoints=[]
        sendDimensions=Point3(self.blockDimensions.x*sizeSet.x,self.blockDimensions.y*sizeSet.y,self.blockDimensions.z*sizeSet.z)
        genericCubeListPoints=self.generateCubeGeneric(Point3(0,0,0),sendDimensions,[])
        for currentArray in genericCubeListPoints:
            self.drawAGenericFace(vertex,normal,color,texcoord,prim,counter,collisionState,activeColorGenUse,collNodeGen,currentArray)
            counter+=4
        prim.closePrimitive()
        tileGeom.addPrimitive(prim)
        node.addGeom(tileGeom)
        gotProcGeom = render.attachNewNode(node)
        gotProcGeom.setName("standAloneCube"+str(gotProcGeom.node().this))
        if(collisionState):
            parentNodePath=gotProcGeom.attachNewNode("standAloneCubePolySet")
            cn=parentNodePath.attachNewNode(collNodeGen)
        
        #self.worldCursorNp=gotProcGeom
        gotProcGeom.setTransparency(TransparencyAttrib.MAlpha)
        if(transparencyState):
            gotProcGeom.setAttrib(DepthOffsetAttrib.make(1))
            gotProcGeom.setBin("fixed", 0)
        del genericCubeListPoints[:]
        return gotProcGeom
    
    #This is a method that simply draws a face that will belong to a cube (it also adds collisionPolygons):
    def drawAGenericFace(self,*args):
        #structure is: 
        #0->positional data.
        #1->normal data.
        #2->color data.
        #3->uv data.
        #4->primitive.
        #5->current starting index for primitive.
        vertex=args[0]
        normal=args[1]
        color=args[2]
        texcoord=args[3]
        prim_dat=args[4]
        numbr=args[5]
        collisionState=args[6]
        activeColorGenUse=args[7]
        collNodeGen=args[8]
        currentArray=args[9]
        for specificPoint in currentArray:
            vertex.addData3f(specificPoint.x,specificPoint.y,specificPoint.z)
            normal.addData3f(self.myNormalize(Vec3(2*specificPoint.x-1,2*specificPoint.y-1,2*specificPoint.z-1)))
            pointColor=activeColorGenUse
            color.addData4f(pointColor.x, pointColor.y, pointColor.z, pointColor.w)
            texcoord.addData2f(1, 1)
        #give the face a collisionSolid:
        if(collisionState):
            colQuadSolid=CollisionPolygon(currentArray[0],currentArray[1],currentArray[2],currentArray[3])
            collNodeGen.addSolid(colQuadSolid)
        prim_dat.addVertices(numbr, numbr+1, numbr+2)
        prim_dat.addVertices(numbr, numbr+2, numbr+3)
    
    #This is a method that returns a nested list which contains the points that make up the faces of a cube:
    def generateCubeGeneric(self,originPoint,cubeDimension,exemptFaces):
        genericCubeListPoints=[]
        for i in range(1,7,1):
            if (i not in exemptFaces):
                #draw it:
                gotArray=self.returnProperFaceCoordinates(originPoint,cubeDimension,i)
                genericCubeListPoints.append(gotArray)
        return genericCubeListPoints
    
    #This returns the points within a list that make up the face of a cube.
    def returnProperFaceCoordinates(self,*args):
        #0->(xOrigin,yOrigin,zOrigin)
        #1->(xDimension,yDimension,zDimension)
        #2->side to draw: 1,2,3,4,5,6: front,back,left,right,top,bottom
        originPoint=args[0]
        dimensionData=args[1]
        sideToDraw=args[2]
        if(sideToDraw==1):
            #drawing the front:
            point1=Point3(originPoint.x,originPoint.y,originPoint.z)
            point2=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z)
            point3=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z+dimensionData.z)
            point4=Point3(originPoint.x,originPoint.y,originPoint.z+dimensionData.z)
        elif(sideToDraw==2):
            #drawing the back:
            point1=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z)
            point2=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z)
            point3=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
            point4=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
        elif(sideToDraw==3):
            #drawing the left:
            point1=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z)
            point2=Point3(originPoint.x,originPoint.y,originPoint.z)
            point3=Point3(originPoint.x,originPoint.y,originPoint.z+dimensionData.z)
            point4=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
        elif(sideToDraw==4):
            #drawing the right:
            point1=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z)
            point2=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z)
            point3=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
            point4=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z+dimensionData.z)
        elif(sideToDraw==5):
            #drawing the top:
            point1=Point3(originPoint.x,originPoint.y,originPoint.z+dimensionData.z)
            point2=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z+dimensionData.z)
            point3=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
            point4=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z+dimensionData.z)
        elif(sideToDraw==6):
            #drawing the bottom:
            point1=Point3(originPoint.x,originPoint.y+dimensionData.y,originPoint.z)
            point2=Point3(originPoint.x+dimensionData.x,originPoint.y+dimensionData.y,originPoint.z)
            point3=Point3(originPoint.x+dimensionData.x,originPoint.y,originPoint.z)
            point4=Point3(originPoint.x,originPoint.y,originPoint.z)
        return [point1,point2,point3,point4]
    
    #This just normalizes the vector that will be used for the normals of the points that make up the cube:
    def myNormalize(self,myVec):
       myVec.normalize()
       return myVec
   
runMe=run_me()
runMe.run()

To recap, this is what causes the bug: the collider is added to a collisionTraverser within the main thread. The geometry of the model is modified within an asyncTask. The application freezes whenever this happens.

The application does not freeze:

  • If nothing is added to the collisionTraverser and the geometry is modified within an asyncTask.
  • If whatever is added to the collisionTraverser does not have any collisionSolids, i.e. if the collisionNode is empty and no collisionBox, etc.,is added to it. If the geometry is modified within an asyncTask, the application does not freeze.

I hope this helps. Also, when the blue-cube in the test-case is modified, collisionSolids are also added to it, just in case this information proves useful. I’m on a windows 10 machine. Is something wrong with the code, or is it a bug with Panda3d itself?

(What I’m trying to achieve in the main app I’m working on is the ability to modify a model’s geometry within an asyncTask, e.g. terrain geometry, character models, etc.)

If anything is unclear about this or the code, please tell me and I’ll clarify.
Thanks.

Not to pester you or anything, but would this be classified as a bug? Should I post it on Github at this point? The code can be run as it is and it should reproduce the issue…if it’s not a bug, what could be the problem?

As I believe it’s a bug with Panda3d and not a problem with the way I’m implementing the code, I added it as an issue to github.

You know, I added a cube, added a Collider to the traverse. And I was able to add geometry without hovering.

I thought I was doing it wrong, but I pressed the keys randomly and nothing freezes.

My action.

  1. Pressed: O key
  2. Pressed: T key
  3. Pressed: A key
  4. Pressed: O key

Hi,

  • WASDQE: These just place the cube in different positions.
  • O: extends the geometry of the original blue cube.
  • P: deletes geometry from the original cube.
  • T: adds a collider to the traverser.

Steps:

  1. Press O a few times in different positions to extend the geometry.
  2. Delete the cubes you added by going to their positions and pressing P on them.
  3. Then, add a collider by pressing T.
  4. Now after adding the collider, try pressing O a few times, then delete some cubes by pressing P on them, this should reproduce the freeze I’m experiencing.

You have to add and remove geometry from the blue cube for a while before the freeze is triggered after you added a collider to a traverser.

I have tried many times and nothing happens.

You’re adding and deleting it too? You can also start by adding a collider to the traverser with T.
Then, you need to move around, adding and removing geometry like this:

freezeTestCaseGif

I’m on the latest stable version of Panda3d: 1.10.7. After adding a collider to the traverser, then moving around, adding and deleting geometry, the application hangs for me.

So, it has been marked as a deadlock here: issue, thanks to all who participated in coming to this conclusion.

It seems the problem lies in the loops.

while not primitiveArrayReader.isAtEnd():

If you add a print function. Then press “T” and" O"," P", and this is enough to freeze the app.

So some things you asked me over on GitHub, the reason I’m using multiple threads when modifying geometry is simple: if something as complicated as modifying character or terrain geometry were done on the main thread, the player would have to wait a long time for the application to perform the changes before it can continue. However, if this is handled on a separate thread, then the player doesn’t have to wait at all and the time taken to complete those operations is reduced. That’s why I’m using an asyncTask to get it done. For example, if you played games like minecraft or terrarria, that’s how those games handle loading chunks of their worlds, through threading. Does that make my approach clearer now?

Absolutely clear to me now. However, why don’t you use a task instead of loops.
I also racked my brain for cycles until I got the idea.

This is because I’m using a GeomVertexReader and a GeomVertexWriter to go through the table that has the data of the model I want to either delete, or modify in any other way. The way to get an entry in the table is like this:

rowIndex=primitiveArrayReader.getData1i()

Each time that method is called, the reader moves a row forward in the table. So in order to go through a model’s entire entries in the table, you’d need a condition that tells you when you’ve reached the end of the table, hence the condition in the while-loop:

while not primitiveArrayReader.isAtEnd():

That will check if the reader has reached the end of the table and if it has, it terminates the loop. Remember that each time you call primitiveArrayReader.getData1i(), it will return the index of the row in the table that the reader is in, but at the same time it will push the reader one row forward, towards the end of the table.

So you would set the reader at the start of the table:

startRow=primitiveArrayReader.getStartRow()
primitiveArrayReader.setRow(startRow)

Then, now that the reader is on the first row, you’d go through all the entries and do whatever you want this way:

while not primitiveArrayReader.isAtEnd():
     rowIndex=primitiveArrayReader.getData1i()
     ##do some stuff in here

And then the loop would terminate once the condition that the reader has reached the end of the table is met.

I don’t really know of a way to modify geometry, e.g. deleting entries or renumbering indices, that involves tasks though. It would be interesting to study it if you can provide some references for it.