有一个非常简单和通用的解决方案。关键思想是永远不要合并不同类型的源。相反,您只合并相同类型的源。实现这项工作的诀窍在于,您将所有不同来源的输出包装在代数数据类型中。
我不是很熟悉netwire
,所以如果您不介意,我将pipes
用作示例。我们想要的是一个merge
函数,它接受一个源列表并将它们组合成一个源,同时合并它们的输出,当它们全部完成时完成。密钥类型签名是:
merge
:: (Proxy p)
=> [() -> Producer ProxyFast a IO r] -> () -> Producer p a IO ()
这只是说它需要一个Producer
type 值的列表a
,并将它们组合成一个Producer
type 值a
。下面是 的实现merge
,如果你很好奇并且想跟随:
import Control.Concurrent
import Control.Concurrent.Chan
import Control.Monad
import Control.Proxy
fromNChan :: (Proxy p) => Int -> Chan (Maybe a) -> () -> Producer p a IO ()
fromNChan n0 chan () = runIdentityP $ loop n0 where
loop 0 = return ()
loop n = do
ma <- lift $ readChan chan
case ma of
Nothing -> loop (n - 1)
Just a -> do
respond a
loop n
toChan :: (Proxy p) => Chan ma -> () -> Consumer p ma IO r
toChan chan () = runIdentityP $ forever $ do
ma <- request ()
lift $ writeChan chan ma
merge
:: (Proxy p)
=> [() -> Producer ProxyFast a IO r] -> () -> Producer p a IO ()
merge producers () = runIdentityP $ do
chan <- lift newChan
lift $ forM_ producers $ \producer -> do
let producer' () = do
(producer >-> mapD Just) ()
respond Nothing
forkIO $ runProxy $ producer' >-> toChan chan
fromNChan (length producers) chan ()
现在,让我们假设我们有两个输入源。第一个以一秒的间隔生成从1
到的整数:10
throttle :: (Proxy p) => Int -> () -> Pipe p a a IO r
throttle microseconds () = runIdentityP $ forever $ do
a <- request ()
respond a
lift $ threadDelay microseconds
source1 :: (Proxy p) => () -> Producer p Int IO ()
source1 = enumFromS 1 10 >-> throttle 1000000
第二个来源String
从用户输入中读取三个 s:
source2 :: (Proxy p) => () -> Producer p String IO ()
source2 = getLineS >-> takeB_ 3
我们想组合这两个源,但是它们的输出类型不匹配,所以我们定义了一个代数数据类型来将它们的输出统一为一个类型:
data Merge = UserInput String | AutoInt Int deriving Show
现在我们可以通过将它们的输出包装在我们的代数数据类型中,将它们组合成一个相同类型的生产者列表:
producers :: (Proxy p) => [() -> Producer p Merge IO ()]
producers =
[ source1 >-> mapD UserInput
, source2 >-> mapD AutoInt
]
我们可以很快地对其进行测试:
>>> runProxy $ merge producers >-> printD
AutoInt 1
Test<Enter>
UserInput "Test"
AutoInt 2
AutoInt 3
AutoInt 4
AutoInt 5
Apple<Enter>
UserInput "Apple"
AutoInt 6
AutoInt 7
AutoInt 8
AutoInt 9
AutoInt 10
Banana<Enter>
UserInput "Banana"
>>>
现在你有一个组合源。然后,您可以编写游戏引擎以仅从该源读取,在输入上进行模式匹配,然后适当地运行:
engine :: (Proxy p) => () -> Consumer p Merge IO ()
engine () = runIdentityP loop where
loop = do
m <- request ()
case m of
AutoInt n -> do
lift $ putStrLn $ "Generate unit wave #" ++ show n
loop
UserInput str -> case str of
"quit" -> return ()
_ -> loop
让我们尝试一下:
>>> runProxy $ merge producers >-> engine
Generate unit wave #1
Generate unit wave #2
Generate unit wave #3
Test<Enter>
Generate unit wave #4
quit<Enter>
>>>
我想同样的技巧也适用于netwire
.