The gory details of Event filtering
Overview
An Event is something that has happened. If Joe says "hi" an event
will be created for this, and it will propagated around the event hierarchy,
ultimately reaching all objects that have ears and are within hearing range
of Joe (in the simplest case). The event will travel along
the branches of the container tree, and will multiply itself at some nodes
and spread along several branches. Each node may affect ("filter")
this event in different ways, depending on what type of event it is and
which direction it is travelling.
Here is an example of propagation through a container tree:
The node with the blue circle (n3) has generated an event. The
red nodes are interested in receiving events ("have ears" so to speak).
The white nodes are not interested, although they have to help ensure that
the red nodes receive the event.
receiveEvent vs processEvent
Each Physical has two methods: receiveEvent and processEvent. The
difference is important. ReceiveEvent is used for event propagation
- even if an item is totally uninterested in events (like the white nodes
above), it may still receive events that are to be propagated along to
other nodes. ProcessEvent is used when an event has been received
that this item is personally interested in processing.
In the above example n3 has produced an event, this will result in a
n3.receiveEvent call. Here the event willl be propagated in all directions,
ie both children (n1 and n2) and the parent (n4). N3, however, is
interested in that event type himself, which means he should "hear himself".
So n3.processEvent will also be called - that is where n3 gets to respond
to his own event.
The same thing will happen to n1 and n2. ReceiveEvent will be
called first, and from there processEvent will be called since they are
registered as listeners for that type of event.
N5 will receive the event, but is not in itself interested in processing
the event. So it will propagate it on up to n6 and down to n4 without
calling it's own processEvent. And so on.
ReceiveEvent is called from "the outside" and will take care of event
propagation and event filtering. It will also call processEvent on
the same object if desired. ProcessEvent, on the contrary, is a "dead
end" in event propagation. This is where someone really receives
the event and gets to respond to it.
Filtering
From the point of view of the ReceiveEvent method, an event can travel
to and arrive from three different "directions". It can come from
above (the parent), below (a child), and from the node itself
(self). I'll use the abbreviations P, C, and S. When
n3 produces the event it will do four progations: to n6 (S->P), to n1 (S->C),
to n2 (S->C), and finally to itself (S->S). N5 will propagate to
n4 (C->C) and n6 (C->P). In total there are seven types of propagation
possible:
-
Self->Self
-
Self->Child
-
Self->Parent
-
Child->Self
-
Child->Child
-
Child->Parent
-
Parent->Self
-
Parent->Child
The only missing combination is Parent->Parent, since that would cause
a recursive behaviour. Note that ->Child and ->Parent means calling
receiveEvent, while ->Self means calling processEvent. The exact
usage of each filtering type is illustrated later in this document.
Each Physical has a set of EventFilters. An event filter is a
"black box" type object that, given an event, will produce a filtered event
(which may be the same event unmodified). You stuff in an event,
indicate the source type (P, S, or C) and destination type (P, S, C) and
a new filtered event will pop out on the other side.
You can connect lots of these together to produce an EventFilterCollection
which acts just like a single EventFilter. Before receiveEvent propagates
an event, the event will be run through this EventFilterCollection.
Filter example: Dome of silence
Here's an example of a filter. The DomeOfSilence spell is cast on
an area or a square and will add a silencing filter that stops all sounds
from entering, although sounds produced from within will be heard from
the outside. People inside the dome won't hear each other either.
Now let's say this is cast on a house. The filter will do the following
for each type of propagation:
Self->Self |
Nothing |
If the house itself was alive and had ears, it would be
able to hear it's own words... |
Self->Child |
Nothing |
If the house itself could talk, the inhabitants of the house would
be able to hear what the house said |
Self->Parent |
Nothing |
If the house itself could talk, people outside the house would be able
to hear it. |
Child->Self |
Nothing |
If the house had ears it would be able to hear what the inhabitants
inside are saying |
Child->Child |
Kill all sound |
The inhabitants inside cannot hear each other talking |
Child->Parent |
Nothing |
People outside the house can hear what the people inside are saying |
Parent->Self |
Nothing |
If the house had ears, it would be able to hear what people outside
are saying |
Parent->Child |
Kill all sound |
The people inside cannot hear what the people outside are saying. |
Filter diagrams
The following diagrams illustrate how the event propagates through the
different methods and filters on it's way around the container hierarchy.
I've drawn one diagram for when an event arrives from the parent, one for
when an event arrives from a child, and finally one where the event is
generated by the item itself. The blue circle is the source of the
event. The diamonds represent the different types of filtering.
Events from parent:
Events from child:
Events from self:
The ItemListener interface
Anyone can register itself as an ItemListener to an Item. All you
have to implement is the ItemListener interface and call the addListener
method on the Item you want to listen to. The ItemListener interface
provides a receiveEvent method, which the Item uses to pass events to the
listener.
There are two types of ItemListeners, and it is important to understand
the distinction. I'll use the term host to refer to the Item
that we are listening to:
-
ItemListeners that want to receive all events that the host receives.
These will be the same events that the children of the Item receive, ie
events that have been X->Child filtered.
-
ItemListeners that want to receive all events as heard by the host.
This means it will receive exactly the same events the are passed to the
processEvent method of the host, ie events that have been X->Self filtered.
If A adds itself as a child of B, then A will automatically get registered
as a listener to B (if A wants events). By default A will be want
X->Child filtered events. That means if someone casts a spell that
makes B deaf, it won't affect A at all.
Soul is a good example of the second type of ItemListener. These
want to hear events as heard by the host. This means if the
host becomes deaf suddently, the soul won't hear anything either.
But the cat that the host is holding (and is also a child of the host)
will be unaffected - events will pass right through to the cat.
The ItemListener provides the following method to indicate the type:
public boolean wantsPrivateEvents();
I use the term "private event" for events that are sent to the processEvent
method of the host, ie stuff that the host really hears. In most
cases this will return false, but listeners that really want to hear what
the host hears should return true.
Indicating which event types are desired
For now you cannot indicate which type of events you like to listen to.
So if you are a registered ItemListener you will receive all kinds of events.
This is a matter of performance. I'm not sure what will give best
performance - to keep track of which types of events are desired and minimize
unnecessary event propagation, or to keep things simple and just send all
event types. Time will tell, I might change this in the future
so that the nodes keep track of which types of events are desired by who.
Automatic registration of ItemListeners
Physical has the following method:
public boolean wantsParentEvents();
If wantsParentEvents() returns true then that Physical will always be
registered as an ItemListener to it's parent. When it moves around
in the container hierarchy, it will automatically be deregistered from
it's old parent and registered to it's new parent. The default behaviour
is that my wantsParentEvents() method returns true only if I have others
listeners registered to me (ie there are items that want to receive events
from me, so I therefore want to receive events from my parent).
If an item always wants to receive events regardless of whether or not
it has listeners it should override this method and simply return true.
Soul is an example of this - it always wants to receive events. So
when the Soul is added to the body using body.addEffect(soul), the soul
will become a child of the body and, since wantsParentEvents() = true,
it will automatically get registered as an ItemListener to the body.
Vóila.
Summary of propagation and filtering methods
-
Physical.receiveEvent(...) is used when one Physical wants to propagate
an event to another. This method will call processEvent to allow
me to act upon this event if I like, and then propagate the event on to
any other listeners
-
Physical.processEvent(...) is where I can react to an event that
has occured (no propagation takes place here).
-
EventFilter.filter(...) will filter an event, given a source type
(PARENT, CHILD, or SELF) and a destination type (PARENT, CHILD, or SELF).
A Physical can have any number of EventFilters, all of which get a chance
to filter every event that passes through.
-
ItemListener.receiveEvent(...) is the method used to propagate an
event to an ItemListener (same as Physical.receiveEvent above).
-
ItemListener.wantsPrivateEvents(...) - if false, this ItemListener
wants to receive events as recieved by the host (after X->Child filtering).
If true, this ItemListener wants to receive events exactly as heard by
the host (after X->Self filtering). This means any events passed
to my host's processEvent method will be passed to me as well.
Henrik
Kniberg
Last updated: