+3 votes

The setget functions are defined in the documentation as

Whenever the value of variable is modified by an external source (i.e. not from local usage in the class), the setter function (setterfunc above) will be called. This happens before the value is changed. The setter must decide what to do with the new value. Vice-versa, when variable is accessed, the getter function (getterfunc above) must return the desired value.

What's the official reason for only external access to the variable going through the setter/getter? I'm guessing it's for performance reasons, but I'd like an option to override this so that ALL access to the variable goes through them.

One reason is that the setter is a great place to emit a signal (that the variable has changed) so that other interested (connected) code can know about it. Obviously you'd only do this for select variables.

in Engine by (722 points)
edited by

1 Answer

+2 votes
Best answer

I assume the part you are wondering about is why local access to variables does not use the setter / getter?
I don't think it's for performance reasons, I think it's to avoid ending up in a loop. Let's say you have stored a certain value in a variable, the code in other classes relies upon that way of storing and now you want to encode the data for internal reasons. What you need is a function that translates from the public use of your variable to your internal way of storing it. That setter needs to be called automatically when the variable is accessed (so external code doesn't break), but the setter function itself needs to access the variable directly, otherwise it would only call itself.
If the setter was called in every case it would merely be a callback but it couldn't really work as a setter.
You'll have to make sure to call the setter from within your class by yourself.

Technically it would be possible to change it so that only the setter or getter methods have access to the variable itself, but as this pattern is usually used to separate the internal and external meaning of a variable it would be confusing and different from other languages.

by (1,120 points)
selected by

ah yeah the question wasn't very clear, I've modified it a little to make it clearer. hmm I'm not really buying those reasons, but then maybe I'm not seeing your reasoning. I'm not sure in what way it would be different from other languages, at least, coming from C++/Java. I know I could call the setter directly but this bypasses the benefit of the setget concept. If enforced in local code, It'd be like a way of making code use the setter rather than in, say C++ where it can be ignored.

There are workarounds, like putting the variable in a singleton class with setter/getter, and then all access would be from external classes and thus via setter/getter, but ironically this is promoting a worse pattern.

Hm - to be honest I fail to see how this is even comparable to other languages.
Let's say you have a class that stores a size in meters in a public variable. Now you want to change it so it stores the value in yards, but as other programmers have already written code that uses your class you don't want the interface to change, so external code should still see the variable as if it contained the value in meters.
In GDScript or Haxe you can modify the variable declaration so that external access to the variable is rerouted to a setter or getter method instead. That way code relying on your class doesn't have to be changed.
That's impossible in C++ or Java, as the syntax for accessing a public variable or accessing a public method are different. In C# it is somewhat possible, but unlike in the first two languages you will have to rename the variable and replace it with a property. Also most C# naming conventions expect you to start property names with an upper case letter and member variables with a lower case letter.

If you want to make sure that every access to a variable uses a setter or getter just wrap it in a subclass so the internal access becomes external access. There's no need to make that variable a singleton as well.

I'd like the setter/getter accessors to be a special case that access the value directly, and other code (same class and external classes) access via the accessors. I'm sure that would be possible. I don't like the thought of wrapping access in yet another class, especially in GDScript where classes are a bit clunky compared to C++ etc. However, having said that, I guess vars with set/get in GDScript are 'special case' variables and it wouldn't be such a bad thing for that to be apparent in the code..

I understand what would like to have, but that's not how setters / getters behave in any language I know. Besides you can use the existing system to easily modify it so that it works like you want (using subclasses), if it were the way you described it would require more of a workaround to get the current behavior.

Do you really think that classes are clunky compared to other languages? I mean these 6 lines are sufficient to solve your problem:

class ObservableVariable:
    signal valueChanged
    var value setget set_value
    func set_value(newVal):
        value = newVal
        emit_signal("valueChanged", newVal)

You will have to access the variable using "myVar.value = ..." instead of "myVar = ...", but that's no more clunky than nullable types in C#, weakrefs in Godot etc.

Here is a short example in case it's not clear how to use this:

extends Node

var myVar = ObservableVariable.new()

func _ready():
    myVar.connect("valueChanged", self, "test")
    myVar.value = 5;

func test(val):
    print("value changed to " + str(val))

class ObservableVariable:
    signal valueChanged
    var value setget set_value
    func set_value(newVal):
        value = newVal
        emit_signal("valueChanged", newVal)

But my bet is that if you use this pattern you will find that there's a good reason why the setget-feature is not built like this. Sooner or later you will have situations where you don't want to trigger callbacks when the variable is changed, and that's where it's likely easier to just call the setter internally, so you still have the option to access the variable directly and bypass the callback.

I guess I'm just used to C++, and I haven't used Godot classes enough. I know there are ways of doing this and I'll be fine using one, it's just that this has caught me out a few times. Saying they are clunky wasn't supposed to be an insult. I guess if there's even one instance where you don't want a setter to be invoked, then you'd be out of luck if it was how I described it.

Don't worry, I didn't take it as an insult (and as GDScript is not "my" language I wouldn't have a reason to feel insulted personally). I am also mostly a C++ programmer, but I think subclasses in GDScript are rather lightweight (as is the whole syntax).

But I think I know what you mean. If you don't use subclasses and put the new class in a new file then calling load() or preload() on the filename to get a variable that contains the "class" feels really weird for C++ developers. But then it's how many scripting languages like Lua handle it. The reason is that in these languages you can't declare anything, you always write procedures. So while in C++ a class declaration or definition merely describes a class and provides code that will be called eventually, in Lua (or Javascript I think) you write a procedure that creates a class factory in a variable. That's why the "class" can be stored in a variable, because it is a runtime object.

Yeah, it's a really good language, I need to practise more with classes and stuff in GDscript.

thanks to the tip!

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.