That is the key idea:
a Signal belongs to an owner instance.
Subscribers still need a reference to that owner in order to subscribe.
When Signal Is a Good Fit
Reach for Signal when:
- one object exposes a callback surface to several listeners
- listeners already know the owner they want to observe
- the event belongs to that one owner instance
- subscription objects,
once(...), and explicit disposal are useful
This is a good fit for local, owner-scoped relationships.
Example: A Door Exposes onOpened
Here the Door owns the signal:
use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\Signal;
final class Door extends Behaviour
{
public Signal $onOpened;
public function awake(): void
{
$this->onOpened = $this->createSignal();
}
public function open(): void
{
$this->onOpened->dispatch($this);
}
}
Now another object can subscribe to that one specific door:
use Lenga\Engine\Core\Behaviour;
use Lenga\Engine\Core\SignalSubscription;
final class DoorIndicator extends Behaviour
{
public ?Door $door = null;
private ?SignalSubscription $subscription = null;
public function onEnable(): void
{
$this->subscription = $this->door?->onOpened->add(function (Door $door): void {
$this->flashIndicator();
});
}
public function onDisable(): void
{
$this->subscription?->dispose();
$this->subscription = null;
}
private function flashIndicator(): void
{
}
}
This works well because DoorIndicator already knows which Door it cares about.
Why createSignal() Matters
Create signals in awake() with createSignal():
public function awake(): void
{
$this->onOpened = $this->createSignal();
}
That keeps the signal tied to the behaviour lifecycle.
What Signals Do Well
Signals are useful when you want:
- one owner to expose a local callback surface
- several listeners attached to the same owner
- explicit subscription objects
once(...)behavior
Signals are not the default answer for general gameplay events.
If your listener should react without holding a dispatcher reference, use EventBus instead.