Scripting with Lua

The Storyboard Lua API (Lua API) gives developers access to the Engine though a Lua scripting interface.  This API is a library of functions which allow interaction with the Engine by manipulating data and working with events and user interface components.  Through the Storyboard Lua API developers can:

  • Get and set data values from the model

  • Inject application events

  • Manipulate model objects such as controls/layers

The Storyboard Lua plugin is built on top of the standard 5.1 release of Lua available from www.lua.org. While the core Lua interpreter is unchanged from the standard release, two additional modules have been incorporated to facilitate development with Storyboard:

The bitwise manipulation module (bit32) from Lua 5.2 has been built-in to this Lua plugin. This module provides a native implementation of several standard bit operations, including those required for text conversion to/from UTF-8. The documentation for the bitwise functions available from this module can be found in the Lua 5.2 Reference Manual
The Storyboard module (gre) is included that provides function extensions to manipulate and work with the currently active Storyboard model. This module also incorporates the Storyboard IO communication API that can be used to send events to external programs.

Lua Action Callback Function

When a Lua callback function is invoked by the Lua action it will be invoked with a single parameter as in the following prototype:

script_function_name(mapargs)

The function argument mapargs , is a Lua table whose keys provide the context in which the action is being invoked along with any action specific argument and parameters. This context includes the application, screen and control the action was associated with, the currently focused control, any arguments provided to the action as well as all of the event data that cause the action to fire.

By default the context is populated with keys lazily which can improve callback performance, but can make certain meta-operations a challenge such as deep table duplication or serialization. By accessing one of the members of the context listed below you will trigger the table to be populated. For most situations, this is completely transparent. Alternatively you can use a command line option to Lua to always pre-populate the table for Lua callbacks:

-olua,lazy_context=0

The following keys are available inside the context’s table but may be nil if not applicable for the context.

context_app

The application context of the current action

context_screen

The screen context of the current action (the current screen)

context_layer

The layer context of the current action (the current layer)

context_group

The group context of the current action (the current group)

context_control

The control context of the current action (the current control)

context_row

If the context_control is a table then this is the row index of the current cell

context_col

If the context_control is a table then this is the column index of the current cell

active_context

The fully qualified name of the model object that invoked the action. If that object is the application, this will be the empty string.

context_event

The name of the event the triggered the action

context_event_data

A pointer to a Lua table containing any event data. The event data is different for each event and is defined in the event definition.

A Lua type called 'context' has been defined inside Storyboard's custom Lua module 'gre' to represent the mapargs object for the purpose of auto completion inside the editor. However since Lua is untyped by default, a Doxygen comment describing the mapargs parameter must be added to a function in order to get auto completion on it, as follows:

--- @param gre#context mapargs
function CBMyFunc( mapargs )
    -- I now have auto completion on mapargs
end
                 

Functions created by Storyboard will automatically be prepended with this comment.

Example of using context data:

--- @param gre#context mapargs
function CBGetContext( mapargs )
    print("Triggered by event : " .. mapargs.context_event)
    print("Event was targeting : " .. mapargs.active_context)
end
            

Passing Extra Parameters to Functions

Lua actions are identified using an action type of Lua and setting the specific Lua function and extra parameters (if required) in the action arguments.  Any extra parameters will be transferred directly to the Lua function through first argument (a Lua table) and the data can be accessed by using the parameter name as the table index.

--- @param gre#context mapargs
function CBUserParameter( mapargs )
    local p = mapargs.paramter1
    print("my_lua_func was passed : ".. tostring(p))
end
            

Advanced Lua Callbacks

Advanced Lua callbacks give developers more powerful ways to organize and architect their code into modular and well-encapsulated behavior. The Lua script action gra.lua has always been able to call global functions in your Lua scripts, developers have much more flexibility with their Lua function callbacks.

Advanced callbacks allow developers to invoke functions that are nested in other tables. This can be done with a . for tables or a: for Lua object methods where self is the first parameter. As long as the top-level variable is global, nested objects and functions can be found.

An advanced Lua function may look like parent.child.grandchild.func if the first parameter of the func function is mapargs, or like parent.child.grandchild:func if the first parameter of the function is self and the second is mapargs, where self is parent.child.grandchild in this example.

lua_advanced_entry.png

Selecting the ADVANCED entry will open the Advanced Lua Callback Function Wizard which allows developers to select an instance of a Lua Object and a method to invoke in the Lua Action.

lua_advanced_callback_wizard.png

Example Advanced Lua Callback Scripts

This section shows some snippets of code to define and create Lua objects and how to annotate them appropriately for Lua to be able to infer types of the variables in your scripts. To get more detailed information and examples of Lua code annotations, refer to.

scripts/callbacks.lua

This script has a global table that holds onto all the buttons of our application. The use of @field in this manner helps Storyboard to understand the contents of the buttons table and their types and provide more information back to the developer.

local ToggleButton = require("widgets.toggleButton")

---
--@type buttons
--@field widgets.toggleButton#toggleButton togglePower
--@field widgets.toggleButton#toggleButton toggleStar
buttons = {}

---@param gre#context mapargs
function CBInit(mapargs)
  buttons.togglePower = ToggleButton.new("Layer.power", false)
  buttons.toggleStart = ToggleButton.new("Layer.start", false
end

scripts/widgets/toggleButton.lua

This script is an example of a toggleButton class. An advantage of Object Oriented programming is that you can maintain state together with the functionality of the object. This module returns a table with a single method on it, new and it returns a toggleButton#toggleButton. This object created with new has a single method, press which takes two parameters, self and mapargs. Pressing an instance of a toggleButton will flip it' it's self.state and trigger "buttonUp" or "buttonDown" animation accordingly.

---@module toggleButton
local ToggleButton = {}

---
--@type toggleButton
--@field gredom#control control
--@field #table context
--@field #boolean state
local toggleButton = {}

--- Create a new toggle button
--@param #string controlName fully qualified name of control
--@param #boolean initialState the initial state of the button
--@return #toggleButton
function ToggleButton.new(controlName, initialState)
  local button = {}
  setmetatable(button, {__index=toggleButton})

  button.control = gredom.get_control(controlName)
  button.context = {context=controlName}
  button.state = initialState
  return button
end

---
--@param #toggleButton self
--@param gre#context mapargs
function toggleButton:press(mapargs)
  self.state = not self.state
  if(self.state) then
    gre.animation_trigger("buttonDown", self.context)  
  else
    gre.animation_trigger("buttonUp", self.context)
  end
end

return ToggleButton

Lua Execution Environment

The Storyboard Engine Lua plugin provides a slightly different execution environment when compared to normal Lua script execution.

Normally a single Lua script serves as the starting point of script execution and all other scripts would be included using the Lua require() declaration. The Storyboard Lua plugin provides a slightly different loading behavior in that it will pre-load all of the Lua scripts contained in the scripts directory at engine initialization time. The load ordering can be controlled by using the require statement to explicitly order dependencies. Since the require mechanism is used to perform the loading, any project files that use the same names as built-in Lua modules (i.e. table.lua, string.lua or io.lua) will generate a load time warning indicating the potential load time resource collision. Care should also be taken with Lua and asset file naming standards with respect to portability as some OSes are case sensitive whereas others are not.

It should be noted that in most OSes the filesystem directory API does not apply any ordering and only utilities like ls and dir apply sorting logic primarily for display purposes. When the Storyboard engine loads Lua files, it loads them based on unsorted content in a directory and hence to manage dependencies as mentioned above the use of Lua require() declarations is recommended good practice.

A side effect of this early module loading and execution is that any Lua script that is located outside of function blocks will have the opportunity to run before the application is fully initialized. This can be used to seed early execution environments or load preferences before the UI is in place and ready to render. Alternatively, this early initialization is possible by binding a callback to the gre.init event.

In addition to loading all of the script files in the scripts directory, the Lua plugin modifies the package.path variable and ;; default search path to automatically search the scripts directory.

A convenience variable, gre.SCRIPT_ROOT is pushed into the execution environment that contains the path from the current working directory to the scripts directory. This variable can be used to locate additional resource files or to include extra script directories in a manner that is relative to the overall deployment bundle. Each seperate path is delineated by a single ';'.

print("Script base directory: " .. tostring(gre.SCRIPT_ROOT))
-- Look for additional module files in the scripts/modules directory. 
-- This will search the added directory first
package.path = gre.SCRIPT_ROOT .. "/modules/?.lua;" .. package.path

Asynchronous Lua Support

The asynchronous Lua support is provided in two fashions:

Lua Action: To create an independent Lua thread in response to an action, the user can add an 'async' parameter to any existing Lua script and it will automatically create and run that action outside of the main UI thread.

Lua Script There is a new Lua API call gre.thread_create() that takes a single parameter which is a function to execute. This function will be executed and scheduled to run in an independent thread of execution.

Threads are created using the system's underlying native operating system thread support. Operations are synchronized explicitly through locks in the Lua VM, however there is no explicit support for data synchronization (i.e. mutexes, condition variables)

The suggested communication pattern for inter-thread communication is to use Storyboard IO to inject event data into the system. This is similar to the idea behind LuaLanes or WebWorkers where inter-task communication is based on message passing.

Threads will be hard-terminated at exit. Clients should establish their own protocol where a soft shutdown is required to trigger any asynchronous threads to terminate.

Lua Debugger

The Storyboard Lua Debugger enables the developer to monitor the flow of execution of the Lua scripts used by the Storyboard application. Using the debugger it is possible to step line by line through a Lua script while examining the variable values that are being used by the Lua functions.

Note

The Lua debugger is configured such that it can only be used with the simulator runtimes on the host platforms that support Storyboard Designer. For assistance in configuring the debugger for embedded targets, contact Crank Software support (support@cranksoftware.com).

The Designer debugging environment communicates with the application's Lua script plugin using network sockets in a client/server model. The Storyboard application acts as the client and is controlled by the Designer debug environment which acts as the server.

Creating and launching the debug server is an automated process. To configure Lua debugging it suffices to take the following steps:

  1. Create a Storyboard application launch configuration. This topic is discussed in more detail in the chapter Simulating your Application.

  2. Enable debugging in the Storyboard Lua plugin

  3. Launch the Storyboard application

The first two steps are part of a one-time configuration required to set-up the simulator launch configuration. After the initial set-up, only the last step needs to be performed in order to launch an application with the Lua debugger running.

When enabling the Lua debugger you may find it convenient to create a second launch configuration for the same project with debug enabled. This will allow you to quickly switch from a development/debug simulation to a standard simulation. To enable the Lua debugger simply check the Enable Lua Debugger option associated with the Lua plugin options.

storyboard_sim_config_debug.png

Once the Lua debugger is enabled, run the Storyboard application the same as you would a normal simulation.

This will launch the Storyboard application and at the same time initialize the debug client within the Storyboard application and the server running within the Designer environment. The two will connect automatically over a network socket and your application should immediately begin running. To confirm that the connection has taken place and the Lua debugger is running, check the output console for the following:

storyboard_lua_debug_connect.png

If you change to the Debugger perspective, the Debug view should look like this:

storyboard_lua_debug_connect_2.png

You are now able to start debugging your Lua code

The Lua debugger, when a breakpoint is activated will automatically switch to the Debug Perspective. This perspective provides an alternative layout of views that are specifically related to debugging activities.

storyboard_lua_debug_view.png
Variables

Variables are displayed in a hierarchical manner in the Variables view. Global variables are displayed in a special table and listed as globals while function parameters and local variables are displayed as top level elements. Strings and numeric values are displayed directly, while tables can be navigated by double clicking their nodes and driving down.

Breakpoints

Breakpoints can be placed directly in the editor for Lua script files. Breakpoints can be toggled on/off by double clicking in the margins of the Lua script file where the execution should be stopped. While it is possible to place breakpoints on all lines of a Lua script file, not all lines are breakable due to the manner in which the Lua script is executed. Declaration breakpoints may not resolve to an execution stop point in the script.

Breakpoints can also be enabled and disabled and removed by selecting them from the listing in the Breakpoints view and performing the appropriate operation. It is also possible to navigate to directly to the script source file from withing the Breakpoints view by right clicking and selecting Go To File.

storyboard_lua_debug_breakpoints.png
Stepping, Continuing and Terminating

The Debug view provides a list of active debug sessions and the execution stack trace when a session is at a breakpoint. Once a breakpoint is hit, then it is possible to single step to the next line of code, to continue the execution until the next breakpoint is encountered or to terminate the application using the view's toolbar commands.

storyboard_lua_debug_connect_2.png

Lua Executables

Storyboard includes a few stand-alone Lua executables packaged for the convenience of customers who are working with Storyboard in environments where it is possible to use shell programs to interact with the environment, most notably Windows, Linux, Mac and QNX environments.

sblua

This is the standard Lua command line interpreter lua that has been linked against the Storyboard Lua shared library libsblua.so. All enhanced Storyboard functionality that is not associated with an Storyboard application can be accessed using this interpreter. This can be specifically useful for creating test and simulation scripts that generate Storyboard IO calls using the gre.send_event API.

sbluac

This is the standard Lua command line compiler luac that has been linked against the shared library libsblua.so. This utility can be used to pre-compile Lua script source files into platform independent byte code for faster load times. This utility is invoked automatically during a Storyboard Export if the Generate precompiled Lua selection is made in the export configuration.

The bytecode is platform independent and can be used the in the same way as non-bytecode Lua files by the Storyboard Engine at runtime. Note that currently, our Storyboard engine only looks for .lua extensions, so if you would like to use bytecode files, make sure to give them a .lua extension. Please be careful not to overwrite your Lua script files, because they cannot be retrieved from the bytecode files.

To see the usage parameters just type 'sbluac' on the command line with no other arguments.

usage: sbluac [options] [filenames].
Available options are:
                  -        process stdin
                  -l       list
                  -o name  output to file 'name' (default is "luac.out")
                  -p       parse only
                  -s       strip debug information
                  -v       show version information
                  --       stop handling options
                                                

A basic compilation to produce a bytecode file with the name luac.out, containing debugging information, would look like this:

sbluac input.lua
                                                

To name the output bytecode file something other than luac.out, use this:

                                              
sbluac -o output.luac input.lua
                                                

To strip out debugging information, use this:

sbluac -s -o output.luac input.lua                                              
                                                
Was this article helpful?
0 out of 0 found this helpful