在 Haskell 仆人应用程序中设置应用程序启动时间间隔

Setting off a interval on application launch in a Haskell Servant app

我正在尝试使用 Servant 为基于浏览器的游戏构建后端,我想要某种游戏循环,让我每 x 秒发出一次请求。我已经在 IORef 中包含了一些游戏状态,作为让某些东西正常工作的初步尝试,我尝试每 2 秒更新一次我的状态值。这是我拥有的:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}

module Main where

import Prelude ()
import Prelude.Compat

import Control.Concurrent(forkIO, threadDelay)
import Control.Monad(forever)
import Control.Monad.Reader
import Data.Aeson.Compat
import Data.Aeson.Types
import Data.Maybe
import Data.IORef
import GHC.Generics
import Network.Wai.Handler.Warp
import Servant
import Servant.Utils.StaticFiles (serveDirectory)

type Api = "players" :> Get '[JSON] [Player]
      :<|> "tick" :> Get '[JSON] Integer

type Game = Api :<|> Raw

data Player = Player
  { name :: String
  } deriving (Eq, Show, Generic)

instance ToJSON Player

data Action = AddPlayer Player
  | Tick

data State = State {
    players :: [Player]
  , tick :: Integer }

initialState :: State
initialState = State { players = []
                     , tick = 0
                     }

update :: Action -> State -> State
update action state =
  case action of
    AddPlayer p ->
        state { players = [p] ++ (players state) }

    Tick ->
        state { tick = 1 + (tick state) }

updateState :: Action -> IORef State -> IO State
updateState action state =
  atomicModifyIORef state (\s -> (next s, s))
  where next = update action

seconds :: Int -> Int
seconds = (* 1000000)

getPlayers :: IORef State -> Handler [Player]
getPlayers state = liftIO $ do
  _ <- updateState (AddPlayer $ Player "Me") state
  s <- readIORef state
  return $ players s

getTick :: IORef State -> Handler Integer
getTick state = liftIO $ do
  s <- readIORef state
  return $ tick s

everything :: Proxy Game
everything = Proxy

server :: IORef State -> Server Game
server state = (getPlayers state
  :<|> getTick state)
  :<|> serveDirectoryFileServer "./build"

app :: IORef State -> Application
app state = serve everything (server state)

main :: IO ()
main = do
  let port = 8000
      state = newIORef initialState

  threadId <- forkIO $ forever $ do
    threadDelay $ seconds 2
    return $ updateState Tick =<< state

  putStrLn $ "Running server on " ++ show port
  run port . app =<< state

应用程序生成,但它没有按照我的意愿运行,总是 returns 0 访问 /tick。我猜这要么与在单独的线程中发生的状态更改有关,要么与 IO 值在两个不同的时间传递有关?但是我相信 forkIO 必须发生在 IO 块内,所以我不确定如何让这两个值满足。

这种事情正是Haskell力求避免的,这可能就是为什么它如此难以实现的原因。我的问题是我想有某种方法每 x 秒触发一个函数(能够修改 State),如果解决方案涉及沿着完全独立的路线走,那就这样吧。

state 每次都创建新的 IORef。您的 Web 服务器和您的线程更新函数在两个不同的 IORef 上工作,因此在两个不同的状态下工作。您想要共享 IORef。像下面这样的东西应该可以工作。

main :: IO ()
main = do
  let port = 8000
  ref <- newIORef initialState
  threadId <- forkIO $ forever $ do
    threadDelay $ seconds 2
    updateState state ref
  run port $ app ref