Game crashes when using WorkerThreadPool

Godot Version

4.4

Question

I want to optimize pathfinding in my game so I have created an autoload to process this using the code below. The enemies call the register_enemy_function when they enter the chase state.

For some reason, the game crashes when I use the WorkerThreadPool but it works just fine when I use the for loop. Why does this happen? I’ve tried only using the WorkerThreadPool when there are 20 or more enemies, but that doesnt change anything.

extends Node



var enemies := [] 
var enemies_to_deregister := []


func register_enemy(enemy: Enemy) -> void:
	enemies.append(enemy)
	print("Registered new enemy, now handling " + str(enemies.size()) + " enemies.")


func deregister_enemy(enemy: Enemy) -> void:
	enemies_to_deregister.append(enemy)


func process_enemy_ai(enemy_index: int) -> void:
	var enemy := enemies[enemy_index] as Enemy
	enemy.navigation_agent.target_position = MainInstances.player.global_position
	enemy.target_direction = enemy.global_position.direction_to(enemy.navigation_agent.get_next_path_position())


func _process(delta: float) -> void:
	if enemies.is_empty():
		return
	
	#works fine
	#for i in enemies.size():
		#process_enemy_ai(i)
	#
	#return
	
	# crashes the game
	var task_id := WorkerThreadPool.add_group_task(process_enemy_ai, enemies.size())
	
	WorkerThreadPool.wait_for_group_task_completion(task_id)
	
	for enemy: Enemy in enemies_to_deregister:
		enemies.erase(enemy)
	
	enemies_to_deregister.clear()
	

I believe if you change the length of lists/dicts you need to mutex them.

Oh okay, what would that look like? I haven’t used mutexes before.

It’s not as simple as this, right? Still crashing…

extends Node

var mutex: Mutex

var enemies := [] 
var enemies_to_deregister := []


func _ready() -> void:
	mutex = Mutex.new()


func register_enemy(enemy: Enemy) -> void:
	mutex.lock()
	enemies.append(enemy)
	mutex.unlock()


func deregister_enemy(enemy: Enemy) -> void:
	enemies_to_deregister.append(enemy)


func process_enemy_ai(enemy_index: int) -> void:
	var enemy := enemies[enemy_index] as Enemy
	enemy.navigation_agent.target_position = MainInstances.player.global_position
	var target_direction := enemy.global_position.direction_to(enemy.navigation_agent.get_next_path_position())


func _process(delta: float) -> void:
	if enemies.is_empty():
		return
	
	var task_id := WorkerThreadPool.add_group_task(process_enemy_ai, enemies.size())
	
	WorkerThreadPool.wait_for_group_task_completion(task_id)
	
	mutex.lock()
	for enemy: Enemy in enemies_to_deregister:
		enemies.erase(enemy)
	mutex.unlock()
	
	enemies_to_deregister.clear()

You might also have to mutex enemies_to_deregister, but otherwise that looks right. It could be something else is the problem.

What does the crash look like?

It was a problem where the order of the code.
Now it works fine. Thank you!

extends Node

var mutex: Mutex

var enemies := [] 
var enemies_to_deregister := []

func _ready() -> void:
	mutex = Mutex.new()


func register_enemy(enemy: Enemy) -> void:
	mutex.lock()
	enemies.append(enemy)
	mutex.unlock()


func deregister_enemy(enemy: Enemy) -> void:
	enemies_to_deregister.append(enemy)


func process_enemy_ai(enemy_index: int) -> void:
	var enemy := enemies[enemy_index] as Enemy
	
	if enemy == null:
		return
	
	enemy.navigation_agent.target_position = MainInstances.player.global_position
	var target_direction := enemy.global_position.direction_to(enemy.navigation_agent.get_next_path_position())
	enemy.target_direction = target_direction


func _physics_process(delta: float) -> void:
	if enemies.is_empty():
		return
	
	if not enemies_to_deregister.is_empty():
		mutex.lock()
		for enemy: Enemy in enemies_to_deregister:
			enemies.erase(enemy)
		mutex.unlock()
		enemies_to_deregister.clear()
	
	var task_id := WorkerThreadPool.add_group_task(process_enemy_ai.call_deferred, enemies.size())
	
	WorkerThreadPool.wait_for_group_task_completion(task_id)

1 Like