renderable 0.0.0.2 → 0.1.0.0
raw patch · 2 files changed
+134/−181 lines, 2 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Data.Renderable: Rendering :: (t -> m ()) -> m () -> Rendering m t
- Data.Renderable: [clean] :: Rendering m t -> m ()
- Data.Renderable: [render] :: Rendering m t -> t -> m ()
- Data.Renderable: attachIfNeeded :: (Renderable a, Monad (RenderMonad a), Monoid (RenderTfrm a), Hashable a) => RenderRsrc a -> Cache (RenderMonad a) (RenderTfrm a) -> a -> (RenderMonad a) (Cache (RenderMonad a) (RenderTfrm a))
- Data.Renderable: cache :: (Renderable a, Monad (RenderMonad a), Monoid (RenderTfrm a)) => RenderRsrc a -> Cache (RenderMonad a) (RenderTfrm a) -> a -> (RenderMonad a) (Cache (RenderMonad a) (RenderTfrm a))
- Data.Renderable: class Decomposable a m r t
- Data.Renderable: class Renderable a where {
- Data.Renderable: data Rendering m t
- Data.Renderable: decompose :: Decomposable a m r t => a -> [Element m r t]
- Data.Renderable: detach :: Monad m => Cache m t -> Int -> m (Cache m t)
- Data.Renderable: detachUnused :: (Monad m, Renderable a) => Cache m t -> a -> m (Cache m t)
- Data.Renderable: instance (Data.Renderable.Renderable a, Data.Hashable.Class.Hashable a) => Data.Renderable.Renderable [a]
- Data.Renderable: instance (Data.Renderable.Renderable a, Data.Hashable.Class.Hashable a, GHC.Show.Show a) => Data.Renderable.Renderable (GHC.Base.Maybe a)
- Data.Renderable: instance (t ~ Data.Renderable.RenderTfrm a, GHC.Show.Show t, GHC.Base.Monoid t, Data.Hashable.Class.Hashable a, Data.Renderable.Renderable a) => Data.Renderable.Renderable (t, a)
- Data.Renderable: instance Data.Renderable.Decomposable (Data.Renderable.Element m r t) m r t
- Data.Renderable: instance Data.Renderable.Renderable (Data.Renderable.Element m r t)
- Data.Renderable: instance GHC.Show.Show (Data.Renderable.Element m r t)
- Data.Renderable: nameOf :: Renderable a => a -> String
- Data.Renderable: renderComposite :: (Monad m, Monoid t) => Cache m t -> t -> Composite t -> m ()
- Data.Renderable: renderDataHidden :: (Renderable a, Monad m, Monoid (RenderTfrm a)) => Cache m (RenderTfrm a) -> (RenderTfrm a) -> a -> m ()
- Data.Renderable: type Composite a = [(Int, Maybe a)]
+ Data.Renderable: class Composite a m r t
+ Data.Renderable: class Primitive a where {
+ Data.Renderable: compilePrimitive :: (Primitive a, Monad (PrimM a)) => PrimR a -> a -> (PrimM a) (Rendering (PrimM a) (PrimT a))
+ Data.Renderable: instance (GHC.Base.Monoid t, Data.Renderable.Composite a m r t) => Data.Renderable.Composite (t, a) m r t
+ Data.Renderable: instance Data.Renderable.Composite a m r t => Data.Renderable.Composite (GHC.Base.Maybe a) m r t
+ Data.Renderable: instance Data.Renderable.Composite a m r t => Data.Renderable.Composite [a] m r t
+ Data.Renderable: instance GHC.Base.Monoid t => Data.Renderable.Composite (Data.Renderable.Element m r t) m r t
+ Data.Renderable: type Rendering m t = (m (), t -> m ())
- Data.Renderable: composite :: Renderable a => a -> Composite (RenderTfrm a)
+ Data.Renderable: composite :: Composite a m r t => a -> [(t, Element m r t)]
- Data.Renderable: renderData :: (Monad m, Renderable a, Monoid (RenderTfrm a)) => Cache m (RenderTfrm a) -> a -> m ()
+ Data.Renderable: renderData :: (Composite a m r t, Hashable a, Monad m, Monoid t) => r -> Cache m t -> a -> m (Cache m t)
- Data.Renderable: type family RenderRsrc a :: *;
+ Data.Renderable: type family PrimR a :: *;
Files
- renderable.cabal +2/−2
- src/Data/Renderable.hs +132/−179
renderable.cabal view
@@ -10,7 +10,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.0.0.2+version: 0.1.0.0 -- A short (one-line) description of the package. synopsis: Provides a nice API for rendering data types that change@@ -20,7 +20,7 @@ description: Instances of Renderable conform to a simple API that makes their visual representations composable through hashing and cacheing. Also provided are some convenience functions for writing- Renderable instances, as well as top level rendering functions.+ Renderable instances. Not provided are actual rendering functions. -- URL for the project homepage or repository. homepage: http://zyghost.com
src/Data/Renderable.hs view
@@ -3,209 +3,162 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}-module Data.Renderable where+{-# 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 Data.Maybe-import Data.Monoid-import Data.List (intercalate)-import qualified Data.IntSet as S import qualified Data.IntMap as IM-import GHC.Stack- ----------------------------------------------------------------------------------- Decomposable Instances+-- Primitives ----------------------------------------------------------------------------------- | Any element is decomposable by returning a list consisting of itself.-instance Decomposable (Element m r t) m r t where- decompose e = [e]+-- | 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)) ----------------------------------------------------------------------------------- Renderable Instances+-- Element ----------------------------------------------------------------------------------- | Any Element is renderable by rendering its contained datatype.-instance Renderable (Element m r t) where- type RenderMonad (Element m r t) = m- type RenderRsrc (Element m r t) = r- type RenderTfrm (Element m r t) = t- cache rz rs (Element a) = attachIfNeeded rz rs a- nameOf (Element a) = "Element " ++ nameOf a- composite (Element a) = composite a---- | A tuple is renderable when it is a pairing of a transform and another--- renderable datatype.-instance ( t ~ RenderTfrm a, Show t, Monoid t- , Hashable a, Renderable a) => Renderable (t,a) where- type RenderMonad (t,a) = RenderMonad a- type RenderTfrm (t,a) = RenderTfrm a- type RenderRsrc (t,a) = RenderRsrc a- cache rz rs (_,a) = attachIfNeeded rz rs a- nameOf (t,a) = "(" ++ show t ++ ", " ++ nameOf a ++ ")"- composite (t,a) = map (fmap $ fmap (t <>)) $ composite a+-- | 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 --- | A Maybe is renderable by rendering the datatype contained in the Just--- constructor or by rendering nothing.-instance (Renderable a, Hashable a, Show a) => Renderable (Maybe a) where- type RenderMonad (Maybe a) = RenderMonad a- type RenderTfrm (Maybe a) = RenderTfrm a- type RenderRsrc (Maybe a) = RenderRsrc a- cache rz rs (Just a) = attachIfNeeded rz rs a- cache _ rs _ = return rs- nameOf (Just a) = "Just " ++ nameOf a- nameOf _ = "Nothing"- composite (Just a) = composite a- composite _ = []+instance Hashable (Element m r t) where+ hashWithSalt s (Element a) = s `hashWithSalt` "Element" `hashWithSalt` a --- | A list of renderable instances is renderable by rendering each--- instance.-instance (Renderable a, Hashable a) => Renderable [a] where- type RenderMonad [a] = RenderMonad a- type RenderTfrm [a] = RenderTfrm a- type RenderRsrc [a] = RenderRsrc a- cache = foldM . attachIfNeeded- nameOf as = "[ " ++ (intercalate ", " names) ++ " ]"- where names = map nameOf as- composite = concatMap composite+instance Eq (Element m r t) where+ a == b = hash a == hash b ----------------------------------------------------------------------------------- Rendering and cacheing+-- Compositing ----------------------------------------------------------------------------------- | Render a datatype using renderings stored in the given cache.-renderData :: (Monad m, Renderable a, Monoid (RenderTfrm a))- => Cache m (RenderTfrm a) -> a -> m ()-renderData c = renderComposite c mempty . composite+-- | 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 ()) --- | Render only the hidden layers of a datatype using renderings stored in--- the given cache. This is sometimes useful for debugging.-renderDataHidden :: (Renderable a, Monad m, Monoid (RenderTfrm a))- => Cache m (RenderTfrm a) -> (RenderTfrm a) -> a -> m ()-renderDataHidden c t = renderComposite c t . catMaybes . map f . composite- where f (i, Nothing) = Just (i, Just mempty)- f _ = Nothing+-- | A cache of renderings.+type Cache m t = IntMap (Rendering m t) --- | Render the composite of a datatype using renderings stored in the--- given cache.-renderComposite :: (Monad m, Monoid t) => Cache m t -> t -> Composite t -> m ()-renderComposite rs t = mapM_ (uncurry go)- where go k (Just t') = maybe (err k) (rend t') $ IM.lookup k rs- go _ _ = return ()- rend t' (Rendering f _) = f $ t <> t'- err k = errorWithStackTrace $ unwords [ "Fatal error! Could not find"- , "rendering (from a layer)"- , show k- ]+instance Monad m => Monoid (Rendering m t) where+ (ca, fa) `mappend` (cb, fb) = (ca >> cb, \t -> fa t >> fb t)+ mempty = (return (), const $ return ()) --- | If needed, create a new rendering given some resources, insert it in--- the cache and return the new cache.-attachIfNeeded :: ( Renderable a, Monad (RenderMonad a)- , Monoid (RenderTfrm a), Hashable a)- => RenderRsrc a -> Cache (RenderMonad a) (RenderTfrm a)- -> a -> (RenderMonad a) (Cache (RenderMonad a) (RenderTfrm a))-attachIfNeeded rz cache' a =- maybe (cache rz cache' a) (const $ return cache') $ IM.lookup (hash a) cache'+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) --- | Detach any renderings that are not needed to render the--- given data.-detachUnused :: (Monad m, Renderable a) => Cache m t -> a -> m (Cache m t)-detachUnused c a =- -- Get the hashes listed in the composite (these are used)- let hashes = S.fromList $ map fst $ composite a- -- Get the hashes currently in the cache- keys = IM.keysSet c- -- Diff them- diff = S.difference keys hashes- -- Detach them- in foldM detach c $ S.toList diff+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 --- | Remove a rendering from a cache and clean up the resources allocated--- for that rendering.-detach :: Monad m => Cache m t -> Int -> m (Cache m t)-detach c k = do- case IM.lookup k c of- Nothing -> let s = "Could not find rendering for " ++ show k- in errorWithStackTrace s- Just rendering -> clean rendering- return $ IM.delete k c------------------------------------------------------------------------------------ Decomposition------------------------------------------------------------------------------------ | An instance of Decomposable can be broken down into a number of elements.-class Decomposable a m r t where- decompose :: a -> [Element m r t]------------------------------------------------------------------------------------ Element----------------------------------------------------------------------------------instance Hashable (Element m r t) where- hashWithSalt s (Element a) = s `hashWithSalt` "Element" `hashWithSalt` a+getElementRenderer :: r -> Cache m t -> Element m r t -> m (Cache m t)+getElementRenderer rez cache (Element a) = getRenderer rez cache a -instance Eq (Element m r t) where- a == b = hash a == hash b+clean :: Rendering m t -> m ()+clean = fst -instance Show (Element m r t) where- show (Element a) = "Element{ " ++ show a ++ " }"+render :: Rendering m t -> t -> m ()+render = snd --- | Element is a generic existential type that can be used to enclose--- instances of Renderable in order to contain them all in a heterogeneous list.--- 'm', 'r' and 't' must be shared with all Renderable instances stored in--- a heterogeneous list of Elements.-data Element m r t where- Element :: ( Monad m, Show a, Hashable a, Renderable a- , m ~ RenderMonad a- , r ~ RenderRsrc a- , t ~ RenderTfrm a)- => a -> Element m r t+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 ----------------------------------------------------------------------------------- Renderable+-- Instances ---------------------------------------------------------------------------------class Renderable a where- -- | The monad needed to render the datatype. In most cases this is- -- probably IO.- type RenderMonad a :: * -> *- -- | The datatype that is used to transform renderings.- type RenderTfrm a :: *- -- | The datatype that holds cached resources that will be used to- -- composite and render the datatype.- type RenderRsrc a :: *- -- | The name of a renderable datatype. This is mostly for debugging.- nameOf :: a -> String- -- | Store the rendering of a datatype in a cache keyed by the hash of that- -- datatype. Returns the new cache.- cache :: (Monad (RenderMonad a), Monoid (RenderTfrm a))- => RenderRsrc a -> Cache (RenderMonad a) (RenderTfrm a) -> a- -> (RenderMonad a) (Cache (RenderMonad a) (RenderTfrm a))- -- | The entire composite list of renderings for a given datatype.- composite :: a -> Composite (RenderTfrm a)---- | A cache of renderings.-type Cache m t = IntMap (Rendering m t)+-- | 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)] -instance Monad m => Monoid (Rendering m t) where- (Rendering a b) `mappend` (Rendering c d) =- Rendering (\t -> a t >> c t) (b >> d)- mempty = Rendering (const $ return ()) (return ())+-- | 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 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.-data Rendering m t = Rendering { render :: t -> m ()- , clean :: m ()- }+-- | 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 composite is a representation of the entire rendered datatype. It is--- a flattened list of all the renderings (denoted by hash), along with--- that rendering\'s local transformation. If a rendering is explicitly run--- by another rendering (as in a Renderable class definition) then the--- transformation for that rendering should be Nothing, which will keep--- 'renderComposite' from running that rendering in addition to the--- rendering its included in. For example:--- @--- [(0, Just $ Transform (10,10) (0.5,0.5) 0)--- ,(1, Nothing)--- ]--- @--- The above is a composite of two renderings, the first will be rendered--- by 'renderComposite' using the given transform while the second is--- effectively hidden but present. Being present in the composite will keep--- 'detachUnused' from detaching and cleaning the rendering.-type Composite a = [(Int, Maybe a)]+-- | 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