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.
PlayerLoopState is using Player Loop Hack, please see document for details.
GetAsync() is using Promise. Please read Task Utils for details.
please see section Signal Permissions and Type Conversion for details.
Signal Permissions
Different type of signal defines the permissions. Select the correct signal for observers.
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.
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.
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 Listenercondition: how to check as trueoption: 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
truewill be return on signal is disposed, and a warning log will be sent
do not forget to dispose the linker
If the signal in linker is disposed, a warning log will be shown, and condition of this adaptor will be true.
There is not result field or Trigger() in linker. The only way to receive condition change event is listener.
Building a linker request almost nothing. Options are all optional.
Any trigger of signals in linker will trigger the linker.
To prevent from duplicated trigger in same frame, use WithTriggerOncePerFrame.
Adaptor is a condition option handler. Adding adaptors to linker to add more condition checking on signals triggered.
Adding listener to linker is the only method to listener the condition.
Do not forget to dispose the linker.
Recommended Usage
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
Create Signal IDs by Enum(s)
Build the Player Life component
Dynamic method to create an UI Text Updater with signal.
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.
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