{-| Module : Description : Copyright : (c) Alexander Vieth, 2015 Licence : BSD3 Maintainer : aovieth@gmail.com Stability : experimental Portability : non-portable (GHC only) -} {-# LANGUAGE AutoDeriveTypeable #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE GADTs #-} import Control.Concurrent import Control.Concurrent.Async import Control.Concurrent.STM import qualified Data.Map as M import Data.AtLeast import Data.TypeNat.Vect import Data.Monoid import Data.Hourglass import System.Hourglass import Rest.Api import Rest.Driver.Wai import Network.Wai import Network.Wai.Handler.Warp import Network.Wai.Handler.WarpTLS import Options.Applicative import Diplomacy.Game import Types.GameState import Types.ServerOptions as Options import Types.Credentials import Types.Server import Types.GameId import Router import qualified Resources.Advance as Advance main :: IO () main = do opts <- execParser (info Options.parser mempty) let username = adminUsername opts let password = adminPassword opts let cert = certificateFile opts let key = Options.keyFile opts let port = Options.port opts initialState <- serverState username password tvar <- newTVarIO initialState let app = waiApp tvar let daemon = advanceDaemon tvar async daemon --x <- mkJsApi (ModuleName "diplomacy") True (mkVersion 1 0 0) (router) --putStrLn x putStrLn ("Starting secure server on port " ++ show port) runTLS (tlsSettings cert key) (setPort port defaultSettings) app putStrLn "Goodbye" waiApp :: TVar ServerState -> Application waiApp tvar = let runner :: forall a . Server a -> IO a runner = runDiplomacyServer tvar in apiToApplication runner api -- To be run in a separate thread; this IO will periodically check every game -- and advance it if it's started and hasn't been advanced for longer than its -- period. -- -- TODO this should get its own module. That would allow us to trim many -- imports in this Main file. advanceDaemon :: TVar ServerState -> IO () advanceDaemon tvar = do -- We check every second. threadDelay oneSecond advanceGamesIO tvar advanceDaemon tvar where -- threadDelay uses microseconds. oneSecond = 1000000 advanceGamesIO :: TVar ServerState -> IO () advanceGamesIO tvar = do t <- timeCurrent gameIds <- atomically $ do state <- readTVar tvar let (advancedGameIds, newState) = advanceState t state writeTVar tvar newState return advancedGameIds return () advanceState :: Elapsed -> ServerState -> ([GameId], ServerState) advanceState t state = let (gameIds, nextGames) = M.foldWithKey (advanceGameFold t) ([], M.empty) (games state) in (gameIds, state { games = nextGames }) advanceGameFold :: Elapsed -> GameId -> (Password, GameState) -> ([GameId], M.Map GameId (Password, GameState)) -> ([GameId], M.Map GameId (Password, GameState)) advanceGameFold t gameId (pwd, gameState) (ids, out) = case gameState of GameStarted m (AtLeast (VCons (SomeGame game) VNil) rest) duration duration' elapsed paused -> let Elapsed t' = t - elapsed (observedDuration, _) = fromSeconds t' thresholdDuration = case game of TypicalGame _ _ _ _ _ -> duration RetreatGame _ _ _ _ _ _ _ -> duration' AdjustGame _ _ _ _ _ -> duration' in if not paused && observedDuration > thresholdDuration then let nextGame = Advance.advance (SomeGame game) in (gameId : ids, M.insert gameId (pwd, GameStarted m (AtLeast (VCons nextGame VNil) ((SomeGame game) : rest)) duration duration' t False) out) else (ids, M.insert gameId (pwd, GameStarted m (AtLeast (VCons (SomeGame game) VNil) rest) duration duration' elapsed paused) out) _ -> (ids, M.insert gameId (pwd, gameState) out) api = [(mkVersion 1 0 0, Some1 router)]