immutaball-core-0.1.0.4.1: Immutaball/Share/State.hs
{-# OPTIONS_GHC -fno-warn-tabs #-} -- Support tab indentation better, for a better default of no warning if tabs are used: https://dmitryfrank.com/articles/indent_with_tabs_align_with_spaces .
-- Enable warnings:
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}
-- State.hs.
{-# LANGUAGE Haskell2010 #-}
{-# LANGUAGE Arrows, TemplateHaskell #-}
module Immutaball.Share.State
(
-- * Immutaball wires
Immutaball,
ImmutaballA,
ImmutaballM,
runImmutaballM,
RequestFrameMulti,
RequestFrameSingle,
RequestFrame,
Request(..), AsRequest(..),
ResponseFrameMulti,
ResponseFrameSingle,
ResponseFrame,
Response(..), AsResponse(..),
{-
closeFork,
closeFork',
immutaballIOLinear,
-}
stepImmutaball,
stepImmutaballWire,
liftIBIO,
IBContext,
ContextConfig,
StaticConfig,
-- * Frame management
immutaballMultiToSingle,
immutaballSingleToMulti,
immutaballSingleToMultiWith,
fromImmutaballMulti,
fromImmutaballSingle,
fromImmutaballSingleWith,
immutaballMultiQueueFrames,
-- * Frame management: Simpler variants
immutaballMultiToSingle',
immutaballSingleToMulti',
fromImmutaballMulti',
fromImmutaballSingle',
immutaballMultiQueueFrames'
) where
import Prelude ()
import Immutaball.Prelude
import Control.Arrow
import Data.Functor.Identity
import Control.Lens
import Control.Monad.Trans.MaybeM
import Immutaball.Share.AutoPar
import Immutaball.Share.Config
import Immutaball.Share.Context
import Immutaball.Share.Context.Config
import Immutaball.Share.ImmutaballIO
import Immutaball.Share.Utils
import Immutaball.Share.Wire
-- * Immutaball wires
-- | An immutaball wire.
--
-- Wire is perhaps like Fixed StateT.
--
-- > data Wire m a b = Wire { _stepWire :: a -> m (b, Wire m a b) }
--type Immutaball = Wire Maybe RequestFrame ResponseFrame
type Immutaball = Wire ImmutaballM RequestFrame ResponseFrame
type ImmutaballA b c = Wire ImmutaballM (RequestFrame, b) (ResponseFrame, c)
-- | Immutaball wire monad.
type ImmutaballM = AutoParT (MaybeMT ImmutaballIOF)
runImmutaballM :: ImmutaballM me -> ImmutaballIOF me
runImmutaballM = runMaybeInMT . runAutoParT
type RequestFrameMulti = [Request]
type RequestFrameSingle = Identity Request
type RequestFrame = RequestFrameMulti
data Request =
Clock Double -- ^ timer dt
| Paint Double -- ^ paint t
| Point Int Int Int Int -- ^ mouse movement x y dx dy
| Stick Int Double -- ^ stick axis value
| Angle Double Double -- ^ angle x z
| Click Int Bool -- ^ click button down
| Keybd Int Bool -- ^ keyboard char down
| Buttn Int Bool -- ^ button button down
| Touch Int Int Double Double Double Double Double -- ^ finger-touch device finger x y dx dy pressure
deriving (Eq, Ord, Show)
--makeClassyPrisms ''Request
type ResponseFrameMulti = [Response]
type ResponseFrameSingle = Identity Response
type ResponseFrame = ResponseFrameMulti
data Response =
-- | Tell the controller that we're alive and to keep stepping.
-- If there is no continue response, this thread is done if there is no
-- forking. If there are multiple continue responses in a single step,
-- this tells the controller to fork (not applicable with single reponse
-- wires). (On a single fork, as the controller source indicates, only use
-- the fork construction like 'PureFork', which means this thread will
-- still continue alongside the fork; if you include both a PureFork a
-- ContinueResponse, the end result will be 3 steppers running, not 2
-- steppers.)
ContinueResponse
-- | Tell the controller to stop stepping. We are done. (In a multi
-- frame, this is equivalent to a no-op; removing it from the response list
-- gives equivalent results, for our controller.)
| DoneResponse
-- | Request the controller fork the wire; requires the context enable forking.
| PureFork Immutaball
-- | Request the controller fork the wire; requires the context enable forking.
| ImmutaballIOFork (ImmutaballIOF Immutaball)
-- | AnythingElseToTellTheController NewIBContextIfNeeded SomeOtherData
--makeClassyPrisms ''Response
-- The controller now manages forking and closing more fully.
{-
-- | End a wire.
--
-- This can be combined with 'ImmutaballIOFork' to keep a single wire running.
{- --closeFork :: Immutaball -}
closeFork :: Wire ImmutaballMClosable () ()
--closeFork = wire $ \_ -> hoistMaybe Nothing
closeFork = withM returnA (const . hoistMaybe $ Nothing)
closeFork' :: Wire ImmutaballMClosable () a
--closeFork' = wire $ \_ -> hoistMaybe Nothing
closeFork' = withM returnA (const . hoistMaybe $ Nothing)
immutaballIOLinear :: ImmutaballIOF Immutaball -> Wire ImmutaballMClosable () ResponseFrame
immutaballIOLinear ibIO = wire (\() -> hoistMaybe $ Just ([ImmutaballIOFork ibIO], closeFork'))
-}
-- | Convenience utility to simplify the 'AutoPar' layer.
--
-- It's 'stepWire' without 'AutoPar'.
stepImmutaball :: Immutaball -> RequestFrame -> ImmutaballIOF (ResponseFrame, Immutaball)
stepImmutaball = stepImmutaballWire
-- | Convenience utility to simplify the 'AutoPar' layer.
--
-- It's 'stepWire' without 'AutoPar'.
stepImmutaballWire :: Wire ImmutaballM a b -> a -> ImmutaballIOF (b, Wire ImmutaballM a b)
stepImmutaballWire immutaball request = runImmutaballM $ stepWire immutaball request
liftIBIO :: ImmutaballIOF a -> ImmutaballM a
liftIBIO = AutoParT . MaybeMT . Left
type IBContext = IBContext' Immutaball
type ContextConfig = ContextConfig' IBContext Immutaball
type StaticConfig = StaticConfig' (IBContext -> Maybe Immutaball)
-- * Frame management
-- | Send one request at a time; if there is no response, then treat it as a
-- DoneResponse (just as the controller would close a wire if a multi-response
-- wire returns [] with no continue).
immutaballMultiToSingle :: Wire ImmutaballM (RequestFrameMulti, a) (ResponseFrameMulti, b) -> Wire ImmutaballM (RequestFrameSingle, a) (ResponseFrameSingle, b)
immutaballMultiToSingle w = proc (Identity request, a) -> do
(responses, b) <- w -< ([request], a)
mresponse <- queue -< responses
returnA -< ((Identity $ maybe DoneResponse id mresponse), b)
-- | If the single wire received no request, we give an empty response frame
-- (like a DoneResponse).
immutaballSingleToMulti :: (Monoid b) => Wire ImmutaballM (RequestFrameSingle, a) (ResponseFrameSingle, b) -> Wire ImmutaballM (RequestFrameMulti, a) (ResponseFrameMulti, b)
immutaballSingleToMulti = immutaballSingleToMultiWith mempty
-- | If the single wire received no request, we give an empty response frame
-- (like a DoneResponse).
immutaballSingleToMultiWith :: b -> Wire ImmutaballM (RequestFrameSingle, a) (ResponseFrameSingle, b) -> Wire ImmutaballM (RequestFrameMulti, a) (ResponseFrameMulti, b)
immutaballSingleToMultiWith whenEmpty w = proc (requests, a) -> do
request <- queue -< requests
case request of
Nothing -> do
returnA -< ([], whenEmpty)
Just jrequest -> do
((Identity response), b) <- w -< (Identity jrequest, a)
returnA -< ([response], b)
fromImmutaballMulti :: Wire ImmutaballM (RequestFrameMulti, a) (ResponseFrameMulti, b) -> ImmutaballA a b
fromImmutaballMulti = id
fromImmutaballSingle :: (Monoid b) => Wire ImmutaballM (RequestFrameSingle, a) (ResponseFrameSingle, b) -> ImmutaballA a b
fromImmutaballSingle = immutaballSingleToMulti
fromImmutaballSingleWith :: b -> Wire ImmutaballM (RequestFrameSingle, a) (ResponseFrameSingle, b) -> ImmutaballA a b
fromImmutaballSingleWith = immutaballSingleToMultiWith
-- | Transform a wire that can handle unlimited requests and response,
-- into one that queues up to the context's limit to only process so many
-- requests and so many responses at once.
immutaballMultiQueueFrames :: IBContext -> Wire ImmutaballM (RequestFrameMulti, a) (ResponseFrameMulti, b) -> Wire ImmutaballM (RequestFrameMulti, a) (ResponseFrameMulti, b)
immutaballMultiQueueFrames cxt w = proc (requests, a) -> do
requestChunk <- maybe returnA queueN (cxt^.ibStaticConfig.maxStepFrameSize) -< requests
(responses, b) <- w -< (requestChunk, a)
responseChunk <- maybe returnA queueN (cxt^.ibStaticConfig.maxResponseFrameSize) -< responses
returnA -< (responseChunk, b)
-- * Frame management: Simpler variants
-- | Send one request at a time; if there is no response, then treat it as a
-- DoneResponse (just as the controller would close a wire if a multi-response
-- wire returns [] with no continue).
immutaballMultiToSingle' :: Wire ImmutaballM RequestFrameMulti ResponseFrameMulti -> Wire ImmutaballM RequestFrameSingle ResponseFrameSingle
immutaballMultiToSingle' = withOpenSecondIO immutaballMultiToSingle
-- | If the single wire received no request, we give an empty response frame
-- (like a DoneResponse).
immutaballSingleToMulti' :: Wire ImmutaballM RequestFrameSingle ResponseFrameSingle -> Wire ImmutaballM RequestFrameMulti ResponseFrameMulti
immutaballSingleToMulti' = withOpenSecondIO immutaballSingleToMulti
fromImmutaballMulti' :: Wire ImmutaballM RequestFrameMulti ResponseFrameMulti -> Immutaball
fromImmutaballMulti' = id
fromImmutaballSingle' :: Wire ImmutaballM RequestFrameSingle ResponseFrameSingle -> Immutaball
fromImmutaballSingle' = immutaballSingleToMulti'
-- | Transform a wire that can handle unlimited requests and response,
-- into one that queues up to the context's limit to only process so many
-- requests and so many responses at once.
immutaballMultiQueueFrames' :: IBContext -> Wire ImmutaballM RequestFrameMulti ResponseFrameMulti -> Wire ImmutaballM RequestFrameMulti ResponseFrameMulti
immutaballMultiQueueFrames' cxt = closeSecondI . closeSecondO . immutaballMultiQueueFrames cxt . openSecondO . openSecondI
-- Lenses at end of file to avoid TH errors.
makeClassyPrisms ''Request
makeClassyPrisms ''Response