packages feed

ohhecs-0.0.2: src/Games/ECS/World.hs

{-# LANGUAGE MagicHash #-}
{-# LANGUAGE Trustworthy #-}

-- |
-- Module      :  Games.ECS.World
-- Description : World definitions
-- Copyright   :  (C) 2020 Sophie Taylor
-- License     :  AGPL-3.0-or-later
-- Maintainer  :  Sophie Taylor <sophie@spacekitteh.moe>
-- Stability   :  experimental
-- Portability: GHC
--
-- Infrastructure for defining ECS worlds.
module Games.ECS.World
  ( Access (..),
    Props (..),
    World (..),
    OpticsFor,
    EntRefStoringType,
    EntRefField,
    AnAffineTraversal,
    AnAffineTraversal',
    AffineTraversal,
    AffineTraversal',
    affine,
    affine',
    newUniqueEntRef,
  )
where

import Control.Lens
import Control.Monad.IO.Class
import Data.IORef
import Data.IntSet qualified as IS
import Data.Kind
import GHC.Base
import GHC.Num
import Games.ECS.Entity
import Games.ECS.Prototype.PrototypeID    
import Games.ECS.Slot
import System.IO.Unsafe (unsafePerformIO)

-- | HKD parameterisation for an ECS.
data Access
  = -- | We are dealing with the entire collection of  entities in a world, represented structure-of-array style.
    Storing
  | -- | We are dealing with a specific individual with specific component values.
    Individual

-- | Different arities.
data Props
  = -- | An individual may or may not have this component.
    Normal
  | -- | Every individual must have this component.
    Required
  | -- | Either a single individual, or none, may have this component.
    Unique

-- | For entity unique values. We don't want to use Data.Unique, because we would like to be able to set the
-- seed upon loading a save game.
entUniqueSource :: IORef Integer
entUniqueSource = unsafePerformIO (newIORef 0)
{-# NOINLINE entUniqueSource #-}

-- | Atomically construct a new entity reference.
newUniqueEntRef :: IO Entity
newUniqueEntRef = do
  r <- atomicModifyIORef' entUniqueSource $ \x -> let z = x + 1 in (z, z)
  pure (EntRef (integerToInt r))
{-# NOINLINE newUniqueEntRef #-}

-- | An entity component system, parameterised by its access type.
class World (w :: Access -> Type) where
  -- | Construct a new world.
  newWorld :: w Storing

  -- | Create a new entity
  createNewEntity :: (MonadIO m) => m (w Individual)
  {-# INLINE createNewEntity #-}
  createNewEntity = do
    seed <- liftIO newUniqueEntRef
    pure (createNewEntityWithRef seed)

  -- | Create a new entity with a given reference.
  createNewEntityWithRef :: Entity -> w Individual

  -- | Traversal over all entities in the ECS.
  entities :: IndexedTraversal' Entity (w Storing) (w Individual)
  {-# INLINE entities #-}
  default entities :: (HasType (EntRefStoringType) (w Storing)) => IndexedTraversal' Entity (w Storing) (w Individual)
  entities p world = lookupEntities ((world ^.. typed @(EntRefField Storing) . knownEntities)) p world

  -- | Get the entity reference of an individual
  entityReference :: IndexedGetter Entity (w Individual) Entity

  -- | Get and set the entity reference of an individual
  unsafeEntityReference :: Lens' (w Individual) Entity

  -- | Get all of the entity references stored in the world.
  entityReferences :: IndexedFold Entity (w Storing) Entity

  -- | Check if a given entity exists in the world, and if so, return the individual.
  lookupEntity :: w Storing -> Entity -> Maybe (w Individual)

  -- | Get a prototype specification from its name.
  prototype :: HasPrototypeID p => p -> AffineTraversal' (w Storing) (w Individual)
                  
  -- | An IndexedTraversal' which returns the individuals associated to the entities given as input.
  {-# INLINE lookupEntities #-}
  lookupEntities :: forall f p fol. (Indexable Entity p, Applicative f, Foldable fol) => fol Entity -> p (w Individual) (f (w Individual)) -> w Storing -> f (w Storing) -- Foldable f => f Entity -> IndexedTraversal' Entity (w Storing) (w Individual)
  lookupEntities list = entities . {-indices-} filtered (\ent -> elemOf folded (ent ^. entityReference) list) -- TODO optimise this fucking thing

  -- | An IndexedTraversal' of individuals matching some constraints. The constraints are included monoidally.
  entitiesWith :: forall f p. (Indexable Entity p, Applicative f) => (forall r. (Monoid r) => Getting r (w Storing) IntersectionOfEntities) -> p (w Individual) (f (w Individual)) -> w Storing -> f (w Storing) -- IndexedTraversal' Entity (w Storing) (w Individual)
  {-# INLINE entitiesWith #-}
  entitiesWith withComponents p world = lookupEntities ((world ^. withComponents) ^.. knownEntities) p world

  -- | Store an individual in a world, returning the new world.
  storeEntity :: w Individual -> w Storing -> w Storing

  -- | Affine traversal for a specified individual from the world.
  entity :: Entity -> AffineTraversal' (w Storing) (w Individual)
  entity ent = affine' (`lookupEntity` ent) (flip storeEntity)
  {-# INLINE entity #-}

-- TODO make this EntitySet. Just need to implement Ixed/At for it, and change IS.member in World/TH.hs.

-- | A type which holds a collection of 'Entity'.
type EntRefStoringType = IS.IntSet

-- Should this go in Component.hs? Perhaps a typeclass "Using", which converts an (affine) traversal into
-- a lens, given evidence from a function like `filtered (has position)`? either a unique typeclass will
-- have to be generated for each named component, or parameterised by the name itself. the former has the
-- advantage of robustness to renaming, but the latter would allow a nice `using @"position"` syntax.
-- perhaps it could inject a (gdp-style) Fact into the context? that way, the accessor can have a
-- requirement on its optic, and might even be able to specify logical dependencies (e.g. a movable object
-- must have a position; an inventory item must have a mass, etc) with :: (CompTypeClass name a) =>
-- IndexedTraversal' Entity (w Individual) (w Individual)

-- | A type function for simplifying the higher-kinded data implementation.
type family EntRefField (acc :: Access) :: Type where
  EntRefField Individual = Entity
  EntRefField Storing = EntRefStoringType

-- TODO Add a global storage type family

-- type family GlobalStorage (name :: Symbol) :: Type

-- | We want to make sure that the API is consistent based on the access type and availability property, so we
-- have a type family to give us the correct optics.
type family OpticsFor (name :: Symbol) (hkd :: Access -> Type) (acc :: Access) (p :: Props) (a :: Type) :: Type where
  OpticsFor name hkd Individual Required a =
    ReifiedIndexedLens' Entity (hkd Individual) a
  OpticsFor name hkd Individual Normal a =
    AnAffineTraversal' (hkd Individual) a
  OpticsFor name hkd Individual Unique a = AnAffineTraversal' (hkd Individual) a
  OpticsFor name hkd Storing Unique a = ReifiedIndexedTraversal' Entity (hkd Storing) a
  OpticsFor name hkd Storing Normal a = ReifiedIndexedTraversal' Entity (hkd Storing) a
  OpticsFor name hkd Storing Required a = ReifiedIndexedTraversal' Entity (hkd Storing) a

-- class ((CompTypeClassFun name a) ~ a, (CompTypeClassFun name a) ~ (CompTypeFun name a)) => CompTypeClass (name :: Symbol) (a :: Type) where
--   type CompTypeClassFun (name :: Symbol) (a :: Type) :: Type

-- instance ((CompTypeClassFun name a) ~ a, (CompTypeClassFun name a) ~ (CompTypeFun name a)) => CompTypeClass name a where
--   type CompTypeClassFun name a = a

-- type family CompTypeFun (name :: Symbol) (a :: Type) :: Type where
--   CompTypeFun name a = a

-- | A reified 'AffineTraversal'.
type AnAffineTraversal s t a b = ReifiedIndexedTraversal Entity s t a b

-- | A reified `AffineTraversal'`.
type AnAffineTraversal' s a = AnAffineTraversal s s a a

-- | An 'AffineTraversal' is one which traverses either 0 or 1 elements.
type AffineTraversal s t a b = Traversal s t a b

-- | Simplified 'AffineTraversal'.
type AffineTraversal' s a = AffineTraversal s s a a

-- | Construct an `AffineTraversal`.
{-# INLINE affine #-}
affine :: (s -> Either t a) -> (s -> b -> t) -> Traversal s t a b
affine getter setter f s = case getter s of
  Left t -> pure t
  Right a -> (\b -> setter s b) <$> f a

-- | Construct an `AffineTraversal'`.
{-# INLINE affine' #-}
affine' :: (s -> Maybe a) -> (s -> a -> s) -> Traversal' s a
affine' getter setter f s = case getter s of
  Nothing -> pure s
  Just a -> (\b -> setter s b) <$> f a