There's a saying that goes, "If you have one problem, you have a difficulty; if you have more than one problem, you have an opportunity." Many of the points below have been suggested by issues described in the mailing lists; the suggested solutions in this document often help solve more than one issue.
Some of these suggestions have already been proposed on the mailing list, but they seemed so useful that they are reproduced here.
Note that these suggestions have been conceived from scratch as if there were no existing code base requiring backward compatibility, nor are they particularly concerned with how well they integrate with native components. Sometimes it's valuable to take a step back and reconsider how it should have been done. It may lead to a lot of rework and trauma, but the result will be better for it. As a result, many of these suggestions are candidates for a 3.0 timeframe rather than something that can be done within the 2.x framework.
This section gives a summary of some proposed revisions. For the most part, the ideas are independent, in that they can be adopted separately. However, many of the ideas complement each other, so that one may enable greater capabilities in another.
There's a half-baked appendix with some musings about API revisions, but neither completeness nor consistency is guaranteed.
There are no direct suggestions for performance improvement (other than to profile the library and eliminate hot spots, something that should also be done), but a number of the other suggestions have the side-effect of improving the performance. Note that it's more important to improve perceived performance, even at the cost of actual performance, than it is to get micro-focused on the number of CPU cycles used by an application.
The simplest way to improve performance, both perceived and real, is to reduce flicker. Generating pixels is slow, so it's worth many dozens of tests to eliminate even a single paint event. Fortunately, neither hiding the paint DC nor revising the repaint logic require dozens of tests; in fact, they are likely to be faster than the logic they replace. Also, separate GUI and worker threads will improve responsiveness on a multi-processor system.
Another suggestion for performance improvement is to provide caching support. For many widgets, it has the potential for a noticeable increase in speed.
Two other, more minor, improvements are composite classes and lazy initialization.
Instead of having long and error-prone parameter sequences for constructors, use an initializer class instead.
An initializer class is something that marshals the parameters for another class. If the initializer class is on the stack (which would be the usual case), the difference in performance is negligible. With a good compiler, the performance impact can be zero.
So how does an initializer class work? Here's a simple example.
class wxFooInit { ... };
class wxFoo
{
public:
wxFoo(wxFooInit &in);
...
};
The idea is that the wxFooInit simulates the long parameter list. The difference is that wxFooInit can be filled in by using mnemonic names.
wxFooInit in;
in.Parent(boss).Id(id).name("top").SimpleBorder().WantsChars();
new wxFoo(in);
The initializer class doesn't even have to be given a name. If the parameters are only going to be used once, the initializer class can be a temporary.
new wxFoo(wxFooInit()
.Parent(boss)
.Id(id)
.name("top")
.SimpleBorder()
.WantsChars()
);
As a result, the parameters to wxFoo tend to be self-documenting and are harder to get wrong.
Initializer classes are inherently more flexible. Member functions can be provided for various common initialization patterns, making it more likely that the user code will be correct. Member functions can also validate that they are being used correctly (although, in general, member functions should be very short so they can be inlined).
Initializer classes are derived the same way as the classes they are used to initialize.
class wxBarInit : public wxFooInit { ... };
class wxBar : public wxFoo { ... };
Frequently, manifest constants used to initialize values can be defined in the initializer class and copied into the regular class.
class wxFooInit
{
public:
enum { flag1, flag2, flag3, flag_last } my_flags;
};
class wxFoo
{
typedef wxFooInit::my_flags my_flags;
};
Further, values can be densely allocated by basing them on the values from the parent class.
class wxBarInit : public wxFooInit
{
enum ( flag4 = wxFooInit::flag_last, flag5, flag_last };
};
The transition to initializer classes can be very gradual. A constructor for an initializer class can be added without modifying the existing constructors. Once initializer classes are present throughout the hierarchy, the current constructors can be deprecated and eventually phased out.
In the current design, there are two parallel hierarchies, one for widgets and one for sizers. The widget hierarchy should be eliminated and embedded in the sizer hierarchy.
A widget no longer tracks its parent or its children. Instead of instantiating a widget in relation to its parent widget, it's instantiated in relation to its associated sizer. A widget's parent and children can still be determined, although it's a bit trickier. (The API doesn't need to be changed, so users wouldn't even know the difference, although an enhancement to provide a "child iterator" would be helpful.)
Note that an active widget will always have an associated sizer, but the reverse is not true. Sizers can exist in the tree without an associated widget. A top-level widget (a widget instantiated with an associated sizer of NULL) is assigned to a sizer generated for the purpose (and that sizer is attached to a sizer maintained by the framework, so that all widgets are eventually descended from this top-level sizer).
The concept of a client area goes away, replaced by a sub-sizer. Current widgets with client areas will need to be revised to create an interior sizer for this purpose. Widgets with multiple internal divisions will have a sizer for each division (making it possible to use sizers to lay out the status bar of a wxFrame, for example).
The size and location information currently part of the widget is removed; all positioning is the responsibility of the sizer. OnSize() is gone; any logic that was in it will need to be moved to the associated sizer. (In fact, a widget can't even change its own location any more.) The flags to hide a window and collapse a sizer are kept in the sizer, as are other flags solely concerned with layout.
This loses no functionality. Everything that the current scheme can do can still be done with this scheme.
Sizers have increased functionality. Perhaps the most interesting is a widget with a "natural" size that can change over time. If the associated sizer is marked as being naturally sized, marking the sizer as dirty also marks the parent's sizer as dirty. If the parent is naturally sized, then its parent is also marked dirty, and so on. Thus, the next layout cycle will resize everything to fit. This eliminates the user's need to find the appropriate enclosing widget and force a Layout() on it; it's all done automatically.
Additional sizers will be needed so that some current layouts can work compatibly. For one, a fixed-position sizer whose size never changes and that places its children in fixed locations with fixed sizes. For another, a sizer that provides constraint-like positioning (replacing the current constraint classes). And the top-level sizer probably needs to deal with the Z-order of its contained widgets.
If sizers are separated from widgets, that means that many more individually-allocated objects will be needed. The allocate and free operations (new and delete) are not horribly expensive, but they're not that cheap, either. It's better to have a single large allocate/free than a bunch of smaller ones.
The framework should provide support so that any number of these objects can be composed into a larger superwidget. Moreover, it should be possible to compose superwidgets into even larger objects.
A window object that should be treated as a group might need several sizers and a number of widgets. The framework should provide support so that these can be constructed in a single object.
This conflicts with the way that the framework takes control of objects and ensures their deletion. If several objects are composed into one metaobject, the framework should make it possible to mark such contained objects so that they are not deleted.
The strategy for this is to add a flag to both sizers and widgets that essentially says "don't delete." Objects are always deleted in a specific depth-first order, so if the metaobject is derived from the last object to be deleted (normally the top-level sizer) and all the contained objects are marked, the metaobject can be deleted as a group.
The framework would provide some useful pre-bundled units; for example, a wxFrame would be composed of four sizers and two widgets. And a user could construct his own composite class with all of the widgets in his frame pre-allocated. Combined with lazy initialization, a composite object could even contain widgets that are rarely used, with little impact on the startup time. (Such objects would be hard to get right manually, so a GUI tool that built such composite classes would need to be provided; this would make sure that all of the initialization and tear-down took place properly.)
This concept of a composite class is sometimes called "widget is-a sizer" to reflect the fact that the primary class from which the metaobject is derived is usually a sizer. That's not necessary, but it's not necessarily a bad thing, either.
The amount of gruntwork to get even a minimal application off the ground can be intimidating. Quite a bit of code has to be written that just specifies the relationship of the widgets and how they work together. Computers are better at this complexity, so it should be assumed that a GUI builder will be used to create and manage it. In the fullness of time, it can be hoped that there will be many GUI builders, but at a minimum the framework should provide one.
In addition to the usual functions, the provided GUI builder should be able to create composite objects for the application.
Since the intent is to encourage innovation in GUI builders, it's likely that the builder subproject should be organized as a library of common functions (or common snippets?) that can be shared among all GUI builders rather than having such builders all have to reinvent the same wheels.
There are a lot of things that cause flicker, but in the end they come down to painting too often. Ideally, the framework should encourage a design strategy that minimizes the opportunity to do unnecessary painting. This proposal replaces the current ad-hoc painting with a more carefully controlled scheme.
In essence, it minimizes the opportunity to paint by not making the screen's DC (viz., a wxClientDC) publicly available, but passing a DC (viz., the wxPaintDC) to the widget's EVT_PAINT callback so that painting can only take place in that context. Stated that way, it's a pretty trivial change, but it has enormous impact on current code, so it's something that needs to be done gradually. The hardest part of this proposal is to come up with tactics to allow a graceful transition.
(There's probably a reason why OnPaint is an event rather than a virtual function call, possibly so that it can propagate up the hierarchy until it's handled. This does not replace that mechanism, but if some of the other suggestions are adopted, there will be better methods to deal with that, so consideration should be given to removing the event and replacing it with a virtual function call.)
Here's a rough outline of possible tactics:
Note that passing the DC in the event means that the DC will be created by the framework; there will be no more cases where a user forgets to create one. In addition, there will be no more cases of a user painting to a window only to have it disappear when the frame is repainted. And lastly, it will reduce the flicker.
There are a lot of things that cause flicker, but in the end they come down to painting too often. Ideally, the framework should not trigger a redisplay too frequently even when events are occurring rapidly. This proposal revises the current implementation with some additional functionality to make processing occur more smoothly.
At the top level, there are two routines, ProcessEvents() and RethinkDisplay(). Processing is divided into three phases: event processing, layout, and painting. In general, operations during these phases are disjoint; no painting occurs during event processing and vice versa.
During event processing, events are removed from a queue and forwarded to a widget for processing. The framework has one eye on the clock so that it can trigger screen redisplays at reasonable times, so there's no reason for user code to care about exactly when redisplays occur.
During layout, the position and size are determined for all widgets. If the widget hierarchy has been subsumed by the sizer hierarchy, the logic for layout is completely contained in sizers.
During painting, the screen's DC is given to the widget for it to render itself.
This routine is the basic event loop. It receives events and forwards them to the correct widget. At appropriate times, it calls RethinkDisplay() to update the image on the screen. (If the framework has access to the "vertical retrace" event, RethinkDisplay() should be called no more often than once per screen update.)
ProcessEvents() tries to accumulate multiple events whenever it can. When it empties its incoming event list, it will delay a short period of time to see if any more events occur. If no more events occur, it calls RethinkDisplay(). The delay should be short enough so that a user preceives the response as immediate, but long enough so that rapid events (mouse movement) don't cause unnecessary updates (flicker). This is something that will need tuning by an ergonomics specialist, but a time in the range of the frame update frequency (60Hz) is probably reasonable.
ProcessEvents() also tries to keep the display updated, even if a continuous flurry of events occurs. The first event after a RethinkDisplay() records its time; if the display does not get updated for a "longish" period of time, ProcessEvents() will call RethinkDisplay() even if there are still events to process. The time would need to be on a scale so that a user would not see updates as being too jumpy, but it doesn't need to be as immediate as the first timeout. This is also something that will need tuning by an ergonomics specialist, but film rates are 24 frames per second, so a time in the range of one-fifteenth or one-twentieth of a second is probably reasonable. (Note that one-fifteenth of a second is a natural divisor of typical monitor display rates of 30Hz, 60Hz, and 75Hz, so it might be the better choice.)
There's also a ForceRedisplay() routine that sets a flag. If ProcessEvents() sees this flag set, it causes a call to RethinkDisplay() at the next vertical trace event. Events that represent real user actions, like clicking on a button, can call this routine to cause an immediate update. (This eliminates much of the need for wxYield() and all the mess it causes.)
ProcessEvents() actually maintains two queues. A QueueIdleEvent() routine puts an event on the second queue. If there's nothing on the regular event queue, ProcessEvents() removes the first entry on the idle queue and dispatches it. Timeouts are not affected by idle events, so RethinkDisplay() will still be called at the appropriate times; an idle event can feel free to requeue itself as needed to complete its own processing.
RethinkDisplay() does two things: it updates the layout and repaints anything that's changed.
Update layout. This walks the tree of sizers and reevaluates any sizer that is marked dirty. If a size or location is changed, the sizer is marked as needing repainting. Evaluating a sizer clears the sizer's dirty bit.
Evaluating a sizer automatically evaluates its children, so the treewalk can be optimized by not descending to the children of any evaluated sizer. Also, there's no need to descend to the children of fixed sizers.
The net result of this pass is that any widgets that have been significantly changed will have their dirty bit set so that they will be repainted in the next phase.
Repaint changes. This walks the tree of sizers and repaints any widget that is marked as needing repainting. Painting a widget clears the widget's dirty bit.
Usually, widgets would be repainted with widgets that are "behind" other widgets being repainted before widgets that are more in the foreground, but with the global view provided by the tree, there are some chances for optimization. Obviously, widgets that are completely covered don't need to be repainted, but there are other opportunities as well. For example, a widget that is shifted but not otherwise modified could be cheaply relocated by the framework rather than going through a potentially-expensive OnPaint invocation.
Automatically create a separate thread and run the GUI in one and the worker actions in the other. The GUI thread is responsible for updating the display. All GUI actions are sent via events to the worker thread. The worker thread sends actions to the GUI via events as well.
There will have to be careful coordination between the GUI and worker threads, which will add some overhead, but this concept is naturally synergistic with some of the other concepts described here:
And, needless to say, separate threads are better able to take advantage of multiple processors, which are becoming more common, even in desktop machines.
Initializing a widget can be very expensive. It may be necessary to acquire secondary resources (such as native widgets), open files, or otherwise do things that take time. In the end, if it's a widget that's never exposed (a tab panel that's never selected) then all that effort spent initializing it is not only wasted but also makes the application feel less responsive.
On the other hand, it can be really useful to have a widget instantiated so that it can be laid out, accept events, and otherwise participate normally, even if it is not currently displayed.
The solution to this is a function, FirstTime(), that is called just before the first time a widget is called on to render itself. It is in this routine that the widget would acquire any resources it needed.
Note that the widget itself could detect this condition (by initializing a flag during construction and checking it when rendering), but providing it as part of the framework encourages the user to think in those terms as well.
Some widgets have very modest requirements, yet for them to operate properly, the widget must provide some fairly complex logic to render itself properly under all circumstances. A way of making it easy to implement simple widgets is for the widget to create the pixels off-screen and have the framework take care of rendering them. This is particularly true for widgets that don't change much (a button doesn't change its text very often).
Although in the extreme case the widget would retain a complete image off-screen, the framework should support caching any fragments of the image as needed. It should also allow the caching of pixels that aren't (currently) displayed so that an application need not recalculate images so often.
For a point of comparison, the Amiga's window manager, Intuition, did this. Each widget could specify what kind of interaction it wanted, from none (the widget painted only to the cached image and the system saw to it that the screen was updated) to very detailed (the application received each obscuring and exposure event and was solely responsible for rendering to the screen). On a 2MHz machine, Intuition is still more responsive than most modern window managers on a 2GHz machine, so it must have been doing something right.
In some cases, a widget can assume different shapes. The ideal would be that the sizer system can negotiate with the widget so that the best layout can be determined.
Negotiation need not be complex. This suggestion is very simple, but would still provide a great deal of flexibility. This may not be the optimal suggestion (consider it still only half-baked), but it's a basis for the future.
In essence, instead of just asking a widget for its best size, the widget should be offered a size. If the offered size is acceptable, the widget just returns it. Otherwise, the widget returns a size it prefers.
By convention, if the size is (0,0), the widget should return a size based upon its best guess. If one axis of the offered size is zero, the widget should try to return a size with the non-zero axis the same. If both axes are non-zero, the widget should return a different size only if the offered size causes serious heartburn (too small, too much out of proportion, or whatever). (Maybe these options could be better expressed in several parameters; that's something that will need to be worked out.)
In general, a dumb widget could always return the same best-guess estimate and it would work fine. Over time, more sizers will start negotiating and smart objects will start replying effectively; eventually, layouts will tend to be more optimized.
Here's a simple example of how a smart sizer might work. Say there's a component that wants to be at least some minimum width (the height is scrollable, but it works better if the full width is visible; think of a text description with some buttons). Imagine a sizer that changes its orientation to adapt to the screen width it has available. If its subcomponents will fit side-by-side, it uses a horizontal orientation, but if they don't, it will use a vertical orientation.
There two major types of debugging: debugging the library and debugging the application. There's some overlap, but for the most part, the features needed are different. The framework should provide support for debugging applications that does not require a full-blown debugging library. In other words, it should be possible to debug applications using a production library.
There's a good reason for this. Some operating system packagers are now starting to provide a production version of the library with their system. Users who wish to debug their applications have a choice of downloading, building, and maintaining a debugging library or developing their own debugging support. Neither choice is particularly palatable, given that the library already has the needed features, but they are shut off.
This suggestion is that the debugging facilities that would be useful to an application developer be refactored so that they are available with a production library. A debugging library would only need to be created by library developers, who would presumably be willing to go to the effort.
A good analogy is the C library. It has facilities for debugging user programs, but the C production library supports both debugging builds of the application and production builds of the application. In fact, the suggestion is that it work the same way: user debugging is turned on by setting a compile-time #define. If the #define is not set, a production version of the application is built.
Here is an example of how this might play out in a library header.
class example1 {
private:
int CheckExampleContract( ... );
int RealExampleFunction( ... );
public:
inline int ExampleFunction( ... ) {
#ifdef DEBUGGING
int = CheckExampleContract( ... );
if (int != 0) return int;
#endif // DEBUGGING
return RealExampleFunction( ... );
}
};
Here's another example, where debugging mode selects a separate function, useful for when the function's flow varies between the two modes.
class example2 {
private:
int DebugExampleFunction( ... );
int RealExampleFunction( ... );
public:
inline int ExampleFunction( ... ) {
#ifdef DEBUGGING
return DebugExampleFunction( ... );
#else
return RealExampleFunction( ... );
#endif // DEBUGGING
}
};
The debugging checks are made only if the #define is set; in production mode, the real function is called with no overhead. The impact on the size of the production library is modest (the function to check the contract is present but never called, but that's no worse than other functions that are present and never called), and if bound statically there's no impact at all.
SourceForge's Bug Tracker is currently used for bug reporting and management. The nicest thing you can say about it is that it's free. The only polite thing you can say about it is that it sucks rocks through a straw.
Most bug trackers are designed to produce the reports that managers think they need. That they happen to provide a few facilities for developers to deal with bugs is a mere side-effect; most bug "management" is done by making copies of bug numbers on a piece of paper (or browser bookmarks in more modern systems). By contrast, Bugzilla was conceived by developers and implements facilities for developers to track and manage bugs. Unsurprisingly, this approach also provides the management reports as a side-effect, and it actually provides better reports since the information in the database tends to reflect reality much more closely.
Bugzilla is widely used by many organizations. It's possible
that one of them would be willing to host the wxWidgets bug
tracker. Failing that, perhaps someone like solidsteel.nl
(who provide the forums) would be willing to host a bug tracker as
well. Implementing Bugzilla in a SourceForge account is
possible, but tricky (I've done it, but I wouldn't recommend the
experience to anyone). However, there's a new option: phpBugTracker, which provides
much of the functionality of Bugzilla and seems to play nice with
SourceForge. There's also Bosco,
although it seems fallow.
The usual operation is that a widget is created and then a sequence of operations is performed on it. This is a simplification to give the general idea; an actual implemetation would have optimizations not described here.
The class constructor is responsible for doing any necessary initialization. However, unlike the current design, it does no sizing (in fact, sizing information is probably not available; in fact, sizing would not generally be done until the next display cycle). The widget is responsible for creating any necessary sub-sizers and child widgets.
This call is made from a sizer during layout. As currently, this call requests that the widget report the size it would prefer to be. In general, only leaf widgets are consulted; sizers will automatically determine the sizes of the containing widgets. (The call is not mandatory; for example, a fixed-positioning sizer may not need to make the call.)
The parameter is the suggested size. If the size is acceptable, it should simply be returned. If there's something wrong with it, an adjusted size should be returned. (This is for the future; it's to allow for negotiation between sizers and widgets. It's something a text box might want to do, since text can be reflowed in a number of ways.)
In reality, sizers will need to be able to consult non-leaf widgets to allow them to influence the layout. Thus, there will also need to be entrypoints for the widget to report the size needed for margins, annotations, and the like.
This is the notification that a new size (or initial size) is in effect. It's intended as a hook in case the widget has any cached information that depends on the size. The call is only advisory; there's no need for the widget to retain the size as it is available from the associated sizer.
The size isn't a parameter; the widget can get it from the associated sizer. Note that only the size is implied; the position may not yet be set.
The sizer system will automatically notify any children of their sizes.
The widget is given a DC and a rectangle within the DC where it should paint itself. The rectangle must be the same size as the last SetNewSize() call.
It is the responsibility of the widget to render itself into the rectangle. It doesn't matter how. It may require telling its children to render themselves within a sub-rectangle or the widget could cache its pixels and blit them into the rectangle. (The framework should assist the widget in creating and managing such an off-screen cache.)
In reality, this operation will be divided into pieces for optimization purposes. For example, there may be individual PaintYourBackground(), PaintYourForeground(), PaintYourWagon(), or whatever. The widget may wish to accumulate a list of damaged subregions so that it can optimize its own behavior when this call is made (the framework should assist the widget with managing damaged subregions).
PaintYourself() is only called if some portion of the widget is exposed. If the widget is never exposed, there's no need to do expensive initialization, like secondary memory allocations and the like. Therefore, there's a function, FirstTime(), that's called just before the widget is initially rendered. This provides lazy evaluation of the class, and often can lead to a more responsive interface by delaying setup until needed.
When the widget detects that its image has changed, it calls this function (defined in the superclass) to notify the framework that it should call PaintYourself() on the widget at the earliest opportunity.
The flag for this is actually kept in the associated sizer. A widget's content is initially marked dirty upon creation. The condition is cleared whenever PaintYourself() is called.
When the widget detects that its size has been impacted, it calls this function (defined in the superclass) to notify the framework that it should consider resizing the widget. (Note that it does not imply that the framework will resize the widget.)
The flag for this is actually kept in the associated sizer. The size is initially marked dirty upon creation. The condition is cleared whenever the sizer is reevaluated.