Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Game server in Haskell

I'm using Network and Gloss for a game server in Haskell. It works fine, except that the client has to close for the server to receive the data it sent. I bet it's a case of laziness...

Minimalist server:

import Network
import System.IO

main = do
    sock <- listenOn (PortNumber (fromIntegral 12345))
    loop sock

loop sock = do
    (hIn, _, _) <- accept sock
    str <- hGetContents hIn
    print str
    loop sock

Minimalist client:

import Network
import System.IO
import Graphics.Gloss.Interface.IO.Game

main = playIO
    (InWindow "Test Multi" (500, 500) (500, 500))
    white
    60
    Nothing
    draw
    (\_ x -> return x)
    advance

draw Nothing = return blank
draw (Just x) = return (Text (show x))

advance _ Nothing = do
    hOut <- connectTo "000.000.0.0" (PortNumber (fromIntegral 12345))
    hSetBuffering hOut NoBuffering
    hPutStr hOut "Hello!"
    return (Just hOut)
advance _ x = return x

I start the server, wait 10 seconds, then start the client, wait 15 seconds, see that nothing happens on the server, closes the client, see "Hello!" suddenly appear on the server. I would like "Hello!" to appear while the client is running, in an advance call, otherwise I can't make a multiplayer game (sob)!


However, if I change the client's code to

main = loop Nothing
loop x = do
    x' <- advance 0 x
    getLine

the sever immediatly shows "Hello!" while the client is waiting for my input.


I tried, as suggested in another question, to use bang patterns and hClose:

-- ...
!str <- hGetContents hIn
hClose hIn
-- ...

which makes the output appear immediatly, without the client closing. That's great. But, I plan to use bytestrings because the data I send to the server is serialized, so I import qualified Data.ByteString as B and change hGetContents to B.hGetContents, which makes the problem re-appear.


The problem was indeed a case of laziness. hGetContents reads lazily all the contents of the Handle, so it finishes only when it's closed, when the client aborts the connection. Instead, I used hGetLine that returns the content each time it encounters a \n, which I use as a end-of-message tag.

like image 575
L01man Avatar asked Aug 26 '12 21:08

L01man


People also ask

Can you make a game in Haskell?

Making a game in Haskell is a pioneering process. Despite the fact that there's a page on the wiki and a full subreddit dedicated to the purpose of making a game in this beautiful language, not many people have actually succeeded making anything close to what current game developers can already achieve.

Is Haskell good for game development?

Yeah, Haskell is much faster than any other popular language: you can develop complex applications 2–5 times faster and with smaller team! :D.


2 Answers

I might be completely wrong, but isn't the problem hGetContents? Surely that should wait till the entire contents sent through your socket have arrived before the next line (print...) starts. hGetContents is designed to give you all the contents sent until the socket is closed. Something like hGetLine could terminate straight away and you can leave the socket open to send more data through later. Your client could then use a hPutStrLn instead of hPutStr.

like image 75
AndrewC Avatar answered Oct 03 '22 21:10

AndrewC


It defaults to line-buffered output, which is why hPutStr (which doesn't provide a line ending) doesn't output anything until you flush the buffer. There are two ways you can solve this:

a) Call hFlush stdout manually any time you want to flush the output.

b) Use hSetBuffering to set the buffering to NoBuffering

All of those functions are found in the System.IO module.

Edit: Never mind, I just saw where you did that in the client. I retract my answer with apologies.

like image 34
Gabriella Gonzalez Avatar answered Oct 03 '22 21:10

Gabriella Gonzalez