2007-12-06

Pondering Woopsi Applications

I’ve been thinking about the ways that Woopsi can be used when building applications. I’ve got some crazy ideas about task managers and base task classes, but I think that would have nasty flaws - it would add unnecessary complication and it would unavoidably prescribe the way applications were built. This is one of the major things that bugs me when I’m forced to use the latest .NET framework-of-the-week, designed by someone who thinks everything should still be done as it was in VB3. Smarter people than me may want to use it in smarter ways, and if I force them to use it in dumb ways because I couldn’t come up with anything better they just won’t use it. Woopsi would stop being a GUI toolkit and become an application framework.

I’ve got my own .NET framework-of-the-week to upload here at some point. Everyone - point and laugh at the hypocrite!

The approach I’ve come up with instead is just to suggest an application layout, but allow the developer to do whatever he wants with the GUI. If someone can come up with a better way to do things, or if a particular circumstance requires that Woopsi be used in an unusual way, the programmer can just ignore the guidelines. However, anyone who doesn’t really know where to start will undoubtedly be grateful for some guidance. I’ll flesh this out some more in the documentation once everything’s finished (and try to keep the language simple and less scatty than my blog posts, which are generally just thoughts splurged into a text editor as they arise), but here’s a few initial ideas.

Imagine you want to create a DSOrganiser-style application. Breaking this down into components gives us a single application (an organiser) that contains a multitude of smaller subtasks, such as a calendar and a text viewer. We need an “Organiser” class that controls the rest of the system, and a set of classes for each subtask.

The Organiser class needs to be able to create and delete instances of the sub-tasks as required. It’s probably easiest to think of it as a combined menu system and task manager. It would look something like this (near-pseudocode):


class Organiser : EventHandler
    Calendar* calendar
    TextEditor* editor
    WebBrowser* browser
    Woopsi* woopsi
    Screen* screen
    Window* window

    void createMenu()
    void closeTask(identifier)
    void handleClick(EventArgs event)
end

The Organiser contains pointers to each of its subtasks. Its “createMenu” function creates a new screen and a window with a bank of buttons - each button represents one of the subtasks that the Organiser contains. When clicked, each button runs the “handleClick” function. This function works out which button was clicked, by checking the EventArgs struct, and creates an instance of that subtask.

When a subtask’s window closes, the subtask’s class calls the Organiser’s “closeTask” function. This can either do nothing (assuming we want the task to remain in memory) or add the task to a deletion queue (aha - this is where the idea for a task manager class/base task classes came from) to be deleted later. The task can’t delete itself, obviously, or the developer ends up with the same problem I had when gadgets were trying to delete themselves and were breaking the call stack.

Each subtask automatically creates its own window and handles its own initalisation; it also sets the window’s EventHandler to the subtask class, like this:


class SubTask : EventHandler
    SubTask()
        window = new Window()
        window->setEventHandler(this)
    end
end

The main.cpp file would look something like this:


main()
    create woopsi
    create main screen
    create instance of Organiser class

    loop
        run woopsi
    end
end

If I did go down the route of creating a task manager class, I could give Woopsi an instance of that class by default. The Organiser could be added to Woopsi’s task list and (this is the smart bit) closing that task would close all of the Organiser’s child tasks. Also (and this is another smart bit), since the Organiser creates its own screen and is essentially self-contained within a set of classes, it is a modular component. If someone wants to include your BSD-licenced (hint hint) organiser within their suite of simple window-based games, they just need to add your classes to their code and register the task with Woopsi’s task manager. They’ve instantly added a huge amount of new functionality to their own ROM.

Of course, this falls apart if the ROM exceeds 4MB or uses all of the DS’ RAM. At this point, I start thinking about time slicing and requesting memory from Woopsi, and decide that WoopsiOS is going a little too far.

Comments

Jeff on 2007-12-06 at 20:06 said:

You’re hacking into my system, aren’t you?

;-)

I agree whole-heartedly with the notion that you should not lock down user applications into your structures. The key difficulty I can see with offering tons of optional functionality, is that you have it in .cpp files - the standard makefiles will build everything in the Woopsi directory, regardless of which things are eventually linked. At the moment, it doesn’t hurt, but it will.

Moving to putting your simpler classes into .h files will help some things but not others.

The screen decorations as seperate classes, whilst easier to implement, does mean that creating my app without your decorations, it still has to link them in (because its just a bit-flag to select/remove them so they are still referenced from the base screen. I’m not sure of the most elegant solution to this particular problem, I suspect its “don’t make them switchable” which is a large loss - perhaps screen would need be be factored down even more.

My experience so far is that the remaining annoying gotcha is the requrement to pass the fonts to the Screen() constructor. I’m thinking that creating/deleting Screens on the fly is the simplest approach to “modal dialogs”. For example, lets say I have a screen with a field for user name. I click in it. The screen pushes itself to the hardware-top, creates a new input screen on the bottom (which does not have depth switching, etc), wire in as the new screens event handler, and let the input screen present a keyboard , and the user then continues. The input screen will manage the input until the user commits or cancells at which point it notifies the original screen (via an event) that its finished. The original screen switches itself back to the bottom and deletes the input screen.

This allows you to only keep the screens/windows/gadgets in memory as long as they are needed, whilst giving the user maximum ease during data entry. The keyboard screen can even notify the original window key-by-key if need be so that dynamic updates could happen.

The problem with all this has been that the “current font” information is not something that is easily available throughout the application. Again, my clumsy solution has to be to introduce my own subclass of Woopsi (called WoopsiApplication) which exposes the TextFont() and GlyphFont() - I also have a global called woopsiApplication which is the currently running instance of the application that the rest of the app can access if need be. This is essentially what Win32 MFC and OSX Cocoa end up doing as well, though Cocoa is a bit more elegant since it happens through a message still.

I could have added a static variable to WoopsiApplication instead, but thats slower to type. And I could overcome that too…

class WoopsiApplication : Woopsi { static WoopsiApplication *woopsiApplication; … WoopsiApplication() : Woopsi() { woopsiApplication = this;

Jeff on 2007-12-06 at 20:09 said:

[damn, hit return to soon, continuing]

define woopsiApplication WoopsiApplication::woopsiApplication

It might be useful to add something similiar to the base-class, in my opinion. I’ve already pushed the control logic back into that class:

pragma once

include

class WoopsiApplication : public Woopsi { public: // object construction inline WoopsiApplication() { PA_Init(); // Initializes PA_Lib PA_InitVBL(); // Initializes a standard VBL

            // Initialise the screens - Woopsi needs 16bit mode on both
            PA_Init16bitBg(0, 0);
            PA_Init16bitBg(1, 0);
    }

    virtual inline ~WoopsiApplication()
    {
    }

    // called once at application startup
    virtual inline void Startup(void) {}

    // main application loop - runs forever, or until somethinng
    // makes it stop
    virtual inline void RunLoop(void) {
            // Infinite loop to keep the program running
            while (1) {
                    this->play();
                    PA_WaitForVBL();
            }
    }

    // called once at application shutdown
    virtual inline void Shutdown(void)
    {
    }

};

so that my main looks like this:

int main() { // Create woopsi application OrganizerApplication theApp; theApp.Startup(); // start it up theApp.draw(); // ensure physical screen is up to date theApp.RunLoop(); // let the event loop run theApp.Shutdown(); // and we are done

    return 0;

}