在 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
我正在尝试使用 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