← Back to Documentation

Signals

⚠️ Experimental - Pre-Release

Signals are Perro's event system for communication between scripts and components. They provide a decoupled way to send and receive events without requiring direct references between objects.

Signals are global and name-based - any script can emit a signal by name, and any script can listen for that signal by name, without needing to know about each other. This completely decouples signal emitters from listeners.

When a signal is emitted, all connected listeners are called instantly with 500ns dispatch performance, making signals suitable for high-frequency events.

How Signals Work

Signals work through a simple pattern:

  1. Emit: A script calls Signal.emit("signal_name") to broadcast an event
  2. Connect: Other scripts connect functions to listen for that signal name
  3. Dispatch: When emitted, all connected functions are called instantly

This decoupling means you can have UI buttons that emit signals without any scripts attached, and game logic scripts that listen for those signals without knowing where they come from.

Complete Decoupling

Signals provide complete decoupling between scripts. Two completely separate scripts can communicate through signals by using the same signal name, without any direct references to each other. As long as one script connects to the signal before the other script emits it, the communication works perfectly.

Here's an example with two separate scripts:

Script 1: Listener (connects to signal)

@script GameManager extends Node    fn init() {        // Connect to a signal by name        Signal.connect("player_Died", on_player_died)    }     fn on_player_died() {        Console.print("Game over!")        // Handle game over logic...    }

Script 2: Emitter (emits signal)

@script Player extends Sprite2D    var health = 100     fn take_damage(amount: int) {        health -= amount        if health <= 0 {            // Emit signal by name - GameManager will receive it            Signal.emit("player_Died")        }    }

These two scripts are completely independent - they don't reference each other at all. They only share the signal name "player_Died". As long as GameManager connects to the signal in itsinit() (which runs when the script is first loaded), it will receive the signal when Player emits it later.

Automatic Signal Connection

Use the on keyword shorthand for automatic signal connection:

@script GameManager extends Node on start_Pressed() {    Console.print("Start button was pressed!")    // Start the game...} on pause_Pressed() {    Console.print("Game paused")}

The on syntax automatically creates a function and connects it to the signal in init().

Manual Signal Connection

You can also manually connect signals using Signal.connect():

@script Player extends Sprite2D fn init() {    Signal.connect("player_Died", on_player_died)} fn on_player_died() {    Console.print("Player died!")}

Emitting Signals

Emit signals from your scripts:

@script Player extends Sprite2D fn take_damage() {    health -= 10    if health <= 0 {        Signal.emit("player_Died")    }}

Performance

Perro's signal system provides instant dispatch with 500ns performance, making it suitable for high-frequency events.

Related