packages feed

aztecs-0.5.0.0: src/Data/Aztecs/Query.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}

module Data.Aztecs.Query
  ( -- * Queries
    Query (..),
    entity,
    fetch,
    fetchMaybe,
    set,
    task,
    all,

    -- * Filters
    QueryFilter (..),
    with,
    without,
    DynamicQueryFilter (..),

    -- * Dynamic queries
    DynamicQuery (..),
    entityDyn,
    fetchDyn,
    fetchMaybeDyn,
    setDyn,

    -- * Reads and writes
    ReadsWrites (..),
    disjoint,
  )
where

import Control.Arrow (Arrow (..))
import Control.Category (Category (..))
import Control.Monad (mapM)
import Data.Aztecs.Component
import Data.Aztecs.Entity (EntityID)
import Data.Aztecs.World (World (..))
import Data.Aztecs.World.Archetype (Archetype)
import qualified Data.Aztecs.World.Archetype as A
import Data.Aztecs.World.Archetypes (Node (nodeArchetype))
import qualified Data.Aztecs.World.Archetypes as AS
import Data.Aztecs.World.Components (Components)
import qualified Data.Aztecs.World.Components as CS
import qualified Data.Map.Strict as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Prelude hiding (all, any, id, lookup, map, mapM, reads, (.))

-- | Query for matching entities.
--
-- === Do notation:
-- > move :: (Monad m) => Query m () Position
-- > move = proc () -> do
-- >   Velocity v <- Q.fetch -< ()
-- >   Position p <- Q.fetch -< ()
-- >   Q.set -< Position $ p + v
--
-- === Arrow combinators:
-- > move :: (Monad m) => Query m () Position
-- > move = Q.fetch &&& Q.fetch >>> arr (\(Position p, Velocity v) -> Position $ p + v) >>> Q.set
--
-- === Applicative combinators:
-- > move :: (Monad m) => Query m () Position
-- > move = (,) <$> Q.fetch <*> Q.fetch >>> arr (\(Position p, Velocity v) -> Position $ p + v) >>> Q.set
newtype Query m i o
  = Query {runQuery :: Components -> (ReadsWrites, Components, DynamicQuery m i o)}

instance (Functor m) => Functor (Query m i) where
  fmap f (Query q) = Query $ \cs -> let (cIds, cs', qS) = q cs in (cIds, cs', fmap f qS)

instance (Monad m) => Applicative (Query m i) where
  pure a = Query $ \cs -> (mempty, cs, pure a)
  (Query f) <*> (Query g) = Query $ \cs ->
    let (cIdsG, cs', aQS) = g cs
        (cIdsF, cs'', bQS) = f cs'
     in (cIdsG <> cIdsF, cs'', bQS <*> aQS)

instance (Monad m) => Category (Query m) where
  id = Query $ \cs -> (mempty, cs, id)
  (Query f) . (Query g) = Query $ \cs ->
    let (cIdsG, cs', aQS) = g cs
        (cIdsF, cs'', bQS) = f cs'
     in (cIdsG <> cIdsF, cs'', bQS . aQS)

instance (Monad m) => Arrow (Query m) where
  arr f = Query $ \cs -> (mempty, cs, arr f)
  first (Query f) = Query $ \comps -> let (cIds, comps', qS) = f comps in (cIds, comps', first qS)

-- | Get the currently matched `EntityID`.
entity :: (Applicative m) => Query m () EntityID
entity = Query $ \cs -> (mempty, cs, entityDyn)

-- | Fetch a `Component` by its type.
fetch :: forall m a. (Applicative m, Component a) => Query m () (a)
fetch = Query $ \cs ->
  let (cId, cs') = CS.insert @a cs
   in (ReadsWrites (Set.singleton cId) (Set.empty), cs', fetchDyn cId)

-- | Fetch a `Component` by its type, returning `Nothing` if it doesn't exist.
fetchMaybe :: forall m a. (Applicative m, Component a) => Query m () (Maybe a)
fetchMaybe = Query $ \cs ->
  let (cId, cs') = CS.insert @a cs
   in (ReadsWrites (Set.singleton cId) (Set.empty), cs', fetchMaybeDyn cId)

-- | Set a `Component` by its type.
set :: forall m a. (Applicative m, Component a) => Query m a a
set = Query $ \cs ->
  let (cId, cs') = CS.insert @a cs
   in (ReadsWrites Set.empty (Set.singleton cId), cs', setDyn cId)

-- | Run a monadic task in a `Query`.
task :: (Monad m) => (i -> m o) -> Query m i o
task f = Query $ \cs ->
  ( mempty,
    cs,
    DynamicQuery
      { dynQueryAll = \is _ arch -> (,arch) <$> mapM f is,
        dynQueryLookup = \i _ arch -> (\a -> (Just a, arch)) <$> f i
      }
  )

-- | Query all matching entities.
--
-- >>> :set -XTypeApplications
-- >>> import Data.Aztecs
-- >>> import qualified Data.Aztecs.World as W
-- >>>
-- >>> newtype X = X Int deriving (Show)
-- >>> instance Component X
-- >>>
-- >>> let (_, w) = W.spawn (bundle $ X 0) W.empty
-- >>> (xs, _) <- all (fetch @_ @X) w
-- >>> xs
-- [X 0]
all :: (Monad m) => Query m () a -> World -> m ([a], World)
all q w = do
  let (rws, cs', dynQ) = runQuery q (components w)
  as <-
    mapM
      (\n -> fst <$> dynQueryAll dynQ (repeat ()) (A.entities $ nodeArchetype n) (nodeArchetype n))
      (Map.elems $ AS.lookup (reads rws <> writes rws) (archetypes w))
  return (concat as, w {components = cs'})

data ReadsWrites = ReadsWrites
  { reads :: !(Set ComponentID),
    writes :: !(Set ComponentID)
  }
  deriving (Show)

instance Semigroup ReadsWrites where
  ReadsWrites r1 w1 <> ReadsWrites r2 w2 = ReadsWrites (r1 <> r2) (w1 <> w2)

instance Monoid ReadsWrites where
  mempty = ReadsWrites mempty mempty

disjoint :: ReadsWrites -> ReadsWrites -> Bool
disjoint a b =
  Set.disjoint (reads a) (writes b)
    || Set.disjoint (reads b) (writes a)
    || Set.disjoint (writes b) (writes a)

-- | Filter for a `Query`.
newtype QueryFilter = QueryFilter {runQueryFilter :: Components -> (DynamicQueryFilter, Components)}

instance Semigroup QueryFilter where
  a <> b =
    QueryFilter
      ( \cs ->
          let (withA', cs') = runQueryFilter a cs
              (withB', cs'') = runQueryFilter b cs'
           in (withA' <> withB', cs'')
      )

instance Monoid QueryFilter where
  mempty = QueryFilter (mempty,)

-- | Filter for entities containing this component.
with :: forall a. (Component a) => QueryFilter
with = QueryFilter $ \cs ->
  let (cId, cs') = CS.insert @a cs in (mempty {filterWith = Set.singleton cId}, cs')

-- | Filter out entities containing this component.
without :: forall a. (Component a) => QueryFilter
without = QueryFilter $ \cs ->
  let (cId, cs') = CS.insert @a cs in (mempty {filterWithout = Set.singleton cId}, cs')

data DynamicQueryFilter = DynamicQueryFilter
  { filterWith :: !(Set ComponentID),
    filterWithout :: !(Set ComponentID)
  }

instance Semigroup DynamicQueryFilter where
  DynamicQueryFilter withA withoutA <> DynamicQueryFilter withB withoutB =
    DynamicQueryFilter (withA <> withB) (withoutA <> withoutB)

instance Monoid DynamicQueryFilter where
  mempty = DynamicQueryFilter mempty mempty

-- | Dynamic query for components by ID.
data DynamicQuery m i o = DynamicQuery
  { dynQueryAll :: !([i] -> [EntityID] -> Archetype -> m ([o], Archetype)),
    dynQueryLookup :: !(i -> EntityID -> Archetype -> m (Maybe o, Archetype))
  }

instance (Functor m) => Functor (DynamicQuery m i) where
  fmap f q =
    DynamicQuery
      { dynQueryAll =
          \i es arch -> fmap (\(a, arch') -> (fmap f a, arch')) $ dynQueryAll q i es arch,
        dynQueryLookup = \i eId arch -> fmap (first $ fmap f) $ dynQueryLookup q i eId arch
      }

instance (Monad m) => Applicative (DynamicQuery m i) where
  pure a =
    DynamicQuery
      { dynQueryAll = \_ es arch -> pure (take (length es) $ repeat a, arch),
        dynQueryLookup = \_ _ arch -> pure (Just a, arch)
      }
  f <*> g =
    DynamicQuery
      { dynQueryAll = \i es arch -> do
          (as, arch') <- dynQueryAll g i es arch
          (fs, arch'') <- dynQueryAll f i es arch'
          return (zipWith ($) fs as, arch''),
        dynQueryLookup = \i eId arch -> do
          (res, arch') <- dynQueryLookup g i eId arch
          case res of
            Just a -> do
              (res', arch'') <- dynQueryLookup f i eId arch'
              return (fmap ($) res' <*> Just a, arch'')
            Nothing -> pure (Nothing, arch')
      }

instance (Monad m) => Category (DynamicQuery m) where
  id =
    DynamicQuery
      { dynQueryAll = \as _ arch -> pure (as, arch),
        dynQueryLookup = \a _ arch -> pure (Just a, arch)
      }
  f . g =
    DynamicQuery
      { dynQueryAll = \i es arch -> do
          (as, arch') <- dynQueryAll g i es arch
          dynQueryAll f as es arch',
        dynQueryLookup = \i eId arch -> do
          (res, arch') <- dynQueryLookup g i eId arch
          case res of
            Just a -> dynQueryLookup f a eId arch'
            Nothing -> pure (Nothing, arch')
      }

instance (Monad m) => Arrow (DynamicQuery m) where
  arr f =
    DynamicQuery
      { dynQueryAll = \bs _ arch -> pure (fmap f bs, arch),
        dynQueryLookup = \b _ arch -> pure (Just (f b), arch)
      }
  first f =
    DynamicQuery
      { dynQueryAll = \bds es arch -> do
          let (bs, ds) = unzip bds
          (cs, arch') <- dynQueryAll f bs es arch
          return (zip cs ds, arch'),
        dynQueryLookup = \(b, d) eId arch -> do
          (res, arch') <- dynQueryLookup f b eId arch
          return
            ( case res of
                Just c -> Just (c, d)
                Nothing -> Nothing,
              arch'
            )
      }

-- | Fetch the `EntityID` belonging to this entity.
entityDyn :: (Applicative m) => DynamicQuery m i EntityID
entityDyn =
  DynamicQuery
    { dynQueryAll = \_ es arch -> pure (es, arch),
      dynQueryLookup = \_ eId arch -> pure $ (Just eId, arch)
    }

-- | Fetch an `Component` by its `ComponentID`.
fetchDyn :: forall m a. (Applicative m, Component a) => ComponentID -> DynamicQuery m () a
fetchDyn cId =
  DynamicQuery
    { dynQueryAll = \_ _ arch -> let !as = A.all cId arch in pure (fmap snd as, arch),
      dynQueryLookup = \_ eId arch -> pure $ (A.lookupComponent eId cId arch, arch)
    }

-- | Fetch an `EntityID` and `Component` by its `ComponentID`.
fetchMaybeDyn ::
  forall m a.
  (Applicative m, Component a) =>
  ComponentID ->
  DynamicQuery m () (Maybe a)
fetchMaybeDyn cId =
  DynamicQuery
    { dynQueryAll = \_ _ arch -> let as = A.allMaybe cId arch in pure (fmap snd as, arch),
      dynQueryLookup = \_ eId arch -> pure $ (Just <$> A.lookupComponent eId cId arch, arch)
    }

-- | Set a `Component` by its `ComponentID`.
setDyn ::
  forall m a.
  (Applicative m, Component a) =>
  ComponentID ->
  DynamicQuery m a a
setDyn cId =
  DynamicQuery
    { dynQueryAll = \is _ arch -> let !arch' = A.withAscList cId is arch in pure (is, arch'),
      dynQueryLookup =
        \i eId arch -> pure (A.lookupComponent eId cId arch, A.insertComponent eId cId i arch)
    }