mischief-ecs-0.2.0.0: src/Mischief/ECS/World/Query/Pipe.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# OPTIONS_GHC -Wno-partial-fields #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
module Mischief.ECS.World.Query.Pipe
( -- * Impure
qtraverse,
qtap,
-- * Mapping
qmap,
qmapM,
qmapMaybe,
qmapMaybeM,
-- * Filtering
qfilter,
qfilterM,
-- * Insertion
qinsert,
qinsertNew,
qinsertIfNeq,
qmodify,
qmodifyNew,
qmodifyIfNeq,
-- * Expansion
qextend,
qentity,
-- * Traversal
qrelateOne,
qrelateMany,
qjoin,
-- * Monad
qpure,
qrefocus,
qres,
qget,
qgetAll,
qcollect,
qmaybe,
qstopIf,
qstop,
qiter,
qiterE,
qlift,
qlift_,
-- * Logging
qinfo,
qwarn,
qerr,
-- * Other
qdespawn,
-- * Parallelism
qpar,
)
where
import Control.Monad (void)
import Control.Monad qualified
import Data.Function
import Data.Maybe
import Data.Text (Text)
import GHC.Stack
import Mischief.ECS.Components
import Mischief.ECS.Components.Bundle
import Mischief.ECS.Entities
import Mischief.ECS.Log
import Mischief.ECS.World
import Mischief.ECS.World.Defer
import Mischief.ECS.World.Insert
import Mischief.ECS.World.Query
import Mischief.ECS.World.Query.Markers
import Mischief.ECS.World.Query.QueryFilter
import Mischief.ECS.World.Query.Queryable
import Mischief.ECS.World.Spawn
{-# RULES
"qmap/qmap" forall f g xs. qmap f (qmap g xs) = qmap (f . g) xs
#-}
qcollect :: (MonadSystem w m) => Query m a -> Query m [From a]
qcollect = CollectQuery
-- | Maps @Query m a@ to @Query m b@. Same as 'fmap'.
--
-- __Example__
--
-- @
-- [q|Name, Position|]
-- & qmap (\\(name, _) -> name)
-- & query
-- @
qmap :: (MonadSystem w m) => (a -> b) -> Query m a -> Query m b
qmap = fmap
{-# INLINE [1] qmap #-}
-- | 'qmap' with side effects. Same as 'qtraverse'.
qmapM :: (MonadSystem w m) => (Entity -> a -> m b) -> Query m a -> Query m b
qmapM = qtraverse
-- | 'qmapMaybe' with side effects.
qmapMaybeM :: (MonadSystem w m) => (Entity -> a -> m (Maybe b)) -> Query m a -> Query m b
qmapMaybeM !f !b = MapQuery (FilterQuery (MapQuery b f) (\_ !x -> pure (isJust x))) (\_ !x -> pure (fromMaybe undefined x))
-- | Possibly maps each element from @Query m a@ to an element from @Query m b@. If Nothing, will filter this Entity out of the Query.
--
-- __Example__
--
-- @
-- f :: Name -> Maybe Name
-- @
--
-- @
-- [q|Name|]
-- & qmapMaybe f
-- & query
-- @
qmapMaybe :: (MonadSystem w m) => (a -> Maybe b) -> Query m a -> Query m b
qmapMaybe !f !x = qmap (fromMaybe undefined) $ qfilter isJust (qmap f x)
-- | Map @Query m a@ to @Query m b@ with side effects.
--
-- __Example__
--
-- @
-- [q|Name, Position|]
-- & qtraverse (\\e (name, pos) -> do
-- [i|The name of entity #{e} is #{name}|]
-- pure pos
-- )
-- & query
-- @
qtraverse :: (Entity -> a -> m b) -> Query m a -> Query m b
qtraverse !f !x = MapQuery x f
qlift :: (MonadSystem w m) => m a -> Query m a
qlift !s = pure () & qtraverse (\_ _ -> s)
qlift_ :: (MonadSystem w m) => m a -> Query m ()
qlift_ = void . qlift
-- | Apply a side effect over a query.
--
-- __Example__
--
-- @
-- [q|Name, Position|]
-- & qtraverse (\\e (name, pos) -> do
-- [i|The name of entity #{e} is #{name}|]
-- )
-- & query
-- @
qtap :: (Entity -> a -> m b) -> Query m a -> Query m a
qtap = flip DoQuery
-- | Insert the values obtained from a mapping function.
--
-- __Example__
--
-- @
-- q[|Position, Velocity|]
-- & qinsert (\\(Position p, Velocity v) -> Position (p + v))
-- & query
-- @
qinsert :: (Bundle b) => (a -> b) -> Query System a -> Query System a
qinsert !f = qtap (\(!e) !a -> insert (f a) e)
-- | Same as 'qinsert' but only inserts components that aren't already on the entity.
qinsertNew :: (Bundle b) => (a -> b) -> Query System a -> Query System a
qinsertNew !f = qtap (\(!e) !a -> insertNew (f a) e)
-- | Same as 'qinsert' but only inserts components that are new or different than the ones already on the entity. Avoids triggering false change detection.
qinsertIfNeq :: (BundleEq b) => (a -> b) -> Query System a -> Query System a
qinsertIfNeq !f = qtap (\(!e) !a -> insertIfNeq (f a) e)
-- | Same as 'qinsert' but will also propagate the mapped elements. Equivalent to @qinsert id . qmap f@.
qmodify :: (Bundle b) => (a -> b) -> Query System a -> Query System b
qmodify !f = qinsert id . qmap f
-- | Same as 'qinsertNew' but will also propagate the mapped elements. Equivalent to @qinsertNew id . qmap f@.
qmodifyNew :: (Bundle b) => (a -> b) -> Query System a -> Query System b
qmodifyNew !f = qinsertNew id . qmap f
-- | Same as 'qinsertIfNeq' but will also propagate the mapped elements. Equivalent to @qinsertIfNeq id . qmap f@.
qmodifyIfNeq :: (BundleEq b) => (a -> b) -> Query System a -> Query System b
qmodifyIfNeq !f = qinsertIfNeq id . qmap f
{-# RULES
"qfilter/qfilter" forall f g xs. qfilter f (qfilter g xs) = qfilter (\x -> f x && g x) xs
#-}
-- | Filter the entities of a query.
--
-- __Example__
--
-- @
-- [q|Name|]
-- & qfilter (== Name "Florian")
-- & query
-- @
qfilter :: (MonadSystem w m) => (a -> Bool) -> Query m a -> Query m a
qfilter !f !x = FilterQuery x (\_ !x -> pure (f x))
{-# INLINE [1] qfilter #-}
-- | Filter the entities of a query with possible side effects.
--
-- __Example__
--
-- @
-- [q|Position|]
-- & qfilterM (\\e _ -> 'changed' \@Position e)
-- & query
-- @
qfilterM :: (Entity -> a -> m Bool) -> Query m a -> Query m a
qfilterM !f !x = FilterQuery x f
-- check :: (MonadSystem w m) => QueryFilter EntityFilter -> Entity -> m Bool
-- check NoFilter _ = pure True
-- check (With !x) !e = do
-- !world <- unsafeGetWorld
-- liftIO $ filterEntity (With x) world e
-- check (Without !x) !e = do
-- !world <- unsafeGetWorld
-- liftIO $ filterEntity (Without x) world e
-- check (Added !x) !e = added x e
-- check (Changed !x) !e = changed x e
-- check (And !a !b) !e = (&&) <$> check a e <*> check b e
-- check (Or !a !b) !e = (||) <$> check a e <*> check b e
-- check (Not !a) !e = not <$> check a e
-- {-# RULES
-- "qcheck/qcheck" forall f g xs. qcheck f (qcheck g xs) = qcheck (f `And` g) xs
-- #-}
-- | Filter the entities of a query with a dedicated query filter.
--
-- __Example__
--
-- @
-- [q|Position|]
-- & qcheck [qf|Changed Position, !Added Position|]
-- & query
-- @
-- qcheck :: (MonadSystem w m) => QueryFilter EntityFilter -> Query m a -> Query m a
-- qcheck !f = qfilterM (\(!e) _ -> check f e)
-- {-# INLINE [1] qcheck #-}
-- | Extends the query with new elements.
--
-- __Example__
--
-- @
-- [q|Name|]
-- & qextend [qd|Position|] (,)
-- & query
-- @
qextend :: (MonadSystem w m, Queryable qd out) => qd -> (a -> out -> c) -> Query m a -> Query m c
qextend !qd !f !a = do
(!e, !a) <- qentity a
a' <- qget e qd
qpure e & qmap (const $ f a a')
-- | Grab a Query Data from an entity resulting from a traversal and map it into the current data wrapped in a @From@:
--
-- __Example__
--
-- @
-- [q|Name|]
-- & qrelateOne (Tree.'Mischief.ECS.Relationships.Tree.root' \@Like) \[qd|Name|] (,)
-- & query_
-- @
qrelateOne :: (MonadSystem w m, Queryable qd out) => (Entity -> m (Maybe Entity)) -> qd -> (a -> From out -> c) -> Query m a -> Query m c
qrelateOne !f !qd !f' !a = do
(!a, !e) <- a & qtraverse (\e a -> (a,) <$> f e)
case e of
Nothing -> EmptyQuery
Just !e -> do
!out <- qget e qd
pure $ f' a (From e out)
-- | Same as 'qrelateOne' but takes a traversal that returns a list of entities.
qrelateMany :: (MonadSystem w m, Queryable (E, qd) (Entity, out)) => (Entity -> m [Entity]) -> qd -> (a -> [From out] -> c) -> Query m a -> Query m c
qrelateMany !f !qd !f' !a = do
((_, !a), !e) <- qentity a & qtraverse (\(!e) !a -> (a,) <$> f e)
case e of
[] -> EmptyQuery
e -> do
!as <-
qgetAll e (E, qd)
& qmap (uncurry From)
& qcollect
& qmap (map (.comp))
pure $ f' a as
qres :: forall c m w. (MonadSystem w m, Component c) => Query m c
qres = (\(Res c) -> c) <$> qget (Entity (# 0##, 0## #)) (Res @c)
qget :: (MonadSystem w m, Queryable qd out) => Entity -> qd -> Query m out
qget !entity !qd = BuildQuery qd NoFilter (Just [entity])
qgetAll :: (MonadSystem w m, Queryable qd out) => [Entity] -> qd -> Query m out
qgetAll !entity !qd = BuildQuery qd NoFilter (Just entity)
qmaybe :: (MonadSystem w m) => Maybe a -> Query m a
qmaybe = qmapMaybe id . pure
qrefocus :: Entity -> Query m a -> Query m a
qrefocus = flip RefocusQuery
qpure :: (MonadSystem w m) => Entity -> Query m ()
qpure !e = qrefocus e $ pure ()
qstopIf :: (MonadSystem w m) => Bool -> Query m ()
qstopIf b = Control.Monad.when b qstop
qstop :: (MonadSystem w m) => Query m a
qstop = StopQuery
qiter :: (MonadSystem w m) => [From a] -> Query m a
qiter = IterQuery
qiterE :: (MonadSystem w m) => [Entity] -> Query m Entity
qiterE l = qiter (map (\x -> From x x) l)
-- | Pairs the query's elements with their entity.
qentity :: (MonadSystem w m) => Query m a -> Query m (Entity, a)
qentity = PairEntityQuery
qjoin :: (MonadSystem w m) => (a -> Query m b) -> (a -> [From b] -> c) -> Query m a -> Query m c
qjoin !f !f' !a = do
(_, !a) <- qentity a
f a & qcollect & qfilter (not . null) & qmap (f' a)
-- | Logs an INFO message.
qinfo :: (HasCallStack, MonadSystem w m) => (a -> Text) -> Query m a -> Query m a
qinfo !f !a = withFrozenCallStack $ qtap (\_ !x -> info (f x)) a
-- | Logs a WARNING message.
qwarn :: (HasCallStack, MonadSystem w m) => (a -> Text) -> Query m a -> Query m a
qwarn !f !a = withFrozenCallStack $ qtap (\_ !x -> warn (f x)) a
-- | Logs an ERROR message.
qerr :: (HasCallStack, MonadSystem w m) => (a -> Text) -> Query m a -> Query m a
qerr !f !a = withFrozenCallStack $ qtap (\_ !x -> err (f x)) a
qdespawn :: Query System a -> Query System a
qdespawn a = do
void $ qtap (\(!e) _ -> despawn e) a
qstop
qpar :: (MonadSystem w m) => Query ParSystem a -> Query m a
qpar = ParQuery