Event Signal

Data-oriented signal system

Overview

The system provides a robust foundation for type-safe, event-driven communication between components while maintaining clean memory management and thread safety.


Project Settings

Signal Getter Timeout

getter will be retry automatically until timeout. default: 1.5s

Signal Trigger State

default trigger state if signal is built with trigger once per frame default: Early Update

Prewarm Providers

build signals after assembly ready.


Why Use It

Consider a UI component that displays a player's life: traditionally, this would require direct references to the Player or Life components. However, such tight coupling makes the UI component less reusable and more difficult to maintain. With Signal, the UI component simply subscribes to life-related signals, making it project-agnostic and highly reusable.

While there are numerous dependency injection and reference management solutions available, many of these packages introduce unnecessary complexity. Unlike engines such as Godot that have built-in signal systems, Unity's traditional GameObject-Component architecture doesn't include a native signal system. However, Unity's flexible architecture allows us to implement custom solutions, which is where this Signal system comes in.

Signals can be created and accessed from anywhere in your codebase, enabling flexible and decoupled system design. This flexibility does come with a responsibility: while powerful, signals should be used thoughtfully and systematically to avoid creating complex, hard-to-debug signal chains.

Key Benefits:

  • Decoupled component communication

  • Improved code reusability

  • Lightweight implementation

  • Flexible system design


API


Quick Start

Signal provides a robust event-driven value management system with built-in observer pattern support and type safety.


Signal Permissions

Different type of signal defines the permissions. Select the correct signal for observers.

Signal Type
Trigger
Listener
Get Value
Set Value
Dispose

ISignal<T>

ISignal

IReadonlySignal<T>

ISignalListener<T>

ISignalListener

ISignalTrigger<T>

ISignalTrigger


Type Conversion

Conversion between different signal type is possible. However conversion from ISignal to other types is inversable.

Conversion
ISignal
IReadonlySignal
ISignalListener
ISignalTrigger

ISignal

IReadonlySignal

ISignalListener

ISignalTrigger


Trigger Once Per Frame

Signal is defaultly triggered immediately on value changed or Trigger() is called. This confirms all observers can be noticed the changes in time.

To prevent from duplicated trigger in same frame, use WithTriggerOncePerFrame() in builder.

When signal is built with trigger once per frame, observers will receive the last trigger before next specified state which may be next frame.

Please read Technical Details in Player Loop Hack for details.


Signal Linker

Linking up multiple Signals for handling multiple signals condition. Accepting only Signal with value.

Setting up signal linker is easy:

  • create a linker with builder

  • add adaptor with params

    • signalListener: one of signal as Listener

    • condition: how to check as true

    • option: how consider with other conditions

  • add listener(s) to linker, listener must received a boolean

    • change of any signal in linker will trigger the listener

    • true will be return on signal is disposed, and a warning log will be sent

  • do not forget to dispose the linker

Building a linker request almost nothing. Options are all optional.


In any system, regardless of its size, proper signal management is crucial for maintaining code clarity and preventing runtime issues. Even in small applications, you'll likely find yourself handling dozens of signals across multiple objects, making a well-organized structure essential.

Below is our recommended approach to signal management, which we use in production. This pattern offers several advantages:

  • Type Safety: Using enums for signal IDs prevents typos and enables IDE auto-completion

  • State Management: Integration with Promise/Task utilities ensures safe asynchronous operations and prevents common runtime issues like timing problems during initialization

  • Code Organization: Clear structural patterns make the system easy to maintain and scale

While this is our recommended approach, feel free to adapt it to your specific needs. The following example demonstrates how we structure signal systems in our own projects.

Create Signal IDs by Enum(s)

please read Task Utils for details of Promise Awaiter.


Prewarm Provider

Signals almost use on creating in runtime, and the lifetime should be same as GameObject or Scene. On the case of long-term signals, creating prewarm providers is the best options.

Prewarm Provider is a scriptable object. System will build the signal after assembly ready state. When the scene starts to load, all signals are already ready. This can also prevent from GetAsync process.


Exceptions

On GetAsync or GetReadonlyAsync function, exceptions will be returned on errors.

Exception
Description

SignalNotFoundException

When signal is not found with given ID

SignalTypeErrorException

When signal with wrong given value type - only on Signal with value

SignalReadonlyAlertException

When signal is built as Readonly but using GetAsync - only on Signal with value


Advanced Use

An example of runtime instantiated a set of UI Elements and a control of Element Group.

Element is Tag Element. Tags will be a button and will be instantiated different amount in runtime.

Group is Tags Group for instantiating and controlling all tag elements.

Tag Element is controlling a single Tag. By received a pointer click to trigger a tag selection for Tags Group to do further actions.

Tag Element should control the UI behaviour only. UI Behaviour should not contains business logics. Tags Group contains business logics and wait for UI behaviours - on pointer clicked event.

In this case, when Tags Group instantiates a Tag Element, it will build a new Signal with signal id as value and record in tag elements collection. This signal will pass to Tag Element and will be triggered on pointer clicked.


Last updated