Differences

This shows you the differences between two versions of the page.

Link to this comparison view

dev:update_notifications [2018/02/07 17:07] (current)
Line 1: Line 1:
 +====== Update Notifications ======
 +
 +Careful design and implementation of update notification mechanisms is critical to the performance of interactive systems. ​ There are three main approaches to update notification:​
 +
 +  * Low-Frequency App-wide
 +  * Polled
 +  * High-Frequency Direct
 +
 +===== Low-Frequency App-wide =====
 +
 +User interaction with a system tends to be low-frequency and sporadic. ​ Users expect immediate response to changes that they make.  In these low-frequency cases, it is acceptable to simply redraw the entire UI in response to changes made by the user.  This reduces the UI update code to a simple "​Update()"​ function that redraws everything.
 +
 +Since the user can only interact with one part of the system at a time, a very large system with many windows can have a single update notification mechanism shared by all.  This might end up being a bit of a global nightmare, so having update notifications in each window is OK too, so long as they don't need to update other windows in the system. ​ Then a single global notification is a good idea.
 +
 +==== Rosegarden Specific ====
 +
 +The RosegardenDocument::​documentModified() signal is the global app-wide notification mechanism that should be used throughout Rosegarden to trigger an update in response to low-frequency user input. ​ As of 11/2017, this is not the case.  Other notification mechanisms sprinkled throughout Rosegarden (e.g. CompositionObserver) should be switched to using RosegardenDocument::​documentModified().
 +
 +Most edits in any of the editors are typical low-frequency use-cases. ​ E.g. deleting a note, changing a note's duration, deleting a segment, pasting from the clipboard, etc... ​ For these cases, it is acceptable to refresh the entire app UI.
 +
 +==== Implementation Details ====
 +
 +  * The notification will have *no parameters* since it always causes a complete refresh.
 +  * *Do not* send a change notification from a setter.
 +  * Notifiers should call this as *infrequently* as possible.
 +  * Only use for *low-frequency* user interactions.
 +  * Observers (the UI) should respond by doing a *complete refresh*.
 +  * The complete refresh can be optimized, but *don't optimize prematurely*. ​ Try without optimization and see how it looks and feels.
 +  * A single app-wide mechanism would be best, but *slightly* finer grain (subsystem-wide or window-wide) is OK.
 +
 +The first three work together to simplify the code and significantly improve performance. ​ The opposite end of the spectrum would be to include every possible detail related to the change in the change notification. ​ This is a "​fine-grained"​ approach. ​ E.g.
 +
 +  // Don't do this!
 +  void HasChanged(operation,​ segment ID, event ID, value);
 +
 +This would allow the observer to make very fine grained changes to the UI.  E.g. changes just to specific fields. ​ The problem is that this now requires anyone making changes to send an update for every single tiny change. ​ That pushes the HasChanged() calls into the setters. ​ And this means that we need optimized handling in the UI for every single possible change that might come in.  It ends up being a great deal of complicated code.  And it isn't reusable in case we also want to use a polled approach (which is very common).
 +
 +Performance-wise,​ this fine-grained approach should be better for very small changes, but it is terrible for large changes like deleting 10 notes. ​ In the end, it is premature optimization of a simpler approach. ​ The complete refresh approach is highly optimized for large changes and good enough for low-frequency small changes (especially if some detection of actual changes is easy to implement). ​ The code is a lot simpler. ​ Notifiers no longer need to send notifications for every change. ​ They can make a whole slew of changes to the underlying data and then fire off a single notification which will update the entire display.
 +
 +==== Examples ====
 +
 +AudioMixerWindow2 is the most recent as of November 2017 to have been rewritten to use RosegardenDocument::​documentModified() as its primary update notification mechanism. ​ AudioMixerWindow2::​slotDocumentModified() handles the signal and calls updateWidgets() which updates the entire UI.
 +
 +===== Polled =====
 +
 +There are two main cases where polling is ideal:
 +
 +  * Slow polling: when there are high-frequency changes in a system and it is not imperative that the user see them immediately.
 +  * Fast polling: when generating real-time audio or video.
 +
 +The slow polling case results in a significant reduction in CPU usage. ​ The UI is only updated maybe once a second while changes may be coming in at a very high rate, e.g. thousands of updates a second. ​ Slow polling combines nicely with the "​Low-Frequency App-Wide"​ approach in the previous section since it can use the same UI update code.
 +
 +The fast polling case is only needed in special circumstances. ​ Since the display is polling the underlying data quickly, there is no need for any other update mechanism. ​ All changes to the system are immediately reflected in the rapidly updating UI.
 +
 +==== Rosegarden Specific ====
 +
 +When recording, a massive amount of MIDI data is coming in and being added to the document. ​ We don't want to refresh the UI for every single note-on or note-off message. ​ That could result in thousands of updates to the UI per second. ​ Instead, we want to update the segment as data comes in, but only refresh the UI maybe 5 to 10 times per second to show that MIDI is indeed being recorded. ​ The slow polling approach is appropriate here.
 +
 +During playback, the playback position pointer and the clock on the transport window need to be updated constantly to show the current position in the document. ​ A fast polling approach works best here.
 +
 +Sending out MIDI and Audio would probably also fit the fast polling approach. ​ Although those might be driven by external (ALSA/JACK) timers, it's the same idea.
 +
 +==== Implementation Details ====
 +
 +  * Use a timer.
 +  * The timer handler should simply call an update routine that does a complete refresh.
 +  * Optimization on the receiving (polling) side is important in the fast polling case.
 +  * Optimization on the sending (data update) side may be important in the slow polling case if the incoming updates are frequent. ​ Try to use as little CPU as possible when handling updates that come in rapidly.
 +
 +==== Examples ====
 +
 +As of November 2017, there are examples of the two polling approaches in Rosegarden, although I've not been in those areas in a while and I would need to track them down.  I do remember that fixing some recording bugs in Rosegarden required moving the code toward a slow polling approach. ​ Also, the playback position pointer was moved toward a fast polling approach. ​ This was older work, though, so these might not be the best examples to build from.
 +
 +===== High-Frequency Direct =====
 +
 +Immediate response to a high-frequency user update. ​ E.g.: dragging things with the mouse, an indicator that changes with mouse position, fast typing (in an editor).
 +
 +This technique should be avoided if possible. ​ It uses the most CPU and should be reserved only for the very specific situations where it is needed.
 +
 +==== Rosegarden Specific ====
 +
 +This approach is usually used for dragging things on the UI.  E.g. dragging segments on the Segment Canvas (CompositionView) or notes on the notation view.
 +
 +==== Implementation Details ====
 +
 +  * Make as *direct* of a connection as possible.
 +  * Details can be included in the notification if that helps optimize things.
 +  * The update routine should be *highly optimized*.
 +  * The update routine should update *as little* of the UI as possible.
 +  * The update routine should *never* update the entire UI.
 +
 +==== Examples ====
 +
 +As of November 2017, InstrumentStaticSignals::​controlChange() is the latest example of a high-frequency direct update mechanism. ​ AudioStrip::​slotFaderLevelChanged() emits controlChange() when the user changes the fader level. ​ AudioMixerWindow2::​slotControlChange() handles it and passes it on to AudioStrip::​controlChange() which updates the appropriate widget.
 +
 +Since this is typically needed when dragging things on the UI, any mouseMoveEvent() in the system should be an example of this technique.
  
 
 
dev/update_notifications.txt ยท Last modified: 2018/02/07 17:07 (external edit)
Recent changes RSS feed Creative Commons License Valid XHTML 1.0 Valid CSS Driven by DokuWiki