module RunScript (runScript, ScriptState(..), ScriptDyn(..), startScript) where import Script import Animation import Graphics.UI.GLUT -- Running a script. Main part is to map non-timing commands to -- drawing actions, and to perform sequencing for the timing commands. -- For executing a script it's simplest to know the following: -- * remaining amount of time -- * current set of commands to run to draw the scene -- To make this smoother, an intermediate structure is used to keep -- track of the current state. import Number data ScriptDyn = ScriptDyn (Number, Number) -- Script dynamics state deriving (Eq, Show) data ScriptState = ScriptState Number -- remaining runtime [Command] -- current drawing actions [Command] -- whole script (could be infinite) ScriptDyn deriving (Eq, Show) -- Dynamics updateDyn dt (ScriptDyn (x,y)) = ScriptDyn (x+dt, y+dt) -- Structural manipulation: flatten subscripts and loop. flatten (Script s) = ft s where ft ((Do (Script sub)) : script) = ft sub ++ ft script ft [] = [] ft (c:cs) = c : ft cs loop s = lp s where lp [] = lp s lp (c:cs) = c : lp cs startScript s = ScriptState 0 [] ((loop . flatten) s) $ ScriptDyn (0,0) -- Parsing a script: treat time and drawing commands differently. collectTime tick2sec = coll 0 where coll t ((Tick n) : cs) = coll t $ (Sec $ tick2sec n) : cs coll t ((Sec s) : cs) = coll (t + s) cs coll t cs = (t, cs) collectDraw = coll [] where coll d cs@((Tick n) : _) = (d, cs) coll d cs@((Sec n) : _) = (d, cs) coll d [] = (d, []) coll d (c : cs) = coll (d ++ [c]) cs collectTimeDraw tick2sec cs = (t,d,cs'') where (t,cs') = collectTime tick2sec cs (d,cs'') = collectDraw cs' -- A "tick" for a script results in a state update and a sequence of -- current drawing commands. runScript' tick2sec = rs where rs (Time abs rel) ss@(ScriptState wait draw next dyn) -- Prevent faulty behaviour for negative and zero offsets | rel <= 0 = ([], ss) -- If not enough time has passed, draw the current scene. | rel <= wait = (draw, ScriptState (wait - rel) draw next (updateDyn rel dyn)) -- Else, consume part of the time and advance to next part of script. | otherwise = rs (Time abs (rel - wait)) (ScriptState wait' draw' next' (updateDyn wait dyn)) where (wait', draw', next') = collectTimeDraw tick2sec next showScriptState = s where s (ScriptState 0 [] [] dyn) = "" s (ScriptState wait draw next dyn) = show (wait, draw) ++ "\n" ++ s ss' where (wait',draw',next') = collectTimeDraw tick2sec next ss' = ScriptState wait' draw' next' dyn tick2sec n = n / 1 runScript = runScript' tick2sec