Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Do I need an event/message system or do I have the wrong mindset?

Discussion in 'Entity Component System' started by peaj_metric, Sep 14, 2020.

  1. peaj_metric

    peaj_metric

    Joined:
    Sep 15, 2014
    Posts:
    145
    For previous projects I was heavily relying on a typesafe event system similar to this one http://thatgamesguy.co.uk/articles/typesafe-unity-event-system/

    Moving forward I was thinking about how to implement a similar system for ECS.
    But I figured I first have to answer the following question: Do I really need it?

    I guess it is best to evaluate the need for an event system with a proper real life example.
    We are currently using events for Quest, Tutorials, Stats, Achievements and UI as most of them are decoupled from the game itself and just need to be informed about specific events.

    Lets take a look at quests as they currently work:

    > Quest 'Kill 10 enemies' is enabled
    > Quest adds listener for KillEnemyEvent
    > Enemy death triggers KillEnemyEvent
    > Quest 'Kill 10 enemies' recieves event
    > Quest updates kill counter
    > Quest disables if counter reached 10 (and possibly triggers a quest finished event)

    Besides the quest system the achievement system can easily listen to the same event and may unlock the 'killed 100 enemies' achievement later on.
    The tutorial system might also listen to it to check wether you learned to kill the first enemy.

    So questions:
    1. How would you go about implementing this in ECS?
    2. Should I build an ECS campatible event system (thinking about pushing and polling events for async reasons)
    3. How would I communicate with non ECS code e.g. UI?
     
    florianhanke likes this.
  2. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    So yeah I may as well chime in since I wrote that event system.

    Firstly, you definitely do not need an event system; In fact I'm not even sure it makes a lot of sense from a data driven design and it is very easy to abuse and over use it.

    That said I do find it very convenient for certain situations, some of which you have mentioned in the original post. Things like analytics, data tracking, debugging etc I have found it to be incredibly useful for.
     
    unity-freestyle likes this.
  4. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    909
    Right now, I don't think ECS needs a generic based event system for what you want to do.
    ECS is already pretty message/event like with queries and such.

    Boilerplate code for creating and receiving messages is not much. Most of it, you would design anyway, like data, ForEach queries/EntityQueries, what code needs to run when or writing the specific trigger points.

    For your example:
    - an entity with an IComponent "KillQuestData" with amount, enemy type,etc...
    - a KillQuest system
    - in a system where you check for health or destroy entities, trigger the kill event. this could happen as newly created entity with IComp (killedBy, enemyType) as a Queue/List that lives inside the KillQuest system or as buffer on your player entity. (all those have different performance characteristics, new entities are fastest I think)
    - The killQuest system loops through the active KillQuestData and updates with the event data
    - when done, create an entity with QuestComplete which can be run through a QuestSystem that handles updates and completion.

    UI data can be a MB that have reference to their entity and update their relevent data with EntityManager. UI runs on main thread so it's not a problem in updating. I wouldn't bother with ECS UI yet. Pretty experimental with pure ECS and probably not much gain for performance. Unless you go CRAZY with UI, which you don't seem to.
     
  5. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    Here's my counter argument.

    Entity event performance is extremely poor in comparison. Don't believe me?

    upload_2020-9-15_7-52-19.png

    1million events, 1200ms vs 3.5ms
    10k events, 8ms vs 0.1ms

    Now why could you possible need so many events per frame?
    Like everything DOTS, high performance allows you to do so much more.

    For example, recently I made it so every node on my AI graph for every entity, fires an event off which allows me to track the exact the state of the AI in my game.
    I use this data to allow real time viewing of the decisions AI is making but I also record it allowing me to view the last 30 seconds of how the AI traversed the graph and made its decisions.
    This has turned out to be incredible for debugging. AI did something unexpected? Pause unity and play back the graph, see the states it entered and why.
    (I actually hope to put up a post about this sometime next week.)

    -edit-

    i also should say the performance of my event system has degraded a little recently due to the addition back of the deterministic mode. I'm working on some fixes that should double the performance back to where it was a couple of months ago - 1mill events in under 2ms.
     
    Last edited: Sep 14, 2020
    NotaNaN, Krajca, jdtec and 1 other person like this.
  6. peaj_metric

    peaj_metric

    Joined:
    Sep 15, 2014
    Posts:
    145
    Thanks for the input. The problem I have with this is that it tightly couples systems. If I communicate via entities (KillComponent) I have to know there is a system that reacts to it and cleans it up afterwards. But if I want my Quest and Achievement system to react to it who cleans it up? And what happens if nothing reacts to it at all?
    If I have a buffer/queue/list either on the player entity or on the Quest System I have to explicitly set it from the code that kills the enemy. Which will result in sth like:

    Code (CSharp):
    1. if(health <= 0)
    2. {
    3.   GetOrCreateSystem<AchievementSystem>().UnlockXYZ();
    4.   GetOrCreateSystem<AchievementSystem>().CurrentQuest.AddKill();
    5.   GetOrCreateSystem<UISystem>().ShowKillNotification();
    6. }
    I found your event system just after posting here.
    It looks really solid, but I am still trying to wrap my head around it.

    I guess I am still missing some knowledge concerning dependency handling and the 2 different approaches to writing jobified code (job struct or Entities.Foreach) confuse me. I thought the job structs would be replaced by the new lambda approach but it seems like they still live side by side.
    Also you explicitly mention "you should be familar with native streams" but I cannot find any good resources on it. The Unity docs dont even have simple method descriptions.

    Anyway if I use your system I will have to extend it in order to trigger 'lagacy' MonoBehavior code. So I have to understand it at least in part.
    I will definitely have a deeper look into it now.
     
  7. Chris-Herold

    Chris-Herold

    Joined:
    Nov 14, 2011
    Posts:
    115
    My over-engineering alarm went off reading through this thread.

    "But I figured I first have to answer the following question: Do I really need it?"
    Based on the examples you gave i can say with confidence that no - you don't need it.
    Don't lose track of what your goal is (a fun game). YAGNI (you aint gonna need it)

    1. How would you go about implementing this in ECS?

    Use entities as events, either one component per event type, or a single component for all and use a byte to distinguish. Entity creation is super fast, especially with premade archetypes.
    Create events via EntityCommandBuffer. Resolve & delete events with usual SystemBase implementation.

    2. Should I build an ECS campatible event system (thinking about pushing and polling events for async reasons)

    Unless you are certain that you'll be dealing with thousands or tens of thousands of events per frame there are more important things to spend your time on.

    3. How would I communicate with non ECS code e.g. UI?

    MonoBehaviour Update get EntityManager, GetComponentData, feed data to UI labels etc
     
    Last edited: Sep 15, 2020
  8. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    Completely random note about this.

    There is a bug in IL2CPP on at least 19.4, but I believe it still exists in 20.1 that NativeStream (and therefore my implementation) have on the iOS and PS4 platforms with ref returns. Therefore if you use them with monobehaviours you might want to considering wrapping it in a burst job running on main thread or function pointer anyway (as burst does not have the issue.)

    This issue does not seem to exist for IL2CPP for windows, android or xbox.
     
    Last edited: Sep 15, 2020
  9. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    I actually hate this approach, it has caused us so much pain and bugs in one of our early projects.
    You end up basically having to wrap everything in if(EntityManager.Exists && EntityManager.HasComponnent) in case your entities are ever destroyed or a component is removed.

    I much prefer driving UI from a system; create a presentation layer for your UI with a MonoBehaviours then write the data to it from a system. This system can also control the life of the gameobjects it controls.
    i.e. replace your MonoBehaviour.Update() with a System and your future self will thank you.
     
    Last edited: Sep 15, 2020
  10. peaj_metric

    peaj_metric

    Joined:
    Sep 15, 2014
    Posts:
    145
    I can absulutely empathize with that. That "over-engineering alarm" is part of why I was asking in the first place.
    From reviewing the code of our last project (non ECS) I can definitely say that an event system can be usefull and can save time and produce decoupled, better extandable code although it may also be over/missused in some cases.

    This was actually my first idea. I think this is also the first event system tertle built (does it still exist anywhere)?
    What I like about it is that it is pretty simple and straight forward even though it is a lot slower than tertle's newer system.

    This is exactly the kind of stuff I am afraid of as this event system is more complex and thus also more effected by future DOTS updates and I don't feel comfortable enough to fix issues like this myself should they occur.

    Which doesn't mean I won't end up using it. I still think its great and thx for making it open source.

    I guess the system just needs to run on the main tread and not use burst then? And I guess it should be multiple systems because each one needs to run on certain components (e.g. player health)
     
  11. Chris-Herold

    Chris-Herold

    Joined:
    Nov 14, 2011
    Posts:
    115
    Not in love with the process either, but it is what it is.
    I would not want to rely on a System handing over component data, that just adds an extra layer of indirection. Boilerplate code can always be wrapped away in some extension methods.
    UI implementation shouldn't depend on a certain system existing imho. A poll-only approach might be a bit more verbose, but you end up with something that is completely untangled (and working).
     
  12. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    MVC-pattern not applicable?
     
  13. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    909
    Pretty amazing @tertle
    Yes, you should post about it! :)

    Okay, it's pseudo code but looks like OOP. It's not what I meant.
    Code (CSharp):
    1.  
    2. if (health <= 0)
    3. {
    4. var newEnt = commandBuffer.CreateEntity();
    5. commandBuffer.AddComponent(newEnt, new EnemyKilled
    6. {
    7.    type = monster.monsterType,
    8.    level = monster.level
    9. });
    10. }
    11.  
    EnemyKilled gets cleaned up by a specific system for example that runs last in the frame or in a system where you know it'll be not needed anymore afterwards.
    HealthSystem->create EnemyKilled event entity
    AchievementSystem picks up EnemyKilled entity -> process (unlocks, completion) -> eventually remove EnemyKilled when it isn't needed anymore
    QuestSystem picks up EnemeyKilled entity -> process very similar to AchievementSystem
    UIQuestSystem -> build current UI and update (I agree that I prefer tertles solution with handling UI in a system that has access to Transform of the UI GameObject)

    So what you wrote shouldn't be called directly in (health <= 0) but later in their respective systems.
    That way everything is untangled and the only instruction left in your HealthSystem is which event you're triggering. I think that's pretty readable and closest to any event system without actually using an event system.
    Bonus points, if you ever find out this is a bottleneck you can implement an event system without having to do huge rewrites. Most of the logic stays the same.
     
  14. peaj_metric

    peaj_metric

    Joined:
    Sep 15, 2014
    Posts:
    145
    I think its a problem of responsibility.
    Because sth has to handle the lifetime of that EnemyKilledComponent and it should be none of these systems (Quest, Achievements, UI, etc.)
    The EnemyKilledSleanupSystem can be run at the end of the frame as you mentioned but then you will have to be very careful about the order of systems that create and consume the EnemyKilledComponents as some system might miss it otherwise.
    Running the cleanup system directly before the EntityCommandBuffer might prevent this as it may assure the Component complete a whole gameloop before being destroyed.
    Having a unique system for each of these components (e.g. EnemyKilled, ItemCollected) would be redundant thogh as it is just managing lifetimes.
    So it would make sense to create "Event" entities with a LifetimeComponent and the specific EnemyKilledComponent so a generic system can just destroy entities with LifetimeComponent and wouldnt need to care for the specific kind of event.

    So now we are crossing the line to an event system again...
     
  15. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,984
    I figured I drop my two cents here on the random bits that came up:

    If your events persist for more than one frame or you are targeting a single-threaded platform like WebGL, entities as events can make a lot of sense. Otherwise, containers are pretty much always better. Like everything else in DOTS, there's no silver bullet.

    Is your spawning system also responsible for killing enemies? Probably not. You may want to reuse the spawning system but have different ways entities get destroyed. You are always going to have systems that assume some other arbitrary system touches and interacts with the same data. There's a difference between assuming some other system exists that cleans things up and explicitly referencing that system in the code. The latter creates hard-coupling, meaning that if one system is used in a project, the other system must also be used or the code modified. I try to avoid hard-coupling of systems as much as possible. However, many people including Unity consider that not nearly as big of a deal as I do.
     
    lclemens likes this.