packages feed

mischief-ecs-0.1.0.0: src/Mischief/ECS/App/Systems.hs

{-# LANGUAGE AllowAmbiguousTypes #-}

module Mischief.ECS.App.Systems where

import Control.Monad.IO.Class
import Control.Monad.Reader
import Data.Default
import Data.Foldable
import Data.IORef
import Data.Map (Map)
import Data.Map qualified as Map
import GHC.Generics
import GHC.StableName (StableName, eqStableName, hashStableName, makeStableName)
import GHC.Stack.Types
import Mischief.ECS.App.Schedules
import Mischief.ECS.App.SystemDef
import Mischief.ECS.Components
import Mischief.ECS.Components.Bundle
import Mischief.ECS.Components.Required
import Mischief.ECS.Entities
import Mischief.ECS.Log
import Mischief.ECS.Relationships.Order
import Mischief.ECS.Resources
import Mischief.ECS.Tables
import Mischief.ECS.Utils
import Mischief.ECS.World
import Mischief.ECS.World.Insert
import Mischief.ECS.World.Query
import Mischief.ECS.World.Query.Markers
import Mischief.ECS.World.Query.Queryable
import Mischief.ECS.World.Spawn

newtype Systems = Systems
  { systemMap :: IORef (Map (ScheduleId, Int) (IORef [(StableName (System ()), SystemId)]))
  }
  deriving anyclass (Component)

newtype SystemFunction = SystemFunction {inner :: System ()}

instance Component SystemFunction where
  required = require @(SystemTick, LastSystemTick)

newSystems :: IO Systems
newSystems = do
  systemMap <- newIORef Map.empty
  return Systems {systemMap}

getSystemId' :: (HasCallStack) => ScheduleId -> System () -> StableName (System ()) -> IORef [(StableName (System ()), SystemId)] -> System SystemId
getSystemId' schedule system stableName list = do
  list' <- liftIO $ readIORef list
  case find (\x -> fst x `eqStableName` stableName) list' of
    Just (_, x) -> return x
    Nothing -> do
      index <- spawn (SystemFunction system, Rel ScheduledIn schedule.id)
      liftIO $ modifyIORef' list (++ [(stableName, SystemId index)])
      return $ SystemId index

getSystemId :: (HasCallStack) => ScheduleId -> System () -> System SystemId
getSystemId sch system = do
  Systems {systemMap} <- unwrap <$> (res @Systems)

  stableName <- liftIO $ makeStableName system
  systemMap' <- liftIO $ readIORef systemMap

  case Map.lookup (sch, hashStableName stableName) systemMap' of
    Just x -> getSystemId' sch system stableName x
    Nothing -> do
      l <- liftIO $ newIORef []
      liftIO $ modifyIORef' systemMap (Map.insert (sch, hashStableName stableName) l)
      getSystemId' sch system stableName l

removeSystemFromMap' :: StableName (System ()) -> IORef [(StableName (System ()), SystemId)] -> System ()
removeSystemFromMap' stableName list = do
  liftIO $ modifyIORef' list $ filter (\x -> not $ fst x `eqStableName` stableName)

removeSystemFromMap :: ScheduleId -> System () -> System ()
removeSystemFromMap sch system = do
  Systems {systemMap} <- unwrap <$> (res @Systems)

  stableName <- liftIO $ makeStableName system
  systemMap' <- liftIO $ readIORef systemMap

  forM_
    (Map.lookup (sch, hashStableName stableName) systemMap')
    (removeSystemFromMap' stableName)

systemEntity :: (HasCallStack, Schedule sch) => sch -> System () -> System Entity
systemEntity sch s = do
  schId <- scheduleEntity sch
  x <- getSystemId (ScheduleId schId) s
  return x.id

self :: forall m w. (MonadSystem w m) => m Entity
self = do
  world <- unsafeGetWorld
  let (SystemId sys) = world.systemId
  return sys

data ScheduledIn = ScheduledIn deriving (Component)

getSystemTicks :: World -> IO (Tick, Tick)
getSystemTicks world = do
  let (SystemId sys) = world.systemId
  runSystem
    ( do
        Just (a, b) <- get (C @LastSystemTick, C @SystemTick) sys
        return (a.inner, b.inner)
    )
    world