Using thread groups on NPCs causes lots of raycast-related errors

Godot Version

4.2.2.stable

Question

Hello there, I’m using Godot to simulate hundreds of AI-driven agents (NPCs) interacting with each other in a simple top-down 2D environment.
At each simulation step, all agents need to feed their current perception into an individual neural network which will decide the agent’s next action. This part is the performance bottleneck of the whole system as all agents are processed sequentially.

To improve performance, I want to parallelise the agents such that they can do these update steps at the same time rather than after each other.
(I have already added some logic to the script running the environment that makes sure no agent moves onto the next step before the others are done)

I wanted to use the editor’s thread group feature to accomplish this by setting the agent scene’s thread group to Sub Thread but doing so results in hundreds of errors per second, all of which are one of the following two:

  1. _update_raycast_state: Parameter "dss" is null.
  2. space_get_direct_state: Condition "main_thread != Thread::get_caller_id()" is true. Returning: nullptr

According to the documentation on thread groups;
“During processing in a sub-thread, accessing most functions in nodes outside the thread group is forbidden (and it will result in an error in debug mode).”
… so I assume the problem is that each agent’s raycasts are interacting with other objects that are not in the same thread and that’s not allowed.
I tried to fix this by wrapping any raycast related calls into call_thread_safe but the errors remain.
Ex: raycast.get_collider()raycast.call_thread_safe("get_collider")

I can see my CPU usage signficantly increase when using thread groups so it seems to work somewhat (the simulation does not crash but is slowed heavily by the constant errors) so I feel like I must be missing one last thing to get this to work properly.

Does anyone here have experience with threaded agents/NPCs interacting other parts of the world that aren’t part of the same thread?

Can you collect their current perception on the main thread and process that information in the thread group? What is their current perception made up of?

Unfortunately I have set up my agents with external C++ code that calculates their updates and consumes most of the performance.

I tried using sephamores around just the update step instead of putting the whole agent node into its own thread group and while that removed all the strange errors I had before, it also didn’t increase CPU usage as I hoped.

Maybe I need to move any optimisation attempts fully into the C++ part of the code, as annoying as it’ll be :confused: