免责声明:我无法评论“推荐”的内容,但我可以展示一种可以满足您的需求的方法。
我想描述两种方法:
第一个是使用有状态的连线,与this a bit outdated tutorial from 2013 非常相似,但基于 Netwire 5.0.2。
第二个是使用无状态的电线。因为它们是无状态的,所以需要反馈它们以前的值,这使得线的输入类型和线的最终组合更加复杂。否则它们非常相似。
两个示例中涉及的基本类型是
type Collision = Bool
type Velocity = Float
type Position = Float
有状态
您可以用两条(有状态的)线来模拟您的问题,然后将它们组合起来。
一根线对速度进行建模,该速度是恒定的,并且在发生碰撞时会改变方向。 this 的(简化)类型是Wire s e m Collision Velocity,即它的输入是碰撞是否发生,输出是当前速度。
另一个模拟位置并处理碰撞。 this 的(简化)类型是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)
mkPure 类似于mkPureN,但允许线路依赖于时间。
dt 是时间差。
nextx' 是新位置,因为它将由integral.
以下几行检查边界并返回新位置,如果发生碰撞,新线具有新状态。
最后,您使用rec 和delay 将它们相互连接。完整示例:
{-# 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
您也可以使用Control.Wire.Core 中的函数mkSF_(然后摆脱对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),它从pos 和速度获取新位置,并应用绑定检查.这样,不同的逻辑部分就可以很好地分开。
完整示例:
{-# LANGUAGE Arrows #-}
module Main where
import Control.Monad.Fix
import Control.Wire
import FRP.Netwire
type Collision = Bool
type Velocity = Float
type Position = Float
-- pure stateless but depending on time
-- output position is input position moved by input velocity (depending on timestep)
pos :: HasTime t s => Wire s e m (Position, Velocity) Position
pos = mkPure $ \ds (x,dx) ->
let dt = realToFrac (dtime ds)
in (Right (x + dt*dx), pos)
-- pure stateless independent from time
-- input position is bounced off the bounds
boundedPos :: Monad m => Position -> Wire s e m (Position, Velocity) (Position, Collision)
boundedPos bound = arr $ \(x, dx) ->
let (nextx, collision)
| x <= 0 && dx < 0 = (-x, True)
| x >= bound && dx > 0 = (bound - (x - bound), True)
| otherwise = (x, False)
in (nextx, 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
-- plug the wires into each other
run :: (HasTime t s, MonadFix m) => Position -> Velocity -> Position -> Wire s () m a Position
run start vel bound = proc _ -> do
rec
v <- velocity <<< delay (vel, False) -< (v, collision)
lastPos <- delay start -< p'
p <- pos -< (lastPos, v)
(p', collision) <- boundedPos bound -< (p, v)
returnA -< p'
main :: IO ()
main = testWire clockSession_ (run 0 5 20)