netserver/hedgewars-server.hs
changeset 1370 ff8863ebde17
child 1381 e9754d1d61a9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netserver/hedgewars-server.hs	Sat Oct 18 11:57:46 2008 +0000
@@ -0,0 +1,94 @@
+module Main where
+
+import Network
+import IO
+import System.IO
+import Control.Concurrent
+import Control.Concurrent.STM
+import Control.Exception (setUncaughtExceptionHandler, handle, finally)
+import Control.Monad (forM, forM_, filterM, liftM)
+import Maybe (fromMaybe)
+import Data.List
+import Miscutils
+import HWProto
+import Opts
+
+acceptLoop :: Socket -> TChan ClientInfo -> IO ()
+acceptLoop servSock acceptChan = do
+	(cHandle, host, port) <- accept servSock
+	cChan <- atomically newTChan
+	forkIO $ clientLoop cHandle cChan
+	atomically $ writeTChan acceptChan (ClientInfo cChan cHandle "" 0 "" False)
+	hPutStrLn cHandle "CONNECTED\n"
+	hFlush cHandle
+	acceptLoop servSock acceptChan
+
+
+listenLoop :: Handle -> [String] -> TChan [String] -> IO ()
+listenLoop handle buf chan = do
+	str <- hGetLine handle
+	if str == "" then do
+		atomically $ writeTChan chan buf
+		listenLoop handle [] chan
+		else
+		listenLoop handle (buf ++ [str]) chan
+
+
+clientLoop :: Handle -> TChan [String] -> IO ()
+clientLoop handle chan =
+	listenLoop handle [] chan
+		`catch` (const $ clientOff >> return ())
+	where clientOff = atomically $ writeTChan chan ["QUIT"] -- if the client disconnects, we perform as if it sent QUIT message
+
+
+sendAnswers [] _ clients _ = return clients
+sendAnswers ((handlesFunc, answer):answers) client clients rooms = do
+	let recipients = handlesFunc client clients rooms
+	putStrLn ("< " ++ (show answer))
+
+	clHandles' <- forM recipients $
+		\ch -> Control.Exception.handle (\e -> putStrLn (show e) >> hClose ch >> return [ch]) $
+			if (not $ null answer) && (head answer == "off") then hClose ch >> return [ch] else -- probably client with exception, don't send him anything
+			do
+			forM_ answer (\str -> hPutStrLn ch str)
+			hPutStrLn ch ""
+			hFlush ch
+			if (not $ null answer) && (head answer == "BYE") then hClose ch >> return [ch] else return []
+
+	let mclients = remove clients $ concat clHandles'
+
+	sendAnswers answers client mclients rooms
+	where
+		remove list rmClHandles = deleteFirstsBy2t (\ a b -> (Miscutils.handle a) == b) list rmClHandles
+
+
+mainLoop :: Socket -> TChan ClientInfo -> [ClientInfo] -> [RoomInfo] -> IO ()
+mainLoop servSock acceptChan clients rooms = do
+	r <- atomically $ (Left `fmap` readTChan acceptChan) `orElse` (Right `fmap` tselect clients)
+	case r of
+		Left ci -> do
+			mainLoop servSock acceptChan (clients ++ [ci]) rooms
+		Right (cmd, client) -> do
+			putStrLn ("> " ++ show cmd)
+
+			let (clientsFunc, roomsFunc, answers) = handleCmd client clients rooms $ cmd
+			let mrooms = roomsFunc rooms
+			let mclients = (clientsFunc clients)
+			let mclient = fromMaybe client $ find (== client) mclients
+
+			clientsIn <- sendAnswers answers mclient mclients mrooms
+			
+			mainLoop servSock acceptChan clientsIn mrooms
+
+
+startServer serverSocket = do
+	acceptChan <- atomically newTChan
+	forkIO $ acceptLoop serverSocket acceptChan
+	mainLoop serverSocket acceptChan [] []
+
+
+main = withSocketsDo $ do
+	flags <- opts
+	putStrLn $ "Listening on port " ++ show (getPort flags)
+	serverSocket <- listenOn $ PortNumber (getPort flags)
+	startServer serverSocket `finally` sClose serverSocket