Simplifying State with Async (with XNA/WPF examples)

Now, let me start this post by saying I love XNA/Monogame. XNA was Microsoft’s first (and currently last) attempt to have a first class managed framework for writing games. Luckily the excellent Monogame project has picked up Microsoft’s slack and put XNA on Mac, Linux, iOS, Android, PS Vita, Windows 8, and more.

I remember back in the late 90’s writing DirectX 7 code in C++ and the ridiculous amount of boilerplate associated with it – setting up a Win32 Window, initialising DirectDraw, initialising Direct3D, doing voodoo incantations and maybe after a couple of hours of tinkering you’d have an empty window. XNA just made it easy to create a new project and start writing a game in seconds.

But, as good as XNA is, it’s still rooted in mid-noughties Microsoft OO conventions. Which means that using it in modern languages makes it feel somewhat awkward.

Now, if you want to follow along at home, you’re going to need XNA for a modern Visual Studio (as MS no longer support it officially in the newer IDEs), and you’re going to need this pure F# XNA Template. You’ll need to install XNA before trying to create a new application in VS.

Code Sample – Version 1

Upon creating a new project using that template, you’re going to see a simple XNA Game implementation, using idiomatic XNA conventions.

open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics

type XnaGame() as this =
    inherit Game()
    
    do this.Content.RootDirectory <- "XnaGameContent"
    let graphicsDeviceManager = new GraphicsDeviceManager(this)

    let mutable sprite : Texture2D = null
    let mutable spriteBatch : SpriteBatch = null
    let mutable x = 0.f
    let mutable y = 0.f
    let mutable dx = 4.f
    let mutable dy = 4.f

    override game.Initialize() =
        graphicsDeviceManager.GraphicsProfile ("Sprite")
        
    override game.Update gameTime = 
        if x > 608.f || x < 0.f then dx <- -dx 
        if y > 448.f || y < 0.f then dy <- -dy 
        
        x <- x + dx
        y <- y + dy

    override game.Draw gameTime = 
        game.GraphicsDevice.Clear(Color.CornflowerBlue)
        spriteBatch.Begin()
        spriteBatch.Draw(sprite, Vector2(x,y), Color.White)
        spriteBatch.End()

let game = new XnaGame()
game.Run()

Most F# developers are probably feeling a bit ill at this point. But to drive home what I hope to fix, let’s highlight the obvious:

  1. Mutable state everywhere! (x,y,dx,dy, sprite, spriteBatch)
  2. Therefore null can be almost everywhere! (sprite, spriteBatch) – even option types wouldn’t help us that much here.
  3. Given there’s so much mutation, it’s hard to tell what is mutable for what reason – x, y, dx and dy are the game state, spriteBatch and sprite are framework code that have to be initialised at specific points after the constructor.
  4. XNA prefers if the Update and Draw cycles are decoupled, so in order to carry state between the 2 calls the framework really enforces mutation through every part of it’s design, leaving the developer little choice in the way of choice.

F# to the rescue (and a WPF diversion)

One of the things I like most about F# is the ability to take a problem in one programming model and solve it in another one. in this small WPF example, we transform from a traditional event based model to async workflow, which makes the code look entirely synchronous, and changes the model from event driven to recursive loops.

open System
open System.Windows
open System.Windows.Controls
open System.Windows.Shapes
open System.Windows.Media
open System.Threading

type DrawingWindow() as window =
   inherit Window()

   let canvas = Canvas(HorizontalAlignment=HorizontalAlignment.Stretch,
                       VerticalAlignment=VerticalAlignment.Stretch)
   do window.Content <- canvas

   let rec addLine() = async {
      let! pt1 = Async.AwaitEvent window.MouseDown
      let pt1 = pt1.GetPosition canvas
      let! pt2 = Async.AwaitEvent window.MouseUp
      let pt2 = pt2.GetPosition canvas
      let line = Line(X1 = pt1.X, X2 = pt2.X, Y1 = pt1.Y, Y2 = pt2.Y,
                      Stroke = Brushes.Black, StrokeThickness=3.0)
      canvas.Children.Add(line) |> ignore
      return! addLine()
   }
   do addLine() |> Async.StartImmediate


type DrawingApplication() =
   inherit Application()
   override app.OnStartup _=
      let window = DrawingWindow()
      window.ShowDialog() |> ignore

[<EntryPoint>]
[<STAThread>]
let main argv =
   let app = DrawingApplication()
   app.Run()

The addLine async workflow is where the magic happens – we await our 1st triggering event (in this case a mouse down), capture some information, then await our exiting event (in this case a mouse up), perform the action (draw the line), then do start again.
The C# equivalent, would likely have ended up capturing the intermediate state between events a class level mutable fields, or worse, some shoehorned in Rx “goodness”.

One of the most common misconceptions I hear about async (in both C# and F#) is that it’s about threads – I prefer describe it as simply “non-blocking” – notice this WPF example has nothing to do with threads, despite relying on async.

Therefore async can be used to help simplify code, even if it has nothing to do with threading.

Back to XNA

Back in XNA, we don’t have events already exposed like WPF. But we can add them, and trigger them at the keys points – Initialise, LoadContent, and then in a loop over Update and Draw.

We can then expose the events as async Workflows, and then consume the game logic entirely outside the XNA Game implementation. Notice that now the gameLoop() workflow has no explicit mutation – each frame is awaited, rendered and then the state accumulates into the next update.

open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics

type XnaGame() as this =
    inherit Game()

    let initializeEvt = Event<_>()
    let loadEvt = Event<_>()
    let updateEvt = Event<_>()
    let drawEvt = Event<_>()

    do this.Content.RootDirectory <- "XnaGameContent"
    let graphicsDeviceManager = new GraphicsDeviceManager(this)

    override game.Initialize() =
       graphicsDeviceManager.GraphicsProfile <- GraphicsProfile.HiDef
       graphicsDeviceManager.PreferredBackBufferWidth <- 640
       graphicsDeviceManager.PreferredBackBufferHeight <- 480
       graphicsDeviceManager.ApplyChanges() 
       initializeEvt.Trigger()<
       base.Initialize()

    override game.LoadContent() =
       loadEvt.Trigger()
 
    override game.Update gameTime = 
       updateEvt.Trigger gameTime

    override game.Draw gameTime = 
       drawEvt.Trigger gameTime
 
    member game.InitializeAsync = Async.AwaitEvent initializeEvt.Publish
    member game.LoadContentAsync = Async.AwaitEvent loadEvt.Publish
    member game.UpdateAsync = Async.AwaitEvent updateEvt.Publish
    member game.DrawAsync = Async.AwaitEvent drawEvt.Publish

let game = new XnaGame()


let gameWorkflow = async {
   do! game.InitializeAsync
   let spriteBatch = new SpriteBatch(game.GraphicsDevice)
   do! game.LoadContentAsync
   let sprite = game.Content.Load<Texture2D>("Sprite")

   let rec gameLoop x y dx dy = async {
      let! time = game.UpdateAsync
      let dx = if x > 608.f || x < 0.f then -dx else dx
      let dy = if y > 448.f || y < 0.f then -dy else dy
      let x = x + dx
      let y = y + dy
      let! time = game.DrawAsync
      game.GraphicsDevice.Clear(Color.CornflowerBlue)
      spriteBatch.Begin()
      spriteBatch.Draw(sprite, Vector2(x,y), Color.White)
      spriteBatch.End()
      return! gameLoop x y dx dy
   }
   return! gameLoop 0.f 0.f 4.f 4.f
}


gameWorkflow |> Async.StartImmediate

game.Run()

Using async workflows we’ve managed to move all the framework related code into it’s own place (the Game class) and the essentially syncronous application flow into an async workflow.

This works great in this sample, but keen XNA game developers will notice that in this sample the Update and Draw are in lockstep with each other – this isn’t always the case. Now we’ve transformed our mutation-heavy, frame-based application and turned it a simple recursive loop with accumulating state, we can tackle this final problem and call this a success.

If Update and Draw both trigger the same event, but differentiated using different cases of a Discriminated Union, we can safely await the single event, and act appropriately. Note that IsFixedTimeStep is now set to false.

open Microsoft.Xna.Framework
open Microsoft.Xna.Framework.Graphics

type LoopState = 
| Update of GameTime
| Draw of GameTime

type XnaGame() as this =
   inherit Game()

    let initializeEvt = Event<_>()
    let loadEvt = Event<_>()
    let loopEvt = Event<_>()

    do this.Content.RootDirectory <- "XnaGameContent"
    let graphicsDeviceManager = new GraphicsDeviceManager(this)

    override game.Initialize() =
       graphicsDeviceManager.GraphicsProfile <- GraphicsProfile.HiDef
       graphicsDeviceManager.PreferredBackBufferWidth <- 640
       graphicsDeviceManager.PreferredBackBufferHeight <- 480
       graphicsDeviceManager.ApplyChanges() 
       game.IsFixedTimeStep <- false
       initializeEvt.Trigger()
       base.Initialize()

    override game.LoadContent() =
       loadEvt.Trigger()
 
    override game.Update gameTime = 
       loopEvt.Trigger (Update gameTime)

    override game.Draw gameTime = 
       loopEvt.Trigger (Draw gameTime)
    member game.InitializeAsync = Async.AwaitEvent initializeEvt.Publish
    member game.LoadContentAsync = Async.AwaitEvent loadEvt.Publish
    member game.LoopAsync = Async.AwaitEvent loopEvt.Publish

let game = new XnaGame()

let gameWorkflow = async {
    do! game.InitializeAsync
    let spriteBatch = new SpriteBatch(game.GraphicsDevice)
    do! game.LoadContentAsync
    let sprite = game.Content.Load<Texture2D>("Sprite")

    let rec gameLoop x y dx dy = async {
       let! nextState = game.LoopAsync
       match nextState with
       | Update time -> 
           let dx = if x > 608.f || x < 0.f then -dx else dx
           let dy = if y > 448.f || y < 0.f then -dy else dy
           let x = x + dx
           let y = y + dy
           return! gameLoop x y dx dy
       | Draw time -> 
           game.GraphicsDevice.Clear(Color.CornflowerBlue)
           spriteBatch.Begin()
           spriteBatch.Draw(sprite, Vector2(x,y), Color.White)
           spriteBatch.End()
           return! gameLoop x y dx dy
    }
    return! gameLoop 0.f 0.f 4.f 4.f
}

gameWorkflow |> Async.StartImmediate

game.Run()

And with that we’re done.

Conclusion

XNA/Monogame is a great tool for writing games, and F# offers many benefits to game developers. However the 2 technologies working together do have some rough edges, which can be transformed into advantages using some basic features of F#.

Features such as F# async empower developers to find ways to tame the accidental complexity, introduced by external frameworks such as XNA or WPF.

The XNA Code for this post is hosted here

Advertisements

About thedo666

Software developer trying to learn a new language - English!
This entry was posted in Async, F#, Monogame, XNA and tagged , , , , . Bookmark the permalink.

13 Responses to Simplifying State with Async (with XNA/WPF examples)

  1. Pingback: F# Weekly #7, 2015 | Sergey Tihon's Blog

  2. Thank you for this article! I was able to take your code and use it with F# in Unity3D. Check it out:

    type AsyncController() =
    inherit MonoBehaviour()
    let startEvt = Event()
    let updateEvt = Event()

    member this.Start() =
    startEvt.Trigger()

    member this.Update() =
    updateEvt.Trigger(Time.time)

    member this.StartAsync() = Async.AwaitEvent startEvt.Publish
    member this.UpdateAsync() = Async.AwaitEvent updateEvt.Publish

    member this.Awake() =
    let gameflow = async {
    do! this.StartAsync()
    Debug.Log(“Start complete”)
    let myvalue = “somestring”
    let rec gameloop somvalue = async {
    let! time = this.UpdateAsync()
    Debug.Log(time.ToString())
    return! gameloop somvalue
    }
    return! gameloop myvalue
    }
    gameflow |> Async.StartImmediate |> ignore

    • thedo666 says:

      Ryan – glad you found it useful! That’s really cool how you’ve made it work in Unity. Do you have any good pointers on making F# work in Unity? I’ve always been intrigued by it, but the thought of using C# always makes me feel slightly off.

  3. freeqaz says:

    I’m curious to hear your thoughts about how you would replicate this in C# using async/await. I’d be really interested in baking a template for this and throwing it on GitHub to spread the architectural goodness of this approach. This is a problem that I’ve been mulling over for the past week now and after a few hours of digging, I found this. Would love to chat, as I’ve got some other problems I’m working with that build on top of this that I’m not sure of the best approach.

    I hit you up on twitter (I’m freeqaz).

    Best,
    -Free

    • thedo666 says:

      Hi FreeQuaz,

      Hopefully you enjoyed the follow on post – did that answer your question? I think compared the F# version the advantages are less clear, but there is still a transformative effect from a frame based system to a recursive loop, even if some of the purity of immutability is not present.

      If you have any thoughts or would like to discuss further, DM me on twitter and I’d be happy to exchange emails.

      Neil

  4. Pingback: F# Weekly #21, 2015 | Sergey Tihon's Blog

  5. krms says:

    Thanks for this interesting approach, but I cannot quite get the final version to work. For some reason, ‘nextState’ ALWAYS matches Update even though I see that ‘loopEvt.Trigger (Draw gameTime)’ is firing… I also copy-pasted the code from the Github repo to no avail. Any idea what might be wrong?

  6. thedo666 says:

    Hi – I’ve take a quick look and I’m a bit baffled. Under XNA I get desired behaviour. Removing references to XNA and updating to Monogame DirectX and as you say – black screen. Switching to Monogame OpenGL, and it works again.

    My 1st hunch was that DX was using 2 threads, but all versions use 1 thread.
    My 2nd hunch was around SynchronizationContexts not being set up, but they are all present and correct.
    My 3rd hunch was timing playing a factor, so a couple of attempts to stagger the draw/update more – no result.
    My final hunch – dont use Async.AwaitEvent and switch in Tomas Petricek’s AwaitObservable – no joy!

    I’m going to drop in an agent, but I have to admit I’m a little stumpted! Going to keep looking!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s