Celx events

Key event, Mouse events, Tick event.

General

In Celestia version 1.5 events were introduced in Celx. They are however very poorly documented and their use is highly limited because of their incompatibility with the all important wait() function.

What we usually do in scripts is something called "procedural programming". In this type of programming, the program (or script) is the acting party and the user (íf any) is the reacting party. It has one specific starting point and a strict flow of control that is 100% predictable. It is the type of programming that we mostly find in batch processing. "Event driven programming" however we typically find in GUI (Graphical User Interface) processing environments like Windows. It is the type of programming where the user is the acting party and the program the reacting one. The flow of the program - what happens when, how long, how often, etc. - is determined by the actions of the user, not by the programmer. Is determined by which events take place when; events like clicking a button, typing in a field or choosing from a menu.

Event driven programming mostly comes down to reacting to signals from the host environment; mostly the operating system but in our case Celestia. Signals that indicate that a specific event has taken place and what the details of that event were. These signals are always received in the form of a package of data and are more commonly known as "messages". A typical event driven program consists of an endless loop doing nothing but reacting to messages, something like this:

function eventHandler(message)

if message.type == windowPaint then

return paintWindow(message) -- paint all the buttons, fields, etc., that make up the GUI window

elseif message.type == windowClose then

return saveAllData() -- save all the data before the program is closed

elseif message.type == mouseDown then

return processMouseDown(message) -- process a mouse up event somewhere in the window

elseif message.type == mouseUp then

return processMouseUp(message) -- process a mouse down event somewhere in the window

elseif message.type == .... then

        and so on .............
    else    

return false -- event type not interesting for program, let host handle it

    end
end
system.registerEventHandler(eventHandler)

After having been registered with the host system, eventHandler is called for évery event that takes place in the host, irrespective of whether the program has any use for it. "eventHandler" is the general event handler that handles all message whereas functions like "paintWindow" are specific event handlers that only handle messages of a given type. Event driven programming is about writing event handlers and is incompatible with procedural programming. This is also true in Cellestia, you can not have both a script ánd an event handler executing at the same time.

Structure

There are four types of events in Celestia; each described in more detail on the pages:

There is no need for a general event handler since Celestia wants you to specify the "specific event handlers" directly. There are only four of them so why not. All event types are controled by the same two functions:

Where string:eventtype is the type of event you want to handle, either:

  • "key"
  • "mousedown"
  • "mouseup"
  • "tick"

and function:eventhandler is the function that you wrote to handle it. Contrary to what it's name might lead you to believe, geteventhandler() does not return the address of the eventhandling function but a boolean value indicating whether a (custom) eventhandler is registered for a given event type (true) or not (false).

The eventhandler is a regular lua/celx function that receives 1 (one) parameter - the message - so it's declaration should look like:

function handlerName(message)
    ......
end

The message contains information about the event; for the "key" event for instance, it contains information about which keyboard key was pressed. Message has a structure that resembles that of a table with one or more fields and is approached in the same way. The message's fields and the information they contain depend on the type of event. For the mouse events the message contains the fields:

  • message.button - the button that was pressed
  • message.x - the x coordinate of the "clicked" point
  • message.y - the y coordinate of the "clicked" point

The handler function must return a value of either true or false in order to indicate to Celestia what it should do with the event. A return value of true tells Celestia to ignore the event and a value of false tells it to perform the default eventhandling on it.

Registereventhandler() does not stárt custom handling of an event, it only registers the handler with Celestia. The custom handing does not start until the script that registered it has finished. To revert Celestia to it's default eventhandlling, register an eventhandler with a value of nil. For the tick event for instance this would become:

  • celestia:registereventhandler( "tick", nil)

There can only be one custom handler active per event type at any one time. When a new handler is registered for a certain type, any previously registered handler for that type is canceled. Another way to cancel all active event handlers is to load and execute a new script.

Example

Custom event handling is easier to understand with a simple example.

01 -- Title: example eventhandler

02

03 -- keyHandler: an eventhandler for the "key" type

04    function keyHandler(message)

05 if message.char == "k" or message.char == "l" then

06 celestia:print("Key canceled = " ..message.char)

07 return true

08 elseif message.char == "g" then

09 celestia:print("Going to object")

10 return false

11 elseif message.char == "c" then

12 celestia:print("Centering object")

13 return false

14 elseif message.char == "x" then

15 celestia:print("Returning to default eventhandling")

16 celestia:registereventhandler("key", nil)

17 return true

18 else

19 celestia:print("Key passed = " ..message.char)

20 return false

21 end

22 end

23

24 -- the main script

25 celestia:registereventhandler("key", keyHandler)

26    observer = celestia:getobserver()
27    planet = celestia:find("Sol/Neptune")
28    observer:goto(planet, 20)
29    wait(20)
30    celestia:print("starting custom event handling")

Lines 03 - 22 form a "key" event handler; it responds to the user pressing a key on the keyboard. The key pressed is passed on as a string in message.char.

    • lines 05 - 07 block the default handling by Celestia of the "L" and "K" keys (return true), making it impossible to change the time rate using keyboard keys.
    • lines 06 - 13 print additional information when the "C" or "G" key is pressed but let the further handling of the event to Celestia (return false)
    • lines 14 - 17 stops the custom event handling by setting the event handler to nil; tells Celestia to not also do something with the keypress. (return true)
    • lines 18 - 21 print a line of text for any of the other characters pressed and lets Celestia do it's own thing with it (return false).

Lines 24 - 30 form the actual script. When the script is run, this is what gets executed.

    1. "keyHandler" is registered as the custom event handler for the key type.
    2. the observer travels to Neptune taking 20 seconds to do that.
    3. a message is shown that custom event handling is started.

Although the registration of "keyHandler" is the first thing that gets done, "keyHandler" is not activated as the custom event handler till áfter line 30 has been executed. Any key that gets pressed by the user during the 20 seconds of the travel to Neptune gets handled by Celestia's default key-event-handler; nót by "keyHandler".

After this script has finished executing and until told differently, Celestia will call "keyHandler" everytime the user presses a keyboard key and act according to the return value of true or false.

Incompatible with wait( ) !!

One of the most indispensable functions in celx is the wait() function. Without it the coordination of celx commands and Celestia activities would be impossible. But it is also the function that will bring any custom event handler to it's knees in no-time! If Celestia encounters a call to the wait() function in an event handler it will generate an error, complaining about an attempt to "cross the lua/c bridge". Celestia might write an error message to the log file, but it will nót display it on the screen. Instead it will quietly return from your custom event handler with a return value of false leaving you with the question what the .... is happening and why your event handler doesn't work even though you can't see anything wrong with it.

Although it does not have the "celestia:" prefix, wait() is not a default lua function but a product of the Celestia development team. It is a separate function, external to lua and written in c++ and that is what is causing the problem here. Celestia only allows you to execute standard lua and celx in an event handler. Anything else is forbidden and this includes external c/c++ functions; even if that is Celestia's own wait() function!

Since the wait() problem makes it impossible to enter standard celx like:

observer = celestia:getobserver()
planet = celestia:find("Sol/Neptune")
observer:goto(planet, 7)
wait(7)
celestia:print("arrived at Neptune", 5)
wait(5)
.......

in a custom event handler, the Celestia custom eventhandling has véry limited use.

[Esc] Escape key

When running a celx scrip, pressing the [Esc] key will stop that script. One would expect that it would also revert Celestia to it's default event handling, but that appears not to be trough. After pressing [Esc] Celestia will still be executing your custom event handling.