Hierarchical task manager

Hi,

I am starting a new Panda3D based project and while designing the architecture a doubt have come to my mind. Is it possible to have a hierarchical task manager?

What I mean, is a TaskManager a Task? It would be very easy anyway to actually convert a TaskManager into a Task:

def mk_task_manager_task (taskmgr):
  def task ():
    taskmgr.step ()
    return Task.cont
  return task

Still, I don’t know if this would actually work, because it seems that each taskmanager has its own clock and I don’t know if it could become a mess having two taskmanagers (the outer and the inner) messing up with the time.

The idea behind this hierarchical TaskManager is that when you have a StateManager for managing states (levels, menus, etc) each State has its own TaskManager and therefore it does not have to register/deregister its task manually when entering or leaving the state, with the potential conflicts that it might create alos (p.e. conflict between task names in two active states…).

Any ideas?

In that case… are you not just better off just using threads instead of using the taskmagr? This way threads can be in both blocking and nonblocking when running code. Also it would keep the cpu free by spreading the work around cores on dual +.

DeBugThread = threading.Thread(target=self.DeBug)
DeBugThread.isDaemon()
DeBugThread.start()

Taskmgr helps new comers on running their code iby keeping track of the task that are needed, this way no threading is needed (less worry about whats going on in another thread, so less debuging) and everything perty much lives in one time zone and state. It also keeps track of panada’s task.

I think it is technically possible to do this. It is true that you only want one task per frame to be responsible for ticking the clock, but this is not (necessarily) associated with the TaskManager itself; in fact, this is done by the “igloop” task by default. As long as this task is only added once, there should be no problem with the clock.

But I’m not sure whether this is really a great idea. Having multiple TaskManagers will introduce the confusion of which TaskManager a particular task needs to be added to or removed from, and if you get it wrong, you might be leaking tasks (attempting to remove a task from the wrong TaskManager will be a no-op, for instance). Also, having a single TaskManager makes it easy to see interactively the entire set of active tasks throughout the system, which is very helpful when developing.

There are better ways to manage the hierarchical scoping of tasks. First, if you always remove tasks by pointer instead of by task name, there is never any conflict between multiple tasks with the same name; but if you want all of your tasks to have unique names, you can simply prefix them all with the name of the State they’re associated with.

Also, if you want to add and remove blocks of tasks at a time when you enter and exit your states, it’s so easy to do this with something like:

for task in thisState.tasks:
  taskMgr.add(task)

since this is so easy, I’m not sure that the other advantage of hierarchical TaskManagers you report buys you anything.

I wouldn’t particularly recommend using threads to solve this problem, either. Once you introduce threading, you introduce all kinds of headaches caused by synchronization problems; it’s much better to avoid it unless there’s a real good reason for it. And Python threads don’t actually allow you to spread the work around multiple cores, because of the Python GIL.

Edit: another idea for grouping your tasks visually in the TaskManager is to assign each State its own task chain. The task chains are new with Panda 1.6.x, and aren’t fully documented yet, but it’s simple to use:

taskMgr.setupTaskChain('foo')
taskMgr.add(self.myTask, 'myTask', taskChain = 'foo')

David

Theres a way around the GIL and the next panda update will include something like this from what i’ve read…

docs.python.org/library/multiprocessing.html

Could be wrong all togather…

Yeah, but that’s nothing to do with threads.

David

Well, I do not think that it can lead to confusion. The idea is that every State object has its own TaskManager and that one should NEVER user the global TaskManager; only the StateManager (or even another more lower layer) would. I actually must admit that I dislike Panda’s wide use of global objects… my proposal just exactly to avoid leaking Tasks by making task management local :smiley: Still, what you say about loosing debugging information is interesting :smiley:

Actually I do not find any good usecase for named tasks, so I would definitely go for that:D

Ok, I will think about this carefully. I was actually thinking about building my own TaskManager. It is easy, and it might be more lightweight than Panda’s, but still that probably would mean rewriting the interval mechanism and so on… Probably the last two suggestions you made are the best way to go.

And yep, I agree that using threads in this case add innecessary complexity for nothing, because of the infamous Python GIL :smiley:

Thanks!
[/quote]

Yeah, there’s plenty of room for philosophical discussion about the proper balance of global vs. local objects.

I agree that Panda–actually, ShowBase, a very small subset of Panda, but one that everyone has to use–egregiously pollutes the Python builtin dictionary, and rather poorly overuses globals in general. I’d really like to fix this one day.

But that’s part of a different discussion. As to whether the Task Manager should be global or local, it’s a little more subtle.

There are tradeoffs between local and global objects. On the one hand, keeping objects local means limiting the size of the system that modifies these objects, and in turn limiting the breadth of knowledge you have to have in order to fully understand the system. A very good thing. On the other hand, making certain objects global makes communication between different parts of the system easier.

In any application framework, there are some structures which really are better suited to be globals. For instance, the memory heap management is usually (though not always) best handled as a single global heap, instead of lots of little individual heaps for each section of the code. The biggest reason for this is technical: the computer hardware presents a single continuous range of memory addresses to the process, and having lots of little heaps that grow and shrink independently would require constantly juggling memory addresses, or special OS-level support to virtualize the memory addresses within user space.

I think the scene graph is also best expressed as a global, because there is after all one scene that is ultimately rendered. (That’s not to say that I think “render” should be a Python builtin, because I don’t. But that’s a different question.) You could have each part of your code operate on its own little segment of the scene graph, and then try to integrate them all together later, but you’d run into problems when they try to create objects that interact with each other.

Similarly, there are sometimes tasks from different systems that must interact with each other, or that at least must be precisely ordered with respect to each other. For instance, the collision traversal normally has to be performed immediately before the igloop task. But sometimes you need to add a new task between the two, or a specialty task immediately before collisions, or immediately after igloop, or wherever.

To the extent that all of your tasks really are isolated and independent, you can effectively manage each of them on their own TaskManager. But in a complex system, tasks don’t often remain isolated and independent. :slight_smile:

But, yeah, this really is a philosophical matter; and if you feel you’d be better off with several local TaskManagers instead of a global one, by all means go for it. :slight_smile:

Note that as of Panda 1.6.x, the TaskManager is actually implemented in C++, and thus has a fairly lightweight runtime overhead.

David