我正在尝试使用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)
Run Code Online (Sandbox Code Playgroud)
你的代码结构看起来很好 - 可变状态本地化在用户界面中(无论如何都是可变的),所以很好.你没有从任何闭包中改变字段,所以你可以使用一个可变字段(声明使用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
Run Code Online (Sandbox Code Playgroud)
该gameLoop
函数是异步的,因此它不会阻塞任何线程.它开始使用Async.StartImmediate
,这意味着它只能在GUI线程上运行(因此从主体访问GUI元素和事件是安全的).在函数内部,您可以等待事件发生(使用Async.AwaitEvent
),然后执行某些操作.最后一行(return!
)是尾调用,因此该函数将继续运行,直到应用程序关闭.
归档时间: |
|
查看次数: |
420 次 |
最近记录: |