@planetis

> At least in my experience I used simple empty components as 'Tags' that 
> notify the next system of changes. They can be added and removed very 
> efficiently.

In general, I agree with this. In many cases using components as "events" works 
better than explicit events because they can be easily inspected and modified, 
and their work can be split over multiple systems in a clearly defined order. 
Supporting ordering, inspection, and work splitting _tends_ to be more 
complex/less transparent with a callback/queue style event system (YMMV, of 
course).

Having said that, I found myself implementing events in my ECS despite it being 
a _huge_ complexity burden (in my case, events are immediate and allow mutating 
entities during system iteration). I rarely use them because, as you say, tags 
are often more ergonomic.

Why bother, then? Events allow defining behaviour for state transitions, and 
for that, they are invaluable. They round out design options. It's one of those 
things that you never need... until you do!

Events are particularly useful for context sensitive initialisations:
    
    
    KillAfter.onInit:
      # Event records when this component is added.
      curComponent.startTime = cpuTime()
    
    makeSystem "updateShrinkAway", [ShrinkAway, KillAfter]:
      # System to normalise the time remaining.
      let curTime = cpuTime()
      all:
        item.shrinkAway.normTime = 1.0 - (
          (curTime - item.killAfter.startTime) / item.killAfter.duration
        )
    
    makeSystem "shrinkAwayModel", [ShrinkAway, Model]:
      # System to shrink and fade out 3D models.
      # A similar system handles [ShrinkAway, FontText].
      added:
        # Event invoked when ShrinkAway and Model first exist together.
        item.shrinkAway.startScale = item.model.scale[0]
        item.shrinkAway.startCol = item.model.col
      all:
        item.model.scale = vec3(item.shrinkAway.startScale * 
item.shrinkAway.normTime)
        item.model.col = item.shrinkAway.startCol * item.shrinkAway.normTime
    
    
    Run

@enthus1ast

It may be worth noting that iteration order in `HashSet` is undefined if event 
order matters for you. There's `OrderedSet` though, if you don't need other set 
operations.

> An entity receives damage. Without an event system the inflictDamage proc 
> must call eg the playDamageSound or the createBlood proc etc. Therefore the 
> inflictDamage proc must know all the others.
> 
> With an event system the inflictDamage proc just emits evInflictDamage event. 
> Nothing else must be known. Other functionality connects from the outside and 
> are therefore easy to add or remove without changing other unrelated parts of 
> the code.

Potentially, this can be data-driven with just components:
    
    
    makeSystem "inflictDamage", [Damage, Health]:
      all:
        item.health.amount -= item.damage.amount
        if item.health.amount <= 0.0:
          entity.add Killed() # Tag this entity for deletion after sound and FX.
          entity.add PlaySound(name: "boom", volume: 1.0)
        elif item.health.amount < someThreshold:
          # Blood particles.
          entity.add Particles(amount: 20, col: vec3(1.0, 0.0, 0.0), speed: 
-0.03 .. 0.03)
          entity.add PlaySound(name: "bleed", volume: 1.0)
        else:
          # Minor damage particles.
          entity.add Particles(amount: 5, col: item.model.col, speed: -0.02 .. 
0.02)
          entity.add PlaySound(name: "scuff", volume: 0.5)
    
    makeSystem "soundEvents", [PlaySound]:
      all:
        # Play sounds.
      sys.remove PlaySound  # Remove PlaySound for entities in this system.
    
    makeSystem "particleFx", [Pos, Particles]:
      all:
        for i in 0 ..< item.particles.amount:
          discard newEntityWith(
            item.pos,
            Vel(x: rand item.particles.speed, y: rand item.particles.speed),
            Graphic(...)
          )
      sys.remove Particles
    
    makeSystem "kill", [Killed]:
      # Delete all entities with Killed.
      sys.clear
    
    
    Run

Anyway, I hope this isn't derailing too much! Just wanted to add some other 
perspectives on why events are useful in an ECS context, and how components 
themselves can act as events. 

Reply via email to