Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Event System

Discussion in 'Entity Component System' started by tertle, Nov 19, 2019.

  1. Sylmerria

    Sylmerria

    Joined:
    Jul 2, 2012
    Posts:
    369
    that works, thanks :)
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    [1.1.6] - 2020-08-13
    Added
    - Added AllocateLarge and ReadLarge extensions to NativeEventStream

    If you need to write an array, writing elements 1 by 1 is not super efficient instead it's much faster to use Allocate.
    However currently the max allocation size is 4092, but I found a use for allocating a larger arrays on occasion so I've added 2 new extensions, AllocateLarge & ReadLarge

    These basically just break up the allocation into 4092 chunks automatically; effectively allowing you to write any size data you want without having manually handle the memory.

    It's orders of magnitude faster than writing/reading elements individually.

    upload_2020-8-13_10-15-25.png
    without burst
     
    Last edited: Aug 13, 2020
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    One more thing, I've set this up on npm now so you can get updates through the package manager

    Just add the scoped registry to your manifest.json like this

    Code (CSharp):
    1. {
    2.   "scopedRegistries": [
    3.     {
    4.       "name": "BovineLabs",
    5.       "url": "http://35.247.98.4:4873",
    6.       "scopes": [
    7.         "com.bovinelabs"
    8.       ]
    9.     }
    10.   ],
    11.   "dependencies": {
     
    Last edited: Aug 15, 2020
    lclemens and Egad_McDad like this.
  4. Sylmerria

    Sylmerria

    Joined:
    Jul 2, 2012
    Posts:
    369
    Oh that great ! thanks a lot
     
  5. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    760
    Where did you get EndSimulationEventSystem? I tried every BovineLabs namespace I could find.
     
  6. Egad_McDad

    Egad_McDad

    Joined:
    Feb 5, 2020
    Posts:
    39
    EndSimulationEventSystem
    (along with
    PresentationEventSystem
    ) have been deprecated. If you wish to have an event system which runs in a group other than
    InitializationSystemGroup
    you will need to create a system inheriting
    EventSystemBase
    .

    For what its worth, if you don't need an event system to run in an alternate update group then I believe a single
    EventSystem
    is all you need to read/write events.
     
    bobbaluba and lclemens like this.
  7. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    So yeah Egad_McDad pretty much answered this for you, it no longer exists. You only need 1 event system per world / update rate (if you want to use it in the new fixed update group you'll need to create a new system in there. I'll probably add one by default soon though~)

    Back when they existed, you had to order your systems before the EventSystem ran and all your readers before writers. bit tedious trying to maintain system order. I have since added a deferred mode where once reading has started (this is per event, so a read on one event won't affect another event type) new writes are simply added to the defer queue and read the next frame instead.

    ES, write1, write2, read1 [w1, w2], read2 [w1, w2], write3, read3 [w1, w2]
    ES, write4, read1 [w3, w4], read2 [w3, w4], read3 [w3, w4]

    This way no events will ever be dropped or missed regardless of the order of your systems. At worse it's 1 framed delayed.
     
    bobbaluba, lclemens and Egad_McDad like this.
  8. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    760
    Awesome, thanks guys! I have several bovinelabs events flying around my systems and I'm seeing lots of great uses for more!
     
  9. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    760
    I'm still wrapping my head around everything here and I know you guys understand this much better so I'd like to get your opinion on a specific approach I'm considering. I too am considering use this event system for creating/destroying entities.

    Would there be any problems with say making a RemoveEntityEvent and firing it like:

    Code (CSharp):
    1. public struct RemoveEntityEvent
    2. {
    3.     public Entity entity;
    4. }
    5.  
    6. public class MySystem : SystemBase
    7. {
    8.     protected EventSystem m_eventSystem;
    9.     protected override void OnCreate()
    10.     {
    11.         m_eventSystem = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem<EventSystem>();
    12.     }
    13.     protected override void OnUpdate()
    14.     {
    15.         var writer = m_eventSystem.CreateEventWriter<RemoveEntityEvent>();
    16.         Entities.ForEach((Entity entity, ...) => {
    17.             writer.Write(new RemoveEntityEvent { entity = entity });
    18.         }).ScheduleParallel();
    19.         m_eventSystem.AddJobHandleForProducer<RemoveEntityEvent>(this.Dependency);
    20.     }
    21. }
    22.  
    And then an event remover system like:

    Code (CSharp):
    1. public class EntityRemovalSystem : ConsumeSingleEventSystemBase<RemoveEntityEvent>
    2. {
    3.     protected override void OnEvent(RemoveEntityEvent evnt)
    4.     {
    5.          EntityManager.DestroyEntity(evnt.entity);
    6.     }
    7. }
    The way I'm thinking about it... Yes, this runs in the main thread without burst... but the alternative version - using EntityCommandBuffer.DestroyEntity() would also be performing the destruction on the main thread without burst. So in theory, they would accomplish the same purpose, except that the event system version would run faster? Or maybe it's the opposite and EntityCommandBuffer would be faster because maybe it somehow collects all the destroy entity calls and does them in a batch or something? I don't really know how EntityCommandBuffer works under the hood. Or maybe this is just a stupid idea and I'm way off track :)
     
    Last edited: Aug 16, 2020
  10. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    Pretty sure ECB would be faster and you should use that.

    The only exception is if you are using a very hybrid game and need a custom destroy to cleanup game objects/effects etc then this design might make sense.

    (side note you're missing addhandleforproducer)
     
    Last edited: Aug 16, 2020
    lclemens likes this.
  11. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    760
    OK, that's good to know - thanks for the reply!

    I edited my last post to fix the missing AddJobHandleForProducer.
     
  12. Jyskal

    Jyskal

    Joined:
    Dec 4, 2014
    Posts:
    28
    @tertle I have a use-case question for your event system.

    I am working on a basebuilding game. I have worker entities that can perform tasks. One of those tasks for example is mining. I have a simple MiningSystem that handles mining of a resource by just reducing a value on a ResourceNode entity every few seconds.

    However now I want to add functionality like:
    1. Create a new entity for the mined resource. (Create Entity)
    2. Apply XP gains to my worker. (Update component)
    3. Update my global inventory stats. (Update component)
    4. Etc...

    This concept would work well with an event system. Now my question is, would there be an advantage in this usecase of using your event system vs Entity Events? Which approach would you take?

    Note that these events only occur every few seconds, the amount of events would be limited 20 max (per task system), don't care about same/next frame consumption and they would all happen within the same world.
     
  13. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Based on the README, it seems like manually ordering systems reading from and writing to event buffers is no longer required. Is this correct?
     
  14. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    Yes you don't need to order it. Obviously if you a read to happen the same frame after write it has to be ordered afterwards but otherwise if the system reading is ordered before the writing system, now it'll just be deferred to the next frame. You can't lose events regardless of system order.

    Sorry somehow I completely missed your post.

    The event system is just much higher performance than entity events, works across different update rates (fixed update vs update) and a bunch of other automated stuff for you.

    For such a simple requirement though I'd probably just stick to entity events.
     
    Abbrew likes this.
  15. Sylmerria

    Sylmerria

    Joined:
    Jul 2, 2012
    Posts:
    369
    Hi @tertle,

    Can you confirm me than IJobEvent have not Run method ?
    I need to do work in main thread for some events which use Object( not in event but on entities). Have you recommendation for that ?
     
  16. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    Doesn't have a run method.
    On the rare times I need to read on main thread there's a couple of custom systems that you can use for this for you.

    ConsumeSingleEventSystemBase<T> or ConsumeEventSystemBase<T> if you need finer control.

    Or I just manually iterate the containers.
     
    lclemens likes this.
  17. Sylmerria

    Sylmerria

    Joined:
    Jul 2, 2012
    Posts:
    369
    Oh I forgot them. It must work with that
    Thanks
     
  18. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    760
    I use ConsumeSingleEventSystemBase quite often and it works very well and is easy to implement. Quite often I find that I need to play particle effects and sounds or use things like a LineRenderer or animation that don't have DOTS support, and I need to shift those things to the main thread. Using tertle's events is a great way to keep functions running in parallel and with burst, but yet still use the many features that Unity hasn't converted (or won't convert) to ECS.
     
    Last edited: Oct 5, 2020
    Sylmerria and Bivens32 like this.
  19. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    529
    Hey @tertle, nice work!

    One issue I got with the safety system checking balanced calls to CreateEventWriter/Reader and AddJobHandlerForProducerConsumer: When this happens once it happens each frame due to producerSafety/consumerSafety not getting reset before the exception is thrown. So I would suggest adding eg
    producerSafety = false;
    before throwing the exception to make development/debugging more enjoyable.
     
  20. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    That seems reasonable!

    I was also considering to add some stack info for when the last write/read was called to help narrow down issues. I just haven't got around to it.
     
    Last edited: Oct 23, 2020
    Egad_McDad and Sylmerria like this.
  21. Bivens32

    Bivens32

    Joined:
    Jul 8, 2013
    Posts:
    36
    Thank you @tertle for the event system. It has replaced most of my event entity code that I really didn't like and felt was cumbersome to deal with. I have one question though. Is there a way to create a NativeMultiHashmap that has a length that matches the number of events of a specific type? The current way I'm doing it requires a Dependency.Complete(), which I'd like to avoid as I plan on reusing this pattern in many systems. Basically the ability to map many events to a specific target.
    Code (CSharp):
    1. NativeArray<int> eventCount = new NativeArray<int>(1, Allocator.TempJob);
    2.  
    3.             var extensions = this.eventSystem.Ex<StatusEffectEvent>();
    4.             Dependency = extensions.GetEventCount(Dependency, eventCount);
    5.             // I'd like to avoid this part
    6.             Dependency.Complete();
    7.  
    8.             // MultiHashMap mapping targets (entities) to a "list" of new status effects
    9.             var statusEffectsMap = new NativeMultiHashMap<Entity, StatusEffectEvent>(eventCount[0], Allocator.TempJob);
    10.          
    11.             // *This job maps all events to their specific targets
    12.             this.Dependency = new MapEventsToTargetJob
    13.             {
    14.                 parallelHashMap = statusEffectsMap.AsParallelWriter(),
    15.             }
    16.             .ScheduleParallel<StatusEffectEventReaderJob, StatusEffectEvent>(eventSystem);
    17.  
    18.     [BurstCompile]
    19.     struct MapEventsToTargetJob : IJobEvent<StatusEffectEvent>
    20.     {
    21.         public NativeMultiHashMap<Entity, StatusEffectEvent>.ParallelWriter parallelHashMap;
    22.  
    23.         public void Execute(StatusEffectEvent e)
    24.         {
    25.             parallelHashMap.Add(e.Target, e);
    26.         }
    27.     }
    28.  
     
  22. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    There is an extension for normal NativeHashMap to ensure capacity

    JobHandle EventSystem.Ex<YourEvent>().EnsureHashMapCapacity<TKey,TValue>(JobHandle, HashMap)

    I could also implement a version for NativeMultiHashMap easy enough.
    Might look at making some updates today at some point.
     
    Bivens32 likes this.
  23. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    So I'm probably not making that change as it makes other things not work as I'd expect, however I have added stack info on the previous call which should pinpoint exactly where the issue is.

    upload_2020-10-25_8-19-3.png
     
    Last edited: Oct 24, 2020
  24. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    -edit-

    ## [1.1.9] - 2020-10-25
    ### Changed
    - Reduced requirements back to Unity 2019.4 and Entities 0.11

    -original-

    ## [1.1.8] - 2020-10-25
    ### Added
    - Added EnsureHashMapCapacity override for NativeMultiHashMaps
    - Added stacktrace info to the consumer and producer safety to help pinpoint a missing AddHandlerFor

    ### Changed
    - ReadLarge now has an optional Allocator if you want to use something other than the Temp allocator
    - StreamBus is now cleaned up after last Unsubscribe

    ## [1.1.7] - 2020-08-14
    ### Changed
    - Updated requirements to Unity 2020.1 and Entities 0.14
    - Tweaked testing

    ### Fixed
    - Warnings as the result of the new burst 1.4 preview
     
    Last edited: Oct 25, 2020
    Bivens32 likes this.
  25. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    529
     
  26. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    Just released a small patch to fix the compile error with new collections package.

    Heads up I'm intending to release a new version in the next 5 days that will have a breaking change. It's going to split the deterministic and non-deterministic modes into 2 separate structs so if you use both of them it will require a quick update. (The reader is still shared between them)

    This is because adding the deterministic mode reduced the performance by more than i expected.
     
    Last edited: Nov 4, 2020
    Bivens32 and Sylmerria like this.
  27. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    ## [1.2.0] - 2020-11-05
    ### Changes
    - *Breaking Change* Split the writer into ThreadWriter and IndexWriter for non-deterministic and deterministic modes

    ### Fix
    - Potential fix for the PS4 and iOS crash when using a writer outside of burst thanks to @julian-moschuering
    - Sample FixedUpdate now works on entities 0.16+


    I haven't thoroughly tested this release in a real world application yet, but it's passing all tests fine and I don't expect any issues. I also updated the containers with the latest attributes that exist on NativeStream from the most recent collections update. Not sure if required, but I like to keep it up to date anyway.

    IndexWriter and the Reader are near identical to NativeStream Writer/Reader and perform nearly identical. The only major change is there is no safety check on the ForEachCount as it's not required as the Unsafe implementation doesn't include a ScheduleConstruct option.

    ThreadWriter a little bit slower vs IndexWriter under certain circumstances (but creates a tiny bit faster reading back in others). Note though we're talking 0.06ms vs 0.05ms at 10,000 events/frame. It's only really noticeable at 1,000,000+ events/frame which is highly unlikely in any type of real world circumstances.
     
    Last edited: Nov 8, 2020
    Egad_McDad, lndcobra and Sylmerria like this.
  28. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    198
    Thanks for this event system @tertle . I am currently trying to test this using simple damage events.

    I send a DamageableDeltaEvent from any system, just need the entity that I am damaging. I have a reader system that detects if the entity has a Damageable component, and if the damage event has the same entity id. If the id's are the same, I damage the entity.

    The only issue is, I need to use arrays which causes some sync points. Is this the proper way to go about something like this? It works as is but I am just wondering if there is a more efficient way.

    Thanks,

    The Damageable Reader System

    Code (CSharp):
    1. using BovineLabs.Event.Containers;
    2. using BovineLabs.Event.Jobs;
    3. using BovineLabs.Event.Systems;
    4. using Unity.Burst;
    5. using Unity.Collections;
    6. using Unity.Entities;
    7. using Unity.Jobs;
    8. using UnityEngine;
    9.  
    10. [UpdateInGroup(typeof(LateSimulationSystemGroup))]
    11. public class DamageableDeltaEventSystemReader : SystemBase
    12. {
    13.     private EventSystem eventSystem;
    14.     private EndSimulationEntityCommandBufferSystem buffer; //buffer for burstable entity manipulation
    15.  
    16.     protected override void OnCreate()
    17.     {
    18.         base.OnCreate();
    19.         //get damage event system
    20.         eventSystem = World.GetExistingSystem<EventSystem>();
    21.         //get buffer from world
    22.         buffer = World
    23.             .GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    24.     }
    25.  
    26.    
    27.     protected override void OnUpdate()
    28.     {
    29.         //create entity command buffer
    30.         var ecb = buffer.CreateCommandBuffer().AsParallelWriter();
    31.  
    32.         EntityQuery query = GetEntityQuery(typeof(Damageable));
    33.         var entityArray = query.ToEntityArrayAsync(Allocator.TempJob, out var entJob);
    34.         entJob.Complete();
    35.         var damageableArray = query.ToComponentDataArrayAsync<Damageable>(Allocator.TempJob, out var damJob);
    36.         damJob.Complete();
    37.  
    38.         for (int i = 0; i < entityArray.Length; i++)
    39.         {
    40.             Dependency = new ConsumerJob
    41.             {
    42.                 ecb = ecb,
    43.                 entity = entityArray[i],
    44.                 dam = damageableArray[i]
    45.             }
    46.             .Schedule<ConsumerJob, DamageableDeltaEvent>(eventSystem);
    47.             buffer.AddJobHandleForProducer(Dependency);
    48.         }
    49.  
    50.         //dispose the lists
    51.         entityArray.Dispose();
    52.         damageableArray.Dispose();
    53.  
    54.     }
    55.  
    56.     [BurstCompile]
    57.     private struct ConsumerJob : IJobEvent<DamageableDeltaEvent>
    58.     {
    59.         public EntityCommandBuffer.ParallelWriter ecb;
    60.         public Entity entity;
    61.         public Damageable dam;
    62.  
    63.         public void Execute(DamageableDeltaEvent e)
    64.         {
    65.             if (e.entity != entity) return;
    66.             dam.curHP += e.delta;
    67.             ecb.SetComponent(e.index, entity, dam);
    68.         }
    69.     }
    70. }
    DamageEvent

    Code (CSharp):
    1. using Unity.Entities;
    2.  
    3. public struct DamageableDeltaEvent : IComponentData
    4. {
    5.     public int index;
    6.     public Entity entity;
    7.     public float delta;
    8. }
     
    Last edited: Nov 15, 2020
  29. Bivens32

    Bivens32

    Joined:
    Jul 8, 2013
    Posts:
    36
    Here's the way I've done it @toomasio. It allows any number of events to target the same entity. The steps are as follows: First, any system can create a StatusEffectEvent (DamageEventDelta in your case) that has an instigator and target (which are both entities). Second, (lines 9-18) this system gathers all the stat events and stores them in a NativeMultiHashMap using the event.Target as the key. Finally, the last job (starting at line 21) iterates over all entities that can receive status effects and then checks if the hashmap has any events that are targeting that entity. If so, you can add them to a dynamic buffer for further processing as I did, or you can just apply the damage then.

    Most of the code inside the while block is specific to my game to allow damaging enemies and allies if necessary so you may not need it. My code is for more of a generic stat system that is meant to work with damage and any other stat. There are more specific systems that read the status effect buffer and check if it's a damage effect and then change health accordingly. By the way, [AutoAssign] and the TryIterate parts are short cuts provided by DOTSNET from the asset store. If you do not have that, you will have to assign the system in OnCreate and iterate the normal way.

    Code (CSharp):
    1. public class GatherAndAddStatusEffectsSystem : SystemBase
    2.     {
    3.         [AutoAssign]
    4.         private EventSystem eventSystem;
    5.  
    6.         protected override void OnUpdate()
    7.         {
    8.             // MultiHashMap mapping targets (entities) to a "list" of new status effect events
    9.             var statusEffectsMap = new NativeMultiHashMap<Entity, StatusEffectEvent>(1, Allocator.TempJob);
    10.             var extensions = this.eventSystem.Ex<StatusEffectEvent>();
    11.             Dependency = extensions.EnsureHashMapCapacity(Dependency, statusEffectsMap);
    12.  
    13.             // This job maps all events to their specific targets
    14.             Dependency = new MapEventsToTargetJob
    15.             {
    16.                 parallelHashMap = statusEffectsMap.AsParallelWriter(),
    17.             }
    18.             .ScheduleParallel<MapEventsToTargetJob, StatusEffectEvent>(eventSystem, Dependency);
    19.  
    20.             // Second job that adds the status effects directly to the target's statusEffect Buffer
    21.             Entities.WithAll<ReceiveStatusEffects>().
    22.             ForEach((Entity targetEntity, ref DynamicBuffer<StatusEffectElement> statusEffects, in Team targetTeam) =>
    23.             {
    24.                 NativeMultiHashMapIterator<Entity>? iterator = default;
    25.  
    26.                 // Use the current entity as a key to check if there are any status effects targeting it
    27.                 while (statusEffectsMap.TryIterate(targetEntity, out StatusEffectEvent e, ref iterator))
    28.                 {
    29.                     if (HasComponent<Team>(e.Instigator) == false)
    30.                         continue;
    31.  
    32.                     ref var actionBlob = ref e.actionRef.Value;
    33.  
    34.                     Team instigatorTeam = GetComponent<Team>(e.Instigator);
    35.                     bool sameTeam = targetTeam.TeamID == instigatorTeam.TeamID;
    36.                     bool shouldAdd = (sameTeam && actionBlob.AffectsAllies) || (!sameTeam && actionBlob.AffectsEnemies);
    37.  
    38.                     if (shouldAdd)
    39.                     {
    40.                         var statusEffectElem = new StatusEffectElement
    41.                         {
    42.                             actionRef = e.actionRef,
    43.                             Instigator = e.Instigator,
    44.                             Duration = actionBlob.Duration,
    45.                             sameTeam = sameTeam
    46.                         };
    47.  
    48.                         statusEffects.Add(statusEffectElem);
    49.                     }
    50.                 }
    51.             })
    52.             .WithReadOnly(statusEffectsMap)
    53.             .WithDisposeOnCompletion(statusEffectsMap)
    54.             .WithName("AddStatusEffectsToTargets")
    55.             .ScheduleParallel();
    56.         }
    57.     }
    58.  
    59.     [BurstCompile]
    60.     struct MapEventsToTargetJob : IJobEvent<StatusEffectEvent>
    61.     {
    62.         public NativeMultiHashMap<Entity, StatusEffectEvent>.ParallelWriter parallelHashMap;
    63.  
    64.         public void Execute(StatusEffectEvent e)
    65.         {
    66.             parallelHashMap.Add(e.Target, e);
    67.         }
    68.     }
     
    lndcobra, toomasio and Egad_McDad like this.
  30. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    Sorry for not replying earlier toomasio I've just been super busy the last 2 days.

    As Bivens32 has pointed out, you can use the extension EnsureHashMapCapacity to help avoid sync points or threading issues.
     
    toomasio likes this.
  31. toomasio

    toomasio

    Joined:
    Nov 19, 2013
    Posts:
    198
    No worries and thanks @Bivens32 for the information. I haven't tried Hashmaps yet. Looks like I need to do some more research :p
     
  32. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    Just released a fix for a pretty serious memory leak I introduced in 1.2.0 (apologizes).

    ## [1.2.1] - 2020-11-21
    ### Fix
    - A memory leak introduced in 1.2.0
     
    lclemens, Ylly, JesOb and 3 others like this.
  33. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    How does one produce events from MonoBehaviours to be consumed by Systems?
     
    dannyalgorithmic likes this.
  34. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    Just grab the system from the wold and send like normal.
     
  35. kheldorin

    kheldorin

    Joined:
    May 28, 2015
    Posts:
    22
    Is there a way to not update the system if there's no events?
     
  36. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    You can early out really fast with HasEventReaders<T> - note this doesn't check event count just that GetEventWriter<T> was called at some point.

    Alternatively there's an extension to calculate event count

    JobHandle eventSystem.Ex<T>().GetEventCount(JobHandle handle, NativeArray<int> count)
     
    lndcobra likes this.
  37. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    How would you recommend implementing delayed events? Something like
    Code (CSharp):
    1. eventWriter.write(new DelayedEvent(), /*seconds*/.5);
     
  38. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    I don't. Use ECS patterns instead.

    If you want to group events for say telemetry so you can send them all in 1 giant packet for optimization just group them in a read system and let that system handle sending them at a later date.
     
    Last edited: Dec 18, 2020
  39. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Ok, that makes sense. Also, I'm currently using your event system like so:

    Code (CSharp):
    1. Dependency =
    2.             messageQueueSystem.GetEventReaders<GunLoadoutSwitchMessage>(
    3.                 Dependency,
    4.                 out var switchMessageReaders
    5.             );
    6.  
    7.         var numMessageReaders = switchMessageReaders.Count;
    8.         for (var i = 0; i < numMessageReaders; i++)
    9.         {
    10.             var messageReader = switchMessageReaders[i];
    11.             /*
    12.              * Switches to the inventory
    13.              */
    14.             Job
    15.                 .WithCode(
    16.                     () =>
    17.                     {
    18.                         var forEachCount = messageReader.ForEachCount;
    19.                         for (var j = 0; j < forEachCount; j++)
    20.                         {
    21.                             var count = messageReader.BeginForEachIndex(j);
    22.                             for (var k = 0; k < count; k++)
    23.                             {
    24.                                 var message =
    25.                                     messageReader.Read<GunLoadoutSwitchMessage>();
    26.  
    27.                                 // processing here
    28.                             }
    29.                         }
    30.                     }).Schedule();
    31.         }
    The reason being that I don't like Job structs and prefer to use Job.WithCode. Is this usage appropriate? I'm running into an issue where systems comprised entirely of this Job.WithCode pattern won't update due to lack of Entity queries. I'd use [AlwaysUpdateSystem] but that seems inefficient. What did you use for your EventSystem?
     
  40. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I've tried using

    Code (CSharp):
    1. using System;
    2. using BovineLabs.Event.Containers;
    3. using BovineLabs.Event.Systems;
    4. using Unity.Entities;
    5. using MessageQueueSystem = BovineLabs.Event.Systems.EventSystem;
    6. using static GunLoadoutSwitchMessageSystem;
    7. using static ItemInventoryMessageSystem;
    8.  
    9. public class GunLoadoutSwitchMessageSystem : ConsumeEventSystemBase<GunLoadoutSwitchMessage>
    10. {
    11.     private MessageQueueSystem messageQueueSystem;
    12.  
    13.     private EntityCommandBufferSystem endFrame;
    14.  
    15.     protected override void OnEventStream(ref NativeEventStream.Reader reader, int eventCount)
    16.     {
    17.         var ecb = endFrame.CreateCommandBuffer();
    18.  
    19.         var switchMessageWriter =
    20.             messageQueueSystem.CreateEventWriter<ItemInventorySwitchMessage>();
    21.         var eventWriter =
    22.             messageQueueSystem.CreateEventWriter<EventInstance>();
    23.  
    24.         var eventReader = reader;
    25.  
    26.         Job.WithCode(() =>
    27.         {
    28.             for (var i = 0; i < eventCount; i++)
    29.             {
    30.                 var message = eventReader.Read<GunLoadoutSwitchMessage>();
    31.  
    32.                 var userEntity = message.userEntity;
    33.  
    34.                 ecb.AddComponent(userEntity, new GunLoadoutWieldedComponent());
    35.  
    36.                 switchMessageWriter.Write(new ItemInventorySwitchMessage
    37.                 {
    38.                     userEntity = userEntity,
    39.                     inventoryType = ItemInventoryType.GUN,
    40.                 });
    41.  
    42.                 // fire off an event
    43.                 eventWriter.Write(new EventInstance
    44.                 {
    45.                     eventTypeId = GunLoadoutSwitchMessage.GUN_LOADOUT_SWITCH_EVENT_TYPE_ID,
    46.                     sourceId = message.SourceId,
    47.                     success = true
    48.                 });
    49.             }
    50.         }).Schedule();
    51.  
    52.  
    53.         messageQueueSystem.AddJobHandleForProducer<EventInstance>(Dependency);
    54.         messageQueueSystem.AddJobHandleForProducer<ItemInventorySwitchMessage>(Dependency);
    55.     }
    56.  
    57.     public struct GunLoadoutSwitchMessage : IEventMessage
    58.     {
    59.         public Entity userEntity;
    60.  
    61.         public Guid SourceId { get; set; }
    62.  
    63.         public static readonly Guid GUN_LOADOUT_SWITCH_EVENT_TYPE_ID = Guid.NewGuid();
    64.     }
    65.  
    66.     protected override void Create()
    67.     {
    68.         messageQueueSystem = World.GetOrCreateSystem<MessageQueueSystem>();
    69.  
    70.         endFrame = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    71.     }
    72. }
    73.  
    instead to reduce clutter. Is this the proper way to use EventSystem?
     
  41. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I'm getting
    ArgumentException: Not all elements (Count) have been read. If this is intentional, simply skip calling EndForEachIndex();
    with the above example
     
  42. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Nevermind, the issue was that scheduling the reading inside of a job bypasses the beginForeachIndex and endForeachIndex. I've made a modified version of ConsumeEventSystemBase like so:

    Code (CSharp):
    1. public abstract class EventMessageSystemBase<T> : SystemBase
    2.     where T : struct
    3. {
    4.     private MessageQueueSystem eventSystem;
    5.  
    6.     protected sealed override void OnCreate()
    7.     {
    8.         this.eventSystem = this.World.GetOrCreateSystem<MessageQueueSystem>();
    9.  
    10.         this.Create();
    11.     }
    12.  
    13.     protected virtual void Create()
    14.     {
    15.  
    16.     }
    17.  
    18.     protected sealed override void OnDestroy()
    19.     {
    20.         this.Destroy();
    21.     }
    22.  
    23.     protected virtual void Destroy()
    24.     {
    25.  
    26.     }
    27.  
    28.     protected sealed override void OnUpdate()
    29.     {
    30.         this.BeforeEvent();
    31.  
    32.         if (!this.eventSystem.HasEventReaders<T>())
    33.         {
    34.             return;
    35.         }
    36.  
    37.         this.Dependency = this.eventSystem.GetEventReaders<T>(
    38.             this.Dependency,
    39.             out IReadOnlyList<NativeEventStream.Reader> readers
    40.         );
    41.         var numReaders = readers.Count;
    42.         this.Dependency.Complete();
    43.  
    44.         try
    45.         {
    46.             for (var i = 0; i < numReaders; i ++)
    47.             {
    48.                 var reader = readers[i];
    49.  
    50.                 for (var foreachIndex = 0; foreachIndex < reader.ForEachCount; foreachIndex++)
    51.                 {
    52.                    
    53.                         //var events = reader.BeginForEachIndex(foreachIndex);
    54.                         this.OnEventStream(ref reader, foreachIndex);
    55.                         //reader.EndForEachIndex();
    56.  
    57.                 }
    58.             }
    59.         }
    60.         finally
    61.         {
    62.             this.eventSystem.AddJobHandleForConsumer<T>(this.Dependency);
    63.         }
    64.     }
    65.  
    66.     protected virtual void BeforeEvent()
    67.     {
    68.  
    69.     }
    70.  
    71.     protected abstract void OnEventStream(ref NativeEventStream.Reader reader, int foreachIndex);
    72. }
    73.  
     
  43. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    ConsumeEventSystemBase is for main thread reading back which should be avoid unless needed for a specific purpose.
    You should really read back in a bursted job for performance, ideally a parallel one (IJobFor or IJobEvent).
     
  44. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    Fair point, though in my case these events occur pretty infrequently. I'm sorta abusing your event system as a messaging system, hence the alias as MessageQueueSystem. Previously I used entity messages, which took up a ton of archetype space

    I forgot about burst, I'll make sure to add .WithBurst() to all my Job.WithCodes.
     
  45. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I would assume that ordering systems that use your event system is still required? Some consumer systems are not picking up the producer's events unless ordered properly
     
  46. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    It shouldn't be and I've done quite a bit of testing on this.
    You may receive events a frame later though.
    How it works is as follows, if you're updating in this order.

    writerA
    readerA
    writerB
    readerB

    What happens is
    Frame1:
    writerA sends event1
    readerA reads event1, event system switches to read mode
    writerB sends event2, event2 is stored in deferred queue
    readerB reads event1

    Frame2: deferred events moved to main queue
    writerA sends event3
    readerA reads event2, event3, event system switches to read mode
    writerB sends event4, event4 is stored in deferred queue
    readerB reads event2, event3

    etc

    I did just fix a bug a couple of days ago where if you were using HasEventReaders to early out it could miss events first frame but apart from that I'm not aware of a case that could cause you to miss events (which would include consumer systems).

    Note though, if you have queries in your system that are empty the system won't run so it will not read events. You need to make it always update.

    If you have any other repo for missing events I'd like to look at it.

    May as well post this here since I never bothered when I updated it.

    [1.2.2] - 2020-12-21
    Changed
    • Made ConsumeEventSystemBase always update
    Fix
    • Timing bug when using HasEventReaders to early out could cause first frame events to be missed


    Also a side note, I'm doing a decent rewrite (2.0) that'll come sometime next year (when 0.17 hits) to work with the new ISystemBase. Overall not much will change but it will require a few tweaks for existing projects.
     
    Last edited: Dec 23, 2020
    Abbrew, Lukas_Kastern and Sylmerria like this.
  47. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    @tertle Awesome thanks! I added [AlwaysUpdateSystem] to my modified version of your ConsumeEventSystemBase and updated the package. It's working now
     
  48. kheldorin

    kheldorin

    Joined:
    May 28, 2015
    Posts:
    22
    For developers using the event system with Netcode, be aware that some system groups can update multiple times in which case the default event system should not be used. Both the ServerSimulationSystemGroup and the PredictionSystemGroup has that behaviour.
     
  49. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,753
    Yep! Every system group that runs at a different update rate needs to have it's own event system in it.
    If setup properly it will work across different update rates fine (without missing events or reading the same event twice).

    Also by default events are tied to their world but it supports sending across worlds if you change the EventSystemBase.Mode. This is useful if for example you say have a GizmoDrawingSystem in the DefaultWorld and want to send it a mesh to draw from the ClientWorld and/or ServerWorld.
     
  50. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    232
    I have a question if that's ok, I'm just creating and destroying entities to use as events I'm curious how this is better and why I might use it?