Open Xrayez opened 2 years ago
Something similar was discussed on the chat: https://chat.godotengine.org/channel/core?msg=vLiEN4BEM9M8TziDw (you need to scroll down a bit, because RocketChat message links suck)
Nice, I didn't expect it, I'm going to link Akien's message here then: https://github.com/godotengine/godot/pull/53767#issuecomment-942285516.
Regarding whether this should be implemented on a signal level or method, I think it's just a matter of front-end implementation, so both can be supported.
If you're not sure whether it's possible to implement, you can look at implementation at Goost, could likely be optimized if needed, ideally should be implemented in MessageQueue
(I've had to copy-paste some implementation from there):
As I said, indeed it may not be as efficient as using a boolean flag, but I don't see a strong reason for not implementing this feature either, moreover it would be likely much more easier to implement using Callable
in 4.x.
MessageQueue
singleton itself could be exposed for advanced use cases and/or convenience (removes the need to add a dedicated singleton just for this):
func _ready():
resource.connect("changed", MessageQueue, "call_deferred_unique", ["_update"])
func _update():
# Heavy processing goes here...
If you think that this will lead to confusion and misuse, the Godot's API already has potential problem in this regard (I don't see this as a problem personally as long as features get properly documented), Like, we do have find_node()
vs get_node()
(see already closed proposal #1303). Similarly, we could have both call_deferred_unique()
and call_deferred()
: the first will always be slower, but it doesn't mean it's not useful. 🙂
The engine could keep using boolean flags internally for everything. Similarly, Node.find_node()
is only used once or twice in 3.x in core, but it's still there because it's widely useful for game projects.
Regardless of whether core developers decide to implement it or not, I invite people to use Godot + Goost builds if you feel like it's a pattern that you use often, along with other useful methods such as invoke()
: goostengine/goost#52.
I support this. Keywords: debounce, debouncing even though those aren't exactly the right words, that's the first thing I searched.
func _print(text): print(text)
func _ready():
_print.bind("a").call_deferred_unique()
_print.bind("b").call_deferred_unique()
_print.bind("a").call_deferred_unique()
What should this print? Either ab
or ba
is acceptable, but should be deterministic.
And some workarounds (you're welcome):
extends Node
signal hello(i)
func _ready() -> void:
if "you want the first emission to pass through (ab in example)":
var queue := []
hello.connect(func(i):
queue.push_back({})
(func():
if not queue.is_empty():
queue.clear()
print("First debounced with ", i)
).call_deferred()
)
if "you want the last emission to pass through (ba in example)":
var queue := []
hello.connect(func(i):
queue.push_back({})
(func():
queue.erase({})
if queue.is_empty():
print("Second debounced with ", i)
).call_deferred()
)
for i in 5:
hello.emit(i)
prints:
First debounced with 0
Second debounced with 4
Describe the project you are working on
Goost - Godot Engine Extension
Some existing Goost users find the feature quite useful so I decided to write this proposal.
Describe the problem or limitation you are having in your project
Sometimes (or oftentimes, depending on a problem), I have to make sure that the method is called only once during idle frame. This is because I don't want the same method to be called multiple times which would lead to performance degradation.
I'm talking about dirty flag optimization technique widely used in software engineering, so hopefully there's no need to explain the problem extensively here.
This optimization technique is especially important for procedural generation.
Describe the feature / enhancement and how it helps to overcome the problem or limitation
I propose to implement a mechanism which allows to make unique deferred calls. I see two approaches:
Object.CONNECT_DEFERRED_UNIQUE
orObject.CONNECT_DEFERRED_ONCE
flag for signals.Object.call_deferred_unique()
orObject.call_deferred_once()
method.Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
Typically, the dirty flag optimization is implemented in Godot the following way:
The update could be scheduled via
Resource.changed
signal emission, which I see as a typical use case for using a dirty flag in Godot context:Gradient
is only an example here, there are many usages of this technique throughout Godot's own codebase.Here's how it would look like:
In contrast to above, this requires much less code (the same amount of code just like for regular calls).
Of course, implementation-wise this won't be as efficient as using the dirty flag technique on a case by case basis, but good enough for most use cases, especially for heavy updates (like in editor).
I believe this is something that must be implemented in the
MessageQueue
C++ singleton in Godot. In 4.x, usingCallable
s will make this efficient enough in order to avoid duplicate calls during idle time.If this enhancement will not be used often, can it be worked around with a few lines of script?
On the case by case basis, this can be implemented via script, but that's far from a few lines of code, and you have to do this every time. I'd like to avoid excess code duplication in my project.
Is there a reason why this should be core and not an add-on in the asset library?
There are several reasons why this must be implemented in core:
Object
functionality.Note that some of this is already implemented in Goost via
GoostEngine.defer_call_unique()
.