如何实现与Netwire(5.0.1)的冲突

时间:2016-08-04 06:16:58

标签: haskell frp netwire

我正在尝试使用Netwire为移动物体建模,并想知道推荐的方法来实现类似于从墙上弹出一个球的东西。我遇到了几种可能的方法,我需要一些帮助才能让它们起作用。

动作代码如下所示:

type Pos = Float
type Vel = Float

data Collision = Collision | NoCollision
           deriving (Show)

motion :: (HasTime t s, MonadFix m) => Pos -> Vel -> Wire s Collision m a Pos
motion x0 v0 = proc _ -> do
             rec
                 v <- vel <<< delay 0 -< x
                 x <- pos x0 -< v
             returnA -< x

pos :: (HasTime t s, MonadFix m) => Pos -> Wire s Collision m Vel Pos
pos x0 = integral x0

main :: IO ()
main = testWire clockSession_ (motion 0 5)

建议使用速度箭头导致某个位置反弹的方法是什么,例如x = 20?

我已经看到了三种不同的方法:

  • netwire -->函数似乎最简单。我有一个使用此功能的原型但我不知道如何基于碰撞时的速度制作新的速度箭头,我只能使用一个固定值,如果对象可以加速则该值无用。

    vel :: (HasTime t s, MonadFix m) => Wire s Collision m Pos Vel
    vel = pure 5 . unless (>20) --> pure (-5)
    
  • 在Netwire中使用Eventswitch。我不明白如何使用它。

  • 使用一般箭头可用的(|||)功能。

前两个似乎是最好的选择,但我不知道如何实现它们。

我已经看到了其他类似的问题,但不同版本的netwire之间的不兼容性使得答案对我没用。

1 个答案:

答案 0 :(得分:3)

免责声明:我不能对“推荐”的内容发表评论,但我可以展示一种能够做你想做的事情。

我想描述两种方法:
第一种是使用有状态线,与this a bit outdated tutorial from 2013非常相似,但基于Netwire 5.0.2 第二种是使用无状态线。因为它们是无状态的,所以它们需要反馈它们先前的值,这使得导线的输入类型和导线的最终组合更加复杂。否则它们非常相似。

两个示例中涉及的基本类型是

type Collision = Bool
type Velocity = Float
type Position = Float

有状态

您可以使用两条(有状态的)电线对问题进行建模,然后合并。

一根导线模拟速度,这是恒定的,并在碰撞发生时改变方向。 (简化)类型是Wire s e m Collision Velocity,即它的输入是发生碰撞而输出是当前速度。

另一个模拟位置,并处理碰撞。 (简化)类型是Wire s e m Velocity (Position, Collision),即它需要一个速度,计算新位置并返回该位置并发生碰撞。

最后将速度输入位置线,并将其中的碰撞结果反馈到速度线中。

让我们来看看速度线的细节:

-- stateful fixed velocity wire that switches direction when collision occurs
velocity :: Velocity -> Wire s e m Collision Velocity
velocity vel = mkPureN $ \collision ->
  let nextVel = if collision then negate vel else vel
  in (Right nextVel, velocity nextVel)

mkPureN创建一个仅依赖于输入及其自身状态的有状态连线(不是Monad或时间)。状态是当前速度,如果Collision=True作为输入传递,则下一个速度被否定。返回值是一对速度值和具有新状态的新线。

对于该位置,直接使用integral线是不够的。我们想要一个integral的增强的“有界”版本,它确保该值保持低于上限且大于0,并在发生此类冲突时返回信息。

-- bounded integral [0, bound]
pos :: HasTime t s => Position -> Position -> Wire s e m Velocity (Position, Collision)
pos bound x = mkPure $ \ds dx ->
  let dt = realToFrac (dtime ds)
      nextx' = x + dt*dx -- candidate
      (nextx, coll)
        | nextx' <= 0 && dx < 0     = (-nextx', True)
        | nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
        | otherwise                 = (nextx', False)
  in (Right (nextx, coll), pos bound nextx)

mkPuremkPureN类似,但允许电线依赖于时间 dt是时差 nextx'是新职位,因为它会由integral返回 以下行检查边界并返回新位置(如果发生了碰撞)和新线路处于新状态。

最后,您使用recdelay将它们相互馈送。完整的例子:

{-# LANGUAGE Arrows #-}

module Main where

import Control.Monad.Fix
import Control.Wire
import FRP.Netwire

type Collision = Bool
type Velocity = Float
type Position = Float

-- bounded integral [0, bound]
pos :: HasTime t s => Position -> Position -> Wire s e m Velocity (Position, Collision)
pos bound x = mkPure $ \ds dx ->
  let dt = realToFrac (dtime ds)
      nextx' = x + dt*dx -- candidate
      (nextx, coll)
        | nextx' <= 0 && dx < 0     = (-nextx', True)
        | nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
        | otherwise                 = (nextx', False)
  in (Right (nextx, coll), pos bound nextx)

-- stateful fixed velocity wire that switches direction when collision occurs
velocity :: Velocity -> Wire s e m Collision Velocity
velocity vel = mkPureN $ \collision ->
  let nextVel = if collision then negate vel else vel
  in (Right nextVel, velocity nextVel)

run :: (HasTime t s, MonadFix m) => Position -> Velocity -> Position -> Wire s () m a Position
run start vel bound = proc _ -> do
  rec
    v <- velocity vel <<< delay False -< collision
    (p, collision) <- pos bound start -< v
  returnA -< p

main :: IO ()
main = testWire clockSession_ (run 0 5 20)

无国籍

无状态变体与有状态变体非常相似,不同之处在于状态徘徊到导线的输入类型,而不是创建导线的函数的参数。

因此,速度线需要一个元组(Velocity, Collision)作为输入,我们可以举起一个函数来创建它:

-- pure stateless independent from time
-- output velocity is input velocity potentially negated depending on collision
velocity :: Monad m => Wire s e m (Velocity, Collision) Velocity
velocity = arr $ \(vel, collision) -> if collision then -vel else vel

您还可以使用mkSF_中的Control.Wire.Core功能(然后摆脱对Monad m的限制。)

pos变为

-- pure stateless but depending on time
-- output position is input position moved by input velocity (depending on timestep)
pos :: HasTime t s => Position -> Wire s e m (Position, Velocity) (Position, Collision)
pos bound = mkPure $ \ds (x,dx) ->
  let dt = realToFrac (dtime ds)
      nextx' = x + dt*dx -- candidate
      (nextx, coll)
        | nextx' <= 0 && dx < 0     = (-nextx', True)
        | nextx' >= bound && dx > 0 = (bound - (nextx' - bound), True)
        | otherwise                 = (nextx', False)
  in (Right (nextx, coll), pos bound)

这里我们仍然需要使用mkPure,因为没有专门用于依赖于时间的无状态线的功能。

为了连接两根导线,我们现在必须将速度输出馈送到自身和位置,并从pos导线将位置输入到自身,并将碰撞信息输入速度导线。

但实际上,对于无状态线,您还可以分离pos线的“积分”和“边界检查”部分。 pos线是Wire s e m (Position, Velocity) Position直接返回上面的nextx'boundedPos线是Wire s e m (Position, Velocity) (Position, Collision)从{{{1}获取新位置1}}和速度,并应用绑定检查。这样,不同的逻辑部分就会很好地分开。

完整示例:

pos