X-Plane / XLua

Ultra-Minimal Lua Plugin for X-Plane
MIT License
31 stars 18 forks source link

XLua

XLua is a very simple Lua adapter plugin for X-Plane. It allows authors to create and modify commands and add create new datarefs for X-Plane aircraft.

XLua's functionality is in its core similar to Gizmo, SASL and FlyWithLua, but it is much smaller and has only a tiny fraction of these other plugn's functionality. The goals of XLua are simplicity, ease of use and to be light weight and minimal. XLua is meant to provide a small amount of "glue" to aircraft authors to connect art assets to the simulator itself.

XLua is developed internally by Laminar Research and is intended to help our internal art team, but anyone is free to use it, modify it, hack it, etc.; it is licensed under the MIT/X11 license and is thus Free and Open Source Software.

XLua is not meant to be an "official" Lua plugin for X-Plane, and it definitely does not replace any of the existing Lua plugins, all of which have significantly more features than XLua itself.

Release Notes

1.3.0r2 - 09/05/2024

1.3.0r1 - 08/09/2023

1.2.0r1 - 02/16/2022

1.1.0r1 - 08/21/2021

Features:

1.0.0r1 - 11/6/2017

Bug fixes:

1.0.0b1 - 11/26/16

Initial Release

Documentation

Script packaging and basic structure

XLua scripts are organized into “modules” - each module is a fully independent script that runs inside your aircraft.

The installation of the plugin looks like this on disk:

/My airplane
    /plugins
        /xlua
            /lin_x64 <-- these contain the real binaries
            /mac_x64
            /win_x64
            init.lua  <-- part of the plugin, do not modify
            /scripts  <-- your aircraft specific scripts
                /adf
                    adf.lua
                    adf_helpers.lua
                /stec_500
                    stec_500.lua

In this example, there are two modules installed: “stec_500” and “adf”. The main lua file for a module has the same name as the module itself.

The adf module has a second lua file - for this file to be used, it must be “included” from adf.lua using dofile("adf_helpers.lua"). Only the “main” lua script in a module is run automatically.

Sub-folders in the scripts folder are not allowed - all modules must be within “scripts”.

The file “init.lua” is part of the XLua plugin itself and should not be edited or removed.

How a Module Script Runs

When your aircraft is loaded (before the .acf and art files are loaded) the XLua plugin is loaded, and it loads and runs each of your module scripts.

When your module’s script is run, all Lua code that is outside of any function is run immediately. Your script should use this immediate execution only to:

All other work should be deferred until you receive additional callbacks.

Once the aircraft itself has been loaded, your script will receive a number of major callbacks - these callbacks run a function in your script if a function with the matching name is found. You do not have to implement a function for every major callback, but you will almost certainly want to implement at least some of them.

Besides major callbacks, one other type of function in your script will run: when you create or modify commands and when you create writeable datarefs, you provide a Lua function that is called when the command is run by the user or when the dataref is written (e.g. by the panel or a manipulator).

Major callbacks

The non-function part of your script is run top-to-bottom when your airplane is loaded - this is the right time to create new commands and datarefs.

Most work is done in response to major callbacks - functions that are automatically called in your script during X-Plane’s execution. Those names are:

These functions take no arguments.

Global variables provided by XLua

Xlua provides two global variables that are available from all callbacks:

Datarefs

Datarefs work just like variables - that is, once you make a variable from a dataref, you just use it and change it like a regular lua variable. Lua supports 3 kinds of datarefs:

x = find_dataref("dataref_name")

This creates a new lua variable x that is mapped to the named dataref that already exists. Its type (string, number or table) depends on the type of the named dataref. Use this to map X-Plane’s datarefs into your plugin.

x = create_dataref("name", "type")

This creates a new read-only dataref name. Type must be one of:

Note that this dataref can be changed by your script, e.g. by doing x = 5. But it cannot be changed by X-plane or other plugins.

x = create_dataref("name", "type", my_func)

This creates a writeable dataref; the function my_func (which takes no arguments) is called each time a plugin OTHER than your script writes to the dataref. It is okay to have the function do nothing.

Command classes

Commands are objects that you can act on. You start by finding or created a command, e.g.

pause_cmd = find_command("sim/operation/pause_toggle")

You can then act on it, e.g.

pause_cmd:once()

Will pause the sim. Command objects have three methods:

Make sure every call to start() is balanced by a stop()!!!

You can also create your own commands:

c = create_command(name, description, function)

The name is the permanent name, e.g. my_namespace/weapons/fire_torpedoes The description is a human readable name, e.g. "This fires the photon torpedos". The function is a lua callback that is called when the command is run:

function command_handler(phase, duration) 
end

The phase is an integer that will be 0 (start) when the command is first pressed, 1 (continue) while it is being held down, and 2 (stop) when it is released. For any command invocation, you are guaranteed exactly one "start" and one "stop", with one or more "continue" in the middle if the command is "held down". But note that if the user has multiple joysticks mapped to the command you could get a second "start" while the first one is running.

The duration is how long the command has been held down in seconds, starting at 0.

create_command returns a command object but you can ignore that object if you just need to make a command and not run it yourself.

You can customize existing commands in three ways:

c = replace_command(name, handler)

This takes an existing command (e.g. one of X-Plane’s commands) and replaces the action the command does with your handler - the handler has the same syntax as the custom command handler - it takes a phase and duration.

c = wrap_command(name, before_handler, after_handler)

This takes an existing command (e.g. one of X-Plane’s commands) and installs two new handlers - the before handler runs before X-Plane, and the after handler runs after. You can use this to “listen” for a command and do extra stuff without losing X-Plane’s capabilities. You must provide both functions even if one does nothing.

c = filter_command(name, handler)

This allows you to provide a function that defines a 'filter' for a command. The filter will determine whether the command is actually invoked or not, or continues to be invoked if it is already active. As with other command modifiers such as wrap_command or replace_command, this returns a reference to the command. The filter function takes no parameters, and should return boolean true if the filtered command is to proceed and boolean false if the filtered command is to be blocked.

The filter function will be called for each "start" and "continue" phase message. It is not called for the "stop" phase because this means the command is ended and so does not need filtering. You may return false at any time so there is no need to differentiate between "start" and "continue".

CAUTION: When a command is filtered and you change the filter state from true to false while the command is in the "continue" phase:

Timers

You can create a timer out of any function. Timer functions take no arguments. Several commands are provided:

run_at_interval(func, interval)

Runs func every interval seconds, starting interval sections from the call.

run_after_time(func, delay)

Runs func once after delay seconds, then timer stops.

run_timer(func, delay, interval)

Runs func after delay seconds, then every interval seconds after that.

stop_timer(func)

This ensures that func does not run again until you re-schedule it; any scheduled runs from previous calls are canceled.

is_timer_scheduled(func)

This returns true if the timer function will run at any time in the future. It returns false if the timer isn’t scheduled or if func has never been used as a timer.

get_timer_remaining(func)

This returns the number of seconds remaining before the given timer function will be called. If the timer function has not been registered, false is returned.

Debugging facilities

Using print("") will output to the terminal on Mac/Linux/Windows (provided X-Plane has been started from the command line), and will also output a line to the X-Plane log.txt file, prefixed with LUA:

Using "Developer > Reload the Current Aircraft and Art" X-Plane menu will reload your XLua script files.

Since 1.2.0, you can also reload your scripts via the aircraft menu, or via the laminar/xlua/reload_all_scripts command.

On some complex aircraft, you might also need to reset your scripts if the "Start with engines running" checkbox on the "Flight Configuration" screen changes. In this case, you can insert the XLuaReloadOnFlightChange() instruction once on any of your scripts, in any place. Note that this DOES NOT invoke an immediate reload of the scripts, but only flags the aircraft scripts to be reloaded when the flight configuration changes, which is something that XLua didn't do before.

FAQ