Netwire 5的基本结构是什么?

Mas*_*tic 8 haskell functional-programming frp reactive-programming netwire

我正在尝试进入Netwire,我已经挖掘了文档,介绍,教程等等,但几乎所有的教程和现有代码都已经过时了Netwire 5并使用了Netwire 4中不再与之相关的功能我们.该自述是一种有益的,但不是万能的编译和它仍然勉强提供了上手的足够信息.

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

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

还有其他相关的东西.

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

谢谢!

Mok*_*sha 9

免责声明:我无法找到任何使用Netwire的大型程序,所以我要写的所有内容都应该带有一点点,因为这是基于我自己使用Netwire的经验.我在这里使用的例子主要来自我自己的图书馆试图使用FRP编写游戏,并且可能不是"正确的方式"来做事情.


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

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

newtype Session m s = Session { stepSession :: m (s, Session m s) }
Run Code Online (Sandbox Code Playgroud)

这里,SessionMonad(通常IO)位于Monad中,每次评估时,都会返回type s和new 的"time state"值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)
Run Code Online (Sandbox Code Playgroud)

例如,在游戏中,您通常需要固定的时间步来进行更新调用.netwire编码这个概念:

countSession_ :: Applicative m => t -> Session m (Timed t ())
Run Code Online (Sandbox Code Playgroud)

A countSession_将时间步长作为输入,在这种情况下是类型的固定值t,并产生Session其状态值是类型的Timed t ().这意味着它们只编码单个值类型t,并且不携带任何具有该()值的附加状态.在我们讨论电线之后,我们将看到它如何在评估电线时发挥作用.

电线: Netwire中"电线"的主要类型是:

Wire s e m a b
Run Code Online (Sandbox Code Playgroud)

此电线描述了执行以下操作的类型的无功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)
Run Code Online (Sandbox Code Playgroud)

stepWire wire timestate input正是我们之前所说的:它需要一个wire并传递当前timestateinput前一个线.然后,在底层的Monad中m,它或者产生一个值Right b或者使用值抑制Left e,然后给出下一个用于计算的线.

问题2:它最终如何进入主流.

手持式的价值观SessionWire,我们可以构造一个循环,它唱完两件事情:

  1. 步骤会话以接收新的时间状态
  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))
Run Code Online (Sandbox Code Playgroud)

问题3:如何处理IO事件(如鼠标单击,按键或游戏循环回调),事件如何进入会话等.

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

mousePos :: Wire s e (State Input) a (Float, Float)
Run Code Online (Sandbox Code Playgroud)

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

(right >>> moveRight) <|> (left >>> moveLeft) <|> stayPut
Run Code Online (Sandbox Code Playgroud)

由于导线是一个实例Alternative,如果right禁止,它只会移动到下一条可能的导线.a <|> b会抑制只有两个ab抑制.

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