Menu
CoddyTech

Custom Iterators

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

Python makes a class behave like a list with magic methods such as __len__, __getitem__, __contains__ and __iter__. Godot has virtual methods for only one of these jobs, the for loop. Three methods make for item in my_object: work:

  • _iter_init(iter) runs once, before the first round. It sets the starting position and returns true if there is a first element.
  • _iter_get(position) returns the element at the current position, and the loop variable gets that value.
  • _iter_next(iter) runs before each further round. It moves to the next position and returns false when there are no more elements, which ends the loop.
# team.gd
class_name Team
extends RefCounted

var _members: Array[String] = []

func add(member: String) -> void:
	_members.append(member)

func _iter_init(iter: Array) -> bool:
	iter[0] = 0
	return iter[0] < _members.size()

func _iter_next(iter: Array) -> bool:
	iter[0] += 1
	return iter[0] < _members.size()

func _iter_get(position):
	return _members[position]

iter is an Array with one slot that Godot creates for each loop. iter[0] holds that loop's position, and Godot passes its value to _iter_get(). A Team now works in a for loop:

var team := Team.new()
team.add("Ada")
team.add("Bo")
for member in team:
	print(member) # Ada, then Bo

Keep the position in iter[0], not in a member variable such as _position. Every loop gets its own iter, so two loops over the same team, even one inside the other, do not disturb each other. With a member variable, the inner loop would move the outer loop's position.

The other list jobs have no virtual method: len(team) and team[0] are errors. Write ordinary methods named like Array's, such as size(), get_at(index) and has(member). Do not test membership with in: "Ada" in team checks whether Team has a property or method named Ada, so it is false.

challenge icon

Challenge

Easy

Write the Bookshelf class in bookshelf.gd. It keeps the titles in _titles and holds at most _capacity books:

  • add(title) appends the title and returns true, or returns false without adding when the shelf is full or already has that title.
  • size() returns the number of books, and has(title) whether the title is on the shelf.
  • get_at(index) returns the title at that index, or an empty string when the index is outside the shelf.
  • _iter_init(), _iter_next() and _iter_get() let a for loop go through the titles in the order they were added.

The supplied code reads the capacity, the number of titles and one title per line, and reports the titles it could not add. It prints the number of books, lists them with a for loop over the shelf, then reads one more title and prints whether the shelf has it, and the last book. For capacity 3, the titles Dune, Emma, Dune, Ulysses, Hamlet and the search Emma it prints:

Could not add: Dune
Could not add: Hamlet
Books: 3
1. Dune
2. Emma
3. Ulysses
Has Emma: true
Last: Ulysses

Your code goes in bookshelf.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 shelf := Bookshelf.new(int(OS.read_string_from_stdin()))
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var title := OS.read_string_from_stdin()
		if not shelf.add(title):
			print("Could not add: " + title)
	print("Books: %d" % shelf.size())
	var number := 1
	for title in shelf:
		print("%d. %s" % [number, title])
		number += 1
	var query := OS.read_string_from_stdin()
	print("Has %s: %s" % [query, shelf.has(query)])
	print("Last: " + shelf.get_at(shelf.size() - 1))
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