【问题标题】:Why does changing my FRP implementation to be more reactive lag?为什么将我的 FRP 实施更改为更具反应性的滞后?
【发布时间】:2021-11-29 08:20:47
【问题描述】:

我得到了一个使用 threepenny-gui 库的蛇版本,但我不喜欢我手动显式调用 newEventaddStateUpdate 而不是完全基于事件定义行为的事实,例如这个:

(updates, addUpdate) <- liftIO newEvent
managerB <- accumB initialManager updates

on UI.tick timer $ \_ -> addUpdate $ \manager -> manager'

相比:

managerB <- accumB initialManager $
  UI.tick timer $> \manager -> manager'

IIUC 第二个是更惯用的 FRP,因为它定义了实际事件的行为,而不是创建代理事件来代理更新。但是当我进行此更改时,它会导致以下两个问题之一:

  1. 如果我先定义managerB(使用RecursiveDo 访问timer,定义如下),则根本不会渲染任何内容
  2. 如果我将 managerB 移动到末尾(使用 RecursiveDo 从 DOM 元素访问 managerB),第一次按箭头键时的初始移动会滞后,并且帧以生涩的方式呈现。

我做错了吗?我应该以什么惯用方式来构建这些事件/行为?

此处的代码差异:https://github.com/brandonchinn178/snake/compare/inline-event-handlers

【问题讨论】:

    标签: haskell frp threepenny-gui


    【解决方案1】:

    我在the alternate branch in your repository 上测试过的一个不是特别好的解决方法是同时使用这两种方法:重新触发滴答事件并使用它而不是UI.tick timer 来定义@987654326 @:

      (timeE, fireTime) <- liftIO newEvent
      on UI.tick timer $ \_ -> liftIO (fireTime ())
    
      let managerUpdateE =
            fmap concatenate . unions $
              [ timeE $> getNextManagerState
              --  Instead of: UI.tick timer $> getNextManagerState
              --  etc.
    

    问题似乎在于,将UI.tick timer 直接插入事件网络会以某种方式妨碍 Threepenny 及时发送更新 UI 所需的 JavaScript 调用。将onfireTime 一起使用的间接性(尤其应该意味着timeE 名义上发生在UI.tick timer 之后)似乎绕过了这个问题。一个不那么侵入性的解决方法是,而不是引入timeE,而是在UI.tick timer 的处理程序中显式调用flushCallBuffer;然而,在我的测试中,这大大减少了抖动,但并没有完全消除它。 (有关可能的相关背景信息,另请参阅 threepenny-gui issue #191。)

    至于第一次击键的延迟,似乎可以通过将您的UI.start timer 调用移动到gui 的最后,在managerB 之后,并设置您的事件网络的其余部分来消除。

    (另外说明,遵循Graphics.UI.Threepenny.Timer 文档的建议并在ghc-options 中设置-threaded 以编译您的可执行文件可能是个好主意,即使这似乎没有对您在此处描述的问题的影响。)

    【讨论】: