Is it possible to create a generator in GDScript 2.0 like in Python?

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

Hi,

I have a function that calculates the positions of all the tiles and stores them into a list, that is returned. However, I would like to use the tiles positions as long as they are generated, without the need of a big array.

Take this simple Python code:

def my_generator(n):

    # initialize counter
    value = 0

    # loop until counter is less than n
    while value < n:

        # produce the current value of the counter
        yield value

        # increment the counter
        value += 1

# iterate over the generator object produced by my_generator
for value in my_generator(3):

    # print each value produced by generator
    print(value)

Extrated from here.

As you can see, I can access to the values as they are calculated. I was wondering if there is something similar in GDScript 2.0. I found the await keyword, but I can’t access the variable of interest in the process, only the final value.

:bust_in_silhouette: Reply From: spaceyjase

Not quite as fancy (essential missing the syntax sugar for yield), gdscript does support custom iterators:

Custom iterators where already mentioned but I think this thread would benefit from an example.

Define your iterator like this

class CustomIterator:
	var m_current:int
	var m_end:int
	func _init(p_end:int)->void:
		m_end = p_end
	func continue_condition():
		return m_current < m_end
	func _iter_init(arg):
		m_current = 0
		return continue_condition()
	func _iter_next(arg):
		m_current += 1
		return continue_condition()
	func _iter_get(arg):
		return m_current

and use it like this

for i in CustomIterator.new(3):
	print(i)

At least from the call site perspective, writing CustomIterator.new(3) is almost the same as writing custom_iterator(3)

I know this post is old, but in case someone comes across this, GDScript’s coroutines are a stronger feature than C# generators and thus it’s possible to somewhat emulate generators using them.
E.g. you can create an abstract Generator class somewhat like this:

class Generator:

#	func _impl(some_args...): IMPLEMENTED IN THE DERIVED CLASS

	func _iter_init(arg): 
		_did_yield = false
		self.callv("_impl", self._args) 
		_args = null
		return _did_yield # if invocation of _impl() didn't finish by calling _yield(), assume it finished by returning

	func _iter_next(arg): 
		if not _did_yield: return false  # fail if we already had finished last time
		_did_yield = false
		_on_next_signal.emit() # _impl() is awaiting this signal - this way we resume it
		return _did_yield

	func _iter_get(arg): return _current_value
		
	func _init(...args)->void:
		self._args = args
		
	var _args;
	var _did_yield : bool = true;
	var _current_value;
		
	signal _on_next_signal();
	
	func _yield(value)->Signal:
		_current_value = value
		_did_yield = true
		return _on_next_signal; # return the signal that we want to await

and then write a generator straightforwardly by deriving from it, like this:

class SimpleRange
	extends Generator
	
	func _impl(start: int, end :int):
		for i in range(start, end): await _yield(i)

Use it like you’d expect

for i in SimpleRange.new(1, 5): print(i) # prints "1234"

It’s a bit hacky and slow, but it works and I’ve had a few occasions where I was glad to be able to use this.

1 Like