packages feed

aztecs-0.16.0: src/Aztecs/ECS/World/Observers.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- |
-- Module      : Aztecs.ECS.World.Observers
-- 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.World.Observers
  ( ObserverID (..),
    Observers (..),
    EntityObservers (..),
    DynamicObserver (..),
    empty,
    insertEntityObserver,
    insertEventObserver,
    addEntityObserver,
    addGlobalObserver,
    lookupEntityObservers,
    lookupGlobalObservers,
    lookupCallback,
    removeObserver,
  )
where

import Aztecs.ECS.Entity
import Aztecs.ECS.World.Observers.Internal
import Data.Dynamic
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import Data.Typeable

-- | Empty `Observers`.
empty :: Observers m
empty =
  Observers
    { entityObservers' = mempty,
      globalObservers = mempty,
      observerCallbacks = mempty,
      nextObserverId = ObserverID 0
    }

-- | Insert an entity observer callback.
insertEntityObserver ::
  forall e m.
  (Typeable e, Monad m) =>
  (EntityID -> e -> m ()) ->
  Observers m ->
  (ObserverID, Observers m)
insertEntityObserver callback os =
  let !oId = nextObserverId os
      dynCallback eId dyn = case fromDynamic dyn of
        Just evt -> callback eId evt
        Nothing -> pure ()
   in ( oId,
        os
          { observerCallbacks = Map.insert oId (DynEntityObserver dynCallback) (observerCallbacks os),
            nextObserverId = ObserverID (unObserverId oId + 1)
          }
      )

-- | Insert an event observer callback.
insertEventObserver ::
  forall e m.
  (Typeable e, Monad m) =>
  (e -> m ()) ->
  Observers m ->
  (ObserverID, Observers m)
insertEventObserver callback os =
  let !oId = nextObserverId os
      dynCallback dyn = case fromDynamic dyn of
        Just evt -> callback evt
        Nothing -> pure ()
   in ( oId,
        os
          { observerCallbacks = Map.insert oId (DynEventObserver dynCallback) (observerCallbacks os),
            nextObserverId = ObserverID (unObserverId oId + 1)
          }
      )

-- | Add an observer to a specific entity for a given event type.
addEntityObserver :: forall m e. (Typeable e) => EntityID -> ObserverID -> Observers m -> Observers m
addEntityObserver e oId os =
  let eventTypeRep = typeOf (Proxy @e)
      updateEntityObs Nothing =
        Just $ EntityObservers {eventObservers = Map.singleton eventTypeRep (Set.singleton oId)}
      updateEntityObs (Just eo) =
        Just $ eo {eventObservers = Map.insertWith Set.union eventTypeRep (Set.singleton oId) (eventObservers eo)}
   in os {entityObservers' = Map.alter updateEntityObs e (entityObservers' os)}

-- | Add a global observer for a given event type.
addGlobalObserver :: forall m e. (Typeable e) => ObserverID -> Observers m -> Observers m
addGlobalObserver oId os =
  let eventTypeRep = typeOf (Proxy @e)
   in os {globalObservers = Map.insertWith Set.union eventTypeRep (Set.singleton oId) (globalObservers os)}

-- | Lookup all observer IDs for an entity and event type.
lookupEntityObservers :: TypeRep -> EntityID -> Observers m -> Set.Set ObserverID
lookupEntityObservers eventTypeRep e os =
  case Map.lookup e (entityObservers' os) of
    Just eo -> Map.findWithDefault Set.empty eventTypeRep (eventObservers eo)
    Nothing -> Set.empty

-- | Lookup all global observer IDs for an event type.
lookupGlobalObservers :: TypeRep -> Observers m -> Set.Set ObserverID
lookupGlobalObservers eventTypeRep os = Map.findWithDefault Set.empty eventTypeRep (globalObservers os)

-- | Lookup an observer callback by ID.
lookupCallback :: ObserverID -> Observers m -> Maybe (DynamicObserver m)
lookupCallback oId os = Map.lookup oId (observerCallbacks os)

-- | Remove an observer by ID.
removeObserver :: ObserverID -> Observers m -> Observers m
removeObserver oId os =
  os
    { observerCallbacks = Map.delete oId (observerCallbacks os),
      entityObservers' = Map.map removeFromEntity (entityObservers' os),
      globalObservers = Map.map (Set.delete oId) (globalObservers os)
    }
  where
    removeFromEntity eo = eo {eventObservers = Map.map (Set.delete oId) (eventObservers eo)}