Tuesday, January 12, 2010

Creating an interactive game using F#


One of the interesting things about functional programming is the notion of immutable types. Functional programmers avoid shared mutable state like the plague. In most functional languages, all values are immutable by default. A value cannot change once it is created. If you want to change a value in struct with many fields, you need to create a new struct, copy all the values and provide a new value for the field that needs to change. My initially thoughts were that this seemed incredibly inefficient. What it buys us though is that we can now modify values without worrying about another thread trying to access the same value. This all sounds great in theory but how do you write a game without having mutable state? The following example is kindof silly, but it serves to illustrate the point. How do you do the equivalent of:

void update() =
int rocketHeight = 0;
while(true)
{
drawRocketAt(rocketHeight);
rocketHeight++;
}

One of the ways of maintaining state is to pass it around in a recursive function. The following snippet has no mutable state, yet the rocket is drawn at a new position each frame.

let rec update(rocketHeight) =
drawRocketAt(rocketHeight)
update(rocketHeight + 1)
update(0)


This is all well and good, but surely you cannot pass all the state of every object in the same loop.
Agents to the rescue!
An agent or Actor is a lightweight object that runs in its own thread. Communication between actors happens through message passing. In F#, the MailboxProcessor implements this functionality and is essentially an actor/agent.

let mailbox = MailboxProcessor.Start(fun inbox ->
let rec update(state) =
async {
let! msg = inbox.Receive()
match msg with
| ActionA ->
let newState = OnActionA()
return! update(newState)
| ActionB ->
let newState = OnActionB()
return! update(newState)
| Stop -> return ()
}
update(initialState) )
mailbox.Post(Action)


Agents allows us to separate concerns and avoid having to pass unrelated game state in our main recursive loop. Of course agents are also multi-threaded so chances are that some work done by agents will actually automatically happen in parallel. The mechanism for passing messages are also thread safe. Only one request can be handled at a time.

In the past week I have written the classic game Pong using these ideas. It is hopelessly over engineered for a game as simple, but it helps to see how this might fit into a game with a bigger scope. In this game there are 3 agents. The main loop, Score and PlayerController. Since there are two player controllers, this is actually 4 different threads. The main loop is implemented as an agent that post an update message to itself for each frame. This allows the Application thread to send a Stop message to end update loop.
The Score agent listens for collision messages. Whenever the ball hits any of the oponents' zones, the score is updated.

type ScoreMessage =
| Collision of Surface
| Stop

type Score(physics:Physics) =

let mailbox = MailboxProcessor.Start(fun inbox ->
let rec update(green, red) =
async {
let! msg = inbox.Receive()
match msg with
| Collision(surface) ->
let score =
match surface with
| GreenZone -> (green, red+1)
| RedZone -> (green+1, red)
| Wall -> (green, red)
Debug.Trace "Score: Green-%d Red-%d" (fst score) (snd score)
return! update(score)
| Stop -> return ()
}
update(0,0) )

let OnCollision(player) =
mailbox.Post(player)

do physics.Event.AddHandler(fun player -> mailbox.Post(Collision(player)))

For the moment it only prints a value to the debug output. (It turns out SlimDX Direct2D is not completely implemented yet. You can not draw to the same surface as a Direct3D renderTarget.)
The PlayerController listens for events from the keyboard and updates the paddle positions accordingly.
The above example used the normal way of handling events. F# also allows you to manipulate the event stream using the Observable class. One can create an event stream that listents for events, reformat them and publish this modified event stream. One can even filter out events that you are not interested in. In the Input class I map from SlimDX KeyInputEventArgs to my own KeyEvent. I only have a subset of all the available keys because I would like them to fit into a 64bit integer bitfield. I therefor need to first map it to my representation and then filter out those that I don't have a mapping for.

module Keyboard =
...
do Device.RegisterDevice(UsagePage.Generic, UsageId.Keyboard, DeviceFlags.None)
// Listen only for key events for which we have a mapping defined
let Input = Device.KeyboardInput
|> Observable.map ( fun e ->
match keyToFlag.TryFind(e.Key) with
| Some(key) ->
{Key = key; IsPressed = e.State = KeyState.Pressed}
| None -> KeyEvent.None )
|> Observable.filter
(fun keyEvent -> keyEvent.Key <> KeyFlags.None)

Clients of the Keyboard module can then subscribe to this event stream. The PlayerController listens for these events and sends the appropriate message to its message queue.

type PlayerController =
...
let keyboardInput = Keyboard.Input.Subscribe(fun (keyEvent:KeyEvent) ->
if keyEvent.Pressed(left) then mailbox.Post(Left)
elif keyEvent.Pressed(right) then mailbox.Post(Right) )

1 comment:

  1. Hi Johan. Thanks.
    You're one of the very few posting re: F# DX11 (XNA). Johann Deneux too, Techneilogy ... and it's been a while; looking forward to more.

    XNA/DXn Vector2.Lerp Vector2 convert to WPF Point ??

    PLH OOE Art
    @semasiographic

    ReplyDelete