I’ve been working heavily on the Big Board lately, and now I’m finally basically done with it. As a result, I’ve modified, updated, and upgraded buster considerably since the first release, and it is now a pretty mature, high performance, and powerful engine for application orchestration. There are a few things about it that bug me; mainly that there tends to be a lot more IO code than I usually like to see, but on the other hand, this tends to be in the behaviour part and the actual orchestration in terms of events and behaviour relations are purely functional and their semantics are well exposed. One thing I am thinking of changing is the way that events are named, to an EventName type rather than relying on group, source, and name, since some of these seem to be irrelevant in certain cases.
One thing I am particularly satisfied with is that behaviours seem to be self-segmenting and I’ve not had to wedge anything into the form of a behaviour or worry about having to wedge something non-behaviour like into the framework because of poor forethought. The metaphor seems to work well. I have found that since I use strings to name events rather than enumerations, it’s helpful to have a module in the code somewhere that ties constants to the names to encourage consistency and type-safety.
Anyway, in a nutshell, here’s how application orchestration via Buster works by example:
iwidth = 1024 iheight = 1024 programBehaviour = mouseSelectionBehaviour >~> selectionBehaviour >~> zoomAndPanBehaviour >~> (uiResponses |~| translateVisibles |~| mouseCoordinateAdjustmentBehaviour) >~> dialogueRaisingBehaviour >~> dialogueResponseBehaviour >~> annotationCreationBehaviour >~> annotationFinishingBehaviour >~> annotationsTouchedBehaviour >~> loadMapMetadataBehaviour >~> (loadTilesBehaviour |~| loadAnnotationsBehaviour) >~> renderBehaviour widgets = [ initializeBus "The Big Board, by the Renaissance Computing Institute. v3.0a" iwidth iheight , configFileWidget "thebigboard.ini" , paletteGeometryWidget] main = boilerplateGLMain widgets programBehaviour
Yes. That plus the imports is the entire main module of The Big Board. In point of fact the entirey of the Big Board, despite its complexity, is just a little under 800 lines of application-specific Haskell code. I’ve highlighted in blue all the behaviours and widgets that are part of Hieroglyph and in green the ones that are part of buster. Here we declare the dependency structure of the component program behaviours in the function programBehaviour. The >~> operator designates that the right operand depends on the left operand. The |~| operator declares that the two operands are independent and are thus functionally concurrent. So we perform mouse-based selection, select items, and go through the normal chain of UI behaviours and then finally hit the render behaviour, which detects if anything’s changed in the scene and tells Hieroglyph to rerender if it has. Main is a single line that calls the boilerplate Hieroglyph main, which initializes the Gtk and OpenGL, creates a window (which can be accessed later and modified for those who want more than a single main window in their application), and starts the bus a’-running.
Now let’s delve into the anatomy of a behaviour…
mouseCoordinateAdjustmentBehaviour b = pollFullyQualifiedEventWith b "Mouse" "Hieroglyph.KeyboardMouseWidget" "Position" $ \evt -> do let [x,y] = fromEDoubleL . fromJust . lookup "coords" . fromEAssocL . head . eventdata $ evt [ox,oy] = fromEDoubleL . fromJust . lookup "origin" metaL scale = fromEDouble . fromJust . lookup "scale" metaDataL metaL = maybe ([("origin",EDoubleL [0,0]),("scale",EDouble 1)]) (fromEAssocL . head . eventdata) (eventForQName "Environment" "Map" "Metadata" b) glcoords <- reverseMouseCoords b x y let Point x' y' = reprojectPoint (Point ox oy) scale glcoords rerender <- produce "Hieroglyph" "Hieroglyph" "Rerender" Persistent  coords <- produce "Visible" "Hieroglyph" "coords text" Persistent [EOther (Geometry (coordsText x' y')] return [rerender, coords]
Again, the buster and Hieroglyph highlighting rules apply. Here, we poll events named (Mouse,Hieroglyph.KeyboardMouseWidget,Position) that sit on the bus and apply the associated behavioural thunk to them. Polling means that the event will stay on the bus even after the behaviour is applied. We then reverse the mouse coords to fit GL coordinates and offset it from the origin by the proper amount. The coordinates are then displayed in a box (the coordsText function maps the x and y values to a visible representation and this visible representation is put on the bus as an event in the group “Visible”, which indicates to Hieroglyph that it should be rendered.
There are times where the poll/consume event helper functions aren’t appropriate. In this case, instead of returning a list of bus differences, we have to return a “future” of a list of bus differences. This is done by replacing the last line of the behaviour with:
future bus . return $ [list of Insertion evt | Deletion evt]
From there, most everything else about buster can be figured out based on this and the previous posts on the buster model and the Haddock documentation.