使用F#和Silverlight的游戏:避免可变状态

时间:2011-04-27 08:34:33

标签: silverlight f#

我正在尝试使用F#和Silverlight编写游戏,并且在不变性方面有点挣扎。

我想将游戏与视图分离一点,所以我把它放在一个模块中,让它的更新函数返回一个世界状态的新实例,从而提供不变性。

视图(AppControl)负责绘制世界。

但是,我认为在视图中无法将世界变成一个ref单元格。

现在,我认为可变状态是本地的,不会引起任何问题(请纠正我,如果我错了),我只是好奇,如果有人能想到一种完全避免可变状态的方法吗?

这是应用程序的概要,我试图将问题简化为本质:

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)

1 个答案:

答案 0 :(得分:7)

你的代码结构看起来很好 - 可变状态本地化在用户界面中(无论如何都是可变的),所以很好。您没有从任何闭包中改变字段,因此您可以使用可变字段(使用let mutable world = ..声明)而不是ref单元格。

要完全避免变异,您可以使用异步工作流程(在GUI线程上运行):

type AppControl() =
    inherit UserControl()

    let canvas = new Canvas()
    let textBlock = new TextBlock()

    let drawWorld (world : Game.World) = 
        textBlock.Text <- world.State.ToString()

    // Asynchronous loop that waits for 'Rendering', updates
    // the world & draws it and then continues waiting
    let gameLoop world = async {
        let! _ = Async.AwaitEvent CompositionTarget.Rendering
        let newWorld = Game.updateWorld world
        drawWorld newWorld 
        return! gameLoop newWorld }

    do  
        canvas.Children.Add(textBlock)
        base.Content <- canvas
        gameLoop Game.init() |> Async.StartImmediate

gameLoop函数是异步的,因此它不会阻塞任何线程。它使用Async.StartImmediate开始,这意味着它只能在GUI线程上运行(因此从主体访问GUI元素和事件是安全的)。在函数内部,您可以等待事件发生(使用Async.AwaitEvent)然后执行某些操作。最后一行(return!)是一个尾调用,因此该函数将继续运行,直到应用程序关闭。