pause/resume scene (intervals-tasks-actors-sounds-movies)


#1

Some related topics :
discourse.panda3d.org/viewtopic.php?t=2579
discourse.panda3d.org/viewtopic.php?t=2116

I took different route than those.

main.py :

from pandac.PandaModules import *
from direct.showbase.DirectObject import DirectObject
from direct.interval.IntervalGlobal import *
from direct.gui.OnscreenText import OnscreenText
from direct.directutil.Mopath import Mopath
from direct.actor.Actor import Actor
from direct.task import Task
import direct.directbase.DirectStart
import os,sys, random

from PRtest_smileyClass import Smiley
from PRtest_pandaClass import Panda

import PauseResume as PR


unstoppable_namePrefix='unstoppable-'

class World(DirectObject):
  def __init__(self):
      camera.setPos(10.00, -40.00, 15.00)
      camera.setHpr(18.43, -21.57, 0.00)
      mat=Mat4(camera.getMat())
      mat.invertInPlace()
      base.mouseInterfaceNode.setMat(mat)
      self.accept('escape',sys.exit)
      self.accept('enter',self.printTask_n_IvalMgr)
      self.accept('space',self.toggleSceneActive)

      self.isPaused=0

      lilsmi=loader.loadModel('misc/lilsmiley')
      lilsmi.reparentTo(aspect2d)
      lilsmi.getChild(0).setScale(.2)
      lilsmi.getChild(0).setAlphaScale(.5)
      taskMgr.add( self.moveLilsmi, unstoppable_namePrefix+'moveLilsmi',
                   extraArgs=[lilsmi,lilsmi.getChild(0)] )
      OnscreenText('unstoppable\ntask',parent=lilsmi,scale=.05,pos=(0,-.15),fg=(1,1,1,1))

      lilsmi2=loader.loadModel('misc/lilsmiley').copyTo(aspect2d)
      lilsmi2.getChild(0).setScale(.2)
      OnscreenText('unstoppable\ninterval',parent=lilsmi2,scale=.05,pos=(0,-.15),fg=(1,1,1,1))
      Sequence( lilsmi2.posInterval(5,Point3(-.9,0,.9),Point3(.9,0,.9)),
                lilsmi2.posInterval(5,Point3(.9,0,.9),Point3(-.9,0,.9)),
                name=unstoppable_namePrefix+'smiIval').loop()

      barDuration=2
      OT=OnscreenText('doLater 0',scale=.075,fg=(1,1,1,1),align=TextNode.ARight,mayChange=1)
      OT.index=0
      NodePath(OT).setPos(render2d,.95,0,-.95)
      T=taskMgr.doMethodLater(barDuration,self.updateText,'updateText',[OT])
      CM=CardMaker('')
      card=render2d.attachNewNode(CM.generate())
      card.setPos(-1,0,-1)
      card.scaleInterval(barDuration,Vec3(2,1,.04),Vec3(1e-5,1,.04)).loop()

      ## create smileys
      self.smileys=Smiley()

      #### create pandas
      num=7
      self.pandas=[Panda() for p in xrange(num)]

      # Ralph
      ralphLoc='../samples/Roaming-Ralph/models/'
      self.Ralph = Actor(ralphLoc+'ralph',{'walk':ralphLoc+'ralph-walk','run':ralphLoc+'ralph-run'})
#       self.Ralph.enableBlend()
      # interpolate frames
#       self.Ralph.find('**/+Character').node().getBundle(0).setFrameBlendFlag(1)
#       self.Ralph.setControlEffect('walk', .5)
#       self.Ralph.setControlEffect('run', .5)
#       self.Ralph.loop('run')
      self.Ralph.loop('walk')
      self.Ralph.reparentTo(render)
      self.Ralph.setPos(-2,-15,0)
#       self.Ralph.setTag('nopause','') # to exclude this actor from getting paused

      # Eve
      eveLoc='../samples/Looking-and-Gripping/models/'
      self.Eve = Actor(eveLoc+'eve', {'walk' : eveLoc+'eve_walk'})
      self.Eve.reparentTo(render)
      self.Eve.setPos(2,-15,0)
      self.Eve.actorInterval("walk", playRate = 2).loop()
#       self.Eve.setTag('nopause','') # to exclude this actor from getting paused


      # motion path interval
      np=render2d.attachNewNode('')
      child=np.attachNewNode('')
      LS=LineSegs()
      NC=NurbsCurve()
      NC.setOrder(2)
      num=100
      degInc=360./num
      for v in range(num):
          np.setR(-v*degInc)
          child.setX(.6+.3*random.random())
          x, z = child.getX(render2d),child.getZ(render2d)
          NC.appendCv(x,0,z)
          LS.drawTo(x,0,z)
          if v==0: ox,oz=x,z
      LS.drawTo(ox,0,oz)
      NC.recompute()
      render2d.attachNewNode(LS.create())

      s=loader.loadModel('misc/lilsmiley')
      s.reparentTo(render2d)
      s.setScale(.07)
      Sequence(
         s.colorScaleInterval(.5,Vec4(0,0,0,1)),
         s.colorScaleInterval(.5,Vec4(1)),
         # to make it not pausable
#          name=unstoppable_namePrefix+'colorscale-%s'%id(s)
      ).loop()

      mp=Mopath('mp1')
      mp.loadNodePath(NodePath(NC))
      mpi=MopathInterval( mp, s, duration=30,
         # to make it not pausable
#          name=unstoppable_namePrefix+'mopath-%s'%id(mp)
         )
      mpi.loop()

      # pausable sound
#       s1=loader.loadSfx('/use/your/own/sound/file')
#       s1.setVolume(.7)
#       s1.setLoop(1)
# #       s1.setPausable(0) # to make it not pausable
#       s1.play()

      # background sound that keeps playing
#       s2=loader.loadSfx('/use/your/own/sound/file')
#       s2.setVolume(.3)
#       s2.setLoop(1)
#       s2.setPausable(0) # to make it not pausable
#       s2.play()

      # movie texture
#       movie='../samples/Media-Player/PandaSneezes.avi'
#       movTex=loader.loadTexture(movie)
#       movTexUV=movTex.getTexScale()
#       CM=CardMaker('')
#       CM.setFrameFullscreenQuad()
#       CM.setUvRange(movTex)
#       card=render.attachNewNode(CM.generate())
#       card.setTwoSided(1)
#       scale=8
#       card.setScale(movTexUV[0]*scale,1,movTexUV[1]*scale)
#       card.setPos(-15,5,2)
#       card.setTexture(movTex)
#       playAudioToo=1  # play audio ?
#       pausable=1
#       if playAudioToo:
#          mA=loader.loadSfx(movie)
#          mA.setLoop(1)
#          mA.play()
#          mA.setPausable(pausable) # to make it pausable or not
#          movTex.synchronizeTo(mA)
#       else:
#          movTex.play()
#          movTex.setPausable(pausable) # to make it pausable or not

      # RTT
      textParent=NodePath('')
      text = OnscreenText(parent=textParent,text="You can't\npause me\nHO HO HO")
      b3=text.getTightBounds()
      bHalf=(b3[0]+b3[1])*.5
      b=(b3[1]-b3[0])*.5
      ratio=b[0]/b[2]
      b.setZ(b[0]) if ratio>1 else b.setX(b[2])
      b*=1.05 # give a little gap from the border, to avoid artifact on texture's small mip level
      textBuffer = base.win.makeTextureBuffer('text buffer', 512,512)
      textBuffer.setClearColor(Vec4(1))
      textCam = base.makeCamera2d(textBuffer)
      textCam.reparentTo(textParent)
      textCam.setPos(bHalf) # put it exactly at text's center
      textCam.setScale(b[0],1,b[2]) # auto-zoom in to text
      self.textTexture = textBuffer.getTexture()
      self.textTexture.setMinfilter(Texture.FTLinearMipmapLinear)
      base.graphicsEngine.renderFrame()
      base.graphicsEngine.removeWindow(textBuffer)

      self.setupLights()
      self.loadFloor()
      self.loadColliders()
      taskMgr.doMethodLater(3,self.spawnNewPanda,'spawnNewPanda')

  def spawnNewPanda(self,t):
      p=Panda().pandaModel
      p.setY(-10)
      p.colorScaleInterval(3,Vec4(0,.7,0,1),Vec4(1)).start()
      p.setTag('nopause','') # to exclude this actor from getting paused
      proj = LensNode('proj')
      proj.setLens(OrthographicLens())
      proj=p.attachNewNode(proj)
      proj.setScale(4)
      proj.setZ(4.7)
      p.projectTexture(TextureStage(''),self.textTexture,proj)
      taskMgr.doMethodLater(3,p.cleanup,'removeNewPanda',extraArgs=[])
      return Task.again

  def setupLights(self):
      ambientLight = AmbientLight( 'ambientLight' )
      ambientLight.setColor( Vec4(.3, 0.3, 0.3, 1) )
      self.ambientLight=render.attachNewNode( ambientLight )
      render.setLight(self.ambientLight)

      directionalLight = DirectionalLight( 'directionalLight1' )
      directionalLight.setDirection( Vec3( 0, 2, -1 ) )
      directionalLight.setColor( Vec4( .7, .7, .7, 1 ) )
      self.directionalLight=render.attachNewNode( directionalLight )
      render.setLight(self.directionalLight)

  def loadFloor(self):
      self.floorBit=BitMask32.bit(1)
      self.sphereBit=BitMask32.bit(2)
      self.offBit=BitMask32.allOff()
      self.floor=loader.loadModel('misc/rgbCube')
      self.floor.reparentTo(render)
      self.floor.setScale(18,18,.2)
      self.floor.setR(25)
      self.floor.flattenLight()
      box=loader.loadModel('box')
      box.reparentTo(self.floor)
      box.setScale(7,5,.5)
      box.setPos(-4,2,1.5)
      box.setR(-25)
      self.floor.flattenLight()
      self.floor.setPos(-5,-10,-7)
      self.floor.setCollideMask(self.floorBit)
      box.setCollideMask(self.floorBit|self.sphereBit)
      self.floor.setLightOff(1)
      self.floor.hprInterval(3,Vec3(360,0,0)).loop()

  def loadColliders(self):
      base.cTrav=CollisionTraverser()
      base.cTrav.setRespectPrevTransform(1)
      self.arrows=[]
      for c in xrange(5):
          arrow=loader.loadModel('misc/Spotlight')
          arrow.reparentTo(render)
          arrow.setP(90)
          arrow.setScale(.3)
          arrow.setColor(.5+.5*random.random(),.5+.5*random.random(),.5+.5*random.random())
          arrow.flattenStrong()
          arrow.setPos(self.floor,random.uniform(-4,4),random.uniform(-4,4),10)
          rayNP = arrow.attachCollisionRay('', 0,0,2, 0,0,-1, self.floorBit,self.offBit)
          rayNP.show()
          sphereNP = arrow.attachCollisionSphere('', 0,0,1.5, .7, self.sphereBit,self.sphereBit)
#           sphereNP.show()
          CHgravity = CollisionHandlerGravity()
          CHgravity.addCollider(rayNP,arrow)
          CHpusher= CollisionHandlerPusher()
          CHpusher.addCollider(sphereNP,arrow)
          base.cTrav.addCollider(rayNP,CHgravity)
          base.cTrav.addCollider(sphereNP,CHpusher)
          self.arrows.append([arrow,CHgravity])
      taskMgr.add(self.moveArrows, 'move arrows')

  def moveArrows(self,task):
      dt=globalClock.getDt()
      if dt>.2: return Task.cont
      speed=10*dt
      for a,CHG in self.arrows:
          if CHG.isOnGround():
             a.setH(a,random.uniform(-20,20))
          lastPos=a.getPos()
          a.setFluidY(a,speed)
          pos=a.getPos(self.floor)
          if not ( (-5 < pos[0] < 5) and (-5 < pos[1] < 5) ):
             a.setFluidPos(lastPos)
             a.headsUp(self.floor) # looks at floor while stays upright
             a.setFluidY(a,speed)
      return Task.cont

  def moveLilsmi(self,np,geom):
      dt=globalClock.getDt()
      if dt>.2: return Task.cont
      geom.setR(geom,random.uniform(-10,10))
      speed=1.5*dt
      np.setZ(geom,speed)
      if np.getDistance(aspect2d)>.5:
         geom.setR(geom,180)
         np.setZ(geom,speed)
      return Task.cont

  def updateText(self,text):
      text.index+=1
      text['text']='doLater %s'%text.index
      return Task.again

  def printTask_n_IvalMgr(self):
      print taskMgr
      print taskMgr.getTasksNamed('move arrows')
      print ivalMgr

  def toggleSceneActive(self):
      self.isPaused=not self.isPaused
      if self.isPaused:
         PR.pause( allAnims=0,
                   allAudios=0,
                   allMovies=0,
                   collision=1,
                   excludedTaskNamePrefix=unstoppable_namePrefix,
                   excludedIvalNamePrefix=unstoppable_namePrefix,
                   )
      else:
         PR.resume()

if __name__=='__main__':
   World()
   run()

PRtest_pandaClass.py :

from direct.actor.Actor import Actor
from random import random, uniform

class Panda:
  def __init__(self):
      self.pandaModel=Actor('panda',{'walk':'panda-walk'})
      self.pandaModel.reparentTo(render)
      scale=.2+random()*.7
      self.pandaModel.setScale(scale)
      self.pandaModel.setPos(uniform(-5,5),uniform(-2,6),0)
      self.pandaModel.setColorScale(scale,scale,scale,1)
      self.pandaModel.setPlayRate(1./scale,'walk')
      self.pandaModel.setP(1/(.8*scale*scale))
      self.pandaModel.loop('walk')
      self.other=[]

PRtest_smileyClass.py :

from pandac.PandaModules import Vec3
from direct.interval.IntervalGlobal import LerpFunc
from math import pi, sin

class Smiley:
  def __init__(self):
      scale=.8
      num=int(10/scale)
      self.smileys=[]
      self.moves = [0 for i in xrange(num)]
      self.roll = [0 for i in xrange(num)]
      for s in xrange(num):
          smi=loader.loadModel('smiley')
          smi.reparentTo(render)
          smi.setScale(scale)
          smi.setPos((-(num-1)*.5+s)*scale*2,-6,scale*1.25)
          self.smileys.append(smi)
          self.moves[s] = LerpFunc(
                       self.oscilateSmiley,
                       duration = 2,
                       fromData = 0,
                       toData = 2*pi,
                       extraArgs=[self.smileys[s], pi*(s%2)]
                       )
          self.moves[s].loop()
          self.roll[s]=self.smileys[s].hprInterval(3.,Vec3(720,0,360))
          self.roll[s].loop()

  def oscilateSmiley(self, rad, np, offset):
      np.setZ(sin(rad + offset) *.9)

PauseResume.py :

__all__ = []

import sys,time
importTime = time.clock()
PRmodules = [k for k in sys.modules.keys() if k.find(__name__)>-1]
PRmodulesTime = [sys.modules[m].importTime for m in PRmodules]
PRmodulesTime.sort()
PRmod1stImport = [sys.modules[m] for m in PRmodules if sys.modules[m].importTime==PRmodulesTime[0]][0]
# print PRmodules,PRmodulesTime
if len(PRmodules)>1:
   for k in PRmodules:
       if importTime==sys.modules[k].importTime:
          sys.modules[k] = PRmod1stImport
          print 'WARNING : PauseResume module was ALREADY IMPORTED,\n          using the 1st imported one.'
          break
else:
   import os
   from direct.task import Task
   directModulesDir=os.path.abspath(os.path.join(os.path.dirname(sys.modules[Task.__name__].__file__),os.pardir))

   from pandac.extension_native_helpers import Dtool_funcToMethod
   from pandac.PandaModules import AnimControl, AudioSound, MovieTexture, NodePath, PandaSystem, AsyncTaskManager
   from direct.interval.IntervalGlobal import ivalMgr
   from myMessenger import Messenger

   atLeast16=PandaSystem.getMajorVersion()*10+PandaSystem.getMinorVersion()>=16
   taskFunc=lambda t: t.getFunction() if atLeast16 else t.__call__
   taskFuncNameQuery=lambda t: 'getFunction' if atLeast16 else '__call__'
   taskXArgs=lambda t: t.getArgs() if atLeast16 else t.extraArgs
   taskXArgsName=lambda t: 'getArgs' if atLeast16 else 'extraArgs'
   taskWakeT=lambda t: t.getDelay() if atLeast16 else t.wakeTime

   PRmsg = Messenger()
   IDE_ivalsName = 'IDE_IVALS_'
   IDE_tasksName = 'IDE_TASKS_'
   PAUSED_TASKCHAIN_NAME = 'YNJH paused tasks'
   isPaused = 0
   resumeLocked = 0

   # keeps the original C++ functions
   AnimControl__origPlay=AnimControl.play
   AnimControl__origLoop=AnimControl.loop
   AnimControl__origPingpong=AnimControl.pingpong
   AnimControl__origStop=AnimControl.stop
   AudioSound__origPlay=AudioSound.play
   AudioSound__origStop=AudioSound.stop
   MovieTexture__origPlay=MovieTexture.play
   MovieTexture__origStop=MovieTexture.stop


   # defines the new method wrappers for intercepting messages

   # ANIMATIONS ################################################################
   def newAnimPlay(self):
       PRmsg.accept('pauseAllAnims',self,pauseAnim,[self,0])
       PRmsg.accept('pauseNotTaggedAnims',self,pauseAnim,[self])
       AnimControl__origPlay(self)
   Dtool_funcToMethod(newAnimPlay,AnimControl,'play')
   del newAnimPlay

   def newAnimLoop(self,restart=1,_from=None,_to=None):
       PRmsg.accept('pauseAllAnims',self,pauseAnim,[self,0])
       PRmsg.accept('pauseNotTaggedAnims',self,pauseAnim,[self])
       if _from is not None and _to is not None :
          AnimControl__origLoop(self,restart,_from,_to)
       else:
          AnimControl__origLoop(self,restart)
   Dtool_funcToMethod(newAnimLoop,AnimControl,'loop')
   del newAnimLoop

   def newAnimPingpong(self,restart=1,_from=None,_to=None):
       PRmsg.accept('pauseAllAnims',self,pauseAnim,[self,0])
       PRmsg.accept('pauseNotTaggedAnims',self,pauseAnim,[self])
       if _from is not None and _to is not None :
          AnimControl__origPingpong(self,restart,_from,_to)
       else:
          AnimControl__origPingpong(self,restart)
   Dtool_funcToMethod(newAnimPingpong,AnimControl,'pingpong')
   del newAnimPingpong

   def newAnimStop(self):
       for e in PRmsg.getAllAccepting(self):
           PRmsg.ignore(e,self)
       AnimControl__origStop(self)
   Dtool_funcToMethod(newAnimStop,AnimControl,'stop')
   del newAnimStop

   def pauseAnim(self,respectTag=1):
       if respectTag:
          part=self.getPart()
          for n in xrange(part.getNumNodes()):
              if NodePath(part.getNode(n)).hasNetTag('nopause'):
                 return
       PRmsg.ignore('pauseAllAnims',self)
       PRmsg.ignore('pauseNotTaggedAnims',self)
       PRmsg.accept('resumeAllAnims',self,resumeAnim,[self,self.getPlayRate()])
       self.setPlayRate(0)

   def resumeAnim(self,PR):
       PRmsg.ignore('resumeAllAnims',self)
       PRmsg.accept('pauseAllAnims',self,pauseAnim,[self,0])
       PRmsg.accept('pauseNotTaggedAnims',self,pauseAnim,[self])
       self.setPlayRate(PR)


   # AUDIO SOUNDS ##############################################################
   notPausableSounds = []
   invulnerableSounds = []

   def newAudioPlay(self):
       PRmsg.accept('pauseAllSounds',self,pauseAudio,[self,0])
       PRmsg.accept('pausePausableSounds',self,pauseAudio,[self])
       AudioSound__origPlay(self)
   newAudioPlay.__doc__=AudioSound__origPlay.__doc__
   Dtool_funcToMethod(newAudioPlay,AudioSound,'play')
   del newAudioPlay

   def newAudioStop(self):
       for e in PRmsg.getAllAccepting(self):
           PRmsg.ignore(e,self)
       AudioSound__origStop(self)
   newAudioStop.__doc__=AudioSound__origStop.__doc__
   Dtool_funcToMethod(newAudioStop,AudioSound,'stop')
   del newAudioStop

   def setPausable(self,status):
       if self in notPausableSounds:
          notPausableSounds.remove(self)
       if not status:
          notPausableSounds.append(self)
   Dtool_funcToMethod(setPausable,AudioSound)
   del setPausable

   def setInvulnerable(self,status):
       if self in invulnerableSounds:
          if status:
             return
          else:
             invulnerableSounds.remove(self)
       elif status:
          invulnerableSounds.append(self)
   Dtool_funcToMethod(setInvulnerable,AudioSound)
   del setInvulnerable

   def pauseAudio(self,respectTag=1):
       if self in invulnerableSounds:
          return
       if respectTag:
          if self in notPausableSounds:
             return
       PRmsg.ignore('pauseAllSounds',self)
       PRmsg.ignore('pausePausableSounds',self)
       PRmsg.accept('resumeAllSounds',self,resumeAudio,[self])
       AudioSound__origStop(self)
       self.setTime(self.getTime())

   def resumeAudio(self):
       PRmsg.ignore('resumeAllSounds',self)
       PRmsg.accept('pauseAllSounds',self,pauseAudio,[self,0])
       PRmsg.accept('pausePausableSounds',self,pauseAudio,[self])
       AudioSound__origPlay(self)


   # MOVIE TEXTURES ############################################################
   notPausableMovies = []

   def newMoviePlay(self):
       PRmsg.accept('pauseAllMovies',self,pauseMovie,[self,0])
       PRmsg.accept('pausePausableMovies',self,pauseMovie,[self])
       MovieTexture__origPlay(self)
   newMoviePlay.__doc__=MovieTexture__origPlay.__doc__
   Dtool_funcToMethod(newMoviePlay,MovieTexture,'play')
   del newMoviePlay

   def newMovieStop(self):
       for e in PRmsg.getAllAccepting(self):
           PRmsg.ignore(e,self)
       MovieTexture__origStop(self)
   newMovieStop.__doc__=MovieTexture__origStop.__doc__
   Dtool_funcToMethod(newMovieStop,MovieTexture,'stop')
   del newMovieStop

   def setPausable(self,status):
       if self in notPausableMovies:
          notPausableMovies.remove(self)
       if not status:
          notPausableMovies.append(self)
   Dtool_funcToMethod(setPausable,MovieTexture)
   del setPausable

   def pauseMovie(self,respectTag=1):
       if respectTag:
          if self in notPausableMovies:
             return
       PRmsg.ignore('pauseAllMovies',self)
       PRmsg.ignore('pausePausableMovies',self)
       PRmsg.accept('resumeAllMovies',self,resumeMovie,[self])
       MovieTexture__origStop(self)

   def resumeMovie(self):
       PRmsg.ignore('resumeAllMovies',self)
       PRmsg.accept('pauseAllMovies',self,pauseMovie,[self,0])
       PRmsg.accept('pausePausableMovies',self,pauseMovie,[self])
       self.restart()


   # INTERVALS #################################################################
   def pauseIvals(excludeNamePrefix=''):
       global pausedIvals
       pausedIvals=ivalMgr.getIntervalsMatching('*')
       excluded=[]
       for i in pausedIvals:
           if ( (excludeNamePrefix and i.getName().find(excludeNamePrefix)==0) or
                i.getName().find(IDE_ivalsName)==0
              ):
              excluded.append(i)
           else:
              #~ print 'PAUSED IVAL:',i.getName()
              i.pause()
       for e in excluded:
           pausedIvals.remove(e)

   def resumeIvals():
       for i in pausedIvals:
           i.resume()


   # TASKS #####################################################################
   def pauseTasks(excludedTaskNamePrefix,noCollision):
       global unneededTasks
       if not AsyncTaskManager.getGlobalPtr().findTaskChain(PAUSED_TASKCHAIN_NAME):
          taskMgr.setupTaskChain(PAUSED_TASKCHAIN_NAME,
             frameBudget=0) # frameBudget=0 doesn't allow any task to run
       unneededTasksName=[]
       # collects unneeded tasks
       if noCollision:
          unneededTasksName+=['collisionLoop','resetPrevTransform']
       unneededTasks=[ taskMgr.getTasksNamed(tn)[0] for tn in unneededTasksName]
       # collects all scene's tasks
       for t in taskMgr.getTasks(): # ordinary tasks
           if ( t and hasattr(t,taskFuncNameQuery(t)) and t.name.find(IDE_tasksName)!=0 and
                ( not excludedTaskNamePrefix or
                  (excludedTaskNamePrefix and t.name.find(excludedTaskNamePrefix))
                )
              ):
              func=taskFunc(t)
              mod=func.__module__
              # python-based intervals
              if mod.find('direct.interval')==0:
                 if not (func.im_class.__name__=='ActorInterval' and\
                         func.im_self.actor.hasNetTag('nopause')):
                    unneededTasks.append(t)
                    t.interval.pause()
              elif mod not in sys.modules or sys.modules[mod].__file__.find(directModulesDir)<0:
                 unneededTasks.append(t)
       currT=globalClock.getFrameTime()
       for t in taskMgr.getDoLaters(): # doLater tasks
           if ( t and hasattr(t,taskFuncNameQuery(t)) and
                ( not excludedTaskNamePrefix or
                  (excludedTaskNamePrefix and t.name.find(excludedTaskNamePrefix)) )
              ):
                 unneededTasks.append(t)
                 # I need to alter the wakeTime during task resume,
                 # so I have to save the remaining time.
                 # Just save it as its attribute, nobody would notice :D
                 t.remainingTime=t.wakeTime-currT
       # "pauses" tasks
       for t in unneededTasks:
           t.ORIG_extraArgs=taskXArgs(t) if hasattr(t,taskXArgsName(t)) else None
           if hasattr(t,taskFuncNameQuery(t)):
              t.ORIG_call=taskFunc(t)
           t.ORIG_priority=t._priority if hasattr(t,'_priority') else t.getSort()
           # only active tasks can be moved to other chain, so removes doLater
           # tasks since they are in sleeping state
           if hasattr(t,'remainingTime'): # doLater tasks
              t.remove()
           else: # ordinary tasks
              t.lastactivetime=-t.time if hasattr(t,'time') else 0
              try:
                  t.setTaskChain(PAUSED_TASKCHAIN_NAME)
              except:
                  pass

   def resumeTasks():
       # restarts tasks
       for t in unneededTasks:
           if hasattr(t,'interval'): # it must be python-based intervals
              t.interval.resume()
              if hasattr(t,'ORIG_call'):
                 if atLeast16:
                    t.setFunction(t.ORIG_call)
                 else:
                    t.__call__=t.ORIG_call
           else:
              if hasattr(t,'remainingTime'): # doLater tasks
                 tempDelay=t.remainingTime-(globalClock.getRealTime()-globalClock.getFrameTime())
                 if hasattr(t,'uponDeath'):
                    uponDeath=t.uponDeath
                 else:
                    uponDeath=None
                 # no need to pass appendTask, since if it's already true,
                 # the task is already appended to extraArgs
                 newTask=taskMgr.doMethodLater( tempDelay, t.ORIG_call,
                                                t.name, priority=t.ORIG_priority,
                                                extraArgs=t.ORIG_extraArgs,
                                                uponDeath=uponDeath)
                 # restore the original delayTime
                 if hasattr(t,'remainingTime'):
                    newTask.delayTime=t.delayTime
              else: # ordinary tasks
                 t.setDelay(t.lastactivetime)
                 t.setTaskChain('default')
                 # very important to avoid assertion error on resume
                 t.clearDelay()


   def pause( allAnims=0,allAudios=0,allMovies=0,collision=1,
              excludedTaskNamePrefix='',excludedIvalNamePrefix='',
              lowerLevelOperation=1
              ):
       '''
           allAnims  : pause all animations or only the not "nopause" tagged ones
           allAudios : pause all audio sounds or only the pausable ones
           allMovies : pause all movies or only the pausable ones
           collision : pause collision detection or not
           excludedTaskNamePrefix : do not pause tasks with this name prefix
           excludedIvalNamePrefix : do not pause intervals with this name prefix
           lowerLevelOperation : <DO NOT use this>
       '''
       global isPaused,resumeLocked
       if isPaused:
          print 'WARNING : SCENE IS ALREADY PAUSED !'
          return isPaused
       PRmsg.send('pauseAllAnims') if allAnims else PRmsg.send('pauseNotTaggedAnims')
       PRmsg.send('pauseAllSounds') if allAudios else PRmsg.send('pausePausableSounds')
       PRmsg.send('pauseAllMovies') if allMovies else PRmsg.send('pausePausableMovies')
       pauseIvals(excludedIvalNamePrefix)
       pauseTasks(excludedTaskNamePrefix,collision)
       base.disableParticles()
       isPaused=1
       resumeLocked=lowerLevelOperation
#        print 'PR:',isPaused
       return isPaused

   def resume(lowerLevelOperation=1):
       global isPaused,resumeLocked
       if resumeLocked and not lowerLevelOperation:
#           print 'WARNING : RESUME IS LOCKED'
          return 2
       if not isPaused:
          print 'WARNING : SCENE IS ALREADY RESUMED'
          return isPaused
       PRmsg.send('resumeAllAnims') # resume all animations
       PRmsg.send('resumeAllSounds') # resume all audio
       PRmsg.send('resumeAllMovies') # resume all movie
       resumeIvals()
       resumeTasks()
       base.enableParticles()
       isPaused=0
#        print 'PR:',isPaused
       return isPaused

NOTE :
myMessenger module is the old Messenger.py, you can use 1.5’s, because 1.6’s wants python-only receiver object.
panda3d.cvs.sf.net/viewvc/panda3 … iew=markup

Use SPACE to toggle pause/resume, ENTER to investigate taskMgr & ivalMgr.
One issue about doLater tasks :
I don’t know what I missed, it’s hard to get it right. The doLater task’s wakeTime is reached to soon if I do pause/resume rapidly.


Various questions about Panda3D
#2

You’re not supposed to fiddle with task.wakeTime after it has been started. That really ought to be a private member, because if you change it, you invalidate the priority queue of tasks already stored in taskMgr, and thereby risk screwing up the wake times for other tasks than the one you are fiddling with.

The right thing to do is to compute the appropriate delayTime before re-queuing the task.

David


#3

OK, I’ve tried it this way :

if hasattr(t,'remainingTime'):
   tempDelay=t.remainingTime-(globalClock.getRealTime()-taskMgr.currentTime)
else:
   tempDelay=t.delayTime
# no need to pass appendTask, since if it's already true,
# the task is already appended to extraArgs
newTask=taskMgr.doMethodLater( tempDelay, t.ORIG_call,
                               t.name, priority=t.ORIG_priority,
                               extraArgs=t.ORIG_extraArgs,
                               uponDeath=uponDeath)
# restore the original delayTime
if hasattr(t,'remainingTime'):
   newTask.delayTime=t.delayTime

But it didn’t help at all.


#4

task._wakeTime ?


#5

What did you mean by that ? A task doesn’t have _wakeTime attr. Did you mean wakeTime ? If so, I already use it originally.


#6

proper naming convention for python to name private vars with a leading “_” see python.org/dev/peps/pep-0008/ this way there would be less confusion over if you can use it or not.


#7

It isn’t the problem, and there isn’t any confusion. It’s wakeTime used by task mgr.


#8

Well, ynjh_jo’s second approach should have solved the problem, so maybe there are other problems I’m not seeing right now.

treeform, I understand what you’re saying: you’re suggesting that we rename task.wakeTime to task._wakeTime in our code to prevent similar misunderstands. Yes, I agree, and this is what I meant when I said it “really ought to be a private member”. But this is low-level code that is already being used in several production games, so I’m reluctant to change it now without real good reason–we might accidentally break something somewhere that’s querying task.wakeTime, for instance.

David


#9

drwr, what option do you think we should take in fixing little errors if we always are slowed down by compatibility? I am not sure here too. You right breaking compatibility of such a small thing is terrible pay off.


#10

This is just part of the cost of having a graphics engine that’s actively being used for professional games–we have to be sensitive about compatibility issues like this. Sure, if we could rewrite everything from the ground up, we’d do it differently this time around, and fix all the stupid things we did the first time through. But if everyone wrote software this way, we’d never have any usable software, since everything would always be in rewrite.

Mostly, we’ve been dealing with this on a case-by-case basis: when an issue comes up, we evaluate (a) the urgency of fixing it and (b) the potential risk of making a change in that code. Then we decide whether it should be fixed, and if so, what is the plan for fixing it. If we accidentally “fix” something that we later discover breaks existing code, well, we just have to deal with that here at Disney. That’s the flip side: part of the cost of developing professional games on a graphics engine that’s open to the public.

There are parts of the system that might lend themselves to complete rewrites without risking breaking existing code. For instance, several people have proposed replacement gui interfaces, which don’t interfere with the existing DirectGui stuff. I think this is great.

I sometimes wonder if we shouldn’t rewrite the whole DirectStart/ShowBase thing at some point, and come up with a new way of starting Panda that doesn’t do all the terrible things that the current system does (like shoving names into the builtin dictionary). If anyone tackled this, it would have to be a new system that completely replaces the old system, while still allowing the old system to work the way it always has.

David


#11

What would be other things that you would fix? (besides the builtins pollution?) I am kinda interested in writing such a replacement but still don’t feel that “direct” is in such a sad shape to warrant it. The talks of a panda3d “framework” on other threads and IRC makes me think of cool things to add (not just fix) that could warrant such a rewrite thought. Your thoughts?

If we do rewrites - i feel total rewrites are bad (being on several such projects in the past) we just need fork it and tackle it peace meal one class at a time.


#12

I would also change the way it opened a window automatically at startup, and probably rework the whole openWindow() system to be a little less magical. But, yeah, it’s not really that bad now, which is one reason it’s still the way it is.

It makes sense to integrate this kind of a redesign into a proposed framework system.

David


#13

David, is it worth to extend AnimControl to entirely skip all anims if the Character it is bound to is stashed ?
So I can pause all anims simply by

pausedActors=np.findAllMatches('**/+Character')
pausedActors.stash()

and resume them by pausedActors.unstash()
Scanning Python namespaces for actors is slow.

About the doLater tasks, try to do 5 keypresses rate per second, for at least 5 seconds, and you’ll see the doLater tasks get out of sync to the whitebar’s interval.


#14

The AnimControl has no way of knowing whether the Character is stashed. In fact, asking whether a node is stashed doesn’t always have an answer, because a node might have multiple instances, and some of them might be stashed and others unstashed. Furthermore, I don’t think skipping animations on stashed Characters is the right behavior, anyway–you might want to start the animations, and then immediately unstash the model and you would expect to see the animations playing.

David


#15

[size=150]CONGRATULATIONS !![/size]
(I hope nobody would mind if I congratulate myself)

pause/resume animation :
Finally I think I can put aside the stupid namespaces scanning idea for good.
I’ve figured out the simplest hack to pause/resume all animations correctly, by taking advantage of Dtool and Messenger. This works too with blended animations, since I directly strike the heart of the animation system’s base class : AnimInterface.

PRmsg=Messenger()

# keeps the original C++ functions
AnimInterface.DtoolClassDict['__origPlay']=AnimInterface.play
AnimInterface.DtoolClassDict['__origLoop']=AnimInterface.loop
AnimInterface.DtoolClassDict['__origPingpong']=AnimInterface.pingpong
AnimInterface.DtoolClassDict['__origStop']=AnimInterface.stop

# defines the new method wrappers for intercepting messages

def newAnimPlay(self):
    PRmsg.accept('pauseAllAnims',self,pauseAnim,[self])
    self.__origPlay()
Dtool_funcToMethod(newAnimPlay,AnimInterface,'play')
del newAnimPlay

def newAnimLoop(self,restart=1,_from=None,_to=None):
    PRmsg.accept('pauseAllAnims',self,pauseAnim,[self])
    if _from is not None and _to is not None :
       self.__origLoop(restart,_from,_to)
    else:
       self.__origLoop(restart)
Dtool_funcToMethod(newAnimLoop,AnimInterface,'loop')
del newAnimLoop

def newAnimPingpong(self,restart=1,_from=None,_to=None):
    PRmsg.accept('pauseAllAnims',self,pauseAnim,[self])
    if _from is not None and _to is not None :
       self.__origPingpong(restart,_from,_to)
    else:
       self.__origPingpong(restart)
Dtool_funcToMethod(newAnimPingpong,AnimInterface,'pingpong')
del newAnimPingpong

def newAnimStop(self):
    evts=PRmsg.getAllAccepting(self)
    for e in evts:
        PRmsg.ignore(e,self)
    self.__origStop()
Dtool_funcToMethod(newAnimStop,AnimInterface,'stop')
del newAnimStop

def pauseAnim(self):
    PRmsg.ignore('pauseAllAnims',self)
    PRmsg.accept('resumeAllAnims',self,resumeAnim,[self,self.getPlayRate()])
    self.setPlayRate(0)
#     print id(self),lastPR

def resumeAnim(self,PR):
    self.setPlayRate(PR)
    PRmsg.ignore('resumeAllAnims',self)
    PRmsg.accept('pauseAllAnims',self,pauseAnim,[self])
#     print id(self),PR

What does anybody think ?


#16

I made up my mind. I think it’d be better if I strike AnimControl instead, so I can exclude some actors from getting paused, in case there is any freak want this behavior. I did it simply by setting a tag on the actor.
The pause method now :

def pauseAnim(self):
    part=self.getPart()
    for n in xrange(part.getNumNodes()):
        if NodePath(part.getNode(n)).hasNetTag('nopause'):
           return
    PRmsg.ignore('pauseAllAnims',self)
    PRmsg.accept('resumeAllAnims',self,resumeAnim,[self,self.getPlayRate()])
    self.setPlayRate(0)
#     print id(self),lastPR

#17

Another update.

  1. added actors exclusion refusal. It’s done per pause() call, if you pass allAnims=0, the “nopause” tagged actors won’t be paused, allAnims=1 will pause all actors.
  2. This one is to keep it compatible with my IDE. My IDE also uses this module, so it’s possible to use this module too in the test scene. Naturally, my IDE uses its PauseResume module, and the test scene uses its own module. So there are 2 exactly the same PauseResume modules, but located at different places, so they are loaded twice at runtime, and exist twice in sys.modules. I’ve set it up so there is only 1 module reference across the app. I did it by replacing the 2nd loaded module by the 1st loaded one. It’s also to keep the consistency of paused/resumed status, seen by the IDE and the test scene. To keep this status safe, i.e. not paused/resumed twice, I give my IDE no privilege to pause/resume when the scene already paused itself. If I don’t do this, when the scene is paused due to menu popup or something, and I go back to my IDE and resume it, I can’t imagine the havoc would happen. To let this works correctly, don’t change module’s name, keep it “PauseResume”.

#18

UPDATE :

  1. sounds now paused individually instead of at audioLoop level, so it’s possible to exclude some sounds from getting paused, e.g. background music.
  2. same thing applied to movie textures, with or without sound synchronization.

#19

UPDATE :

  1. able to pause unbound tasks function
  2. doesn’t pause IDE’s tasks

#20

UPDATE :
[1] 1.6 compatible,
[2] fixed actor interval pause
[3] added actor interval and motion path interval into the sample