packages feed

aztecs-0.17.1: src/Aztecs/ECS/Observer.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}

-- |
-- Module      : Aztecs.ECS.Observer
-- 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.Observer
  ( Observer (..),
    ObserverID (..),
    ObserverKind (..),
    observer,
    observerFor,
    observerGlobal,
  )
where

import Aztecs.ECS.Access.Internal
import Aztecs.ECS.Component
import Aztecs.ECS.Entity
import Aztecs.ECS.Event
import qualified Aztecs.ECS.World as W
import Aztecs.ECS.World.Bundle
import Aztecs.ECS.World.Internal (World (..))
import qualified Aztecs.ECS.World.Observers as O
import Aztecs.ECS.World.Observers.Internal
import Control.Monad.State
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Typeable
import Data.Vector (Vector)

-- | The kind of observer - either entity-specific or global.
data ObserverKind m e
  = -- | Observe events on specific entities (callback receives EntityID and event).
    EntityObserver !(Set EntityID) !(EntityID -> e -> Access m ())
  | -- | Observe all events of this type globally (callback receives just the event).
    EventObserver !(e -> Access m ())

-- | Observer component
data Observer m e = Observer
  { -- | The kind and callback for this observer.
    observerKind :: !(ObserverKind m e),
    -- | The ObserverID assigned after registration (Nothing before registration).
    observerId :: !(Maybe ObserverID)
  }

instance Show (ObserverKind m e) where
  show (EntityObserver targets _) = "EntityObserver " ++ show targets
  show (EventObserver _) = "EventObserver"

instance Show (Observer m e) where
  show o = "Observer { kind = " ++ show (observerKind o) ++ ", id = " ++ show (observerId o) ++ " }"

instance (Monad m, Typeable m, Event e) => Component m (Observer m e) where
  type StorageT (Observer m e) = Vector (Observer m e)

  componentOnInsert ownerEntity o = Access $ do
    !w <- get
    (oId, observers') <- case observerKind o of
      EntityObserver targets f -> do
        let f' eId evt = unAccess $ f eId evt
            (oId, observers') = O.insertEntityObserver @e f' $ observers w
            observers'' = foldr (\e os -> O.addEntityObserver @_ @e e oId os) observers' targets
        return (oId, observers'')
      EventObserver callback -> do
        let wrappedCallback evt = unAccess $ callback evt
            (oId, observers') = O.insertEventObserver @e wrappedCallback (observers w)
            observers'' = O.addGlobalObserver @_ @e oId observers'
        return (oId, observers'')
    let o' = o {observerId = Just oId}
        w' = W.insertUntracked ownerEntity (bundleUntracked o') w {observers = observers'}
    put w'

  componentOnRemove _ownerEntity o = Access $ case observerId o of
    Just oId -> do
      !w <- get
      put w {observers = O.removeObserver oId (observers w)}
    Nothing -> pure ()

-- | Create an observer for specific entities.
observerFor :: forall m e. (Event e) => Set EntityID -> (EntityID -> e -> Access m ()) -> Observer m e
observerFor targets callback = Observer {observerKind = EntityObserver targets callback, observerId = Nothing}

-- | Create a global observer (observes all events of this type).
observerGlobal :: forall m e. (Event e) => (e -> Access m ()) -> Observer m e
observerGlobal callback = Observer {observerKind = EventObserver callback, observerId = Nothing}

-- | Create an observer for a single entity.
observer :: forall m e. (Event e) => EntityID -> (EntityID -> e -> Access m ()) -> Observer m e
observer e = observerFor (Set.singleton e)