2017-02-07 6 views
3

PureScript를 사용하여 캔버스 게임을 만들고 있는데, 특히 이벤트 모으기를 처리하는 가장 좋은 방법은 무엇인지, 특히 맞춤형 모나드 스택 내에서 콜백을 실행하는 것이 궁금합니다. 이(PureScript) Eff 이외의 모나 딕 (monadic) 컨텍스트 내에서 DOM 이벤트 리스너 콜백을 실행하려면 어떻게해야합니까?

type BaseEffect e = Eff (canvas :: CANVAS, dom :: DOM, console :: CONSOLE | e) 
type GameState = { canvas :: CanvasElement, context :: Context2D, angle :: Number } 
type GameEffect e a = StateT GameState (BaseEffect e) a 

는 내가 뭘하려는 어떤 키를 누를 때 GameState에서 "각도"속성을 변경입니다 ... 내 게임 스택이다 (개발 목적은 그래서 그래픽을 조정할 수 있습니다 단지에 대한). 이것은 내 콜백 함수 ... 그러나 addEventListener 그들이 다음 확인을 입력하지 않도록 만 EFF의와 함께 사용되는 의미있는 것처럼 eventListener 보기

changeState :: forall e. Event -> GameEffect e Unit 
changeState a = do 
    modify \s -> s { angle = s.angle + 1.0 } 
    liftEff $ log "keypress" 
    pure unit 

입니다 ...

addEventListener 
    (EventType "keypress") 
    (eventListener changeState) 
    false 
    ((elementToEventTarget <<< htmlElementToElement) bodyHtmlElement) 

addEventListener 및 eventListener를 직접 정의 할 수 있고 외부 함수 인터페이스 (GameEffect로 Eff 변경)를 사용하여 가져올 수 있다고 생각했습니다. 그 입력 된 체크하지만 브라우저에서 실행하려고 할 때 콘솔 오류가 발생했습니다.

foreign import addEventListener :: forall e. EventType -> EventListener e -> Boolean -> EventTarget -> GameEffect e Unit 
foreign import eventListener :: forall e. (Event -> GameEffect e Unit) -> EventListener e 

모나드 스택 내에서 콜백을 실행하는 가장 좋은 방법은 무엇입니까?

답변

3

이 경우 purescript-aff-coroutines을 사용합니다. 그것은 AffBaseEffect을 변경하는 것을 의미하지만 아무것도 EffAff도 할 수있는 수행 할 수 있습니다

import Prelude 

import Control.Coroutine as CR 
import Control.Coroutine.Aff as CRA 
import Control.Monad.Aff (Aff) 
import Control.Monad.Aff.AVar (AVAR) 
import Control.Monad.Eff.Class (liftEff) 
import Control.Monad.Eff.Console (CONSOLE, log) 
import Control.Monad.Rec.Class (forever) 
import Control.Monad.State (StateT, lift, modify) 
import Data.Either (Either(..)) 

import DOM (DOM) 
import DOM.Event.EventTarget (addEventListener, eventListener) 
import DOM.Event.Types (Event, EventTarget, EventType(..)) 
import DOM.HTML.Types (HTMLElement, htmlElementToElement) 
import DOM.Node.Types (elementToEventTarget) 

import Graphics.Canvas (CANVAS, CanvasElement, Context2D) 

type BaseEffect e = Aff (canvas :: CANVAS, dom :: DOM, console :: CONSOLE, avar :: AVAR | e) 
type GameState = { canvas :: CanvasElement, context :: Context2D, angle :: Number } 
type GameEffect e = StateT GameState (BaseEffect e) 

changeState :: forall e. Event -> GameEffect e Unit 
changeState a = do 
    modify \s -> s { angle = s.angle + 1.0 } 
    liftEff $ log "keypress" 
    pure unit 

eventProducer :: forall e. EventType -> EventTarget -> CR.Producer Event (GameEffect e) Unit 
eventProducer eventType target = 
    CRA.produce' \emit -> 
    addEventListener eventType (eventListener (emit <<< Left)) false target 

setupListener :: forall e. HTMLElement -> GameEffect e Unit 
setupListener bodyHtmlElement = CR.runProcess $ consumer `CR.pullFrom` producer 
    where 
    producer :: CR.Producer Event (GameEffect e) Unit 
    producer = 
    eventProducer 
     (EventType "keypress") 
     ((elementToEventTarget <<< htmlElementToElement) bodyHtmlElement) 
    consumer :: CR.Consumer Event (GameEffect e) Unit 
    consumer = forever $ lift <<< changeState =<< CR.await 

그래서 여기에 eventProducer 함수가 이벤트 리스너의 코 루틴 프로듀서를 만든 다음 setupListener는 이론적 addEventListener의 동등하지 당신이 위에 사용했다.

수신기의 제작자를 만든 다음 Event을 받으면 changeState을 호출하는 소비자에게 연결하여 작동합니다. 코 루틴 프로세스는 모나드 컨텍스트로 실행됩니다. 여기서 모든 것이 작동하는 이유는 GameEffect 모나드입니다.

+0

나는 그것을 작동시킬 수 있었지만, 오직 하나의 키 누르기와 keypress가 그래픽 렌더링을 막는 것처럼 보였다. 첫 번째 키를 누르면 소비자가 닫히고 그래픽이 렌더링됩니다. 나는'CR.runProcess $ connect producer consumer'를 사용할 수 있도록 GameEffect (newtype로 래핑 된)에 대한'Parallel' 인스턴스를 생성해야한다고 생각합니다. 나는 옳은 길에있는 것처럼 들리는가? – Albtzrly

+0

죄송합니다. 'consumer'는 다음과 같이 정의되어야합니다 :'forever $ lift <<< changeState = << CR.await','Control.Monad.Rec.Class'에서 영원히옵니다. –

+0

쉬운 대답은 [ "just fork it"] (https://github.com/slamdata/purescript-fork)이 될 수 있기 때문에 블로킹은 조금 까다 롭습니다. 그러나 'StateT'를 포크하는 인스턴스는 없습니다. 당신은 아마도'GameEffect'를위한'newtype'을 가지고 뭔가를 생각해 낼 수 있습니다. 렌더링 루프에서'setupListener'를 호출하는 것이 좋습니다. 설치가 끝나면 한 번만 할 수 있어야합니다. 근본적으로 나는이 비트를 위해 좋은 대답을 가지지 않고있다, 미안하다! –