packages feed

renderable 0.1.0.0 → 0.2.0.0

raw patch · 2 files changed

+149/−129 lines, 2 filesdep +transformers

Dependencies added: transformers

Files

renderable.cabal view
@@ -10,20 +10,24 @@ -- PVP summary:      +-+------- breaking API changes --                   | | +----- non-breaking API additions --                   | | | +--- code changes with no API change-version:             0.1.0.0+version:             0.2.0.0  -- A short (one-line) description of the package.-synopsis:            Provides a nice API for rendering data types that change-                     over time.+synopsis:            An API for managing renderable resources.  -- A longer description of the package.-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. Not provided are actual rendering functions.+description: The `renderable` package provides a method for managing resources+    of a rendering system. Resources are allocated according to a strategy and+    released automatically when your renderable data changes. These changes are+    detected during each draw call based on the hash of your renderable+    datatype. +    This package is meant to be pulled in as a portion of your rendering system.+    It aims to ease the task of managing allocation of resources over time as+    the value of your renderable datatype changes.+ -- URL for the project homepage or repository.-homepage:            http://zyghost.com+homepage:            https://github.com/schell/renderable  -- The license under which the package is released. license:             MIT@@ -36,7 +40,7 @@  -- An email address to which users can send suggestions, bug reports, and -- patches.-maintainer:          efsubenovex@gmail.com+maintainer:          schell.scivally@synapsegroup.com  -- A copyright notice. -- copyright:@@ -65,12 +69,13 @@   -- other-modules:    -- LANGUAGE extensions used by modules in this package.-  other-extensions:    TypeFamilies, GADTs, FlexibleContexts+  other-extensions:    -- Other library packages from which modules are imported.   build-depends:       base >=4.8 && <4.9,                        containers >= 0.5 && < 0.6,-                       hashable >= 1.2 && < 1.3+                       hashable >= 1.2 && < 1.3,+                       transformers >= 0.4 && < 0.5    -- Directories containing source files.   hs-source-dirs:      src
src/Data/Renderable.hs view
@@ -1,164 +1,179 @@-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# OPTIONS_GHC -fno-warn-orphans #-} module Data.Renderable (-    Primitive(..),-    Element(..),-    Composite(..),+    RenderStrategy(..),+    Renderer,     Rendering,+    CleanOp,     Cache,-    renderData+    CacheStats(..),+    renderPrims,+    renderPrimsDebug,+    renderPrimsWithStats,+    emptyRenderer,+    appendRenderer ) where  import Prelude hiding (lookup)-import Control.Arrow (first) import Control.Monad+import Control.Monad.IO.Class 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+-- A strategy for rendering ----------------------------------------------------------------------------------- | 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+-- | A 'RenderStrategy' is a method for creating a renderer that can render+-- your primitives. Examples of primitives are are points, lines, triangles and+-- other shapes. A 'RenderStrategy' is parameterized by four types -+--+-- @m@ - the monad in which rendering calls will take place.+--+-- @t@ - type of the graphics transformation that can be applied to the+--       renderer+--+-- @r@ - type that holds static resources such as windows, shaders, etc.+--+-- @a@ - type of the primitive that can be renderered.+data RenderStrategy m t r a = RenderStrategy+    { canAllocPrimitive :: r -> a -> Bool+      -- ^ Determines whether a renderer can be allocated for the primitive.+      -- A result of 'False' will defer compilation until a later time (the next+      -- frame). -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)]+    , compilePrimitive :: r -> a -> m (Renderer m t)+      -- ^ Allocates resources for rendering the primitive and return+      -- a monadic call that renders the primitive using a transform.+      -- Tuples that with a call to clean up the allocated resources.+    } -------------------------------------------------------------------------------- -- 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 Rendering is an effectful computation for displaying something given a+-- transform.+type Rendering m t = t -> m () --- | A cache of renderings.-type Cache m t = IntMap (Rendering m t)+-- | A CleanOp is an effectfull computaton that cleans up any resources+-- allocated during the creation of an associated Rendering.+type CleanOp m = m () -instance Monad m => Monoid (Rendering m t) where-    (ca, fa) `mappend` (cb, fb) = (ca >> cb, \t -> fa t >> fb t)-    mempty = (return (), const $ return ())+-- | A Renderer is the pairing of a Rendering and a Cleanup.+type Renderer m t = (CleanOp m, Rendering m t) -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))+-- | Create a renderer that renders nothing and releases no resources.+emptyRenderer :: Monad m => Renderer m t+emptyRenderer = (return (), const $ return ())++-- | Appends two renderers into one.+appendRenderer :: Monad m => Renderer m t -> Renderer m t -> Renderer m t+appendRenderer (c1,r1) (c2,r2) = (c1 >> c2, \t -> r1 t >> r2 t)++-- | A cache of renderers.+type Cache m t = IntMap (Renderer m t)++findRenderer :: (Monad m, Hashable a)+             => Cache m t -> (Cache m t, IntMap a) -> a -> (Cache m t, IntMap a) 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+getRenderer :: (Hashable a, Monad m)+            => RenderStrategy m t r a -> r -> Cache m t -> a -> m (Cache m t)+getRenderer s rez cache a =+    if canAllocPrimitive s rez a+    then do r <- compilePrimitive s rez a+            return $ IM.insert (hash a) r cache+    else return cache -clean :: Rendering m t -> m ()+clean :: Renderer m t -> m () clean = fst -render :: Rendering m t -> t -> m ()+render :: Renderer m t -> t -> m () render = snd -renderElement :: Monad m => Cache m t -> t -> Element m r t -> m ()-renderElement cache t (Element a) = do+renderElement :: (Hashable a, Monad m) => Cache m t -> t -> a -> m ()+renderElement cache t 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+-- | A sum of lists of rendering hashes between two cache states.+-- Used for debugging resource management.+data CacheStats a = CacheStats { cachedPrev    :: [Int]+                               -- ^ All the keys of the previous cache state.+                               , cachedFound   :: [Int]+                               -- ^ The keys needed for the next state that+                               -- were found in the previous cache (no need+                               -- to allocate).+                               , cachedMissing :: [Int]+                               -- ^ The keys needed for the next state that+                               -- were not found in the previous cache (these+                               -- will need allocating).+                               , cachedStale   :: [Int]+                               -- ^ The keys found in the previous cache that+                               -- are not needed for the next state (these+                               -- can be deallocated).+                               , cachedNext    :: [Int]+                               -- ^ All the keys of the next cache state.+                               }++-- | Map a 'CacheStats' into a nice readable string.+showCacheStats :: CacheStats a -> String+showCacheStats (CacheStats cache found missing stale next) = unlines+    [ "Prev:    " ++ show cache+    , "Found:   " ++ show found+    , "Missing: " ++ show missing+    , "Stale:   " ++ show stale+    , "Next:    " ++ show next+    ]++-- | Render a list of primitives using renderings stored in the given cache,+-- return a new cache that can be used to render the next list of+-- primitives, along with some info about the comparison of the given and+-- returned cache.+renderPrimsWithStats :: (Monad m, Monoid t, Hashable a)+                     => RenderStrategy m t r a -> r -> Cache m t -> [(t, a)]+                     -> m (Cache m t, CacheStats a)+renderPrimsWithStats s rez cache prims = do+    let (found, missing) = foldl (findRenderer cache)+                                 (mempty, mempty)+                                 (map snd prims)         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+    new <- foldM (getRenderer s 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)]+        stats = CacheStats { cachedPrev = IM.keys cache+                           , cachedFound = IM.keys found+                           , cachedMissing = IM.keys missing+                           , cachedStale = IM.keys stale+                           , cachedNext = IM.keys next+                           } --- | 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+    -- Render the composite+    mapM_ (uncurry $ renderElement next) prims+    return (next,stats) --- | 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 _ = []+-- | Render a list of primitives using renderings stored in the given cache,+-- return a new cache that can be used to render the next list of+-- primitives. Optionally print some debug info.+renderPrimsDebug :: (MonadIO m, Monoid t, Hashable a)+                 => Bool -> RenderStrategy m t r a -> r -> Cache m t -> [(t, a)]+                 -> m (Cache m t)+renderPrimsDebug debug s rez cache prims = do+    (next, stats) <- renderPrimsWithStats s rez cache prims+    when debug $ liftIO $ putStrLn $ showCacheStats stats+    return next --- | 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+-- | Render a list of primitives using renderings stored in the given cache,+-- return a new cache that can be used to render the next list of+-- primitives.+renderPrims :: (Monad m, Monoid t, Hashable a)+            => RenderStrategy m t r a -> r -> Cache m t -> [(t, a)]+            -> m (Cache m t)+renderPrims s rez cache prims = fst <$> renderPrimsWithStats s rez cache prims