Invalid get index 'x' Error threw randomly

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

Hello!

My system:
Windows 10 Pro 1909 x64
Nvidia GTX 1050 Ti
Godot 3.2 stable mono

About the error:
I’m making a script to control bullets and I’m having a problem to delete the array element of the bullet when the bullet is destroyed. Sometimes it works without any problem but times to times it throws an error saying

"Invalid get index 'x' (on base: 'Array')

The x is not always the same value. Sometimes the error appears in one second and sometimes it appears in several seconds (after hundreds of bullets were created).

Here is the code:

var _bullet_ref := []

func shoot() -> void:
	var bullet_instance = bullet_scene.instance()
	add_child(bullet_instance)
	_init_bullet(bullet_instance, p)


func _init_bullet(p_bullet_instance, p_player_instance) -> void:
	var new_rot = p_player_instance.bullet_socket.global_transform.basis.get_euler()
	p_bullet_instance.rotation = new_rot
	p_bullet_instance.set_global_3d_position(
			p_player_instance.bullet_socket.get_global_3d_position()
			)
	p_bullet_instance.start_position = p_bullet_instance.get_global_3d_position()
	p_bullet_instance.can_process = true
	var b_ref = weakref(p_bullet_instance)
	_bullet_ref.append(weakref(p_bullet_instance))


func _physics_process(p_delta: float) -> void:
	var idx_to_remove := []
	for i in range(0, _bullet_ref.size()):
		var bullet = _bullet_ref[i].get_ref()
		if bullet:
			if not bullet.can_process:
				continue
			var forward = bullet.get_forward_vector()			
			var new_pos = forward.normalized() * bullet.speed * p_delta			
			bullet.global_translate(new_pos)
			
			if bullet.get_global_3d_position().distance_to(bullet.start_position) > bullet.max_distance:
				idx_to_remove.append(i)

	for idx in idx_to_remove:
		var b = _bullet_ref[idx].get_ref()
		_bullet_ref.remove(idx)
		if b:
			b.queue_free()

dont know if this is the cause, which array creates the error? Is it _bullet_ref?
If it is, likely the issue come from _bullet_ref.remove(idx) changing the size of the array. When the array changes in size, the index in the idx_to_remove do not represent the bullet you wanted to remove anymore (and if the index is high, and you remove enough indexes in the same frame, some value inside idx_to_remove might be higher than _bullet_ref.size(), which expalins the invalid get index)

I suggest you to work with the values of the array instead of the indexes,
idx_to_remove[]=>bullet_to_remove[], the last part of you code became:

if bullet.get_global_3d_position().distance_to(bullet.start_position) > bullet.max_distance:
                    bullet_to_remove.append(bullet)


for b in bullet_to_remove:
    _bullet_ref.erase(b)
     b.queue_free()

Andrea | 2020-05-27 12:16

by the way, instead of constructing your own array with the reference of bullet, you could accomplish a similar results in a simpler way by placing all the bullet nodes inside a parent_bullet node and cycle through them directly with parent_bullet.get_children(), or even by adding the bullets to a bullet group with bullet.add_to_group("bullets"), and ciclying through them by get_tree().get_nodes_in_group("bullets")
After that you can queue_free() them without having to update any array with the reference

Andrea | 2020-05-27 12:27

Thank you! It worked.
What solution you think is better, performance-wise? I thought array was the fastest option.

equus | 2020-05-27 18:24

cant tell which solution is more efficient among the one discussed, but you can do some analisys using OS.get_thick_msec(), or looking at the profiler.
I usually use the parent_node method though, because it generates a cleaner project tree.
However i dont think any solution is particularly more efficient than the others, because the heavier part of the code is the calculation of the distance for each bullet, and this is independent from the way you want to cycle through them.
If you need to increase performance for this specific bullet-delete function, i would suggest to re-think the condition the bullets are deleted, like adding a timer to the bullet node that kill them (you have to check if it is really better), or performing the queue_free action dilated in time (like no more than 10 bullets in a frame)

Andrea | 2020-05-27 18:53

Thank you for the information!

equus | 2020-05-27 18:58