Bugbox Multi-Threaded

Download the BugboxMultiThreaded EXE (36KB) in compressed ZIP format.

Download the BugboxMultiThreaded project files (32KB) in compressed ZIP format.
   
The multi-threaded solution
This solution uses the idea of live objects. Not live because living creatures are being simulated, but rather because the objects perform their functions indepedendently and automatically instead of being passive pieces of code waiting to be called which objects generally are. I have written an abstract base class, CLiveObject, which creates a new thread in its constructor. The new thread loops in the MessageLoop member function until it receives a WM_QUIT message, upon which the thread ends. Any WM_TIMER messages are routed to the OnTimer method. As this is a pure virtual method on CLiveObject, a further class has to be derived from it and OnTimer implemented. Immediately before the thread ends, it calls the virtual AnyLastRequests method which, if it is not overriden, does nothing.

For this version of Bugbox I derived CBug from CLiveObject and left the implementation virtually unchanged except for synchronising access to the window's Device Context. There are two additional small but important tasks for any class which derives from CLiveObject, and they are documented below in this copy of the header file for the class. The Box remains largely the same, except that its work gets easier. The Box no longer creates a timer so it doesn't have to handle timer messages; it has only to construct its Bugs and leave them to get on with living. When asked to kill them, the Box deletes the Bugs and their destructor takes care of terminating the threads with a WM_QUIT message. The same thing could be done with an automatic (stack) Bug object by allowing it to go out of scope.

WinZip® brings the convenience of Windows to the use of Zip files and other compression formats. If you don't have "the most celebrated shareware app in the history of computing" already, use the above link to download it.
   
LiveObject.h
// LiveObject.h: interface for the CLiveObject class.
//
// class CLiveObject:
// Abstract base class from which live objects may be derived.

// Thread creation:
// CLiveObject::CLiveObject calls the Win32 API ::CreateThread to create a
// new suspended thread. The thread function is the static member function
// CLiveObject::ThreadFunc, and the argument passed is a pointer to the object
// being constructed (the 'this' pointer) cast to a pointer to void. In order
// that MessageLoop calls no virtual function until the entire object is
// constructed, the constructor of the most derived class must finish with this
// line:
// ::ResumeThread(m_dwThreadHandle); 

// Thread end:
// CLiveObject::EndThread ends the thread created in the constructor. This
// method *must* be called before CLiveObject::~CLiveObject is called so either
// have the creator of the live object call it before detroying it or, more
// conveniently, call it from the destructor of the most-derived class. It
// cannot be called from CLiveObject::~CLiveObject as the object will be
// partially destructed by then and so the still-running MessageLoop will fail.

// CLiveObject::ThreadFunc:
// The thread function's job is simply to take the 'this' pointer passed in the
// thread argument, cast it to a CLiveObject* and call the non-static member
// function CLiveObject::MessageLoop on it. Only when MessageLoop returns will
// ThreadFunc end and, consequently, the thread itself.

// CLiveObject::MessageLoop:
// The main message loop of the object. Before beginning the loop a timer is
// set. Each WM_TIMER message received by the thread causes the OnTimer method
// to be called. Its loop ends when a WM_QUIT message is posted to the thread.

// CLiveObject::OnTimer:
// A pure virtual function intended to be overriden by a base class. It is
// pure so that CLiveObject is abstract and cannot accidentally be instantiated
//  - CLiveObject is useless as it is. It also serves to define the interface
// for a CLiveObject-derived class. Derived classes should override OnTimer and
// perform arbitrarily small discrete changes to their state in it. How small
// depends on the timer frequency. The derived class may control this by
// passing a value to CLiveObject's constructor.

// CLiveObject::AnyLastRequests:
// A virtual function overridable by the derived class in order to do final
// cleanup. Called immediately before the thread but after the message loop
// has stopped ends.
//////////////////////////////////////////////////////////////////////

class __declspec(novtable) CLiveObject  
{
protected:
    HANDLE m_dwThreadHandle;
    DWORD m_dwThreadId;
    UINT m_nTimerInterval;
    
    virtual BOOL OnTimer() = 0;
    virtual BOOL AnyLastRequests(){return FALSE;};
    static DWORD WINAPI ThreadFunc(LPVOID pLiveObject);
    DWORD MessageLoop();
public:
    CLiveObject(UINT nTimerInterval = 10);
    virtual ~CLiveObject(){};
    void EndThread();
};
 
   
Conclusion
By adjusting default values in the CBox::AddBugs member function you can make the bugs more erratic in their walking and quicker to rotate whilst obstructed (numbr of radians), or change the size of their stride (number of pixels). For my taste, though, the default motion does look pretty much like that of living insects. Why not download the executable and see if you agree. Or get the project files for further study. You'll notice that CPU usage doesn't depend only on the number of Bugs in the Box, but the window area in use by them. Add a hundred Bugs and resize the window into a fairly small square about twice the width of the menu bar text. Leave it like that until Task Manager's CPU usage levels out at close to zero. Then maximise the window and keep an eye on the Task Manager. As the Bugs break free from the scrum you should see CPU usage rise steadily and peak when the new area is evenly populated. So the area of device context being rendered onto is a big factor. It isn't simply the size of the window: squash a hundred bugs as small as they'll go with Bug collision detection on and CPU usage is zero. Maximise the window and the Bugs are so too tight to break free and the CPU is still at zero.

Compared to the single-threaded version, the CPU is now being much better utilised and overall performance is greatly improved. Up to a limit, Bugs can continue to be added without slowing them all down. The limit comes when it is no longer possible to render all the Bugs in the interval between timer ticks.

Although this solution is somewhat more successful than the single-threaded one, some problems remain. The CBug class contains the rendering code which means that the physical appearance of the Bug is coupled to the class - i.e. it would not be easy to reuse the CBug code from another Box type which wanted CBug to calculate moves but the Box wanted to render its own representation of the Bug. CBug is also dependent on the physical display device used. Probably least important is the fact that we cannot perform double-buffered rendering with the application at present. This technique uses one framebuffer to present to the user while another is being rendered into by the application, then the buffers are swapped. This prevents the user seeing the frame being drawn into - a situation which can cause flickering, shearing and other undesirable visual artifacts. As it stands, this solution does not lend itself to double-buffering as the rendering is being carried out asynchronously by many threads. There is no sense of the location of all the Bugs at any given moment with respect to any given single thread, and it would have to be a single thread which peforms a buffer swap. What is needed is a single thread to perform the rendering, and this is what will be looked at in the next solution (Bugbox Multi-Threaded2).

 
last updated: 5-oct-01