simple-sessions-0.1: Control/Concurrent/SimpleSession/Examples/Positional.lhs
\ignore{
> {-# OPTIONS -F -pgmF ixdopp #-}
> {-# LANGUAGE TypeOperators #-}
> module Control.Concurrent.SimpleSession.Examples.Positional where
>
> import Control.Concurrent (threadDelay)
> import Random
>
> import Control.Concurrent.SimpleSession.Positional
}
\subsection{An Example with Multiple Channels}
\label{sec:example:multiple}
As an example, we give an implementation of the Sutherland-Hodgman
\citeyearpar{Sutherland1974Reentrant} reentrant polygon clipping algorithm,
which takes a plane and a series of points representing the
vertices of a polygon,
and produces vertices for the polygon restricted to one side of the plane.
\Citet{Shivers2006Continuations}
present a stream transducer implementation, which we follow. Each
transducer takes one plane to clip by, and two |Rendezvous| objects for
the same protocol. It connects on both, and then receives original points
on one channel and sends clipped points on the other.
We assume that we have types |Plane| and |Point|, a predicate
|above| that indicates whether a given point is on the visible side
of a given plane, and a partial function |intersection| that computes
where the line segment between two points intersects a plane.
GHC infers all the types in this example.
< type SendList a = Rec (Eps :+: (a :!: Var Z))
<
< clipper :: Plane -> Rendezvous (SendList Point)
< -> Rendezvous (SendList Point)
< -> Session x x ()
< clipper plane inrv outrv =
< accept outrv $ \oc ->
< request inrv $ \ic -> ixdo
< let shutdown = ixdo close ic; sel1 oc; close oc
< put pt = dig $ ixdo
< sel2 oc; send oc pt; zero oc
< -- Attempt to get a point; pass it to yes, or
< -- call no if there are no more:
< get no yes = offer ic no $ ixdo
< pt <- recv ic; zero ic; yes pt
< -- If the line crosses the plane, send the intersection point:
< putCross line =
< maybe (ireturn ()) put (line `intersect` plane)
< putIfVisible pt =
< if pt `above` plane then put pt else ireturn ()
< dig (enter oc)
< enter ic
< get shutdown $ \pt0 ->
< let loop pt = ixdo
< putIfVisible pt
< get (putcross (pt, pt0) >>>= \_ -> shutdown)
< (\pt' -> ixdo putcross (pt,pt')
< loop pt')
< in loop pt0
\par
We use |sendlist| to send a list of points to the first
transducer in the pipeline, and we use |recvlist| to accumulate
points produced by the last transducer.
< sendlist :: [a] -> Rendezvous (SendList a)
< -> Session x x ()
< sendlist xs rv = accept rv start where
< start oc = enter oc >>>= \_ -> loop xs where
< loop [] = ixdo sel1 oc; close oc
< loop (x:xs) = ixdo sel2 oc; send oc x
< zero oc; loop xs
<
< recvlist :: Rendezvous (SendList a) -> Session x x [a]
< recvlist rv = request rv start where
< start ic = enter ic >>>= \_ -> loop [] where
< loop acc = offer ic
< (close ic >>>= \_ -> ireturn (reverse acc))
< (recv ic >>>= \x -> zero ic >>>= \_ -> loop (x : acc))
\par
Given a list of planes and a list of points, |clipMany| starts a |clipper|
for each plane in a separate thread. It starts |sendlist| a new thread,
giving it the list of points and connecting it to the first |clipper|.
It then runs |recvlist| in the main thread to gather up the result.
< clipMany :: [Plane] -> [Point] -> IO [Point]
< clipMany planes points = runSession $ ixdo
< rv <- io newRendezvous
< forkSession (sendlist points rv)
< let loop [] rv = recvlist rv
< loop (p:ps) rv = ixdo
< rv' <- io newRendezvous
< forkSession (clipper p rv rv')
< loop ps rv'
< loop planes rv
\ignore{
< bench n m = do
< g1 <- getStdRandom split
< g2 <- getStdRandom split
< let groupPoints (x:y:z:r) = Point x y z : groupPoints r
< groupPlanes (a:b:c:r) = Plane a b c 0 : groupPlanes r
< points = groupPoints [ 10 * (x - 0.5) | x <- randoms g1 ]
< planes = groupPlanes [ 10 * (x - 0.5) | x <- randoms g2 ]
< points <- clipMany (take m planes) (take n points)
< print (length points)
<
< data Point = Point !Double !Double !Double
< data Plane = Plane !Double !Double !Double !Double
<
< instance Show Point where
< showsPrec _ (Point x y z) =
< ('(':) . shows x . (", "++) . shows y . (", "++) . shows z . (')':)
<
< instance Show Plane where
< showsPrec _ (Plane a b c d) =
< shows a . ("x + "++) . shows b . ("y + "++) .
< shows c . ("z + "++) . shows d . (" = 0"++)
<
< above :: Point -> Plane -> Bool
< above (Point x y z) (Plane a b c d)
< = (a * x + b * y + c * z + d) / sqrt (a * a + b * b + c * c) > 0
<
< intersect :: (Point, Point) -> Plane -> Maybe Point
< intersect (p1@(Point x1 y1 z1), p2@(Point x2 y2 z2)) plane@(Plane a b c d)
< = if above p1 plane == above p2 plane
< then Nothing
< else Just (Point x y z) where
< x = x1 + (x2 - x1) * t
< y = y1 + (y2 - y1) * t
< z = z1 + (z2 - z1) * t
< t = (a * x1 + b * y1 + c * z1 + d) /
< (a * (x1 - x2) + b * (y1 - y2) + c * (z1 - z2))
> newPrinter = do
> spec <- newRendezvous
> let printer = ixdo
> clet c = accept spec
> offer c
> (recv c >>>= io . putStrLn >>>= \_ -> close c >>>= \_ -> printer)
> (close c)
> let say s = runSession $
> clet c = request spec in
> ixdo sel1 c; send c s; close c
> let shutdown = runSession $
> clet c = request spec in
> sel2 c >>>= \_ -> close c
> runSession (forkSession printer)
> return (say, shutdown)
>
> logger say c = enter c >>>= \_ -> loop where
> loop =
> offer c
> (ixdo
> msg <- recv c
> io (say ("logger: " ++ msg))
> zero c
> loop)
> (ixdo
> io (say "logger: exiting")
> send c ()
> close c)
>
> echoServer espec lspec = ixdo
> clet lc = request lspec
> enter lc
> let loop = ixdo
> clet ec = accept espec
> offer ec
> (ixdo
> swap
> sel1 lc; send lc "echo server forking"
> zero lc
> dig (forkSession (recv ec >>>= send ec >>>= \_ -> close ec))
> loop)
> (ixdo
> dig $ ixdo
> sel1 lc; send lc "echo server exiting"
> zero lc
> sel2 lc; recv lc
> close lc
> send ec ()
> close ec)
> loop
>
> client delay say espec = ixdo
> clet c = request espec
> s <- io getLine
> case s of
> "q" -> ixdo
> sel2 c
> recv c
> close c
> _ -> ixdo
> forkSession $ ixdo
> sel1 c
> io (threadDelay $ round $ 1000000 * delay)
> send c s
> str <- recv c
> io (say str)
> close c
> client delay say espec
>
> go delay = do
> espec <- newRendezvous
> lspec <- newRendezvous
> (say, shutdown) <- newPrinter
> runSession $
> forkSession (accept lspec $ logger say) >>>= \_ ->
> forkSession (echoServer espec lspec) >>>= \_ ->
> client delay say espec
> shutdown
}