aztecs-0.9.1: src/Aztecs/ECS/Query/Dynamic/Reader.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
-- |
-- Module : Aztecs.ECS.Query.Dynamic.Reader
-- 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.Dynamic.Reader
( -- * Dynamic queries
DynamicQueryReader,
DynamicQueryReaderT (..),
ArrowDynamicQueryReader (..),
-- ** Running
allDyn,
filterDyn,
singleDyn,
singleMaybeDyn,
runDynQueryReader,
runDynQueryReaderT,
-- * Dynamic query filters
DynamicQueryFilter (..),
)
where
import Aztecs.ECS.Component
import Aztecs.ECS.Query.Dynamic.Reader.Class
import Aztecs.ECS.World.Archetype (Archetype)
import qualified Aztecs.ECS.World.Archetype as A
import Aztecs.ECS.World.Archetypes (Node)
import qualified Aztecs.ECS.World.Archetypes as AS
import Aztecs.ECS.World.Entities (Entities (..))
import Control.Arrow
import Control.Category
import Control.Monad.Identity
import Data.Either
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
import GHC.Stack
-- | @since 0.9
type DynamicQueryReader = DynamicQueryReaderT Identity
-- | Dynamic query for components by ID.
--
-- @since 0.9
newtype DynamicQueryReaderT m i o
= DynamicQueryReader
{ -- | Run a dynamic query with a list of inputs with length equal to the number of entities in the `Archetype`.
-- This is an internal function that should typically not be used directly.
--
-- @since 0.9
runDynQueryReader' :: [i] -> Archetype -> m [o]
}
deriving (Functor)
-- | @since 0.9
instance (Monad m) => Applicative (DynamicQueryReaderT m i) where
{-# INLINE pure #-}
pure a = DynamicQueryReader $ \is _ -> pure $ replicate (length is) a
{-# INLINE (<*>) #-}
f <*> g =
DynamicQueryReader $ \i arch -> do
!as <- runDynQueryReader' g i arch
!fs <- runDynQueryReader' f i arch
return $ zipWith ($) fs as
-- | @since 0.9
instance (Monad m) => Category (DynamicQueryReaderT m) where
{-# INLINE id #-}
id = DynamicQueryReader $ \as _ -> pure as
{-# INLINE (.) #-}
f . g = DynamicQueryReader $ \i arch -> do
!as <- runDynQueryReader' g i arch
runDynQueryReader' f as arch
-- | @since 0.9
instance (Monad m) => Arrow (DynamicQueryReaderT m) where
{-# INLINE arr #-}
arr f = DynamicQueryReader $ \bs _ -> pure $ fmap f bs
{-# INLINE first #-}
first f = DynamicQueryReader $ \bds arch -> do
let !(bs, ds) = unzip bds
!cs <- runDynQueryReader' f bs arch
return $ zip cs ds
-- | @since 0.9
instance (Monad m) => ArrowChoice (DynamicQueryReaderT m) where
{-# INLINE left #-}
left f = DynamicQueryReader $ \eds arch -> do
let !(es', ds) = partitionEithers eds
!cs <- runDynQueryReader' f es' arch
return $ fmap Left cs ++ fmap Right ds
-- | @since 0.9
instance (Monad m) => ArrowDynamicQueryReader (DynamicQueryReaderT m) where
{-# INLINE entity #-}
entity = DynamicQueryReader $ \_ arch -> pure $ Set.toList $ A.entities arch
{-# INLINE fetchDyn #-}
fetchDyn cId = DynamicQueryReader $ \_ arch -> pure $ A.lookupComponentsAsc cId arch
{-# INLINE fetchMaybeDyn #-}
fetchMaybeDyn cId = DynamicQueryReader $ \is arch -> pure $ case A.lookupComponentsAscMaybe cId arch of
Just as -> fmap Just as
Nothing -> map (const Nothing) is
-- | Dynamic query filter.
--
-- @since 0.9
data DynamicQueryFilter = DynamicQueryFilter
{ -- | `ComponentID`s to include.
--
-- @since 0.9
filterWith :: !(Set ComponentID),
-- | `ComponentID`s to exclude.
--
-- @since 0.9
filterWithout :: !(Set ComponentID)
}
-- | @since 0.9
instance Semigroup DynamicQueryFilter where
DynamicQueryFilter withA withoutA <> DynamicQueryFilter withB withoutB =
DynamicQueryFilter (withA <> withB) (withoutA <> withoutB)
-- | @since 0.9
instance Monoid DynamicQueryFilter where
mempty = DynamicQueryFilter mempty mempty
-- | Run a dynamic query.
--
-- @since 0.9
{-# INLINE runDynQueryReaderT #-}
runDynQueryReaderT :: i -> DynamicQueryReaderT m i o -> Archetype -> m [o]
runDynQueryReaderT i q arch = runDynQueryReader' q (replicate (length $ A.entities arch) i) arch
-- | Run a dynamic query.
--
-- @since 0.9
{-# INLINE runDynQueryReader #-}
runDynQueryReader :: i -> DynamicQueryReader i o -> Archetype -> [o]
runDynQueryReader i q arch = runIdentity $ runDynQueryReaderT i q arch
-- | Match all entities.
--
-- @since 0.9
allDyn :: (Monad m) => Set ComponentID -> i -> DynamicQueryReaderT m i a -> Entities -> m [a]
allDyn cIds i q es =
if Set.null cIds
then runDynQueryReaderT i q A.empty {A.entities = Map.keysSet $ entities es}
else
let go n = runDynQueryReaderT i q $ AS.nodeArchetype n
in concat <$> mapM go (AS.find cIds $ archetypes es)
-- | Match all entities with a filter.
--
-- @since 0.9
filterDyn :: (Monad m) => Set ComponentID -> i -> (Node -> Bool) -> DynamicQueryReaderT m i a -> Entities -> m [a]
filterDyn cIds i f q es =
if Set.null cIds
then runDynQueryReaderT i q A.empty {A.entities = Map.keysSet $ entities es}
else
let go n = runDynQueryReaderT i q $ AS.nodeArchetype n
in concat <$> mapM go (Map.filter f $ AS.find cIds $ archetypes es)
-- | Match a single entity.
--
-- @since 0.9
singleDyn :: (HasCallStack) => Set ComponentID -> i -> DynamicQueryReader i a -> Entities -> a
singleDyn cIds i q es = case singleMaybeDyn cIds i q es of
Just a -> a
_ -> error "singleDyn: expected a single entity"
-- | Match a single entity, or `Nothing`.
--
-- @since 0.9
singleMaybeDyn :: Set ComponentID -> i -> DynamicQueryReader i a -> Entities -> Maybe a
singleMaybeDyn cIds i q es =
if Set.null cIds
then case Map.keys $ entities es of
[eId] -> case runDynQueryReader i q $ A.singleton eId of
[a] -> Just a
_ -> Nothing
_ -> Nothing
else case Map.elems $ AS.find cIds $ archetypes es of
[n] -> case runDynQueryReader i q $ AS.nodeArchetype n of
[a] -> Just a
_ -> Nothing
_ -> Nothing