packages feed

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

module Mischief.ECS.World.Spawn where

import Control.Monad
import Control.Monad.IO.Class
import Data.Foldable
import Data.IORef
import Data.Maybe
import Data.Text qualified as T
import GHC.Base (Int (..))
import GHC.Stack
import Mischief.ECS.Archetypes.Graph (getArchetypeOnSpawn)
import Mischief.ECS.Components
import Mischief.ECS.Components.Bundle
import Mischief.ECS.Components.Common
import Mischief.ECS.Components.Spawn
import Mischief.ECS.Entities
import Mischief.ECS.Log
import Mischief.ECS.Tables
import Mischief.ECS.Vec qualified as Vec
import Mischief.ECS.World
import Mischief.ECS.World.Change
import Mischief.ECS.World.Defer
import Mischief.ECS.World.Insert
import Mischief.ECS.World.Remove
import Mischief.ECS.World.Utils

-- | Spawn an entity given a bundle of components.
spawn :: (HasCallStack, Bundle b) => b -> System Entity
spawn !bundle =
  do
    !world <- unsafeGetWorld
    !entity <- liftIO $ getNewEntity world.entities

    spawnEntity entity bundle
    pure entity

-- | Same as 'spawn' but discards the returned entity.
spawn_ :: (HasCallStack, Bundle b) => b -> System ()
spawn_ = void . spawn

-- | Spawn an entity given a bundle of components, inside a @ParSystem@.
--
-- This will immediately reserve and return an Entity index which can be used, while defering
-- the actual spawn.
spawnDefer :: (Bundle b) => b -> ParSystem Entity
spawnDefer !bundle = do
  !world <- unsafeGetWorld
  !entity <- liftIO $ getNewEntity world.entities

  defer $ spawnEntity entity bundle
  pure entity

data SpawnEventsSettings = WithSpawnEvents | WithoutSpawnEvents

-- | Spawn an Entity given an existing, reserved id. This is not meant for general use.
spawnEntity :: (HasCallStack, Bundle b) => Entity -> b -> System ()
spawnEntity !entity !bundle = do
  !world <- unsafeGetWorld
  let BundleData {elements = !elements, resources = !resources, external = !external} = addComponentToBundleData (Name (show entity)) $ bundleData bundle

  for_ external $ \(!e, !s) -> do
    insert s e

  for_ resources $ \BundleElement {component = ErasedComponent (!val :: c)} -> do
    !m <- meta @c
    insert val m

  !currentTick <- liftIO $ readIORef world.tick
  !bundleD <- liftIO $ processBundleElements world elements

  !archetype <- getArchetypeOnSpawn $ map (.id) bundleD.elements

  !entityPointer <- liftIO $ newIORef $ EntityPointer (# 0#, 0# #)

  liftIO $ insertEntityIntoTables (ProcessedBundleData {elements = []}) currentTick [] world.tables (ArchetypeId 0) (entity, entityPointer)

  liftIO $ insertPointer entity entityPointer world.entities

  ChangeResult {requiredComponentsAdded = !requiredComponentsAdded} <- changeArchetype entity archetype (Just bundleD)

  !supressEvents <- fromMaybe False <$> getPref @SupressEventsPref

  unless supressEvents $ do
    let !d = ProcessedBundleData $ requiredComponentsAdded ++ bundleD.elements
    triggerAddEvent d entity
    triggerSetEvent d entity

-- Spawn an entity as having no components and then immediately insert a bundle on it. Useful for certain engine internals, should be avoided.
spawnEntityByInsert :: (Bundle b) => Entity -> b -> System ()
spawnEntityByInsert !entity !bundle = do
  !world <- unsafeGetWorld

  !entityPointer <- liftIO $ newIORef $ EntityPointer (# 0#, 0# #)

  !currentTick <- liftIO $ readIORef world.tick
  liftIO $ insertEntityIntoTables (ProcessedBundleData {elements = []}) currentTick [] world.tables (ArchetypeId 0) (entity, entityPointer)
  liftIO $ insertPointer entity entityPointer world.entities
  insert bundle entity

  insertNew (Name (show entity)) entity

-- | Spawn an entity given a bundle of components, in IO.
spawnIO :: (Bundle b) => World -> b -> IO Entity
spawnIO !world !bundle =
  do
    !entity <- liftIO $ getNewEntity world.entities

    runSystem (spawnEntity entity bundle) world
    pure entity

-- | Despawn an entity. This will trigger the @OnRemove@ events and hooks on all its components.
despawn :: Entity -> System ()
despawn !entity =
  do
    !world <- unsafeGetWorld
    !pointer <- liftIO $ getPointer entity world.entities
    case pointer of
      Nothing -> warn $ "Despawn failed: Entity " <> T.show entity <> " is not alive."
      Just !pointer -> do
        let Tables !tables = world.tables

        (EntityPointer (# !archetypeId, _ #)) <- liftIO $ readIORef pointer

        !table <- Vec.read tables (I# archetypeId)

        !c <- liftIO $ collectComponentIdsFromTable table
        triggerRemoveEvent c entity

        (EntityPointer (# !newArchetypeId, !newRowIndex #)) <- liftIO $ readIORef pointer

        !table <- Vec.read tables (I# newArchetypeId)
        void $ liftIO $ takeComponentsFromTable (EntityPointer (# newArchetypeId, newRowIndex #)) table
        liftIO $ removeEntity entity world.entities