> For the complete documentation index, see [llms.txt](https://docs.parsue.io/aceland-unity-packages/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.parsue.io/aceland-unity-packages/core-packages/event-driven.md).

# Event Driven

Type-safe EventBus + reactive Signals for modern Unity.

[![Open Runtime, Paid Editor Tools](https://img.shields.io/badge/Open%20Runtime,%20Paid%20Editor%20Tools-C24B42)](https://docs.parsue.io/aceland-unity-packages/open-and-paid/open-core-and-paid-editor-tools) ![CoreCLR Ready](https://img.shields.io/badge/CoreCLR-Ready-4A5BC2)

[![Sponsor](https://img.shields.io/badge/Sponsor-%E2%9D%A4-db61a2?logo=githubsponsors\&logoColor=white)](https://github.com/sponsors/parsue) [![Discord](https://img.shields.io/badge/Discord-Join-5865F2?logo=discord\&logoColor=white)](https://discord.gg/XsCYGnYzuc) [![Github](https://img.shields.io/badge/github-repo-blue?logo=github)](https://github.com/parsue/com.aceland.eventdriven)

### In One Line

Talk in types, not in strings.

### Overview

AceLand Event Driven is a **zero-dependency Core framework** for Unity that gives you two independent, decoupled messaging tools:

* **Event Bus** — a strongly-typed, global publish/subscribe bus where **an event is just an interface**. Registration is produced by a **Source Generator at compile time**, so startup does zero reflection and zero assembly scanning (friendly to Unity 7 / CoreCLR and the "no domain reload" workflow).
* **Event Signal** — lightweight, id-based reactive values you can create anywhere at runtime, then hand out as narrow "capability" views (listen-only, trigger-only, read-only, linker).

It also ships an **Editor Event Monitor** window so you can watch events, listeners, payloads and raise history live while you play.

{% hint style="info" %}
Two subsystems, one package. They share a single bootstrapper but never depend on each other — use one, the other, or both.
{% endhint %}

### package Info

<table data-header-hidden><thead><tr><th width="212.5999755859375"></th><th></th></tr></thead><tbody><tr><td>display name</td><td>AceLand Event Driven</td></tr><tr><td>package name</td><td>com.aceland.eventdriven</td></tr><tr><td>latest version</td><td>3.1.1</td></tr><tr><td>namespace</td><td>AceLand.EventDriven</td></tr><tr><td>git repository</td><td><a href="https://github.com/parsue/com.aceland.eventdriven.git">https://github.com/parsue/com.aceland.eventdriven.git</a></td></tr><tr><td>dependencies</td><td>none (zero-dependency Core Package)</td></tr></tbody></table>

***

### Why Use It

* **Strongly typed, no magic strings.** Events are interfaces. The compiler — not a runtime string lookup — tells you when something is wrong.
* **Zero reflection at startup.** A Roslyn Source Generator writes the registration code at compile time. No `AppDomain` scan, no `ReflectionTypeLoadException`, no domain-reload reliance.
* **Zero dependency.** The runtime assembly references nothing else. Drop it into any project without dragging a dependency tree along.
* **Sticky events built in.** Cache the last raise and let late subscribers catch up instantly with `KickStart()`.
* **Least-privilege by design.** Signals expose narrow interfaces so a consumer only gets the capability it needs (listen, trigger, or read).
* **See it live.** The Editor Event Monitor shows every event, its listeners, payloads and raise history — including the calling script and line.

***

### How It Works

```mermaid
flowchart TD
    GEN[Source Generator at compile time] --> BOOT[Per-assembly RuntimeInitialize bootstrap]
    BOOT --> STORE[EventBus generated signature store]
    INIT[RuntimeInitialize AfterAssembliesLoaded] --> BUILD[EventBus.Initialize builds services]
    STORE --> BUILD
    BUILD --> READY[Registry ready - zero reflection]
```

1. You declare an event as an interface implementing `IEvent` or `IEvent<T>`.
2. At **compile time**, the Source Generator finds it, validates the contract, and emits a tiny `[RuntimeInitializeOnLoadMethod]` bootstrap that registers the event signature.
3. At **runtime startup**, `EventBus.Initialize()` snapshots those pre-generated signatures into a registry — no reflection, no scanning.
4. From then on, `EventBus.Event<T>()` publishes and subscribes against that strongly-typed registry, optionally caching the last payload for late subscribers.

Signals are simpler: they are created on demand through a builder, stored in an id-keyed registry, and observed through capability interfaces.

***

### Why It's Different

Most Unity event systems make you choose between convenience and safety. AceLand Event Driven refuses that trade-off:

<table><thead><tr><th width="138.4000244140625">-</th><th width="143.7999267578125">Typical string/enum event bus</th><th width="158.199951171875">Reflection-scanning frameworks</th><th>AceLand Event Driven</th></tr></thead><tbody><tr><td>Type safety</td><td>❌ magic strings, runtime failures</td><td>⚠️ attributes, discovered late</td><td>✅ <strong>interfaces, compile-time checked</strong></td></tr><tr><td>Startup cost</td><td>cheap but fragile</td><td>❌ <code>AppDomain</code> scan + reflection</td><td>✅ <strong>zero reflection, source-generated</strong></td></tr><tr><td>Domain-reload / CoreCLR</td><td>relies on reload</td><td>❌ <code>ReflectionTypeLoadException</code> risk</td><td>✅ <strong>no reload reliance, Unity 7 ready</strong></td></tr><tr><td>Late subscribers</td><td>manual bookkeeping</td><td>varies</td><td>✅ <strong>built-in sticky cache + <code>KickStart()</code></strong></td></tr><tr><td>Dependencies</td><td>varies</td><td>often heavy</td><td>✅ <strong>zero dependency</strong></td></tr></tbody></table>

Because registration is generated at **compile time**, the cost you'd normally pay at startup simply isn't there — and mistakes surface in your IDE, not in a play-test.

***

### Quick Start

#### Event Bus

{% tabs %}
{% tab title="1. Define" %}

```csharp
using AceLand.EventDriven.Bus;

// An event is just an interface.
// IEvent<T> = one payload; the single method must return void and take exactly T.
[EventDescription("Player Scored")]   // optional: shown in the Event Monitor
public interface IPlayerScored : IEvent<int>
{
    void OnPlayerScored(int score);
}
```

{% endtab %}

{% tab title="2. Listen" %}

```csharp
using AceLand.EventDriven.Bus;
using UnityEngine;

public class ScoreBoard : MonoBehaviour, IPlayerScored
{
    private void OnEnable()  => EventBus.Event(this).Listen();
    private void OnDisable() => EventBus.Event(this).Unlisten();

    // Called automatically whenever IPlayerScored is raised.
    public void OnPlayerScored(int score) =>
        Debug.Log($"Score is now {score}");
}
```

{% endtab %}

{% tab title="3. Raise" %}

```csharp
using AceLand.EventDriven.Bus;

public class Game
{
    public void AddPoint(int score)
    {
        // Raise without cache:
        EventBus.Event<IPlayerScored>()
            .WithData(score)
            .Raise();

        // Or raise AND remember the last value (sticky):
        EventBus.Event<IPlayerScored>()
            .WithData(score)
            .WithCache()
            .Raise();
    }
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
Want late subscribers to immediately receive the last cached value? Call `EventBus.Event(this).Listen().KickStart().Done();`
{% endhint %}

#### Event Signal

{% tabs %}
{% tab title="Create & Read" %}

```csharp
using AceLand.EventDriven.EventSignal;

// Create (or get) a signal by id.
var health = Signal<int>.GetOrCreate("player.health");
health.Value = 100;

// React to changes.
health.AddListener(v => Debug.Log($"Health = {v}"));
health.Value = 80;   // listener fires with 80
```

{% endtab %}

{% tab title="Capability Views" %}

```csharp
using AceLand.EventDriven.EventSignal;

var signal = Signal<int>.GetOrCreate("player.health");

// Hand out only what a consumer needs:
IReadonlySignal<int> readOnly = signal.AsReadonly();   // can read + listen
ISignalListener<int> listener = signal.AsListener();   // can only listen
ISignalTrigger<int>  trigger  = signal.AsTrigger();    // can only set/trigger
```

{% endtab %}
{% endtabs %}

***

### Other Sections

* [Event Bus](/aceland-unity-packages/core-packages/event-driven/event-bus.md) — event contract, publishing, subscribing, cache & KickStart, clearing cache.
* [Event Signal](/aceland-unity-packages/core-packages/event-driven/event-signal.md) — signals, builders, getters, capability views, and the Signal Linker.
* [Event Monitor](/aceland-unity-packages/core-packages/event-driven/event-monitor.md) — the Editor window, raise history, and Project Settings.

***

### Best Practices

* Declare one method, returning `void`, on every event interface. `IEvent` = 0 parameters; `IEvent<T>` = exactly one parameter of type `T`.
* For `MonoBehaviour` listeners, `Listen()` in `OnEnable` and `Unlisten()` in `OnDisable`.
* Use `WithCache()` only for state-like "last value" events; skip it for pure one-shot signals.
* Prefer `[EventDescription("...")]` so events read clearly in the Event Monitor.
* Expose Signals as the narrowest capability view a consumer needs (`AsReadonly` / `AsListener` / `AsTrigger`) instead of the full `ISignal`.
* Give Signals stable, namespaced ids (e.g. `player.health`) — or use an `enum` id overload to avoid typos.
* Keep the event-declaring assembly referencing `AceLand.EventDriven` so the Source Generator runs and the event gets registered.

***
