packages feed

mischief-ecs-0.2.0.0: src/Mischief/ECS/World/Utils.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

module Mischief.ECS.World.Utils where

import Control.Monad.IO.Class (MonadIO (liftIO))
import Data.Bifunctor qualified
import Data.IORef
import Data.Proxy
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Typeable
import Mischief.ECS.Components
import Mischief.ECS.Components.Bundle
import Mischief.ECS.Components.Spawn
import Mischief.ECS.Entities
import Mischief.ECS.Tables
import Mischief.ECS.World

-- | Process a 'BundleElement', turning its 'TypeRep' into a 'ComponentId'.
processBundleElement :: World -> BundleElement ErasedComponent -> IO ProcessedBundleElement
processBundleElement !world BundleElement {rep = (ComponentRep !r), component = !component} =
  do
    !id <- runSystem (getOrAddComponentId r) world
    pure
      ProcessedBundleElement
        { id,
          component
        }
processBundleElement !world BundleElement {rep = (PairRep (!r, !entity)), component = !component} =
  do
    !id <- runSystem (getOrAddPairId (Pair (r, entity))) world
    pure
      ProcessedBundleElement
        { id,
          component
        }

-- | Process a set of 'BundleElement's into a 'ProcessedBundleData'.
processBundleElements :: World -> Set (BundleElement ErasedComponent) -> IO ProcessedBundleData
processBundleElements !world !elements =
  do
    !elements <- mapM (processBundleElement world) (Set.toList elements)
    pure
      ProcessedBundleData {elements}

-- | Combine two 'ProcessedBundleData's, merging their sets of elements.
combineProcessedBundles :: ProcessedBundleData -> ProcessedBundleData -> ProcessedBundleData
combineProcessedBundles !bundle1 !bundle2 =
  let !elements = Set.toList $ Set.union (Set.fromList bundle1.elements) (Set.fromList bundle2.elements)
   in ProcessedBundleData {elements}

-- | Check if a 'ComponentId' is inside a 'ProcessedBundleData'.
isInProcessedBundle :: ProcessedBundleData -> ComponentId -> Bool
isInProcessedBundle ProcessedBundleData {elements = !elements} !id = id `elem` map (.id) elements

-- | Sets the change tick of certain elements of the bundle to the specified 'Tick'.
-- setChangedTickOfComponents :: ProcessedBundleData -> (ComponentId -> Bool) -> Tick -> ProcessedBundleData
-- setChangedTickOfComponents ProcessedBundleData {elements = !elements} !shouldChange !tick =
--   ProcessedBundleData
--     { elements =
--         map
--           ( \ProcessedBundleElement {id = !id, component = ComponentData {value = !value, ticks = ComponentTicks {added = !added, changed = !changed}}} ->
--               if shouldChange id
--                 then ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added, changed = tick}}}
--                 else ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added, changed}}}
--           )
--           elements
--     }

-- | Sets the added tick of certain elements of the bundle to the specified 'Tick'.
-- setAddedTickOfComponents :: ProcessedBundleData -> (ComponentId -> Bool) -> Tick -> ProcessedBundleData
-- setAddedTickOfComponents ProcessedBundleData {elements = !elements} !shouldChange !tick =
--   ProcessedBundleData
--     { elements =
--         map
--           ( \ProcessedBundleElement {id = !id, component = ComponentData {value = !value, ticks = ComponentTicks {added = !added, changed = !changed}}} ->
--               if shouldChange id
--                 then ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added = tick, changed}}}
--                 else ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added, changed}}}
--           )
--           elements
--     }
removeComponentFromProcessedBundle :: ComponentId -> ProcessedBundleData -> ProcessedBundleData
removeComponentFromProcessedBundle !componentId !bundle =
  do
    let elements = filter ((/= componentId) . (.id)) bundle.elements
     in ProcessedBundleData {elements}

tryGetEntityRelCollection :: forall c. (Component c) => World -> Entity -> IO (Maybe (Maybe [Rel c]))
tryGetEntityRelCollection !world !entity =
  do
    !componentId <- getComponentId (typeRep $ Proxy @c) world.components
    case componentId of
      Nothing -> pure Nothing
      Just !componentId -> do
        !pointer <- getPointer entity world.entities
        case pointer of
          Nothing -> do
            pure Nothing
          Just !pointer ->
            do
              !pointer <- readIORef pointer
              !res <- tryGetRelCollectionFromTables world.tables entity pointer componentId
              pure $ Just $ fmap (map snd) res

tryGetEntityComponent :: forall c. (Component c) => World -> Entity -> IO (Maybe (Maybe c))
tryGetEntityComponent !world !entity =
  do
    !pointer <- getPointer entity world.entities

    case pointer of
      Nothing -> pure Nothing
      Just !pointer ->
        do
          !componentId <- getComponentId (typeRep $ Proxy @c) world.components
          case componentId of
            Nothing -> pure $ Just Nothing
            Just !componentId -> do
              !pointer <- readIORef pointer
              !res <- tryGetComponentFromTables world.tables pointer componentId
              pure $ Just res

tryGetEntityRel :: forall c. (Component c) => Entity -> World -> Entity -> IO (Maybe (Maybe c))
tryGetEntityRel !target !world !entity =
  do
    !pointer <- getPointer entity world.entities

    case pointer of
      Nothing -> pure Nothing
      Just !pointer ->
        do
          !componentId <- getComponentId (typeRep $ Proxy @c) world.components
          case componentId of
            Nothing -> return Nothing
            Just (ComponentId (# !id, _ #)) -> do
              !pointer <- readIORef pointer
              !res <- tryGetComponentFromTables world.tables pointer (ComponentId (# id, Just target #))
              pure $ Just res

tryGetRelCollections :: forall c. (Component c) => World -> [ArchetypeId] -> IO [(Entity, [Rel c])]
tryGetRelCollections !world !archetypes =
  do
    !componentId <- getComponentId (typeRep $ Proxy @c) world.components
    case componentId of
      Nothing -> pure []
      Just !componentId ->
        tryGetRelCollectionsFromTables world.tables archetypes componentId

tryGetComponents :: forall c. (Component c) => World -> [ArchetypeId] -> IO [(Entity, c)]
tryGetComponents !world !archetypes =
  do
    !componentId <- getComponentId (typeRep $ Proxy @c) world.components
    case componentId of
      Nothing -> pure []
      Just !componentId ->
        tryGetComponentsFromTables world.tables archetypes componentId

tryGetEntities :: World -> [ArchetypeId] -> IO [Entity]
tryGetEntities !world = tryGetEntitiesFromTables world.tables

tryGetRels :: forall c. (Component c) => Entity -> World -> [ArchetypeId] -> IO [(Entity, Rel c)]
tryGetRels !target !world !archetypes =
  do
    !componentId <- getComponentId (typeRep $ Proxy @c) world.components
    case componentId of
      Nothing -> pure []
      Just !componentId -> do
        !res <- tryGetComponentsFromTables world.tables archetypes (setCompIdTarget (Just target) componentId) -- {entity = Just target}
        pure $ map (\(!e, !res) -> (e, Rel res target)) res

tryGetRelsMaybe :: forall c. (Component c) => Entity -> World -> [ArchetypeId] -> IO [(Entity, Maybe (Rel c))]
tryGetRelsMaybe !target !world !archetypes =
  do
    !componentId <- getComponentId (typeRep $ Proxy @c) world.components
    case componentId of
      Nothing -> do
        !e <- tryGetEntities world archetypes
        pure $ map (,Nothing) e
      Just !componentId -> do
        !res <- tryGetComponentsFromTablesMaybe world.tables archetypes (setCompIdTarget (Just target) componentId) -- {entity = Just target}
        pure $
          map
            ( Data.Bifunctor.second
                (fmap (`Rel` target))
            )
            res

tryGetComponentsMaybe :: forall c. (Component c) => World -> [ArchetypeId] -> IO [(Entity, Maybe c)]
tryGetComponentsMaybe !world !archetypes =
  do
    !componentId <- getComponentId (typeRep $ Proxy @c) world.components
    case componentId of
      Nothing -> do
        !e <- tryGetEntities world archetypes
        pure $ map (,Nothing) e
      Just !componentId ->
        tryGetComponentsFromTablesMaybe world.tables archetypes componentId

tryGetTicks :: ComponentId -> World -> [ArchetypeId] -> IO [Maybe ComponentTick]
tryGetTicks !componentId !world !archetypes = tryGetTicksFromTables world.tables archetypes componentId

-- tryGetEntityTicks :: Entity -> ComponentId -> World -> IO (Maybe ComponentTicks)
-- tryGetEntityTicks !entity !componentId !world = do
--   !pointer <- getPointer entity world.entities
--   case pointer of
--     Nothing -> pure Nothing
--     Just !pointer -> do
--       !pointer <- readIORef pointer
--       tryGetEntityTicksFromTables world.tables pointer componentId

isAlive :: forall m w. (MonadSystem w m) => Entity -> m Bool
isAlive !entity = do
  !world <- unsafeGetWorld
  liftIO $ isAliveIO entity world.entities

newtype GetSystem = GetSystem (forall c. (Component c) => Proxy c -> Entity -> System (Maybe c))