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.
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.
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;
}
}
);
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 |
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.
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() {
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.