packages feed

ecstasy 0.2.0.1 → 0.2.1.0

raw patch · 5 files changed

+198/−28 lines, 5 filesdep +kan-extensionsPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: kan-extensions

API changes (from Hackage documentation)

- Data.Ecstasy.Internal.Deriving: instance GHC.Base.Applicative m => Data.Ecstasy.Internal.Deriving.GGetEntity m (GHC.Generics.K1 i (Data.Ecstasy.Types.VTable m a)) (GHC.Generics.K1 i' (GHC.Base.Maybe a))
- Data.Ecstasy.Internal.Deriving: instance GHC.Base.Applicative m => Data.Ecstasy.Internal.Deriving.GSetEntity m (GHC.Generics.K1 i (Data.Ecstasy.Types.Update a)) (GHC.Generics.K1 i' (Data.Ecstasy.Types.VTable m a))
+ Data.Ecstasy: surgery :: (Monad (t m), Monad m, StorageSurgeon t m world) => (forall x. t m x -> m (x, b)) -> SystemT world (t m) a -> SystemT world m (b, a)
+ Data.Ecstasy.Internal: class StorageSurgeon t m world
+ Data.Ecstasy.Internal: graftStorage :: (StorageSurgeon t m world, Generic (world ( 'WorldOf m)), Generic (world ( 'WorldOf (t m))), GGraft (Rep (world ( 'WorldOf m))) (Rep (world ( 'WorldOf (t m))))) => world ( 'WorldOf m) -> world ( 'WorldOf (t m)) -> world ( 'WorldOf m)
+ Data.Ecstasy.Internal: hoistStorage :: (StorageSurgeon t m world, Generic (world ( 'WorldOf m)), Generic (world ( 'WorldOf (t m))), GHoistWorld t m (Rep (world ( 'WorldOf m))) (Rep (world ( 'WorldOf (t m)))), MonadTrans t) => world ( 'WorldOf m) -> world ( 'WorldOf (t m))
+ Data.Ecstasy.Internal: instance (GHC.Generics.Generic (world ('Data.Ecstasy.Types.WorldOf m)), GHC.Generics.Generic (world ('Data.Ecstasy.Types.WorldOf (t m))), Data.Ecstasy.Internal.Deriving.GHoistWorld t m (GHC.Generics.Rep (world ('Data.Ecstasy.Types.WorldOf m))) (GHC.Generics.Rep (world ('Data.Ecstasy.Types.WorldOf (t m)))), Data.Ecstasy.Internal.Deriving.GGraft (GHC.Generics.Rep (world ('Data.Ecstasy.Types.WorldOf m))) (GHC.Generics.Rep (world ('Data.Ecstasy.Types.WorldOf (t m)))), Control.Monad.Trans.Class.MonadTrans t) => Data.Ecstasy.Internal.StorageSurgeon t m world
+ Data.Ecstasy.Internal: surgery :: (Monad (t m), Monad m, StorageSurgeon t m world) => (forall x. t m x -> m (x, b)) -> SystemT world (t m) a -> SystemT world m (b, a)
+ Data.Ecstasy.Internal.Deriving: class GGraft a b
+ Data.Ecstasy.Internal.Deriving: class GHoistWorld (t :: (* -> *) -> * -> *) (m :: * -> *) a b
+ Data.Ecstasy.Internal.Deriving: gGraft :: GGraft a b => a x -> b x -> a x
+ Data.Ecstasy.Internal.Deriving: gHoistWorld :: GHoistWorld t m a b => a x -> b x
+ Data.Ecstasy.Internal.Deriving: instance (Control.Monad.Trans.Class.MonadTrans t, GHC.Base.Functor (t m), GHC.Base.Monad m) => Data.Ecstasy.Internal.Deriving.GHoistWorld t m (GHC.Generics.K1 i (Data.Ecstasy.Types.VTable m a)) (GHC.Generics.K1 i' (Data.Ecstasy.Types.VTable (t m) a))
+ Data.Ecstasy.Internal.Deriving: instance (Data.Ecstasy.Internal.Deriving.GGraft a c, Data.Ecstasy.Internal.Deriving.GGraft b d) => Data.Ecstasy.Internal.Deriving.GGraft (a GHC.Generics.:*: b) (c GHC.Generics.:*: d)
+ Data.Ecstasy.Internal.Deriving: instance (GHC.Base.Applicative (t m), Data.Ecstasy.Internal.Deriving.GHoistWorld t m a c, Data.Ecstasy.Internal.Deriving.GHoistWorld t m b d) => Data.Ecstasy.Internal.Deriving.GHoistWorld t m (a GHC.Generics.:*: b) (c GHC.Generics.:*: d)
+ Data.Ecstasy.Internal.Deriving: instance (GHC.Base.Functor (t m), Data.Ecstasy.Internal.Deriving.GHoistWorld t m f f') => Data.Ecstasy.Internal.Deriving.GHoistWorld t m (GHC.Generics.M1 i c f) (GHC.Generics.M1 i' c' f')
+ Data.Ecstasy.Internal.Deriving: instance Data.Ecstasy.Internal.Deriving.GGraft (GHC.Generics.K1 i (Data.Ecstasy.Types.VTable m a)) (GHC.Generics.K1 i' (Data.Ecstasy.Types.VTable (t m) a))
+ Data.Ecstasy.Internal.Deriving: instance Data.Ecstasy.Internal.Deriving.GGraft (GHC.Generics.K1 i a) (GHC.Generics.K1 i' a)
+ Data.Ecstasy.Internal.Deriving: instance Data.Ecstasy.Internal.Deriving.GGraft f f' => Data.Ecstasy.Internal.Deriving.GGraft (GHC.Generics.M1 i c f) (GHC.Generics.M1 i' c' f')
+ Data.Ecstasy.Internal.Deriving: instance Data.Ecstasy.Internal.Deriving.GHoistWorld t m (GHC.Generics.K1 i a) (GHC.Generics.K1 i' a)
+ Data.Ecstasy.Internal.Deriving: instance GHC.Base.Monad m => Data.Ecstasy.Internal.Deriving.GGetEntity m (GHC.Generics.K1 i (Data.Ecstasy.Types.VTable m a)) (GHC.Generics.K1 i' (GHC.Base.Maybe a))
+ Data.Ecstasy.Internal.Deriving: instance GHC.Base.Monad m => Data.Ecstasy.Internal.Deriving.GSetEntity m (GHC.Generics.K1 i (Data.Ecstasy.Types.Update a)) (GHC.Generics.K1 i' (Data.Ecstasy.Types.VTable m a))
- Data.Ecstasy.Internal.Deriving: gGetEntity :: GGetEntity m a b => a x -> Int -> m (b x)
+ Data.Ecstasy.Internal.Deriving: gGetEntity :: GGetEntity m a b => a x -> Int -> Codensity m (b x)
- Data.Ecstasy.Internal.Deriving: gSetEntity :: GSetEntity m a b => a x -> Int -> b x -> m (b x)
+ Data.Ecstasy.Internal.Deriving: gSetEntity :: GSetEntity m a b => a x -> Int -> b x -> Codensity m (b x)

Files

ChangeLog.md view
@@ -1,5 +1,11 @@ # Revision history for ecstasy +## 0.2.1.0  -- 2018-05-15++* Added the 'surgery' function to introduce temporary effects.+* Significant performance improvements due to constructing monadic generic+    functions via 'Codensity'.+ ## 0.2.0.1  -- 2018-05-10  * Also export 'StorageType'.
ecstasy.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                ecstasy-version:             0.2.0.1+version:             0.2.1.0 synopsis:   A GHC.Generics based entity component system. @@ -39,6 +39,6 @@                  , Data.Ecstasy.Internal                  , Data.Ecstasy.Internal.Deriving   -- other-extensions:-  build-depends:       base >=4.9 && <5, containers, mtl, transformers+  build-depends:       base >=4.9 && <5, containers, mtl, transformers, kan-extensions   hs-source-dirs:      src   default-language:    Haskell2010
src/Data/Ecstasy.hs view
@@ -89,6 +89,9 @@   , Update (..)   , maybeToUpdate +  -- * Introducing effects+  , surgery+   -- * Miscellany   , Ent ()   , VTable (..)@@ -153,7 +156,7 @@ --              } --          } --    'runSystemT' storage $ do---      void $ 'createEntity' 'unchanged+--      void $ 'createEntity' 'newEntity' --        { stdout = Just "hello world" --        } -- @
src/Data/Ecstasy/Internal.hs view
@@ -6,6 +6,7 @@ {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MonoLocalBinds         #-} {-# LANGUAGE MultiParamTypeClasses  #-}+{-# LANGUAGE RankNTypes             #-} {-# LANGUAGE ScopedTypeVariables    #-} {-# LANGUAGE TupleSections          #-} {-# LANGUAGE TypeApplications       #-}@@ -16,10 +17,11 @@  import           Control.Arrow (first, second) import           Control.Monad (mzero, void)-import           Control.Monad.Trans.Class (lift)+import           Control.Monad.Codensity (lowerCodensity)+import           Control.Monad.Trans.Class (MonadTrans (..)) import           Control.Monad.Trans.Maybe (runMaybeT) import           Control.Monad.Trans.Reader (runReaderT, asks)-import           Control.Monad.Trans.State.Strict (modify, get, gets, evalStateT)+import           Control.Monad.Trans.State.Strict (StateT (..), modify, get, gets, evalStateT) import qualified Control.Monad.Trans.State.Strict as S import           Data.Ecstasy.Internal.Deriving import qualified Data.Ecstasy.Types as T@@ -55,7 +57,10 @@       -> SystemT world m (world 'FieldOf)   getEntity e = do     w <- SystemT $ gets snd-    lift . fmap to . gGetEntity @m (from w) $ T.unEnt e+    lift . lowerCodensity+         . fmap to+         . gGetEntity @m (from w)+         $ T.unEnt e   {-# INLINE getEntity #-}    ----------------------------------------------------------------------------@@ -77,7 +82,10 @@       -> SystemT world m ()   setEntity e s = do     w <- SystemT $ gets snd-    x <- lift . fmap to . gSetEntity (from s) (T.unEnt e) $ from w+    x <- lift . lowerCodensity+              . fmap to+              . gSetEntity (from s) (T.unEnt e)+              $ from w     SystemT . modify . second $ const x   {-# INLINE setEntity #-} @@ -90,6 +98,7 @@          )       => world ('WorldOf m)   defStorage = def @'True+  {-# INLINE defStorage #-}   class HasWorld' world where@@ -172,6 +181,84 @@                       (Rep (world 'FieldOf))          , Monad m          ) => HasWorld world m+++------------------------------------------------------------------------------+-- | Utilities for defining 'surgery'.+class StorageSurgeon t m world where+  ----------------------------------------------------------------------------+  -- | Hoist storage through a monad transformer.+  hoistStorage+    :: world ('WorldOf m)+    -> world ('WorldOf (t m))+  default hoistStorage+    :: ( Generic (world ('WorldOf m))+       , Generic (world ('WorldOf (t m)))+       , GHoistWorld t m+                     (Rep (world ('WorldOf m)))+                     (Rep (world ('WorldOf (t m))))+       , MonadTrans t+       )+    => world ('WorldOf m)+    -> world ('WorldOf (t m))+  hoistStorage = to . gHoistWorld @t @m . from+  {-# INLINE hoistStorage #-}++  ----------------------------------------------------------------------------+  -- | Grafts two worlds together, using data from the second argument and+  -- vtables from the first.+  graftStorage+    :: world ('WorldOf m)+    -> world ('WorldOf (t m))+    -> world ('WorldOf m)+  default graftStorage+    :: ( Generic (world ('WorldOf m))+       , Generic (world ('WorldOf (t m)))+       , GGraft (Rep (world ('WorldOf m)))+                (Rep (world ('WorldOf (t m))))+       )+    => world ('WorldOf m)+    -> world ('WorldOf (t m))+    -> world ('WorldOf m)+  graftStorage a b = to $ gGraft (from a) (from b)+  {-# INLINE graftStorage #-}+++instance ( Generic (world ('WorldOf m))+         , Generic (world ('WorldOf (t m)))+         , GHoistWorld t m (Rep (world ('WorldOf m)))+                           (Rep (world ('WorldOf (t m))))+         , GGraft (Rep (world ('WorldOf m)))+                  (Rep (world ('WorldOf (t m))))+         , MonadTrans t+         ) => StorageSurgeon t m world+++------------------------------------------------------------------------------+-- | Run a monad transformer /underneath/ a 'SystemT'.+--+-- Due to the recursive interactions between 'SystemT' and 'QueryT', we're+-- often unable to put a temporary monad transformer on the top of the stack.+-- As a result, often 'surgery' is our ony means of introducting ephemeral+-- effects.+--+-- @+-- draw :: 'SystemT' World IO [Graphics]+-- draw = fmap fst . 'surgery' runWriterT $+--   for_ thingsToRender $ \\thingy ->+--     tell [thingy]+-- @+surgery+    :: ( Monad (t m)+       , Monad m+       , StorageSurgeon t m world+       )+    => (forall x. t m x -> m (x, b))+    -> SystemT world (t m) a+    -> SystemT world m (b, a)+surgery f m = SystemT $ StateT $ \(i, s) -> do+  (((i', s'), a), b) <- f $ yieldSystemT (i, hoistStorage s) m+  pure ((b, a), (i', graftStorage s s'))   ------------------------------------------------------------------------------
src/Data/Ecstasy/Internal/Deriving.hs view
@@ -12,6 +12,8 @@  module Data.Ecstasy.Internal.Deriving where +import           Control.Monad.Codensity+import           Control.Monad.Trans.Class (MonadTrans (..)) import           Data.Ecstasy.Types (Update (..), VTable (..), Ent (..)) import           Data.IntMap (IntMap) import qualified Data.IntMap as I@@ -20,6 +22,57 @@ import           GHC.TypeLits  +------------------------------------------------------------------------------+-- | Utility class for implementing 'Data.Ecstasy.Internal.hoistStorage'.+class GHoistWorld (t :: (* -> *) -> * -> *) (m :: * -> *) a b where+  gHoistWorld :: a x -> b x++instance {-# OVERLAPPING #-} (MonadTrans t, Functor (t m), Monad m)+    => GHoistWorld t m (K1 i (VTable m a)) (K1 i' (VTable (t m) a)) where+  gHoistWorld (K1 (VTable g s)) = K1 $ VTable (fmap lift g) (fmap (fmap lift) s)+  {-# INLINE gHoistWorld #-}++instance {-# OVERLAPPABLE #-} GHoistWorld t m (K1 i a) (K1 i' a) where+  gHoistWorld (K1 a) = K1 a+  {-# INLINE gHoistWorld #-}++instance (Functor (t m), GHoistWorld t m f f')+    => GHoistWorld t m (M1 i c f) (M1 i' c' f') where+  gHoistWorld (M1 a) = M1 $ gHoistWorld @t @m a+  {-# INLINE gHoistWorld #-}++instance (Applicative (t m), GHoistWorld t m a c, GHoistWorld t m b d)+    => GHoistWorld t m (a :*: b) (c :*: d) where+  gHoistWorld (a :*: b) = gHoistWorld @t @m a :*: gHoistWorld @t @m b+  {-# INLINE gHoistWorld #-}+++------------------------------------------------------------------------------+-- | Utility class for implementing 'Data.Ecstasy.Internal.graftStorage'.+class GGraft a b where+  gGraft :: a x -> b x -> a x++instance {-# OVERLAPPING #-} GGraft (K1 i (VTable m a))+                                    (K1 i' (VTable (t m) a)) where+  gGraft a _ = a+  {-# INLINE gGraft #-}++instance GGraft (K1 i a) (K1 i' a) where+  gGraft _ (K1 a) = K1 a+  {-# INLINE gGraft #-}++instance (GGraft f f') => GGraft (M1 i c f) (M1 i' c' f') where+  gGraft (M1 a) (M1 e) = M1 $ gGraft a e+  {-# INLINE gGraft #-}++instance (GGraft a c, GGraft b d) => GGraft (a :*: b) (c :*: d) where+  gGraft (a :*: b) (c :*: d) = gGraft a c :*: gGraft b d+  {-# INLINE gGraft #-}++++------------------------------------------------------------------------------+-- | Utility class for implementing 'Data.Ecstasy.Internal.convertSetter'. class GConvertSetter a b where   gConvertSetter :: a x -> b x @@ -36,45 +89,56 @@   gConvertSetter (K1 Nothing)  = K1 Unset   {-# INLINE gConvertSetter #-} -instance GConvertSetter f f' => GConvertSetter (M1 i c f) (M1 i' c' f') where+instance GConvertSetter f f'+    => GConvertSetter (M1 i c f) (M1 i' c' f') where   gConvertSetter (M1 a) = M1 $ gConvertSetter a   {-# INLINE gConvertSetter #-} -instance (GConvertSetter a c , GConvertSetter b d) => GConvertSetter (a :*: b) (c :*: d) where+instance (GConvertSetter a c, GConvertSetter b d)+    => GConvertSetter (a :*: b) (c :*: d) where   gConvertSetter (a :*: b) = gConvertSetter a :*: gConvertSetter b   {-# INLINE gConvertSetter #-}  +------------------------------------------------------------------------------+-- | Utility class for implementing 'Data.Ecstasy.Internal.getEntity'. class GGetEntity m a b where-  gGetEntity :: a x -> Int -> m (b x)+  gGetEntity :: a x -> Int -> Codensity m (b x) -instance (Applicative m)-      => GGetEntity m (K1 i (VTable m a)) (K1 i' (Maybe a)) where-  gGetEntity (K1 (VTable vget _)) e = fmap K1 $ vget $ Ent e+instance (Monad m)+    => GGetEntity m (K1 i (VTable m a)) (K1 i' (Maybe a)) where+  gGetEntity (K1 (VTable vget _)) e = lift $ fmap K1 $ vget $ Ent e   {-# INLINE gGetEntity #-} -instance Applicative m => GGetEntity m (K1 i (IntMap a)) (K1 i' (Maybe a)) where+instance Applicative m+    => GGetEntity m (K1 i (IntMap a)) (K1 i' (Maybe a)) where   gGetEntity (K1 a) e = pure . K1 $ I.lookup e $ a   {-# INLINE gGetEntity #-} -instance Applicative m => GGetEntity m (K1 i (Maybe (Int, a))) (K1 i' (Maybe a)) where+instance Applicative m+    => GGetEntity m (K1 i (Maybe (Int, a))) (K1 i' (Maybe a)) where   gGetEntity (K1 (Just (e', a))) e | e == e' = pure . K1 $ Just a   gGetEntity _ _ = pure $ K1 Nothing   {-# INLINE gGetEntity #-} -instance (Functor m, GGetEntity m f f') => GGetEntity m (M1 i c f) (M1 i' c' f') where+instance (Functor m, GGetEntity m f f')+    => GGetEntity m (M1 i c f) (M1 i' c' f') where   gGetEntity (M1 a) e = fmap M1 $ gGetEntity a e   {-# INLINE gGetEntity #-} -instance (Applicative m, GGetEntity m a c , GGetEntity m b d) => GGetEntity m (a :*: b) (c :*: d) where+instance (Applicative m, GGetEntity m a c, GGetEntity m b d)+    => GGetEntity m (a :*: b) (c :*: d) where   gGetEntity (a :*: b) e = (:*:) <$> gGetEntity a e <*> gGetEntity b e   {-# INLINE gGetEntity #-}  +------------------------------------------------------------------------------+-- | Utility class for implementing 'Data.Ecstasy.Internal.setEntity'. class GSetEntity m a b where-  gSetEntity :: a x -> Int -> b x -> m (b x)+  gSetEntity :: a x -> Int -> b x -> Codensity m (b x) -instance Applicative m => GSetEntity m (K1 i (Update a)) (K1 i' (Maybe (Int, a))) where+instance Applicative m+    => GSetEntity m (K1 i (Update a)) (K1 i' (Maybe (Int, a))) where   gSetEntity (K1 (Set a)) e _ = pure . K1 $ Just (e, a)   gSetEntity (K1 Unset) e (K1 (Just (e', b))) =     pure $ if e == e'@@ -83,24 +147,28 @@   gSetEntity _  _ (K1 b) = pure $ K1 b   {-# INLINE gSetEntity #-} -instance (Applicative m)-      => GSetEntity m (K1 i (Update a)) (K1 i' (VTable m a)) where+instance (Monad m)+    => GSetEntity m (K1 i (Update a)) (K1 i' (VTable m a)) where   gSetEntity (K1 a) e (K1 z@(VTable _ vset)) =-    vset (Ent e) a *> pure (K1 z)+    lift (vset (Ent e) a) *> pure (K1 z)   {-# INLINE gSetEntity #-} -instance Applicative m => GSetEntity m (K1 i (Update a)) (K1 i' (IntMap a)) where+instance Applicative m+    => GSetEntity m (K1 i (Update a)) (K1 i' (IntMap a)) where   gSetEntity (K1 Keep) _ (K1 b) = pure $ K1 b   gSetEntity (K1 (Set a)) e (K1 b) = pure . K1 $ I.alter (const $ Just a) e b   gSetEntity (K1 Unset) e (K1 b) = pure . K1 $ I.alter (const Nothing) e b   {-# INLINE gSetEntity #-} -instance (Functor m, GSetEntity m f f') => GSetEntity m (M1 i c f) (M1 i' c' f') where+instance (Functor m, GSetEntity m f f')+    => GSetEntity m (M1 i c f) (M1 i' c' f') where   gSetEntity (M1 a) e (M1 b) = fmap M1 $ gSetEntity a e b   {-# INLINE gSetEntity #-} -instance (Applicative m, GSetEntity m a c, GSetEntity m b d) => GSetEntity m (a :*: b) (c :*: d) where-  gSetEntity (a :*: b) e (c :*: d) = (:*:) <$> gSetEntity a e c <*> gSetEntity b e d+instance (Applicative m, GSetEntity m a c, GSetEntity m b d)+    => GSetEntity m (a :*: b) (c :*: d) where+  gSetEntity (a :*: b) e (c :*: d) = (:*:) <$> gSetEntity a e c+                                           <*> gSetEntity b e d   {-# INLINE gSetEntity #-}  @@ -109,6 +177,10 @@ {-# INLINE def #-}  +------------------------------------------------------------------------------+-- | Utility class for implementing various defaults. The 'keep' parameter is+-- used to statically describe whether or not to keep the previous value when+-- dealing with 'Update' fields. class GDefault (keep :: Bool) f where   gdef :: f a @@ -133,7 +205,8 @@   {-# INLINE gdef #-}  instance {-# OVERLAPPING #-} (Applicative m, KnownSymbol sym)-      => GDefault keep (M1 S ('MetaSel ('Just sym) x y z) (K1 i (VTable m a))) where+    => GDefault keep (M1 S ('MetaSel ('Just sym) x y z)+                     (K1 i (VTable m a))) where   gdef = M1 $ K1 $ VTable (const err) (const $ const err)     where       err :: err@@ -148,7 +221,8 @@   gdef = M1 $ gdef @keep   {-# INLINE gdef #-} -instance (GDefault keep a, GDefault keep b) => GDefault keep (a :*: b) where+instance (GDefault keep a, GDefault keep b)+    => GDefault keep (a :*: b) where   gdef = gdef @keep :*: gdef @keep   {-# INLINE gdef #-}