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.
๐ฏ 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)
- Open Package Manager in Unity
- Click "+" and select "Add package from git URL"
- Enter:
https://github.com/werdnajoc/CleanEventBus.git?path=CleanEventBus/Assets/CleanEventBus - 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
- Clone the repository
- Open in Unity 2021.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
- Repository: GitHub
- Issues: Report bugs or request features
- Author: Andrew Ortiz
๐ก 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? ๐