I’ve seen that there are some interesting add-ons for simulating JavaScript Promises in Godot, although, for now, I prefer to expand what I have as I need new features.
Don’t do things like this in a regular project. The stuff in the video was done just for fun. It’s the equivalent of code golfing. Doing it in a real project will result in a total uncontrollable mess and cause you a lot of suffering.
Just use signals as they’re supposed to be used. I’d even advocate against the usage of popular signal-globalization patterns like “signal bus”.
As for signal arguments per se, they’re not really that advanced. It will be a part of normal signal usage in any average project. Once you understand that a signal emission is just a regular function call under the hood, there will be no mystery whatsoever about arguments.
There’s no “delicate issues” with signals whatsoever.
…
And yeah, I almost forgot - never use await unless you absolutely 100% know what you’re doing.
There’s certainly no serious problem with using signals. I didn’t express it well. My problem lies with the solution I need to apply when, for example, I have to wait for several independent processes to finish. I’ve used the SignalGroup approach I mentioned earlier, but it’s true that I can probably solve it in other ways.
I think I’m mixing up ideas, signals, and asynchronicity (await).
You can implement a simple countdown latch (aka fan-in) pattern for things like that. In a nutshell - you set a counter to the number of started tasks. Whenever a task is finished - you decrement the counter. When the counter reaches zero - you’re done. This can be done in a very straightforward manner, using only regular signal handlers.
I looked that up and it does give me really bad vibes. If you want reiable, reusable code I would rather just have a function on a global node, and said function uses call_group or set_group as necessary
I’d really like to hear about your thoughts on that. I use this method constantly. It seems to work really well. The listening nodes do not have to connect to the signal emitter or even know about them at all. I can’t think of a single negative about it.
It globalizes the signals. This consequently breaks the hierarchy and encapsulation of signaling. That’s quite enough to consider it bad, in the same way global variables are considered bad, especially if there’s a lot of them.
You can always refactor so that you don’t actually need global signals. If it looks like your project absolutely cannot live without global signal - it’s a sign there may be some fundamental architectural problems there.
I wasn’t sure if I should continue this on a different topic. Sorry to the OP if this is a bit off topic.
So as an example, My creatures are spawned, de-spawned, frozen or unfrozen when the world chunk they are in is frozen or unloaded. Very, very far chunks are unloaded. Very far chunks are frozen. The way I know which creatures are still in particular chunks is with a creature registry. Creatures register themselves and alert the registry when they change chunks.
So the creature registry is alerted via the signal manager with these signals:
signal register_creature(creature:Node2D, chunk_id:int)
signal creature_changed_chunk(creature:Node2D, old_chunk_id:int, new_chunk_id:int)
So the alternative is that on creation the creature has a variable set in it that tells it where the registry is, so it can connect to the registry that way.
Oh, I just realised that would work fine too. But why would it be better?
After reading a bit more about this I think I see the problem for a global signal bus, but I think it is a problem of scale. With just me making a smallish game, I think the issues don’t arise. However I can see why a large project with many developers, this might cause a problem. Having looked at all the signals in the bus, I could refactor all the signals out of the global bus quite easily, however I am not sure what I gain from that in my particular circumstance.
Its funny when you realise something you rely on is actually bad practice, but you find it so convenient that you think perhaps bad practice in this circumstance is ok. I suppose that is why I am an amateur game dev. I will refactor them out if I have a good reason to. But I am a bit upset with myself that I am doing something that is bad, and it is going to bug me now.
On the other hand, don’t be too bothered if someone thinks something you do is a bad practice. If it works well for your project - use it. As long as you’re aware of its shortcomings and potential problems, and can work around them - you’re fine.
Some events are just inherently global, so it makes sense to express them as global signals. If you want something to happen when a specific enemy dies, you attach a signal to the enemy. If you want something to happen when an enemy dies in a specific level, you attach a signal to the level. If you want something to happen if an enemy dies anywhere at all in the game, you need to some to attach the signal to something of global lifetime, whether that’s a top-level scene that’s never unloaded or an auto-load.
Workarounds exist, of course, but they don’t change that the event itself is fundamentally global.