Using multiprocessing module to spawn multiple ShowBase instances

Hi, I am currently developing a simple collision detection program which takes the inputs of coords of the two models that I want to check for a collision. I have roughly 1 million test cases that I need to test in order to check if one of the models collides with one another. I have been using multiprocessing.Pool() to make multiple processes of the app. I run it on an HPC which has 32 CPUs, so I can run the process 32 times. But as soon as 1 of those 32 processes finishes to start the next process, I get the error: Exception: Attempt to spawn multiple ShowBase instances!
I have tried multiple ways by destroying the ShowBase after every process, but the results I get from that run do not line up if I run the program on a single thread.

If someone is able to help me with a possible solution that would be great!!! Also is it worth writing the code in C++ and running it in parallel?

Thanks!

Hello, if you use collisions only for mathematical calculations, then you don’t have to create an instance of ShowBase.

from panda3d.core import (CollisionTraverser, CollisionHandlerQueue,
CollisionNode, CollisionSphere, NodePath)

root = NodePath("root")

cn_a = CollisionNode('A')
cn_a.add_solid(CollisionSphere(0, 0, 0, 1))

np_a = NodePath(cn_a)
np_a.set_pos(0, 0, -0.8)
np_a.reparent_to(root)

cn_b = CollisionNode('B')
cn_b.add_solid(CollisionSphere(0, 0, 0, 1))

np_b = NodePath(cn_b)
np_b.set_pos(0, 0, 0.8)
np_b.reparent_to(root)

handler_queue = CollisionHandlerQueue()

traverser = CollisionTraverser()
traverser.add_collider(np_a, handler_queue)
traverser.add_collider(np_b, handler_queue)

traverser.traverse(root)

if handler_queue.get_num_entries() > 0:
    for entry in handler_queue.get_entries():
        print(entry)

An example of a collision between two spheres.

Thank you very much for your reply! So for example, i want to check if my drone model, for simplicity is just a sphere, collides with an aircraft model, I have multiple .bam files for the aircraft, I would not have to import the models? As right now I loadModel in order to get the getTightBounds(). Is there a way I can do this without ShowBase?

Of course you can do it.

from panda3d.core import Loader

loader = Loader.get_global_ptr()

model = loader.load_sync("panda.egg")
np = NodePath(model)
print(np.get_tight_bounds())
(LPoint3f(-8.01252, -3.18482, 0.0283853), LPoint3f(8.01252, 3.00317, 12.21))

You are a life saver! I may have more questions down the line if i get stuck converting my code into non-ShowBase, but I appreciate your help alot!

“I run it on an HPC which has 32 CPUs, so I can run the process 32 times.”

On the one hand, yes, but multiprocessing is weirder than that.

That maximum is only for “32 processes at max efficiency” but that’s a theoretical limit, which almost never happens anyway. And python doesn’t really complain if you create more processes. Worst case is that some of them will idle a bit. You can’t really break things that way.

So you should or can try to start more processes than 32, see what happens.

This is particularly true if you’re doing lots of file input output or other operations that take longer, that are not necessarily CPU bottlenecked.

E.g. a process will

  • request to open a file and then idle a loooooong time.
  • get the data and do some quick calculation
  • request to write the result to a file and then idle a looooong time.

and during that idle time, other processes can run.

Basically, start more than the theoretical limit, shove work in the direction of those processes and leave it to your OS to handle the resources efficiently.

Makes sense. I may have phrased my sentence a bit poorly, but what I essentially was saying that I do let the OS handle the resources efficiently but as soon as the range in multiprocessing.Pool() is larger than 32, in my instance it would be ~range(100000), the program throws the exception of Exception: Attempt to spawn multiple ShowBase instances! after the first 32 cases are finished processing. This was because I was always had base = ShowBase(windowType=None) in the function so after reaching the cpu_count of 32, it would attempt to make another ShowBase in one of the 32 processes, which results in my error. Saying that I attempted to destroy the ShowBase after every process had finished, but this resulted in incorrect results and didn’t work as intended.

Am i able to use CollisionHandlerEvent instead of CollisionHandlerQueue?

You can use whatever you need, this is not part of the ShowBase.

But how can i accept a collision and have a function run as I can’t do it with root

It can be done this way, but I don’t understand how you are going to implement a collision test without moving from frame to frame.

https://docs.panda3d.org/1.10/python/programming/tasks-and-events/event-handlers

This handler works if you move, but it won’t work if the models were initially positioned in each other.

So thats what I intetend to do, simulate frame by frame the drone model moving and the aircraft model moving and then checking for collision

This circumstance slightly changes the approach… I need to think about it.

1 Like

After thinking about it, I came to the conclusion that it is easier to use CollisionHandlerQueue, CollisionHandlerEvent depends on the GraphicsEngine instance, I’m not sure what a good idea. My statement was wrong.

If you plan to use only two objects, then this is quite suitable.

from panda3d.core import (CollisionTraverser, CollisionHandlerQueue,
CollisionNode, CollisionSphere, NodePath)

root = NodePath("root")

cn_a = CollisionNode('A')
cn_a.add_solid(CollisionSphere(0, 0, 0, 1))

np_a = NodePath(cn_a)
np_a.set_pos(0, 0, -0.8)
np_a.reparent_to(root)

cn_b = CollisionNode('B')
cn_b.add_solid(CollisionSphere(0, 0, 0, 1))

np_b = NodePath(cn_b)
np_b.set_pos(0, 0, 0.8)
np_b.reparent_to(root)

handler_queue = CollisionHandlerQueue()

traverser = CollisionTraverser()
traverser.add_collider(np_a, handler_queue)
traverser.add_collider(np_b, handler_queue)
traverser.traverse(root)

def collide(entry):
    print(entry.from_node_path, entry.into_node_path)

if handler_queue.get_num_entries() > 0:
    collide(handler_queue.get_entries()[0])

Theoretically, if you skip this through additional logic, then you can write behavior similar to CollisionHandlerEvent.

Yeah I sort of realised this may be the case and also came to the same conclusion as yourself in this scenario, opting to use CollisionHandlerQueue(). I’m sure I can make it work, but thank you soo much for being of help and taking your time out to assist me!

1 Like