packages feed

aztecs-0.15.0: src/Aztecs/ECS/Query/Dynamic/Reader.hs

{-# 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 (..),
    DynamicQueryReaderF (..),

    -- ** Running
    allDyn,
    filterDyn,
    singleDyn,
    singleMaybeDyn,

    -- * 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 qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Vector (Vector)
import qualified Data.Vector as V
import GHC.Stack

-- | Dynamic query reader.
--
-- @since 0.10
newtype DynamicQueryReader a = DynamicQueryReader
  { -- | Run a dynamic query reader.
    --
    -- @since 0.10
    runDynQueryReader :: Archetype -> Vector a
  }
  deriving (Functor)

-- | @since 0.10
instance Applicative DynamicQueryReader where
  pure a = DynamicQueryReader $ \arch -> V.replicate (length $ A.entities arch) a
  {-# INLINE pure #-}

  f <*> g = DynamicQueryReader $ \arch ->
    V.zipWith ($) (runDynQueryReader f arch) $ runDynQueryReader g arch
  {-# INLINE (<*>) #-}

-- | @since 0.10
instance DynamicQueryReaderF DynamicQueryReader where
  entity = DynamicQueryReader $ \arch -> V.fromList . Set.toList $ A.entities arch
  {-# INLINE entity #-}

  fetchDyn cId = DynamicQueryReader $ \arch -> A.lookupComponentsAsc cId arch
  {-# INLINE fetchDyn #-}

  fetchMaybeDyn cId = DynamicQueryReader $ \arch -> case A.lookupComponentsAscMaybe cId arch of
    Just as -> V.map Just as
    Nothing -> V.replicate (length $ A.entities arch) Nothing
  {-# INLINE fetchMaybeDyn #-}

-- | Dynamic query for components by ID.
--
-- @since 0.9

-- | 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

-- | Match all entities.
--
-- @since 0.10
allDyn :: Set ComponentID -> DynamicQueryReader a -> Entities -> Vector a
allDyn cIds q es =
  if Set.null cIds
    then runDynQueryReader q A.empty {A.entities = Map.keysSet $ entities es}
    else
      let go n = runDynQueryReader q $ AS.nodeArchetype n
       in V.concat . fmap go . Map.elems $ AS.find cIds $ archetypes es

-- | Match all entities with a filter.
--
-- @since 0.10
filterDyn :: Set ComponentID -> (Node -> Bool) -> DynamicQueryReader a -> Entities -> Vector a
filterDyn cIds f q es =
  if Set.null cIds
    then runDynQueryReader q A.empty {A.entities = Map.keysSet $ entities es}
    else
      let go n = runDynQueryReader q $ AS.nodeArchetype n
       in V.concat . fmap go . Map.elems . Map.filter f $ AS.find cIds $ archetypes es

-- | Match a single entity.
--
-- @since 0.10
singleDyn :: (HasCallStack) => Set ComponentID -> DynamicQueryReader a -> Entities -> a
singleDyn cIds q es = case singleMaybeDyn cIds q es of
  Just a -> a
  _ -> error "singleDyn: expected a single entity"

-- | Match a single entity, or `Nothing`.
--
-- @since 0.10
singleMaybeDyn :: Set ComponentID -> DynamicQueryReader a -> Entities -> Maybe a
singleMaybeDyn cIds q es =
  if Set.null cIds
    then case Map.keys $ entities es of
      [eId] -> case runDynQueryReader q $ A.singleton eId of
        v | V.length v == 1 -> Just (V.head v)
        _ -> Nothing
      _ -> Nothing
    else case Map.elems $ AS.find cIds $ archetypes es of
      [n] -> case runDynQueryReader q $ AS.nodeArchetype n of
        v | V.length v == 1 -> Just (V.head v)
        _ -> Nothing
      _ -> Nothing