Dictionaries (.erase() and .values.max() ) behaving strangely

Godot Version

v4.6.stable.official [89cea1439]

Question

I’m working on a task queue in GDScript. It’s a Dictionary of String keys, the tasks, and int values, their priority.

A task than has been called is taken out of the Dictionary, and put in the current_task String variable, its priority being put in the current_priority int variable.

The _process() gets the max() value from the Dictionary, compares it to the current_priority.

If the max priority in the Dictionary is higher than the current_priority, it puts the current_task back in the Dictionary with the current_priority, then .erase() the max-priority task from the Dictionary and put it in current_task, with its priority in current_priority. The documentation for .erase() specifies

Note: Do not erase entries while iterating over the dictionary. You can iterate over the keys() array instead.

but I’m not iterating over the Dictionary. I’m erasing the key directly.

Scenario :

By default, there’s one task : idle, of priority 1.

When I left click, another task is added to the Dictionary : attack, of priority 50.

extends Node

# D_tasks is a Dictionary of keys : String specifying the task
# and values : int specifying the priority
var D_tasks : Dictionary[String,int]
# vars to get a current state
var current_task : String
var current_priority : int = 0

func _process(_delta: float) -> void:
	# first frame, the tasks dict is empty
	if D_tasks.is_empty() :
		#queue an idle
		D_tasks["idle"] = 1
	else:
		# get the first task of the highest priority
		print(D_tasks)
		var max_priority : int = D_tasks.values().max()
		var task : String = D_tasks.find_key(max_priority)
		# a priority 0 is a null task
		if max_priority == 0:
			#queue an idle
			D_tasks["idle"] = 1
		# call the task if its priority is greater than the current one
		elif max_priority > current_priority:
			print("%o superior to %o" % [max_priority,current_priority])
			# reenter the current task
			D_tasks[current_task] = current_priority
			# pop the task
			D_tasks.erase(task)
			# call the task
			print("calling %s %o" % [str(task),max_priority])
			current_task = task
			current_priority = max_priority
			print(task)
			
func _unhandled_input(event: InputEvent) -> void:
	if (event is InputEventMouseButton
		and event.pressed
		and event.button_index == MOUSE_BUTTON_LEFT
		):
		D_tasks["attack"] = 50

I expect it to print the Dictionary with two tasks, idle:1 and attack:50, then print “50 is superior to 1”, then “calling attack 50”, then “attack”.

It does not :

...
{ "": 0, "idle": 1 }
{ "": 0, "idle": 1, "attack": 50 }
62 superior to 1
calling attack 62
attack
{ "": 0, "idle": 1 }
...

My questions :

  1. there’s a ““ (null) key of 0 value in the Dictionary when I .erase() a key. Why is there a null:0 tuple in the Dictionary ? How do I erase a key:value properly ? I tried
...
for key in D_tasks.keys():
   if key = task:
      D_tasks.erase(key)

with the same result.

  1. It prints out the values of the Dictionary : 0, 1, 50. And tells me immediately after that the .values().max() is 62. Where does 62 come from ? There’s no other line of code.

62 is 50 in octal base

1 Like

Ok so I’m going to switch %o to %d.
Thank you for that.
Now somebody’s got something for the erase ?

I think I corrected this one also.
Case closed for now.

Don’t forget to add the answer so future people can fix it.
It is likely because the line before the erase adds an empty task:
D_tasks[current_task] = current_priority

1 Like

Don’t forget to add the answer so future people can fix it.
It is likely because the line before the erase adds an empty task:
D_tasks[current_task] = current_priority

It was.

The code here was a self-contained test taken from the actual code, and the line

D_tasks[current_task] = current_priority

wasn’t meant to stand on its own (you can see that there’s no attributing current_task in this example).

Still, this test had the same result as the actual code, so I didn’t spot the mistake.

In the actual code, the null:0 entry in the Dictionary was due to :

  • the Dictionary keys and current_task are of Callable type, which doesn’t accept null
  • so the current_task can’t actually be emptied
  • I resorted to set current_priority = 0 as the “no task” current state
  • but I then failed to add the current_priority == 0 check before switching the current_task for the max_priority task
  • as in this example, the first time it tried to switch to the max_priority task, it created a null:0 entry in the Dictionary.
  • the next times over, it would take the last finished task, which was still in current_task, and put it back into the Dictionary with the current_priority (which is 0 when a task is finished)

The solution is

if current_priority == 0:

#call max_priority task

elif max_priority > current_priority:

#reenter current_task and call max_priority task

As this solution is highly specific to my code, I didn’t feel it belonged in here, but I’m happy to share it.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.