------------------------------------------------------------------------ -- | -- Module : ALife.Creatur.Universe -- Copyright : (c) Amy de Buitléir 2012-2013 -- License : BSD-style -- Maintainer : amy@nualeargais.ie -- Stability : experimental -- Portability : portable -- -- Provides a habitat for artificial life. -- ------------------------------------------------------------------------ {-# LANGUAGE TemplateHaskell, TypeFamilies, FlexibleContexts, UndecidableInstances #-} module ALife.Creatur.Universe ( Universe(..), SimpleUniverse, mkSimpleUniverse, agentDB, clock, logger, multiLookup, extra, agentIds, getAgent, archivedAgentIds, getAgentFromArchive, addAgent, storeOrArchive, archiveAgent ) where import Prelude hiding (lookup) import ALife.Creatur (Agent, AgentId, agentId, isAlive) import ALife.Creatur.AgentNamer (AgentNamer, SimpleAgentNamer, mkSimpleAgentNamer) import qualified ALife.Creatur.AgentNamer as N (genName) import ALife.Creatur.Clock (Clock, currentTime, incTime) import ALife.Creatur.Counter (PersistentCounter, mkPersistentCounter) import ALife.Creatur.Database as D (Database, DBRecord, Record, delete, key, keys, archivedKeys, lookup, lookupInArchive, store) import ALife.Creatur.Database.FileSystem (FSDatabase, mkFSDatabase) import ALife.Creatur.Logger (Logger, SimpleRotatingLogger, mkSimpleRotatingLogger, writeToLog) import ALife.Creatur.Util (modifyLift) import Control.Lens (makeLenses, zoom, view, set) import Control.Monad (unless) import Control.Monad.State (StateT, gets) import Data.Either (partitionEithers) import Data.Serialize (Serialize) import System.Directory (doesDirectoryExist, createDirectory) -- | A habitat containing artificial life. data Universe c l d n x a = Universe { _initialised :: Bool, _dirName :: FilePath, _clock :: c, _logger :: l, _agentDB :: d, _namer :: n, _extra :: x } makeLenses ''Universe initIfNeeded :: StateT (Universe c l d n x a) IO () initIfNeeded = do isInitialised <- gets (view initialised) unless isInitialised $ modifyLift initialise initialise :: Universe c l d n x a -> IO (Universe c l d n x a) initialise u = do let d = view dirName u dExists <- doesDirectoryExist d unless dExists (createDirectory d) return $ set initialised True u instance (Clock c, Logger l) => Logger (Universe c l d n x a) where writeToLog msg = do initIfNeeded t <- currentTime zoom logger $ writeToLog $ show t ++ "\t" ++ msg instance Clock c => Clock (Universe c l d n x a) where currentTime = initIfNeeded >> zoom clock currentTime incTime = initIfNeeded >> zoom clock incTime -- instance (Database d, DBRecord d ~ DBRecord (Universe c l d n x a)) => -- Database (Universe c l d n x a) where -- type DBRecord (Universe c l d n x a) = a -- keys = zoom agentDB keys -- archivedKeys = zoom agentDB archivedKeys -- lookup = zoom agentDB . lookup -- lookupInArchive = zoom agentDB . lookupInArchive -- store = zoom agentDB . store -- delete = zoom agentDB . delete instance AgentNamer n => AgentNamer (Universe c l d n x a) where genName = initIfNeeded >> zoom namer N.genName agentIds :: Database d => StateT (Universe c l d n x a) IO [String] agentIds = initIfNeeded >> zoom agentDB keys archivedAgentIds :: Database d => StateT (Universe c l d n x a) IO [String] archivedAgentIds = initIfNeeded >> zoom agentDB archivedKeys getAgent :: (Serialize a, Database d, a ~ DBRecord d) => String -> StateT (Universe c l d n x a) IO (Either String a) getAgent name = do initIfNeeded zoom agentDB $ lookup name getAgentFromArchive :: (Serialize a, Database d, a ~ DBRecord d) => String -> StateT (Universe c l d n x a) IO (Either String a) getAgentFromArchive name = do initIfNeeded zoom agentDB $ lookupInArchive name multiLookup :: (Serialize a, Database d, Record a, a ~ DBRecord d) => [AgentId] -> StateT d IO (Either String [DBRecord d]) multiLookup names = do results <- mapM D.lookup names let (msgs, agents) = partitionEithers results if null msgs then return $ Right agents else return . Left $ show msgs storeOrArchive :: (Serialize a, Database d, Record a, Agent a, a ~ DBRecord d) => a -> StateT d IO () storeOrArchive a = do store a -- Even dead agents should be stored (prior to archiving) unless (isAlive a) $ (delete . agentId) a addAgent :: (Serialize a, Database d, Record a, a ~ DBRecord d) => DBRecord d -> StateT (Universe c l d n x a) IO () addAgent a = do initIfNeeded zoom agentDB $ store a archiveAgent :: (Serialize a, Database d, Record a, a ~ DBRecord d) => DBRecord d -> StateT (Universe c l d n x a) IO () archiveAgent a = do initIfNeeded zoom agentDB . delete $ D.key a type SimpleUniverse a = Universe PersistentCounter SimpleRotatingLogger (FSDatabase a) SimpleAgentNamer () a mkSimpleUniverse :: String -> FilePath -> Int -> SimpleUniverse a mkSimpleUniverse name dir rotateCount = Universe False dir c l d n () where c = mkPersistentCounter (dir ++ "/clock") l = mkSimpleRotatingLogger (dir ++ "/log/") name rotateCount d = mkFSDatabase (dir ++ "/db") n = mkSimpleAgentNamer (name ++ "_") (dir ++ "/namer")