How does Physics2DDirectSpaceState handle queries?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By ichster

TL;DR: How can I query Physics2DDirectSpaceState from a thread a whole bunch of times.

When I first load a new level (as a scene) as part of the initialization of that level I want to make many, many queries to Physics2DDirectSpaceState. To handle doing this I make a thread that belongs to the parent of the level which runs initialization functions. This is the script of the thread that is created and owned by the parent:

var queue = []
var worker_semaphore
var worker_mutex
var worker_thread
var run_worker_thread

func stop():
	worker_semaphore.post()
	worker_mutex.lock()
	run_worker_thread = false
	worker_mutex.unlock()
	worker_thread.wait_to_finish()

func post_function(node, f_name, args):
	worker_mutex.lock()
	queue.push_back({
		'n' : node,
		'f' : f_name,
		'a' : args
	})
	worker_mutex.unlock()
	worker_semaphore.post()
	
func _worker_thread_process(u):
	while run_worker_thread:
		worker_semaphore.wait()
		
		worker_mutex.lock()
		
		if queue.size() > 0:
			var current_function_set = queue[0]
			
			var node = current_function_set['n']
			var thread_func = current_function_set['f']
			var thread_args = current_function_set['a']
			worker_mutex.unlock()
			
			# call given function
			# is this thread safe?
			node.callv(thread_func, thread_args)
			
			worker_mutex.lock()
			queue.erase(current_function_set)
			worker_mutex.unlock()
			node.emit_signal("init_finished")
			
		worker_mutex.unlock()

func start():
	# start up worker thread
	worker_semaphore = Semaphore.new()
	worker_mutex = Mutex.new()
	worker_thread = Thread.new()
	run_worker_thread = true
	var err = worker_thread.start(self, "_worker_thread_process", null, Thread.PRIORITY_NORMAL)
	if err != OK:
		print("ERROR WITH STARTING WORKER THREAD")

Then levels that are being initialized post the functions that they want initialized and then wait until the thread finishes with that function and emit the init_finished signal which tells the parent that the new level is done initializing and is ready to be used:

var space_state
signal init_finished

func _ready():
    # get direct space state outside of thread
    space_state = get_world_2d().direct_space_state
    # tell parent to give this level's function to its thread
    get_parent().post_function(self, "_init", [])

# simplified init function
func _init():
    # prepare physics query
    var shape_query = Physics2DShapeQueryParameters.new()
    shape_query.set_collide_with_areas(true)
    shape_query.set_collide_with_bodies(true)
    shape_query.set_collision_layer(524288)
    var shape = CircleShape2D.new()
    shape.set_radius(8)
    shape_query.set_shape(shape)
    # do a whole bunch of physics queries (anywhere from 20,000 to 200,000)
    for idx in range(20000):
        var result = space_state.intersect_shape(shape_query, 1)
        if result:
            # do something
            pass

The issue is that my game crashes if I run too many queries at once with the message: ERROR: CowData<class RID>::get: FATAL: Index p_index=0 out of size (size()=0). My guess is:
The physics thread is simply overwhelmed by my queries during a single frame and blows up.
In this case should I wait for another frame to finish making my queries? Or maybe sink the user thread to the main thread every once in a while?

I would like to know if the Physics2DDirectSpaceState tries to handle any queries its given during one frame, or if it handles queries some other way.

If you think that I am crashing for another reason or one of my assumptions is wrong, I would consider responses to those issues very greatly as well.