The custom iterator example here leaves a lot out. My expectation, which might have been wrong, is that if I do
var arr = [1, 4, 7]
for x in arr:
for y in arr:
print(x, y)
that I should get the same behavior from
var iter = ForwardIterator.new(1, 10, 3)
for x in iter:
for y in iter:
print(x, y)
It doesn’t work that way, but it can be made to. The example ignores the “arg” parameter, and understanding this parameter is necessary if you want to make custom iterators that are reentrant or that encapsulate other iterators, such as a concat iterator, a zip iterator, or a filter or map iterator.
When an iterator is used in a for
loop, the _iter_init
function is passed an array with one null element in it. arg[0]
is where you should store your iterator state, so that if you are called in a nested for
loop or other situation, your code will be reentrant.
Your _iter_next
is given the same arg
value, and you’ll find that arg[0]
contains whatever you put into it in _iter_init
. In _iter_next
you should update arg[0]
with the new state. Don’t try to resize this array. It has to be the same single-element array that was given to you.
The _iter_get
works a little differently. The value of arg[0]
is passed to it, not the whole arg array.
So a “corrected” ForwardIterator class would look like this:
class ForwardIteratorCorrected:
var start
var end
var increment
func _init(start, stop, increment):
self.start = start
self.end = stop
self.increment = increment
func should_continue(arg):
return (arg[0] < end)
func _iter_init(arg):
arg[0] = start
return should_continue(arg)
func _iter_next(arg):
arg[0] += increment
return should_continue(arg)
func _iter_get(current):
return current
And this will behave the same as the array example when used in nested for
loops.
If your iterator encapsules another iterator, you’ll want to allocate your own [null]
array for each of the contained iterators that you’re using, and you’ll need to implementation the same value vs reference behavior when you call the nested _iter_get
. Here’s an example that wraps an array so that we can call the internal iterator functions, and another that combines two iterators into pairs of elements from each iterator, like a zipper:
class ArrayIterator:
var array
func _init(array):
self.array = array
func _iter_init(args):
# Store the next array index in the state slot
args[0] = 0
return array.size()
func _iter_next(args):
# Increment the array index
args[0] += 1
return args[0] < array.size()
func _iter_get(index):
# Get the indexed element
return array[index]
class ZipIterator:
var first
var second
func _init(first, second):
self.first = first
self.second = second
func _iter_init(args):
# Initialize state for each sub-iterator
args[0] = [[null], [null]]
return first._iter_init(args[0][0]) and second._iter_init(args[0][1])
func _iter_next(args):
return first._iter_next(args[0][0]) and second._iter_next(args[0][1])
func _iter_get(states):
# Note that "states" is a pair of sub-args, not the array that contains the pair
return [first._iter_get(states[0][0]), second._iter_get(states[1][0])]
func _ready() -> void:
var a = ArrayIterator.new(range(3))
var b = ArrayIterator.new(["red", "green", "blue", "skipped"])
for pair in ZipIterator.new(a, b):
print(pair)