{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Data.Renderable ( Primitive(..), Element(..), Composite(..), Rendering, Cache, renderData ) where import Prelude hiding (lookup) import Control.Arrow (first) import Control.Monad import Data.Hashable import Data.IntMap (IntMap) import qualified Data.IntMap as IM -------------------------------------------------------------------------------- -- Primitives -------------------------------------------------------------------------------- -- | A 'Primitive' is the smallest thing can can be rendered in your graphics -- system. Some examples are points, lines, triangles and other shapes. class Primitive a where -- | The monad in which rendering calls will take place. type PrimM a :: * -> * -- | The type of the graphics transformation. type PrimT a :: * -- | The datatype that holds cached resources such as references to -- windows, shaders, etc. type PrimR a :: * -- | Allocate resources for rendering the primitive and return -- a monadic call that renders the primitive using a transform. Tuple -- that with a call to clean up the allocated resources. compilePrimitive :: Monad (PrimM a) => PrimR a -> a -> (PrimM a) (Rendering (PrimM a) (PrimT a)) -------------------------------------------------------------------------------- -- Element -------------------------------------------------------------------------------- -- | Element is an existential type that can be used to enclose -- instances of Primitive in order to contain them all in a heterogeneous list. -- 'm', 'r' and 't' must be shared with all Primitive instances stored in -- the heterogeneous list of Elements. data Element m r t where Element :: ( Monad m, Hashable a, Primitive a , m ~ PrimM a , r ~ PrimR a , t ~ PrimT a) => a -> Element m r t instance Hashable (Element m r t) where hashWithSalt s (Element a) = s `hashWithSalt` "Element" `hashWithSalt` a instance Eq (Element m r t) where a == b = hash a == hash b -------------------------------------------------------------------------------- -- Compositing -------------------------------------------------------------------------------- -- | A 'Composite' is a type that can be broken down into a list of -- transformed primitives. class Composite a m r t where -- | Break down a 'Composite' into a heterogeneous list of transformed -- primitives. composite :: a -> [(t, Element m r t)] -------------------------------------------------------------------------------- -- Rendering -------------------------------------------------------------------------------- -- | A rendering is a type that contains some effectful computation for -- displaying something given a transform. It also contains an effectful -- computation for cleaning up any resources allocated during its creation. type Rendering m t = (m (), t -> m ()) -- | A cache of renderings. type Cache m t = IntMap (Rendering m t) instance Monad m => Monoid (Rendering m t) where (ca, fa) `mappend` (cb, fb) = (ca >> cb, \t -> fa t >> fb t) mempty = (return (), const $ return ()) findRenderer :: Monad m => Cache m t -> (Cache m t, IntMap (Element m r t)) -> Element m r t -> (Cache m t, IntMap (Element m r t)) findRenderer cache (found, missing) a = let k = hash a in case IM.lookup k cache of Nothing -> (found, IM.insert k a missing) Just r -> (IM.insert k r found, missing) getRenderer :: (Primitive a, Hashable a, Monad (PrimM a)) => PrimR a -> Cache (PrimM a) (PrimT a) -> a -> (PrimM a) (Cache (PrimM a) (PrimT a)) getRenderer rez cache a = do r <- compilePrimitive rez a return $ IM.insert (hash a) r cache getElementRenderer :: r -> Cache m t -> Element m r t -> m (Cache m t) getElementRenderer rez cache (Element a) = getRenderer rez cache a clean :: Rendering m t -> m () clean = fst render :: Rendering m t -> t -> m () render = snd renderElement :: Monad m => Cache m t -> t -> Element m r t -> m () renderElement cache t (Element a) = do let k = hash a case IM.lookup k cache of Nothing -> return () Just r -> render r t -- | Render a datatype using renderings stored in the given cache, return a -- new cache that can be used to render the next datatype. renderData :: (Composite a m r t, Hashable a, Monad m, Monoid t) => r -> Cache m t -> a -> m (Cache m t) renderData rez cache a = do -- comp is a heterogeneous list of all the primitives needed to render -- this datatype 'a'. let comp = composite a (found, missing) = foldl (findRenderer cache) (mempty, mempty) $ map snd comp stale = cache `IM.difference` found -- Clean the stale renderers sequence_ $ fmap clean stale -- Get the missing renderers new <- foldM (getElementRenderer rez) mempty $ IM.elems missing let next = IM.union found new -- Render the composite mapM_ (uncurry $ renderElement next) comp return next -------------------------------------------------------------------------------- -- Instances -------------------------------------------------------------------------------- -- | Any Element is a composite of itself if its transform type is a monoid. instance Monoid t => Composite (Element m r t) m r t where composite e = [(mempty, e)] -- | A tuple is a composite if its right type is a composite and the -- left type is the transform and the transform is a Monoid. In this case the -- result is the right type transformed by the left type. instance (Monoid t, Composite a m r t) => Composite (t,a) m r t where composite (t, a) = map (first (mappend t)) $ composite a -- | A Maybe is a composite if its contained type is composite. The result -- is is the composite of its contained type or an empty list. instance Composite a m r t => Composite (Maybe a) m r t where composite (Just a) = composite a composite _ = [] -- | A list is a composite by compositing each element and concatenating -- the result. instance Composite a m r t => Composite [a] m r t where composite = concatMap composite