Note: This post is going to make me even less popular with the Rosegarden developer community than I already am.

My topic for today ...

There are places in the codebase where ViewElements are constructed only to be destructed shortly thereafter, sometimes immediately. Is this a problem? Maybe, maybe not.

My particular interest is the Matrix Editor. I submitted Bug #1626 and Merge Request #57 because I found that MatrixElements are very expensive to construct and destruct. Not so much the objects themselves, but adding and removing their QAbstractGraphicsShapeItems from the QGraphicsScene. This is both documented and experimentally observable.

The solution in Merge Request #57 was to not do the adding/removing in MatrixElement::reconfigure(), which is where the element's positions/shapes/sizes/etc are set. Instead, two or more QAbstractGraphicsShapeItems are permanently maintained by the MatrixElement object, and reconfigured/hidden/shown as necessary.

I thought that would be sufficient because I believed MatrixElements were only constructed when the user created a note, and persisted until they were erased by her/him. So the constructor still creates and adds the Qt objects, and the destructor removes them.

In the process of working on Feature Request #504 I discovered there is far more "thrash" constructing/destructing MatrixElements than I thought. For example, Matrix Editor tools such as MatrixPainter construct a MatrixElement, but also a MatrixInsertionCommand. The latter triggers a ViewSegment::eventAdded() call (I'm skipping several steps here) which eventually constructs a new MatrixElement identical to the first, and the original one is then destructed.

The above could probably be "fixed" (if fixing is necessary), but a worse situation happens in ViewSegment::findEvent(). That constructs a polymorphic ViewElement for the sole purpose of using it to find matching existing ViewElements bound to the same Event, and then immediately destructs it. (In an excellent example of good coding practice, the local variable is appropriately named "dummy".) The issue is that findEvent() is called several times per note during many Matrix Editor user interactions, and with lots of notes and expensive constructors/destructors ... well, the problem is obvious.

Again, it could be argued that none of this matters. Hardware is very fast these days, users haven't complained about performance, and I only did when it impacted me working with many thousands of notes at once. So maybe the solution is to implement a wait cursor that shows up when needed and move on to more important bugs (IMO having the program hang for 15-30 seconds or more with no feedback to the user is unacceptable).

But as I've written before, I'm an optimization freak -- appropriate optimization, not premature (that's well and correctly known as the root of all evil) -- and all of the above bothers me. As an immediate remedy I'm going to add a static CachedFactory (is that a real design pattern?) to the MatrixElement class which will keep a vector of QAbstractGraphicsShapeItems (actually two, one of QGraphicsRectItems for notes and one of QGraphicsPolygonItems for percussion). When the MatrixElement constructor (actually the reconfigure() method) needs one it will pop it off and return it, or if the vector is empty do the expensive construction and scene::addItem(). Likewise the MatrixElement destructor will return the items, and the factory will call hide() and pop them back onto the vector for reuse.

This will solve a lot of the inefficiency, and maybe that's enough. But I'm also still thinking about ViewSegment::findEvent(). What if Events had an std::set of ViewElements that referenced them? Each ViewElement constructor would register itself and be added to the set, and its destructor would unregister and it would be removed. Then findEvent() could just return the set's begin() iterator instead doing the current equal_range() lookup. The important point is not the lookup, but that the expensive "dummy" ViewElement object wouldn't be needed.

I get the feeling that in general the RG architecture frowns on keeping duplicate copies of data, which is a design philosophy I support. (There's always the possibility that the copies can get out of sync, with disastrous results.) But I also temper my support with pragmatism. RG makes very heavy use of STL design patterns (another thing I like). But it's cases like this where a full understanding of the implications and trade-offs need to be considered.

Note that I never planned on doing this kind of deep surgery on the RG code. I came here to fix one bug and implement one feature, yet in doing so I keep falling deeper and deeper down the rabbit hole. I've tried to stay strictly within the Matrix Editor sources (with the exception of removing the unneeded menus and buttons in other windows that brought up the Percussion Editor variant of it I deprecated) but the ideas in this post go way further. Please understand that I haven't gone looking for (IMO) problems like the above -- they just seem to find me.

Comments/reactions/criticisms/suggestions welcome. And I obviously understand that there's a good chance anything I do (including the merge requests I've already submitted) will end up living forever only in my own repository fork. I guess that's OK -- I really like Rosegarden, and if and when I hammer into the shape I need I plan on using it heavily. My college advisor used to tell me, "The dark side of ability is choice". If I didn't have the chops (I claim) to make these changes I'd have to accept the program as-is, or move to another software package. Probably would have been a lot easier in the long run.


_______________________________________________
Rosegarden-devel mailing list
Rosegarden-devel@lists.sourceforge.net - use the link below to unsubscribe
https://lists.sourceforge.net/lists/listinfo/rosegarden-devel

Reply via email to