XML: Events and Traps
EVENT
An Event is a user-initiated instruction to execute a certain Flight Sim (FS) function. Examples include control surface movement, radio tuning, landing gear retraction/extension, throttle movement, etc. Events are initiated via joystick or other controller, keyboard, mouse, or gauge software. Initiating an event from within one’s gauge software is often referred to as “throwing”, “firing” or “triggering” an event.
From within XML gauge script, an event is triggered using (>K:EVENT) or <Click Event="EVENT"/> instructions.
Examples:
(>K:MIXTURE_RICH)
instructs FS to set all mixture levers to maximum rich. This event does not require a number, or argument, to be passed to it. It’s just (>K:MIXTURE_RICH). The majority of events require no argument.
8100 (>K:MIXTURE_SET)
instructs FS to set mixture levers to 8100, or about mid range. This is an example of an event that requires an argument.
Almost all events have related read-only simulation variables (A:Variables) that change in response to the event’s execution. The simulation variable
(A:GENERAL ENG MIXTURE LEVER POSITION:index, percent)
is associated with the MIXTURE_SET event. If 8100 is passed to MIXTURE_SET, then the A:Var will return 49.44. (Note: 8100/16383 = 49.44. As per SDK, 16383 is the maximum value allowed for MIXTURE_SET. Its range is 0 to 16383 **).
Events are single and instantaneous. Even the smooth increase of the throttle on a game controller is sampled as a series of singular THROTTLE_INCR_SMALL events.
Event expressions don’t contain Units.
The FSX SDK has the complete list in the Events ID section of the online Microsoft ESP SDK.
The FS9 SDK can be downloaded here. Go to the Panels and Gauges link.
Use the Simconnect Name / String Name entries for XML gauges rather than the Event ID name (containing KEY_) which is used with C++ gauges.
EVENT TRAP
An Event Trap is gauge code that ‘listens’ for specified events to be triggered. Its purpose is to allow customized, user-defined script to also be executed anytime the specified event is triggered. By nature, it’s conditional: if Event A is triggered, then process the event trap script and then execute the event.
In XML, the trap is defined using an <On Event> block placed within a <Keys> block:
<Keys> <On Event="LANDING_LIGHTS_TOGGLE"> 1 (>L:Sound_Click,number) </On> </Keys>
The event is LANDING_LIGHTS_TOGGLE. The event trap routine is 1 (>L:Sound_Click,number).
<On Event> blocks should be placed at the bottom of the gauge script in order to catch all events triggered during each gauge update cycle.
EVENT PROCESSING AND XML SCRIPT
All events that are triggered in the current gauge update cycle are queued and then executed sequentially along with their trap routines, if any exist, at the end of the cycle. All simulation variables (A:Vars) affected by the executed events return updated values on the subsequent cycle.
Event Traps across the Gauge Set: FS processes <On Event> trap routines found in all gauges of the aircraft’s panel (the ‘gauge set’) before coming back to the point where the original event was processed – at the end of the update cycle in the gauge that triggered the event.
So, if Gauge00 contains the following:
<Keys> <On Event="PITOT_HEAT_ON"> (L:MyVarA,enum) 10 + (>L:MyVarA,enum) </On> </Keys>
and Gauge01 contains:
<Keys> <On Event="PITOT_HEAT_ON"> (L:MyVarA,enum) 20 + (>L:MyVarA,enum) </On> </Keys>
then on the update cycle of the triggering gauge following the PITOT_HEAT_ON event trigger, (L:MyVarA, enum) will return 30.
Visible Window: A condition is that gauges must be in a visible window for their trap routines to be processed. If a gauge is part of a window whose visibility is set to visibility=0, then no <On Event> routines in that gauge will be run until visibility of the window is set to 1, or until the window is made visible on the screen (e.g., drop down menu Views - Instrument Panel - Window whatever).
Another important condition is that event trap routines in XML gauges are disabled when the user is in External view. Code-fired events still work fine, but their <On Event> capture sections will not. This is a problem for those who like to manage systems (i.e. the autopilot) via keystrokes while flying in External (Spot plane) view. A discussion of work-arounds for this limitation can be found in this EVENTS wiki thread (see post number 13).
Note: A recently released FSX expansion module (XMLTools v.1.0, June 2014, from Tom Aguilo) will handle FSX event traps in any view mode, thus overcoming the visible and external window limitations of stock FSX. XMLTools can be freely downloaded from the FSDeveloper Resources site. Check that site or FSDeveloper Gauges Forum for XMLTools update notices.
Events are processed regardless of aircraft type: Events and event traps are processed whether or not the event is relevant for the current aircraft. Any SDK defined event can be triggered and executed in any aircraft. The event (>K:TOGGLE_WATER_RUDDER), for example, can be triggered and trap routines processed in an aircraft that is not a float plane. Same thing for helicopter events; their events and traps will be processed in any aircraft.
As for associated simulation variables, however, following an event the sim will update only those A:Vars that are active for the current aircraft. (A:WATER RUDDER HANDLE POSITION, percent) will return a non-zero value only if the current aircraft contains water rudders.
This can be helpful to know because sometimes a user may want to ‘hijack’ an otherwise useless event to accomplish an un-related chore. A recent FSDeveloper forum thread discussed use of the default water rudder toggle keyboard assignment (ctrl+W) to actually perform a prop reversing routine in a non-seaplane aircraft. The event trap routine, below, is performed whenever keyboard ctrl+W is pressed. It has nothing to do with water rudders, but that doesn’t matter.
<Keys> <On Event="TOGGLE_WATER_RUDDER"> (L:Reversing,bool) ! (>L:Reversing,bool) (L:Reversing,bool) if{ 491 (>K:THROTTLE_SET) -491 (>K:PROP_PITCH_SET) } els{ (>K:PROP_PITCH_LO) } </On> </Keys>
Note: Prop reverse action will simulate correctly only on aircraft whose model and aircraft.cfg file are already appropriately configured, but the <On Event="TOGGLE_WATER_RUDDER"> trap above will always fire the THROTTLE_SET and PROP_PITCH_SET events.
AIRCRAFT LOAD AND RE-LOAD ISSUES
When an aircraft loads or is re-loaded, gauge script begins running slightly before the sim’s event and trap handling system is operational. Consequently, if events are fired as part of an <Update> initialization sequence, event and trap errors may occur where the events are apparently not processed, or may seem to be processed incorrectly.
In fact, however, triggered events are queued, and the queued events plus trap routines are processed all together when the event handling system finally becomes functional. The length of delay is variable, on the order of 10s of update cycles, and is dependent upon how and when the aircraft is loaded, cache, where the gauge is defined in the panel.cfg, etc. To deal with this, event triggering in an <Update> initialization sequence should be delayed until the sim’s event handling system is ready.
There are a couple of approaches to construct the delay. The first invokes a cycle counting routine; the other lets the sim tell you when it’s ready.
Consider the initialization sequence below. The intention is to set one notch of flaps when the aircraft loads. As written, however, that is not what occurs.
Initialization Sequence: <Update> (L:MyVarA, enum) 0 == if{ (>K:FLAPS_INCR) (L:MyVarB, enum) ++ (>L:MyVarB, enum) } </Update>
Event Trap: <Keys> <On Event="FLAPS_INCR"> (L:MyVarA, enum) ++ (>L:MyVarA, enum) </On> </Keys>
On my system, nothing appears to happen until the 20th cycle after the gauge initialization sequence starts running, when the following is returned:
3 (A:FLAPS HANDLE INDEX, enum) 100 (A:FLAPS HANDLE PERCENT, percent) 19 (L:MyVarA, enum) 19 (L:MyVarB, enum)
The flaps have been increased to the maximum, 100% position, not just one notch, and both MyVarB and MyVarA are 19. This result seemingly does not make sense.
Without delaying execution of the initialization sequence, the (>K:FLAPS_INCR) event and it’s associated event trap responsible for incrementing MyVarA, is queued each cycle until the event handling system is ready, at which point the queue is processed at once: the flaps are incremented 19 times as is MyVarA which returns 19, not 1.
Cycle Counting approach: If <Update> is edited to include a cycle skipping delay after aircraft reload (the Event Trap is the same as above),
<Update> (L:CycleCounter, enum) 20 <= if{ (L:CycleCounter, enum) ++ (>L:CycleCounter, enum) } (L:MyVarA, enum) 0 == (L:CycleCounter, enum) 20 >= and if{ (>K:FLAPS_INCR) (L:MyVarB, enum) ++ (>L:MyVarB, enum) } </Update>
then the following is obtained. One notch, only, of flaps and both MyVarA and MyVarB are 1.
1 (A:FLAPS HANDLE INDEX, enum) 33.3 (A:FLAPS HANDLE PERCENT, percent) 21 (L:CycleCounter, enum) 1 (L:MyVarA, enum) 1 (L:MyVarB, enum)
In this case, the 20 cycle delay provided sufficient time for the event and trap handling system to load before the initialization sequence containing (>K:FLAPS_INCR) was executed. The event trap was processed in the same cycle, incrementing MyVarA from 0 to 1, and thereby shut off the init sequence in the next cycle. The event fire initialization sequence now works as intended.
Note: <Update> could have been written:
<Update> (L:MyVarB, enum) 0 == if{ (>K:FLAPS_INCR) (L:MyVarB, enum) ++ (>L:MyVarB, enum) } </Update>
and the FLAPS_INCR event and trap would have been triggered and processed only once. For the purposes of this wiki, it was written as (L:MyVarA, enum) 0 == with the incrementer in the Event Trap rather than in Update to shed light on the event handling system delay.
Let FS tell you when it’s ready: In another FSDeveloper thread, Rob Barendregt proposed an elegant and more fool-proof method of introducing the necessary delay. He suggests to simply fire an event, in his case, the infrequently used
CABIN_SEATBELTS_ALERT_SWITCH_TOGGLE
event, and when its associated simulation variable,
(A:CABIN SEATBELTS ALERT SWITCH, bool)
returns 1 that means the sim's event handling system is operating. Following that, his desired initialization events can be executed successfully.
A slight clarification is that if event traps are included, they will not execute until the cycle following (A:CABIN SEATBELTS ALERT SWITCH, bool) = 1.
C-code version: A "C" language version of Rob Barendregt's method was written by Dai Griffiths and posted in this FSDeveloper thread
“RACE” CONDITION - CONSTANT EVENT FIRING
As commonly defined in XML related discussions in this forum, a Race Condition is an uncontrolled and likely unintentional, repetitive event firing situation.
It’s an issue because continuously firing events can cause unforeseen errors across the gauge set and potentially interfere or conflict with other commands being received. Race conditions can adversely affect stream code from <Update> and <Element> sections as well as event code from <Mouse> and <Keys> sections in any gauge, not just the gauge that is firing the event. The culprit gauge (or controller) may be difficult to identify without use of an event logging application. Doubly difficult if your panel contains a mix of XML and C/C++ gauges.
To mitigate, there are a few best practices to incorporate in XML:
- Take care when including an event trigger in an <Update> section. Unless its execution is conditional, it will fire continuously. Add a check, or condition that will limit the event to one firing only. A good example of such logic is included in this FSDeveloper post by Roman Stoviak (see response #16). - Place the event trigger within a mouse click when applicable. That way, the event is sure to be fired only once unless Repeat="Yes" is invoked for example, in connection with a VOR1_OBI_INC event.
TOGGLE EVENTS
Event toggles come in two varieties; those that require an argument and those that do not. Most event toggles are of the second type and simply reverse the state of the associated simulation variable. If the variable was “On”, it now becomes “Off”. If it was “Off”, it now becomes “On”.
The current state of the variable can be tricky to keep track of in a panel where multiple gauges can trigger the same event toggle. To mitigate this, most contributors to this forum suggest instead of using toggles, use _SET, _ON, or _OFF events to explicitly set the state of the variables. TOGGLE events are best avoided or limited to only a single gauge in the gauge set.
** (>K:MIXTURE_RICH) results in (A:GENERAL ENG MIXTURE LEVER POSITION:1, percent) = 100 16383 (>K:MIXTURE_SET) results in (A:GENERAL ENG MIXTURE LEVER POSITION:1, percent) = 99.9939 16384 (>K:MIXTURE_SET) results in (A:GENERAL ENG MIXTURE LEVER POSITION:1, percent) = 100 Consequently, if you want 100.000% full rich, then use (>K:MIXTURE_RICH), best, or 16384 (>K:MIXTURE_SET), second best. A similar situation may apply to other events, but experiment to be sure. 16383 = 14 bit binary 11 1111 1111 1111
Acknowledgments: Tom Aguilo, Bill Leaming, Paul (Gypsy Baron), Rob Barendregt, Manfred Jahn, Doug Dawson, Roy Holmes, Roman Stoviak, Arne Bartels, Dai Griffiths
rpmc edited April 2018