Netwire 5的基本结构是什么?

时间:2014-08-16 09:36:03

标签: haskell functional-programming reactive-programming frp netwire

我试图进入Netwire,我已经挖掘过找到文档,介绍,教程和诸如此类的东西,但几乎每个教程都是如此。现有代码已经过时了Netwire 5,并使用了Netwire 4中不再与我们合作的功能。 README有点乐于助人,但并非所有内容都可以编译,但它仍然无法提供足够的信息来开始使用。

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

  1. 基本结构(例如,如何在反应性香蕉中启动消耗处理程序的网络描述,定义行为并重新启动事件)。
  2. 最终如何进入main
  3. 如何处理IO事件(如鼠标单击,按键或游戏循环回调),事件如何进入会话等。
  4. 还有其他任何相关内容。

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

    谢谢!

2 个答案:

答案 0 :(得分:9)

免责声明:我还没有能够找到任何使用Netwire的大型程序,所以我要写的所有内容都应该带有一点点,因为它是基于它的根据我自己使用Netwire的经验。我在这里使用的示例主要是from my own libraryattempt at writing a game using FRP,可能不是"正确的方式"做事。


问题1:基本结构(例如,如何在反应性香蕉中启动消耗处理程序的网络描述,定义行为并重新启动事件)。

<强>会话 netwire库的作者给出了关于netwire程序基本结构的really good answer。由于它有点旧,我将在此概述一些要点。在我们看看电线之前,让我们先来看看netwire如何处理时间,这是FRP的潜在驱动因素。在不使用测试工具testWire的情况下推进时间的唯一方法是生成一个将有状态地返回时间增量的SessionSessions保存状态的方式封装在其类型中:

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

此处,Session位于Monad(通常为IO)内,每次评估时,都会返回&#34;时间状态&#34;类型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的{​​{1}}。这意味着它们仅编码Timed t ()类型的单个值,并且不携带具有t值的任何其他状态。在我们讨论电线之后,我们将看到它如何在评估电线时发挥作用。

电线:电线的主要类型&#39;在Netwire是:

()

此电汇描述Wire s e m a b 类型b的值,其执行以下操作:

  • a
  • 类型的无效值作为输入
  • 在Monad m
  • 内操作
  • 可能会抑制或不产生值,从而产生e
  • 类型的抑制值
  • 假设s
  • 给出的时间状态

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

此外,电线可能禁止或不产生值。这对于未定义计算时很有用(例如当鼠标位于应用程序窗口之外时)。这允许你实现switch之类的东西,其中一条线路变为另一条线路以继续执行(例如一个玩家完成他的跳跃)。

有了这些想法,我们可以在netwire中看到电线的主要驱动因素:

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并将前一个电汇中的当前timestateinput传递给它。然后,在基础Monad m中,它生成值Right b或禁止值为Left e,然后给出下一个用于计算的连线。

问题2:它最终如何发展。

使用SessionWire类型的值,我们可以构建一个循环,一遍又一遍地做两件事:

  1. 步骤会话以接收新的时间状态
  2. 使用新时间状态来执行电汇
  3. 这是一个程序的例子,它改变固定计数器永远计数两次:

    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事件(如鼠标单击,按键或游戏循环回调),事件如何进入会话等。

    关于如何做到这一点有一些争论。我认为在这种情况下最好利用底层的Monad m,并简单地将当前状态的快照传递给stepWire函数。这样做,我的大多数输入线看起来像这样:

    mousePos :: Wire s e (State Input) a (Float, Float)
    

    忽略导线输入,并从State monad读取鼠标输入。我使用State而不是Reader来正确处理关键的去抖动(因此点击用户界面也不会点击UI下面的内容)。状态在我的main函数中设置并传递给runState,它也执行电线步进。像这样的电线的抑制行为可以产生一些优雅的代码。例如,假设您的箭头键有rightleft,如果按下该键则会生成一个值,否则会禁止。您可以使用如下所示的导线创建角色移动:

    (right >>> moveRight) <|> (left >>> moveLeft) <|> stayPut
    

    由于导线是Alternative的实例,如果right禁止,它只会移动到下一条可能的导线。 a <|> b仅在ab都禁止时才会禁止。

    您也可以编写代码以利用netwire的Event系统,但是您必须使用{{1}自行制作返回Event的电话。 }。话虽这么说,我还没有发现这种抽象比简单的抑制更有用。

答案 1 :(得分:0)

回答这个可能为时已晚,但我在这里有一个答案,其中包含Netwire 5程序的(简约)结构:Console interactivity in Netwire?