aztecs-0.11.0: src/Aztecs/ECS/Query.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
-- |
-- Module : Aztecs.ECS.Query
-- Copyright : (c) Matt Hunzinger, 2025
-- License : BSD-style (see the LICENSE file in the distribution)
--
-- Maintainer : matt@hunzinger.me
-- Stability : provisional
-- Portability : non-portable (GHC extensions)
module Aztecs.ECS.Query
( -- * Queries
Query,
QueryT (..),
-- ** Operations
entity,
fetch,
fetchMap,
fetchMapM,
zipFetchMap,
zipFetchMapAccum,
zipFetchMapM,
zipFetchMapAccumM,
-- ** Filters
with,
without,
-- ** Running
query,
readSingle,
readSingle',
readSingleMaybe,
readSingleMaybe',
querySingle,
querySingleMaybe,
queryEntities,
readQueryEntities,
-- ** Conversion
fromDyn,
)
where
import Aztecs.ECS.Component
import Aztecs.ECS.Entity
import Aztecs.ECS.Query.Dynamic
import Aztecs.ECS.World.Components (Components)
import qualified Aztecs.ECS.World.Components as CS
import Aztecs.ECS.World.Entities (Entities (..))
import Control.Monad.Identity
import GHC.Stack
-- | @since 0.11
type Query = QueryT Identity
-- | Query for matching entities.
--
-- @since 0.11
newtype QueryT f a = Query
{ -- | Run a query, producing a `DynamicQueryT`.
--
-- @since 0.11
runQuery :: Components -> (Components, DynamicQueryT f a)
}
deriving (Functor)
-- | @since 0.11
instance (Applicative f) => Applicative (QueryT f) where
{-# INLINE pure #-}
pure a = Query (,pure a)
{-# INLINE (<*>) #-}
(Query f) <*> (Query g) = Query $ \cs ->
let !(cs', aQS) = g cs
!(cs'', bQS) = f cs'
in (cs'', bQS <*> aQS)
-- | Fetch the current `EntityID`.
--
-- @since 0.11
{-# INLINE entity #-}
entity :: QueryT f EntityID
entity = Query (,entityDyn)
-- | Fetch a component.
--
-- @since 0.11
{-# INLINE fetch #-}
fetch :: forall f a. (Component a) => QueryT f a
fetch = fromDynInternal @f @a $ fetchDyn
-- | Fetch a component and map it, storing the result.
--
-- @since 0.11
{-# INLINE fetchMap #-}
fetchMap :: forall m a. (Component a) => (a -> a) -> QueryT m a
fetchMap f = fromDynInternal @_ @a $ fetchMapDyn f
-- | Fetch a component and map it with a monadic function, storing the result.
--
-- @since 0.11
{-# INLINE fetchMapM #-}
fetchMapM :: forall m a. (Monad m, Component a) => (a -> m a) -> QueryT m a
fetchMapM f = fromDynInternal @_ @a $ fetchMapDynM f
-- | Fetch a component and map it with some input, storing the result.
--
-- @since 0.11
{-# INLINE zipFetchMap #-}
zipFetchMap :: forall m a b. (Component a) => (b -> a -> a) -> QueryT m b -> QueryT m a
zipFetchMap f = fromWriterInternal @a $ zipFetchMapDyn f
-- | Fetch a component and map it with some input, storing the result and returning some output.
--
-- @since 0.11
{-# INLINE zipFetchMapAccum #-}
zipFetchMapAccum ::
forall m a b c. (Component a) => (b -> a -> (c, a)) -> QueryT m b -> QueryT m (c, a)
zipFetchMapAccum f = fromWriterInternal @a $ zipFetchMapAccumDyn f
-- | Fetch a component and map it with some input and a monadic function, storing the result.
--
-- @since 0.11
{-# INLINE zipFetchMapM #-}
zipFetchMapM :: forall m a b. (Monad m, Component a) => (b -> a -> m a) -> QueryT m b -> QueryT m a
zipFetchMapM f = fromWriterInternal @a $ zipFetchMapDynM f
-- | Fetch a component and map it with some input and a monadic function,
-- storing the result and returning some output.
--
-- @since 0.11
{-# INLINE zipFetchMapAccumM #-}
zipFetchMapAccumM ::
forall m a b c. (Monad m, Component a) => (b -> a -> m (c, a)) -> QueryT m b -> QueryT m (c, a)
zipFetchMapAccumM f = fromWriterInternal @a $ zipFetchMapAccumDynM f
-- | Filter for entities with a component.
--
-- @since 0.11
{-# INLINE with #-}
with :: forall f a. (Component a) => QueryT f ()
with = fromDynInternal @f @a $ withDyn
-- | Filter for entities without a component.
--
-- @since 0.11
{-# INLINE without #-}
without :: forall f a. (Component a) => QueryT f ()
without = fromDynInternal @f @a $ withDyn
-- | Convert a `DynamicQueryT` to a `QueryT`.
--
-- @since 0.11
{-# INLINE fromDyn #-}
fromDyn :: DynamicQueryT f a -> QueryT f a
fromDyn q = Query (,q)
{-# INLINE fromDynInternal #-}
fromDynInternal ::
forall f a b.
(Component a) =>
(ComponentID -> DynamicQueryT f b) ->
QueryT f b
fromDynInternal f = Query $ \cs ->
let !(cId, cs') = CS.insert @a cs in (cs', f cId)
{-# INLINE fromWriterInternal #-}
fromWriterInternal ::
forall c f a b.
(Component c) =>
(ComponentID -> DynamicQueryT f b -> DynamicQueryT f a) ->
QueryT f b ->
QueryT f a
fromWriterInternal f q = Query $ \cs ->
let !(cId, cs') = CS.insert @c cs
!(cs'', dynQ) = runQuery q cs'
in (cs'', f cId dynQ)
-- | Match and update all entities.
--
-- @since 0.11
{-# INLINE query #-}
query :: (Monad m) => QueryT m a -> Entities -> m ([a], Entities)
query q es = do
let !(cs', dynQ) = runQuery q $ components es
(as, es') <- queryDyn dynQ es
return (as, es' {components = cs'})
-- | Match and update a single matched entity.
--
-- @since 0.11
{-# INLINE querySingle #-}
querySingle :: (HasCallStack, Monad m) => QueryT m a -> Entities -> m (a, Entities)
querySingle q es = do
let !(cs', dynQ) = runQuery q $ components es
(as, es') <- mapSingleDyn dynQ es
return (as, es' {components = cs'})
-- | Match and update a single matched entity, or `Nothing`.
--
-- @since 0.11
{-# INLINE querySingleMaybe #-}
querySingleMaybe :: (Monad m) => QueryT m a -> Entities -> m (Maybe a, Entities)
querySingleMaybe q es = do
let !(cs', dynQ) = runQuery q $ components es
(as, es') <- mapSingleMaybeDyn dynQ es
return (as, es' {components = cs'})
-- | Match and update the specified entities.
--
-- @since 0.11
queryEntities :: (Monad m) => [EntityID] -> QueryT m a -> Entities -> m ([a], Entities)
queryEntities eIds q es = do
let !(cs', dynQ) = runQuery q $ components es
(as, es') <- queryEntitiesDyn eIds dynQ es
return (as, es' {components = cs'})
-- | Match a single entity.
--
-- @since 0.11
{-# INLINE readSingle #-}
readSingle :: (HasCallStack, Monad m) => QueryT m a -> Entities -> m (a, Entities)
readSingle q es = do
let !(cs', dynQ) = runQuery q $ components es
as <- singleDyn dynQ es
return (as, es {components = cs'})
-- | Match a single entity.
--
-- @since 0.11
{-# INLINE readSingle' #-}
readSingle' :: (HasCallStack, Monad m) => QueryT m a -> Entities -> m (a, Components)
readSingle' q es = do
let !(cs', dynQ) = runQuery q $ components es
as <- singleDyn dynQ es
return (as, cs')
-- | Match a single entity, or `Nothing`.
--
-- @since 0.11
{-# INLINE readSingleMaybe #-}
readSingleMaybe :: (Monad m) => QueryT m a -> Entities -> m (Maybe a, Entities)
readSingleMaybe q es = do
let !(cs', dynQ) = runQuery q $ components es
as <- singleMaybeDyn dynQ es
return (as, es {components = cs'})
-- | Match a single entity, or `Nothing`.
--
-- @since 0.11
{-# INLINE readSingleMaybe' #-}
readSingleMaybe' :: (Monad m) => QueryT m a -> Entities -> m (Maybe a, Components)
readSingleMaybe' q es = do
let !(cs', dynQ) = runQuery q $ components es
as <- singleMaybeDyn dynQ es
return (as, cs')
-- | Match the specified entities.
--
-- @since 0.11
readQueryEntities :: (Monad m) => [EntityID] -> QueryT m a -> Entities -> m ([a], Entities)
readQueryEntities eIds q es = do
let !(cs', dynQ) = runQuery q $ components es
as <- readQueryEntitiesDyn eIds dynQ es
return (as, es {components = cs'})