Made a bit of progress with Woopsi (source in SVN, as usual). First of all, the Window class no longer includes all those +/- 1 values when handling border sizes. Next, I’ve removed any calls to Gadget::getType() by refactoring the need for it out. Much tidier.
There are a couple of places where gadgets make unchecked assumptions about their parent’s/children’s type and cast to that type. The ScreenFlipGadget assumes that its parent is a screen, for example, and Woopsi assumes that its children are also screens. However, this isn’t too much of a problem, as Woopsi should only ever contain classes that are (or inherit from) screens, and the ScreenFlipGadget should only ever be a child of a screen.
I’ve added the decoration code, too. The Gadget class now includes a “decoration” flag in its Flags struct. If this is set to “true”, the gadget is automatically inserted into a reserved section at the start of the parent gadget’s child vector when it is added to that gadget. When depth swapping its children, a gadget ignores anything in the decorations section of the vector.
The reserved decorations section isn’t really partitioned off from the rest of the vector; it’s implemented by remembering how many decoration gadgets there are in the vector. Non-decoration gadgets are always added to the vector either at the end or at _gadgets[_gadgets.begin() + _decorationCount]). Decoration gadgets are always added at same location or at the start of the vector, at which point the _decorationCount value is increased by one.
This has two main benefits. I no longer need to worry about swapping depths with something that shouldn’t be depth-swapped because it’s a part of the containing gadget. Secondly, I didn’t need to implement a separate _decorations vector, which would have added complications all over the place.
The SuperBitmap class now contains an “isDecoration” parameter in its constructor, which controls whether it is added as a standard SuperBitmap gadget or as a decoration (in which case it is borderless and treated as a window or screen background image).
So, nothing particularly exciting, but it tidies up some of the tedious stuff that needed to be sorted out first.
Oh, whilst I’m on, some thoughts about the GraphicsPort object:
Implementing something like this will actually be rather difficult. It’s easy for OSX and Windows because of the way I assume they work with their windows - the same way the Amiga did. They buffer their windows in memory and draw them to the screen as needed, like the SuperBitmap. Woopsi doesn’t do this - it draws everything from scratch as and when it is necessary. Drawing to the SuperBitmap is easy. It works like this:
- Constructor called
- Reserve a region of RAM
- Draw a line to the reserved RAM
- Main draw function called
- Calculate visible portions of SuperBitmap
- Copy visible portions of reserved RAM to screen
If we try to do the same thing without buffering the drawing somewhere else first, we get this for every primitive drawing function:
- Draw function called
- Calculate visible portions of GraphicsPort
- Draw visible portions of primitive to screen
A rectangle is built from 4 lines. That means we’d need to re-calculate the visible portions of the Graphics port 4 times per rectangle. As calculating a rectangle’s visible regions is the most computationally-expensive algorithm in Woopsi, this is incredibly inefficient. The only way around is would be to have a “prepareToDraw()” function that caches the visible regions of the GraphicsPort class before any other drawing functions are called. Either that, or each primitive drawing command checks whether a variable (call it “_preparedToDraw”) is true before drawing, and if not runs the “prepareToDraw()” function before drawing anything. The variable and cache would need to be flushed out either every VBL (dumb solution) or any time anything on the screen shifts around and invalidates its cache (smart solution, but horribly difficult to implement). Raises an interesting question - should Woopsi’s gadgets maintain a cache of their visible portions, and only recalculate when they have been invalidated by something higher up the z-order moving/resizing/closing? I think that’s a more Windows-style solution. More memory usage, though.
I realised this a while ago (here, but it looks like I didn’t explain the reasons), which is why I decided to rename all of the drawing functions within the gadgets to drawIntXXX(). The internal drawing functions within each gadget shouldn’t be used without a valid clipping rect as they will otherwise draw outside the boundaries of the containing gadget, and generating this clipping rect is too expensive to do for every draw command. Drawing using these commands should only be done within the “draw(Rect clipRect)” function, as that is automatically passed the correct clipping rect for each visible portion of the gadget.
Caching visible portions of gadgets makes so much sense that I can’t ignore it. It’ll need to work like this:
- Gadget moves/resizes/closes
- Calculates regions that have become visible/overlapped
- Sends those newly invalidated regions to all siblings below it in the z-order
- Siblings remove overlapped regions from their cache and add newly visible regions (I’ve got to add rectangle merging here or I’ll end up with massive fragmentation problems). As each gadget adds the overlapped regions to its cache they get removed from the list being sent through the vector, and the function ends when the vector is empty (same as the current drawing system)
That should make the drawing routines even faster than they already are; it’ll also mean that the GraphicsPort class can be implemented incredibly easily.
Now, is there anything I’ve overlooked? Will screen dragging break the caching system unless I’ve stored the rectangles relative to the gadget? Does this imply lots of screen->gadget space conversion? Hmmm…
Another thought - what if I just send an “invalidated” command to any siblings within the regions invalidated by a higher gadget moving/etc, which empties those gadgets’ cache? When they attempt to redraw, they rebuild their cache using the existing code. No need for huge changes and complicated new functionality. Not as quick to run, but so much easier to code!