Skip to content

Commit

Permalink
Move and simplify Object's connect description
Browse files Browse the repository at this point in the history
  • Loading branch information
Mickeon committed Jul 9, 2024
1 parent 26d1577 commit 824d4ee
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 126 deletions.
128 changes: 3 additions & 125 deletions doc/classes/Object.xml
Original file line number Diff line number Diff line change
Expand Up @@ -469,131 +469,9 @@
<param index="2" name="flags" type="int" default="0" />
<description>
Connects a [param signal] by name to a [param callable]. Optional [param flags] can be also added to configure the connection's behavior (see [enum ConnectFlags] constants).
A signal can only be connected once to the same [Callable]. If the signal is already connected, this method returns [constant ERR_INVALID_PARAMETER] and pushes an error message, unless the signal is connected with [constant CONNECT_REFERENCE_COUNTED]. To prevent this, use [method is_connected] first to check for existing connections.
If the [param callable]'s object is freed, the connection will be lost.
[b]Examples with recommended syntax:[/b]
Connecting signals is one of the most common operations in Godot and the API gives many options to do so, which are described further down. The code block below shows the recommended approach.
[codeblocks]
[gdscript]
func _ready():
var button = Button.new()
# `button_down` here is a Signal variant type, and we thus call the Signal.connect() method, not Object.connect().
# See discussion below for a more in-depth overview of the API.
button.button_down.connect(_on_button_down)

# This assumes that a `Player` class exists, which defines a `hit` signal.
var player = Player.new()
# We use Signal.connect() again, and we also use the Callable.bind() method,
# which returns a new Callable with the parameter binds.
player.hit.connect(_on_player_hit.bind("sword", 100))

func _on_button_down():
print("Button down!")

func _on_player_hit(weapon_type, damage):
print("Hit with weapon %s for %d damage." % [weapon_type, damage])
[/gdscript]
[csharp]
public override void _Ready()
{
var button = new Button();
// C# supports passing signals as events, so we can use this idiomatic construct:
button.ButtonDown += OnButtonDown;

// This assumes that a `Player` class exists, which defines a `Hit` signal.
var player = new Player();
// We can use lambdas when we need to bind additional parameters.
player.Hit += () =&gt; OnPlayerHit("sword", 100);
}

private void OnButtonDown()
{
GD.Print("Button down!");
}

private void OnPlayerHit(string weaponType, int damage)
{
GD.Print($"Hit with weapon {weaponType} for {damage} damage.");
}
[/csharp]
[/codeblocks]
[b][code skip-lint]Object.connect()[/code] or [code skip-lint]Signal.connect()[/code]?[/b]
As seen above, the recommended method to connect signals is not [method Object.connect]. The code block below shows the four options for connecting signals, using either this legacy method or the recommended [method Signal.connect], and using either an implicit [Callable] or a manually defined one.
[codeblocks]
[gdscript]
func _ready():
var button = Button.new()
# Option 1: Object.connect() with an implicit Callable for the defined function.
button.connect("button_down", _on_button_down)
# Option 2: Object.connect() with a constructed Callable using a target object and method name.
button.connect("button_down", Callable(self, "_on_button_down"))
# Option 3: Signal.connect() with an implicit Callable for the defined function.
button.button_down.connect(_on_button_down)
# Option 4: Signal.connect() with a constructed Callable using a target object and method name.
button.button_down.connect(Callable(self, "_on_button_down"))

func _on_button_down():
print("Button down!")
[/gdscript]
[csharp]
public override void _Ready()
{
var button = new Button();
// Option 1: In C#, we can use signals as events and connect with this idiomatic syntax:
button.ButtonDown += OnButtonDown;
// Option 2: GodotObject.Connect() with a constructed Callable from a method group.
button.Connect(Button.SignalName.ButtonDown, Callable.From(OnButtonDown));
// Option 3: GodotObject.Connect() with a constructed Callable using a target object and method name.
button.Connect(Button.SignalName.ButtonDown, new Callable(this, MethodName.OnButtonDown));
}

private void OnButtonDown()
{
GD.Print("Button down!");
}
[/csharp]
[/codeblocks]
While all options have the same outcome ([code]button[/code]'s [signal BaseButton.button_down] signal will be connected to [code]_on_button_down[/code]), [b]option 3[/b] offers the best validation: it will print a compile-time error if either the [code]button_down[/code] [Signal] or the [code]_on_button_down[/code] [Callable] are not defined. On the other hand, [b]option 2[/b] only relies on string names and will only be able to validate either names at runtime: it will print a runtime error if [code]"button_down"[/code] doesn't correspond to a signal, or if [code]"_on_button_down"[/code] is not a registered method in the object [code]self[/code]. The main reason for using options 1, 2, or 4 would be if you actually need to use strings (e.g. to connect signals programmatically based on strings read from a configuration file). Otherwise, option 3 is the recommended (and fastest) method.
[b]Binding and passing parameters:[/b]
The syntax to bind parameters is through [method Callable.bind], which returns a copy of the [Callable] with its parameters bound.
When calling [method emit_signal] or [method Signal.emit], the signal parameters can be also passed. The examples below show the relationship between these signal parameters and bound parameters.
[codeblocks]
[gdscript]
func _ready():
# This assumes that a `Player` class exists, which defines a `hit` signal.
var player = Player.new()
# Using Callable.bind().
player.hit.connect(_on_player_hit.bind("sword", 100))

# Parameters added when emitting the signal are passed first.
player.hit.emit("Dark lord", 5)

# We pass two arguments when emitting (`hit_by`, `level`),
# and bind two more arguments when connecting (`weapon_type`, `damage`).
func _on_player_hit(hit_by, level, weapon_type, damage):
print("Hit by %s (level %d) with weapon %s for %d damage." % [hit_by, level, weapon_type, damage])
[/gdscript]
[csharp]
public override void _Ready()
{
// This assumes that a `Player` class exists, which defines a `Hit` signal.
var player = new Player();
// Using lambda expressions that create a closure that captures the additional parameters.
// The lambda only receives the parameters defined by the signal's delegate.
player.Hit += (hitBy, level) =&gt; OnPlayerHit(hitBy, level, "sword", 100);

// Parameters added when emitting the signal are passed first.
player.EmitSignal(SignalName.Hit, "Dark lord", 5);
}

// We pass two arguments when emitting (`hit_by`, `level`),
// and bind two more arguments when connecting (`weapon_type`, `damage`).
private void OnPlayerHit(string hitBy, int level, string weaponType, int damage)
{
GD.Print($"Hit by {hitBy} (level {level}) with weapon {weaponType} for {damage} damage.");
}
[/csharp]
[/codeblocks]
A signal can only be connected once to the same [Callable]. If the signal is already connected, this method returns [constant ERR_INVALID_PARAMETER] and generates an error, unless the signal is connected with [constant CONNECT_REFERENCE_COUNTED]. To prevent this, use [method is_connected] first to check for existing connections.
[b]Note:[/b] If the [param callable]'s object is freed, the connection will be lost.
[b]Note:[/b] In GDScript, it is generally recommended to connect signals with [method Signal.connect] instead.
</description>
</method>
<method name="disconnect">
Expand Down
125 changes: 124 additions & 1 deletion doc/classes/Signal.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,128 @@
delegate void ItemDroppedEventHandler(string itemName, int amount);
[/csharp]
[/codeblocks]
Connecting signals is one of the most common operations in Godot and the API gives many options to do so, which are described further down. The code block below shows the recommended approach.
[codeblocks]
[gdscript]
func _ready():
var button = Button.new()
# `button_down` here is a Signal Variant type. We therefore call the Signal.connect() method, not Object.connect().
# See discussion below for a more in-depth overview of the API.
button.button_down.connect(_on_button_down)

# This assumes that a `Player` class exists, which defines a `hit` signal.
var player = Player.new()
# We use Signal.connect() again, and we also use the Callable.bind() method,
# which returns a new Callable with the parameter binds.
player.hit.connect(_on_player_hit.bind("sword", 100))

func _on_button_down():
print("Button down!")

func _on_player_hit(weapon_type, damage):
print("Hit with weapon %s for %d damage." % [weapon_type, damage])
[/gdscript]
[csharp]
public override void _Ready()
{
var button = new Button();
// C# supports passing signals as events, so we can use this idiomatic construct:
button.ButtonDown += OnButtonDown;

// This assumes that a `Player` class exists, which defines a `Hit` signal.
var player = new Player();
// We can use lambdas when we need to bind additional parameters.
player.Hit += () =&gt; OnPlayerHit("sword", 100);
}

private void OnButtonDown()
{
GD.Print("Button down!");
}

private void OnPlayerHit(string weaponType, int damage)
{
GD.Print($"Hit with weapon {weaponType} for {damage} damage.");
}
[/csharp]
[/codeblocks]
[b][code skip-lint]Object.connect()[/code] or [code skip-lint]Signal.connect()[/code]?[/b]
As seen above, the recommended method to connect signals is not [method Object.connect]. The code block below shows the four options for connecting signals, using either this legacy method or the recommended [method Signal.connect], and using either an implicit [Callable] or a manually defined one.
[codeblocks]
[gdscript]
func _ready():
var button = Button.new()
# Option 1: Object.connect() with an implicit Callable for the defined function.
button.connect("button_down", _on_button_down)
# Option 2: Object.connect() with a constructed Callable using a target object and method name.
button.connect("button_down", Callable(self, "_on_button_down"))
# Option 3: Signal.connect() with an implicit Callable for the defined function.
button.button_down.connect(_on_button_down)
# Option 4: Signal.connect() with a constructed Callable using a target object and method name.
button.button_down.connect(Callable(self, "_on_button_down"))

func _on_button_down():
print("Button down!")
[/gdscript]
[csharp]
public override void _Ready()
{
var button = new Button();
// Option 1: In C#, we can use signals as events and connect with this idiomatic syntax:
button.ButtonDown += OnButtonDown;
// Option 2: GodotObject.Connect() with a constructed Callable from a method group.
button.Connect(Button.SignalName.ButtonDown, Callable.From(OnButtonDown));
// Option 3: GodotObject.Connect() with a constructed Callable using a target object and method name.
button.Connect(Button.SignalName.ButtonDown, new Callable(this, MethodName.OnButtonDown));
}

private void OnButtonDown()
{
GD.Print("Button down!");
}
[/csharp]
[/codeblocks]
While all options have the same outcome ([code]button[/code]'s [signal BaseButton.button_down] signal will be connected to [code]_on_button_down[/code]), [b]option 3[/b] offers the best validation: it will print a compile-time error if either the [code]button_down[/code] [Signal] or the [code]_on_button_down[/code] [Callable] are not defined. On the other hand, [b]option 2[/b] only relies on string names and will only be able to validate either names at runtime: it will generate an error at runtime if [code]"button_down"[/code] is not a signal, or if [code]"_on_button_down"[/code] is not a method in the object [code]self[/code]. The main reason for using options 1, 2, or 4 would be if you actually need to use strings (e.g. to connect signals programmatically based on strings read from a configuration file). Otherwise, option 3 is the recommended (and fastest) method.
[b]Binding and passing parameters:[/b]
The syntax to bind parameters is through [method Callable.bind], which returns a copy of the [Callable] with its parameters bound.
When calling [method emit] or [method Object.emit_signal], the signal parameters can be also passed. The examples below show the relationship between these signal parameters and bound parameters.
[codeblocks]
[gdscript]
func _ready():
# This assumes that a `Player` class exists, which defines a `hit` signal.
var player = Player.new()
# Using Callable.bind().
player.hit.connect(_on_player_hit.bind("sword", 100))

# Parameters added when emitting the signal are passed first.
player.hit.emit("Dark lord", 5)

# We pass two arguments when emitting (`hit_by`, `level`),
# and bind two more arguments when connecting (`weapon_type`, `damage`).
func _on_player_hit(hit_by, level, weapon_type, damage):
print("Hit by %s (level %d) with weapon %s for %d damage." % [hit_by, level, weapon_type, damage])
[/gdscript]
[csharp]
public override void _Ready()
{
// This assumes that a `Player` class exists, which defines a `Hit` signal.
var player = new Player();
// Using lambda expressions that create a closure that captures the additional parameters.
// The lambda only receives the parameters defined by the signal's delegate.
player.Hit += (hitBy, level) =&gt; OnPlayerHit(hitBy, level, "sword", 100);

// Parameters added when emitting the signal are passed first.
player.EmitSignal(SignalName.Hit, "Dark lord", 5);
}

// We pass two arguments when emitting (`hit_by`, `level`),
// and bind two more arguments when connecting (`weapon_type`, `damage`).
private void OnPlayerHit(string hitBy, int level, string weaponType, int damage)
{
GD.Print($"Hit by {hitBy} (level {level}) with weapon {weaponType} for {damage} damage.");
}
[/csharp]
[/codeblocks]
</description>
<tutorials>
<link title="Using Signals">$DOCS_URL/getting_started/step_by_step/signals.html</link>
Expand Down Expand Up @@ -59,14 +181,15 @@
<param index="1" name="flags" type="int" default="0" />
<description>
Connects this signal to the specified [param callable]. Optional [param flags] can be also added to configure the connection's behavior (see [enum Object.ConnectFlags] constants). You can provide additional arguments to the connected [param callable] by using [method Callable.bind].
A signal can only be connected once to the same [Callable]. If the signal is already connected, returns [constant ERR_INVALID_PARAMETER] and pushes an error message, unless the signal is connected with [constant Object.CONNECT_REFERENCE_COUNTED]. To prevent this, use [method is_connected] first to check for existing connections.
A signal can only be connected once to the same [Callable]. If the signal is already connected, this method returns [constant ERR_INVALID_PARAMETER] and generates an error, unless the signal is connected with [constant Object.CONNECT_REFERENCE_COUNTED]. To prevent this, use [method is_connected] first to check for existing connections.
[codeblock]
for button in $Buttons.get_children():
button.pressed.connect(_on_pressed.bind(button))

func _on_pressed(button):
print(button.name, " was pressed")
[/codeblock]
[b]Note:[/b] If the [param callable]'s object is freed, the connection will be lost.
</description>
</method>
<method name="disconnect">
Expand Down

0 comments on commit 824d4ee

Please sign in to comment.