# How Unciv's EventBus Works for Game Notifications: A Technical Deep Dive

> Explore Unciv's EventBus a lightweight publish-subscribe system. Discover how it decouples game event generation from consumption through type-safe listeners. Learn event handling in Unciv.

- Repository: [Yair Morgenstern/Unciv](https://github.com/yairm210/Unciv)
- Tags: deep-dive
- Published: 2026-06-18

---

**Unciv's EventBus is a lightweight publish-subscribe singleton that decouples game event generation from consumption using a type-safe listener registry in [`core/src/com/unciv/logic/event/EventBus.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/event/EventBus.kt).**

The yairm210/Unciv codebase implements a custom event bus to handle game notifications without tight coupling between systems. This Kotlin-based implementation allows UI components, game logic, and multiplayer handlers to communicate through a centralized dispatcher. Understanding this mechanism reveals how the 4X strategy game manages state changes across its architecture.

## Core Architecture of the EventBus

### The Singleton Pattern Implementation

The EventBus operates as a Kotlin `object` singleton, ensuring exactly one global dispatcher exists per game session. This design eliminates synchronization concerns while providing universal access to event routing.

In [`core/src/com/unciv/logic/event/EventBus.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/event/EventBus.kt), the singleton maintains internal state through a mutable map that associates event types with their listeners:

```kotlin
object EventBus {
    private val listeners = mutableMapOf<KClass<out Event>, MutableList<(Event) -> Unit>>()
    // ...
}

```

### Type-Safe Listener Registry

The registry uses Kotlin's reified generics to maintain type safety across event subscriptions. The `addListener` method accepts a `KClass` reference and a lambda callback, storing them in the internal map:

```kotlin
fun <T : Event> addListener(eventClass: KClass<T>, listener: (T) -> Unit)

```

This implementation stores listeners in a `MutableMap<KClass<out Event>, MutableList<(Event) -> Unit>>`, allowing multiple handlers for a single event type.

## How Events Flow Through the System

### Registering Listeners with addListener

Components subscribe to notifications by calling `addListener` with the specific event class and a handler lambda. The bus supports polymorphic dispatch, meaning listeners registered for parent event classes receive child event notifications.

```kotlin
// In a UI screen or game logic component
EventBus.addListener(CityCapturedEvent::class) { event ->
    toast("City captured: ${event.cityName}")
}

```

### Posting Events via post()

When game systems generate notifications, they instantiate concrete `Event` subclasses and dispatch them through the `post` method. The bus looks up registered listeners by the event's runtime class and invokes each callback synchronously:

```kotlin
fun post(event: Event) {
    listeners[event::class]?.forEach { it(event) }
}

```

This synchronous execution ensures immediate state propagation, critical for turn-based game logic where sequence matters.

### Cleanup with removeAllListeners

To prevent memory leaks when UI screens dispose, the bus provides `removeAllListeners(owner)`, which removes all callbacks associated with a specific owner object:

```kotlin
override fun dispose() {
    EventBus.removeAllListeners(this)
}

```

## Event Types and Multiplayer Integration

### Base Event Class and Concrete Implementations

All game notifications extend the base `Event` class defined in [`core/src/com/unciv/models/ruleset/Event.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/models/ruleset/Event.kt). Concrete implementations carry specific payload data relevant to the notification type.

The JSON schema in [`docs/Modders/schemas/Events.schema.json`](https://github.com/yairm210/Unciv/blob/main/docs/Modders/schemas/Events.schema.json) defines the structure for moddable events, allowing community extensions without core code modification.

### Multiplayer-Specific Events

Multiplayer functionality leverages the same bus architecture through [`core/src/com/unciv/logic/multiplayer/OnlineMultiplayerEvents.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/multiplayer/OnlineMultiplayerEvents.kt). Events like `ReceiveTurn` travel through the standard bus, enabling uniform handling of network messages:

```kotlin
// OnlineMultiplayerEvents.kt defines events such as:
class ReceiveTurn(val gameId: String) : Event()

```

This consolidation allows both local and networked games to use identical notification patterns.

## Testing and Validation

The implementation includes comprehensive unit tests in [`tests/src/com/unciv/logic/event/EventBusTest.kt`](https://github.com/yairm210/Unciv/blob/main/tests/src/com/unciv/logic/event/EventBusTest.kt). These tests verify listener registration, event dispatch ordering, and proper cleanup:

```kotlin
@Test
fun testListenerReceivesEvent() {
    var received = false
    EventBus.addListener(TestEvent::class) { received = true }
    EventBus.post(TestEvent())
    assertTrue(received)
}

```

## Summary

- **Singleton Architecture**: The EventBus uses a Kotlin `object` singleton to provide global access while maintaining single-instance guarantees.
- **Type-Safety**: Generic `addListener` methods with `KClass` parameters ensure compile-time safety for event handlers.
- **Synchronous Dispatch**: Events process immediately via the `post` method, maintaining deterministic ordering for turn-based gameplay.
- **Memory Management**: The `removeAllListeners` method prevents leaks by allowing components to deregister during disposal.
- **Multiplayer Unification**: Network events use the same infrastructure as local events, simplifying cross-platform logic.

## Frequently Asked Questions

### How does Unciv's EventBus prevent memory leaks in UI components?

The `removeAllListeners(owner)` method in [`core/src/com/unciv/logic/event/EventBus.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/logic/event/EventBus.kt) accepts an owner object reference and removes all listeners associated with that instance. UI screens call this in their `dispose()` lifecycle method, ensuring callbacks don't retain references to destroyed views.

### Can mods create custom events using the EventBus?

Yes, mods can define custom events through the JSON schema specified in [`docs/Modders/schemas/Events.schema.json`](https://github.com/yairm210/Unciv/blob/main/docs/Modders/schemas/Events.schema.json). These events extend the base `Event` class from [`core/src/com/unciv/models/ruleset/Event.kt`](https://github.com/yairm210/Unciv/blob/main/core/src/com/unciv/models/ruleset/Event.kt) and integrate with the bus using the same `addListener` and `post` methods as core events.

### Are EventBus listeners executed on a background thread?

No, listeners execute synchronously on the same thread that calls `post(event)`. This design ensures immediate consistency for game state changes, which is essential for turn-based mechanics where event ordering affects gameplay logic.

### What is the performance overhead of the EventBus implementation?

The implementation uses a simple `MutableMap` lookup by `KClass` key, providing O(1) complexity for listener retrieval. Since the bus avoids reflection during dispatch and uses direct lambda invocation, overhead remains minimal even with frequent game notifications.