我正在使用 PureScript 制作 Canvas 游戏,我想知道处理事件监听器的最佳方法是什么,特别是在自定义 monad 堆栈中运行回调。这是我的游戏堆栈...
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 中的“角度”属性(仅出于开发目的,以便我可以调整图形)。这是我的回调函数...
changeState :: forall e. Event -> GameEffect e Unit
changeState a = do
modify \s -> s { angle = s.angle + 1.0 }
liftEff $ log "keypress"
pure unit
但是 addEventListener 和 eventListener 看起来它们只能与 Eff 一起使用,因此以下内容不会键入检查...
addEventListener
(EventType "keypress")
(eventListener changeState)
false
((elementToEventTarget <<< htmlElementToElement) bodyHtmlElement)
我想我可以自己定义 addEventListener 和 eventListener 使用外部函数接口(interface)导入它们(将 Eff 更改为 GameEffect)。该类型已检查,但当我尝试在浏览器中运行时导致控制台错误。
foreign import addEventListener :: forall e. EventType -> EventListener e -> Boolean -> EventTarget -> GameEffect e Unit
foreign import eventListener :: forall e. (Event -> GameEffect e Unit) -> EventListener e
处理 monad 堆栈中正在运行的回调的最佳方法是什么?
最佳答案
我会使用purescript-aff-coroutines
为了这。这意味着将 BaseEffect
更改为 Aff
,但任何 Eff
能做的事情 Aff
也能做:
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
的消费者。协程进程在单子(monad)上下文中运行,这里是您的 GameEffect
单子(monad),这就是一切顺利的原因。
关于dom - (PureScript) 如何在 Eff 以外的单子(monad)上下文中运行 DOM 事件监听器回调?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42080807/