packages feed

aztecs-0.9.1: src/Aztecs/Asset/AssetLoader.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE Arrows #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- |
-- Module      : Aztecs.Asset.AssetLoader
-- 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.Asset.AssetLoader
  ( AssetLoader,
    AssetLoaderT (..),
    MonadAssetLoader (..),
    load,
    loadQuery,
  )
where

import Aztecs.Asset.AssetLoader.Class
import Aztecs.Asset.AssetServer
import Aztecs.Asset.Class
import Aztecs.ECS
import qualified Aztecs.ECS.Query as Q
import qualified Aztecs.ECS.System as S
import Control.Arrow
import Control.Concurrent
import Control.Monad.Identity
import Control.Monad.State.Strict
import Data.IORef
import qualified Data.Map.Strict as Map

-- | @since 0.9
type AssetLoader a o = AssetLoaderT a Identity o

-- | Asset loader monad.
--
-- @since 0.9
newtype AssetLoaderT a m o = AssetLoaderT
  { -- | State of the asset loader.
    --
    -- @since 0.9
    unAssetLoader :: StateT (AssetServer a) m o
  }
  deriving newtype (Functor, Applicative, Monad)

-- | @since 0.9
instance (Monad m, Asset a) => MonadAssetLoader a (AssetLoaderT a m) where
  asset path cfg = AssetLoaderT $ do
    server <- get
    let assetId = nextAssetId server
        go = do
          v <- newIORef Nothing
          _ <- forkIO $ do
            a <- loadAsset path cfg
            writeIORef v (Just a)
          return v
    put $
      server
        { loadingAssets = Map.insert assetId (Left go) (loadingAssets server),
          nextAssetId = AssetId (unAssetId assetId + 1)
        }
    return $ Handle assetId

-- | Query to load assets.
--
-- @since 0.9
loadQuery :: (Asset a, ArrowQuery m arr) => AssetLoader a o -> arr () o
loadQuery a = proc () -> do
  server <- Q.fetch -< ()
  let (o, server') = runState (unAssetLoader a) server
  Q.set -< server'
  returnA -< o

-- | System to load assets.
--
-- @since 0.9
load :: forall m q s a o. (ArrowQuery m q, MonadSystem q s, Asset a) => AssetLoader a o -> s o
load a = S.mapSingle @q () $ loadQuery a