User:Philosopher/Advanced Input Programming
Work in progress This article or section will be worked on in the upcoming hours or days. See history for the latest developments. |
While Input device contains information to get you started with input hacking, this page is meant to provide more advanced uses of the building blocks represented in the former article. Presumably, if you are reading this page, then you should have some basic bindings up and running or at least have found what your joystick's "name" (or id) is.
Using the Nasal scripting language with joysticks
Currently there are three ways to use Nasal in joysticks:
- In the initialization section – aka any input/joysticks/js/nasal/script[n] nodes
- In a binding with command=nasal
- Or in a binding with a custom fgcommand
The first is really useful to prepare the namespace, load libraries and files, etc., and I use it to load my two libraries (functions.nas and hardware.nas) and my aircraft-specific initialization script (initialization.nas). The second I typically use as a wrapper to execute functions appearing in the other files (with the exception of the mode switcher which is coded in the main file). The last is available as of 2013 but has not been used; James Turner added support for custom fgcommands via an addcommand() function that will allow bindings to indirectly execute Nasal code.
One of the goals of my joystick programming has been to implement everything in a modular and reuseable way. As mentioned I make extensive use of libraries that I have coded and I hope that others can use these libraries as well, since they provide advanced features that are not normally available – and I would hate to make anyone recode what I have already done. I also aim to make the most use of the property tree since FlightGear was designed around it and since it is easiest for the end-user, they can just modify and XML file or use the property browser to do such changes. (At present all of the settings and most of the runtime state is stored in the property tree; some of the classes I made use their own Nasal storage.)
One of the aims of being generic is the ability to live where it happens to find itself in, though it is practically constrained to /input/joysticks/js[n] and a __jsN namespace. The name of the namespace is important for me to figure out: since I use io.load_nasal I need to figure out the name of the namespace, not its hash, but this I can do using some calls to cmdarg(). Before we begin, I would like to point you to my post in the FlightGear forums that contains my joystick files. I will be basing the rest of this tutorial off of these and it is recommended reading: [1] .
Initialization
I do several things in initialization. In order they are:
- Create a Joystick object with data about the joystick, including path in the property tree and Root in the computer's file system. It also is an instance of the props.Node class and one can use Joystick.getNode("axis") to get the first axis, for example. This ensures the locality of nodes in the tree, like the modifier value, which is very important for maintaining good code.
- Initialize some nodes and variables and provide a couple functions.
- Load functions.nas and hardware.nas, both in the same __jsN namespace as the nasal/script is getting run in (again, this is to ensure locality, also so that it works properly).
- Make my axis objects: aileron, elevator, rudder, throttle1, and throttle2 and point them to the setting nodes under their respective axes.
- Compile & run the self-load/script in initialization.xml so that aircraft specific settings are used.
- Cache throttle mode functions and set listeners to update them – ensuring that internal values reflect those in the property tree is another important design point, since what users see & change in the property browser should be what the joystick sees too.
Guide to libraries
hardware.nas
This library provides classes for buttons and axes. Buttons have several features (double-click and hold-down) and axes have tons of configuration options (too many, actually!). Both are documentated in the file and can be based off of a node for options (as I mentioned before, it currently cannot quite live in the node as it requires a Nasal hash for state information, but that will change in the future).
functions.nas
This one is bigger and less documented, so I will try and provide that documentation here.
Control function library: This library, implemented in lines 690-847, allows various control inputs (axis, buttons, etc.) to take control of an input so that only it can use it. It also has a prop member to allow speedbrakes on one plane to be under the /controls/flight/spoilers property, for example, and has get and set methods that makes use of the property (or throws an error if "prop" is not a member in the hash). It is currently being reworked, but basically everything should run through its get/set methods to ensure aircraft-specificy. The brakes are currently separate, but there's no need for that, the code could be adapted for the brakes to have different inputs so that trigger would have a value contributing to the result that is separate from axis, etc. The main concept of the "take control"/"release control" idea is that something (usually a button, except for the four main axes) will have default control of something and when something more powerful (an axis) comes along, that uses its unique ID ('axis') to force the other's requests to be ignored (since buttons should not compete with axes). When the axis is done, control goes back to default.
Modifier code: There are three things that make up the very simple modifier code: the mod_handled flag, the listener on Joystick.getNode("modifier"), and the modifier() function, in addition to the mod variable that represents the current state in Nasal-space (the official one is kept in the property tree, but having it mirrored as a Nasal variable makes typing easier). The listener allows resetting the modifier state from the property tree while the mod_handled flag prevents duplication of efforts – i.e. it prevents the listener from setting mod when the modifier() function has already updated that variable. The modifier() function just needs a /input/joysticks/js/button/number node and to be called from a Nasal binding – everything else is taken care of.
Flapscontrol-esque functions: These functions, including the "infamous" flapscontrol(), as well as gearcontrol() and tailhookcontrol() functions, are interfaces to both the control_functions library and the controls namespace wrappers (since, for example, controls.flapsDown often gets overridden by aircraft) that returns a message suitable for gui.popupTips. They are easily chained together, as can be evidenced by the clean/dirty code which only takes 1 line. The controls-namespace functions provides the mechanism to effect the action; the control_functions library only serves to make sure that an axis isn't controlling the same thing as well.
initialization.xml
This is a PropertyList-XML/Nasal/DSL hybrid file that is still evolving and getting changed. I recently (July 5th 2013) ported it from initialization.nas but have been reworking my integration with the control_function library. I originally used the same idea as the .nas file: three variables (now properties) to tell whether we had flaps, gear, and/or a tailhook (mainly because it would look bad in popupTips for the clean/dirty configuration code). Then I changed to an opt-out scheme, and I had to add <no-tailhook type="bool">true</no-tailhook> (and worse: <non-retractable-gear type="book">true</non-retractable-gear>) into the configs I was still converting, and now I have just started (July 8th) experimenting with a DSL to configure the controls. The current syntax is a comma-separated list of control names (with optional indices specifiers) to say that this control exists on this plane, with suffixes to configure parts of the control_function. Whitespace is blindly removed since I'm not running a Lexer to handle this.
Suffixes used (order doesn't matter):
- @ [property path or property path with [,]/ inside]
- This sets the home property for the control
- & { [opt. script] , [opt. script] , [opt. script] }
- Two Nasal scripts that are setter, getter, and stepper functions or the names of such (as opposed to directly using the property, which IMO would actually be the best way to do things).