Open mattismoel opened 2 months ago
Emitting the changed
signal under the hood for every change can lead to big performance concerns (even if the signal is not connected to anything), and seemingly unpredictable, "quirky" behavior.
This is mostly because what would happen would be different than the written code. As an existing example, var my_node = $MyNode
does not automatically assign the node on _ready
. Instead, you need to use the @onready
annotation, which keeps the intent more obvious without "compiler magic".
This may not be the best way to do it but maybe if there was a Resource-specific @emit_changed
annotation that could be applied to exported variables like this:
# some_custom_resource.gd
class_name SomeCustomResource
extends Resource
@emit_changed @export var var1: Type
@emit_changed @export var var2: Type
@emit_changed @export var var3: Type
As a workaround, I think
func _set(property: StringName, value: Variant) -> bool:
if property in ["var1", "var2", "var3"]:
emit_changed()
return false
should work
See also:
In a nutshell, something similar to @observable
with a custom signal name would also solve this proposal, because changed
could then be passed directly and be emitted accordingly.
@emit_changed @export var var1: Type
I think annotations should work with all classes starting from Object
.
Edit: "@onready" can only be used in classes that inherit "Node"
So maybe this will be possible but it may be better to use only one annotation for both of them, @notify_changed
as a separation for the below exported variables.
This may be useful to avoid creating setters for all the variables that calls one method, and we replace the method with a connection to changed.
Something like this can be written in just 7 lines.
@export var start_position := Vector2(4, 4):
set(value):
if value != start_position:
start_position = value
emit_changed()
@export var end_position := Vector2(32, 32):
set(value):
if value != end_position:
end_position = value
emit_changed()
@export_range(0.0, 1.0, 0.01) var opacity := 1.0:
set(value):
if value != opacity:
opacity = value
emit_changed()
@export var stroke_color := Color(1.0, 1.0, 1.0, 1.0):
set(value):
if value != stroke_color:
stroke_color = value
emit_changed()
@export var stroke_width := 4.0:
set(value):
if value != stroke_width:
stroke_width = value
emit_changed()
@export var stroke_linecap := LineCap.BUTT:
set(value):
if value != stroke_linecap:
stroke_linecap = value
emit_changed()
@export var stroke_dash_array := PackedFloat32Array([0]):
set(value):
stroke_dash_array = value
emit_changed()
# This doesn's call `changed`.
@export var my_int: int
# This calls `changed`.
@notify_changed
@export var start_position := Vector2(4, 4)
@export var end_position := Vector2(32, 32)
@export_range(0.0, 1.0, 0.01) var opacity := 1.0
@export var stroke_color := Color(1.0, 1.0, 1.0, 1.0)
@export var stroke_width := 4.0
@export var stroke_linecap := LineCap.BUTT
@export var stroke_dash_array := PackedFloat32Array([0])
@WhalesState emitting a broad "something in object X changed" from an object instead of a more precise "Y property in object X changed" makes very little sense to me. Typically different listeners will care about different subsets of properties (eg. object X is a player character, then the HP UI would care about listening to properties related to health) and it would be better practice to connect listeners only to the specific properties they should care about.
Typically in observable patterns, there's a way to group multiple observables together to effectively do what you're suggesting, but I don't think that setting up a broad "something changed in this object" should be the default/out of the box way that it works.
Describe the project you are working on
A driving game, where a script is responsible for creating a RoadLayout (Custom Resource) in the inspector. Other nodes should be able to listen to changes to any property on the layout.
This is very much related to #10263, though implementation details differ.
Describe the problem or limitation you are having in your project
Currently, one would have to create setters for each exported Resource variable, that emits the signal itself. This is very cumbersome, if many variables are exported, and need to emit this signal.
Describe the feature / enhancement and how it helps to overcome the problem or limitation
Emit the Resource.changed signal for any Resource - also custom Resources.
Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
From a custom Resource:
The resource is exported from some script
When any property on the exported
SomeCustomResource
is changed in the editor or through code, thechanged
signal is emitted.If this enhancement will not be used often, can it be worked around with a few lines of script?
This can be worked around by doing setters for each exported member:
Is there a reason why this should be core and not an add-on in the asset library?
Improves workflow tremendously, and is part of GDScript itself. It also avoids boilerplate and code repetition.