|
DUDS
|
Distributed Update of Data from Something
|
The DUDS library can be used to make various gizmos that require a user interface.
Some of these gizmos are not well served by any of the user interfaces that are common on personal computers. This part of the DUDS library is intended to assist with creating such user interfaces. Interfaces common on personal computers are not ignored here, but are not the focus.
Page objects have a title and are managed by std::shared_ptr. The Page class is intended to be the base class for things providing something like a screen of stuff for the user to see. Path objects keep track of a stack of Page objects and the current page. Pages can be pushed onto the path, and the current page can be changed by moving in either direction within the path. When a new page is pushed, all pages in the forward direction are first removed. This functionality is patterned after the common forward and backward operations of a web browser.
The menu infrastructure defines a menu at a high level and does not include code to obtain user input or provide any output. This is intended to make it applicable to many different user interfaces, and to allow it to service multiple user interfaces simultaneously. The infrastructure is thread-safe, allows for multiple users working with the same menu data, and for multiple display outputs.
A Menu has a set of options, or items: MenuItems.
A user may invoke an item by choosing it.
Before an item can by chosen, it must first be selected. This is similar to the notion of input focus.
Thus, for a user to pick/take/invoke an option from a menu, they must first select the corresponding item, and then chose it.
All menu objects, save for access objects and MenuOutput, must be managed by std::shared_ptr. The non-abstract classes have a static function named make() that will construct an object and return the managing std::shared_ptr to make life a little easier.
The following graphs illustrate the relationships between the classes and objects of the menu system, and will hopefully make the text following them easier to grasp. However, they aren't very helpful without the text after the graphs, and I don't see a good place to fit the graphs within that text.
This graph shows ownership by std::shared_ptr objects using solid lines; the line direction is from the object with the shared_ptr. The dashed lines show locks on menu data used to make the system thread-safe.
The next graph shows how data moves between the menu classes.
Each option in the menu is represented by a MenuItem object. It holds a label (name) and a description string, and manages several attributes that affect the menu's operation. The MenuItem class is an abstract base class. Implementations must provide a chose() function, and optionally select() and deselect() functions. Simple items can be created using GenericMenuItem, which uses Boost signals to invoke functions when chose() is called.
Items have the following attributes:
Menu objects hold MenuItems using std::shared_ptr<MenuItem>, or MenuItemSptr, objects. They are tasked with allowing thread-safe access to the items such that the items are not altered while in use for output. Modifications to the menu are done with the help of an exclusive and recursive lock on the menu data. The MenuAccess class provides this lock and has functions to make modifications. MenuItem objects can also modify themselves, but require the same lock after they have been added to a menu. They will automatically get and release that lock as needed. A shared lock is required during output and is acquired by way of a MenuOutputAccess object.
Because of this, menus cannot be changed while being output. If an attempt is made, the thread attempting modification will be blocked until after all output has completed. If the modification is attempted while a MenuOutputAccess object is in scope and working on the same Menu, the offending thread will be deadlocked. This deadlock situation requires code outputting the menu to modify the same menu during the output, which is bad design.
The MenuView class is responsible for handling user input to select and chose menu items. There can be multiple MenuView objects for each Menu; each view will have its own selected item and handle input independently.
Input may be provided to the view asynchronously of other menu operations. It will be queued for later resolution. Because of this, the view will not provide any feedback to the program if the input was useful. An attempt to select a non-existent or disabled menu item will not cause an error because the view will not have a lock on the menu's data. This allows the user input code to feed input to the MenuView in an almost brainless fashion.
MenuView is also a Page, which allows the option for it to be tracked by a Path to implement forward/backward functionality much like in a web browser.
Queued input is resolved by calling update(). This allows control over when the menu handles input, and on what thread. The update() function will call a MenuItem::chose() function if the view's chose() function was called since the last call to update(). In order to make this work, the update() function requires an exclusive lock on the menu data. The function will end early without an update if the MenuView is in use for output by MenuOutputAccess objects.
When update() invokes MenuItem::chose(), it passes along a reference to the MenuView and a MenuAccess object. The access object can be used to make changes to the menu; attempting to get a new access object to the same menu will cause a deadlock. Through the MenuView reference, the MenuItem::chose() function can obtain a context object by calling the context() function. The context object is a boost::any, so it can hold any arbitrary object that may be useful to the program. Since it is maintained with the MenuView, different views of the same menu may have different context objects to allow the MenuItem objects to respond in a way specific to that view.
The MenuOutput class produces and provides the information needed to render a menu. The output object is attached to a MenuView, and multiple outputs may be attached to the same MenuView.
The functionality of the output object is used through a MenuOutputAccess object. This access object should be created just before performing menu output, and it should be destroyed immediately afterwards. While it exists, the access object effectively becomes a shared lock on the menu's data, and the MenuOutput processes which menu items should currently be visible. The access object provides iterator access to the visible menu items, which will remain valid during the shared lock on menu data. The shared lock is relinquished with the destruction of the access object.
Unlike the other parts of the menu system, the output object is not thread-safe. A MenuOutputAccess object will not prevent another thread from attempting to use another MenuOutputAccess object on the same output view. Such multi-threaded use only makes sense to output multiple identical views. This is of more limited use than a potentially differently rendered view, and it is easily achieved by either using multiple MenuOutput objects, or using the same MenuOutputAccess object to render multiple times. This makes the additional effort and resources for thread-safety on output views of questionable value.
The MenuAccess class provides an exclusive lock on a Menu object's data, and functions to modify the menu. The lock is recursive, so a thread may have multiple locks. This allows the MenuItem class to have functions to modify itself without awkward usage between the item and other menu modifications. It also makes it easier to have an implementation of MenuItem::chose() invoke other functions that modify the menu containing the item.
The MenuOutputAccess provides the visible menu items in an iterable list. To do this requires a shared lock on the menu data to ensure the visible list remains valid. This scheme allows multiple threads to use MenuOutputAccess objects on the same view and the same menu. However, changes to the menu and updates to the view require an exclusive lock on the menu data.