HSL Event System
- See also: Character data tutorial
The HSL Event System implements the ObserverPattern allowing nodes and scripts to subscribe to "listen" for the occurance of events that matter to them. It supports very granular events by allowing subscription to events that affect a particular ID. One of the most important benefits of using the ObserverPattern is that objects( code ) need to be tightly coupled to other objects to receiving notifications of state changes.
Why Use an Event?
Without Events: the Tightly Coupled Solution
Suppose Joe wanted to know each time his best friend Bob eats a cheeto, it makes sense to just ask Bob to give you a phone call each time. The equivalent of this in scripting is you add a call from the
BobScript script to
JoeScript in the
CheetoHappened function. This works tolerably well when you know in advance that Joe wants to know about cheeto events, but does not work well if you need to dynamically be interested or not and it tightly couples Bob's code with Joe's code.
Knowing Bob's Cheeto consumption was fun for a day or two, but Joe (on his path to world domination via control of Cheeto consumption) feels his data set insufficient to get a good feel for overall Cheeto consumption. Joe decides he needs to know when any of his friends consume a Cheeto, since his friends will of course assist him in world domination Joe feels it would be nice to let them hear about each other's cheeto consumption as well.
Joe could ask each of his friends to give him and a list of people he specifies a phone call whenever that friend eats a cheeto. If he did that, each of his 5 friends would have to make 5 phone calls ( one to joe and one to each of the other friends ). Suppose Joe adds a new friend, he would now have to call each of his friends to have them update their list.
From a HSL scripting perspective, each friend would have function calls to each other friend's script and adding a new friend would require updating all of the scripts to include the new friend.
So what happens when Joe becomes popular and adds 50 new friends?
As you can see, tightly coupling leads to an impossible situation to maintain that becomes increasingly error prone as the number of interested parties increases.
With Events: the Loosely Coupled Solution
Observer Pattern Terms
- A thing to which listeners may register to get notifications about state changes
- Something that is interested in some subject's state changes
In our example above, the subject in the event system is the event
CheetoHappened. Joe and each of his friends create listeners and subscribe(register) them with the subject and they all send
CheetoHappened event to the event system when they eat a cheeto. The event system is then responsible for telling everyone interested ( as indicated by their having registered a listener ) that someone ate a cheeto.
The event system uses proxies, called listeners, for the interested parties. Each listener knows how to notify the thing for which it is a proxy, this allows us to create an infinite variety of listener types if necessary without requiring the event system know the specifics of how each listener notifies the object it represents. For example the event system currently supports nodeListeners and scriptListeners, a node listener knows that it needs to call the script contained in the script field of the node that registered and a script listener knows that it needs to call a shared function in the script that registered.
This design means that the friends do not need to know about each other (ie you do not need to edit each friend's code when new friends are added ), it also supports the idea that Joe's friends might not always want to hear about
CheetoHappened events ( they all need to sleep sometime right? ). The friends also do not even need to know how notification is sent to other friends, one might get it via email the other might get an IM and a third might get notification via snail mail.
Ok, We all Like Cheetos...But Lets Talk Real World
So we all like Cheetos but how does this translate to real usage for Hero's Journey?
Lets look at a Hero's Journey specific pattern, the Oracle Pattern. The oracle pattern is meant to supply a mechanism that informs one or more clients about state data and store/notify clientside scripts that might be interested in the data. ( Can you see where we are going with this? )
Well...we know the Observer pattern is a great way to loosely couple objects ( code ) so that any given object does not require the modification of another object's behavior.
Lets consider the event
Somewhere on the server, some code says
Hurt the Ukar. An event is raised that the Ukar's health just changed, to which the server's creatureOracle is listening. When the creatureOracle receives an event, it analyzes what type of event it is and then sends updates to clients in the area.
On the client, the CreatureOracleSystem receives a remote call from the server with a package of updates which it processes (updating its local data store) and sending event notifications when appropriate. Which in the case of creaturehealth, it raises a creaturehealthchanged event. Zero or more listeners may have registered with the event system that they want to hear about creaturehealthchanged events. In this particular case, the targetWindowGUI script has registered to know about health changes for the creature it represents so it can update its display.
If a GameMaster later on adds a new GUI that wants to know about changes in creature health, they merely need to register a listener with the creature health event. The only code necessary is the addition of a register a listener for the GUI during instantiation of the GUIControl, no changes need to be made in the event system or oracle scripts.
Anatomy of the EventObject Class
Event objects are instantiated from the EventObject class, and then additional classes are Glommed on as necessary to contain any information that a particular type of event may require.
|eventType||The type of event this eventObject is, the string contained is always lowercased.|
|eventAffectsID||Contains the ID of the thing that is affected by the event.|
|eventCausedByID||Contains the ID of the thing that caused the event to happe. This may or may not be set depending on the event|
Adding a new Event
Adding a new event to the EventSystem script is fairly straightforward.
- Add the event to the GetKnownEvents LIST structure in the EventSystem script [Note: WHEN/IS Structure is no longer in use and is deprecated - GM Kurrasoe]
- Document via comments the class and fields that you plan to add to the eventObject to store your event's data. Not all events need additional information, it may be enough to simply know that something happened.
Raising an Event from Script
Assuming you have defined your event as detailed in Adding a new Event, raising an event (i.e. sending notification that the event has happened ) is simple.
- Create an eventObject using the CreateEventObject function
- Glom any additional required classes onto the event object and populate the fields
- Send the event using NotifyOfEvent( eventObject )
eventObject as noderef of class EventObject = EventSystem:CreateEventObject( eventaffectsID, eventcausedByID, "death" ) GlomClass( "YOUROPTIONALCLASS", eventObject ) where eventObject is kindof YOUROPTIONALCLASS eventObject.someField = "stuff" . EventSystem:NotifyOfEvent( eventObject )
Subscribing to an Event
Subscribing to an event is the process of creating a listener and registering it with the event system to listen for a particular event. Listeners currently come in two basic types, nodeListeners and scriptListeners. Each of those may be created as a listener for all events of a type (e.g. all death events no matter what thing is actually dying) or a listener for an event that affects a particular ID (e.g. the death of a particular creature).
When a NodeListener has determined that an incoming event is one it cares about, it calls the shared function EventNotification in the .script field of the node that was registered. The registered node MUST be of class Scripted for this to function.
When a ScriptListener has determined that an incoming event is one it cares about, it calls the shared function EventNotification in the registered script.
- Create a listener using one of the following functions
- Register the listener using the function RegisterListenerForEvent
- Implement the shared function EventNotification in the script that will be called by the listener (i.e. for a node listener it is the node's script field, for a script listener it is the script you registered. )
IMPORTANT - the eventObject that is passed into the shared function is OWNED by the event system. If you need to persist any of the data from the event you must copy it to another node. Do not delete the eventObject, the eventSystem handles cleanup when it is finished with the eventObject.
Example: Create a script listener that wants to be notified when a particular node dies
listener as noderef of class ObserverSystemListener listener = EventSystem:CreateScriptListenerForSpecificNode("death", SYSTEM.INFO.THISSCRIPT, 4487000022) RegisterListenerForEvent(listener, "death")
Example: Implement the EventNotification function in your script to do something when a death event comes in.
shared function EventNotification(eventObject as NodeRef of Class eventObject) when eventObject.eventType is "death" '' doStuff . . .
When your node/script is no longer interested in an event to which it previously subscribed, you should unsubscribe it to avoid unnecessary processing.
- Use one of the unregister functions
UnRegisterScriptForSpecificNode( "death", SYSTEM.INFO.THISSCRIPT, 4487000022 )