mischief-assets-0.1.0.0: src/Mischief/Assets/Internal/Watcher.hs
module Mischief.Assets.Internal.Watcher where
import Control.Concurrent (forkIO, threadDelay)
import Control.Concurrent.STM (atomically, modifyTVar')
import Control.Monad (forever, void)
import Control.Monad.IO.Class
import Data.Data
import Data.Foldable
import Data.HashTable.IO qualified as H
import Data.Text qualified as T
import Mischief.Assets.Internal.Asset
import Mischief.Assets.Internal.Path
import Mischief.ECS
import Mischief.ECS.Utils (fromMaybeM)
import Mischief.ECS.World
import Path (Path)
import Path qualified
import System.Directory
import System.FSNotify
-- | Listens to changes in your asset folder and reloads assets when they have been changed.
--
-- On Windows running with @-fthreaded@ might be required.
data FileWatcherPlugin
instance Plugin FileWatcherPlugin where
init = do
systems managerSystem & schedule @Startup
void $ spawn $ Observer onFileAdd
void $ spawn $ Observer onFileModified
managerSystem :: System ()
managerSystem = do
(AssetSource source) <- fromMaybeM defAbsAssetSource =<< res @AssetSource
dir <- liftIO $ makeAbsolute $ Path.toFilePath source
dir <- Path.parseAbsDir dir
world <- unsafeGetWorld
void $ liftIO $ forkIO $ do
withManager $ watcher world dir
watcher :: World -> Path Path.Abs Path.Dir -> WatchManager -> IO ()
watcher world dir mgr = do
void $ watchTree mgr (Path.toFilePath dir) (const True) (action world)
forever $ threadDelay 10000000000000000
action :: World -> System.FSNotify.Event -> IO ()
action world (System.FSNotify.Added path _ IsFile) = do
file <- Path.parseAbsFile path
triggerEvent world (OnFileAdded file)
action world (System.FSNotify.Modified path _ IsFile) = do
file <- Path.parseAbsFile path
triggerEvent world (OnFileModified file)
action world (System.FSNotify.Removed path _ IsFile) = do
file <- Path.parseAbsFile path
triggerEvent world (OnFileRemoved file)
action _ _ = pure ()
triggerEvent :: (Mischief.ECS.Event e) => World -> e -> IO ()
triggerEvent World {deferredAsync} m = atomically $ do
modifyTVar' deferredAsync (++ [trigger m])
newtype OnFileAdded = OnFileAdded (Path Path.Abs Path.File) deriving anyclass (Mischief.ECS.Event)
newtype OnFileModified = OnFileModified (Path Path.Abs Path.File) deriving anyclass (Mischief.ECS.Event)
newtype OnFileRemoved = OnFileRemoved (Path Path.Abs Path.File) deriving anyclass (Mischief.ECS.Event)
onFileAdd :: OnFileAdded -> System ()
onFileAdd (OnFileAdded path) = triggerReload path
onFileModified :: OnFileModified -> System ()
onFileModified (OnFileModified path) = triggerReload path
triggerReload :: Path Path.Abs Path.File -> System ()
triggerReload path = do
AssetSource s <- fromMaybeM defAbsAssetSource =<< res @AssetSource
rel <- Path.stripProperPrefix s path
typeRegistry <- res @AssetTypeRegistry
case typeRegistry of
Nothing -> pure ()
Just (AssetTypeRegistry typeRegistry) -> do
types <- liftIO $ H.lookup typeRegistry path
case types of
Nothing -> pure ()
Just types -> do
for_ types $ \(AssetType (_ :: Proxy a)) -> do
reloadByAbsPath @a path $ T.pack (Path.toFilePath rel)