Broadcasting - coldrockgames/gml-raptor GitHub Wiki

The Broadcasting subsystem in raptor is similar to the signal system you might know from the Godot engine.

You can send messages like a radio station sends a song.
You create receivers which act like a radio.
If you turn it on, you can hear the song.
If you turn it off, you will not hear anything.

And just like the frequency a radio station uses to send its songs, your receivers have filters to receive only those messages you want to hear (normally you don't want to hear all songs of all radio stations at once, that would be a horrific noise).

Now let's take a look at how this concept is implemented and used.

BROADCASTER (The Sender)

This is your radio station.

Raptor provides only one sender. This is all you need. It can be reached through the BROADCASTER macro.

To send a message just invoke the send method:

/// @func	send(_from, _title, _data = undefined)
/// @desc	Sends a broadcast and returns self for call chaining if you want to
///		send multiple broadcasts.
///		Set .handled to true in the broadcast object delivered to the function
///		to stop the send-loop from sending the same message to the remaining recipients.
static send = function(_from, _title, _data = undefined) {

A broadcast consists of these elements:

member description
from Who is sending this? In most cases you will provide self here
title The name of the broadcast (the song name, to keep the analogy up)
data Any struct you want to attach (the lyrics of the song or the EventArgs if you prefer the C# pattern)

Example:

// Imagine this is the death-function of your "Monster" object
// You send the message and attach your hp to it.
// Your hp may be negative, so an "overkill" can be detected
// (just an imaginary situation!)
on_die = function() {
    BROADCASTER.send(self, "BC_MONSTER_DIED", { 
        hp: my_hp
    });
}

Next thing to look at is the "who and how" to receive this message.

The Receiver

GameMaker offers a concept for many of its events that we call here "pre/main/post".

Examples are Begin Draw / Draw / End Draw or Begin Step / Step / End Step.

Raptor offers the same pattern for broadcast messages, so you always have the chance to be "the first" or "the last" to receive a broadcast. But similar to the GameMaker pattern, the "normal" mode (Draw or Step) is the most widely used. Still, you have the option to receive it as PRE or POST message if you need it.

To be able to receive a message, you must create a Receiver.

#macro Description
PRE_BROADCASTER The PRE receiver. This is your Begin Step in the GameMaker pattern.
BROADCASTER The normal receiver. This is your Step in the GameMaker pattern.
Yes, this is the same macro that also offers the send method!
POST_BROADCASTER The POST receiver. This is your End Step in the GameMaker pattern.

Each of those three offers this function:

/// @func	add_receiver(_owner, _name, _message_filter, _callback)
/// @desc	adds a listener for a specific kind of message.
///		NOTE: If a receiver with that name already exists, it gets overwritten!
///		The _message_filter is a wildcard string, that may
///		contain "*" as placeholder according to the string_match specifications
static add_receiver = function(_owner, _name, _message_filter, _callback) {
argument description
owner Who owns this receiver? In most cases this is also self.
This can become handy if you want to clean up your instance by calling remove_owner (explained a bit down this page under "Other Functions")
name The name of the receiver. This must be a system-wide unique string to identify this receiver.
Practice Tipp: Construct the name using the macro MY_NAME or MY_CLASS_NAME (for code classees) of raptor, like $"{MY_NAME}_monster_death" as they resolve to instancename+id and are always unique and they are quite readable (like "GameController-00012345_monster_death" in this case
message_filter A wildcard string telling which messages shall arrive here.
For the example above, something like *_MONSTER_* would fit good
callback A function receiving one argument bc which is the broadcast.

This example shows the receiving part of the death message of the monster we sent above:

BROADCASTER.add_receiver(self, $"monster_death_{SUID}", "*_MONSTER_*",
    function(bc) {
        switch (bc.title) {
            case "BC_MONSTER_DIED":
                if (bc.data.hp < 0) ilog("Overkill!");
                array_remove(alive_monsters, bc.from);
                break;
        }
    }
);

Contents of the bc argument

The bc argument in the callback offers exactly the members of a broadcast plus some extra information:

Member Datatype Contains
uniqueid int Every broadcast has a uniqueid. It's a numeric value, unique for each send call, counting up
handled bool Initialized with false. Set it to true, to mark this broadcast as being done.
The send loop will abort then and stop sending this broadcast to the remaining recipients
from instance The sender of this broadcast
title string The string you set as title argument to the send method
data struct Any custom data struct you supplied to the send method

Creating a "one-shot-receiver"

There are situations where you want to just wait for one single event to arrive and then stop receiving, like entering or leaving a level. Those events will be sent only once and there is no need to keep a receiver up and active when the event will not arrive again (or you don't want to receive it again).

For this situation, raptor offers a shortcut to create a one-shot-receiver:

Tip

You may return true from the receiving callback method to tell the broadcaster "I am done with this event, remove me".
The broadcaster will then remove this receiver and free the allocated memory and remove it from the recipients list. It's an implicit clean-up.

Other BROADCASTER Functions

These functions are available on all three broadcasters (PRE_BROADCASTER, BROADCASTER, POST_BROADCASTER).

/// @func	remove_receiver(_name)
/// @desc	Removes the receiver with the specified name and returns true, if found.
///		If it does not exist, it is silently ignored, but false is returned.
static remove_receiver = function(_name) {
/// @func	remove_owner(_owner)
/// @desc	Removes ALL receivers with the specified owner and returns the number of removed receivers.
///		NOTE: If your object is a child of _raptorBase, you do not need to call this,
///		because the base object removes all owned receivers in the CleanUp event
static remove_owner = function(_owner) {
/// @func	receiver_exists(_name)
/// @desc	Checks whether a receiver with the specified name exists
static receiver_exists = function(_name) {
/// @func	receiver_count(_name_or_owner)
/// @desc	Counts the existing receivers.
///		Supply a (wildcard-)string as argument to count
///		receivers that match a specified name or supply
///		an owner instance to count the number of receivers
///		registered for this instance.
static receiver_count = function(_name_or_owner) {
/// @func	clear()
/// @desc	Removes all receivers.	
static clear = function() {

Naming your messages and receivers

It is very important to know, that the names of your receivers must be system-wide unique!
If you add a receiver with an already existing name, it will replace the former one.

Important

Keep in mind: Adding a receiver with the same name as an existing one, will replace the existing. So take care, how you name your receivers, as this can be a useful feature, or a trap, depending on how you use it.
As an example, naming a receiver "active_weapon" on the player object might be a good idea, as any weapon that gets activated could just add itself as a receiver under the same name and replacing the so-far active weapon with itself in one go.
On the other hand, naming a receiver in a monster "active_weapon" is a bad idea, when there are many monsters on the screen, they would permanently replace each other for the broadcast.
To have a unique name for each instance, consider using the macros MY_NAME or MY_CLASS_NAME (for code classes) as part of the receiver name, like $"{MY_NAME}_active_weapon", as this resolves to the object name plus the instance id and it always delivers a unique string for one object instance.

raptor also uses the broadcasting system internally, all its messages are prefixed with raptor_<message_name> to avoid collisions with your own custom messages.
However, it might be handy to listen to some of the raptor-internal messages, as some of them can be of interest for your running game.
You can find the full list of internal messages at Raptor-internal broadcasts.

⚠️ **GitHub.com Fallback** ⚠️