[server] Add return value to tree modules

Now, tree modules can't just stop, they can Just stop. Sorry... But they can
return values now, with a tiny bit of type class trickery.
This commit is contained in:
Joscha 2020-02-13 23:38:26 +00:00
parent 220b5a3234
commit 2a2b148046
6 changed files with 55 additions and 39 deletions

View file

@ -1,3 +1,4 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
module Forest.Server module Forest.Server
@ -29,8 +30,8 @@ sendUpdatesThread conn nodeChan nodeA = do
{- Main server application that receives and processes client packets -} {- Main server application that receives and processes client packets -}
receivePackets :: TreeModule a => WS.Connection -> a -> IO () receivePackets :: TreeModule a () => WS.Connection -> a () -> IO ()
receivePackets conn treeModule = whileM $ do receivePackets conn treeModule = runUntilJustM $ do
packet <- receivePacket conn packet <- receivePacket conn
case packet of case packet of
ClientEdit path text -> do ClientEdit path text -> do
@ -47,7 +48,7 @@ receivePackets conn treeModule = whileM $ do
act treeModule path act treeModule path
ClientHello _ -> closeWithErrorMessage conn "Invalid packet: Hello can only be sent once" ClientHello _ -> closeWithErrorMessage conn "Invalid packet: Hello can only be sent once"
serverApp :: TreeModule a => Int -> ModuleConstructor a -> WS.ServerApp serverApp :: TreeModule a () => Int -> ModuleConstructor (a ()) -> WS.ServerApp
serverApp pingDelay constructor pendingConnection = do serverApp pingDelay constructor pendingConnection = do
conn <- WS.acceptRequest pendingConnection conn <- WS.acceptRequest pendingConnection
chan <- newChan chan <- newChan

View file

@ -1,3 +1,5 @@
{-# LANGUAGE MultiParamTypeClasses #-}
module Forest.TreeModule module Forest.TreeModule
( TreeModule(..) ( TreeModule(..)
, ModuleConstructor , ModuleConstructor
@ -7,17 +9,17 @@ import qualified Data.Text as T
import Forest.Node import Forest.Node
class TreeModule a where class TreeModule a r where
edit :: a -> Path -> T.Text -> IO Bool edit :: a r -> Path -> T.Text -> IO (Maybe r)
edit _ _ _ = pure True edit _ _ _ = pure Nothing
delete :: a -> Path -> IO Bool delete :: a r -> Path -> IO (Maybe r)
delete _ _ = pure True delete _ _ = pure Nothing
reply :: a -> Path -> T.Text -> IO Bool reply :: a r -> Path -> T.Text -> IO (Maybe r)
reply _ _ _ = pure True reply _ _ _ = pure Nothing
act :: a -> Path -> IO Bool act :: a r -> Path -> IO (Maybe r)
act _ _ = pure True act _ _ = pure Nothing
type ModuleConstructor a = (Node -> IO ()) -> (a -> IO ()) -> IO () type ModuleConstructor a = (Node -> IO ()) -> (a -> IO ()) -> IO ()

View file

@ -1,21 +1,22 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
module Forest.TreeModule.Animate module Forest.TreeModule.Animate
( AnimateModule ( AnimateModule
, animateModule , animateModule
) where ) where
import Control.Concurrent import Control.Concurrent
import Forest.TreeModule
import Forest.Node import Forest.Node
import Forest.TreeModule
import Forest.Util import Forest.Util
data AnimateModule = AnimateModule data AnimateModule r = AnimateModule
instance TreeModule AnimateModule where instance TreeModule AnimateModule () where
animateModule :: Int -> [Node] -> ModuleConstructor AnimateModule animateModule :: Int -> [Node] -> ModuleConstructor (AnimateModule ())
animateModule delay frames sendNode continue = animateModule delay frames sendNode continue =
withThread (animateThread frames) $ continue AnimateModule withThread (animateThread frames) $ continue AnimateModule
where where

View file

@ -1,4 +1,5 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
module Forest.TreeModule.Const module Forest.TreeModule.Const
( ConstModule ( ConstModule
@ -9,11 +10,11 @@ module Forest.TreeModule.Const
import Forest.Node import Forest.Node
import Forest.TreeModule import Forest.TreeModule
data ConstModule = ConstModule data ConstModule r = ConstModule
instance TreeModule ConstModule where instance TreeModule ConstModule () where
constModule :: Node -> ModuleConstructor ConstModule constModule :: Node -> ModuleConstructor (ConstModule ())
constModule node sendNode continue = do constModule node sendNode continue = do
sendNode node sendNode node
continue ConstModule continue ConstModule

View file

@ -1,5 +1,6 @@
{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE KindSignatures #-} {-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
module Forest.TreeModule.Fork module Forest.TreeModule.Fork
@ -16,31 +17,32 @@ import qualified Data.Text as T
import Forest.Node import Forest.Node
import Forest.TreeModule import Forest.TreeModule
data Prong = forall a . TreeModule a => Prong a data Prong = forall r a . TreeModule a r => Prong (a r)
data ProngConstructor = forall a . TreeModule a => ProngConstructor (ModuleConstructor a) data ProngConstructor = forall r a . TreeModule a r =>
ProngConstructor (ModuleConstructor (a r))
newtype ForkModule = ForkModule (Map.Map NodeId Prong) newtype ForkModule r = ForkModule (Map.Map NodeId Prong)
instance TreeModule ForkModule where instance TreeModule ForkModule () where
edit _ (Path []) _ = pure True edit _ (Path []) _ = pure Nothing
edit (ForkModule prongs) (Path (x:xs)) text = case prongs Map.!? x of edit (ForkModule prongs) (Path (x:xs)) text = case prongs Map.!? x of
Just (Prong a) -> edit a (Path xs) text Just (Prong a) -> Just () <$ edit a (Path xs) text
Nothing -> pure True Nothing -> pure Nothing
delete _ (Path []) = pure True delete _ (Path []) = pure Nothing
delete (ForkModule prongs) (Path (x:xs)) = case prongs Map.!? x of delete (ForkModule prongs) (Path (x:xs)) = case prongs Map.!? x of
Just (Prong a) -> delete a (Path xs) Just (Prong a) -> Just () <$ delete a (Path xs)
Nothing -> pure True Nothing -> pure Nothing
reply _ (Path []) _ = pure True reply _ (Path []) _ = pure Nothing
reply (ForkModule prongs) (Path (x:xs)) text = case prongs Map.!? x of reply (ForkModule prongs) (Path (x:xs)) text = case prongs Map.!? x of
Just (Prong a) -> reply a (Path xs) text Just (Prong a) -> Just () <$ reply a (Path xs) text
Nothing -> pure True Nothing -> pure Nothing
act _ (Path []) = pure True act _ (Path []) = pure Nothing
act (ForkModule prongs) (Path (x:xs)) = case prongs Map.!? x of act (ForkModule prongs) (Path (x:xs)) = case prongs Map.!? x of
Just (Prong a) -> act a (Path xs) Just (Prong a) -> Just () <$ act a (Path xs)
Nothing -> pure True Nothing -> pure Nothing
sendNodeFromProng sendNodeFromProng
:: T.Text :: T.Text
@ -68,7 +70,7 @@ constructProngs text nodesVar sendNode =
constructProng nodeId (ProngConstructor constructor) = constructProng nodeId (ProngConstructor constructor) =
Prong <$> cont (constructor $ sendNodeFromProng text nodesVar sendNode nodeId) Prong <$> cont (constructor $ sendNodeFromProng text nodesVar sendNode nodeId)
forkModule :: T.Text -> [ProngConstructor] -> ModuleConstructor ForkModule forkModule :: T.Text -> [ProngConstructor] -> ModuleConstructor (ForkModule ())
forkModule text prongs sendNode continue = do forkModule text prongs sendNode continue = do
nodesVar <- newMVar Map.empty nodesVar <- newMVar Map.empty
let digits = length $ show $ length prongs let digits = length $ show $ length prongs

View file

@ -4,6 +4,7 @@ module Forest.Util
( findPrev ( findPrev
, findNext , findNext
, whileM , whileM
, runUntilJustM
, withThread , withThread
, sendPacket , sendPacket
, closeWithErrorMessage , closeWithErrorMessage
@ -34,6 +35,14 @@ whileM f = do
then whileM f then whileM f
else pure () else pure ()
-- | Run a monadic action until it returns @Just a@ for the first time.
runUntilJustM :: Monad m => m (Maybe a) -> m a
runUntilJustM f = do
result <- f
case result of
Nothing -> runUntilJustM f
Just a -> pure a
sendPacket :: ToJSON a => WS.Connection -> a -> IO () sendPacket :: ToJSON a => WS.Connection -> a -> IO ()
sendPacket conn packet = WS.sendTextData conn $ encode packet sendPacket conn packet = WS.sendTextData conn $ encode packet