2011-04-27 66 views
4

我正在嘗試使用F#和Silverlight編寫遊戲,並且正在努力嘗試一些不變性。使用F#和Silverlight的遊戲:避免可變狀態

我想從視圖中分離一點遊戲,所以我把它放在一個模塊中,並使其更新函數返回一個新的世界狀態實例,從而提供了不變性。

視圖(AppControl)負責繪製世界。

但是,我認爲沒有辦法讓世界變成視圖中的參考單元格。

現在,我認爲可變狀態是足夠本地的,不會引起任何問題(如果我錯了,請糾正我),我只是好奇,如果有人可以想辦法完全避免可變狀態?

這裏的應用程序的輪廓,我試着把減少問題的實質:

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) 

回答

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!)是一個尾部調用,所以函數將繼續運行,直到應用程序關閉。

+0

正是我在找的東西,非常感謝!我打算使用異步工作流進行輸入處理(正如我從你的書中學到的,順便說一句,這真是太棒了!),但是我也沒有想過用它來更新狀態。 – bknotic 2011-04-27 16:19:40

+0

我有點新F#,在你的例子中,'let! _'有一個特殊的含義,或者你可以用'do!'離開嗎? – R0MANARMY 2011-04-28 16:29:18

+0

@ R0MANARMY @bknotic你實際上必須使用'let! _ ='當你調用的函數返回一些結果。 'do!'只能用於沒有返回值的異步工作流('Async ')。所以,'做! ...'是'let的簡寫! ()= ...'。 – 2011-04-28 22:44:25