packages feed

reanimate-0.1.0.0: server/Main.hs

{-# LANGUAGE OverloadedStrings #-}
module Main where

import Control.Concurrent
import Control.Exception
import Control.Monad
import Data.Monoid
import Data.Time
import Network.WebSockets
import Control.Monad.Fix
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Map as Map

import Cache
import Reanimate.Misc

main :: IO ()
main = do
  runServerWith "127.0.0.1" 9161 opts $ \pending -> do
  conn <- acceptRequest pending
  thread <- newEmptyMVar
  forkPingThread conn 30
  forever $ do
    msg <- receiveData conn :: IO T.Text
    stopWorker conn thread
    putMVar thread =<< forkIO (generateResponse conn msg >> tryTakeMVar thread >> return ())
  where
    opts = defaultConnectionOptions
      { connectionCompressionOptions = PermessageDeflateCompression defaultPermessageDeflate }

stopWorker conn mvar = do
  mbTid <- tryTakeMVar mvar
  case mbTid of
    Nothing -> return ()
    Just tid -> do
      putStrLn "Interrupt"
      killThread tid
      sendTextData conn $ T.pack $ "Error" ++ "Reset"

generateResponse conn msg = do
  mbCached <- lookupCache msg
  case mbCached of
    Just svgs -> do
      putStrLn "Returning cached svg."
      sendTextDatas conn (T.pack "Success!" : svgs)
      sendTextData conn (T.pack "Done")
    Nothing ->
      withTempFile ".exe" $ \tmpExecutable ->
      withTempFile ".hs" $ \tmpSource ->
      withTempDir $ \tmpDir -> do
        T.writeFile tmpSource $ T.unlines
          ["{-# LANGUAGE Arrows, OverloadedStrings, CPP #-}"
          ,"module Main where"
          ,"import Reanimate.Monad"
          ,"import           Reanimate.Combinators"
          ,"import           Reanimate.LaTeX"
          ,"import           Codec.Picture.Types"
          ,"import           Reanimate.Svg"
          ,"import           Reanimate.Render"
          ,"import           Data.Monoid"
          ,"import           Graphics.Svg as S"
          ,"main = renderSvgs animation " <> T.pack (show tmpDir)
          ,"#line 1 \"animation.hs\""
          ] <> msg
        putStrLn $ "Compiling program:\n" ++ T.unpack msg
        sendTextData conn (T.pack "Compiling")
        ret <- timeIt "compile" $
                runCmd_ "stack" $ ["ghc", "--"] ++ ghcOptions ++ [tmpSource, "-o", tmpExecutable]
        case ret of
          Left err -> do
            sendTextData conn $ T.pack $ "Error" ++ unlines (drop 3 (lines err))
          Right{} -> do
            queue <- newChan
            tid <- forkIO $ forever $ sendTextData conn =<< readChan queue
            sendTextData conn (T.pack "Rendering")
            flip onException (killThread tid) $
              timeIt "render" $ withTimeout queue conn 60 $ do
              getFrame <- runCmdLazy tmpExecutable ["+RTS", "-N", "-M50M", "-RTS"]
              flip fix [] $ \loop acc -> do
                frame <- getFrame
                case frame of
                  Left "" -> do
                    writeChan queue (T.pack "Done")
                    insertCache msg (reverse acc)
                  Left err -> do
                    _ <- getChanContents queue
                    writeChan queue $ T.pack $ "Error" ++ err
                  Right frame -> do
                    writeChan queue frame
                    loop (frame : acc)

ghcOptions :: [String]
ghcOptions = ["-rtsopts", "--make", "-threaded", "-O"]

withTimeout queue conn t action = do
  finished <- newEmptyMVar
  worker <- forkIO (action >> putMVar finished ())
  timer <- forkIO $ do
    threadDelay (10^6 * t)
    putMVar finished ()
    putStrLn "Timeout"
    killThread worker
    _ <- getChanContents queue
    writeChan queue $ T.pack $ "Error" ++ "Timeout"

  takeMVar finished `onException` do
    killThread worker
    killThread timer

timeIt :: String -> IO a -> IO a
timeIt label fn = do
  t1 <- getCurrentTime
  a <- fn
  t2 <- getCurrentTime
  putStrLn $ label ++ ": " ++ show (diffUTCTime t2 t1)
  return a