Saturday, July 14, 2012

The first interactive application

In the last Post we wrote the first "Hello, World!" application. We saw how to import javascript functions in UHC and haste.

We now want to do something more game like. Out goal over the next few post will be to write a breackout clone in haskell, running in the browser! But first there are still a few things we need. To explore this we will write a little application displaying the paddle that can be moved with the arrow keys. Here is a preview (you have to click on it so that it gains focus):

Update: This should now also work in firefox.

For this we need to learn how to:

Set callbacks

Let different callback communicate

Draw on the canvas

GameLoop in JavaScript

Before we want to start our game, we have to allow the browser to load the full page and its elements. Otherwise we can not access e.g. the canvas (the drawing area we will use).

The browser tells us, that it is done with loading by invoking the callback window.onLoad. Depending on how we compile with haste, our main will already be set to the window.onLoad (the option --start==asap prevents this), but in UHC we have to set a callback by hand. We will use the --start=asap option in haste so that our main code can be the same for haste and UHC.

As can be read at several places (e. g. here or here) we can not just write an infinite loop for our GameLoop in javascript because it would block the browser. The contents of the canvas will only be updated when our code returns.

So we need a function that is called in intervals. Javascript allows us to set the interval with window.setInterval.

Setting callbacks

We need to set haskell functions as callbacks to be invoked from javascript code.

UHC

In UHC we import a special function which converts haskell functions to callbacks that can be called from javascript (see Improving UHC js).

We now need to import the functions with which we set the callbacks. The interval function is set with "setInterval" while "onLoad" and the key event callbacks can be set with "addEventListener" which must be called on an element of the webpage. This element can be retrieved with "getElementById". We define simplified versions that take care of creating the callback for us.

Remember that the >>= operator chains monadic actions. "setOnKeyDown" and "setOnKeyUp" set the event listener on an element defined by the given name. They define wrapper functions that extract the keycode and passes it to our callback functions. This is convenient because the keycode is the information we are really interested in.

We will follow the convention, that functions taking javascript specific parameters (such as JSString) will be prefixed by "js" and have corresponding functions without the "js" prefix.

Haste

Update: The way FFI functions have to writting with haste has changed since the blog post has original been written. At that time returning values from javascript to haskell was a little bit more cumbersome. I have updated this blog post to reflect the new way of doing it. I hope I did not forget something in the process. So if you find an error, please comment.

Setting callbacks in haste is a little different.

For every callback function a javascript function has to be created which invokes the special function "A()" with the callback. This function can than be used for the callback. Read the section "Callbacks" of js-externls.txt in the doc subdirectory of the haste github repository.

The arguments for the haskell function are the second argument of "A()" and have to be passed as a list similar to the required return value of javascript function included by the FFI. Again, this is explained in js-externals.txt.

For "setOnKeyUp" and "setOnKeyDown" we do not need to define any haskell function, because we can set them using the haste library in haskell. On the haskell side callbacks have to be created with "mkCallback" and have the Type "JSFun a".

The "withElem" functions is defined in the haste library and executes an action with a webpage element defined by the provided name.

Letting callbacks communicate

UHC

In a javascript program we would let the onKeyUp, onKeyDown and Interval functions communicate through global variables. In haskell we do not have a mechanism such as global variables (at least non that I am aware of). In a normal situation we do not need it, because the only moment when main exits is when the program ends.

To store global variables we write a few helper functions in javascript:

Summary

This has been a rather long post, but we have learned a lot. With the tools at hand we can now update out game with an callback function we set via "setInterval" and receive keyboard events. The next post will be another step towards our goal of a breakout clone, we will implement the ball and let it bounce on the paddle and the walls.

I compiled successfully (yeah!) and copy-pasted the two lines to head and body, respectively, of Main.html. When I open Main.html in a browser (Chrome or FF), nothing shows. The JS error console shows the following error:

In the "Letting callbacks communicate" section, "UHC" subsection, in the javascript code, the name of the JS function should be "jsSaveGlobalObject", and not just "jsSaveObject". And similarly for "jsLoadObject".

"In haskell we do not have a mechanism such as global variables (at least non that I am aware of)"

Sure there is, you can use create an `IORef a` using `newIORef :: a -> IORef a`, and then use `readIORef`, `modifyIORef` etc.

My drawing callback is `drawStuff :: IORef St -> IO ()` and bind the state when setting the initial interval callback to the IO action `drawStuff $ newIORef initialState`. Then `drawStuff` updates the state inside the IORef.