我正在尝试使用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)
答案 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!
)是一个尾调用,因此该函数将继续运行,直到应用程序关闭。