2007-11-25

Thoughts on Gadgets and Decorations

Another period of not much happening. This has mainly been caused by another spike in the complexity of the code I’m writing at work - the more complicated it gets, the less inclined I am to sit at a keyboard when I get home. The code I get paid to write takes precedence over the code I write for myself, unfortunately.

Anyhoo. One of the problems with Woopsi at the moment is that it uses a “gadget type” enum to identify the type of each gadget (window, screen, button, etc). The reason for this was to avoid using runtime type identification (RTTI), which I’d heard was too slow to be useful. Much the same criticism has been levelled at reflection in .NET, but I’ll use that when I need to - if performance is critical, you don’t use .NET in the first place.

Jeff pointed out that using the gadget type variable was causing a problem in his code - he’s working on subclassing Woopsi’s gadgets in order to extend their functionality, so having a set list of possible gadgets will limit what he can achieve. A valid point.

To be honest, I wasn’t expecting anyone to to use Woopsi at the level Jeff is - if anyone used it at all, I was working on the assumption that people would cobble together GUIs using the existing gadgets rather than create their own. I’d planned to write documentation to allow for it, but having someone jump straight into advanced coding at such an early stage has swapped my priorities around somewhat, and I’ve been concentrating on tidying things up and improving the code rather than adding new functionality.

I’ve been looking into ways around the gadget type problem. Everything I’ve read about RTTI suggests that it isn’t appropriate for the DS. It was omitted from the Embedded C++ spec because it adds bulk to binaries, and every article that discusses C++ performance says to avoid it. There’s a hugely informative set of pages here by an XBox developer that discusses C++ optimisation, and his advice is to not only avoid RTTI, but refactor if you find yourself in a situation where RTTI is a valid solution. You should never need to detect the type of an object - you should be able to call the same function on all of your objects and have the object decide what to do with that call. For example, Woopsi’s window class does this in its click() function:

loop through gadgets
    if gadget is a depth gadget
        remember clicked gadget
        swap depths
    else 
        end click to gadget
    end
next

The gadget type check should not be there - all of the code to detect clicks within the depth gadget should be contained within the depth gadget class.

That’s easy enough, but then I run into another problem. Each gadget’s vector of sub-gadgets can contain decorations that are really a part of the parent (depth gadgets, borders, etc), and genuine children (buttons, bitmaps, etc). If I depth-swap a window, I don’t want it to swap depths with its parent screen’s title bar, which is what would happen if I remove type checking.

One solution I came up with for this was to introduce another gadget vector to store nothing but gadget decorations, but then I’d need arrays of vectors in order to pass everything through the collision and drawing functions. It’ll end up being a mess. The other solution I’ve come up with is to store both decorations and children in the same vector, but keep a count of the number of decorations (and thus the index value of the first child). I’d need new “addDecoration()” and “removeDecoration()” functions, and a “_decorationCount” variable. Each time a new decoration is added, the decoration count increases. Each time a decoration is removed, the decoration count decreases. When I run any of the depth swap routines (or anything else that should operate on children, not decorations), I work only with those gadgets higher up the array than the decorations. This removes the need for the second vector or any gadget type identification.

Comments

Jeff on 2007-11-25 at 23:04 said:

Sorry to have been a burden.

:-)

Actually, my need for subclassing gadgets really happened because of the lack of a ‘reference constant’ to tie the gadget back to a corresponding model value. Once that arrived, I was able to remove a few of my custom subclasses. However, its still a very desirable feature to have, if only because a C++ class hierarchy shouldn’t dictate what its subclasses can be capable of, wherever possible. Someone will always come along and use your code in ways you weren’t expecting, and unless you have a good reason to prevent this, you shouldn’t.

As to the problem of the depth gadget, thats one area where I think you might still have confused things. Your inline code (above) shows that its not the depth gadget that knows it changes depth, its the enclosing window. Instead, the depth gadget should just send a message to its container saying “change depth” - of course, it can’t do that if changeDepth() isn’t part of Gadget:: unless it uses RTTI to confirm that its container is in fact a Window::

Microsoft avoid this problem in Win32 by having all communications of this sort be sent through a generic MESSAGE funnel. Paraphrasing, you can call SendMessage(window,message,param1,param2) to send any message you like - any message w window does not handle is supposed to be silently swallowed and return a zero.

(Interestingly, Apples preferred platform these days (Cocoa) takes the same tack - you can send any message to any object, whereas Carbon (which is more suited to C++ static linkage) does not)

You could have DepthGadget->click() call _container->SendMessage(CHANGE_DEPTH,0,0)

Personally, I’m still a fan of RTTI, and I can’t quite see how it can “bulk up” binaries, given that the DS is a single-binary-no-shared-libraries execution model. If you have to deal with multiple inheritance with merging RTTI tables from shared libraries, then sure it becomes complex. In the DS, you are going to have a few extra u32’s stored per class.

Remember, I didn’t turn on RTTI in my makefiles, its already enabled. All the overhead is already in any PAlib binary you compile. Its just that you aren’t benefitting from it.

Jeff on 2007-11-25 at 23:15 said:

With regard to decorations, I wonder whether the decoration stuff is complex because you are setting your scope too wide. I can’t see any classes (yet) whose decorations leave a non-rectangular area for gadgets to be drawn into. There should be no need to clip gadgets against decorations because gadgets should not be put into decoration space, and at worse you click gadgets against the ‘client rectangle’

I’ve been thinking about how to do a List gadget (which has been discussed previously). At the moment, I’m of the opinion that it needs to be a rectangular gadget which takes a ‘datasource’ object (which is almost inspired by OSX Cocoa)

The list gadget expects the datasource to - tell it how many entities are in the list - draw entry (n) in rectangle ® in state (selected,non-selected)

The list gadget can handle all the appropriate scrolling, etc and just calls back to the datasource when it wants to actually draw. The trick comes in what coordinate system it asks the datasource to draw into. Currently, Woopsi would pass a ‘Hardware Coordinate’ bounds in and expect the data source to offset its drawing appropriately.

On OS9 (not really, but close enough for discussion), it would have passed in a ‘graphic port’ which had methods to draw graphic primitives - you consider the graphic port to be based at (0,0) with a fixed (w,h) and it handles clipping.

I suspect that Woopsi might benefit from this approach - it seems like this is what I expected a SuperBitmap to be, whereas I think that its actually a “gadget+graphic port” welded together, though I don’t think it does the coordinate transforms for me.

Jeff on 2007-11-25 at 23:17 said:

Doh! By “click gadgets against” I meant “clip gadgets against”

ant on 2007-11-26 at 00:54 said:

Regarding depth swapping, all depth swap functionality should be part of the Gadget class - the fact that it’s currently limited to Screen and Window classes is part of the problem.

The screen title bar and associated buttons is an example of a decoration that can be overwritten by child gadgets. The screen can also have a SuperBitmap attached to it that should also be considered a decoration (though I might remove that and add in windows that can be permanently at the back of the stack).

Messaging means creating message queues and everything else associated with it. I can see that it would be handy to implement, but I can also see that it could start to get complicated to work with.

The list idea sounds good, I can definitely see that working.

Did I get around to implementing the client rect thing yet? I think I was going to look at that after fixing the problems with Window border offsets (everything is blahblah+1, but I could return a graphic port object with extra drawing functionality instead. Or just supply the drawing tools as static methods of another class that accept a graphic port (basically a rect - might just use that) as a parameter. I could move the internal drawing functions out of the Gadget class, then, which would tidy things up considerably.

Jeff on 2007-11-26 at 02:11 said:

Messaging doesn’t have to imply queues. Your current ‘raiseClick()’ method is just a concrete implementation of SendMessage(CLICK,…).

raiseClick() is nicer because it gives a strong data typing component to the message being sent - ie, the callee can be confident that the caller has passed the correct data types because the prototype prevents error, whereas for SendMessage(), the paranoid callee needs to ensure that the other arguments are present.

In the past I’ve done systems that were non-fool-tolerant (as opposed to fool-proof which is impossible). Our message passing was of the form:

RESULTTYPE SendMessage(RECIPIENT r, MESSAGETYPE m, …);

where thats an explicit … implying a function. Then, inside the callee, it did:

switch (msgtype) { case CLICK: x = va_arg(args,u32); y = va_arg(args,u32); return processclick(x,y);

You get absolutely no safety margin because its insanely easy to get your data types wrong. But its also fairly efficient in terms of the number of explicit API’s you need. Remember, if you complain about RTTI bloating your objects, you need to consider the size of the vtable as well. Every (virtual) method you add to a class burns another u32 so that subclasses can overload that method.

The typesafe way to make this all work, of course, is to add an inline non-virtual method to send the message that has correct prototype. ie,

inline int raiseClick(s32 x, s32y) { return this->SendMessage(CLICK,x,y); }

The compiler checks argument types, but should not generate any extra code.

The purist points out that using a switch statement with message codes like this is merely replacing C++’s vtables with your own implementation of inheritance. Any message you do not understand, you merely pass to your superclass. This is why Microsofts MFC was so simple for them to build - it essentially did exactly whats described here, translated C++ methods into corresponding Win32 SendMessage() calls.

Jeff on 2007-11-26 at 02:13 said:

Aarrgghhh, forgot the HTML. When I say “explict … implying a function” its supposed to say “implying an <stdarg> function”. You know, like printf(). Var args functions do not have to have a format statement associated with them, the function can decide how many arguments it has based on any input it likes, it could use NULL termination, or as we did, a message code indexing into a switch statement.

Jeff on 2007-11-26 at 02:17 said:

As to the graphics point, my preference would be to be passed a graphics port object that exposed real member methods, as opposed to static functions that take a port as an argument.

Imagine a different kind of graphics port that wrote its content to a printer. Hmmm, ok, that sounds a little freaky on a DS. How about, to a VNC device that happens to be at the other end of a wifi link? The draw operation would be passed across as raw arguments - you don’t want the static methods having to know such a thing existed, you want the graphics port to know that “its remote”.

Being more grounded in reality, you could debug this stuff by having a graphics port that not only did the draw, but also write its arguments to a log file via libfat.

Jeff on 2007-11-26 at 23:31 said:

I added some disassembly analysis of RTTI to defect 1826423 on SourceForge. I’m reasonably comfortable that it shows that RTTI is being slandered, at least on the NDS platform.