buster (empty) → 0.99.1
raw patch · 5 files changed
+494/−0 lines, 5 filesdep +basedep +bytestringdep +containerssetup-changed
Dependencies added: base, bytestring, containers, gtk, mtl, old-locale, pretty, time
Files
- App/Behaviours/PrintEvents.hs +30/−0
- App/EventBus.hs +345/−0
- App/Widgets/GtkMouseKeyboard.hs +54/−0
- Setup.lhs +6/−0
- buster.cabal +59/−0
+ App/Behaviours/PrintEvents.hs view
@@ -0,0 +1,30 @@+module App.Behaviours.PrintEvents where++import qualified Data.ByteString as B+import Text.PrettyPrint+import Data.Time+import System.Locale+import App.EventBus++printEventsBehaviour b = pollAllEventsWith b $ (\(Event n g lifetime edata source t) -> do+ putStrLn.render $ text "name: " <+> text n $+$+ text "source: " <+> text source $+$+ text "group: " <+> text g $+$+ text "ttl: " <+> text (show lifetime) $+$+ text "emitTime:" <+> text (formatTime defaultTimeLocale "%T" t) $+$+ (vcat.map showIfPossible $ edata)+ return [])+ where showIfPossible (EString x) = text x+ showIfPossible (EByteString x) = text "ByteString"+ showIfPossible (EByteStringL x) = text "[ByteString]"+ showIfPossible (EInt x) = text (show x)+ showIfPossible (EDouble x) = text (show x)+ showIfPossible (EBool x) = text (show x)+ showIfPossible (EStringL x) = text (show x)+ showIfPossible (EIntL x) = text (show x)+ showIfPossible (EDoubleL x) = text (show x)+ showIfPossible (EBoolL x) = text (show x)+ showIfPossible (EChar x) = char x+ showIfPossible (EOther x) = text "Custom Data"+ showIfPossible (EOtherL x) = text "Custom Data List"+
+ App/EventBus.hs view
@@ -0,0 +1,345 @@+{-# LANGUAGE TypeSynonymInstances #-}+-- | Not exactly the FRP model, but rather a model of a large application with+-- heterogenous data and many inputs and outputs. An application is in its+-- essence a collection of widgets and behaviours and events with a bus.+-- The bus holds events and manages the event timeline. Behaviours and+-- widgets are continuous. Widgets applied to the bus make insertions and+-- never deletions. Behaviours applied to the bus make insertions and deletions.+--+-- Behaviours are composable using combinators that set one Behaviour as either+-- behind, in front, or beside another behaviour on the bus. The in front and+-- behind combinators establish that the behaviour "behind" the others+-- sees the results of the other behaviours' application to the bus. The beside+-- combinator says that the combinators see the same bus.+--+module App.EventBus where++import Control.Applicative+import Control.Concurrent+import Control.Monad+import Data.Maybe+import Data.List (foldl')+import Data.Monoid+import qualified Data.Set as Set+import Data.Time.Clock+import qualified Data.Map as Map+import qualified Data.ByteString as B+import System.IO.Unsafe++-- generic functions for key ordering. move somewhere else later+EQ />/ b = b+a />/ _ = a++a /</ EQ = a+_ /</ b = b++g %=> f = f `on` g++on f g a b = f (g a) (g b)++-- IO version of the <* Applicative operator+a =<<^ b = \m -> b >> a m++-- | Defines the amount of time that an event exists.+data TimeSpan =+ Persistent -- ^ The event exists forever+ | Time DiffTime -- ^ The event exists for a specified amount of real time+ | Iterations Int -- ^ The event exists for a certain number of samples of time from its inception.+ deriving (Eq,Ord,Show)++seconds :: Integer -> TimeSpan+seconds = Time . secondsToDiffTime++minutes :: Integer -> TimeSpan+minutes = Time . secondsToDiffTime . (60*)++hours :: Integer -> TimeSpan+hours = Time . secondsToDiffTime . (3600*)++days :: Integer -> TimeSpan+days = Time . secondsToDiffTime . (86400*)++once :: TimeSpan+once = Iterations 1++-- | Defines time in terms of the differences from time t0 to the next instant. This is the type+-- returned by Behaviours to describe time directly after the Behaviour.+data Diff a =+ Insertion (Event a) -- ^ Time t1 contains all events at time t0 plus this event.+ | Deletion (Event a) -- ^ Time t1 contains all events at time t0 minus this event.+++-- | Defines the data attachable to events.+data EData a =+ EString String+ | EByteString B.ByteString+ | EByteStringL [B.ByteString]+ | EChar Char+ | EDouble Double+ | EInt Int+ | EBool Bool+ | EStringL [String]+ | EDoubleL [Double]+ | EIntL [Int]+ | EBoolL [Bool]+ | EOther a+ | EOtherL [a]+ deriving (Eq, Show, Read)++-- | An discrete event in time+data Event a = Event+ { name :: String -- ^ The unique name of an event. Group + src + name = the fully qualified name FQN of the event.+ , group :: String -- ^ The group of an event.+ , timespan :: TimeSpan -- ^ The timespan from "time" that an event exists.+ , eventdata :: [EData a] -- ^ The data attached to the event.+ , src :: String -- ^ The behaviour or widget that assigned the event to time.+ , time :: UTCTime } -- ^ The time of the event's inception.+++instance Ord (Event a) where+ compare l r = ((src %=> compare) l r) />/+ ((time %=> compare) l r) />/+ ((group %=> compare) l r) />/+ ((name %=> compare) l r)++instance Eq (Event a) where+ x == y = (name %=> (==)) x y &&+ (group %=> (==)) x y &&+ (src %=> (==)) x y++-- | The type of a discrete sample of continuous time.+data Bus a = Bus+ { nameMap :: Map.Map String (Set.Set (Event a)) -- ^ The map of just Event.name to events.+ , srcMap :: Map.Map String (Set.Set (Event a)) -- ^ The map of just Event.src to events.+ , groupMap :: Map.Map String (Set.Set (Event a)) -- ^ The map of just Event.group to events.+ , fullyQualifiedMap :: Map.Map (String,String,String) (Event a) } -- ^ The map of FQNs to events.++instance Monoid (Bus a) where+ mempty = emptyBus+ mappend (Bus n0 s0 g0 f0) (Bus n1 s1 g1 f1) = Bus (Map.union n0 n1) (Map.union s0 s1) (Map.union g0 g1) (Map.union f0 f1)++-- | The empty bus+emptyBus :: Bus a+emptyBus = Bus Map.empty Map.empty Map.empty Map.empty++-- | Add an event to time within the bus+addEvent :: Event a -> Bus a -> Bus a+addEvent edata b = b{ nameMap = Map.insertWith (Set.union) (name edata) (singleton edata) (nameMap b)+ , srcMap = Map.insertWith (Set.union) (src edata) (singleton edata) (srcMap b)+ , groupMap = Map.insertWith (Set.union) (group edata) (singleton edata) (groupMap b)+ , fullyQualifiedMap = Map.insert (group edata, src edata, name edata) edata (fullyQualifiedMap b) }++-- | The type of widgets.+-- A widget is an input-only way to assign Events to time. A mouse is a widget. A keyboard is a+-- widget. A webcam is a widget, and so on.+type Widget a = MVar (Bus a) -> IO ()++-- | The type of future events..+-- A behaviour doesn't know about the time that it assigns events, only that they exist+-- at some point after the time that the Behaviour sampled.+type Future a = IO (MVar a)++-- | An IO action sometime in the future.+future :: IO a -> Future a+future thunk = do+ ref <- newEmptyMVar+ forkIO $ thunk >>= putMVar ref+ return ref++-- | Obtain the final value of a Future. Blocks until the value is available+immediate = takeMVar++-- | The type of a Behaviour. A behaviour maps the bus to a list of differences to apply to the bus+-- before the next Behaviour's sample of time.+type Behaviour a = Bus a -> Future [Diff a]++instance Monoid (Behaviour a) where+ mempty = passthrough+ mappend = (>~>) -- x behind y++-- | The null Behaviour. Samples the bus and adds and deletes nothing.+passthrough :: Behaviour a+passthrough a = future (return [])++-- | the in front of behaviour combinator. behaviour 1 is in front of behaviour 0, so behavour 0 will see the bus filtered through behaviour 1+(<~<) :: Behaviour a -> Behaviour a -> Behaviour a+behaviour1 <~< behaviour0 = \m -> behaviour0 m >>= applyDiff m >>= behaviour1++-- | the behind behaviour combinator. behaviour 0 is behind behaviour 1, so behaviour 0 will see the bus filtered through behaviour 1+(>~>) :: Behaviour a -> Behaviour a -> Behaviour a+behaviour0 >~> behaviour1 = \m -> behaviour0 m >>= applyDiff m >>= behaviour1++-- | the beside behaviour combinator. All behaviours that are side-by-side see the same bus.+(|~|) :: Behaviour a -> Behaviour a -> Behaviour a+behaviour0 |~| behaviour1 = \m -> do+ future0 <- behaviour0 m+ future1 <- behaviour1 m+ future $ concat <$> mapM immediate [future0, future1]++behind = (>~>)+beside = (|~|)+infrontof = (<~<)++-- | An infinite loop of behaviours and widgets over time, sampled forward.+bus :: [Widget a] -> IO b -> Behaviour a -> IO ()+bus widgets widgetThunk behaviour = do+ evBus <- newMVar emptyBus+ forM_ widgets ($evBus)++ let loop = do+ widgetThunk+ busIteration evBus behaviour+ loop++ loop++-- | Sample time and apply the behaviour to that sample.+busIteration :: MVar (Bus a) -> Behaviour a -> IO ()+busIteration b behaviour = do+ v <- tryTakeMVar b+ case v of+ Nothing -> return ()+ Just m -> putMVar b . expire =<< decrementTimeSpan =<< applyDiff m =<< behaviour m++-- | Assign an event to time given some event data and a TimeSpan.+produce :: String -> String -> String -> TimeSpan -> [EData a] -> IO (Diff a)+produce group source nm timetolive edata =+ (return . Insertion . Event nm group timetolive edata source) =<< getCurrentTime++-- | Assign an event to time from a widget.+produce' :: String -> String -> String -> TimeSpan -> [EData a] -> MVar (Bus a) -> IO ()+produce' group source nm timetolive edata b = getCurrentTime >>= \t -> modifyMVar_ b (return . addEvent (Event nm group timetolive edata source t))++-- | Sample all events with a given name at the current time and output their deletions as Diffs as+-- well as any additional Diffs returned by the behaviour.+consumeNamedEventsWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]+consumeNamedEventsWith em nm f =+ maybe (future . return $ [])+ (\ev -> future $ (map Deletion (Set.toList ev) ++) <$> f ev)+ (Map.lookup nm (nameMap em))++-- | Sample all events with a given group at the current time and output their deletions as Diffs as+-- well as any additional Diffs returned by the behaviour.+consumeEventGroupWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]+consumeEventGroupWith em gp f =+ maybe (future . return $ [])+ (\ev -> future $ (map Deletion (Set.toList ev) ++) <$> f ev)+ (Map.lookup gp (groupMap em))++-- | Sample all events with a given source at the current time and output their deletions as Diffs as+-- well as any additional Diffs returned by the behaviour.+consumeEventsFromSourceWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]+consumeEventsFromSourceWith em source f =+ maybe (future . return $ [])+ (\ev -> future $ (map Deletion (Set.toList ev) ++) <$> f ev)+ (Map.lookup source (srcMap em))++-- | Sample a single fully qualified event at the current time and output their deletions as Diffs as+-- well as any additional Diffs returned by the behaviour.+consumeFullyQualifiedEventWith :: Bus a -> String -> String -> String -> (Event a -> IO [Diff a]) -> Future [Diff a]+consumeFullyQualifiedEventWith em group source name f =+ maybe (future . return $ [])+ (\ev -> future $ (Deletion ev :) <$> f ev)+ (Map.lookup (group,source,name) (fullyQualifiedMap em))++-- | Sample all events with a given name and apply a Behaviour+pollNamedEventsCollectivelyWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]+pollNamedEventsCollectivelyWith b nm f = maybe (future . return $[]) (future . f) (Map.lookup nm (nameMap b))++-- | Sample all events with a given name and apply a Behaviour to each+pollNamedEventsWith :: Bus a -> String -> (Event a -> IO [Diff a]) -> Future [Diff a]+pollNamedEventsWith b nm f = future $ concat <$> (mapM f . Set.toList $ fromMaybe Set.empty (Map.lookup nm (nameMap b)))++-- | Sample all events with a given group and apply a Behaviour+pollEventGroupCollectivelyWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]+pollEventGroupCollectivelyWith b nm f = maybe (future . return $[]) (future . f) (Map.lookup nm (groupMap b))++-- | Sample all events with a gien group and apply a Behaviour to each.+pollEventGroupWith :: Bus a -> String -> (Event a -> IO [Diff a]) -> Future [Diff a]+pollEventGroupWith b nm f = future $ concat <$> (mapM f . Set.toList $ fromMaybe Set.empty (Map.lookup nm (groupMap b)))++-- | Sample all events with a given source and apply a Behaviour+pollEventsFromSourceCollectivelyWith :: Bus a -> String -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]+pollEventsFromSourceCollectivelyWith b nm f = maybe (future . return $[]) (future.f) (Map.lookup nm (srcMap b))++-- | Sample all events with a given source and apply a Behaviour to each.+pollEventsFromSourceWith :: Bus a -> String -> (Event a -> IO [Diff a]) -> Future [Diff a]+pollEventsFromSourceWith b nm f = future $ concat <$> (mapM f . Set.toList $ fromMaybe Set.empty (Map.lookup nm (srcMap b)))++-- | Sample a single fully qualified event and output some Diffs.+pollFullyQualifiedEventWith :: Bus a -> String -> String -> String -> (Event a -> IO [Diff a]) -> Future [Diff a]+pollFullyQualifiedEventWith b gp source nm f = maybe (future . return $ []) (future . f) (Map.lookup (gp,source,nm) (fullyQualifiedMap b))++-- | Apply a behaviour to all events in the bus, one event at a time.+pollAllEventsWith :: Bus a -> (Event a -> IO [Diff a]) -> Future [Diff a]+pollAllEventsWith b f = future $ concat <$> (mapM f . Map.elems . fullyQualifiedMap $ b)++-- | Apply a behaviour to the collection of all events on the bus at once+pollAllEventsCollectivelyWith :: Bus a -> (Set.Set (Event a) -> IO [Diff a]) -> Future [Diff a]+pollAllEventsCollectivelyWith b f = future $ f . Set.fromList . Map.elems . fullyQualifiedMap $ b++applyDiff m ds = immediate ds >>= (return . foldl' busDiff m)+ where busDiff b (Insertion ev) = b{ nameMap = Map.insertWith (Set.union) (name ev) (singleton ev) (nameMap b)+ , srcMap = Map.insertWith (Set.union) (src ev) (singleton ev) (srcMap b)+ , groupMap = Map.insertWith (Set.union) (group ev) (singleton ev) (groupMap b)+ , fullyQualifiedMap = Map.insert (group ev, src ev, name ev) ev (fullyQualifiedMap b) }+ busDiff b (Deletion ev) = b { nameMap = deleteOneFrom ev (name ev) (nameMap b) -- should change this to alter instead of delete, check for empty lists+ , srcMap = deleteOneFrom ev (src ev) (srcMap b)+ , groupMap = deleteOneFrom ev (group ev) (groupMap b)+ , fullyQualifiedMap = Map.delete (group ev, src ev, name ev) (fullyQualifiedMap b) }+ deleteOneFrom ev key mp = case Map.lookup key mp of+ Just eset -> let eset' = Set.delete ev eset in if eset' == Set.empty then Map.delete key mp else Map.insert key eset' mp+ Nothing -> mp+ +singleton a = Set.fromList [a] + +decrementTimeSpan b = return $ b{ nameMap = Map.map decrements (nameMap b)+ , srcMap = Map.map decrements (srcMap b)+ , groupMap = Map.map decrements (groupMap b)+ , fullyQualifiedMap = Map.map decrement (fullyQualifiedMap b) }+ where decrement e = e{ timespan = decTimeSpan e (timespan e) }+ decrements = Set.map (\e -> e{timespan = decTimeSpan e (timespan e)} )+ decTimeSpan _ Persistent = Persistent+ decTimeSpan e (Time x) = Time . realToFrac $ diffUTCTime (addUTCTime (realToFrac x) (time e)) (unsafePerformIO getCurrentTime)+ decTimeSpan _ (Iterations x) = (Iterations (x-1))++expire b = Bus (Map.filter (/=Set.empty) . Map.map (Set.filter (current . timespan)) . nameMap $ b)+ (Map.filter (/=Set.empty) . Map.map (Set.filter (current . timespan)) . srcMap $ b)+ (Map.filter (/=Set.empty) . Map.map (Set.filter (current . timespan)) . groupMap $ b)+ (Map.filter (current . timespan) . fullyQualifiedMap $ b)+ where current (Time x) = x > 0+ current Persistent = True+ current (Iterations x) = x > 0+ +{- example usage...+ -+ - handleDataLoad :: Behaviour+ - ...+ -+ - handleZoom :: Behaviour+ - ...+ -+ - handlePan :: Behaviour+ - ...+ -+ - handleRot :: Behaviour+ - ...+ -+ - handleWriteData :: Behaviour+ - ...+ -+ - main = do+ - ui <- getUIFromFile "something.glade"+ - mapM_ makeGtkProducers ui+ - multitouch <- getMultitouchProducer "localhost" 8080+ - bus (multitouch:ui) $ handleDataLoad >~> handleZoom |~| handlePan |~| handleRot >~> handleWriteData+ -+ -}++{- example of generically wrapping a Gtk widget into a EventBus.Widget+ - buttonWidget :: Gtk.Widget -> Bus a -> IO (Behaviour a)+ - buttonWidget button em = Gtk.onClick button $ do+ - name <- Gtk.getWidgetName button+ - value <- EString <$> Gtk.getText button+ - produce' "ui" name "Click" once em+ -}
+ App/Widgets/GtkMouseKeyboard.hs view
@@ -0,0 +1,54 @@+-- | Gtk mouse keyboard widget.+--+-- For a mouse button press or release, add events named SingleClick or ClickRelease respectively to the bus.+-- For this widget, all events have source ''KeyboardMouseWidget'', and group ''Mouse''+-- Additionally, the data attached to the event follows the form [EString SingleClick|ClickRelease, EDouble x, EDouble y, EStringL [Gtk modifier names]]+--+-- For a keyboard press or release, add events named KeyDown or KeyUp respectively to the bus.+-- All keyboard events have group ''Keyboard'' and source ''WidgetName.KeyboardMouseWidget''+-- Additionally, the data attached to a keyboard event follows the form [EString keyName | EChar keyChar, EStringL [Gtk modifier names]]+module App.Widgets.GtkMouseKeyboard where++import Control.Applicative+import Control.Concurrent+import Data.Maybe+import qualified Graphics.UI.Gtk as Gtk+import qualified Graphics.UI.Gtk.Gdk.Events as Gtk+import App.EventBus++-- Gtk's button click event system is annoying, so we're ignoring it and only bothering with the single clicks.+-- when we receive a click, fire off a thread (once) that waits for 100ms to see how many clicks we get total in that time. Then fire off that number.+buttonHandler _ _ (Gtk.Button _ Gtk.DoubleClick _ _ _ _ _ _ _) = return True+buttonHandler _ _ (Gtk.Button _ Gtk.TripleClick _ _ _ _ _ _ _) = return True+buttonHandler wname b (Gtk.Button sent click time x y modifiers button _ _) = do+ produce' "Mouse" (wname ++ ".KeyboardMouseWidget") (show click) once [EString . show $ button, EDouble x, EDouble y, EStringL . map show $ modifiers] b+ return True++scrollWheelHandler wname b (Gtk.Scroll _ _ x y direction _ _) = do+ produce' "Mouse" (wname ++ ".KeyboardMouseWidget") (show direction) once [EDouble x, EDouble y] b+ return True++keyboardHandler wname b (Gtk.Key released sent time modifiers withCapsLock withNumLock withScrollLock keyVal keyName keyChar) = do+ produce' "Keyboard" (wname ++ "KeyboardMouseWidget") (if released then "KeyUp" else "KeyDown") once+ [ fromMaybe (EString . show $ keyName) (EChar <$> keyChar)+ , EStringL . map show $ modifiers ] b+ return False++motionHandler wname w b evt = do+ produce' "Mouse" (wname ++ ".KeyboardMouseWidget") "Position" once [EDouble . Gtk.eventX $ evt, EDouble . Gtk.eventY $ evt] b+ dwin <- Gtk.widgetGetDrawWindow w+ Gtk.drawWindowGetPointer dwin+ return False++-- | Bind a keyboard mouse widget to the given Gtk widget. Se module documentation for description of events.+bindMouseKeyboardWidget :: Gtk.Widget -> Widget a+bindMouseKeyboardWidget w b = do+ ref <- newEmptyMVar+ wname <- Gtk.widgetGetName w+ Gtk.onButtonPress w (buttonHandler wname b)+ Gtk.onButtonRelease w (buttonHandler wname b)+ Gtk.onScroll w (scrollWheelHandler wname b)+ Gtk.onKeyPress w (keyboardHandler wname b)+ Gtk.onKeyRelease w (keyboardHandler wname b)+ Gtk.onMotionNotify w True (motionHandler wname w b)+ return ()
+ Setup.lhs view
@@ -0,0 +1,6 @@+#!/usr/bin/runhaskell +> module Main where+> import Distribution.Simple+> main :: IO ()+> main = defaultMain+
+ buster.cabal view
@@ -0,0 +1,59 @@+name: buster+version: 0.99.1+cabal-version: -any+build-type: Simple+license: BSD3+license-file: ""+copyright: 2009 Renaissance Computing Institute+maintainer: Jeff Heard <jeff@renci.org>+build-depends: old-locale -any, time -any, containers -any,+ base -any, bytestring -any, gtk -any, mtl -any,+ pretty -any+stability: Experimental+homepage: http://vis.renci.org/jeff/buster+package-url:+bug-reports:+synopsis: Almost but not quite entirely unlike FRP+description: Buster is best described by the following blog post: http://vis.renci.org/jeff/2009/03/31/almost-but-not-quite-entirely-like-frp/+ .+ It is an engine for orchestrating large, complex, and multifaceted applications by couching them in terms of time, events, a bus,+ behaviours, and widgets. Time is continuous and infininte. Events are discrete and exist for a particular time. The bus is a+ discrete sample of time made available to behaviours. Behaviours are continuous and exist for all time, but sample time via+ the bus. They filter Events to determine what is on the bus at future times. Widgets are input-only objects that sample the+ outside world and assign events to discrete portions of time.+ .+ Buster is designed to be flexible, with a flexible event model and the ability to add custom data to events, and designed to be+ high performance. It is simple to integrate with Gtk while at the same time able to handle other kinds of resources, like files+ and sockets.+category: FRP+author: Jeff Heard+tested-with:+data-files:+data-dir: ""+extra-source-files:+extra-tmp-files:+exposed-modules: App.EventBus App.Behaviours.PrintEvents+ App.Widgets.GtkMouseKeyboard +exposed: True+buildable: True+build-tools:+cpp-options:+cc-options:+ld-options:+pkgconfig-depends:+frameworks:+c-sources:+extensions:+extra-libraries:+extra-lib-dirs:+includes:+install-includes:+include-dirs:+hs-source-dirs: .+other-modules:+ghc-prof-options:+ghc-shared-options:+ghc-options:+hugs-options:+nhc98-options:+jhc-options: