1
\$\begingroup\$

Godot has several built-in types:

Built-in types are stack-allocated. They are passed as values. This means a copy is created on each assignment or when passing them as arguments to functions. The exceptions are Object, Array, Dictionary, and packed arrays (such as PackedByteArray), which are passed by reference so they are shared.

Further, there is a subclass of Object (RefCounted) that frees the developer from manually having to free any subclass of RefCounted. Three of the built-in types (Array, Dictionary and packed arrays) appear to be in a 'weird' middle ground as such, I would like to check my understanding (I will use Arrays for brevity, but I believe the statements apply to all three):

  1. The documentation is incorrect: when calling a func with an Array argument "the reference is passed by value"; it is not a true "pass by reference" as instead stated since the variable in the outer scope cannot be modified (only the elements in the array can be modified).

  2. The Array objects themselves are reference counted — i.e. there is no need to free them, in fact, there is no free() method to do so. (Note: Arrays are not part of the RefCounted class hierarchy, this reference counting appears to be an internal of the Engine.)

  3. Arrays correctly manage the reference counting for elements within them — i.e. they take a reference to any RefCounted elements and release any such references when the Array is released.

  4. It is possible to leak memory with Arrays: if two arrays are allocated and each contains the other as an element, the internal reference counts for each will be incremented, hence the engine will not free either of them - even if they are otherwise out of scope.

  5. There is no concept of a WeakRef for Arrays: it is not possible to use the built-in weakref() method since that only works with Objects and you will get an error if you try to use weakref() with non-Objects.

Are any of these 5 statements incorrect?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Statement #1

When calling a func with an Array argument, the reference is passed by value rather than reference since the variable in the outer scope cannot be modified.

This is incorrect. All Arrays, Dictionaries and PackedArrays are indeed passed by reference and can thus be modified in a different context within which they are defined. That's why they also have a duplicate() method. You can try it yourself:

extends Node

func _ready() -> void:
    var local_array: Array = [1, 2, 3]
    print("Local before: ", local_array)
    test_1(local_array)
    print("Local after: ", local_array)

func test_1(array: Array) -> void:
    print("Arg before: ", array)
    array.pop_back()
    print("Arg after: ", array)

The above script will generate the following console output:

Local before: [1, 2, 3]
Arg before: [1, 2, 3]
Arg after: [1, 2]
Local after: [1, 2]

Statement #2

The Array objects themselves are reference counted.

No, they are not reference counted. Arrays are a container built-in type, not a RefCounted-derived class — you can't get any reference count about them as you would with RefCounted instances.

This reference counting appears to be an internal of the Engine.

You didn't provide any source supporting the above statement. To the best of my knowledge, the official documentation never mentions such a thing, which means that either (1) it's not true or (2) it doesn't affect the regular reference counting.

There is no need to free them.

Arrays don't exist on their own, they are always defined as members of a class or an object, which you can free() or queue_free() and thus deallocate both them and their instance members. This also addresses your previous statement about internal referencing: you release Arrays and Dictionaries by simply free()ing the instance or class owning them.

Statement #3

Arrays correctly manage the reference counting for an element within them.

No, they don't. RefCounted objects keep an internal reference counter; Arrays are just containers for objects and data.

They (...) release any such references when the Array is released.

Nope. When an object is being freed and has any references to RefCounted objects, the Engine will notify such objects to update their reference counter, not the Array.

Statement #4

If two arrays are allocated and each contains the other as an element, the internal reference counts for each will be incremented.

No, it won't because, as per Q#2, Arrays are not reference counted specifically. However...

It is possible to leak memory with Arrays.

This is possible if you use Arrays to hold mutual references to two Objects (or Nodes). RefCounted instances caught in a cyclic reference will not be freed automatically.

Statement #5

There is no concept of a WeakRef for Arrays.

Correct. WeakRefs hold `Objects, and in Godot Arrays are not Objects.

It is not possible to use the built-in weakref() method since that only works with Objects and you will get an error if you try to use weakref() with non-Objects.

Correct. weakref() returns null if its argument is neither Object-derived nor null.

\$\endgroup\$
3
  • \$\begingroup\$ For points 2-4 - If Godot is not reference counting how does it know when to free the memory (note: In the question I called out it's not related to the RefCounted class) but I don't know what Godot is doing if its not reference counting (the technique) to manage the memory? \$\endgroup\$
    – DavidT
    Commented May 4 at 14:31
  • 1
    \$\begingroup\$ It doesn't: non-reference counted, Engine-related things are either persistent (such as Servers) or handled internally. If you create non-reference counted entities, it's your responsibility to free the memory allocated (e.g. Nodes, Objects). \$\endgroup\$
    – liggiorgio
    Commented May 4 at 15:05
  • 1
    \$\begingroup\$ docs on basic mem management Godot implements reference counting to free certain instances that are no longer used, instead of a garbage collector, or requiring purely manual management. RefCounted things are freed automatically when not in use. Anything else will remain in memory until it is deleted with free() or queue_free(). \$\endgroup\$
    – Pikalek
    Commented May 4 at 15:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .