Aldrin Undo/Redo Implementation Struggles

This article serves mostly as an attempt to gather my thoughts on the issue of getting Aldrin to work with the new libzzub 0.3 API, which most prominently features support for undo/redo, but also requires some major reworking of application structure and, something which Aldrin lacks tremendously, a disciplined design.

Aldrin interfaces with libzzub in following way: libzzub maintains the document, or rather: song. It allows accessing the document and making changes to it. Every change made to the document requires a commit procedure, similar to the way source control works. Only after committing a change using zzub.Player.history_commit(), the change becomes visible to the application. After or during the commit, libzzub emits an event for every action taken place: creation of new plugins, connections, deletions and so on. When zzub.Player.undo() is being called, libzzub changes its internal structure and emits events that mirror the changes made to the structure, as if the user himself had done them.

This is good design, opposed to the design we once started with when Aldrin was initially being written. Back then, there was no real event system. Changes made to the document, and their implications had to be guessed, e.g. since deleting a plugin is an action issued by the router view, this means that also the pattern editor and sequencer must be updated, not only the router. So the router told the other views to update. This approach turned into horrible spaghetti, where views had to know each other and tell each other what to do - this knowledge was already present in the libzzub source, but it had to be duplicated in Aldrin as well. You had to know what else would be affected by a certain change.

The right way, which is what I currently trying to move the design to, is simply focusing on a views own task and hooking up to a central event emission system like the one libzzub offers now. A pattern view simply hooks to zzub_plugin_created or zzub_plugin_deleted, and upon activation, updates its view accordingly. When doing changes to the document, at no point does it need to know that there are other views around.

Also, you had to pass view references around so everybody would know everybody. Views would usually resolve other views through the root window, which had all references to any ui object of importance. So views had always to be initialized with a reference to the root window. However, when testing a view, you would often like to only test the view you were working on, not the other ones dependent on it. The current structure makes this impossible.

To solve this problem, I introduced something I dubbed "Aldrin COM" (COM = Component Object Model), which is basically a very primitive, Python-centric implementation of Firefox' XPCOM. Modules would register their classes as component factories to a central COM hub, along with some helpful metadata on whether they were intended to be instantiated only once ("singletons"), or what kind of component category they would like to belong to. This allows to instantiate classes and request live objects, while storing only one global weak reference under a human readable name like e.g. "aldrin.core.player" for zzub.Player. You could also instantiate objects for a certain category like "menuitem.tool", and populate a menu with instances of classes implementing that category. This allows objects to know only as little of each other as needed.

Aldrin COM also serves as an extension system for Aldrin. Through importing new modules and registering classes for certain categories, you can easily extend the application in exactly the same way the core components work. There remains a configuration view to be added for importing your modules easily, but the foundation exists.

So what I need to do right now is, as far as I gather, to remove all cross references of components between each other, and replace them with calls to the event distribution hub.

To complicate things further, some components store persistent UI related information not managed by zzub, and removing cross references would either require each component to store its own copy of that information (a potential source of asynchronous behaviour), or for me to establish a central place for storing such kind of information. As an example for the problem described, the pattern view toolbar controls the currently selected pattern of the pattern view, requiring both components to interface with each other. However the pattern view toolbar provides functionality that could as well be used in conjunction with any other view. A more complex example: the current track selected in the sequencer should control the plugin and pattern currently visible in the pattern editor.

I guess a proper and easy to implement solution would be to add a libzzub-like layer for a view related document, which stores things such as "currently selected plugins/patterns/waves" and so on; takes commands, alters its data, and emits events to which views can react. This way views would never have to talk to each other, but instead only communicate with the UI manager (as i would call it) and zzub.

It would be even better if common actions interacting with the song document would always pass through the UI manager, which also abstracts away the tedious task of committing changes, and streamlines common tasks such as "insert/add new plugin, remove old one, change pattern size", which are currently unfortunately bound to the view first using them. It would also make life much easier for third party coders seeking to extend Aldrin with new functionality.

Lastly, I need to turn all context menus into independent components as well. A context menu for a plugin, currently implemented in the router view, could as well be used in the sequencer. Separating menu code from view code also makes sure that no action takes influence on the view directly.

Comments

You the man Paniq!

It sounds like a hell of a refactoring process, but you'll have big brass coder balls if you can pull it off. If you do it right, it could be totally wicked. Good Luck!!!!

-BLuRry

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.

Back to top