2010-01-19

A Bugfix Bonanza

If you have been following the comments for the previous few posts, or the forum, you’ll have seen work progressing at a frantic pace. Most of the thanks for this goes to Lakedaemon, who has been zapping bugs throughout Woopsi’s text rendering system. Working so quickly and not regularly blogging about it means that I’ve probably got a lot of changes to list here. Let’s see if I can collate them into some sort of order…

The WoopsiString class has seen a lot of changes. I’ve added indexOf(), lastIndexOf() and subString() methods. The first will find the first index of a particular character in the string and return its index. The second does the same thing, but in reverse - it finds the last index of a character. The last will return a pointer to a WoopsiString containing a subsection of the original string.

WoopsiStrings are no longer null-terminated. They already included a member variable that stores the string length, so appending a terminator was a waste of a byte.

The Text class’ wrap() function has had some long-standing bugs fixes. The strangest was probably an invalid comparison between a line index and a char index that was supposed to prevent the function from wrapping the entire text, but actually just screwed up the function most of the time, and made it slower the rest of the time. Not sure how that stayed in there for so long.

The next most significant bugfix was to the GraphicsPort’s clipScroll() method. This is used primarily in the ScrollingPanel gadget. It performs two functions. If it is possible to copy any of the existing visible region to the new location, the function copies it. This prevents redrawing, which is slow, if the required visual data is already available. Secondly, it remembers any rectangular regions that require redrawing and passes the list back to its caller. The ScrollingPanel uses the list to redraw any non-copied regions of itself.

The clipScroll() method worked perfectly if scrolling in just one plane, but as soon as horizontal and vertical movement was attempted simultaneously, it fell apart. Rectangular regions were missed out or copied to the wrong locations, particularly in the corners.

The function was originally tackling the problem backwards. It calculated the size of the source and destination rectangles then clipped those rectangles. However, this introduces two problems:

  • The rectangles could become different sizes;
  • The rectangles could acquire different offsets from their original locations.

Neither problem is too difficult to solve, but it does mean that if either problem arises, the function will not be able to copy the regions and must therefore fall back on the much slower redraw option.

The “correct” approach to the problem is to clip before calculating the sizes of the source and destination rectangles. That will ensure that neither of the two highlighted issues arises, which means that the copy can be used in preference to redrawing much more frequently.

I’ve finally got around to removing the glyphs from the Topaz and NewTopaz fonts. As the glyphs are now an entirely separate font, keeping that data in the bitmaps was a waste of space. Neither font requires colour now that the glyphs are gone, to I’ve converted Topaz from a Font to a MonoFont, and converted NewTopaz from a PackedFont16 to a PackedFont1. This has made both fonts considerably smaller.

Related to this, PackedFontBase::isCharBlank() returns the correct value (false) if the requested character is outside the range of the available bitmap data.

Dragging a background screen below the level of the topmost screen no longer crashes the system. The Screen class was attempting to copy a region with a negative height, which the DMA hardware really doesn’t like.

The FileRequester has had yet more fixes since the last post. When running in SDL mode, it now shows a dummy list of files and directories. I had intended to get it to show a real list of directories, but I ran into The Windows Problem - every major operating system but Windows has POSIX-compliant file system access. Unfortunately, of those major operating systems Windows is the most prevalent. I really couldn’t be bothered to write the POSIX code if most Woopsi developers are using Windows, and I couldn’t be bothered to write Windows code when I only run the SDL version in OSX. The compromise, which will work on all platforms, POSIX or not, was just to create the dummy list.

Finally, both the MultiLineTextBox and the standard TextBox respond to d-pad repeats. Holding down left or right on the d-pad when either of these two gadgets has focus and a visible cursor will cause the cursor to move repeatedly until the d-pad is released.

2008-10-09

Strings and Things

The Text class is fixed. It now wraps lines of text that are wider than the available display window but have no natural breaking points. The wrapping function was skipping over characters when attempting to force breaks into non-broken text and drifting off into memory after the end of the string.

Next up, there’s now a WoopsiString class. This wraps around a char array and provides all of the functionality I’ve been doing manually in the TextBox class - setting the string (and moving memory about), concatenating strings (and moving memory about), inserting one string into another (and moving memory about), and removing sections of a string (and moving- well, you get the idea).

I’ve taken the opportunity afforded by this rethink to improve the way the memory allocation is handled. Instead of blindly reallocating a new char array every time the string changes length, the new WoopsiString class attempts to work within the memory it has already allocated. For example, imagine we make this string:

WoopsiString* s = new WoopsiString("abcd");

Now we append another string to end:

s->append("efgh");

We’ve got no choice here but to expand the memory used, which means allocating a new char array of length 9 (initial string length plus second string length, plus terminator) and copying the two strings into it. However, if we then truncate the string there’s no reason for us to allocate a smaller array:

s->remove(2);   // Remove chars from index 2 until end of string

All we really need to do here in order to truncate the string from index 2 onwards is put a terminating character in the 3rd array element. We’ve then got 6 empty slots in the char array. Now, should we append anything else to our string, we can either fill in the empty slots (if there are enough slots) and save time, or we can go through the memory allocation and string copy process again (if there aren’t enough slots):

s->append("xyz");

In this instance, we can append “xyz” to the “ab” we’ve got stored in the WoopsiString s without allocating any new memory.

I imagine every string class ever written does something like this, so it’s not an amazing innovation, but it’s a definite improvement over the char array mangling and memory fragmenting that the TextBox was doing before.

There’s no change to the way the TextBox interfaces with the outside world; that’s still all done with char arrays or chars. This is just an internal change.

One more class done today - Label. The Label is a borderless, read-only (from the user’s point of view) gadget that allows text to be put onto the screen. Basically, it’s the TextBox with GADGET_BORDERLESS set in its flags minus the cursor stuff I’ve been adding to TextBox. TextBox now inherits from Label.