Browse Source

*** empty log message ***

Joe Shochet 23 years ago
parent
commit
0f94a11a53

+ 3 - 5
direct/src/directtools/DirectManipulation.py

@@ -52,11 +52,9 @@ class DirectManipulationControl(PandaObject):
             self.constraint = None
         # Check to see if we are moving the object
         # We are moving the object if we either wait long enough
-        taskMgr.add(
-            Task.doLater(MANIPULATION_MOVE_DELAY,
-                         Task.Task(self.switchToMoveMode),
-                         'manip-move-wait'),
-            'manip-move-wait')
+        taskMgr.doMethodLater(MANIPULATION_MOVE_DELAY,
+                              self.switchToMoveMode,
+                              'manip-move-wait')
         # Or we move far enough
         self.moveDir = None
         watchMouseTask = Task.Task(self.watchMouseTask)

+ 5 - 9
direct/src/directtools/DirectSession.py

@@ -535,12 +535,10 @@ class DirectSession(PandaObject):
             # Temporarily set node path color
             nodePath.setColor(flashColor)
             # Clean up color in a few seconds
-            t = taskMgr.add(
-                Task.doLater(DIRECT_FLASH_DURATION,
-                             # This is just a dummy task
-                             Task.Task(self.flashDummy),
-                             'flashNodePath'),
-                'flashNodePath')
+            t = taskMgr.doMethodLater(DIRECT_FLASH_DURATION,
+                                      # This is just a dummy task
+                                      self.flashDummy,
+                                      'flashNodePath')
             t.nodePath = nodePath
             t.doneColor = doneColor
             # This really does all the work
@@ -729,9 +727,7 @@ class DirectSession(PandaObject):
         self.hideDirectMessageLater()
 
     def hideDirectMessageLater(self):
-        seq = Task.doLater(3.0, Task.Task(self.hideDirectMessage),
-                           'hideDirectMessage')
-        t = taskMgr.add(seq, 'hideDirectMessageLater')
+        taskMgr.doMethodLater(3.0, self.hideDirectMessage, 'hideDirectMessage')
 
     def hideDirectMessage(self, state):
         self.directMessageReadout.reparentTo(hidden)

+ 5 - 0
direct/src/showbase/PythonUtil.py

@@ -13,6 +13,11 @@ def ifAbsentPut(dict, key, newValue):
         dict[key] = newValue
         return newValue
 
+def unique(L1, L2):
+    """Return a list containing all items in 'L1' that are not in 'L2'"""
+    L2 = dict([(k,None) for k in L2])
+    return [item for item in L1 if item not in L2]
+
 def indent(stream, numIndents, str):
     """
     Write str to stream with numIndents in front it it

+ 338 - 148
direct/src/task/Task.py

@@ -7,6 +7,7 @@ import fnmatch
 import string
 import signal
 
+
 # MRM: Need to make internal task variables like time, name, index
 # more unique (less likely to have name clashes)
 
@@ -62,19 +63,26 @@ class Task:
         self.id = Task.count
         Task.count += 1
         self.__call__ = callback
-        self._priority = priority
+        self.__priority = priority
         self.uponDeath = None
         self.dt = 0.0
         self.maxDt = 0.0
         self.avgDt = 0.0
         self.runningTotal = 0.0
         self.pstats = None
+        self.__removed = 0
+
+    def remove(self):
+        self.__removed = 1
+
+    def isRemoved(self):
+        return self.__removed
 
     def getPriority(self):
-        return self._priority
+        return self.__priority
 
     def setPriority(self, pri):
-        self._priority = pri
+        self.__priority = pri
 
     def setStartTimeFrame(self, startTime, startFrame):
         self.starttime = startTime
@@ -90,11 +98,18 @@ class Task:
             import PStatCollector
             self.pstats = PStatCollector.PStatCollector("App:Show code:" + name)
 
-def doLater(delayTime, task, taskName):
-    task.name = taskName
-    # make a sequence out of the delay and the task
-    seq = sequence(pause(delayTime), task)
-    return seq
+    def finishTask(self, verbose):
+        if self.uponDeath:
+            self.uponDeath(self)
+        if verbose:
+            # We regret to announce...
+            messenger.send('TaskManager-removeTask', sentArgs = [self, self.name])
+
+    def __repr__(self):
+        if hasattr(self, 'name'):
+            return ('Task id: %s, name %s' % (self.id, self.name))
+        else:
+            return ('Task id: %s, no name' % (self.id))
 
 def pause(delayTime):
     def func(self):
@@ -146,7 +161,7 @@ def make_sequence(taskList):
 
             # If we got to the end of the list, this sequence is done
             if (self.index >= len(self.taskList)):
-                # TaskManager.notify.debug('sequence done: ' + self.name)
+                TaskManager.notify.debug('sequence done: ' + self.name)
                 frameFinished = 1
                 taskDoneStatus = done
                 
@@ -201,15 +216,12 @@ def make_loop(taskList):
             elif (ret == exit):
                 taskDoneStatus = exit
                 frameFinished = 1
-
             # If we got to the end of the list, wrap back around
             if (self.index >= len(self.taskList)):
                 self.prevIndex = -1
                 self.index = 0
                 frameFinished = 1
-
         return taskDoneStatus
-
     task = Task(func)
     task.name = 'loop'
     task.taskList = taskList
@@ -218,6 +230,36 @@ def make_loop(taskList):
     return task
 
 
+class TaskPriorityList(list):
+    def __init__(self, priority):
+        self.__priority = priority
+        self.__emptyIndex = 0
+    def getPriority(self):
+        return self.__priority
+    def getEmptyIndex(self):
+        return self.__emptyIndex
+    def setEmptyIndex(self, index):
+        self.__emptyIndex = index
+    def add(self, task):
+        if (self.__emptyIndex >= len(self)):
+            self.append(task)
+            self.__emptyIndex += 1
+        else:
+            self[self.__emptyIndex] = task
+            self.__emptyIndex += 1
+    def remove(self, i):
+        assert(i <= len(self))
+        if (len(self) == 1) and (i == 1):
+            self[i] = None
+            self.__emptyIndex = 0
+        else:
+            # Swap the last element for this one
+            lastElement = self[self.__emptyIndex-1]
+            self[i] = lastElement
+            self[self.__emptyIndex-1] = None
+            self.__emptyIndex -= 1
+
+
 class TaskManager:
 
     notify = None
@@ -226,6 +268,8 @@ class TaskManager:
         self.running = 0
         self.stepping = 0
         self.taskList = []
+        self.newTasks = []
+        self.doLaterList = []
         self.currentTime, self.currentFrame = self.__getTimeFrame()
         if (TaskManager.notify == None):
             TaskManager.notify = directNotify.newCategory("TaskManager")
@@ -236,6 +280,9 @@ class TaskManager:
         self.pStatsTasks = 0
         self.resumeFunc = None
         self.fVerbose = 0
+        self.nameDict = {}
+        self.add(self.__doLaterProcessor, "doLaterProcessor")
+        # Dictionary of task name to list of tasks with that name
 
     def stepping(self, value):
         self.stepping = value
@@ -252,6 +299,74 @@ class TaskManager:
             # Next time around use the default interrupt handler
             signal.signal(signal.SIGINT, signal.default_int_handler)
 
+    def hasTaskNamed(self, taskName):
+        # Get the tasks with this name
+        tasks = self.nameDict.get(taskName)
+        # If we found some, see if any of them are still active (not removed)
+        if tasks:
+            for task in tasks:
+                if not task.isRemoved():
+                    return 1
+        # Didnt find any, return 0
+        return 0
+
+    def __doLaterProcessor(self, task):
+        # Make a temp list of all the dolaters that expired this time
+        # through so we can remove them after we are done with the
+        # for loop. Removing them during the for loop is a bad idea
+        removedTasks = []
+        for dl in self.doLaterList:
+            if dl.isRemoved():
+                removedTasks.append(dl)
+                continue
+            # If the time now is less than the start of the doLater + delay
+            # then we are not ready yet, continue to next one
+            if task.time < dl.starttime + dl.delayTime:
+                # Since the list is sorted, the first one we get to, that
+                # is not ready to go, we can return
+                break
+            else:
+                removedTasks.append(dl)
+                dl.setStartTimeFrame(self.currentTime, self.currentFrame)
+                self.newTasks.append(dl)
+                continue
+        if removedTasks:
+            self.doLaterList = unique(self.doLaterList, removedTasks)
+        return cont
+
+    def __spawnDoLater(self, task):
+        if TaskManager.notify.getDebug():
+            TaskManager.notify.debug('spawning doLater named: ' + task.name)
+        # Add this task to the nameDict
+        nameList = ifAbsentPut(self.nameDict, task.name, [])
+        nameList.append(task)
+        task.setStartTimeFrame(self.currentTime, self.currentFrame)
+        # search from the beginning of the list to put this doLater in
+        # the list in order of execution from earliest to latest
+        # Assume it goes last unless we break out early
+        index = len(self.doLaterList) + 1
+        for i in range(len(self.doLaterList)):
+            dl = self.doLaterList[i]
+            remainingTime = ((dl.starttime + dl.delayTime) - self.currentTime)
+            if task.delayTime < remainingTime:
+                index = i
+                break
+        self.doLaterList.insert(index, task)
+        if self.fVerbose:
+            # Alert the world, a new task is born!
+            messenger.send('TaskManager-spawnDoLater',
+                           sentArgs = [task, task.name, index])
+        return task
+
+    def doLater(self, delayTime, task, taskName):
+        task.delayTime = delayTime
+        task.name = taskName
+        return self.__spawnDoLater(task)
+
+    def doMethodLater(self, delayTime, func, taskName):
+        task = Task(func)
+        return self.doLater(delayTime, task, taskName)
+
     def add(self, funcOrTask, name, priority = 0):
         """
         Add a new task to the taskMgr.
@@ -265,14 +380,6 @@ class TaskManager:
         else:
             self.notify.error('Tried to add a task that was not a Task or a func')
 
-    def remove(self, taskOrName):
-        if type(taskOrName) == type(''):
-            return self.__removeTasksNamed(taskOrName)
-        elif isinstance(taskOrName, Task):
-            return self.__removeTask(taskOrName)
-        else:
-            self.notify.error('remove takes a string or a Task')
-
     def __spawnMethodNamed(self, func, name, priority=0):
         task = Task(func, priority)
         return self.__spawnTaskNamed(task, name)
@@ -280,19 +387,45 @@ class TaskManager:
     def __spawnTaskNamed(self, task, name):
         if TaskManager.notify.getDebug():
             TaskManager.notify.debug('spawning task named: ' + name)
+        # Init params
         task.name = name
         task.setStartTimeFrame(self.currentTime, self.currentFrame)
+        nameList = ifAbsentPut(self.nameDict, name, [])
+        nameList.append(task)
+        # Put it on the list for the end of this frame
+        self.newTasks.append(task)
+
+    def __addNewTask(self, task):        
+        # The taskList is really an ordered list of TaskPriorityLists
         # search back from the end of the list until we find a
-        # task with a lower priority, or we hit the start of the list
+        # taskList with a lower priority, or we hit the start of the list
+        taskPriority = task.getPriority()
         index = len(self.taskList) - 1
         while (1):
             if (index < 0):
+                newList = TaskPriorityList(taskPriority)
+                newList.add(task)
+                # Add the new list to the beginning of the taskList
+                self.taskList.insert(0, newList)
                 break
-            if (self.taskList[index].getPriority() <= task.getPriority()):
+            taskListPriority = self.taskList[index].getPriority()
+            if (taskListPriority == taskPriority):
+                self.taskList[index].add(task)
+                break
+            elif (taskListPriority > taskPriority):
+                index = index - 1
+            elif (taskListPriority < taskPriority):
+                # Time to insert
+                newList = TaskPriorityList(taskPriority)
+                newList.add(task)
+                # Insert this new priority level
+                # If we are already at the end, just append it
+                if (index == len(self.taskList)-1):
+                    self.taskList.append(newList)
+                else:
+                    # Otherwise insert it
+                    self.taskList.insert(index+1, newList)
                 break
-            index = index - 1
-        index = index + 1
-        self.taskList.insert(index, task)
 
         if __debug__:
             if self.pStatsTasks and name != "igloop":
@@ -313,46 +446,13 @@ class TaskManager:
                 
         return task
 
-    def doMethodLater(self, delayTime, func, taskName):
-        task = Task(func)
-        seq = doLater(delayTime, task, taskName)
-        seq.laterTask = task
-        return self.__spawnTaskNamed(seq, taskName)
-
-    def __removeTask(self, task):
-        if TaskManager.notify.getDebug():
-            TaskManager.notify.debug('removing task: ' + `task`)
-        if task in self.taskList:
-            self.taskList.remove(task)
-            if task.uponDeath:
-                task.uponDeath(task)
-            if self.fVerbose:
-                # We regret to announce...
-                messenger.send('TaskManager-removeTask',
-                               sentArgs = [task, task.name])
-
-    def __removeTasksNamed(self, taskName):
-        if TaskManager.notify.getDebug():
-            TaskManager.notify.debug('removing tasks named: ' + taskName)
-
-        # Find the tasks that match by name and make a list of them
-        removedTasks = []
-        for task in self.taskList:
-           if (task.name == taskName):
-               removedTasks.append(task)
-
-        # Now iterate through the tasks we need to remove and remove them
-        for task in removedTasks:
-            self.__removeTask(task)
-
-        # Return the number of tasks removed
-        return len(removedTasks)
-
-    def hasTaskNamed(self, taskName):
-        for task in self.taskList:
-            if (task.name == taskName):
-                return 1
-        return 0
+    def remove(self, taskOrName):
+        if type(taskOrName) == type(''):
+            return self.__removeTasksNamed(taskOrName)
+        elif isinstance(taskOrName, Task):
+            return self.__removeTasksEqual(taskOrName)
+        else:
+            self.notify.error('remove takes a string or a Task')
 
     def removeTasksMatching(self, taskPattern):
         """removeTasksMatching(self, string taskPattern)
@@ -363,19 +463,79 @@ class TaskManager:
         """
         if TaskManager.notify.getDebug():
             TaskManager.notify.debug('removing tasks matching: ' + taskPattern)
-        removedTasks = []
+        num = 0
+        keyList = filter(lambda key: fnmatch.fnmatchcase(key, taskPattern), self.nameDict.keys())
+        for key in keyList:
+            num += self.__removeTasksNamed(key)
+        return num
 
-        # Find the tasks that match by name and make a list of them
-        for task in self.taskList:
-            if (fnmatch.fnmatchcase(task.name, taskPattern)):
-                removedTasks.append(task)
-
-        # Now iterate through the tasks we need to remove and remove them
-        for task in removedTasks:
-           self.__removeTask(task)
+    def __removeTasksEqual(self, task):
+        if TaskManager.notify.getDebug():
+            TaskManager.notify.debug('__removeTasksEqual: removing task: %s' % (task))
+        taskName = task.name
+        # Remove this task from the nameDict (should be a short list)
+        self.__removeTaskFromNameDict(task)
+        # Flag the task for removal from the real list
+        task.remove()
+        # Cleanup stuff
+        task.finishTask(self.fVerbose)
+        return 1
 
-        # Return the number of tasks removed
-        return len(removedTasks)
+    def __removeTasksNamed(self, taskName):
+        if TaskManager.notify.getDebug():
+            TaskManager.notify.debug('__removeTasksNamed: removing tasks named: %s' % (taskName))
+        if not self.nameDict.has_key(taskName):
+            return 0
+        for task in self.nameDict[taskName]:
+            # Flag for removal
+            task.remove()
+            # Cleanup stuff
+            task.finishTask(self.fVerbose)
+        # Record the number of tasks removed
+        num = len(self.nameDict[taskName])
+        # Blow away the nameDict entry completely
+        del self.nameDict[taskName]
+        return num
+
+    def __removeTaskFromNameDict(self, task):
+        taskName = task.name
+        # If this is the only task with that name, remove the dict entry
+        tasksWithName = self.nameDict.get(taskName)
+        if tasksWithName:
+            tasksWithName.remove(task)
+            if len(tasksWithName) == 0:
+                del self.nameDict[taskName]
+
+    def __executeTask(self, task, currentTime, currentFrame):
+        task.setCurrentTimeFrame(currentTime, currentFrame)
+        if not self.taskTimerVerbose:
+            # don't record timing info
+            ret = task(task)
+        else:
+            # Run the task and check the return value
+            if task.pstats:
+                task.pstats.start()
+            startTime = time.clock()
+            ret = task(task)
+            endTime = time.clock()
+            if task.pstats:
+                task.pstats.stop()
+
+            # Record the dt
+            dt = endTime - startTime
+            task.dt = dt
+
+            # See if this is the new max
+            if dt > task.maxDt:
+                task.maxDt = dt
+
+            # Record the running total of all dts so we can compute an average
+            task.runningTotal = task.runningTotal + dt
+            if (task.frame > 0):
+                task.avgDt = (task.runningTotal / task.frame)
+            else:
+                task.avgDt = 0
+        return ret
 
     def step(self):
         if TaskManager.notify.getDebug():
@@ -387,51 +547,57 @@ class TaskManager:
         self.fKeyboardInterrupt = 0
         self.interruptCount = 0
         signal.signal(signal.SIGINT, self.keyboardInterruptHandler)
-        for task in self.taskList:
-            task.setCurrentTimeFrame(self.currentTime, self.currentFrame)
 
-            if not self.taskTimerVerbose:
-                # don't record timing info
-                ret = task(task)
-            else:
-                # Run the task and check the return value
-                if task.pstats:
-                    task.pstats.start()
-                startTime = time.clock()
-                ret = task(task)
-                endTime = time.clock()
-                if task.pstats:
-                    task.pstats.stop()
-
-                # Record the dt
-                dt = endTime - startTime
-                task.dt = dt
-
-                # See if this is the new max
-                if dt > task.maxDt:
-                    task.maxDt = dt
-
-                # Record the running total of all dts so we can compute an average
-                task.runningTotal = task.runningTotal + dt
-                if (task.frame > 0):
-                    task.avgDt = (task.runningTotal / task.frame)
+        # Traverse the task list in order because it is in priority order
+        for taskPriList in self.taskList:
+            # Traverse the taskPriList with an iterator
+            i = 0
+            while (i < len(taskPriList)):
+                task = taskPriList[i]
+                # See if we are at the end of the real tasks
+                if task is None:
+                    TaskManager.notify.debug('step: end of priList %s' % (taskPriList.getPriority()))
+                    break
+                # See if this task has been removed in show code
+                if task.isRemoved():
+                    TaskManager.notify.debug('step: task is flagged for removal %s' % (task))
+                    task.finishTask(self.fVerbose)
+                    taskPriList.remove(i)
+                    # Do not increment the iterator
+                    continue
+                # Now actually execute the task
+                ret = self.__executeTask(task, self.currentTime, self.currentFrame)
+                # See if the task is done
+                if (ret == cont):
+                    # Leave it for next frame, its not done yet
+                    pass
+                elif ((ret == done) or (ret == exit)):
+                    TaskManager.notify.debug('step: task is finished %s' % (task))
+                    # Remove the task
+                    if not task.isRemoved():
+                        task.remove()
+                        task.finishTask(self.fVerbose)
+                        self.__removeTaskFromNameDict(task)
+                    taskPriList.remove(i)
+                    # Do not increment the iterator
+                    continue
                 else:
-                    task.avgDt = 0
+                    raise StandardError, "Task named %s did not return cont, exit, or done" % task.name
+                # Move to the next element
+                i += 1
+                
+        # Add all the new tasks now
+        for task in self.newTasks:
+            TaskManager.notify.debug('step: adding new task to list %s' % (task))
+            self.__addNewTask(task)
+        # Reset for next frame
+        self.newTasks = []
 
-            # See if the task is done
-            if (ret == cont):
-                continue
-            elif (ret == done):
-                self.__removeTask(task)
-            elif (ret == exit):
-                self.__removeTask(task)
-            else:
-                raise StandardError, "Task named %s did not return cont, exit, or done" % task.name
         # Restore default interrupt handler
         signal.signal(signal.SIGINT, signal.default_int_handler)
         if self.fKeyboardInterrupt:
             raise KeyboardInterrupt
-        return len(self.taskList)
+        return
 
     def run(self):
         # Set the clock to have last frame's time in case we were
@@ -467,29 +633,32 @@ class TaskManager:
 
     def replaceMethod(self, oldMethod, newFunction):
         import new
-        for task in self.taskList:
-            method = task.__call__
-            if (type(method) == types.MethodType):
-                function = method.im_func
-            else:
-                function = method
-            #print ('function: ' + `function` + '\n' +
-            #       'method: ' + `method` + '\n' +
-            #       'oldMethod: ' + `oldMethod` + '\n' +
-            #       'newFunction: ' + `newFunction` + '\n')
-            if (function == oldMethod):
-                newMethod = new.instancemethod(newFunction,
-                                               method.im_self,
-                                               method.im_class)
-                task.__call__ = newMethod
-                # Found it return true
-                return 1
+        for taskPriList in self.taskList:
+            for task in taskPriList:
+                if task is None:
+                    break
+                method = task.__call__
+                if (type(method) == types.MethodType):
+                    function = method.im_func
+                else:
+                    function = method
+                #print ('function: ' + `function` + '\n' +
+                #       'method: ' + `method` + '\n' +
+                #       'oldMethod: ' + `oldMethod` + '\n' +
+                #       'newFunction: ' + `newFunction` + '\n')
+                if (function == oldMethod):
+                    newMethod = new.instancemethod(newFunction,
+                                                   method.im_self,
+                                                   method.im_class)
+                    task.__call__ = newMethod
+                    # Found it return true
+                    return 1
         return 0
 
     def __repr__(self):
         import fpformat
         taskNameWidth = 32
-        dtWidth = 7
+        dtWidth = 10
         priorityWidth = 10
         totalDt = 0
         totalAvgDt = 0
@@ -500,23 +669,43 @@ class TaskManager:
                + 'priority'.rjust(priorityWidth)
                + '\n')
         str = str + '---------------------------------------------------------------\n'
-        for task in self.taskList:
-            totalDt = totalDt + task.dt
-            totalAvgDt = totalAvgDt + task.avgDt
-            if (self.taskTimerVerbose):
-                str = str + (task.name.ljust(taskNameWidth)
-                             + fpformat.fix(task.dt*1000, 2).rjust(dtWidth)
-                             + fpformat.fix(task.avgDt*1000, 2).rjust(dtWidth)
-                             + fpformat.fix(task.maxDt*1000, 2).rjust(dtWidth)
-                             + `task.getPriority()`.rjust(priorityWidth)
-                             + '\n')
+        for taskPriList in self.taskList:
+            priority = `taskPriList.getPriority()`
+            for task in taskPriList:
+                if task is None:
+                    break
+                totalDt = totalDt + task.dt
+                totalAvgDt = totalAvgDt + task.avgDt
+                if task.isRemoved():
+                    taskName = '(R)' + task.name
+                else:
+                    taskName = task.name
+                if (self.taskTimerVerbose):
+                    str = str + (taskName.ljust(taskNameWidth)
+                                 + fpformat.fix(task.dt*1000, 2).rjust(dtWidth)
+                                 + fpformat.fix(task.avgDt*1000, 2).rjust(dtWidth)
+                                 + fpformat.fix(task.maxDt*1000, 2).rjust(dtWidth)
+                                 + priority.rjust(priorityWidth)
+                                 + '\n')
+                else:
+                    str = str + (task.name.ljust(taskNameWidth)
+                                 + '----'.rjust(dtWidth)
+                                 + '----'.rjust(dtWidth)
+                                 + '----'.rjust(dtWidth)
+                                 + priority.rjust(priorityWidth)
+                                 + '\n')
+        str = str + '---------------------------------------------------------------\n'
+        str = str + ' doLaterList\n'
+        str = str + '---------------------------------------------------------------\n'
+        for task in self.doLaterList:
+            remainingTime = ((task.starttime + task.delayTime) - self.currentTime)
+            if task.isRemoved():
+                taskName = '(R)' + task.name
             else:
-                str = str + (task.name.ljust(taskNameWidth)
-                             + '----'.rjust(dtWidth)
-                             + '----'.rjust(dtWidth)
-                             + '----'.rjust(dtWidth)
-                             + `task.getPriority()`.rjust(priorityWidth)
-                             + '\n')
+                taskName = task.name
+            str = str + ('  ' + taskName.ljust(taskNameWidth-2)
+                         +  fpformat.fix(remainingTime, 2).rjust(dtWidth)
+                         + '\n')
         str = str + '---------------------------------------------------------------\n'
         if (self.taskTimerVerbose):
             str = str + ('total'.ljust(taskNameWidth)
@@ -531,6 +720,7 @@ class TaskManager:
         return str
 
     def resetStats(self):
+        # WARNING: this screws up your do-later timings
         for task in self.taskList:
             task.dt = 0
             task.avgDt = 0