Unclaimed Package Is this your package? Claim it to unlock full analytics and manage your listing.
Claim This Package

Install via UPM

Add to Unity Package Manager using this URL

https://www.pkglnk.dev/unisignal.git

README Markdown

Copy this to your project's README.md

Style
Preview
pkglnk installs badge
## Installation

Add **UniSignal** to your Unity project via Package Manager:

1. Open **Window > Package Manager**
2. Click **+** > **Add package from git URL**
3. Enter:
```
https://www.pkglnk.dev/unisignal.git
```

[![pkglnk](https://www.pkglnk.dev/badge/unisignal.svg?style=pkglnk)](https://www.pkglnk.dev/pkg/unisignal)

README

UniSignal Logo

A lightweight, zero-allocation signal (event) library for Unity. Decouple your systems with type-safe, struct-based signals — no strings, no boxing, no garbage.

Github license Unity 2021.3+ GitHub package.json version

Features

  • Type-safe signals — signals are structs, caught at compile time
  • Zero GC in steady state — subscription objects are pooled and recycled automatically
  • Multiple subscription modes — by type, by specific value, or by predicate
  • Reentrant dispatch — subscribe/unsubscribe safely from inside a signal callback
  • Listener-based bulk unsubscribe — pass this as listener, then unsubscribe everything in one call (great for OnDestroy)
  • No singletons — create as many SignalHub instances as you need (global, per-scene, per-entity)

Table of Contents

Installation

Option 1: Unity Package Manager (UPM)

  1. Open Window → Package Manager
  2. Click +Add package from git URL...
  3. Enter:
https://github.com/actionk/UniSignal.git#1.0.0

Option 2: manifest.json

Add to your Packages/manifest.json:

"com.actionik.polymorphex.unisignal": "https://github.com/actionk/UniSignal.git#1.0.0"

Quick Start

// 1. Define a signal
public struct PlayerDiedSignal : ISignal { }

// 2. Create a hub (or inject one via DI)
var signalHub = new SignalHub();

// 3. Subscribe
signalHub.Subscribe<PlayerDiedSignal>(this, () =>
{
    Debug.Log("Player died!");
});

// 4. Dispatch
signalHub.Dispatch(new PlayerDiedSignal());

// 5. Cleanup
signalHub.Unsubscribe(this);

Defining Signals

Simple Signals

A signal is any struct that implements ISignal. It can be completely empty if you only need to notify that something happened:

public struct GameStartedSignal : ISignal { }
public struct GameOverSignal : ISignal { }

Signals with Data

Signals can carry data. Subscribers can optionally receive it:

public struct DamageSignal : ISignal
{
    public int amount;
    public Entity source;
}

Matchable Signals

If you want subscribers to match on a specific signal value (not just the type), implement ISignal<T> which requires IEquatable<T>:

public struct ItemPickedUpSignal : ISignal<ItemPickedUpSignal>
{
    public int itemId;

    public bool Equals(ItemPickedUpSignal other) => itemId == other.itemId;
    public override int GetHashCode() => itemId;
}

This enables subscribing to, for example, only item ID 42 being picked up.

Subscribing

All Subscribe overloads return a SignalSubscription<T> that can be used to unsubscribe later.

By Signal Type

Callback fires for every signal of that type.

// Without data
signalHub.Subscribe<DamageSignal>(this, () =>
{
    Debug.Log("Something took damage");
});

// With data
signalHub.Subscribe<DamageSignal>(this, (DamageSignal signal) =>
{
    Debug.Log($"Took {signal.amount} damage");
});

By Signal Type with Data

Same as above, but the callback receives the signal struct:

signalHub.Subscribe<DamageSignal>(this, (DamageSignal signal) =>
{
    healthBar.SetValue(healthBar.Value - signal.amount);
});

By Specific Value

Only fires when the dispatched signal equals the subscribed value. Requires ISignal<T>:

// Without data
signalHub.Subscribe(this, new ItemPickedUpSignal { itemId = 42 }, () =>
{
    Debug.Log("Picked up item 42!");
});

// With data
signalHub.Subscribe(this, new ItemPickedUpSignal { itemId = 42 }, (ItemPickedUpSignal signal) =>
{
    Debug.Log($"Got item {signal.itemId}");
});

By Predicate

Only fires when the predicate returns true:

// Without data
signalHub.Subscribe<DamageSignal>(this,
    signal => signal.amount > 50,
    () => Debug.Log("Big hit!")
);

// With data
signalHub.Subscribe<DamageSignal>(this,
    signal => signal.amount > 50,
    (DamageSignal signal) => Debug.Log($"Big hit: {signal.amount}")
);

By Predicate with Data

signalHub.Subscribe<DamageSignal>(this,
    signal => signal.source != Entity.Invalid,
    (DamageSignal signal) =>
    {
        Debug.Log($"Damage from {signal.source}: {signal.amount}");
    }
);

Without a Listener

You can omit the listener object. This is useful for static or one-off subscriptions, but you won't be able to bulk-unsubscribe by listener:

signalHub.Subscribe<GameStartedSignal>(() => Debug.Log("Game started"));

Dispatching

signalHub.Dispatch(new DamageSignal { amount = 25 });

Dispatch is reentrant — it's safe to subscribe, unsubscribe, or dispatch again from inside a callback. Changes are queued and applied after the current dispatch completes.

Unsubscribing

Single Subscription

var sub = signalHub.Subscribe<DamageSignal>(this, () => { });

// Either via the hub:
signalHub.Unsubscribe(sub);

// Or directly on the subscription:
sub.Unsubscribe();

All Subscriptions of a Listener

Removes all subscriptions (across all signal types) registered with this listener object. Ideal for OnDestroy:

signalHub.Unsubscribe(this);

All Subscriptions of a Listener for a Specific Signal

Removes only subscriptions of a specific signal type for the given listener:

signalHub.Unsubscribe<DamageSignal>(this);

All Subscriptions of a Signal Type

Removes all subscriptions of a signal type, regardless of listener:

signalHub.UnsubscribeAllFrom<DamageSignal>();

API Reference

SignalHub

Method Description
Subscribe<T>(listener, callback) Subscribe to all signals of type T
Subscribe<T>(listener, Action<T>) Subscribe with signal data
Subscribe<T>(listener, predicate, callback) Subscribe with a filter predicate
Subscribe<T>(listener, predicate, Action<T>) Subscribe with predicate + signal data
Subscribe<T>(listener, signal, callback) Subscribe to a specific signal value (ISignal<T>)
Subscribe<T>(listener, signal, Action<T>) Subscribe to a specific value with data
Dispatch<T>(signal) Dispatch a signal to all matching subscribers
Unsubscribe(subscription) Remove a single subscription
Unsubscribe(listener) Remove all subscriptions for a listener
Unsubscribe<T>(listener) Remove all T subscriptions for a listener
UnsubscribeAllFrom<T>() Remove all subscriptions of type T
GetSubscriptionListeners() Get all registered listener objects

All Subscribe overloads are also available without the listener parameter.

ISignal

public interface ISignal { }

Marker interface for signal structs.

ISignal<T>

public interface ISignal<T> : ISignal, IEquatable<T> { }

For signals that can be matched by value using Subscribe(listener, signalValue, callback).

Performance

  • Object pooling: subscription objects are pooled per signal type — no allocations after warmup
  • Indexed for-loops: all internal iteration uses indexed loops to avoid enumerator allocations
  • Reentrant dispatch: uses a depth counter instead of copying lists, so nested dispatches are efficient
  • Struct signals: no boxing — signals stay on the stack

Tests

Unit tests are included in the Tests/ folder. Run them via Unity's Test Runner (Window → General → Test Runner).

License

Distributed under the MIT License. See LICENSE for more information.

Comments

No comments yet. Be the first!