EtaMOO-0.1.0.0: src/MOO/Builtins/Tasks.hs
{-# LANGUAGE OverloadedStrings #-}
module MOO.Builtins.Tasks ( builtins ) where
import Control.Concurrent (forkIO, threadDelay, killThread)
import Control.Concurrent.STM
import Control.Monad (liftM, void)
import Control.Monad.Cont (callCC)
import Control.Monad.Reader (asks)
import Control.Monad.State (gets, modify, get)
import Data.List (sort)
import Data.Time (getCurrentTime, addUTCTime)
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
import System.Posix (nanosleep)
import MOO.Types
import MOO.Task
import MOO.Object
import MOO.Verb
import MOO.Parser
import {-# SOURCE #-} MOO.Compiler
import {-# SOURCE #-} MOO.Builtins
import MOO.Builtins.Common
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Text as T
{-# ANN module ("HLint: ignore Use camelCase" :: String) #-}
-- | § 4.4.6 MOO-Code Evaluation and Task Manipulation
builtins :: [BuiltinSpec]
builtins = [
("raise" , (bf_raise , Info 1 (Just 3) [TAny, TStr,
TAny] TAny))
, ("call_function" , (bf_call_function , Info 1 Nothing [TStr] TAny))
, ("function_info" , (bf_function_info , Info 0 (Just 1) [TStr] TLst))
, ("eval" , (bf_eval , Info 1 (Just 1) [TStr] TLst))
, ("set_task_perms", (bf_set_task_perms, Info 1 (Just 1) [TObj] TAny))
, ("caller_perms" , (bf_caller_perms , Info 0 (Just 0) [] TObj))
, ("ticks_left" , (bf_ticks_left , Info 0 (Just 0) [] TInt))
, ("seconds_left" , (bf_seconds_left , Info 0 (Just 0) [] TInt))
, ("task_id" , (bf_task_id , Info 0 (Just 0) [] TInt))
, ("suspend" , (bf_suspend , Info 0 (Just 1) [TNum] TAny))
, ("resume" , (bf_resume , Info 1 (Just 2) [TInt, TAny] TAny))
, ("queue_info" , (bf_queue_info , Info 0 (Just 1) [TObj] TLst))
, ("queued_tasks" , (bf_queued_tasks , Info 0 (Just 0) [] TLst))
, ("kill_task" , (bf_kill_task , Info 1 (Just 1) [TInt] TAny))
, ("callers" , (bf_callers , Info 0 (Just 1) [TAny] TLst))
, ("task_stack" , (bf_task_stack , Info 1 (Just 2) [TInt, TAny] TLst))
]
bf_raise (code : optional) = raiseException $ Exception code message value
where [Str message, value] = defaults optional [Str $ toText code, nothing]
bf_call_function (Str func_name : args) =
callBuiltin (T.toCaseFold func_name) args
formatInfo :: (Id, (Builtin, Info)) -> Value
formatInfo (name, (_, Info min max types _)) =
fromList [ Str name
, Int $ fromIntegral min
, Int $ maybe (-1) fromIntegral max
, fromListBy (Int . typeCode) types
]
bf_function_info [] = return $ fromListBy formatInfo $ M.assocs builtinFunctions
bf_function_info [Str name] =
case M.lookup name' builtinFunctions of
Just detail -> return $ formatInfo (name', detail)
Nothing -> raise E_INVARG
where name' = T.toCaseFold name
bf_eval [Str string] = do
checkProgrammer
case parse string of
Left errors -> return $ fromList [truthValue False,
fromListBy (Str . T.pack) errors]
Right program -> do
(programmer, this, player) <- frame $ \frame ->
(permissions frame, initialThis frame, initialPlayer frame)
let verb = initVerb {
verbNames = "Input to EVAL"
, verbProgram = program
, verbCode = compile program
, verbOwner = programmer
, verbPermD = True
}
vars = mkVariables [
("player", Obj player)
, ("caller", Obj this)
]
value <- evalFromFunc "eval" 0 $
runVerb verb initFrame {
variables = vars
, initialPlayer = player
}
return $ fromList [truthValue True, value]
bf_set_task_perms [Obj who] = do
checkPermission who
modifyFrame $ \frame -> frame { permissions = who }
return nothing
bf_caller_perms [] = (Obj . objectForMaybe) `liftM` caller permissions
bf_ticks_left [] = (Int . fromIntegral) `liftM` gets ticksLeft
bf_seconds_left [] = return (Int 5) -- XXX can this be measured?
bf_task_id [] = (Int . fromIntegral) `liftM` asks (taskId . task)
bf_suspend optional = do
maybeMicroseconds <- case maybeSeconds of
Just (Int secs)
| secs < 0 -> raise E_INVARG
| otherwise -> return (Just $ fromIntegral secs * 1000000)
Just (Flt secs)
| secs < 0 -> raise E_INVARG
| otherwise -> return (Just $ ceiling $ secs * 1000000)
Nothing -> return Nothing
state <- get
estimatedWakeup <- case maybeMicroseconds of
Just usecs
| time < now || time > endOfTime -> raise E_INVARG
| otherwise -> return time
where now = startTime state
time = (fromIntegral usecs / 1000000) `addUTCTime` now
Nothing -> return endOfTime -- XXX this is a sad wart in need of remedy
resumeTMVar <- liftSTM newEmptyTMVar
task <- asks task
let wake value = do
now <- getCurrentTime
atomically $ putTMVar resumeTMVar (now, value)
task' = task {
taskStatus = Suspended (Wake wake)
, taskState = state { startTime = estimatedWakeup }
}
case maybeMicroseconds of
Just usecs -> delayIO $ void $ forkIO $ do
if usecs <= fromIntegral (maxBound :: Int)
then threadDelay (fromIntegral usecs)
else nanosleep (usecs * 1000)
wake nothing
Nothing -> return ()
putTask task'
callCC $ interrupt . Suspend maybeMicroseconds . Resume
(now, value) <- liftSTM $ takeTMVar resumeTMVar
putTask task' { taskStatus = Running }
modify $ \state -> state { ticksLeft = 15000, startTime = now } -- XXX ticks
case value of
Err error -> raise error
_ -> return value
where (maybeSeconds : _) = maybeDefaults optional
bf_resume (Int task_id : optional) = do
maybeTask <- getTask task_id'
case maybeTask of
Just task@Task { taskStatus = Suspended (Wake wake) } -> do
checkPermission (taskOwner task)
putTask task { taskStatus = Running }
delayIO (wake value)
_ -> raise E_INVARG
return nothing
where [value] = defaults optional [nothing]
task_id' = fromIntegral task_id
bf_queue_info [] =
(objectList . S.toList . foldr (S.insert . taskOwner) S.empty) `liftM`
queuedTasks
bf_queue_info [Obj player] =
(Int . fromIntegral . length . filter ((== player) . taskOwner)) `liftM`
queuedTasks
bf_queued_tasks [] = do
tasks <- queuedTasks
programmer <- frame permissions
wizard <- isWizard programmer
let ownedTasks = if wizard then tasks
else filter ((== programmer) . taskOwner) tasks
return $ fromListBy formatTask $ sort ownedTasks
where formatTask task = fromListBy ($ task) [
Int . fromIntegral . taskId -- task-id
, Int . floor . utcTimeToPOSIXSeconds . startTime . taskState
-- start-time
, const (Int 0) -- clock-id
, const (Int 15000) -- ticks XXX
, Obj . taskOwner -- programmer
, Obj . verbLocation . activeFrame -- verb-loc
, Str . verbFullName . activeFrame -- verb-name
, Int . lineNumber . activeFrame -- line
, Obj . initialThis . activeFrame -- this
, Int . fromIntegral . storageBytes -- task-size
]
bf_kill_task [Int task_id] = do
maybeTask <- getTask task_id'
case maybeTask of
Just task@Task { taskStatus = status } | isQueued status -> do
checkPermission (taskOwner task)
purgeTask task
delayIO $ killThread (taskThread task)
return nothing
_ -> do
thisTaskId <- taskId `liftM` asks task
if task_id' == thisTaskId
then interrupt Suicide
else raise E_INVARG
where task_id' = fromIntegral task_id
bf_callers optional = do
Stack frames <- gets stack
return $ formatFrames include_line_numbers (tail frames)
where [include_line_numbers] = booleanDefaults optional [False]
bf_task_stack (Int task_id : optional) = do
maybeTask <- getTask task_id'
case maybeTask of
Just task@Task { taskStatus = Suspended{} } -> do
checkPermission (taskOwner task)
let Stack frames = stack $ taskState task
return $ formatFrames include_line_numbers frames
_ -> raise E_INVARG
where [include_line_numbers] = booleanDefaults optional [False]
task_id' = fromIntegral task_id