<commit_before>""" This module defines a Python-level wrapper around the C++
AsyncTaskManager interface.  It replaces the old full-Python
implementation of the Task system. """

__all__ = ['Task', 'TaskManager',
           'cont', 'done', 'again', 'pickup', 'exit',
           'sequence', 'loop', 'pause']

from direct.directnotify.DirectNotifyGlobal import *
from direct.showbase import ExceptionVarDump
from direct.showbase.PythonUtil import *
from direct.showbase.ProfileSession import ProfileSession
import signal
import types
import time
import random
import string

from pandac.PandaModules import *

def print_exc_plus():
    """
    Print the usual traceback information, followed by a listing of all the
    local variables in each frame.
    """
    import sys
    import traceback

    tb = sys.exc_info()[2]
    while 1:
        if not tb.tb_next:
            break
        tb = tb.tb_next
    stack = []
    f = tb.tb_frame
    while f:
        stack.append(f)
        f = f.f_back
    stack.reverse()
    traceback.print_exc()
    print "Locals by frame, innermost last"
    for frame in stack:
        print
        print "Frame %s in %s at line %s" % (frame.f_code.co_name,
                                             frame.f_code.co_filename,
                                             frame.f_lineno)
        for key, value in frame.f_locals.items():
            print "\t%20s = " % key,
            #We have to be careful not to cause a new error in our error
            #printer! Calling str() on an unknown object could cause an
            #error we don't want.
            try:
                print value
            except:
                print "<ERROR WHILE PRINTING VALUE>"

# For historical purposes, we remap the C++-defined enumeration to
# these Python names, and define them both at the module level, here,
# and at the class level (below).  The preferred access is via the
# class level.
done = AsyncTask.DSDone
cont = AsyncTask.DSCont
again = AsyncTask.DSAgain
pickup = AsyncTask.DSPickup
exit = AsyncTask.DSExit

# Alias PythonTask to Task for historical purposes.
Task = PythonTask

# Copy the module-level enums above into the class level.  This funny
# syntax is necessary because it's a C++-wrapped extension type, not a
# true Python class.
Task.DtoolClassDict['done'] = done
Task.DtoolClassDict['cont'] = cont
Task.DtoolClassDict['again'] = again
Task.DtoolClassDict['pickup'] = pickup
Task.DtoolClassDict['exit'] = exit

# Alias the AsyncTaskPause constructor as Task.pause().
pause = AsyncTaskPause
Task.DtoolClassDict['pause'] = staticmethod(pause)

def sequence(*taskList):
    seq = AsyncTaskSequence('sequence')
    for task in taskList:
        seq.addTask(task)
    return seq
Task.DtoolClassDict['sequence'] = staticmethod(sequence)

def loop(*taskList):
    seq = AsyncTaskSequence('loop')
    for task in taskList:
        seq.addTask(task)
    seq.setRepeatCount(-1)
    return seq
Task.DtoolClassDict['loop'] = staticmethod(loop)

class TaskManager:
    notify = directNotify.newCategory("TaskManager")

    extendedExceptions = False
    MaxEpochSpeed = 1.0/30.0

    def __init__(self):
        self.mgr = AsyncTaskManager.getGlobalPtr()

        self.resumeFunc = None
        self.globalClock = self.mgr.getClock()
        self.stepping = False
        self.running = False
        self.fKeyboardInterrupt = False
        self.interruptCount = 0

        self._frameProfileQueue = Queue()

        # this will be set when it's safe to import StateVar 
        self._profileFrames = None
        self._frameProfiler = None
        self._profileTasks = None
        self._taskProfiler = None
        self._taskProfileInfo = ScratchPad(
            taskId = None,
            profiled = False,
            session = None,
            )

    def finalInit(self):
        # This function should be called once during startup, after
        # most things are imported.
        from direct.fsm.StatePush import StateVar
        self._profileTasks = StateVar(False)
        self.setProfileTasks(ConfigVariableBool('profile-task-spikes', 0).getValue())
        self._profileFrames = StateVar(False)
        self.setProfileFrames(ConfigVariableBool('profile-frames', 0).getValue())

    def destroy(self):
        # This should be safe to call multiple times.
        
        self._frameProfileQueue.clear()
        self.mgr.cleanup()

    def setClock(self, clockObject):
        self.mgr.setClock(clockObject)
        self.globalClock = clockObject

    def invokeDefaultHandler(self, signalNumber, stackFrame):
        print '*** allowing mid-frame keyboard interrupt.'
        # Restore default interrupt handler
        signal.signal(signal.SIGINT, signal.default_int_handler)
        # and invoke it
        raise KeyboardInterrupt

    def keyboardInterruptHandler(self, signalNumber, stackFrame):
        self.fKeyboardInterrupt = 1
        self.interruptCount += 1
        if self.interruptCount == 1:
            print '* interrupt by keyboard'
        elif self.interruptCount == 2:
            print '** waiting for end of frame before interrupting...'
            # The user must really want to interrupt this process
            # Next time around invoke the default handler
            signal.signal(signal.SIGINT, self.invokeDefaultHandler)

    def setupTaskChain(self, chainName, numThreads = None, tickClock = None,
                       threadPriority = None, frameBudget = None,
                       timeslicePriority = None):
        """Defines a new task chain.  Each task chain executes tasks
        potentially in parallel with all of the other task chains (if
        numThreads is more than zero).  When a new task is created, it
        may be associated with any of the task chains, by name (or you
        can move a task to another task chain with
        task.setTaskChain()).  You can have any number of task chains,
        but each must have a unique name.

        numThreads is the number of threads to allocate for this task
        chain.  If it is 1 or more, then the tasks on this task chain
        will execute in parallel with the tasks on other task chains.
        If it is greater than 1, then the tasks on this task chain may
        execute in parallel with themselves (within tasks of the same
        sort value).

        If tickClock is True, then this task chain will be responsible
        for ticking the global clock each frame (and thereby
        incrementing the frame counter).  There should be just one
        task chain responsible for ticking the clock, and usually it
        is the default, unnamed task chain.

        threadPriority specifies the priority level to assign to
        threads on this task chain.  It may be one of TPLow, TPNormal,
        TPHigh, or TPUrgent.  This is passed to the underlying
        threading system to control the way the threads are scheduled.

        frameBudget is the maximum amount of time (in seconds) to
        allow this task chain to run per frame.  Set it to -1 to mean
        no limit (the default).  It's not directly related to
        threadPriority.

        timeslicePriority is False in the default mode, in which each
        task runs exactly once each frame, round-robin style,
        regardless of the task's priority value; or True to change the
        meaning of priority so that certain tasks are run less often,
        in proportion to their time used and to their priority value.
        See AsyncTaskManager.setTimeslicePriority() for more.
        """
        
        chain = self.mgr.makeTaskChain(chainName)
        if numThreads is not None:
            chain.setNumThreads(numThreads)
        if tickClock is not None:
            chain.setTickClock(tickClock)
        if threadPriority is not None:
            chain.setThreadPriority(threadPriority)
        if frameBudget is not None:
            chain.setFrameBudget(frameBudget)
        if timeslicePriority is not None:
            chain.setTimeslicePriority(timeslicePriority)

    def hasTaskNamed(self, taskName):
        """Returns true if there is at least one task, active or
        sleeping, with the indicated name. """
        
        return bool(self.mgr.findTask(taskName))

    def getTasksNamed(self, taskName):
        """Returns a list of all tasks, active or sleeping, with the
        indicated name. """
        return self.__makeTaskList(self.mgr.findTasks(taskName))

    def getTasksMatching(self, taskPattern):
        """Returns a list of all tasks, active or sleeping, with a
        name that matches the pattern, which can include standard
        shell globbing characters like *, ?, and []. """
        
        return self.__makeTaskList(self.mgr.findTasksMatching(GlobPattern(taskPattern)))

    def getAllTasks(self):
        """Returns list of all tasks, active and sleeping, in
        arbitrary order. """
        return self.__makeTaskList(self.mgr.getTasks())

    def getTasks(self):
        """Returns list of all active tasks in arbitrary order. """
        return self.__makeTaskList(self.mgr.getActiveTasks())

    def getDoLaters(self):
        """Returns list of all sleeping tasks in arbitrary order. """
        return self.__makeTaskList(self.mgr.getSleepingTasks())

    def __makeTaskList(self, taskCollection):
        l = []
        for i in range(taskCollection.getNumTasks()):
            l.append(taskCollection.getTask(i))
        return l

    def doMethodLater(self, delayTime, funcOrTask, name, extraArgs = None,
                      sort = None, priority = None, taskChain = None,
                      uponDeath = None, appendTask = False, owner = None):

        """Adds a task to be performed at some time in the future.
        This is identical to add(), except that the specified
        delayTime is applied to the Task object first, which means
        that the task will not begin executing until at least the
        indicated delayTime (in seconds) has elapsed.

        After delayTime has elapsed, the task will become active, and
        will run in the soonest possible frame thereafter.  If you
        wish to specify a task that will run in the next frame, use a
        delayTime of 0.
        """
        
        if delayTime < 0:
            assert self.notify.warning('doMethodLater: added task: %s with negative delay: %s' % (name, delayTime))

        task = self.__setupTask(funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath)
        task.setDelay(delayTime)
        self.mgr.add(task)
        return task

    def add(self, funcOrTask, name = None, sort = None, extraArgs = None,
            priority = None, uponDeath = None, appendTask = False,
            taskChain = None, owner = None):
        
        """
        Add a new task to the taskMgr.  The task will begin executing
        immediately, or next frame if its sort value has already
        passed this frame.

        The parameters are:

        funcOrTask - either an existing Task object (not already added
        to the task manager), or a callable function object.  If this
        is a function, a new Task object will be created and returned.

        name - the name to assign to the Task.  Required, unless you
        are passing in a Task object that already has a name.

        extraArgs - the list of arguments to pass to the task
        function.  If this is omitted, the list is just the task
        object itself.

        appendTask - a boolean flag.  If this is true, then the task
        object itself will be appended to the end of the extraArgs
        list before calling the function.

        sort - the sort value to assign the task.  The default sort is
        0.  Within a particular task chain, it is guaranteed that the
        tasks with a lower sort value will all run before tasks with a
        higher sort value run.

        priority - the priority at which to run the task.  The default
        priority is 0.  Higher priority tasks are run sooner, and/or
        more often.  For historical purposes, if you specify a
        priority without also specifying a sort, the priority value is
        understood to actually be a sort value.  (Previously, there
        was no priority value, only a sort value, and it was called
        "priority".)

        uponDeath - a function to call when the task terminates,
        either because it has run to completion, or because it has
        been explicitly removed.

        taskChain - the name of the task chain to assign the task to.

        owner - an optional Python object that is declared as the
        "owner" of this task for maintenance purposes.  The owner must
        have two methods: owner._addTask(self, task), which is called
        when the task begins, and owner._clearTask(self, task), which
        is called when the task terminates.  This is all the owner
        means.

        The return value of add() is the new Task object that has been
        added, or the original Task object that was passed in.

        """
        
        task = self.__setupTask(funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath)
        self.mgr.add(task)
        return task

    def __setupTask(self, funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath):
        if isinstance(funcOrTask, AsyncTask):
            task = funcOrTask
        elif callable(funcOrTask):
            task = PythonTask(funcOrTask)
        else:
            self.notify.error(
                'add: Tried to add a task that was not a Task or a func')

        if hasattr(task, 'setArgs'):
            # It will only accept arguments if it's a PythonTask.
            if extraArgs is None:
                extraArgs = []
                appendTask = True
            task.setArgs(extraArgs, appendTask)
        elif extraArgs is not None:
            self.notify.error(
                'Task %s does not accept arguments.' % (repr(task)))

        if name is not None:
            assert isinstance(name, types.StringTypes), 'Name must be a string type'
            task.setName(name)
        assert task.hasName()

        # For historical reasons, if priority is specified but not
        # sort, it really means sort.
        if priority is not None and sort is None:
            task.setSort(priority)
        else:
            if priority is not None:
                task.setPriority(priority)
            if sort is not None:
                task.setSort(sort)

        if taskChain is not None:
            task.setTaskChain(taskChain)

        if owner is not None:
            task.setOwner(owner)

        if uponDeath is not None:
            task.setUponDeath(uponDeath)

        return task
        
    def remove(self, taskOrName):
        """Removes a task from the task manager.  The task is stopped,
        almost as if it had returned task.done.  (But if the task is
        currently executing, it will finish out its current frame
        before being removed.)  You may specify either an explicit
        Task object, or the name of a task.  If you specify a name,
        all tasks with the indicated name are removed.  Returns the
        number of tasks removed. """
        
        if isinstance(taskOrName, types.StringTypes):
            tasks = self.mgr.findTasks(taskOrName)
            return self.mgr.remove(tasks)
        elif isinstance(taskOrName, AsyncTask):
            return self.mgr.remove(taskOrName)
        else:
            self.notify.error('remove takes a string or a Task')

    def removeTasksMatching(self, taskPattern):
        """Removes all tasks whose names match the pattern, which can
        include standard shell globbing characters like *, ?, and [].
        See also remove().

        Returns the number of tasks removed.
        """
        tasks = self.mgr.findTasksMatching(GlobPattern(taskPattern))
        return self.mgr.remove(tasks)

    def step(self):
        """Invokes the task manager for one frame, and then returns.
        Normally, this executes each task exactly once, though task
        chains that are in sub-threads or that have frame budgets
        might execute their tasks differently. """

        # Replace keyboard interrupt handler during task list processing
        # so we catch the keyboard interrupt but don't handle it until
        # after task list processing is complete.
        self.fKeyboardInterrupt = 0
        self.interruptCount = 0
        signal.signal(signal.SIGINT, self.keyboardInterruptHandler)

        startFrameTime = self.globalClock.getRealTime()

        self.mgr.poll()<commit_msg>remove accepts a list<commit_after>""" This module defines a Python-level wrapper around the C++
AsyncTaskManager interface.  It replaces the old full-Python
implementation of the Task system. """

__all__ = ['Task', 'TaskManager',
           'cont', 'done', 'again', 'pickup', 'exit',
           'sequence', 'loop', 'pause']

from direct.directnotify.DirectNotifyGlobal import *
from direct.showbase import ExceptionVarDump
from direct.showbase.PythonUtil import *
from direct.showbase.ProfileSession import ProfileSession
import signal
import types
import time
import random
import string

from pandac.PandaModules import *

def print_exc_plus():
    """
    Print the usual traceback information, followed by a listing of all the
    local variables in each frame.
    """
    import sys
    import traceback

    tb = sys.exc_info()[2]
    while 1:
        if not tb.tb_next:
            break
        tb = tb.tb_next
    stack = []
    f = tb.tb_frame
    while f:
        stack.append(f)
        f = f.f_back
    stack.reverse()
    traceback.print_exc()
    print "Locals by frame, innermost last"
    for frame in stack:
        print
        print "Frame %s in %s at line %s" % (frame.f_code.co_name,
                                             frame.f_code.co_filename,
                                             frame.f_lineno)
        for key, value in frame.f_locals.items():
            print "\t%20s = " % key,
            #We have to be careful not to cause a new error in our error
            #printer! Calling str() on an unknown object could cause an
            #error we don't want.
            try:
                print value
            except:
                print "<ERROR WHILE PRINTING VALUE>"

# For historical purposes, we remap the C++-defined enumeration to
# these Python names, and define them both at the module level, here,
# and at the class level (below).  The preferred access is via the
# class level.
done = AsyncTask.DSDone
cont = AsyncTask.DSCont
again = AsyncTask.DSAgain
pickup = AsyncTask.DSPickup
exit = AsyncTask.DSExit

# Alias PythonTask to Task for historical purposes.
Task = PythonTask

# Copy the module-level enums above into the class level.  This funny
# syntax is necessary because it's a C++-wrapped extension type, not a
# true Python class.
Task.DtoolClassDict['done'] = done
Task.DtoolClassDict['cont'] = cont
Task.DtoolClassDict['again'] = again
Task.DtoolClassDict['pickup'] = pickup
Task.DtoolClassDict['exit'] = exit

# Alias the AsyncTaskPause constructor as Task.pause().
pause = AsyncTaskPause
Task.DtoolClassDict['pause'] = staticmethod(pause)

def sequence(*taskList):
    seq = AsyncTaskSequence('sequence')
    for task in taskList:
        seq.addTask(task)
    return seq
Task.DtoolClassDict['sequence'] = staticmethod(sequence)

def loop(*taskList):
    seq = AsyncTaskSequence('loop')
    for task in taskList:
        seq.addTask(task)
    seq.setRepeatCount(-1)
    return seq
Task.DtoolClassDict['loop'] = staticmethod(loop)

class TaskManager:
    notify = directNotify.newCategory("TaskManager")

    extendedExceptions = False
    MaxEpochSpeed = 1.0/30.0

    def __init__(self):
        self.mgr = AsyncTaskManager.getGlobalPtr()

        self.resumeFunc = None
        self.globalClock = self.mgr.getClock()
        self.stepping = False
        self.running = False
        self.fKeyboardInterrupt = False
        self.interruptCount = 0

        self._frameProfileQueue = Queue()

        # this will be set when it's safe to import StateVar 
        self._profileFrames = None
        self._frameProfiler = None
        self._profileTasks = None
        self._taskProfiler = None
        self._taskProfileInfo = ScratchPad(
            taskId = None,
            profiled = False,
            session = None,
            )

    def finalInit(self):
        # This function should be called once during startup, after
        # most things are imported.
        from direct.fsm.StatePush import StateVar
        self._profileTasks = StateVar(False)
        self.setProfileTasks(ConfigVariableBool('profile-task-spikes', 0).getValue())
        self._profileFrames = StateVar(False)
        self.setProfileFrames(ConfigVariableBool('profile-frames', 0).getValue())

    def destroy(self):
        # This should be safe to call multiple times.
        
        self._frameProfileQueue.clear()
        self.mgr.cleanup()

    def setClock(self, clockObject):
        self.mgr.setClock(clockObject)
        self.globalClock = clockObject

    def invokeDefaultHandler(self, signalNumber, stackFrame):
        print '*** allowing mid-frame keyboard interrupt.'
        # Restore default interrupt handler
        signal.signal(signal.SIGINT, signal.default_int_handler)
        # and invoke it
        raise KeyboardInterrupt

    def keyboardInterruptHandler(self, signalNumber, stackFrame):
        self.fKeyboardInterrupt = 1
        self.interruptCount += 1
        if self.interruptCount == 1:
            print '* interrupt by keyboard'
        elif self.interruptCount == 2:
            print '** waiting for end of frame before interrupting...'
            # The user must really want to interrupt this process
            # Next time around invoke the default handler
            signal.signal(signal.SIGINT, self.invokeDefaultHandler)

    def setupTaskChain(self, chainName, numThreads = None, tickClock = None,
                       threadPriority = None, frameBudget = None,
                       timeslicePriority = None):
        """Defines a new task chain.  Each task chain executes tasks
        potentially in parallel with all of the other task chains (if
        numThreads is more than zero).  When a new task is created, it
        may be associated with any of the task chains, by name (or you
        can move a task to another task chain with
        task.setTaskChain()).  You can have any number of task chains,
        but each must have a unique name.

        numThreads is the number of threads to allocate for this task
        chain.  If it is 1 or more, then the tasks on this task chain
        will execute in parallel with the tasks on other task chains.
        If it is greater than 1, then the tasks on this task chain may
        execute in parallel with themselves (within tasks of the same
        sort value).

        If tickClock is True, then this task chain will be responsible
        for ticking the global clock each frame (and thereby
        incrementing the frame counter).  There should be just one
        task chain responsible for ticking the clock, and usually it
        is the default, unnamed task chain.

        threadPriority specifies the priority level to assign to
        threads on this task chain.  It may be one of TPLow, TPNormal,
        TPHigh, or TPUrgent.  This is passed to the underlying
        threading system to control the way the threads are scheduled.

        frameBudget is the maximum amount of time (in seconds) to
        allow this task chain to run per frame.  Set it to -1 to mean
        no limit (the default).  It's not directly related to
        threadPriority.

        timeslicePriority is False in the default mode, in which each
        task runs exactly once each frame, round-robin style,
        regardless of the task's priority value; or True to change the
        meaning of priority so that certain tasks are run less often,
        in proportion to their time used and to their priority value.
        See AsyncTaskManager.setTimeslicePriority() for more.
        """
        
        chain = self.mgr.makeTaskChain(chainName)
        if numThreads is not None:
            chain.setNumThreads(numThreads)
        if tickClock is not None:
            chain.setTickClock(tickClock)
        if threadPriority is not None:
            chain.setThreadPriority(threadPriority)
        if frameBudget is not None:
            chain.setFrameBudget(frameBudget)
        if timeslicePriority is not None:
            chain.setTimeslicePriority(timeslicePriority)

    def hasTaskNamed(self, taskName):
        """Returns true if there is at least one task, active or
        sleeping, with the indicated name. """
        
        return bool(self.mgr.findTask(taskName))

    def getTasksNamed(self, taskName):
        """Returns a list of all tasks, active or sleeping, with the
        indicated name. """
        return self.__makeTaskList(self.mgr.findTasks(taskName))

    def getTasksMatching(self, taskPattern):
        """Returns a list of all tasks, active or sleeping, with a
        name that matches the pattern, which can include standard
        shell globbing characters like *, ?, and []. """
        
        return self.__makeTaskList(self.mgr.findTasksMatching(GlobPattern(taskPattern)))

    def getAllTasks(self):
        """Returns list of all tasks, active and sleeping, in
        arbitrary order. """
        return self.__makeTaskList(self.mgr.getTasks())

    def getTasks(self):
        """Returns list of all active tasks in arbitrary order. """
        return self.__makeTaskList(self.mgr.getActiveTasks())

    def getDoLaters(self):
        """Returns list of all sleeping tasks in arbitrary order. """
        return self.__makeTaskList(self.mgr.getSleepingTasks())

    def __makeTaskList(self, taskCollection):
        l = []
        for i in range(taskCollection.getNumTasks()):
            l.append(taskCollection.getTask(i))
        return l

    def doMethodLater(self, delayTime, funcOrTask, name, extraArgs = None,
                      sort = None, priority = None, taskChain = None,
                      uponDeath = None, appendTask = False, owner = None):

        """Adds a task to be performed at some time in the future.
        This is identical to add(), except that the specified
        delayTime is applied to the Task object first, which means
        that the task will not begin executing until at least the
        indicated delayTime (in seconds) has elapsed.

        After delayTime has elapsed, the task will become active, and
        will run in the soonest possible frame thereafter.  If you
        wish to specify a task that will run in the next frame, use a
        delayTime of 0.
        """
        
        if delayTime < 0:
            assert self.notify.warning('doMethodLater: added task: %s with negative delay: %s' % (name, delayTime))

        task = self.__setupTask(funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath)
        task.setDelay(delayTime)
        self.mgr.add(task)
        return task

    def add(self, funcOrTask, name = None, sort = None, extraArgs = None,
            priority = None, uponDeath = None, appendTask = False,
            taskChain = None, owner = None):
        
        """
        Add a new task to the taskMgr.  The task will begin executing
        immediately, or next frame if its sort value has already
        passed this frame.

        The parameters are:

        funcOrTask - either an existing Task object (not already added
        to the task manager), or a callable function object.  If this
        is a function, a new Task object will be created and returned.

        name - the name to assign to the Task.  Required, unless you
        are passing in a Task object that already has a name.

        extraArgs - the list of arguments to pass to the task
        function.  If this is omitted, the list is just the task
        object itself.

        appendTask - a boolean flag.  If this is true, then the task
        object itself will be appended to the end of the extraArgs
        list before calling the function.

        sort - the sort value to assign the task.  The default sort is
        0.  Within a particular task chain, it is guaranteed that the
        tasks with a lower sort value will all run before tasks with a
        higher sort value run.

        priority - the priority at which to run the task.  The default
        priority is 0.  Higher priority tasks are run sooner, and/or
        more often.  For historical purposes, if you specify a
        priority without also specifying a sort, the priority value is
        understood to actually be a sort value.  (Previously, there
        was no priority value, only a sort value, and it was called
        "priority".)

        uponDeath - a function to call when the task terminates,
        either because it has run to completion, or because it has
        been explicitly removed.

        taskChain - the name of the task chain to assign the task to.

        owner - an optional Python object that is declared as the
        "owner" of this task for maintenance purposes.  The owner must
        have two methods: owner._addTask(self, task), which is called
        when the task begins, and owner._clearTask(self, task), which
        is called when the task terminates.  This is all the owner
        means.

        The return value of add() is the new Task object that has been
        added, or the original Task object that was passed in.

        """
        
        task = self.__setupTask(funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath)
        self.mgr.add(task)
        return task

    def __setupTask(self, funcOrTask, name, priority, sort, extraArgs, taskChain, appendTask, owner, uponDeath):
        if isinstance(funcOrTask, AsyncTask):
            task = funcOrTask
        elif callable(funcOrTask):
            task = PythonTask(funcOrTask)
        else:
            self.notify.error(
                'add: Tried to add a task that was not a Task or a func')

        if hasattr(task, 'setArgs'):
            # It will only accept arguments if it's a PythonTask.
            if extraArgs is None:
                extraArgs = []
                appendTask = True
            task.setArgs(extraArgs, appendTask)
        elif extraArgs is not None:
            self.notify.error(
                'Task %s does not accept arguments.' % (repr(task)))

        if name is not None:
            assert isinstance(name, types.StringTypes), 'Name must be a string type'
            task.setName(name)
        assert task.hasName()

        # For historical reasons, if priority is specified but not
        # sort, it really means sort.
        if priority is not None and sort is None:
            task.setSort(priority)
        else:
            if priority is not None:
                task.setPriority(priority)
            if sort is not None:
                task.setSort(sort)

        if taskChain is not None:
            task.setTaskChain(taskChain)

        if owner is not None:
            task.setOwner(owner)

        if uponDeath is not None:
            task.setUponDeath(uponDeath)

        return task
        
    def remove(self, taskOrName):
        """Removes a task from the task manager.  The task is stopped,
        almost as if it had returned task.done.  (But if the task is
        currently executing, it will finish out its current frame
        before being removed.)  You may specify either an explicit
        Task object, or the name of a task.  If you specify a name,
        all tasks with the indicated name are removed.  Returns the
        number of tasks removed. """
        
        if isinstance(taskOrName, types.StringTypes):
            tasks = self.mgr.findTasks(taskOrName)
            return self.mgr.remove(tasks)
        elif isinstance(taskOrName, AsyncTask):
            return self.mgr.remove(taskOrName)
        elif isinstance(taskOrName, types.ListType):
            for task in taskOrName:
                self.remove(task)
        else:
            self.notify.error('remove takes a string or a Task')

    def removeTasksMatching(self, taskPattern):
        """Removes all tasks whose names match the pattern, which can
        include standard shell globbing characters like *, ?, and [].
        See also remove().

        Returns the number of tasks removed.
        """
        tasks = self.mgr.findTasksMatching(GlobPattern(taskPattern))
        return self.mgr.remove(tasks)

    def step(self):
        """Invokes the task manager for one frame, and then returns.
        Normally, this executes each task exactly once, though task
        chains that are in sub-threads or that have frame budgets
        might execute their tasks differently. """

        # Replace keyboard interrupt handler during task list processing
        # so we catch the keyboard interrupt but don't handle it until
        # after task list processing is complete.
        self.fKeyboardInterrupt = 0
        self.interruptCount = 0
        signal.signal(signal.SIGINT, self.keyboardInterruptHandler)

        startFrameTime = self.globalClock.getRealTime()

        self.mgr.poll()
{'fraction_non_alphanumeric': 0.06674623302998657, 'fraction_numerical': 0.001551544084738177, 'mean_word_length': 2.303370786516854, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6687863', 'n_tokens_mistral': 8658, 'n_tokens_neox': 8446, 'n_words': 3552}