为什么订单在Observable.merge的使用中很重要?

Asi*_*sik 4 f# system.reactive

我正在尝试使用F#中的Observables编写一个基本的"游戏循环".基本上我将两个流合并在一起的事件的基本输入流概念化:用户的按键操作(游戏仅使用键盘开始),以及游戏的常规刻度(例如,每秒60次).

我的问题似乎源于这样一个事实,即观察到的序列之一,即刻度,也是在Window上调用DispatchEvents()的循环,允许它处理其输入并触发按键事件,因此实际上有一个事件流如果这是有意义的,由另一个驱动.这是代码:

open System;
open System.IO
open SFML.Window
open SFML.Graphics
open System.Reactive
open System.Reactive.Linq
open System.Diagnostics

type InputEvent =
| Tick of TimeSpan
| KeyPressed of Keyboard.Key

[<EntryPoint;STAThread>]
let main _ = 

    use window = new RenderWindow(VideoMode(640u, 480u), "GameWindow")
    window.SetVerticalSyncEnabled(true)

    let displayStream = 
        Observable.Create(
            fun (observer:IObserver<TimeSpan>) -> 
                let sw = Stopwatch.StartNew()
                while (window.IsOpen()) do
                    window.DispatchEvents() // this calls the KeyPressed event synchronously
                    window.Display() // this blocks until the next vertical sync
                    window.Clear()
                    observer.OnNext sw.Elapsed
                    sw.Restart()
                observer.OnCompleted();
                { new IDisposable with member this.Dispose() = ()})    

    let onDisplay elapsedTime = 
        // draw game: code elided

    let inputEvents = Observable.merge 
                          (window.KeyPressed |> Observable.map (fun key -> KeyPressed(key.Code)))
                          (displayStream |> Observable.map (fun t -> Tick(t)))
    use subscription = 
        inputEvents.Subscribe(fun inputEvent -> match inputEvent with
                                                | Tick(t) -> onDisplay(t)
                                                | KeyPressed(key) -> printfn "%A" key)

    0
Run Code Online (Sandbox Code Playgroud)

但是,如果我在Observable.merge中更改参数的顺序,这是有效的:

    let inputEvents = Observable.merge 
                          (displayStream |> Observable.map (fun t -> Tick(t)))
                          (window.KeyPressed |> Observable.map (fun key -> KeyPressed(key.Code)))
Run Code Online (Sandbox Code Playgroud)

然后游戏渲染(onDisplay被调用),但我没有看到KeyPressed事件打印到控制台.这是为什么?

(如果你想知道什么是SFML,这里是链接).

Bra*_*don 6

在伪代码中,合并的作用是:

firstStream.Subscribe(...);
secondStream.Subscribe(...);
Run Code Online (Sandbox Code Playgroud)

您传递给的订阅函数Observable.create是同步的,永远不会将控制权交还给调用者.这意味着它merge本身被阻止尝试订阅之后的任何流displayStream.当displayStreamKeyPressed对流进行重新排序时,首先要阻止它订阅您的流.这就是您看到所看到的行为的原因.

在某些方面,你displayStream表现得很糟糕. Subscribe方法不应该阻止.

因此,要么确保displayStream是列表中的最后一项,要么对代码进行一些重构.你可以用一个Subjectfor displayStream.然后订阅所有内容,最后启动"显示循环",执行当前displayStream定义中的循环,每次循环,只需调用OnNext主题.