Search Unity

Question Is Global Event Bus pattern a good practice ?

Discussion in 'General Discussion' started by AlexuzZz, Jan 18, 2024.

  1. AlexuzZz

    AlexuzZz

    Joined:
    Aug 11, 2023
    Posts:
    11
    Hello everyone, I have been using Global Event Bus for a while, and I didn't notice that other developers are also implementing that pattern in the projects.
    So I am wondering if there is a better practice.
    Here is the GitHub link to one of the projects, where I used it - *click*.
    I would appreciate it if you could help me.
     
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,563
    In context of unity it is seen as overengineering by portion of the developers. Event bus is a singleton and appears to be an attempt to cram all communication into the same mechanism. Is it even necessary?

    Regarding your repository. Because you key messages by type, you have duplicate code. DecayingTimeUpgrade, GrowingTimeUpgrade, WalkingSpeedUpgrade.

    This issue repeats into ChangeMapSizeText, MapManager and MoneyTextChanger. Because you key by type, you have identical subscribe/unsubscribe code which is error-prone.

    Based on what I see in that repository, I would remove message bus and alter the code so no duplication occurs. If you have a repeating pattern where something subscribes/unsubscribes, then what it subscribes to should become a single parameter. You end up duplicating code over and over, which means MessageBus is not helping you.

    Regarding events like change of money, I'd have UI subscribe to player's wallet if I were implementing it through events. Without middlemen.

    I also question the logic of subscribing the type. Effectively that treats type as value. But why would you do that when a single function is already a value or a single type could have multiple events?
     
    CodeSmile, AlexuzZz and CodeRonnie like this.
  3. AlexuzZz

    AlexuzZz

    Joined:
    Aug 11, 2023
    Posts:
    11
    Alright, actually that was my very first project, but I only uploaded this one on GitHub, therefore there are a lot of mess. Thank you so much for your reply ! So... instead of event bus I just should use C#/ Unity events ? Will it give the same result ? Regarding your question, I am not exactly sure how it works under the hood, since I found realisation of this pattern on internet.
     
  4. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,563
    I can only say what I would use. Not what you should. Because there's more than one way to do things.

    I follow KISS (Keep it Simple), YAGNI (You ain't gonna need it) and to an extent DRY. And prefer defensive programming if it is possible in the language.

    YAGNI means I'd remove things that are not immediately useful. KISS means I'd try to use simple solution, unless the need arises. DRY means that repeating code patterns should be collapsed into functions, BUT "to an extent" means "when this is necessary".

    This is a fairly robust approach. The idea is to save time. Duplicated code wastes time when you need to alter it. That's why you use DRY. Classes you do not immediately need eat your development time, and add maintenance time. That's why you implement only what's needed. Which is YAGNI. Complex solution brings maintenance/development time up, that's why you do not needlessly complicate things. Which is KISS. The logic here is that programmers make mistakes and get distracted by interesting, "shiny" problems. This approach keeps your grounded.

    Obviously, there's no ideal appraoch, and this too, can backfire. Overuse of KISS may result in code rewrite when you find that you NEED complex solution later, Not all "not immediately useful" things are useless and so on. But this is generally a useful approach. For me.

    I haven't launched your project and only skimmed through parts of codebase to see what you're doing with the message bus. As far as I can tell, you're using it to notify about upgrade and to notify about cash change. In my opinion, cash change should normally be handled by an UI, and UI can subscribe directly to the player's wallet, bypassing the bus.

    Bus could emerge, however, when I have MULTIPLE sources generating messages that multiple things listen too. For example, a possible (not ideal) example is a chat on a map. Multiple entities speak, but there's hearing range. So they spawn messages with range and position and feed t hem to the bus people listen to. But this is not an idieal example, because you can bypass the use of the bus here as well. You can implement function "speak" and let that query and notify listeners.

    Something like that.

    I'd recommend not to use things you find on the internet blindly. If something says that "this technique is good", it is a good to understand/question WHY. So when you're using something, you should understand why are you using it, and what's the benefit. And what's the tradeoff.

    At least that's my opinion.
     
    ippdev, NotaNaN, CodeRonnie and 3 others like this.
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,812
    I set up something like this some time ago, though under the moniker of a 'global event system'. Really as a way to issue 'system wide' events, being able to bubble up events from lower level assemblies to ones higher up.

    Though it's main use was to send a variety of events to a large variety of listers, with a strongly typed value passed through. Bringing me to...
    It's not quite being used in OP's example, but I used the Type of the event in my global event system, which allowed for a way to raise events and send through a strongly-typed parameter.
    Code (CSharp):
    1. // somewhere
    2. GlobalEvents.RegisterEvent<PlayerDeathEvent>(OnPlayerDeath);
    3.  
    4. // somewhere else
    5. PlayerDeathEvent pde = new PlayerDeathEvent(timeOfDeath, locationOfDeath);
    6. GlobalEvents.RaiseEvent<PlayerDeathEvent>(pde);
    The generic parameter wasn't constrained either. Could be whatever type I wanted.

    That said I haven't used the system that much, at least not in the earlier part of my current project. So maybe a bit of a YAGNI moment, but still useful.

    In a more general sense, I know that some games relied on a global event system too much, and it became a performance bottleneck. Kerbal Space Program (1 & 2) for example. With that in mind, probably something to use sparingly.
     
    Last edited: Jan 19, 2024
    CodeRonnie likes this.
  6. AlexuzZz

    AlexuzZz

    Joined:
    Aug 11, 2023
    Posts:
    11
    I got the problems I have, thank you so much for that descriptive answer. Having all those problems with bus, is that still a good idea to put the project in my junior unity programmer cv ?
     
  7. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,563
    The thing is, I don't see the point. In your example PlayerDeathEvent is effectively a constant. Why not just make it a constant? Why not directly subscribe to events held in player instance? There are tons of other questions like that.

    It could be another FizzBuzz, though. Meaning, matter of preference.

    Whoever's browsing the repo will be able to gauge your overall skill level. So it is useful for that purpose. The code is not awful, but someone could begin to ask questions. That's the extent of it. At least that's how I see it.
     
    AlexuzZz likes this.
  8. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    Global event can be an easier way for multi-scene environment.
    ScripableObject-events would also be an option, but I think that can quickly become confusing with larger projects.
     
    AlexuzZz likes this.
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,812
    Yeah sometimes it's a pain to get the right reference to something, save using yet another singleton.

    Also useful when multiple things can invoke the same event, and multiple different things might be listening to said event.

    The decoupling also means that listeners don't really need to care about what's raising the events. The underlying implementation could drastically change and listeners don't need to care. Though this is true in a number of event patterns.

    But definitely down to preference. Though a surprising amount of depth to a single design pattern.
     
    CodeRonnie and AlexuzZz like this.
  10. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    529
    I agree with some of the things that have already been said. Usually I prefer specific solutions to specific problems. However, I've also had that feeling that I should have an abstract event system to implement the observer pattern application-wide in a very general way. But, then the practical question usually becomes, which objects are even using this generalized event system, and why?

    However, there can be scenarios when it makes sense. If multiple sources of different types could fire an event, and/or many different types of observers all need to receive it. It might be easier to add an event to the existing messaging service, than make a whole new system.

    Also, when you are working on a team with developers who aren't going to whip up complex custom code solutions, it can help them add valuable content to the game without tasking an engineer. But, the use, or abuse, of those types of generalized tools by the team is a whole other topic in itself.

    There's nothing inherently wrong with having such a system, and creating one is an accomplishment you should be proud of, but you may or may not actually need it if you are customizing the architecture to the end goal.
     
    Last edited: Jan 20, 2024
  11. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,769
    Interestingly I was working few years ago in ROS Robot Operating System.
    It implements messages subscriber and listener pattern, which allows high extendibility of many robotic addons.
    It is very functional approach to link many independent systems, which are developed by various developers.
    Doesn't matter what system are doing, as long they can communicate on common messaging bus.

    Unity GameObject have messaging methods. But I don't remember using them at any point.
    For clear patterns, probably don't want to pollute 1000s of messages in same space.
    Namespaces and Scopes allow to easier filter such. And also is easier to debug in enclosed scenario environment.
     
    AlexuzZz and spiney199 like this.
  12. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,563
    Animation. Animation systems uses messaging, when you add triggers to the clips.
     
    Antypodish likes this.
  13. akuno

    akuno

    Joined:
    Dec 14, 2015
    Posts:
    88
    I'm also surprised that I dont see it being used more often. I find it to be very helpful.
     
  14. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,697
    To put it in short words why it is not a much used technique:
    It's very easy to loose the overview of what is communicating with what.
     
  15. AlexuzZz

    AlexuzZz

    Joined:
    Aug 11, 2023
    Posts:
    11
    Well, I found a decent alternation for almost everything where I used the EventBus.
    Here is a script from my newly created project
    The sender looks like this :
    Code (CSharp):
    1. public event EventHandler<Vector2> OnTouchStarted, OnTouchPerformed, OnTouchEnded;
    2.  
    3. OnTouchStarted?.Invoke(this, _inputController.PlayerMovement.PrimaryPosition.ReadValue<Vector2>());
    And receiver looks like this :
    Code (CSharp):
    1. using UnityEngine;
    2. public class PlayerOverviewRotation : MonoBehaviour
    3. {
    4.    [SerializeField] private InputProcessor _inputProcessor;
    5.    [SerializeField] private Rigidbody _playerRigidbody;
    6.  
    7.    [SerializeField] private float _rotationSensitivity;
    8.  
    9.    private float _startTouchPositionX, _currentTouchPositionX;
    10.    private void OnEnable()
    11.    {
    12.       _inputProcessor.OnTouchStarted += OnTouchStarted;
    13.       _inputProcessor.OnTouchPerformed += OnTouchPerformed;
    14.    }
    15.    private void OnDisable()
    16.    {
    17.       _inputProcessor.OnTouchStarted -= OnTouchStarted;
    18.       _inputProcessor.OnTouchPerformed -= OnTouchPerformed;
    19.    }
    20.  
    21.    private void OnTouchStarted(object sender, Vector2 position) => _startTouchPositionX = position.x;
    22.  
    23.    private void OnTouchPerformed(object sender, Vector2 position)
    24.    {
    25.       _currentTouchPositionX = position.x;
    26.      
    27.       float touchPositionXDifference = _startTouchPositionX - _currentTouchPositionX;
    28.        
    29.       _playerRigidbody.AddTorque(0 , touchPositionXDifference / _rotationSensitivity, 0, ForceMode.Acceleration);
    30.    }
    31. }
    32.  
     
  16. akuno

    akuno

    Joined:
    Dec 14, 2015
    Posts:
    88
    I used to think that was the case. But you can simply make a tag/enum to each event and literally find all occurrences of it to know who can trigger the event and who can listen to it. You can also add logging with filters to see only selected events, then see the stack call of where it is being called.
     
  17. PizzaPie

    PizzaPie

    Joined:
    Oct 11, 2015
    Posts:
    106
    When I've found about this pattern, it seemed like the best thing, a panacea, eventually developed an (over engineered!?) implementation* of it and started using it (alot of times mindlessly).

    From a top down perspective it's quite promising, it decouples everything and makes it extremely easy to pass data everywhere.
    Second part is the biggest issue, you stop thinking about data flow and start passing stuff everywhere around "mindlessly" which for any project of medium size and above eventually becomes a nightmare, sure it is easy to track down who listens and invokes what but there will be no consistency and no clear execution flow.

    In the end, I find this pattern quite easy to abuse which results on a mess, sure it is good for global events (game exits or stuff like that) but you need to setup clear rules for when you gonna use it and have clear vision of execution flow. Especially when working with a team (the atrocities I've seen still give me nightmares).

    *: implementation has bugs in it that never bothered upload a fix for, use with caution (ex. unsub loop should go in reverse to allow unsub on event invoke).
     
    CodeRonnie likes this.
  18. Glader

    Glader

    Joined:
    Aug 19, 2013
    Posts:
    456
    Yes, I wrote an EventBus library for C#/.NET that I use in Unity3D. Though I'd not call it global since it's injected and UI elements also have their own Bus they publish to as well. I've found it quite useful for writing the MMORPG I've been working on for many years. I was inspired by how World of Warcraft handles a lot of their clientside UI scripting which they do by raising events which many LUA scripts listen for: https://wowwiki-archive.fandom.com/wiki/Events_A-Z_(full_list)
     
    PanthenEye and CodeRonnie like this.