GitHub - werdnajoc/CleanEventBus: Clean EventBus is an advanced messaging system designed specifically for Unity projects that follow Clean Architecture patterns. Unlike traditional Unity event systems, it provides:

A layered, type-safe event bus system for Unity implementing Clean Architecture principles with context-aware targeting.

Perfect for developers who want to build maintainable, decoupled Unity applications without sacrificing performance.

Unity License Tests Performance


๐ŸŽฏ What is Clean EventBus?

Clean EventBus is an advanced messaging system designed specifically for Unity projects that follow Clean Architecture patterns. Unlike traditional Unity event systems, it provides:

  • Layer separation - Different event buses for Domain, Application, and Adapter layers
  • Type safety - Prevents cross-layer pollution and ensures architectural integrity
  • Smart targeting - Events can be directed to specific instances using context
  • Zero configuration - Works out of the box with intuitive APIs

Why Clean EventBus?

// โŒ Traditional approach - tight coupling
public class StoreView : MonoBehaviour 
{
    public StoreManager storeManager;  // Direct reference
    
    void OnUpgradeClick() 
    {
        storeManager.UpgradeStore(storeId);  // Tightly coupled
    }
}

// โœ… Clean EventBus approach - decoupled
public class StoreView : MonoBehaviour 
{
    void OnUpgradeClick() 
    {
        eventBus.Publish(new StoreUpgradeRequested(storeId));  // Decoupled
    }
}

โšก Features

๐Ÿ—๏ธ Clean Architecture Ready

  • Domain EventBus - For business logic events
  • Application EventBus - For use case coordination and UI updates
  • Adapter EventBus - For infrastructure operations
  • Zero cross-layer pollution - Each layer stays clean

๐ŸŽฏ Context-Aware Targeting

// Only the specific store receives this event
using (EventContext.SetContext("store_123"))
{
    eventBus.Subscribe<StoreUpdated>(OnStoreUpdated);
}

eventBus.Publish(new StoreUpdated("store_123")); // Only goes to store_123

๐Ÿš€ High Performance

  • Sub-microsecond latency per callback
  • Minimal memory allocation (<200 bytes per subscription)
  • Efficient targeting - No unnecessary callbacks
  • Thread-safe operations

๐Ÿ›ก๏ธ Type Safety

// โœ… This works - same layer
appEventBus.Publish(new StoreUIUpdated());

// โŒ This won't compile - cross-layer
appEventBus.Publish(new StoreDomainEvent()); // Compile error!

๐Ÿ“ Simple API

  • Familiar syntax - Similar to C# events but better
  • IntelliSense friendly - Clear autocomplete
  • Minimal boilerplate - Just inherit from base classes

๐Ÿ“ฆ Installation

Via Package Manager (Git URL)

  1. Open Package Manager in Unity
  2. Click "+" and select "Add package from git URL"
  3. Enter: https://github.com/werdnajoc/CleanEventBus.git?path=CleanEventBus/Assets/CleanEventBus
  4. Click "Add"

Via manifest.json

Copy And Past this line to your Packages/manifest.json:

"com.werdnajoc.clean-event-bus": "https://github.com/werdnajoc/CleanEventBus.git?path=CleanEventBus/Assets/CleanEventBus",

Example

{
  "dependencies": {
    "com.werdnajoc.clean-event-bus": "https://github.com/werdnajoc/CleanEventBus.git?path=CleanEventBus/Assets/CleanEventBus",
    // ... other dependencies
  }
}

Requirements

  • Unity 2021.3 or higher
  • .NET Standard 2.1 compatible

๐ŸŽฎ Use Cases & Examples

1. Basic Store Management System

using CleanEventBus.Application;
using CleanEventBus.Application.Events;

// Define your events
[TargetedEvent("StoreId")]
public class StoreUpgraded : BaseApplicationEvent
{
    public string StoreId { get; set; }
    public int NewLevel { get; set; }
    public decimal Cost { get; set; }
}

// In your View (UI)
public class StoreView : MonoBehaviour
{
    [SerializeField] private string storeId;
    private IEventBusApplication eventBus;
    
    void Start()
    {
        eventBus = new ApplicationEventBus();
        
        // Subscribe to events for this specific store
        using (EventContext.SetContext(storeId))
        {
            eventBus.Subscribe<StoreUpgraded>(OnStoreUpgraded);
        }
    }
    
    void OnUpgradeButtonClick()
    {
        // Publish upgrade request
        eventBus.Publish(new StoreUpgradeRequested(storeId));
    }
    
    void OnStoreUpgraded(StoreUpgraded evt)
    {
        // Only this store receives this event!
        ShowUpgradeEffect(evt.NewLevel);
        UpdateUI();
    }
}

// In your ViewModel/UseCase
public class UpgradeStoreUseCase
{
    private readonly IEventBusApplication eventBus;
    
    public UpgradeStoreUseCase(IEventBusApplication eventBus)
    {
        this.eventBus = eventBus;
        eventBus.Subscribe<StoreUpgradeRequested>(HandleUpgrade);
    }
    
    void HandleUpgrade(StoreUpgradeRequested request)
    {
        // Business logic here
        var store = storeService.UpgradeStore(request.StoreId);
        
        // Notify UI of success
        eventBus.Publish(new StoreUpgraded(request.StoreId)
        {
            NewLevel = store.Level,
            Cost = store.UpgradeCost
        });
    }
}

2. Clean Architecture Example

// Domain Layer - Business Events
using CleanEventBus.Domain;
using CleanEventBus.Domain.Events;

public class PlayerLeveledUp : BaseDomainEvent
{
    public string PlayerId { get; set; }
    public int NewLevel { get; set; }
    public int ExperienceGained { get; set; }
}

// Application Layer - UI Coordination
using CleanEventBus.Application;
using CleanEventBus.Application.Events;

[TargetedEvent("PlayerId")]
public class PlayerUIUpdateRequired : BaseApplicationEvent
{
    public string PlayerId { get; set; }
    public int Level { get; set; }
    public int Experience { get; set; }
    public List<string> UnlockedFeatures { get; set; }
}

// Usage in Domain Service
public class PlayerService
{
    private readonly IEventBusDomain domainEventBus;
    
    public void GainExperience(string playerId, int experience)
    {
        // Domain logic...
        
        if (playerLeveledUp)
        {
            domainEventBus.Publish(new PlayerLeveledUp 
            { 
                PlayerId = playerId,
                NewLevel = newLevel,
                ExperienceGained = experience
            });
        }
    }
}

// Application Layer - Coordinate between Domain and UI
public class PlayerApplicationService
{
    private readonly IEventBusDomain domainEventBus;
    private readonly IEventBusApplication appEventBus;
    
    public PlayerApplicationService(IEventBusDomain domainEventBus, IEventBusApplication appEventBus)
    {
        this.domainEventBus = domainEventBus;
        this.appEventBus = appEventBus;
        
        // Listen to domain events and coordinate UI updates
        domainEventBus.Subscribe<PlayerLeveledUp>(OnPlayerLeveledUp);
    }
    
    void OnPlayerLeveledUp(PlayerLeveledUp domainEvent)
    {
        // Coordinate UI update
        appEventBus.Publish(new PlayerUIUpdateRequired(domainEvent.PlayerId)
        {
            Level = domainEvent.NewLevel,
            Experience = GetPlayerExperience(domainEvent.PlayerId),
            UnlockedFeatures = GetUnlockedFeatures(domainEvent.PlayerId)
        });
    }
}

3. Multiple Store Instances Example

// You have 10 store prefabs in your scene
// Each one has a unique storeId: "store_1", "store_2", etc.

// When you publish this event:
eventBus.Publish(new StoreUpgraded("store_5") { NewLevel = 3 });

// Only the store with storeId "store_5" will receive it
// The other 9 stores won't be notified - efficient!

4. Global vs Targeted Events

// Global event - all subscribers receive it
public class GamePaused : BaseApplicationEvent
{
    public bool IsPaused { get; set; }
}

eventBus.Publish(new GamePaused { IsPaused = true });
// All UI elements pause

// Targeted event - only specific subscriber receives it  
[TargetedEvent("DialogId")]
public class DialogClosed : BaseApplicationEvent
{
    public string DialogId { get; set; }
    public DialogResult Result { get; set; }
}

eventBus.Publish(new DialogClosed("settings_dialog") { Result = DialogResult.OK });
// Only the settings dialog handler receives this

๐Ÿ“Š Performance Test Results

Our comprehensive test suite ensures Clean EventBus performs excellently in production scenarios:

โšก Speed Benchmarks

โœ… Subscribe 1000 events: <100ms
โœ… Publish to 1000 subscribers: <50ms  
โœ… Targeted event delivery: <1ms (even with 1000+ potential targets)
โœ… EventBus overhead: ~12x vs direct calls (comparable to Unity Events)

๐Ÿ’พ Memory Efficiency

โœ… Average memory per subscription: <200 bytes
โœ… Single subscription allocation: <500 bytes
โœ… Memory per publish operation: <1KB

๐ŸŽฏ Targeting Accuracy

โœ… 100% accuracy - targeted events only reach intended recipients
โœ… Zero wrong callbacks - other instances never receive targeted events
โœ… Efficient routing - no unnecessary processing for non-target instances

๐Ÿงช Stress Testing

โœ… Handles 10,000+ subscribers without issues
โœ… Processes large payloads (1MB+) efficiently  
โœ… Thread-safe concurrent operations
โœ… Rapid subscribe/unsubscribe cycles (1000+ cycles)

๐Ÿ“ˆ Cache Performance

โœ… Metadata caching provides 2-3x performance improvement on subsequent calls
โœ… Property access optimization via compiled expressions
โœ… Zero reflection overhead after warmup

๐Ÿ—๏ธ Architecture Benefits

Clean Separation

// Each layer has its own event bus - no mixing!
using CleanEventBus.Domain;     // Business logic events IF needed
using CleanEventBus.Application; // For use cases publish events
using CleanEventBus.Adapter;   // For view models, controllers, presenters

Type Safety Enforcement

// โœ… This compiles
domainEventBus.Publish(new PlayerLeveledUp());

// โŒ This doesn't compile - prevents architectural violations
domainEventBus.Publish(new UIButtonClicked()); // Compile error!

Testability

// Easy to mock and test
var mockEventBus = new Mock<IEventBusApplication>();
var useCase = new MyUseCase(mockEventBus.Object);

// Verify events were published
mockEventBus.Verify(x => x.Publish(It.IsAny<MyEvent>()), Times.Once);

๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Development Setup

  1. Clone the repository
  2. Open in Unity 2021.3+
  3. Run tests in Test Runner (Window โ†’ General โ†’ Test Runner)

Running Tests

Window โ†’ General โ†’ Test Runner โ†’ PlayMode โ†’ Run All

All performance tests should pass with the benchmarks listed above.


๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


๐Ÿ”— Links


๐Ÿ’ก Tips for Success

Start Simple

Begin with Application layer events for UI coordination, then expand to Domain and Adapter layers as needed.

Use Descriptive Names

// โœ… Good
public class StoreUpgradeCompleted : BaseApplicationEvent

// โŒ Less clear  
public class StoreEvent : BaseApplicationEvent

Leverage Targeting

Use targeted events for instance-specific operations, global events for system-wide notifications.

Keep Events Immutable

Design events as data containers - avoid business logic in event classes.


Ready to build cleaner, more maintainable Unity applications? ๐Ÿš€

Get started now โ€ข View Examples โ€ข Report Issues