Menu
CoddyTech

RefCounted vs Node

Part of the Object Oriented Programming section of Coddy's GDScript journey. Lesson 43 of 65.

Every object takes memory until it is freed. RefCounted and Node are freed in different ways.

A RefCounted object counts the variables, arrays and objects that refer to it. When the count drops to zero, Godot frees it on its own. NOTIFICATION_PREDELETE, from the Special Methods chapter, shows the moment:

# note.gd
class_name Note
extends RefCounted

func _notification(what: int) -> void:
	if what == NOTIFICATION_PREDELETE:
		print("Note freed")

# main.gd
func write() -> void:
	var note := Note.new()
	print("Note written")

func _ready():
	write()          # Note written, then Note freed
	print("Back in _ready")

note was the only reference, so the note is freed as soon as write() ends.

A Node is not counted. A node in the scene tree is freed with its parent, and the whole tree is freed when the game ends. A node that you take out of the tree, or never add to it, must be freed by you:

crate.free()         # freed now, with all its children
crate.queue_free()   # freed at the end of the current frame

After free(), the node is gone: is_instance_valid(crate) returns false, and calling a method on it is an error. queue_free() is the safe choice while other code may still use the node in this frame, but until that frame ends the node stays in the tree and still counts in get_child_count().

A node that is never freed is a memory leak: Godot reports "ObjectDB instances were leaked at exit" when the program ends. RefCounted objects leak too when two of them refer to each other, because neither count can reach zero. Store the link in one direction only, or store a name instead of the object.

Use RefCounted for data: items, scores, records. Use Node for things that live in the scene tree.

challenge icon

Challenge

Easy

A Wave node keeps its enemies as child nodes. enemy.gd is complete: Enemy.new("Goblin", 5) creates an enemy node named Goblin with 5 HP. Write the three methods of Wave:

  • spawn(enemy_name, hp) adds a new enemy as a child. If a child with that name exists, it prints Orc is already here and creates nothing.
  • hit(enemy_name, damage) lowers that enemy's hp and prints Orc has 8 HP. At 0 HP or less, it prints Orc defeated and removes the enemy for good, at once: the next alive() must not count it. A name that is not there prints No Orc here.
  • alive() returns how many enemies are left.

Every node must be freed: any leak at exit fails the test. The supplied code adds the wave to the scene tree, reads how many commands follow (spawn Goblin 5, hit Goblin 6, count) and prints the enemies left. For spawn Goblin 5, spawn Orc 12, hit Goblin 6, count, hit Goblin 1, spawn Orc 3, hit Orc 4 it prints:

Goblin defeated
Enemies: 1
No Goblin here
Orc is already here
Orc has 8 HP
Enemies left: 1

Your code goes in wave.gd and enemy.gd. main.gd holds the supplied input/output code and cannot be edited.

Try it yourself

extends Node

func _ready():
	# Supplied input/output code: keep it as it is
	var wave := Wave.new()
	add_child(wave)
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var parts := OS.read_string_from_stdin().split(" ")
		if parts[0] == "spawn":
			wave.spawn(parts[1], int(parts[2]))
		elif parts[0] == "hit":
			wave.hit(parts[1], int(parts[2]))
		else:
			print("Enemies: ", wave.alive())
	print("Enemies left: ", wave.alive())
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online GDScript compiler