Для этого есть очень простое и универсальное решение. Основная идея заключается в том, что вы никогда не объединяете источники разных типов. Вместо этого вы объединяете только источники одного типа. Хитрость, которая заставляет это работать, заключается в том, что вы заключаете вывод всех ваших разнообразных источников в алгебраический тип данных.
Я не совсем знаком с netwire
, поэтому, если вы не возражаете, я буду использовать pipes
в качестве примера. Что нам нужно, так это функция merge
, которая берет список источников и объединяет их в один источник, который одновременно объединяет их выходные данные и завершает работу, когда все они завершены. Подпись типа ключа:
merge
:: (Proxy p)
=> [() -> Producer ProxyFast a IO r] -> () -> Producer p a IO ()
Это просто говорит о том, что он берет список Producer
значений типа a
и объединяет их в один Producer
значений типа 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
с пользовательского ввода:
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
.
person
Gabriel Gonzalez
schedule
03.02.2013