Apps

Apps

Introduction

The VisionAppster Engine can be thought of as a platform-independent light-weight operating system that runs on top of a real operating system. In the operating system analogy, apps are platform-independent executables.

Apps are written in QML. The structure of the app is built using QML components while the application logic is written in JavaScript. QML makes it easy to create user interfaces to vision apps, but it is also used to build server apps that run in a displayless environment.

Usually, apps are created with the Builder and run by the Engine service. To create an app that can be run by va-run or the Engine service you just need to export the app from the Builder as an .app file.

It is also possible to create apps manually yourself. This document gives an overview of how apps can be built and run.

Main app component

At a bare minimum, an app consists of a single qml file (e.g. app.qml) with the following contents:

import VisionAppster.Core 1.0

App
{
  function start() {}
}

This declares a single QML component of type App. The component must contain a start function, which will be invoked when the app is started. Unlike the main function in many programming languages, the purpose of the start function is not to run until the program ends. Instead, it is an event handler whose purpose is to prepare the app for execution.

You can run this app with va-run:

va-run app.qml

This will cause the Engine to start the app and remain listening to events from various sources, which you haven't defined yet. To stop the Engine and hence the app, you need to press Ctrl+C or kill the process otherwise.

If the app component defines a function called stop, it will be called before the app exits. The stop function can return false to indicate that it cannot be stopped right now. It is however always possible to kill an app that refuses to shut down cleanly. When shut down, the Engine first tries to stop all running apps and then kills them.

import VisionAppster.Core 1.0

App
{
  function start() {}
  function stop() {
    console.log("Stopping");
  }
}

To stop an app, call AppManeger.stop(appInfo.pid). To abruply stop execution, one can call die() in the app's context. The function takes an optional error message as a parameter.

Structure of an app

In addition to the main component, an app may contain any number of other components, JavaScript files and resources such as images and text files. You are free to arrange these in any directory structure as far as the root directory contains an app description file called appconfig.json.

The app description file defines the entry point of the app and any other meta-data that may be used by the app itself or other apps. For example, the Launcher and Configurator apps use the "appName" meta-data entry as the user-visible name of an app. A minimal configuration file could be like this:

{
  "appName":       "My Application",
  "mainComponent": "app.qml",
  "appId":         "34e9955d-bed3-4938-afc5-feb0a50c4e1b"
}

At run time, the contents of the app configuration file are accessible through a global object called appInfo. The object contains not only the contents of the configuration file but also information collected at start-up:

uri : string
The full URI of the app, either an absolute path with a file:// schema or a HTTP URI. This field will always be set.
repositoryUri : string
The URI of the application repository this app was loaded from. This field does not exist if the app was not started from a repository.
pid : integer
The process ID of the app.
args : array<string>
Start-up (e.g. command-line) arguments.
appId : string
The unique ID of the app. If this is not given in the configuration file, one will be automatically generated from the URI of the app.

The following code prints out the appName and pid of the app.

import VisionAppster.Core 1.0

App
{
  function start() {
    console.log(appInfo.appName); // from appconfig.json
    console.log(appInfo.pid);     // generated at start-up
  }
}

Transferring apps as directory structures would not be too convenient. Therefore, an app can be packed into a single file by simply zipping the app's directory structure. To separate app zips from ordinary ones, the .zip extension must be changed to .app. This is exactly what the Builder does when exporting an app.

App repositories

An app repository is simply a directory with a text file called appindex that lists either relative or absolute app URIs. For example, the following file system structure represents a repository with two apps:

/approot/
/approot/appindex
/approot/app1/
/approot/app1/appconfig.json
/approot/app2.app

The contents of appindex are as follows:

# Comment lines start with a hash.
# The slash at the end of a path name is important.
app1/
app2.app

It is also possible to create an application repository that provides no apps by itself but merely gives a list of absolute app URIs. An example of such appindex could be like so:

http://example.com/fancyapp/
http://192.168.0.1:8888/rather/long/path/here/isnt/it/
https://secure.lan/Secret.app

Empty lines and lines starting with a hash (#) are ignored.

Continuing with the operating system analogy app repositories can be thought of as mounted file systems that contain apps.

When the Engine is started as a service, it creates an automatically managed app repository. On Linux, this repository is located either at /opt/visionappster/uploaded (system service) or ~/VisionAppster/uploaded (user service), on Windows in apps\uploaded under the installation directory. The appindex of an automatically managed repository is automatically updated whenever .app files are added to or removed from the directory.

On Threading

In the VisionAppster Engine, heavy processing such as image analysis is not done in the main thread. QML and JavaScript are however inherently single-threaded. Therefore, the JavaScript code defining application logic will always be run in a single thread. Furthermore, the design choice has been to execute the application logic JS code of all running apps in the main thread. This makes inter-app communication easier but has the drawback that a poorly behaving app may hang others. If an app runs into an eternal loop, the main thread will be blocked and no events will be delivered to any app.

It should be noted that JS code embedded in a processing graph can be run in parallel threads. This is possible because interactions between other components of the app are limited by the interface of the tool.