packages feed

ohhecs-0.0.1: src/Games/ECS/Component/Store.hs

{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE Trustworthy #-}

-- |
-- Module      :  Games.ECS.Component.Store
-- Description : Collection types for components.
-- Copyright   :  (C) 2020 Sophie Taylor
-- License     :  AGPL-3.0-or-later
-- Maintainer  :  Sophie Taylor <sophie@spacekitteh.moe>
-- Stability   :  experimental
-- Portability: GHC
--
-- Different components, when stored in bulk, may be suited to different collection types.
module Games.ECS.Component.Store
  ( EntityIndexedTraversable (..),
    InternedComponentStore,
    ComponentStore,
    EntitySet,
    theKeys,
    theMap,
    theInternedMap,
    IntersectionOfEntities,
  )
where

import Control.Lens
import Data.Hashable
import Data.IntMap.Strict qualified as IM
import Data.IntSet qualified as IS
import Data.Interned
import GHC.Generics (Generic, Generic1)
import Games.ECS.Entity

-- | A class for types which contain objects which are indexed by an 'Entity'.
class EntityIndexedTraversable t c where -- TODO make this kind polymorphic

  -- | Traverse the collection.
  entitiesTraversed :: IndexedTraversal' Entity (t c) c
  default entitiesTraversed :: (TraversableWithIndex Entity t) => IndexedTraversal' Entity (t c) c
  entitiesTraversed = itraversed
  {-# INLINE entitiesTraversed #-}

-- | An 'IS.IntMap'-based 'Games.ECS.Component.Component' store
data ComponentStore c = ComponentStore {_theMap :: IM.IntMap c, _theKeys :: EntitySet} deriving (Eq, Show, Functor, Generic1, Foldable, Traversable, Generic)

-- deriving instance Generic (ComponentStore c)
instance FunctorWithIndex Entity (ComponentStore) where
  {-# INLINE imap #-}
  imap f (ComponentStore s ks) = ComponentStore (IM.mapWithKey (f . EntRef) s) ks

instance EntityIndexedTraversable ComponentStore c

instance FoldableWithIndex Entity ComponentStore where
  {-# INLINE ifoldMap #-}
  {-# INLINE ifoldr #-}
  {-# INLINE ifoldl' #-}
  ifoldMap f (ComponentStore s _) = IM.foldMapWithKey (f . EntRef) s
  ifoldr f b (ComponentStore s _) = IM.foldrWithKey (f . EntRef) b s
  ifoldl' f b (ComponentStore s _) = IM.foldlWithKey' (flip (f . EntRef)) b s

instance TraversableWithIndex Entity ComponentStore where
  {-# INLINE itraverse #-}
  itraverse f (ComponentStore s ks) = fmap (\i -> ComponentStore i ks) $ itraverse (f . EntRef) s

type instance Index (ComponentStore c) = Entity

type instance IxValue (ComponentStore c) = c

instance At (ComponentStore c) where
  {-# INLINE at #-}
  at (EntRef k) f cs@(ComponentStore s (EntitySet ks)) =
    f mv <&> \r -> case r of
      Nothing -> maybe cs (const (ComponentStore (IM.delete k s) (EntitySet (IS.delete k ks)))) mv
      Just v' -> ComponentStore (IM.insert k v' s) (EntitySet (IS.insert k ks))
    where
      mv = IM.lookup k s

instance Ixed (ComponentStore c) where
  {-# INLINE ix #-}
  ix (EntRef k) f (ComponentStore s ks) = fmap (\i -> ComponentStore i ks) $ ix k f s

instance AsEmpty (ComponentStore c) where
  {-# INLINE _Empty #-}
  _Empty = nearly (ComponentStore IM.empty (EntitySet IS.empty)) (\(ComponentStore _ (EntitySet ks)) -> IS.null ks)

makeLenses ''ComponentStore

instance HasEntitySet (ComponentStore c) where
  {-# INLINE entitySet #-}
  entitySet = theKeys

data InternedComponent c = InternedComponent {_internedId :: Id, _internedValue :: c} deriving (Generic, Show)

instance Eq (InternedComponent c) where
  {-# INLINE (==) #-}
  InternedComponent a _ == InternedComponent b _ = a == b

instance Ord (InternedComponent c) where
  {-# INLINE compare #-}
  compare (InternedComponent a _) (InternedComponent b _) = compare a b

instance Hashable (InternedComponent c) where
  {-# INLINE hashWithSalt #-}
  hashWithSalt s (InternedComponent i _) = hashWithSalt s i

instance (Hashable c, Eq c) => Interned (InternedComponent c) where
  type Uninterned (InternedComponent c) = c
  newtype Description (InternedComponent c) = DSC c deriving (Eq)
  {-# INLINE CONLIKE describe #-}
  describe = DSC
  {-# INLINE CONLIKE identify #-}
  identify = InternedComponent
  {-# INLINE CONLIKE cache #-}
  cache = itCache

itCache :: (Eq c, Hashable c) => Cache (InternedComponent c)
itCache = mkCache
{-# NOINLINE itCache #-}

instance (Hashable c) => Hashable (Description (InternedComponent c)) where
  hashWithSalt s (DSC c) = hashWithSalt s c
  {-# INLINE hashWithSalt #-}

instance (Eq c, Hashable c) => Uninternable (InternedComponent c) where
  unintern (InternedComponent _ c) = c
  {-# INLINE unintern #-}

-- | Some components may be shared in common among a large number of entities, and may be expensive to compare for equality. This type can be used to store them efficiently by keeping only a single example around.
data InternedComponentStore c = InternedComponentStore {_theInternedMap :: IM.IntMap (InternedComponent c), _theInternedKeys :: EntitySet} deriving (Eq, Show, Generic, Generic1)

type instance Index (InternedComponentStore c) = Entity

type instance IxValue (InternedComponentStore c) = c

instance (Eq c, Hashable c) => At (InternedComponentStore c) where
  {-# INLINE at #-}
  at (EntRef k) f cs@(InternedComponentStore s (EntitySet ks)) =
    f mv <&> \r -> case r of
      Nothing -> maybe cs (const (InternedComponentStore (IM.delete k s) (EntitySet (IS.delete k ks)))) mv
      Just v' -> InternedComponentStore (IM.insert k (intern v') s) (EntitySet (IS.insert k ks))
    where
      mv = fmap unintern $ IM.lookup k s

instance (Eq c, Hashable c) => Ixed (InternedComponentStore c) where
  {-# INLINE ix #-}
  ix (EntRef k) f (InternedComponentStore s ks) = fmap (\i -> InternedComponentStore i ks) $ ix k (fmap intern . f . unintern) s

instance AsEmpty (InternedComponentStore c) where
  {-# INLINE _Empty #-}
  _Empty = nearly (InternedComponentStore IM.empty (EntitySet IS.empty)) (\(InternedComponentStore _ (EntitySet ks)) -> IS.null ks)

makeLenses ''InternedComponentStore

{-# INLINE toNormalCS #-}
toNormalCS :: (Uninternable (InternedComponent c)) => InternedComponentStore c -> ComponentStore c
toNormalCS (InternedComponentStore ics iks) = ComponentStore (fmap unintern ics) iks

{-# INLINE toInternedCS #-}
toInternedCS :: (Interned (InternedComponent c)) => ComponentStore c -> InternedComponentStore c
toInternedCS (ComponentStore cs ks) = InternedComponentStore (fmap intern cs) ks

internedComponentStore :: (Uninternable (InternedComponent c)) => Iso' (ComponentStore c) (InternedComponentStore c)
internedComponentStore = iso toInternedCS toNormalCS
{-# INLINE internedComponentStore #-}

instance HasEntitySet (InternedComponentStore c) where
  {-# INLINE entitySet #-}
  entitySet = theInternedKeys

instance (Uninternable (InternedComponent c)) => EntityIndexedTraversable InternedComponentStore c where
  {-# INLINE entitiesTraversed #-}
  entitiesTraversed = (from internedComponentStore) . entitiesTraversed