I’m trying to write a game using F# and Silverlight and am struggling a bit with immutability.
I want to decouple the game from the view a little, so I put it in a module and made its update function return a new instance of the world-state, thus providing immutability.
The view (AppControl) is responsible for drawing the world.
However, I see no way around making the world a ref cell in the view.
Now, I think the mutable state is local enough to not cause any problems (please correct me, if I’m wrong), I am just curious, if someone can think of a way to avoid mutable state completely?
Here’s the outline of the application, I tried to reduce the problem down to the essence:
open System
open System.Windows
open System.Windows.Controls
open System.Windows.Media
module Game =
type World = { State : int }
let init() =
{ State = 0 }
// immutable update loop
let updateWorld world =
{ State = world.State + 1 }
type AppControl() =
inherit UserControl()
let canvas = new Canvas()
let textBlock = new TextBlock()
let world = Game.init() |> ref // mutable world
let drawWorld (world : Game.World) =
textBlock.Text <- world.State.ToString()
// mutating game loop
let gameLoop world =
world := Game.updateWorld !world
drawWorld !world
()
do
canvas.Children.Add(textBlock)
base.Content <- canvas
CompositionTarget.Rendering.Add (fun _ -> gameLoop world)
type App() as this =
inherit Application()
let main = new AppControl()
do this.Startup.Add(fun _ -> this.RootVisual <- main)
The structure of your code looks fine – the mutable state is localized in the user interface (which is mutable anyway), so it is fine. You’re not mutating the field from any closure, so you could use a mutable field (declared using
let mutable world = ..) instead ofrefcell.To completely avoid the mutation, you can use asynchronous workflow (running on the GUI thread):
The
gameLoopfunction is asynchronous, so it doesn’t block any thread. It is started usingAsync.StartImmediate, which means that it will run only on GUI thread (so accessing GUI elements & events from the body is safe). Inside the function, you can wait for event occurrence (usingAsync.AwaitEvent) and then do some action. The last line (return!) is a tail-call, so the function will continue running until the application is closed.