Array element changes on its own (duplicate doesn't help)

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

I’ve got into a weird situation, I have no idea how this happens…
(initially i was trying to generate a curved road of variable width, but I’ve shortened the code as much as possible). I’ve created a class, the problem doesn’t happen when I use a simple int. The problem is in “for” loop in generate function:

extends Node

class Pathpt:
	var wid: int

var points = [] #array of class Pathpt
const leng = 4
var lastpt = Pathpt.new()

func _ready():
    #just initializing stuff, nothing much important here
	points.resize(leng)
	for i in range(leng):
		points[i] = Pathpt.new()
	lastpt.wid = rand_range(50, 150)
	generate(lastpt)

func generate(startpoint): #startpoint should be of class Pathpt
	points[0] = startpoint
	for i in range(1, leng):
		print(i, ", ", points[0].wid)
		points[i].wid = rand_range(50, 150)
		print(i, ", ", points[0].wid)
	lastpt = points[leng - 1]
	
func _GenBtn():
	print("generate")
	generate(lastpt)

Note that I print only the 0th element of array which should not be changed in the loop at all, so I expected to see the same numbers until I press “generate” button (connected to _GenBtnfunction). What I actually get is:

1, 112
1, 112
2, 112
2, 112
3, 112
3, 112
generate
1, 81
1, 81
2, 81
2, 81
3, 81
3, 125
generate
1, 125
1, 125
2, 125
2, 125
3, 125
3, 86

So except for the first time when function is called from _ready(), the 0th element changes on the last run of “for” loop. According to the way my full code works, the 0th element becomes equal to the last one as soon as the last is calculated.
When I remove the string lastpt = points[leng - 1] the problem disappears, but moving it to _GenBtn function does not help. Most surprisingly, neither does even replacing it with the following:

	var arrdup = points.duplicate()
	lastpt = arrdup[leng - 1]

I just don’t understand how this happens. Any solutions to this?

:bust_in_silhouette: Reply From: njamster

I print only the 0th element of array which should not be changed in the loop

Not true. In the first line of generate you make sure the 0th element in the array is lastpt, then in the last line of generate you set lastpt up as a reference to the last element of the array. Thus if you change the wid-value of the last element, you also change the wid-value of lastpt which is the 0th element.

However, I don’t really get what you expect to happen instead ?

Oh, I get it. I thought lastpt is not the 0th element, but a separate variable. Is this a difficulty of working with classes? How to just assign the value then? I want to store the last point parameters to begin generating new curve from the end of previous.
Also, maybe it’s good to use a dictionary instead?

GreyMiller | 2020-07-29 16:19

Is this a difficulty of working with classes?

It will be an issue with any complex data-type in GDScript!

It’s documented here: “In GDScript, only base types (int, float, string and the vector types) are passed by value to functions (value is copied). Everything else (instances, arrays, dictionaries, etc) is passed as reference.”


However, there’s a workaround:

  1. Create a custom class by extending a Node:

Pathpt.gd

extends Node

var wid: int
  1. Replace the class-definition in your script, i.e.:
var Pathpt = preload("res://Pathpt.gd")

instead of

class Pathpt:
    var wid: int
  1. Now you can use the Node’s duplicate-method to create a deep copy:
func generate(startpoint):
	points[0] = startpoint
	for i in range(1, leng):
		print(i, ", ", points[0].wid)
		points[i].wid = rand_range(50, 150)
		print(i, ", ", points[0].wid)
	lastpt = points[leng - 1].duplicate()

njamster | 2020-07-30 17:52

Thanks.
However now I can’t define a variable of this type, Pathpt.new()and : Pathpt result in an error.
Well I still don’t like this workaround, so I’ll try something else, but thank you for the explanation very much.
ps. lastpt.wid = points[leng - 1].wid worked pretty good

GreyMiller | 2020-07-31 06:22

Pathpt.new() and : Pathpt result in an error.

You can add a class-name to the custom-class to enable this:

extends Node
class_name Pathpt

var wid: int

That way you would not need to preload the script as well.

lastpt.wid = points[leng - 1].wid worked pretty good

Yes, because an integer is a base type, thus copied by value instead of reference. But in most cases a class includes a lot of different information and you probably don’t want to copy each of its properties manually.

njamster | 2020-08-01 17:36