在Haskell Servant应用程序中启动应用程序启动时间间隔 [英] Setting off a interval on application launch in a Haskell Servant app

查看:143
本文介绍了在Haskell Servant应用程序中启动应用程序启动时间间隔的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图为使用Servant的基于浏览器的游戏构建后端,我希望有一种游戏循环让我每隔< x 秒发射请求。我已经在 IORef 中包含了一些游戏状态,并且作为尝试获取某些内容的初始尝试,我试图每2秒更新一次状态值。这里是我的:

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

模块Main其中

导入Prelude()
导入Prelude.Compat

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

键入Api =players:>获取'[JSON] [Player]
:< |> tick:>获取'[JSON]整数

类型游戏= Api:< |>原始

数据播放器=播放器
{名称::字符串
}派生(Eq,Show,Generic)

实例ToJSON播放器

data Action = AddPlayer Player
| Tick

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

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

update :: Action - >状态 - >状态
更新动作状态=
案例动作
AddPlayer p - >
state {players = [p] ++(players state)}

Tick - >
状态{tick = 1 +(tick状态)}

updateState :: Action - > IORef状态 - > IO State
updateState action state =
atomicModifyIORef state(\s - >(next s,s))
where next =更新动作

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

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

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

everything :: Proxy Game
everything = Proxy

server :: IORef状态 - >服务器游戏
服务器状态=(getPlayers状态
:< |> getTick状态)
:< |> serveDirectoryFileServer./build

app :: IORef状态 - >应用程序
app state =提供所有内容(服务器状态)
$ b $ main main :: IO()
main = do
let port = 8000
state = newIORef initialState

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

putStrLn $在++ show port
run port上运行服务器。 app =<<状态

应用程序可以构建,但它不会按照我想要的那样访问 / tick 总是返回 0 。我猜这是要改变状态在单独的线程中发生,还是在两个不同的时间传递 IO 值?不过我相信 forkIO 必须发生在 IO 块中,所以我不确定如何获得这两个值见面。

这种事情正是Haskell试图避免的,这可能是为什么它很难实现。我的问题是,我想要一些方法来触发一个函数(可以修改 State )每个 x 秒,如果解决方案涉及到一个完全独立的路线,那就这样吧。

解决方案

/ code>每次创建新的IORef。您的Web服务器和您的线程更新功能适用于两种不同的IORefs,因此可以在两种不同的状态下工作。你想分享IORef。

  main :: IO()
main = do
let port = 8000
ref< - newIORef initialState
threadId< - forkIO $ forever $ do
threadDelay $秒2
updateState状态ref
运行端口$ app ref


I'm trying to build the backend for a browser based game using Servant, I want to have some kind of game loop that lets me fire out requests every x seconds. I already have some game state contained in an IORef, and as an initial attempt to get something working I am trying to update my state value every 2 seconds. Here is what I have:

{-# 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

The app builds, but it doesn't do what I want it to, visiting /tick always returns 0. I'm guessing this is either something to do with the change to state happening in a separate thread, or the IO value being passed in two separate times? However I believe that forkIO has to happen inside an IO block, so I'm unsure how to get the two values to meet.

This kind of thing is exactly what Haskell seeks to avoid, which is probably why it's so difficult to achieve. My problem is that I want to have some way to trigger a function (that is able to modify State) every x seconds, if the solution involves going down an entirely separate route then so be it.

解决方案

state creates new IORef every time. Your web server and your thread update function work on two different IORefs and therefore on two different states. You want to share the IORef. Something like the following should work.

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

这篇关于在Haskell Servant应用程序中启动应用程序启动时间间隔的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆