【问题标题】:FRP vs. State Machine w/ Lenses for Game Loop用于游戏循环的 FRP 与带镜头的状态机
【发布时间】:2023-06-18 18:08:01
【问题描述】:

我正在尝试了解 FRP 图和带镜头的状态机之间的实际差异 - 特别是对于像游戏循环这样的东西,其中整个状态在每个滴答时都会重新绘制。 p>

使用 javascript 语法,以下实现基本上都可以工作:

选项 1:带镜头的状态机

//Using Sanctuary and partial.lenses (or Ramda) primitives
//Each update takes the state, modifies it with a lens, and returns it

let state = initialValues;
eventSource.addEventListener(update, () => {
  state = S.pipe([
    updateCharacter,
    updateBackground,
  ])
  (state) //the first call has the initial settings

  render(state);
});

选项 2:玻璃钢

//Using Sodium primitives
//It's possible this isn't the best way to structure it, feel free to advise

cCharacter = sUpdate.accum(initialCharacter, updateCharacter)
cBackground = sUpdate.accum(initialBackground, updateBackground)
cState = cCharacter.lift(cBackground, mergeGameObjects)
cState.listen(render)

我看到Option 1 允许任何更新在游戏状态的任何地方获取或设置数据,但是Option 2 中的所有单元格/行为都可以调整为GameState 类型,然后同样适用。如果是这种情况,那么我真的对差异感到困惑,因为那将归结为:

cGameState = sUpdate
  .accum(initialGameState, S.pipe(...updates))
  .listen(render)

然后它们真的非常等价......

实现该目标的另一种方法是将所有单元格存储在某个全局引用中,然后任何其他单元格都可以对它们进行采样以供读取。可以传播新的更新以进行通信。该解决方案在一天结束时也与选项 1 非常相似。

在这种情况下,有没有办法构建 FRP 图,使其比事件驱动状态机具有明显优势?

【问题讨论】:

    标签: functional-programming state reactive-programming frp


    【解决方案1】:

    我不太确定您的问题是什么,也是因为您不断更改说明文本中的第二个示例。

    无论如何,在我看来,FRP 方法的主要好处如下:游戏状态取决于很多东西,但它们都明确地列在定义的右侧cGameState.

    相比之下,在命令式风格中,您有一个全局变量state,它可能会或可能不会被您刚刚提供的未显示在 sn-p 中的代码更改。据我所知,下一行可能是

    eventSource2.addEventListener(update, () => { state = state + 1; })
    

    游戏状态突然取决于第二个事件源,这一事实在您展示的 sn-p 中并不明显。这在 FRP 示例中不会发生:cGameState 的所有依赖项在右侧都是显式的。 (当然,它们可能非常复杂,但至少它们是明确的。)

    【讨论】:

    • 哇 - 得到你的评论真是太棒了!几乎我所有的谷歌搜索都让我写了关于反应香蕉的文章,即使不了解 Haskell,你的教程也很有帮助!为编辑道歉-由于“frp”标签的受欢迎程度较低,我认为没有人会在该窗口中看到它。就我的问题而言 - * 可能不是适合它的论坛......我更像是在一个白板的地方,宁愿在一杯咖啡上讨论各种解决方案,呵呵。
    • Fwiw - 此评论非常符合我正在寻找的信息类型的方向:*.com/questions/26785025/… 例如,如果我有一个“拖动”事件或某个手势,它依赖于随着时间的推移数据,上面的状态机方法需要显式存储数据结构中的每个更改,而 frp 方法可以是更简单的映射