gpt4 book ai didi

haskell - Netwire 5 的基本结构是什么?

转载 作者:行者123 更新时间:2023-12-04 07:22:12 26 4
gpt4 key购买 nike

我正在尝试进入 Netwire,我已经挖掘以找到文档、介绍、教程等等,但几乎每个教程和现有代码对于 Netwire 5 来说都是过时的,并且使用了 Netwire 4 中不再使用的功能我们。 README有点帮助,但并不是所有的东西都能编译,而且它仍然几乎没有提供足够的信息来开始。

我要求解释或示例只是为了让游戏循环运行并能够响应事件,所以我寻求信息以便我最终知道:

  • 基本结构(例如在响应式(Reactive)香蕉中,您如何驱动使用处理程序、定义行为和响应事件的网络描述)。
  • 最终结果如何main .
  • 如何处理 IO 事件(如鼠标单击、按键按下或游戏循环回调)、事件如何进入 session 等。

  • 以及任何其他相关的东西。

    我认为从那里我可以运行一些东西,所以我可以通过实验来学习其余的东西(因为第 5 版中的文档和教程的状态非常不存在,我希望很快就会出现一些)。

    谢谢!

    最佳答案

    免责声明:我还没有找到任何使用 Netwire 的大型程序,所以我将要写的一切你都应该持保留态度,因为它是基于我自己使用 Netwire 的经验。我这里使用的例子大多取自from my own libraryattempt at writing a game using FRP ,并且可能不是做事的“正确方式”。

    问题 1:基本结构(例如在响应式(Reactive)香蕉中,您如何驱动使用处理程序、定义行为和响应事件的网络描述)。

    session :
    netwire库作者给了一个really good answer关于netwire程序的基本结构。由于它有点旧,我将在这里概述一些要点。在看wires之前,我们先来看看netwire是如何处理时间的,FRP的底层驱动。不使用测试工具提前时间的唯一方法 testWire是生产Session这将有状态地返回时间增量。方式Sessions保留状态封装在它们的类型中:

    newtype Session m s = Session { stepSession :: m (s, Session m s) }

    在这里,一个 Session位于 Monad 内(通常是 IO ),每次计算时,都会返回类型为 s 的“时间状态”值。和一个新的 Session .通常,任何有用的状态 s可以写成 Timed可以返回 Real t 的一些实例的值:
    data Timed t s
    class (Monoid s, Real t) => HasTime t s | s -> t where
    -- | Extract the current time delta.
    dtime :: s -> t
    instance (Monoid s, Real t) => HasTime t (Timed t s)

    例如,在游戏中,你通常 want a fixed timestep做你的更新电话。 netwire 将这个概念编码为:
    countSession_ :: Applicative m => t -> Session m (Timed t ())

    一个 countSession_将时间步长作为输入,在本例中为 t 类型的固定值,并产生 Session其状态值为 Timed t () 类型.这意味着它们只对 t 类型的单个值进行编码。 ,并且不带有任何附加状态 ()值(value)。在我们讨论电线之后,我们将看到这在评估它们时如何发挥作用。

    电线: Netwire 中“电线”的主要类型是:
    Wire s e m a b

    这条线描述了 reactive类型值 b执行以下操作:
  • a 类型的无功值作为输入
  • 在 Monad 内运行 m
  • 可以抑制或不产生值,产生类型 e 的抑制值
  • 假定由 s 给出的时间状态

  • 由于是 react 值的性质,线可以被认为是随时间变化的函数。因此,每条线都被编码为时间(或时间状态 s)的函数,在那个时刻产生一个类型为 b 的新值。 ,以及用于评估下一个 a 类型的输入的新连线.通过返回一个值和一个新的连线,该函数可以通过在函数定义中传播它来包含状态。

    此外,连线可能会抑制或不产生值。这对于未定义计算时(例如,当鼠标位于应用程序窗口之外时)很有用。这允许您实现诸如 switch 之类的内容。 ,其中一条线更改为另一条线以继续执行(例如玩家完成跳跃)。

    有了这些想法,我们可以看到netwire中wires的主要驱动力:
    stepWire :: Monad m => Wire s e m a b -> s -> Either e a -> m (Either e b, Wire s e m a b)
    stepWire wire timestate input完全符合我们之前所说的:它需要一个 wire并将当前 timestate 传递给它和 input从之前的电线。然后,在底层 Monad m ,它要么产生值 Right b或禁止,值为 Left e ,然后给出用于计算的下一条线。

    问题 2:它最终如何进入 main。

    配备类型 Session 的值和 Wire ,我们可以构建一个循环,一遍又一遍地做两件事:
  • 步进 session 以接收新的时间状态
  • 使用新的时间状态步进线

  • 这是一个程序示例,该程序将固定计数器更改为永远按 2 计数:
    import Control.Wire

    -- My countLoop operates in the IO monad and takes two parameters:
    -- 1. A session that returns time states of type (Timed Int ())
    -- 2. A wire that ignores its input and returns an Int
    countLoop :: Session IO (Timed Int ()) -> Wire (Timed Int ()) () IO a Int -> IO ()
    countLoop session wire = do
    (st, nextSession) <- stepSession session
    (Right count, nextWire) <- stepWire wire st (Right undefined)
    print count
    countLoop nextSession nextWire

    -- Main just initializes the procedure:
    main :: IO ()
    main = countLoop (countSession_ 1) $ time >>> (mkSF_ (*2))

    问题 3:如何处理 IO 事件(如鼠标点击、按键按下或游戏循环回调),事件如何进入 session 等。

    关于如何做到这一点存在一些争论。我认为在这种情况下最好利用底层 Monad m ,然后简单地将当前状态的快照传递给 stepWire功能。这样做时,我的大部分输入线看起来像这样:
    mousePos :: Wire s e (State Input) a (Float, Float)

    忽略线的输入,从 State 读取鼠标输入。单子(monad)。我用 State而不是 Reader为了正确处理键去抖动(这样单击 UI 不会同时单击 UI 下方的某些内容)。状态设置在我的 main 中函数并传递给 runState ,这也做电线步进。像这样的电线的抑制行为可以产生一些优雅的代码。例如,假设您有电线 rightleft为您的箭头键产生一个值,如果该键被按下,否则禁止。您可以使用如下所示的线创建角色运动:
    (right >>> moveRight) <|> (left >>> moveLeft) <|> stayPut

    由于电线是 Alternative 的一个实例, 如果 right抑制,它只会移动到下一个可能的电线。 a <|> b只有在 a 时才会禁止和 b抑制。

    您也可以编写代码以利用 netwire 的 Event系统,但您必须自己制作返回 Event 的电线使用 Control.Wire.Unsafe.Event .话虽如此,我还没有发现这种抽象比简单的抑制更有用。

    关于haskell - Netwire 5 的基本结构是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25338856/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com