【问题标题】:Why does order matter in this usage of Observable.merge?为什么顺序在 Observable.merge 的这种用法中很重要?
【发布时间】:2014-09-08 15:12:01
【问题描述】:

我正在尝试使用 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

但是,如果我更改 Observable.merge 中的参数顺序,则此方法有效:

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

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

(如果您想知道什么是 SFML,这里是 link)。

【问题讨论】:

    标签: f# system.reactive


    【解决方案1】:

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

    firstStream.Subscribe(...);
    secondStream.Subscribe(...);
    

    您传递给Observable.create 的订阅函数是同步的,并且永远不会将控制权交还给调用者。这意味着merge 本身被阻止尝试订阅displayStream 之后的任何流。当您对流重新排序以使 displayStream 排在第一位时,您会阻止它订阅您的 KeyPressed 流。这就是为什么您会看到您所看到的行为。

    在某些方面,您的 displayStream 表现不佳。 Subscribe 方法不应阻塞。

    所以,要么确保displayStream 是列表中的最后一项,要么对代码进行一些重构。您可以将Subject 用于displayStream。然后订阅所有内容,最后启动“显示循环”,执行当前位于 displayStream 定义中的循环,每次循环时,只需调用主题上的 OnNext

    【讨论】:

    • +1 - 很好的观察 - 我没有注意到那里的阻塞循环:(
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-21
    • 1970-01-01
    • 2013-05-18
    • 1970-01-01
    • 2021-10-29
    • 1970-01-01
    相关资源
    最近更新 更多