aztecs-0.10.0: src/Aztecs/ECS/Query/Reader.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.Dynamic
-- 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.Reader
( -- * Queries
QueryReader (..),
QueryReaderF (..),
DynamicQueryReaderF (..),
-- ** Running
all,
all',
single,
single',
singleMaybe,
singleMaybe',
-- * Filters
QueryFilter (..),
with,
without,
DynamicQueryFilter (..),
)
where
import Aztecs.ECS.Component
import Aztecs.ECS.Query.Dynamic.Reader
import Aztecs.ECS.Query.Reader.Class
import Aztecs.ECS.World.Components (Components)
import qualified Aztecs.ECS.World.Components as CS
import Aztecs.ECS.World.Entities (Entities (..))
import qualified Aztecs.ECS.World.Entities as E
import Control.Monad.Identity
import Data.Set (Set)
import qualified Data.Set as Set
import GHC.Stack
import Prelude hiding (all)
-- | Query to read from entities.
--
-- @since 0.10
newtype QueryReader a
= QueryReader
{ -- | Run a query reader.
--
-- @since 0.10
runQueryReader :: Components -> (Set ComponentID, Components, DynamicQueryReader a)
}
deriving (Functor)
-- | @since 0.10
instance Applicative QueryReader where
pure a = QueryReader (mempty,,pure a)
{-# INLINE pure #-}
(QueryReader f) <*> (QueryReader g) = QueryReader $ \cs ->
let !(cIdsG, cs', aQS) = g cs
!(cIdsF, cs'', bQS) = f cs'
in (cIdsG <> cIdsF, cs'', bQS <*> aQS)
{-# INLINE (<*>) #-}
-- | @since 0.10
instance QueryReaderF QueryReader where
fetch :: forall a. (Component a) => QueryReader a
fetch = QueryReader $ \cs ->
let !(cId, cs') = CS.insert @a cs in (Set.singleton cId, cs', fetchDyn cId)
{-# INLINE fetch #-}
fetchMaybe :: forall a. (Component a) => QueryReader (Maybe a)
fetchMaybe = QueryReader $ \cs ->
let !(cId, cs') = CS.insert @a cs in (Set.singleton cId, cs', fetchMaybeDyn cId)
{-# INLINE fetchMaybe #-}
-- | @since 0.10
instance DynamicQueryReaderF QueryReader where
{-# INLINE entity #-}
entity = QueryReader (mempty,,entity)
{-# INLINE fetchDyn #-}
fetchDyn cId = QueryReader (Set.singleton cId,,fetchDyn cId)
{-# INLINE fetchMaybeDyn #-}
fetchMaybeDyn cId = QueryReader (Set.singleton cId,,fetchMaybeDyn cId)
-- | Filter for a `Query`.
--
-- @since 0.9
newtype QueryFilter = QueryFilter
{ -- | Run a query filter.
runQueryFilter :: Components -> (DynamicQueryFilter, Components)
}
-- | @since 0.9
instance Semigroup QueryFilter where
a <> b =
QueryFilter
( \cs ->
let !(withA', cs') = runQueryFilter a cs
!(withB', cs'') = runQueryFilter b cs'
in (withA' <> withB', cs'')
)
-- | @since 0.9
instance Monoid QueryFilter where
mempty = QueryFilter (mempty,)
-- | Filter for entities containing this component.
--
-- @since 0.9
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.
--
-- @since 0.9
without :: forall a. (Component a) => QueryFilter
without = QueryFilter $ \cs ->
let !(cId, cs') = CS.insert @a cs in (mempty {filterWithout = Set.singleton cId}, cs')
-- | Match all entities.
--
-- @since 0.10
{-# INLINE all #-}
all :: QueryReader a -> Entities -> ([a], Entities)
all q es = let !(as, cs) = all' q es in (as, es {E.components = cs})
-- | Match all entities.
--
-- @since 0.10
{-# INLINE all' #-}
all' :: QueryReader a -> Entities -> ([a], Components)
all' q es = let !(rs, cs', dynQ) = runQueryReader q (E.components es) in (allDyn rs dynQ es, cs')
-- | Match a single entity.
--
-- @since 0.10
{-# INLINE single #-}
single :: (HasCallStack) => QueryReader a -> Entities -> (a, Entities)
single q es = let !(a, cs) = single' q es in (a, es {E.components = cs})
-- | Match a single entity.
--
-- @since 0.10
{-# INLINE single' #-}
single' :: (HasCallStack) => QueryReader a -> Entities -> (a, Components)
single' q es = let !(rs, cs', dynQ) = runQueryReader q (E.components es) in (singleDyn rs dynQ es, cs')
-- | Match a single entity.
--
-- @since 0.10
{-# INLINE singleMaybe #-}
singleMaybe :: QueryReader a -> Entities -> (Maybe a, Entities)
singleMaybe q es = let !(a, cs) = singleMaybe' q es in (a, es {E.components = cs})
-- | Match a single entity.
--
-- @since 0.10
{-# INLINE singleMaybe' #-}
singleMaybe' :: QueryReader a -> Entities -> (Maybe a, Components)
singleMaybe' q es = let !(rs, cs', dynQ) = runQueryReader q (E.components es) in (singleMaybeDyn rs dynQ es, cs')